From 2f3bb6f4481c86334590c14a1eadd6567a624836 Mon Sep 17 00:00:00 2001 From: Kanishka Date: Mon, 29 May 2023 14:45:13 -0600 Subject: [PATCH 01/85] feat/scale: add `BitVec` (#3253) --- pkg/scale/README.md | 33 +++++++ pkg/scale/bitvec.go | 87 ++++++++++++++++++ pkg/scale/bitvec_test.go | 186 +++++++++++++++++++++++++++++++++++++++ pkg/scale/decode.go | 30 +++++++ pkg/scale/decode_test.go | 24 +++++ pkg/scale/encode.go | 19 ++++ pkg/scale/encode_test.go | 56 +++++++++++- pkg/scale/errors.go | 1 + 8 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 pkg/scale/bitvec.go create mode 100644 pkg/scale/bitvec_test.go diff --git a/pkg/scale/README.md b/pkg/scale/README.md index edb7f9db67..253b3574bd 100644 --- a/pkg/scale/README.md +++ b/pkg/scale/README.md @@ -77,6 +77,39 @@ SCALE uses a compact encoding for variable width unsigned integers. | `Compact` | `uint` | | `Compact` | `*big.Int` | +### BitVec + +SCALE uses a bit vector to encode a sequence of booleans. The bit vector is encoded as a compact length followed by a byte array. +The byte array is a sequence of bytes where each bit represents a boolean value. + +**Note: This is a work in progress.** +The current implementation of BitVec is just bare bones. It does not implement any of the methods of the `BitVec` type in Rust. + +```go +import ( + "fmt" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +func ExampleBitVec() { + bitvec := NewBitVec([]bool{true, false, true, false, true, false, true, false}) + bytes, err := scale.Marshal(bitvec) + if err != nil { + panic(err) + } + + var unmarshaled BitVec + err = scale.Unmarshal(bytes, &unmarshaled) + if err != nil { + panic(err) + } + + // [true false true false true false true false] + fmt.Printf("%v", unmarshaled.Bits()) +} +``` + + ## Usage ### Basic Example diff --git a/pkg/scale/bitvec.go b/pkg/scale/bitvec.go new file mode 100644 index 0000000000..2ebedee8f6 --- /dev/null +++ b/pkg/scale/bitvec.go @@ -0,0 +1,87 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package scale + +const ( + // maxLen equivalent of `ARCH32BIT_BITSLICE_MAX_BITS` in parity-scale-codec + maxLen = 268435455 + // byteSize is the number of bits in a byte + byteSize = 8 +) + +// BitVec is the implementation of the bit vector +type BitVec struct { + bits []bool +} + +// NewBitVec returns a new BitVec with the given bits +// This isn't a complete implementation of the bit vector +// It is only used for ParachainHost runtime exports +// TODO: Implement the full bit vector +// https://github.com/ChainSafe/gossamer/issues/3248 +func NewBitVec(bits []bool) BitVec { + return BitVec{ + bits: bits, + } +} + +// Bits returns the bits in the BitVec +func (bv *BitVec) Bits() []bool { + return bv.bits +} + +// Bytes returns the byte representation of the BitVec.Bits +func (bv *BitVec) Bytes() []byte { + return bitsToBytes(bv.bits) +} + +// Size returns the number of bits in the BitVec +func (bv *BitVec) Size() uint { + return uint(len(bv.bits)) +} + +// bitsToBytes converts a slice of bits to a slice of bytes +// Uses lsb ordering +// TODO: Implement msb ordering +// https://github.com/ChainSafe/gossamer/issues/3248 +func bitsToBytes(bits []bool) []byte { + bitLength := len(bits) + numOfBytes := (bitLength + (byteSize - 1)) / byteSize + bytes := make([]byte, numOfBytes) + + if len(bits)%byteSize != 0 { + // Pad with zeros to make the number of bits a multiple of byteSize + pad := make([]bool, byteSize-len(bits)%byteSize) + bits = append(bits, pad...) + } + + for i := 0; i < bitLength; i++ { + if bits[i] { + byteIndex := i / byteSize + bitIndex := i % byteSize + bytes[byteIndex] |= 1 << bitIndex + } + } + + return bytes +} + +// bytesToBits converts a slice of bytes to a slice of bits +func bytesToBits(b []byte, size uint) []bool { + var bits []bool + for _, uint8val := range b { + end := size + if end > byteSize { + end = byteSize + } + size -= end + + for j := uint(0); j < end; j++ { + bit := (uint8val>>j)&1 == 1 + bits = append(bits, bit) + } + } + + return bits +} diff --git a/pkg/scale/bitvec_test.go b/pkg/scale/bitvec_test.go new file mode 100644 index 0000000000..c0fde7f3b6 --- /dev/null +++ b/pkg/scale/bitvec_test.go @@ -0,0 +1,186 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package scale + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/stretchr/testify/require" +) + +func TestBitVec(t *testing.T) { + t.Parallel() + tests := []struct { + name string + in string + wantBitVec BitVec + wantErr bool + }{ + { + name: "empty_bitvec", + in: "0x00", + wantBitVec: NewBitVec(nil), + wantErr: false, + }, + { + name: "1_byte", + in: "0x2055", + wantBitVec: NewBitVec([]bool{true, false, true, false, true, false, true, false}), + wantErr: false, + }, + { + name: "4_bytes", + in: "0x645536aa01", + wantBitVec: NewBitVec([]bool{ + true, false, true, false, true, false, true, false, + false, true, true, false, true, true, false, false, + false, true, false, true, false, true, false, true, + true, + }), + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resultBytes, err := common.HexToBytes(tt.in) + require.NoError(t, err) + + bv := NewBitVec(nil) + err = Unmarshal(resultBytes, &bv) + require.NoError(t, err) + + require.Equal(t, tt.wantBitVec.Size(), bv.Size()) + require.Equal(t, tt.wantBitVec.Size(), bv.Size()) + + b, err := Marshal(bv) + require.NoError(t, err) + require.Equal(t, resultBytes, b) + }) + } +} + +func TestBitVecBytes(t *testing.T) { + t.Parallel() + tests := []struct { + name string + in BitVec + want []byte + wantErr bool + }{ + { + name: "empty_bitvec", + in: NewBitVec(nil), + want: []byte(nil), + wantErr: false, + }, + { + name: "1_byte", + in: NewBitVec([]bool{true, false, true, false, true, false, true, false}), + want: []byte{0x55}, + wantErr: false, + }, + { + name: "4_bytes", + in: NewBitVec([]bool{ + true, false, true, false, true, false, true, false, + false, true, true, false, true, true, false, false, + false, true, false, true, false, true, false, true, + true, + }), + want: []byte{0x55, 0x36, 0xaa, 0x1}, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, tt.in.Bytes()) + }) + } +} + +func TestBitVecBytesToBits(t *testing.T) { + t.Parallel() + tests := []struct { + name string + in []byte + want []bool + wantErr bool + }{ + { + name: "empty", + in: []byte(nil), + want: []bool(nil), + wantErr: false, + }, + { + name: "1_byte", + in: []byte{0x55}, + want: []bool{true, false, true, false, true, false, true, false}, + wantErr: false, + }, + { + name: "4_bytes", + in: []byte{0x55, 0x36, 0xaa, 0x1}, + want: []bool{ + true, false, true, false, true, false, true, false, + false, true, true, false, true, true, false, false, + false, true, false, true, false, true, false, true, + true, false, false, false, false, false, false, false, + }, + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, bytesToBits(tt.in, uint(len(tt.in)*byteSize))) + }) + } +} + +func TestBitVecBitsToBytes(t *testing.T) { + t.Parallel() + tests := []struct { + name string + in []bool + want []byte + wantErr bool + }{ + { + name: "empty", + in: []bool(nil), + want: []byte{}, + wantErr: false, + }, + { + name: "1_byte", + in: []bool{true, false, true, false, true, false, true, false}, + want: []byte{0x55}, + wantErr: false, + }, + { + name: "4_bytes", + in: []bool{ + true, false, true, false, true, false, true, false, + false, true, true, false, true, true, false, false, + false, true, false, true, false, true, false, true, + true, + }, + want: []byte{0x55, 0x36, 0xaa, 0x1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, bitsToBytes(tt.in)) + }) + } +} diff --git a/pkg/scale/decode.go b/pkg/scale/decode.go index 45a527f0b8..77a1476551 100644 --- a/pkg/scale/decode.go +++ b/pkg/scale/decode.go @@ -114,6 +114,8 @@ func (ds *decodeState) unmarshal(dstv reflect.Value) (err error) { err = ds.decodeBigInt(dstv) case *Uint128: err = ds.decodeUint128(dstv) + case BitVec: + err = ds.decodeBitVec(dstv) case int, uint: err = ds.decodeUint(dstv) case int8, uint8, int16, uint16, int32, uint32, int64, uint64: @@ -768,3 +770,31 @@ func (ds *decodeState) decodeUint128(dstv reflect.Value) (err error) { dstv.Set(reflect.ValueOf(ui128)) return } + +// decodeBitVec accepts a byte array representing a SCALE encoded +// BitVec and performs SCALE decoding of the BitVec +func (ds *decodeState) decodeBitVec(dstv reflect.Value) error { + var size uint + if err := ds.decodeUint(reflect.ValueOf(&size).Elem()); err != nil { + return err + } + + if size > maxLen { + return fmt.Errorf("%w: %d", errBitVecTooLong, size) + } + + numBytes := (size + (byteSize - 1)) / byteSize + b := make([]byte, numBytes) + _, err := ds.Read(b) + if err != nil { + return err + } + + bitvec := NewBitVec(bytesToBits(b, size)) + if len(bitvec.bits) > int(size) { + return fmt.Errorf("bitvec length mismatch: expected %d, got %d", size, len(bitvec.bits)) + } + + dstv.Set(reflect.ValueOf(bitvec)) + return nil +} diff --git a/pkg/scale/decode_test.go b/pkg/scale/decode_test.go index 3309c58a9b..68a1a4b767 100644 --- a/pkg/scale/decode_test.go +++ b/pkg/scale/decode_test.go @@ -9,6 +9,8 @@ import ( "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/assert" @@ -251,6 +253,28 @@ func Test_decodeState_decodeMap(t *testing.T) { } } +func Test_decodeState_decodeBitVec(t *testing.T) { + for _, tt := range bitVecTests { + t.Run(tt.name, func(t *testing.T) { + dst := reflect.New(reflect.TypeOf(tt.in)).Elem().Interface() + if err := Unmarshal(tt.want, &dst); (err != nil) != tt.wantErr { + t.Errorf("decodeState.unmarshal() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(dst, tt.in) { + t.Errorf("decodeState.unmarshal() = %v, want %v", dst, tt.in) + } + }) + } +} + +func Test_decodeState_decodeBitVecMaxLen(t *testing.T) { + t.Parallel() + bitvec := NewBitVec(nil) + maxLen10 := []byte{38, 0, 0, 64, 0} // maxLen + 10 + err := Unmarshal(maxLen10, &bitvec) + require.Error(t, err, errBitVecTooLong) +} + func Test_unmarshal_optionality(t *testing.T) { var ptrTests tests for _, t := range append(tests{}, allTests...) { diff --git a/pkg/scale/encode.go b/pkg/scale/encode.go index d312b85f91..5a07919136 100644 --- a/pkg/scale/encode.go +++ b/pkg/scale/encode.go @@ -73,6 +73,8 @@ func (es *encodeState) marshal(in interface{}) (err error) { err = es.encodeBigInt(in) case *Uint128: err = es.encodeUint128(in) + case BitVec: + err = es.encodeBitVec(in) case []byte: err = es.encodeBytes(in) case string: @@ -423,3 +425,20 @@ func (es *encodeState) encodeUint128(i *Uint128) (err error) { err = binary.Write(es, binary.LittleEndian, padBytes(i.Bytes(), binary.LittleEndian)) return } + +// encodeBitVec encodes a BitVec +func (es *encodeState) encodeBitVec(bitvec BitVec) (err error) { + if bitvec.Size() > maxLen { + err = fmt.Errorf("%w: %d", errBitVecTooLong, bitvec.Size()) + return + } + + err = es.encodeUint(bitvec.Size()) + if err != nil { + return + } + + data := bitvec.Bytes() + _, err = es.Write(data) + return +} diff --git a/pkg/scale/encode_test.go b/pkg/scale/encode_test.go index 92de411919..2d8c578671 100644 --- a/pkg/scale/encode_test.go +++ b/pkg/scale/encode_test.go @@ -225,7 +225,7 @@ var ( uintTests = tests{ { name: "uint(1)", - in: int(1), + in: 1, want: []byte{0x04}, }, { @@ -938,6 +938,29 @@ var ( }, } + bitVecTests = tests{ + { + name: "BitVec{Size:__0,_Bits:__nil}", + in: NewBitVec(nil), + want: []byte{0}, + }, + { + name: "BitVec{Size:_8}", + in: NewBitVec([]bool{true, false, true, false, true, false, true, false}), + want: []byte{0x20, 0x55}, + }, + { + name: "BitVec{Size:_25}", + in: NewBitVec([]bool{ + true, false, true, false, true, false, true, false, + false, true, true, false, true, true, false, false, + false, true, false, true, false, true, false, true, + true, + }), + want: []byte{0x64, 0x55, 0x36, 0xaa, 0x1}, + }, + } + allTests = newTests( fixedWidthIntegerTests, variableWidthIntegerTests, stringTests, boolTests, structTests, sliceTests, arrayTests, @@ -1172,6 +1195,37 @@ func Test_encodeState_encodeMap(t *testing.T) { } } +func Test_encodeState_encodeBitVec(t *testing.T) { + for _, tt := range bitVecTests { + t.Run(tt.name, func(t *testing.T) { + buffer := bytes.NewBuffer(nil) + es := &encodeState{ + Writer: buffer, + fieldScaleIndicesCache: cache, + } + if err := es.marshal(tt.in); (err != nil) != tt.wantErr { + t.Errorf("encodeState.encodeBitVec() error = %v, wantErr %v", err, tt.wantErr) + } + if !reflect.DeepEqual(buffer.Bytes(), tt.want) { + t.Errorf("encodeState.encodeBitVec() = %v, want %v", buffer.Bytes(), tt.want) + } + }) + } +} + +func Test_encodeState_encodeBitVecMaxLen(t *testing.T) { + t.Parallel() + + var bits []bool + for i := 0; i < maxLen+1; i++ { + bits = append(bits, true) + } + + bitvec := NewBitVec(bits) + _, err := Marshal(bitvec) + require.Error(t, err, errBitVecTooLong) +} + func Test_marshal_optionality(t *testing.T) { var ptrTests tests for i := range allTests { diff --git a/pkg/scale/errors.go b/pkg/scale/errors.go index 5b317979dd..9930cebfbc 100644 --- a/pkg/scale/errors.go +++ b/pkg/scale/errors.go @@ -13,6 +13,7 @@ var ( errUnsupportedOption = errors.New("unsupported option") errUnknownVaryingDataTypeValue = errors.New("unable to find VaryingDataTypeValue with index") errUint128IsNil = errors.New("uint128 in nil") + errBitVecTooLong = errors.New("bitvec too long") ErrResultNotSet = errors.New("result not set") ErrResultAlreadySet = errors.New("result already has an assigned value") ErrUnsupportedVaryingDataTypeValue = errors.New("unsupported VaryingDataTypeValue") From f00185458adfd66741eb9927ce3fd12c70f7a239 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 8 Jun 2023 10:30:49 -0400 Subject: [PATCH 02/85] feat(erasure_coding): introduce erasure coding for PoV Distributor (#3281) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Eclésio Junior Co-authored-by: Edward Mack --- go.mod | 1 + go.sum | 2 + lib/erasure/erasure.go | 70 +++++++++++++++++++ lib/erasure/erasure_test.go | 132 ++++++++++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 lib/erasure/erasure.go create mode 100644 lib/erasure/erasure_test.go diff --git a/go.mod b/go.mod index c0f4b38503..987678a1c0 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/ipfs/go-ds-badger2 v0.1.3 github.com/jpillora/ipfilter v1.2.9 github.com/klauspost/compress v1.16.7 + github.com/klauspost/reedsolomon v1.11.8 github.com/libp2p/go-libp2p v0.31.0 github.com/libp2p/go-libp2p-kad-dht v0.25.0 github.com/minio/sha256-simd v1.0.1 diff --git a/go.sum b/go.sum index b371295068..765ad6150b 100644 --- a/go.sum +++ b/go.sum @@ -471,6 +471,8 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= +github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/lib/erasure/erasure.go b/lib/erasure/erasure.go new file mode 100644 index 0000000000..7a23841259 --- /dev/null +++ b/lib/erasure/erasure.go @@ -0,0 +1,70 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package erasure + +import ( + "bytes" + "errors" + "fmt" + + "github.com/klauspost/reedsolomon" +) + +// ErrNotEnoughValidators cannot encode something for zero or one validator +var ErrNotEnoughValidators = errors.New("expected at least 2 validators") + +// ObtainChunks obtains erasure-coded chunks, divides data into number of validatorsQty chunks and +// creates parity chunks for reconstruction +func ObtainChunks(validatorsQty int, data []byte) ([][]byte, error) { + recoveryThres, err := recoveryThreshold(validatorsQty) + if err != nil { + return nil, err + } + enc, err := reedsolomon.New(validatorsQty, recoveryThres) + if err != nil { + return nil, fmt.Errorf("creating new reed solomon failed: %w", err) + } + shards, err := enc.Split(data) + if err != nil { + return nil, err + } + err = enc.Encode(shards) + if err != nil { + return nil, err + } + + return shards, nil +} + +// Reconstruct the missing data from a set of chunks +func Reconstruct(validatorsQty, originalDataLen int, chunks [][]byte) ([]byte, error) { + recoveryThres, err := recoveryThreshold(validatorsQty) + if err != nil { + return nil, err + } + + enc, err := reedsolomon.New(validatorsQty, recoveryThres) + if err != nil { + return nil, err + } + err = enc.Reconstruct(chunks) + if err != nil { + return nil, err + } + buf := new(bytes.Buffer) + err = enc.Join(buf, chunks, originalDataLen) + return buf.Bytes(), err +} + +// recoveryThreshold gives the max number of shards/chunks that we can afford to lose and still construct +// the full initial data. Total number of chunks will be validatorQty + recoveryThreshold +func recoveryThreshold(validators int) (int, error) { + if validators <= 1 { + return 0, ErrNotEnoughValidators + } + + needed := (validators - 1) / 3 + + return needed + 1, nil +} diff --git a/lib/erasure/erasure_test.go b/lib/erasure/erasure_test.go new file mode 100644 index 0000000000..0cc08c4111 --- /dev/null +++ b/lib/erasure/erasure_test.go @@ -0,0 +1,132 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package erasure + +import ( + "testing" + + "github.com/klauspost/reedsolomon" + "github.com/stretchr/testify/assert" +) + +var testData = []byte("this is a test of the erasure coding") +var expectedChunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {97, 32, 116, 101}, {115, 116, 32, 111}, + {102, 32, 116, 104}, {101, 32, 101, 114}, {97, 115, 117, 114}, {101, 32, 99, 111}, {100, 105, 110, 103}, + {0, 0, 0, 0}, {133, 189, 154, 178}, {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} + +// erasure data missing chunks +var missing2Chunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {}, {115, 116, 32, 111}, + {102, 32, 116, 104}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, + {0, 0, 0, 0}, {133, 189, 154, 178}, {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} +var missing3Chunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {}, {115, 116, 32, 111}, + {}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, {0, 0, 0, 0}, {133, 189, 154, 178}, + {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} +var missing5Chunks = [][]byte{{}, {}, {}, {115, 116, 32, 111}, + {}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, {0, 0, 0, 0}, {133, 189, 154, 178}, + {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} + +func TestObtainChunks(t *testing.T) { + type args struct { + validatorsQty int + data []byte + } + tests := map[string]struct { + args args + expectedValue [][]byte + expectedError error + }{ + "happy_path": { + args: args{ + validatorsQty: 10, + data: testData, + }, + expectedValue: expectedChunks, + }, + "nil_data": { + args: args{ + validatorsQty: 10, + data: nil, + }, + expectedError: reedsolomon.ErrShortData, + }, + "not_enough_validators": { + args: args{ + validatorsQty: 1, + data: testData, + }, + expectedError: ErrNotEnoughValidators, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got, err := ObtainChunks(tt.args.validatorsQty, tt.args.data) + expectedThreshold, _ := recoveryThreshold(tt.args.validatorsQty) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.args.validatorsQty+expectedThreshold, len(got)) + } + assert.Equal(t, tt.expectedValue, got) + }) + } +} + +func TestReconstruct(t *testing.T) { + type args struct { + validatorsQty int + chunks [][]byte + } + tests := map[string]struct { + args + expectedData []byte + expectedChunks [][]byte + expectedError error + }{ + "missing_2_chunks": { + args: args{ + validatorsQty: 10, + chunks: missing2Chunks, + }, + expectedData: testData, + expectedChunks: expectedChunks, + }, + "missing_2_chunks,_validator_qty_3": { + args: args{ + validatorsQty: 3, + chunks: missing2Chunks, + }, + expectedError: reedsolomon.ErrTooFewShards, + expectedChunks: expectedChunks, + }, + "missing_3_chunks": { + args: args{ + validatorsQty: 10, + chunks: missing3Chunks, + }, + expectedData: testData, + expectedChunks: expectedChunks, + }, + "missing_5_chunks": { + args: args{ + validatorsQty: 10, + chunks: missing5Chunks, + }, + expectedChunks: missing5Chunks, + expectedError: reedsolomon.ErrTooFewShards, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + data, err := Reconstruct(tt.args.validatorsQty, len(testData), tt.args.chunks) + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.expectedData, data) + assert.Equal(t, tt.expectedChunks, tt.args.chunks) + }) + } +} From d2c016333f98ee2180fff89d3ad9623e48603384 Mon Sep 17 00:00:00 2001 From: Kanishka Date: Fri, 9 Jun 2023 03:11:42 -0600 Subject: [PATCH 03/85] Fix(lint): fixes a lint issue in bitvec-test (#3306) --- pkg/scale/bitvec_test.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/scale/bitvec_test.go b/pkg/scale/bitvec_test.go index c0fde7f3b6..03fcb9c4a4 100644 --- a/pkg/scale/bitvec_test.go +++ b/pkg/scale/bitvec_test.go @@ -45,6 +45,7 @@ func TestBitVec(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt := tt t.Parallel() resultBytes, err := common.HexToBytes(tt.in) require.NoError(t, err) @@ -74,7 +75,7 @@ func TestBitVecBytes(t *testing.T) { { name: "empty_bitvec", in: NewBitVec(nil), - want: []byte(nil), + want: []byte{}, wantErr: false, }, { @@ -98,6 +99,7 @@ func TestBitVecBytes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt := tt t.Parallel() require.Equal(t, tt.want, tt.in.Bytes()) }) @@ -139,6 +141,7 @@ func TestBitVecBytesToBits(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt := tt t.Parallel() require.Equal(t, tt.want, bytesToBits(tt.in, uint(len(tt.in)*byteSize))) }) @@ -179,6 +182,7 @@ func TestBitVecBitsToBytes(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + tt := tt t.Parallel() require.Equal(t, tt.want, bitsToBytes(tt.in)) }) From 710b03cb5d363a59528c9ce567fd80642b559d37 Mon Sep 17 00:00:00 2001 From: Kanishka Date: Thu, 15 Jun 2023 06:55:01 -0600 Subject: [PATCH 04/85] feat(parachain): add types (#3297) Co-authored-by: Kishan Sagathiya --- lib/parachain/testdata/westend.yaml | 25 ++ lib/parachain/types.go | 388 ++++++++++++++++++++++++++++ lib/parachain/types_test.go | 373 ++++++++++++++++++++++++++ 3 files changed, 786 insertions(+) create mode 100644 lib/parachain/testdata/westend.yaml create mode 100644 lib/parachain/types.go create mode 100644 lib/parachain/types_test.go diff --git a/lib/parachain/testdata/westend.yaml b/lib/parachain/testdata/westend.yaml new file mode 100644 index 0000000000..363c748742 --- /dev/null +++ b/lib/parachain/testdata/westend.yaml @@ -0,0 +1,25 @@ +# The result hex is taken from the response of a westend node + +# ParachainHost_validators +validators: "0x44a262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe7274c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68e61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c51194ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050eac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841b61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547cee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237e2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53ce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016ae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848a0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936" + +# ParachainHost_validator_groups +validatorGroups: "0x0c1800000000010000000200000003000000040000000500000018060000000700000008000000090000000a0000000b000000140c0000000d0000000e0000000f0000001000000062e9ee000a00000054eaee00" + +# ParachainHost_AvailabilityCores +availabilityCores: "0x0c0001e8030000009652f3009c52f30001e803000000440000000200000058b29da15fc66c19ef0ad4e66e95c8e42718d2455001ec6876f4173489a705fbe8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b51453370001e9030000009652f3009c52f30001e90300000044000000000000000571a387d344bef8a77d3a99895958644dee97345861f7747b1cb8186a14089ee9030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083c2d011b32106cc9030d8d22770c632b611c613e4ed351c149ba59e7d61cd6d0055e865b40a8dcf60422987c985c6022231106e0a4556c0270ffcb99e656dd7be25c5eb2a93cbada931620b5bceebe187bf77aa6dde41a6cbb09f0cbbf291faa5a33ec5ebf7fa730c95c84650a68d432ed53fc77b6d8bff3ce50f19d48317c30aca695f1737408d7dd47052c3b3016128ed3d6ac5b3b46be0cc9537919b9f594fa704bc2e304492c6e835db7f2bfe17d5da9102834f5be31865468357055164895cd3b32bdb452067ab9b91421ace94cd58599e42f287eb47663289375824a5f02dbd44d6cb9847e46210a665f28147957290a6d3cf758aa31af9d3ba54c01e670001ea030000009652f3009c52f30001ea0300000044000000010000009b716202cd9fabb93b278a41d9344faecb1300241d255d1de0675a42ebff6b6eea030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083004c606d630584c98d4165a9dfbf9f2a60890f303686b7e5fcb428773a88ad6f08db9fcb4c0e4c4883dee0f9281e1de995b0575ef784a9fda4d58c55cdcc326dc8c838bfbf21eaca646aa5eef8e34d5776310f51b2e1eb1d6b1d86f05739c208efde19b42cad56b4c1e50a381a480ea65aea7c0b0e9f254174bec6f36ebccb70a69233b495ea5a88376ca43e908bbf8b921398dc491d94d1f8ac6f4f4ca6f02d4d71b3b7a81d9a11d797928c060d8c992f908005c1f1460d3ad4fa284dae0b8d9a32fdfc8217ce1456dc87c592ae674614dfadbd1098a3a2889e9c2ada44dac40d6b0154b039a3c7dfead7371f3f1d406f3a7a52307e99588a5d1743cfc52996" + +# ParachainHost_session_index_for_child +sessionIndex: "0x0f690000" + +# ParachainHost_candidate_pending_availability +pendingAvailability: "0x01e9030000328b2b271db466af1870b5918aa43aec671de9803aeadf75013d3c78de35ceea84b5c6e57a116cba4148aaad37fbfc3edc54e7728b5192a40391d9c8ef33bc40e0a84b429996989d65526edda87a56b79f51b98cbeb347ce22a356cc7da02e38ab0e77029716d6a3e8b45e3ac27c623c347f28d433926a7f762cf0018484f58e505b19ef3223929a5ac8e2f17e5add18665a164f002b806899329b25c8da3be76c5664eb273bc0dbc3016d1304e53fc86b20d4d3583c4b72599ffe4386aa7060c9e6f1ad9699ddcc8f2bbe324c882cba6307b47e991cff013ffc307cc7d3e4831efb8e925358db3e93e466f6238871a8a29a6028279db855f0eed3fe4d7705f2dcc6cda49b51efbe0fd184b974074d1258ca9f15b9fccfedc35b7dee6e1ab9e9000000e902bc8e637ddab3bc69db43a62181e9a5807647ae518b205161b1beb25cc72bb441a6dc4600cd7190aaa808a47ac7c2cce73922a0cdde3fc639969c76d5ab44566e25e5fee50cc06625cdc46c76bd0bf8c046308022fe7b2cfd31859fcab2c7d84a2b208b1b080661757261206f495c0800000000056175726101012cd5b2805efcf8f0d7a35e05a9b3582df5af3cab0c912af9a56e83a10a42ef56b4feea6347c7c862c71ad66e0a6296cb6e46cdf10baf65dc89315f2768fd4f8100000000a18fea00" + +# ParachainHost_session_info +sessionInfo: "0x0144070000000c0000000e000000010000000400000010000000030000000b00000009000000060000000d0000000f0000000500000000000000080000000a000000020000009a14667dcf973e46392904593e8caf2fb7a57904edbadf1547531657e7a56b5e0600000044a262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe7274c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68e61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c51194ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050eac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841b61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547cee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237e2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53ce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016ae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848a0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e3685893644407a89ac6943b9d2ef1ceb5f1299941758a6af5b8f79b89b90f95a3e38179341307744a128c608be0dff2189557715b74734359974606d96dc4d256d61b1047d74fff2667b4a2cc69198ec9d3bf41f4d001ab644b45feaf89a21ff7ef3bd261898ab99b4b982d6a1d983ab05ac530b373043e6b7a4a7e5a7dc7ca1942196ae6c94f9e38609dd9972bfdbe4664f2063499f6233f895ee13b71793c926018a94284ce0e8ec374f50c27948b8880628918a41b56930f1af675a5b5099d23f3267633a58b8f1f529e55fc3dac1dd81cb4547565c09f6e98d97243acb98bdda890028982bcec62ad60cf9fd00e89b7e3589adb668fcbc467127537851b5a5f3dbbb160695b906f52a88f18bdecd811785b4299c51ebb2a2755f0b4c0d83fbef4318610ec5e1d2d044023009c63659c65a79aaf07ecbf5b9887958243aa873a63e5a1b52ef04ed449e4db577d98ad433b779c36f0d122df03e1cdc3e840a49016c5f16c2d4b5973000d0b175631dde5d1657b3e34c2f75e8a6d5414013ce4036d83355a6e01665b2d8490abf45551088021041dfb41772a9d596ed6e9f261ed1c8ae72b436c143e295617afb60353a01f2941bd33370a662c99c040984e52a072b5f224c4c4b178f1a3d67e5f26d6b93b9a43937cd2d1d1cb2acc4650f504125df2e18ca17f0edc319c140113a44722f829aa1313da1b54298a10df49ad7d67d9de85f5a6bf6911fc41d8981c7c28f87e8ed4416c65e15624f7b4e36c6a1a72c7a7819446acc35b896fe346adeda25c4031cf6a81e58dca091164370859828cc4456901a466627d554785807aaf50bfbdc9b8f729e8e20eb596ee5def5acd2acb72e405fc05cab9e7773ffaf045407579f9c8e16d56f119117421cd18a250c2e37fcb53ae2dca6ce9b3ebb40052c34392dc74d3cdd648399119fa470222a10956769d64f7477459916ace4f77d97d6ab5e1a2f06092282c7f0a1332628c14896e8e9be62c2574de3dc8feebfad1b3bee36a7bfe6c994e5d1459a5372ff447ac32dd46c11b0a8ed99f1e7ab160e0ac2fcfeee0d92d807c8fb4c1678e37997715578926c5c6c9bfa7c2e0f8e10a1a78bb982313c5c347a018cb3828886b99e109a8799d272e6037f1fc5b19015b7089ecf90034349e3f5c37cb50dec5356743614f94f8c33964b85f2b8e10e859e306d3670b8bdc0cea17b97dfd3edc8a9e1be1f127fee5b44d421ae62038ba15a377cad85e4ecd3c2a63b54fdbb82c47fb3e9c02640522648c51db949a58fd5f36a19888986275547b0c2fbb0b348ccb85dfc6c998dbe160ae9425710301a9241837d624438a5d82edbbd6bf2cdbcc2694ad7db31ef99219e47376e9af08b294901b879c7d658c41386453c6baa7c26560c5fd3b164e05d8af1a51649d44d12dffc24337f0a5424b18db9604133eafcb2639ddcdc2a7f0fae7a30d143fd125490434ca7325025a2338d0b8bb28dcd9373dfd83756191022eeba7c46f5fa1ea21e736d9ebd7a171fb2afe0a4f828a222ea0605a4ad0e60670c1800000000010000000200000003000000040000000500000018060000000700000008000000090000000a0000000b000000140c0000000d0000000e0000000f00000010000000030000000000000001000000280000000200000002000000" + +# ParachainHost_inbound_hrmp_channels_contents +hrmpChannelContents: "0x28e803000000d007000000d207000000d407000000db07000000ee07000000f007000000f207000000f307000000fe07000000" + +# ParachainHost_candidate_events +candidateEvents: "0x9001e80300002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f3282a8119a659fb3c94c3c951e5215daa35c741d13719d0be7368ca8e089537b5b3704113bf3c0d7fafa20033cad97002e9b9cad03fb998cb35cd71a9873d87a6af72afd5fa059b18e2e13eb3ac6158212b57ba060772886bf962b12034127908e54749e8014a7525ca4eb8815d1565185b31aecb38121e4347b54ccc8e49d13e2a732f9e332e0b425c741a07206c71d801011c2c0a278c87fefbf8064e0631875d1ed14e57816dde31202e999e6e24ad0f8ae2fc830bc1d325e73c3f3dcd186be8e59074c5553d8b65ed0a7034b310abd88b894263dd5869a8cf87e5ef4f0a79985e134020e8a1e2e211afcd5ac9ec6a2fd21dfb5e1c39b3b670f4415e904061adfacb103f25340b938dbfd331ba478351f9173a81134bff8a8fa1c0fe6be68e9025ea1f9f9f24e7fa5437238b01826b451001da46638f62b334300923068d1691f42d0e400b65ae90c283a08ee958a10a0e8c33eebbcb01d137f1612e39973a10489164135f64606c111f8d5d21ec2f8ae932676beda36b07e3577c7d8103c7cf2cf878ce90806617572612083635c0800000000056175726101016f5b6937ad29ad8721ba848694820840b25d91a631d2cf19f8c63ffcf1fcef260d25513fbaeb64c090d371bdba09663f2cd3bb4a8f8f7838b6620bde5c6e5905000000002000000001e90300002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f84b5c6e57a116cba4148aaad37fbfc3edc54e7728b5192a40391d9c8ef33bc405ecd07dedf12350fca80ea9faa4288186ef3dd0b40badbbc49f858a38cfde88c2bf80adf8f3282e527e242daa821b07b2972d9630b1eea6d2ffbaeb90446a8bc62a19241993b622e6321aa73a113c354958dd7266e7e2a28acde078ae276bcf7cad7aa37dace9d97c31549cc22292b650a04cfbcdc44e20c9225d499c8241735168858f1c5382ae1cbb9ece1165e63f01471338afe7714b8928ef3b2a15e6f86fb4ae217da22aa2880ae8d874d21f8852d76ce7d32554830cb82467a66658784dcc6cda49b51efbe0fd184b974074d1258ca9f15b9fccfedc35b7dee6e1ab9e9bc06de0d02e4b3871341b268751813d0fcfbdb0bbb2ec4fa9798959ab01a48dbe9029ccf0aff2230779960fcd86efb10ab9d36ee09ed20b3404b8da50f63a2f2aaeb6e43470084a5205b111c66b80cabbd010043085f5ba73c3a4e0f19105e32274bf19fab97145aca984aa0eaaa60c4528cdfb9aecfc2dba582729ad01ebf8d72ad39f1d70e0806617572612083635c080000000005617572610101ecd027c637c53ee91d7a0e98cea0e7e72f26f69415985ee9e28509aac917565522d428904e56757639946c30eb6228f11feae3bed1e1e60458922e892f89ba8c010000002100000001d00700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f605b88b35bbbc7463cd981713be480f1e7ea1d12b6c21eacf0c120126d24347f57dd8f8b64cdf03c5a171480ed3b0718ed0d2ba49ed0bc088b33c45e9f40c929db5ceb08943ef0f0cea09c1606e0822d9bf9f0a7297ac08335d13c87f28490ad2532a4846d8b951e37ed71582b2562027373adc871ec734cf1b77fd6de383cd5a2b09b240d86f318e67f5fe77e4b3714d5267699515c6696fbf7eb8d2150826f89faadb98f6b52ca914fb474ae9d10ee57c48669ebcf6568bc6c0a8833466f8174c4eaaceb528233d6b6ca4823171880f8853b0f02e6f829444f6eac27fbfaf477bb8d6c11c2ed5ee8a4e0933aa661d2e44e03dc8823cb180cb069463128a2c766b405ebd52f05687eb3288165c4808ab492750a0c0ad69f3d29134eb883cf5de9028ad5bd67ae9e27b97131608d072644251f45c38a49ed0ac3dcdfe3e6c6c9178e8620d600c955c50fe95eecec5125170c2ee7031c13b46c6c64d26278c805e74f5d98a89770c37ca072e8a11ec0adb6e178a689d285b066c93bf63444a1be78977081c6fa0806617572612083635c0800000000056175726101017a9dd3c3f5b3c34e7804b2a4e92c08a4aba153098f51229d81be212fd0345e2be37a41cbe3265e448b8afc03475e33b2b9cb762a9df3e04bcdae19af3aef0d8c020000002200000001d60700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fa23d6b412989d9b453ba20b524d14a4932218d5d20cb57529b5d8481fd9d1717c79dc1dd9fbc475efab87f98c901499443b467cb30fef5e85ffb9429b028fb69b7632d7583557ea812404498c4c544c574b43b952c0f642f208a47c837871974d324446e0b5362729c6f287ae6781da900bf67a02a0675327c8b0c4eb716d79c12dcfed6687a9e1cbf5a58b4d4c1428cddfc93b6031fe5a45441dc6a363f43676fa41104feb13bb292af09d5929eccb40b00b1ba9856a4a7727e3a82f9689c8d58eed3e4d8a61504356c2f5f230bd53ac9ad08605c6366a65ee919decea46ef17520623e2407d6c2a89b4c660b073fbbaf0418452b0959d1018293e923cc0c70ef48a7f0993dd843c38142939ccc1d8937f7600ec33f8e203529f3c0bb741bde0d1e56f9f2bbf7b0f5b44f5a8fd26c1641938379cb13bde0d422c3e5db5675964cd736a2d500e8c1d62f574c4a67bc94e0d38ce45fa94b7fd1af9dfd8c1902f34802a4f849af258f105cc555928f36e117e2efd519ee691784304be5a7c7fdfe5ba3128576150c06617572612083635c08000000000466726f6e091b01ccd9ece066c3e5c23701aef8a8d166534049e0866bc96618feb63b27516af2bfd4c90a72b83065ae02bc073d72ec49901539e3dc74b84025913b2599c392f6daf5109eae1cef4251a2652df775975fcbd3267c254655f0623cff8d48c24d3a0f935472c4b01eeb46871659e4085d8afbb3091dd6a9559d56a889ecdd239ce4cafa23ba44589f0ec8aab1726096d29ee0c9e2676cb1b26cf2da888205cbd2dfaf1d2e87536aa7ca2330a777b9a43a8523bcfd9d9f041d661c688a8c2791fa5927ffd02ac2ea36c1f3f8a3250b94398171098e4de128bcc6333d68e8de6176f3209f66cef788ef9cf9bcdfd1560c5081311c00fa0d4a4d952bfe6c879f3752f7a1015e5ecbfcf729e0713df598b79f1b7981acd0595d8bdd4cf33f3a3b4d44bfa16b0fe8275f58a908984a0cea1fa464abe66705577c82cb660ba42391739003a6ec0dfccf329c2b6d81fe7ea47987e94b00fab2444a1c34eb3e1be1a21a31b6bf0a2c01828ca4247cdff7b0769ecbe0d186ca9af743e0ab05a823ae65bfe7fe871f7d1e5e2fe06d88139bfdc57f9169ae6f869c155bf1dab7d97ddd8ed0b31e2d7b6a1097a9e0e1ebe09539865f3abdfb20d83f39cd9a9ddbe70f2264cc5cc34cdaa45942ce51e3af015302a9a25df2ddc220657322ad6b03e843c24779d4ca45cc4ee30a6830e3c82855458becde33a4481a1e0bb4a548c244b7ecb8be06c6cd182f5e7d3e39010b0cbaff53d43f3bb471e994e01e8e79fba4dee058c59c0eb2364ef45fc1371f62f14dce74c67cafe44cba43cce596b6a37b0044ca9613180029a14a77908f74613db7650b31a2176cfd2cf7ef35e680b45d72bbf45c56a4b8a6327926f708a899548f224faa0cc95e0b58066d7a526f92bf53524d25e5e25c39a9f7523576f0af291e0ae37c7cd5282b14737a46a01b52d5b0a46b7c4674fb28551d2db05d6b6bf9ca560b49911d8087a6898b672e75d9447edf4700e7935cc0899cece13496a9f53e3533ab2e2f32814e36d2162d1316497ed6112c2b8fdb36bad16f697098f1445ccc3ff6efd641cde1b90e8012eecf0c3c5a005eed41d5a312d6ddc76c78b2ddc7219bcc62dc3c2e0dd84ee7c161a2181bd844830d8a533fe8a73ccb7af3a290f1cfd0758182572b5a4b9da22f93854ae35d0b6e79f7b289253ae05ae173ade67ee65f312577e0a1623b4c14982f33db8e10ed43874de707843f49e7d186a0b3ce48f5efa449647f230eb490ce8ab38fb460ebc8b6a26b5331a0dc5224b1914fa1c9a0e98b2092769b3e46066aabe127f3866f55d56faa702b5ce9faf3fbe22ab352469f5f86719624375497b2e9142e12b3f18dbfa0d6545c7637eb25f6f0243e14a7de2a79f5a725e484bd484cf4e8af7b2ad7e781bf6d5cc72b0f14abc95a407cba0bd01c4d47e192d9eced14bc33d509313d37ac40d2abd213d3961547fec1df1ce3450f1271575684cfaaa6867d7ac9b9fa9e939267e11f1bcece14cad2c9956fb316b8e4063db5389738faa82e1cac2685c1bb14c35f2846b1fd44076df9851ff3f6043fc3824e44ec0fd70f684d2140525e37f1ca00190672cf0d3f496d917e4e63eb23cf89aec96c62e352bae3010a165807e51ee2d8e90178f9285da18ed954fde7c7614fe88015f06cd290455c05667c58cd8210dbfb528bfeb70878776e9ace349aafe5ff9340b4598bd70097f8faa9e37a9e3caa2a628e67d83117107e99257785a45c144d29e970c424a0eda79be84fb94287e56f9fb3e746da3847a56403e9618a5420e93832989fb58116d712ef4b1d2d23659682053b38d6410322a2f07444ada2aee3ef85d631e01cfaf5ed28bec9dcb2bae747f6c63e9e4a12a8814da4ec2ac713565574227d197851422b823db3f5eb669fe9ea324f0b939835b73eab6eaf8393221ce5540462b71770bbaa8b77682198e9830f75537221146d306ad07bf3f9de76cdd485a669607d9c5c8c517cbb87730b6e90a783b0f1bad7aac92ae0a0972a8424f6683c42d78400442e9271108953f1d40e0af58e8da33c1d9fc1aa77b572f0672a3020ef1465b1687381cd70e5b1ece01a7c08be69f55b1e0683c1f742dae07de6657e6e88f97b6d3314dc30e461ae492f8e3a180ba1eeecc64a856be520f8198a1c2a4e0bc6d295a2fc13d4bb81ada3553923ece3cb67047b7347ffcd13c6bae49885be17de3d478d3aa8dd2f964ec3ede11d84348f7a40015e588ef9c9a446a3699de1cbe1afa916d10356f412c32e085c3ec0115fd488d3785f9552f893bbe0b22c3773df967b7e36713177151c7c21b9b9b1b367953ab7199fb03947c31a57a94329c6edb9b6ca20d762423fefd38b1ef2ba3f179998a5a5bce656d8b5f14dff06715909d5b7a6343003bf33c0377003d680eeafe54a348cb7815e33c842eb658ffdf3151507e496343305617572610101744acd32f811aae5d80e20534e44195d03abd773156a54dc42204e7c33370702db76b09ff7448955b79a53f0ae958312e2bdaef5a6662350349ce955b8561286050000002500000001d70700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f7efb9393ca81fe0825670473a2d400ac00ad22c1f401a14bc154c5f92ad2443192f6141b3e3c8ff6efa5a85a4433a49aa88b225d64a40a6c500854222e4957243012e86d4a295ca3422a771ca745241b2ff615dc7a71758ca806b1f48525836b78e14daf47e1789603860a916feb342edb58a2b7acb095c4e93a52b987442ad39c854c794e49072f464fd7e0a418d9c7eaa790c89320f830a4919c109dd9795bd4c96ae86882d18ae5a8edbebc1be36c269313f50ee8e1ea30ad62770fe9f88ed5f6cf4e7fe30b0808f01d7b1e9571367979d1cbb8359088cf98ae22e8abab91ccfad85a4a6ce77f402b64b0f0524a831daf156f8af91f9d5d62ce4c687947c3fef1228b81636a938a7a242ad5f78aed0a218d582faa5176c28748621c0e24b9e902757eab60aabf67c35b6f64b7dfe695fa70c9213e15d0cf39dbc547924a5b2ef9f69d630014ff54af3384cb0391d00b06d7309bf0869519cba60f2bc41be171ed2f372f3e1b71a4f5d6958d3b1fd4e43f3173218aa9092f05bac9e7f4cdd4ec490f4d177a0806617572612083635c080000000005617572610101fab1ead14c7c2cff2e1f1ba2f2c0fad3d5a5d5b6c3cfde4e88dde08d978dfa1da06bcb5daedc8e960b11f9012473e7c237b5864609cacc9b4fe9832785864b83060000002600000001d80700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fe4bd11531d6789a4478dbf7a36a3c7d23b58c5a1debdc455e1fbaf1b5b2ab412ec6a464b90ff6ada8a89e3f8d180e74e293a8b2f01c8499ddb1e994293c4c0d0779b68a85a99a3e2d4e0a792e1537c07b3a56fc0a637f1db059d9c2755625b778096d82240cfa6f9fe0dd38b9e597504a42e58cbeb7919c789a917450ee00e73fc355482abce20f5b06ae119b1a9ee79866d5b4854a389550c9d9580a0c0332260e7a1a1b7d1ec159e23065c342070603750ab953543978b4c8f018b2565948ff775ceffb1e33fabccc1c2c6ed03cc463645aa63019a0e9a8aae015cc11c6c4d370085df65a76eb880baf100b4c7401d011a1fa1648876798a747f9aa372d114a81db419e0c739689abf0882f2dc771d2b3f247cad2cff8b1630840b4892b8d5e902dd6b8d78a9a6e1651c21c2928db2b305d78290e51cfbb807ffbf93ceb6b1696b1e0d4200471f7d3b2dbab4c04349ddd829a29c58d75f91bdf56534369705c46523b29229840f9bf10916cad670f0d3727bd747cd737bfcc525bf923632dc5957c032aa800806617572612083635c080000000005617572610101164bf5fff13882bf1a85e593295939d26115f85d9539faff0d76d7be8b7eaa59ea1f86566b58362a71c19076c5ed65d72cb79994091851bd8811193bda730b8d070000002700000001db0700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f0c743a94cd1707ad4117ab058e336425f029d591d3a0d48e895c732013c7085053dc502546f00302e84fc41e74a7a310c556481c76ff18fc1bbb97c457866b972dc5d56deb74248c917228b08ea5774022ad965420fb4130a53edd44dbe883c91beac7ff23003ff41339e09470ba672bd4b9de9d51aa32ea9c4ca080c6f1f9820e698a3c6c6ffa8523ffd01d125f46795c6cc3c4047c60b36f44d1e89ee037319344d5b89f31dc79f5db9e5aca9ad7925ee5b750c1a78ce34ce503d4fa4f0d8dc38992403757e8242f70fa6e3898ee68b82eedcf933db38e0adfd352a1b6671ec7691f79817eeba28caf545200d0e9df486d47af1c5ac79ddd05aac62546f6f8723aa1c622a8d4a7757a130d4c7a24e2928acd073b425bab04c5e960b4ce22f7e9028813af0548dbba39f8e667b2679069aadf411ad3a194a5ca50253221be0784058e668c006cb6f4c83ba533b775447a77060f2a2fc873d79045329954a814221b7b6d52b96a216e614a1e963366a98b3e4fcb728ef8c2265b2a6a0b9fcce0ecaa41f094a00806617572612083635c080000000005617572610101d8d56b387d8b2b434c1c4d2186a48765e9ee91a39b214c4808eeb9f3461351196740631854c04bcda5980e50b7cb759df2f64ceae11f6f321ed05f8e6323aa80080000002800000001dc0700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f08fc371c2ce887eb3e132908c4dd533583177847b8434f5c7c0cfabd4852c24e6c7ae7aea674dd4bf6796cdc2d337782f885482bce24c9030e87b11bca54d7f5e50b6d38ff0fb29a9e76bdfe49f6f82140d112c80a357002c99a8941a08ec62190d3eda2b3ccfeed95db0ab62327db71f640621d80c753b4e69db9868dedfa7da60ce0b09e321b053d10dff1b04e435472170d760c41282be74154810f277b1360cc17225d667bf15fb35266c8cab27968b58813ea1ce41baede882d43bc1d83e973614a3dea1a30b67783af1e57f732e89eba9958b0fcb19fee08f9a3314eb9b44a574641d090ec0cf776b81af6ac92015443353b22a7db67f42bc7347a67aa8f846aa19dcdaf075ea1af3c7f1b0f66b33bf8505b7e94862bcc0c4f6e9eeabe890371dcb58c1c4fb0f5cad90c3ca6f598d596f8e47005536da04f424868b9963e9256e1d2002610491cd018b9dc0c88d6c9790b14b4b866b6e0600feffdc0c94c667a68c14f2264ecd4a757b0f14bf0b6d9ba93cb7e2e949191c86ee32c5402d158b85fadaf0c06617572612083635c08000000000466726f6e88016d1ffba7d4ede2a4c73b3740c4a775da3a0a1fb08e3b989ec5e49684d663be2700056175726101015adb821043ece09a08d82ae2cef89f37d7ca8f4f9350efeedc12db5884a0016763c60aa38c1abfccacd5f9ba16c4a8ab93f6b9177479100d0b6c975015a82280090000002900000001dd0700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fca221b74c8d32fa6a45bdc2271371109a97cdbe2c82fcbe452c548874b7dc1665af5692ca9a27a9d2c2f55affabdaa466fd435945f091b45944e0f0596984b664b7f5bee6c782d18ae489b645be2afffac110bc7bd97840260a807550ee056ca22a4b9f9c32f4c502ebe462083ec11fa689a7f3fd948fad1e3cb5ebdeb4737c7ce6a2e873ae8e530de6065dda3890084d568507ca5d9a69df19683805bf5d24ddf938c7c9decbab422bd00c4757dfe18489bff7e017b40ad9552871b9a7be88a4e315ffb734e34f9c25170faa579eebc5b74caa8cac8b1b87827e84055b256d35c644799d29d2e6f4f2c065ca606744598719d85f8c3e7c4d39abc3ea59be4a35ba689f6c0af4bcf5988a4580840e313618deb3b835c4e4a78cc56434b2ed6e6e90231a890359de5d57ef9864cf19f3a48c1fd1a63812b0765661c598856f956177cb62e8900830038feac3e62aff1f1333ad0ee8696a4558f6cc42b876e0603d703ab0d7f0eea80b0973cfd2d3a59268bf68211e1bc014bd94fd917b6e9475c3a78508c03fc0806617572612083635c080000000005617572610101b86bda32f157f1e933143a934f6505a6690c0a31dc52bda5f49d40e5e26073545851c15949664b7f55b8cb2163a3caf7f487d8b4cc87752aa3b4f40638c2ed850a0000002a00000001e30700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fac991d1eb1203b3ac74ce441fa62e97c8d7b99628a85cdab3806bc68ec92131490b0d19498a2d0ec54c48572abadcde6a5a2aa9ed3ab74314fb4dabe30ba6408d4894734e270af31d21c80c4e9e3fd540899669aed2f9bc7fe28891bc0fa6ff9a58ebfa37a19b1e8479d3d41c7b78204d9719c8ad1212ef0bae7f2cb82746ddb6816850f2467e875176ce398d1607d13d2f458526126a222d47f10c13396eb069577928443d3ca93d4582e1291c0c7caa7bef8c1f39c9863385e472ff1e9b38d85bafb3d2fe2ca2354c2fd1fd197b36bcff57292ec4e7b8ffba280227b13aa0247ba47d4824fd30328f70f64b056d4e36e3b75594d6f590e1b85e90a65f9a63ad6d42232ebb0628b11aa6a5bc31c1b8ac45f683b0290b14ea725b2b53ec8017ee90265f1561c96e26a3ac239745a0746ae6fac255bbc9a19aca4765900819e7a532c12d1760059118acf89d6a62c0f724e283081c8bcfd74a1db5435983c6cb67272699024dcc1f7c4089088d1fd7a4ece328c3611ab9219a8e0f43240841673317a5d3932a90806617572612083635c080000000005617572610101eccee865e2c48f93bf2d1ef99bf22549e3994e74b2f91cd00e742be73173cc40e5518cd480b62778a627e15268c8439cde21a27eb24fe0386645a5b11430c1820b0000000000000001ea0700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f0cd0c3ce103900b501fc82da6f8999bfd42354f8fc4db6230a764b7926c75118d570816caa86a63f7fdf4d0e0cae604060a1ca4d53a6bc0aeda87d1f5c28918b5f8233fecb446796519206c3b97eddddf86237cadeded98b041ef6ff4e474e53287a1697c8caf461dd58930f749d53af8832c488780a012590b005c36eda0d6e16c775b8eb42663d5517b3e9cc198e9658421ae7af2a76706d64e3324ce39240a32fc27041caffa0ead9c0064ae0ace86242f8074273018316dca50bc96a978da3883c3993b1a32b26325ff85be0b63084960a8b50132433176fdfb5c6ffc07bad1936428467f481ac53e71165512663839cf61457fba5ff17ed63e71b8c717680a10e1f33cda8f8d81c50c91152776a54b50195d6ec7f0d4bda452f9ac73012e902dc413d61ee372b7bc1305b79f10d3644a42c88cca1c4c8539760bdcced3d8c2b16819f00b2faf4d0fcf22d03a7cd76920487eeffed5b599f6cd32ccc9a64801fea2aa079feff2af002f0a71bd1fac90e9fea3a0573a5bfa2765a3a931bb5b8d55c9717290806617572612083635c08000000000561757261010166e2910c9513e1dbd2a6d3f938cd6a6253fbb2cfcf6a1f6cd76a8b7941b07b36824ead10eb5d99188c749988b49b9bb673571fac8b9317235b89b8d443a5fa860d0000000200000001f00700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f0e4c8eb3dc54e5c0d1b9402f44f89137c1c2d095bed90ecbb80e38c5cf245c4828efb3599bef57b84619255f76f0aa168ba42a0c7d3a7e519bcaa784bf0c66e38b0a4168467cc53f93902feb5d97440405d82d672f86e440c362301c6283263fe870ecde7baa2895003dec3d07836c095ae4f3e4a05347dbd7d7c4eee1f811e18628cc961b32b53e4cdd3a60be566fd3ab6a686041c07a8d4750133047a6907232365380453504c52c6b96438fdabd2d972e3dfe6bd1e9bf0f254bdc07583e80acfe461dc363714109b6e04784803614ee2b00967cb51eb899327193cf485260130b0b9ac802b3db074f304ddc428950e80ee3e2a14d9f009d8376fdff493b7da24d7edadba1d6f9a8bd642f426851bab186b7aae7eb8fe526b05e362b996704e9028625ec7da0150afcd6c1431ed2e8b56be1dfdbbbcc7e3eef1275b5bcdcf5f4091622a20013b839aa611349e4450391abb30a5e0bf166d201351ff2eeb43d5057714a5b6c959ed33b8bae5fa99ba5d0154e08ce851e94502c8cd069c78c0a0d748ba782160806617572612083635c08000000000561757261010112e21a778c7c5404d9cf20fb4ecfe2c06b14e3e5ce45c76619077a2d47920b5e2d605528db37f2d4724f811429bb3bd308c9a20ac0d05b91329bb6b49b3f5a80110000000600000001f20700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fa6703abcf0fc554a4cec1cb5677c287f06e3bfc3f74bbab462dcc1062c88ce353939f9e6aaad990f2c9837dafd9f5a22c17455389a73f05570a74aed86a63b1ca65a09155dcea6eb1d458cc292968124a0f9b2bf125cb797ab96086dd3ed40da478e5401533e30c628939b081e4e383a28a6890ff949d27913939b7bf5d9c4dc0419a23af2d8a14090a06412179f31a575a917c04973f1f645d777e5ec48c938ad24670905b57cb5e29d8e2b285fd25778a69930cb1e71a366be9594f58c1a80322a31962e767ce5d6aed172950bf0d2ace5df6050df675a4e6a58a4c308372d89fd8d101e7dc06f0ee9a515dd338f2d29098485ffa2c2eff98c6bcdb6805b483ebfb1a48db52c4f8bc5cd264c2746ab4034bad57e11be369e459fdda1bc9810e9025375b7d9d546b18adc6ed91373ba5ca0a1e0e2549458ed06d4a9ff337dc4b7f11a9e9700a5ac8cecafd414c670faa4c56cc84c1efdfbdd63620fb2afb77983d5bf004a11177c31f7cac28712d992f31156ff0627678961f8ac57d3a41afede86cb1e31d30806617572612083635c080000000005617572610101e8bc4a1acbda300bdc4da4c20caa87ec14e7d91c04718c20d677ba82a2adf422e72d52c66fdfa17eb1fa426428ea9f015f8ec3b4dafe3b59e9062f0e8a4e3580120000000700000001f30700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f12528c5a9d2d33f06fe0197fd72642634a924caa92f954c84d0b30a5af9f621282ef2d609cd65387717749105fc4e7266cc29d8fa13accb72282526ac1e28f285f18f4e449efb717850369313139477445e5f16e58c14d5fb079d1e95a4c15f9bb6baaef2e82cfb97712dcbcef475efdca635371ff35d5e300090491f05aab135cfff964324889a26d9a999c5644bfab36a5f2b900df8dd33260f70ff4685c212d423273981adabcb2d89ab22068042fda0beb26bffb730348f31d3d739eef8ec6b6f7e799362b07a29726bde1d02cd6fd231852f6d0df84695832f70b69df9f724160bbde1c8a99b1f8014f200f6752e90265361f4c1e55a576e58a0a1ada06c73c69f77b4076a93404329f96cc0d9fa7ff77bfd5e457f1caf71688c95cbef6e9022b8b83e8888c042d9e2f0fb1a9bbc456a8b07a9572f1e4fafdc8d699438a9bb296be96007d6837a96593348603378036a8638aa191f7164617e732af28d26929f83245736740228224f3d42f71eafba39c8f4faa94a01a589f51929cdee85a28b7895f760806617572612083635c080000000005617572610101a2f595ba846acda337e0a6891b3f517de907a15ae96920676e7aa300c603d903091eb30a8613fa54a684f1aa488272ebce844a5f9c44a0c56990c60f6149e882130000000800000001f50700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fba252d10741845a93492f9ad5fcb431e93c51be0f71ea6a8bd164093d4fc9f06df0b8c4567e4808dbbde228cad475e6060a923c214a5c4636f556eb42a7a4ba91f04569f398c5b44d0aa5cb46a0e95f958b5a3e8352da4092daab9cd8c4859bfcfd415dec6748a823e1141a8ea36b283cf6384420f9bfb21b22579a580402394ecccae790c40a892f3417def8c361ff5a86b33779c74316c77c548c4612fb838c362ebc4332cf2a926e56af72339da43cf00bfcf190ab013df11fc575919858136699ab86fbca58f8f5aed50b70c125329af294571a4e1172e3d25b8d1c0894079c48d94b4f1140f83e68d0517db11f4d29ed81a93ba725c0516ef4d81f2543579a1430ae725802df267fe527b925ac709ea5f238c6b87cfbe7ecaf751855e688903d303d17a8af293a6d14b5f06dd6ace4d5a465ebc84d372da88b1d00fba851237864c8b00f9c1ba4d0f26ecb08528e3c156de07ee51cdbf6fe50c332a727d46f303f688f8ef451f0175c1f5902c4200a374026f4e1b73706cf03f33dc1a1a2fc0877346cb0c06617572612083635c08000000000466726f6e88014968c2fd312c9100837a25718581fa2438136bfce75918d9974e85bab2d26a6300056175726101017a5c5bd08201a067464db34a431fdd575008542f500582eb799c010c99c28f325a1092ab8501da0a0e9dcb07dc7eb849c6ade998b512282ce5786fe3b84d168c140000000900000001f70700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fe0b5632409e78aba11d0d559c96acff0247194879afad0741ae3848f61e74a02397ee4075a31cdfdf00c080c5ae192e9a8921bf216eec48cd06a0ec54f3728e627e68a325aa980ab408e110b842221f262f65fb9c9ff3bf2526d7a454e1f5bb31c4957f9abac96eaee0b0e4592b60fc6abf498f2b216099590c593fd2d6956525001477ea73bc4f27bbf93246c8ed89995f7e69d9e003c4b80bafac51cba1b7a8669945a41c3693a006a1cb8a9000b443f45cc4db5b1ccdb50ea7c8d7330768b445c9a763b1e6309dbc77d14e1e27ddb362cce7ef9e4023660b6db4e520bde91088b3f2153fdc8dedb22d5984ca5773ab7e794d3a9a6f700d1fdac1e54ca83a9455b24f78b25d8dc601e67cb9c1a60ea2e7e907576b4579a091566ce062d7b9fe90294e33d043375a9630b8b43a8176a4b8620e3341e1e22e9ea344ca3bc8b6d989e86ea5900c0bac6b93a608c8ed70554e38d2a513b20072d9f2556008e9cd917066c54e371326902973308c86b18c426b4dfe19c571cd158847f719b5b23daead339f4889a0806617572612083635c0800000000056175726101018a4d75e4d4af8ea9dcd1ee5c8d18659be74f9e3dd4af5312f566705f7fe08f03b6453775880c2d6cc9552f0c99462de4845de92be70ec21d537143377fbf4e8b150000000a00000001f80700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f30cd84fed2904f8f55b7c41d0638af65a209ed81abf76645862f999e1900273d800e3bae3d3c043d319ff1b86f66954e336dfb77f4f62e2cba632ca321a19edf3f600c334e02e5f6ffb1b0a482e3d1ff6e8da744cb98ff90f4e32f87f038cc682462c29e53670de6368795f8e42b58b565b4b0edaee2dab8a1b9e7f2c5b485b4ece9de94fb93817787e0a41ec5703599be3d47882754fd9de578996f9c23f441060bc13cea84d987edb6859731738ee0046151a27ca5d150d89d09dfc33c7b8ba56677abec76be5329edd0100932d6ad28aa84ec94ee0f38fa28dcd7428370186c113fd2ca1d93c9c59e2047eea8b9d0c6d52e15c73398d58433ecaf2c85274ccdc35e3d74de2af6b8b3a2af12ca02f9f14c0a168519ed83cce058a4409204c5e9023d756ae602b1db65712f5c037388b6162cc31b73268aff4572f0b02f706d67145aa38d009ee4491a3c7e2d63f2f2bdc16c70244435ca7f4b86b282a82aec767fac5f02f7489304e192850975b4acfbd1825ee836b803947e5704b6dd1ff7c59c44adaf950806617572612083635c080000000005617572610101d8fd11466fd2a4ea198104ce9c2d273b49e32dcac2900be2313085c941ef415464e0f1fbd3431900521af212814b266900a8a95d2ba9fd74db69092891ef1681160000000b00000001fe0700002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f007a3643f15ab58061fe650dddb783e26ac918157f6414ceafa7027500721f43b043862b71d1dd71242ecaaeccbfb79a5d742557eb7ea49c6f6c9f8a4d0f3057c7b4ea49c745843988fc3e575d3afe2433ae789f87be82d6aaca8f8eca674e4c2c7d5e2ea06f6b1768ca7fd09ae4f4b40866331a4da0cde2d5092394f50c7f58500047700c9e0db25489c6f932e40c357e945914ee47ca84666410e7d5f4320731aa082085acca46517147f2925244a62cf6cdc12e0d1dba722ac51ebc034988c2d2ba2d6a33d8c373a69d92f154d8b404c16f73fa872a864488126f84d3168b3f0f212826e2b9c0c9e49fcdd3d099bf3ef8d93b11c71abd2601da09acf1f8fb139a332d9a5601f727c537d8edba21117d7be408a87d6b9279dad0d31da1dea289033d2444574e1a1ea77abf1d1183b53dd83993c39fe1c82e21a2c331e84673c2d10e7e0400e6bc71df504d3496437a3c4851298d686579cdcd6a0eb507d3bb3807b10b483abda468fe3b2e3f5c42ee4223b991b3a15f0f52c7e847691a692c36791391980d0c06617572612083635c08000000000466726f6e880127856dcce783370e0f2f8d245e6962e65da80ac30e97fb2fbe2fed1b290f77910005617572610101e6ee0eb043a6b5ef5dd2060a1280d0c51cb63c1cdd386c77f1b03fbfea95741e8e4f7f250c9e031120c116a69f5091690ac472b19bc082d2b8955711c28d508d180000000d00000001000800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f4c31488e9125c86f7c78b1174f11bc54e10e24d63b075bf9edb77d7a521b8a7c72aa802d70982fe9261673dfbde97a4e0085892f7e826784a62e974449168d280cc6780f3f88b07267c5bd28035c1ea8d3c7d57bd46c7aea9f3686d5dcbf0e5cd67ae5a926a93a34221dfa7965bb1bf5bdfe550eb4302b952a19747373223929defaaaac2a4312b976551770dd90b277a0fea279f4e133172053e186276ea502a4c071f88e10a9fea38468083ada172cf4c4b71fb676cc911d061b1182557483e9bf72a5cb0332c44eb9f1c9e7621ad6edb850cea38d14550d91a326c4501a9201ed82c8a883f444d0300947104879b646d005468d5cf853725170f9b358bd5ad59c197ab76485206c59f36a6036994dc543aa252b11a3c056814311d27f1404e9024f2df319184d0dfbb1db4f4259dd717a98a856adc2a8c8912efcc98fdf4abdfa2e002d00399fa53f7c1f185d8427c874a8f23b13afc475e5cee1f760d46974e71a54ccb2d353e4aa9bb6d3b4a641c9472a2faefe5adac9bd04b36beefb4168050b77a7f70806617572612083635c08000000000561757261010132730fe531d29230b22a9a0935ef83d80e40aff7efd8db34ce12604e23492d3ca79135c8bd2a4df2849c018629b7aa544222db8ad821d0f04720ee11e255a48c190000000e00000001030800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f48c1b5e26d4c749374e8f69ead4a5b953951cafcbc541903b9ff0496ce82796005d8114aa42d92f25e4aab90e7c0e5bcaf8e0b27364123b0a0442634aa351e0df5c0abdb50c18e3c20d85c1c0d85ffe9fc4b3d2fa6334a888f89fda67fd34125075303600635530959cfa99e3dab3e4e48746930e6c4b416c857d7974b5dfd703c0ad6dcc383bf87f8204dd9c4204e7586a5048bf43854682d5e6753e824c0358e2e245bd9ffa05c64f572f7ff97753222ace44ed6d7599503a4d5b0bb4674819ade986462caf64eb706b9dc9cac0f23d5a9ac8784f067d43663b936525e949c99823eaf56a8d5abc4a8efb4cdcf6f72330a94b423b765fb0267f6be2fd343d2e68724dfd57dc416a9e429bfea4c4265c30f8f3420f1dc6e7ee002b7d73edf0de9027005ab65c401c5eaafe10bac663bad41901d29b6303401251affd054103b251c22b24400e801ed65820b0cd6e8deebbac787b371c89060775a0decfc2a7112b01c51ef7c1e616da83f89c8305d421b535e08d1b33570743958df7c712c4adf9c96cddd330806617572612083635c08000000000561757261010146354b89f12715e609b5e0a272b1e790070fae3c06593211bcb1fc0c40d9806fa21be4cec44a86a847982f6598f92ea1895acbebe39ee857012ef88fc80eb9891a0000000f000000010a0800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fc00ba151fa23caf3a11590fe480f8713e26df35213521f26010a50d2da4d7c7356448cf00a57c405a2759052775a9ab72ef3975fbbfaa8c972b777958774db6c053837856a44c2ba69cb62bb627f28404b1de1f564f254f1a9b3d34d5b698f697ca16b1f5bb61e5b6cbbc9414a5bf0621ed419b43cc72ec90edd901a648d92f0b65504c69ee4a2793b9566effe4685816cc9e9b5afbeb574617a44181f56f84e9485fd594e5a541005820e3fbdaed7713dcedd4ba1c39ee31e386cdab27ea68b95d5c4474278dcea31832321ab93eb6b9d8f2cb5fb75a8e03cbcfba0dc6d495a900f07dfc7cfa4567f2f8ee94fb18793c79d46f233331913516c82a8ea379095c06172eb64373f8e6e622f61cc963278de4558aebbfcef14e1146d85964197e89501ef226b220d0a9f3ce159cd9fe3ce808391b3b90055c3960918277984e45f4c249e9a01006070e574c692886332a9cdfdd2bdbd972889c16e16354f1e470e52bf36db81d2bc7851c7622decd220c83b3668e028145434255ec44cd50cef9c25e5b868e92c001f0000001400000001260800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9fead4b15c40389b68b80da252f1c64ccdff8b7deda378cad3bc2040df93bc0b5e7252eaa94a54db1b10710b81df91791d58a801e17ac7e6c5be9558f6ddbd3c555b19f587cb3e1436049a1e54564e1539f558a5641e3822542ef04a8029f6c03c7cb08847b3ec399f336d9532afb4a312db3b0226c3e740d12b274eb378aec9f87c169bc67da0715fb92c783b5b8fa646489179f8bf7e4a4271fa7d6edee05e74605c69aee2333339e8a08abafeb357a0281a206a64d184dceec971d6d55f248b741ed705f509eb20a558dd1772cd34ff1359d80a4c176982169f7a7f44e8250f9027b2b9d48451380121cdfc5a041491c3fb5eefc07009d1aee921df7a0599bc01034b5e0124a5042e25c42f67aa1303afd59571d83ea865eb1a3aee512467d6e9025befd6bf43197109f64511896b4ab95b2bd221e6810459242b4053c8284686e822d3e200a8f49a3d18ac36932f3608c00c7e004147b3868fdb6d9fa0d431f2ac5c180bbff6f0a28f55b64489087c1e2e91989c0b886fa55e5ca22989fec8626754b531320806617572612083635c0800000000056175726101016c40c4eb4eba5a7917edb38b4813bd5160442f44c16f743b1ab83fbca0aab837686b92638dc57ff1280973721c79102295a43260c4dcd93a6b6dea614077ff812000000015000000012b0800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f7ec2f69737ac6304d9b45fc7bdad23a6e8b6b8c35c77226a9270a105205d5816f8f871b0e0903970117dac29c057cd2d5090f7381019703f20ef82e38de9484d4487ceaa35c2b85da43887dd27238c4a77a28d76326c0bb44244f2fd51fa8ec2298c0bd4ee975e581149da8c6b891bb511a5ae7f5b1794b58a133adba5beb7a75ec607a812b0ef58815e341279396158ae4d5234e03f750c7039bbf2b8b31c2ec4e675c6f7dd170b2fb31de633e340a39fba599e45cc13275825f7c505bc8088f059c6fb23e2e6148346053ee02154cfb8f1eae02f04419e59d9e23ec672d96610883519517ecee74ee2ca4c3a7d47ce8dea260ccfa1542ec8255ba9eca949a06f1c283dba5bef677dbbbe5b25fe5171ac3f48bebf64747f70bb7f689c57ecade90254ea986a37d66353e404a2f75415fe3470a1b41fd447a220d33953b9284c16d83ea543000d8737c8c55fcd4c37ab87b7558e052dfaeacdd7cb168de9064e583276e1a7046e68b68199397af29e1c83f02e4083890c222e4da2b762463e62a870cb327adb0806617572612083635c080000000005617572610101e6ab5f369c205dfda284a6744f7a74a83d94369526a4126c20161c5d273c81053f65482be2ac6e762d95a8a5f0e0474385b10e52bcff0b4e2116d9128108c6892200000017000000012c0800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f8eabe2f87cf524db949c7ce4a300c9943d5ce7cb61eaf056407256614567cf6808599ad0612f70b2011e286f46bb6499e08a50a483fd8f6dd0af19e1596945f877d87e4f34c7c2a362ad3ab7860336ef91c50037eb839a57418f6989b347c701e0b8c7d6d75a732aaa50c303c7c411e87bbb8479f3afc1e31fd7713002d275bb7a0ca5e2cb228b6e38d1806487741429d1dc70ee946a51c3ba3285432ccd0b3dd07663c15018b3931889fc09646632be942a7ec9e4764e9aaeb031663f6fc380401dfb396fe48d83b1d0d25eab92e1dcbc61365db4fc2178436ef2b7b9ab29b50cf12be580eb72539ac227b4fe392c8868b2feb21de9067224d77fae5f6769e46c526f8430531d3b87d1abd12f8df54b0fe7d2e5c6c68c7d45c88eb6a8266ad24903339562159a30ba82f5e54db05cfcf1b517c911492234e3698b3a25410f07a55beee0af0018bc9c144df2b89cf89269120ac2db451dca153e63509b970e86778f340bab743fa69abcba4b75ffe19442f27b7522589c8dca513b1bbfa2ceb2dea2358fefb908066e6d62738088197ec695687b3c54268b18d071a0ebb4323e84179ba664894124beefc56f52056e6d627301016609447513272ebcb0271154883e924a6165a990dbe10ac8f63c33dd71124432c2bb3550926a2c1edf8d3f392c22046af6f1e86347864de3465660969b55a3832300000018000000012d0800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f66064f3465604d64246bf810b9fe8e49a1cf470be7263b11a699edc83a9f352d4c87742fc24e8aeb858e82d633c5d21aaf645efce69123d14a5bf10cf2e87b829b622d5143362571270ca3f4f451bda43be9f870145afb34e3c3a25c97960a255c35ea9c5f11d8b5b87d7eca9b38db17bba211193fcb4df872dc755078e4bdf096a0b786e3efadc128eba8062eb163b018b1763689f1765872b64a88a4c3103c4796565257c6f24d0ee68e26627947ae8d033d2dbabd19d2f6796fab2a3488814af90dc6120f52e8b176ec45828b93ab9ed570537cb311f3006eb17a4b04add9aaf8b111859be1b3d33934ee9bc8a6fdc2bd674f5236099d42bf469cae8e9dde924db656504ab1637673c75be1d51e171906c6e8c12c51a06f6f89e712f7fa19e902f00e5727bb4eb555d4a4f5140b82f4d72ded6eff3506cef9f2eb8b57e36d13f732d41c00310b800ce61efabc623f074c2f728d6c7c2cfbd7c2be565b5bc4943a7a3bb2794ce3e5a2cae2afbc804926c92b104ba1e8eebb2fae209f6b244ced22de968a4e0806617572612083635c080000000005617572610101e81f196b8d3114ddeaf9eaf15d34be8e9ff43db5e25d1aad1774d6a819da913919f6f0b6a16dd668e2880547df0716338b7d0a8bf27203dd8b8d03c90adea88f2400000019000000012e0800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f723e5619fa775cfa0b85ed1f73557d87bfaf34c269c00141e20a8e4fede6c76c8148b38f39593ea9b1d336460e35f225c624104259c9e8338b6f7f74322dd1b8e18091df02039f421e89714802f6be2f2652d714893299798f6f059c5a37dda0fba4bcfd13dbee90229d0771f973af9995468146472ac6aeb912116b4d8bb5ea147bfe6f234a1d1f7bc9d4f10656648269075b7268ae6a243a1a745c7c85f077f2a0fc35b52911a0984a7d2d8641e25952ec16186995a251213ce8c932d3008a05530d6923ca084b8837f15cc4d4dc0bb3c63a1921fa15ec9556ae3ff9d359b1647a0094fc9493f16cf2deb282b73f53985acb3177cfcec28a6f455ef0b43a6eddd7bbebc1214a12baf9cc2bd7a46fbc1e5204d6c192fab5db3fb53c0304c3a6e902acc812718e754faa8f3b4979311ce24e7a9510bbc1160e124e881180d6f678c2debb20001d57ebbdc54d39ebc6c72bda6004b766154578ed841d357151fe9f7b7d80c42d43197f6f6983c8e53d10ce0384d8830a6f1b2121958dab34edff2a7849253d620806617572612083635c0800000000056175726101017e927e09f71f3a2af25d161aff972a98f588d4f42f84a28d295488cc2ebbe024223147f58a7afc121215ee03e9300d5e11058ae1e7f0e80f675967c1367e8e8b250000001a00000001350800002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f1c3964b2c71a0c5fb5c51687909a83cbaa4e94b907d2518bad392fc9a5a7ab7fe35190789786820fddab97684b50b4e8ce0f3595106ab256593fb1cae9ec8c9656a2f0d8e332d511e9b80f31f01eb57248a1a68fc87f8745b71b7d3c8f81982981df3189ee192b2a32bbd331fe28d70851d1d41462957f232206c470fe7c8e0be6133da394c2bb60467b7764e7648794626315f4f2ca7028fe235b0e9b47c33a974fd23cace0213c08cb20565b61ca4bcb3c0e5cdd4a20cf290b0e58d814688d0f34ff3fc478732698c85d8ddf6d0b5bd428df77b267a77999f6ab24b9728458cffa7727cd0ea5c03a212dabe51c377dbd53f0e7f0a1ddb79033fb3d7b73c1db5558455d3b6442a3da1f7827a645952460ccc5c3dd691bcae64a2d2668fe0097e9020425014b3839b51b99be80baa9dbf1a3ef01662a7ea51d6cb2da23814ae835357270a9006870fd9e022c9ac5b4de06bc198a481ae60f3909b8affe519a6c04ffed18438f3c163fa7c3cc25faf40c18eae7c40dbd5a27625fdc6646dce86a5773b6ab6d6b0806617572612083635c0800000000056175726101018c3417489b99706c04255a8b8648f2d6b6c701576dfd25ecd1de9d3b1e0cce4970616567b06ab1190b95a95572d61a4ae637ddbf7bf13bf648af9be585166785270000001c00000001050d00002245952bd39fbc912d3bcf6ab6d9c191b5c9df70a4e1da6f670f4761bf96cc9f54de495b57c7c3505c62633f1ac3a2f22be74ed497a5884379e0821607d9173beb5dd269e10d71dc754ce3fac591364afce61b007925730544bf530068087c7d38e3e2cd8bdf7fef72ad23b076e0620ab5d97d3c0a98207dd8b6af8c3becff696cd2c59aefd1a3bd654df00febe502e9f6ba788b8584c7278c47a2983fd8e87bcea2f2c3fdf7cfe328e695f1a111ef699739c5054adcfd40969c673b39080e4dfa869839757fab6bf946cf5695e1a0d9335ad95e445cd5fe9988a1c82fb53c83fedc8cf6b2555199ecc95e7092742c5959f96bce04becaebfd9266f7642c23d76e25771c2505f549b5af7734c84213bd1fd39278fa8f07298ba69da55a5c70899ece96d300d33d733840cfd4035249b50618e4e81f1cd425bd304b0cffc13b8ee902e66008704ade0c28fa58a8abc788e7b1c3ef78710408947dd4fb2f400effb2344a581900c5c07c6729c92d2b6d2dd7b5e6a05c26adc79e138d57c1b74a2d42ff7c021eb3f127490b8eef8e2c1cff1b685fc398f2dfeef4554328f288259cd9de2280e3200806617572612083635c0800000000056175726101017e7b7d496aef40f4e44a8819b2652fe2d64d286288f21f25a08b31f937d82c7ad7a48673092c23d7a2aa5cb2ca8dead00a2ace14361537ef7f338dc51f34bb842a0000001f00000000d207000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc43216af2664480544590bc51a68be4ab955ba1e9d66544fb5ddd87455187539e427c0f0596d61ef285c22dc8d3e2f29bcfa542b00f0474045735c327e32b81a1e7fb4856020ebb9fb922899fe872508d3105d22f9749a0ed5cd6a2ee894140e1bc46ea832c2ab3e85936053ce6b203eb5396a696dc8d4977092004738ed02c4f5504a932e236c018a5ffcb5f0bed02fc84ee8a1d18fc7e02bd2550ea4606e043e4e6018a6818a0dc0acfb0e5ea67880a8e20dc6ef1f7399658c2fd4a7603eac9085a5574ce91797bfb2df82d4939ddd9f8aff952e1ce48b1d261c97088dcd5c86e532283647ac87830d1b136bce5fe3bad5acba67add2a89f005497d93fac9e5d2088e825d7969dad2cc6c04f8c4a6e65b5002877aaf6b7c32d0943efe5e127824089032a6b0cfbd22318888f75e0f1d6fd92d22de5cd8fb974579b68a848ee1a394979c675d0000e427f6dd8407605cc5ecb040ad322f127d040c84616ed6f41af284720d7c4e289ca21a7e551c98bed636f70900a463ebd20ae087491f8cab4b90668e7b3bc3c0c06617572612007c7b810000000000466726f6e8801b1adac139bde159ecba683741d3ecc31221e98eab5a429f3bb3f38c553018ecf00056175726101013c7952f659e18808ab2c763a25fb0c7532634085cec8d495f3030c6c3cb5b05722c5143a5dd1fad4f7d84eee22196d845e2e1309a48eba4859aba690c2e6a083030000002300000000d407000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc43228919c055a0f2137f13690e949df8f7d97d322fd990946b698f985989116ba7725269c36d48994939f136e4118d4344b4ec13a8c468ea71c52848177ee200ebab55f213795cf2769a0bc7d5ece59be01b8afc921529684f158ec31c4bc58eca35e4f74b2e3c8decba237f50ffbfe45753135891432ca98e1ce2cd1aafc02e67a80d7bd3ff76b4162eb32a149d0673960080c609ace1755ec2f60d6edcdd79309b9bcefd1b379e2291be3ec29e5429c11bb5269d085ef32e86996bb6086f9528541ab122ce2a5db613f84f3b2f96a7dc6bc53ceaa18f6985f08391a449a440c6332e9b1f9c0b02e36ec11b5167c7e7a00d24222061b4769e03517ab2e2ee6170c55a25e183e83a80c3d4968040681b1d15366669926dbf45ab4d7a84e553e426389065fd3dc2ad1fe4f17b0d3bb1bc2bbb7090363c52706ca6509f14a6a128d85ef337e52d5008cc9bdf131d19272e7143953afec22aa607608e1399048c920388988e7834c68139f2af193d65a5517524c47908fc3a6a6275d581dc9661a5594a853b0aacbe810066e6d6273801ec11d2d43768a4d5d45b5611db0b3298b77621d26742547fe41ce91c0d661260672616e648101ceaf24ecf730d7e039d2de8aa45733921ffba8cb0e7c6f75bd06d2591b901c77b1e807742745efcbd5a57bf08935eb21cc1fbeb85d80ed7c615ca7d35e249a07bf6d39c123ba9ba0bd7e75318040bc489985b106d6c98c5aa5a0811ede08f9060466726f6e890101ff0a22a78c0683078446688ddeebbc95a99b1884cd22a8201bf911cabb18059908309e9c2c6a18f7387da2ee66bf312501f6b7d5c7e5f0b9a17df6a084a87d334c3022239463cef94bfd88bf8317dd9b6cab00f087cb8df769cb6bb2c0d0f1d312056e6d62730101d6fa19a093846a11dbd3b9f26dca8a0dcdba8493d6b346b86486f3c54dd7ec72b46a8f8d7d17f1192788c741d069e068c08eb0c5cc6f6aae09d7548b6faabf82040000002400000000e507000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc432cc8a84b06644b0fd1e790c2de73d66c562f0cd3f6239d81b25cd278e5111266388a9171ad58bcbd15396dde9b68b6aeb9a7a6a5b5c7a47d77603f1889cfde56f1ac40ba6eb288239b7dd9c8af7f2b9fb0795cd5b28820c87180e3d735e7bcc0b51df051cf8f33419ca0f07f31f4e8dd2ad6f62bafdc1f0c47f9b727603cda6ae34a3726a20df66d660d77f1c01195a112535db1e046a4beaef91de906901911dc374d89fe1f805734c6f6144db3152748f9b0541cd6583e6c3d2894e04970082b98df5d914b951c9183bf25117b1a9072768e4df487f8e6ed918e5d6754319322d991a6158947113ff320990f38f5e37f0ece574395edfae743439824efbabd2d04f9ebc499a3c2040abe8fddc43943b8f123b71bb490e5f93c5f6093dad9f58e90231d5d69ba54e8052c9c50daa412c580711b273461ec198a7d5399fbfe614faa5ae8d9f004508b43b4a234f1cb724cce8e3e6b00c81f94bc0f321502794f4616392849994f67c842d1b63e4a18df453da38b88267d8389934b1f86bbb988e29162c71dd480806617572612083635c0800000000056175726101011c43c9dc41050f47d24f799941efe385c0b41380cb1b19fb9910ce4f2c55222a6e836b3aeb5c1c931f9c3e95dc9cd27e1d1585d69e18813f5cb889ecea1da3830c0000000100000000ee07000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc432021071f1207ce1d22e80903c737f4c89f2113bd1c7753de41092a70552dd6c17cffb003d8b5359ae9c274650df76ffdd14c52814ce9b5c17900bc52834e30a83ce59e7d5f163371f5d99dd3784d693dfac0e4eec4fa8accf45a881150ceebab3f42382ac9fa47baae525b7af81f2ba3125302b795ec05b0a3d9b4bca133d3a63e2c5da2612da3f1b3c8f42cd1522f6a7a48d2693d58088e5288694b90307074ef116265b6660b03112547414f40025cc4154c6eb7a6de2ad52e51a55af54b38aa1c54fa1bd03bcbd527584aa648fdd075dc0f8e79c247f5ffb1e6ac9c2be225ea60db84bb8d7ab17120d682d16696c329e958433a449faeb88b1c50464e2e4e2eaa75a54e13a8d890399906e24d00454ec4e709e4f7b939691771a23dab762bbe9023a3389663194f4d084a27298adb033bfd6a263f4d3c4c20a3e6f0e00863cb85196408b005a0aee92f8c6b9858c667067f971772855a47e11daf73a2aab1ae29e5db1dc095e8b6f463c0264370e99ac799f7b717e685218b437e4f6181e4abd56e1a0c0d60806617572612083635c08000000000561757261010122106da90907f90dfa17d639b60ab9d3aabdd7ea95daaeeccf3b4dee072fce038e61f86d7189ee821aec9a11d6ecc5c484d12229183553ca27a9d20f668af9860f0000000400000000ef07000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc4327ef0f295b0fdc0c650ffebced6be72c4dd0e1176da30930364fdb01a6713d036f0831c577230924e9326d19f346e64c95e84193c52352eda105704b09d804268b5a6b9e2d162fdb73841ea9372584d9699f321a111fe6f470b89b99b07d8431bafca0227eea00a532e1484c02ec006fa6495eba21abc438584d76f48802d0a910a508a2ee6c3f905a242e92b41e1bb438c7a083504fda57a1849ddd35896c47d78a5956fe6c7531a3c8ddefd9d17c1284ba60dac9040aca954dec18f365c9b8fbe455b67d90249d0096cd35843e3d71badc12c49381bce7312852dec8a1eb64498bbf023fc9e033deee0b54e1b2e1ca8e747d2dd9cb658cc07344df196fbd222e9520eab0f4b1fa37fc4afcb93a1c0ace0cde92aa0689ebcb0c5d1245bcd2c10e9028a0cc2de076fcc8655cd5cc9ae23351591616aded6d8ecab7f2a44e5d624a8fe422bb00056c4bf23b31c58b9773346f043fb8bf5373ded1962c0fec66853e9d754bb642036900eb42f10c8e6021345c5ca5bd1ab4e22e5b8022bf38b06f66425eebf4c5c0806617572612083635c0800000000056175726101011a3c100d29a795c4fbb068012b0291991aa5db384bc0d8c775ffadb60c9a955b4e259391b7f5c22a32ed066345b09aca2fb0b06fb2254ea3723b462ee7ce478a100000000500000000fb07000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc432740beb55375d7c8b84577fbe1c0743965ea389fe7585d6f68975111716c1ce24eb2dadcc3c91fcb5f23b9008ea9c6f934dc261a84b3d1b5b43ae437c6aa992452aea0a684700ff36d0a8b8ebd07fecc353bda87aad24b37c8944de3ce45efcb42fd6220dd68bba2bbc6a3756154fa5cf878797dacda5fe063c16fd8436a46bc160c9820134984676bc4e14204671db0b5494513b5a7fe4df5d0ea250e554a2080e31802525806814554d3a5677af77a3673b500d3539f04afdcfd720573b608ab0b09939c11cbff67c66f806f5221a61fdcfab5c4fa11c0d7ca12988135efa54ed1d69cd74ceee75480f46a7b15b926904d12cdea281aa28e908ec00a9d07cf8a82bcf585ae95386cc747fdf003d5802d9fe7484687bbbad0448af45553ff3b58903174881b8182392f9757efb143ba61d046a0208302a7a7b3df8041059ca538fef169b8d0012fd33199e57825afe600bbfff192f2b9e0f6b72f5da7e8f1f3147e859987cc129b23e32b2278bada2bc2795d7cd6034e3afa8e9a333d01b36b4e2250e583fe20c06617572612083635c08000000000466726f6e88014920420a08c71a4b56953bf3efbae9a45e737f4cea978bdbc470de907d64e1550005617572610101b491cb7862f8d14fb0e2f6cb8d1287b83a55ee7feb468577ca78d9c120fabe5f6b909448827d93de5eb8bd7afe6c9aea63fef46254208969cdab95fe6d02f18b170000000c000000000808000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc432c663d22aea82481985ee77a58f1e54bb8c64fe22c48df15aa43f69aad1c7276b53c400f7fb9008e65a12443807e49224efa495a9cfa03e6ce5298a88fc744478f3820cb625cff327e14fe2006713cce73555c4336a7d861715aa2429a7d349102f0b4f9017440cf5a06a286856c5795b974f5006aa07357854c3eb4c3892b7f5125cb08a3e444ff92b8868bb8d6d64947c84862353870b0e5121b002929c136db31e000378c0f3241a5408a49bc458bd75af5c0d16b35661e212a981ecf5be8d61707c8adc571e83812efa6aa3673e4423c1b56780c437c500583abf6839a68d096600b21370ef6b990d6922acbfc71de68d751ee2d62fbfc49125bd9709ffe33bbce32c9e23b266d24cc175dcc0d99e7c1182b6ce8824d724e9c34ad852a611e9021145925a3f018c9aaba6b3981aaa8e1e24f0c7d9b38f65643a16d3f55d77ff6ca60a4800e3f4f2125d650231ba3ecaa0a51aeda71beada9dad70479d4571b5f2ebfa480c16116166d9e35a66dcadb999da853bfcd1872563f5d0a785507bf67796e568ec0806617572612083635c080000000005617572610101de7241c718f67134d32b6b5656f8e8f3f31e8d29af28043a37c2b49429a082633c8c7abbe502084c28c4a548ac75f97732521c7e66ea9917e704e5469572558c1e00000013000000003808000026662318f64c51f6ed779aa7e32b1b7489bbdb6118812d1b0a2902b7405bc43278b266c6c3ba60a6e648093bcd62419d4ea68eec772f22b7dfc78c6b255dac57bd149cd2b1b166d73be2ed9409a0bebe842c1901d65b1fd3c5f8d98d82ea2a5c3ae10acc8770276d737e6334c59e468be878e6264c050f48ac3e4a6524b76b37c399c4a820e1a50b2bcbc805192c2609e8372e8ddd8432127f6200427dcb2d72386429023e01fcf193d96cc55f1d4d85db5b2f0a9e3a5fb6855c0e07b891d6581ea371a39cb30eaa0ea2d0b9bbe5734e8793d4db7c366277619bdff9611dfb8d4bba89ec371746a5e689ece6f2a117d2b4e7894776b7a96209e7177446c076874bde474bbe3f7bb78669eb48648bf238110f690c144c097988314989bf9b3d4ef410cd043b32cf71a25a00655ab6a0558e383e546c3728412be664273b51908f4903de6835c05f9da3c6ca7fc65846438da7947f93a152f77bb75a85e3d05385feb47e071300b797adcbfd7d3a94ba4b2e0ab454bfded856f759d2b674839d4adcacb72d2c1361edc442862859f46d384db20db351390b2d261be35211db5232e585f985291a08066e6d6273804852f3c0a603d7da194fbff174c24700f3f89d86b462760033bdcb1663c76c60056e6d62730101e2f5f706646cbfc613a98564ff9b4a958acead02fa488e1f3bbcee114d13594d75e9b79d5f5be257e9da81d3ab97cefe36a620a2384f6f1f8719ec2b23532684280000001d000000" \ No newline at end of file diff --git a/lib/parachain/types.go b/lib/parachain/types.go new file mode 100644 index 0000000000..1eba5fc375 --- /dev/null +++ b/lib/parachain/types.go @@ -0,0 +1,388 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// NOTE: https://github.com/ChainSafe/gossamer/pull/3297#discussion_r1214740051 + +// ValidatorIndex Index of the validator. Used as a lightweight replacement of the `ValidatorId` when appropriate +type ValidatorIndex uint32 + +// ValidatorID The public key of a validator. +type ValidatorID [sr25519.PublicKeyLength]byte + +// BlockNumber The block number type. +type BlockNumber uint32 + +// GroupRotationInfo A helper data-type for tracking validator-group rotations. +type GroupRotationInfo struct { + // SessionStartBlock is the block number at which the session started + SessionStartBlock BlockNumber `scale:"1"` + // GroupRotationFrequency indicates how often groups rotate. 0 means never. + GroupRotationFrequency BlockNumber `scale:"2"` + // Now indicates the current block number. + Now BlockNumber `scale:"3"` +} + +// ValidatorGroups represents the validator groups +type ValidatorGroups struct { + // Validators is an array of validator set Ids + Validators [][]ValidatorIndex `scale:"1"` + // GroupRotationInfo is the group rotation info + GroupRotationInfo GroupRotationInfo `scale:"2"` +} + +// ParaID Unique identifier of a parachain. +type ParaID uint32 + +// GroupIndex The unique (during session) index of a validator group. +type GroupIndex uint32 + +// CollatorID represents the public key of a collator +type CollatorID [sr25519.PublicKeyLength]byte + +// CollatorSignature is the signature on a candidate's block data signed by a collator. +type CollatorSignature [sr25519.SignatureLength]byte + +// ValidationCodeHash is the blake2-256 hash of the validation code bytes. +type ValidationCodeHash common.Hash + +// CandidateDescriptor is a unique descriptor of the candidate receipt. +type CandidateDescriptor struct { + // The ID of the para this is a candidate for. + ParaID uint32 `scale:"1"` + + // RelayParent is the hash of the relay-chain block this should be executed in + // the context of. + RelayParent common.Hash `scale:"2"` + + // Collator is the collator's sr25519 public key. + Collator CollatorID `scale:"3"` + + // PersistedValidationDataHash is the blake2-256 hash of the persisted validation data. This is extra data derived from + // relay-chain state which may vary based on bitfields included before the candidate. + // Thus, it cannot be derived entirely from the relay-parent. + PersistedValidationDataHash common.Hash `scale:"4"` + + // PovHash is the hash of the `pov-block`. + PovHash common.Hash `scale:"5"` + // ErasureRoot is the root of a block's erasure encoding Merkle tree. + ErasureRoot common.Hash `scale:"6"` + + // Signature on blake2-256 of components of this receipt: + // The parachain index, the relay parent, the validation data hash, and the `pov_hash`. + Signature CollatorSignature `scale:"7"` + + // ParaHead is the hash of the para header that is being generated by this candidate. + ParaHead common.Hash `scale:"8"` + // ValidationCodeHash is the blake2-256 hash of the validation code bytes. + ValidationCodeHash ValidationCodeHash `scale:"9"` +} + +// OccupiedCore Information about a core which is currently occupied. +type OccupiedCore struct { + // NOTE: this has no ParaId as it can be deduced from the candidate descriptor. + // If this core is freed by availability, this is the assignment that is next up on this + // core, if any. nil if there is nothing queued for this core. + NextUpOnAvailable *ScheduledCore `scale:"1"` + // The relay-chain block number this began occupying the core at. + OccupiedSince BlockNumber `scale:"2"` + // The relay-chain block this will time-out at, if any. + TimeoutAt BlockNumber `scale:"3"` + // If this core is freed by being timed-out, this is the assignment that is next up on this + // core. nil if there is nothing queued for this core or there is no possibility of timing + // out. + NextUpOnTimeOut *ScheduledCore `scale:"4"` + // A bitfield with 1 bit for each validator in the set. `1` bits mean that the corresponding + // validators has attested to availability on-chain. A 2/3+ majority of `1` bits means that + // this will be available. + Availability scale.BitVec `scale:"5"` + // The group assigned to distribute availability pieces of this candidate. + GroupResponsible GroupIndex `scale:"6"` + // The hash of the candidate occupying the core. + CandidateHash common.Hash `scale:"7"` + // The descriptor of the candidate occupying the core. + CandidateDescriptor CandidateDescriptor `scale:"8"` +} + +// Index returns the index +func (OccupiedCore) Index() uint { + return 0 +} + +// ScheduledCore Information about a core which is currently occupied. +type ScheduledCore struct { + // The ID of a para scheduled. + ParaID ParaID `scale:"1"` + // The collator required to author the block, if any. + Collator *CollatorID `scale:"2"` +} + +// Index returns the index +func (ScheduledCore) Index() uint { + return 1 +} + +// Free Core information about a core which is currently free. +type Free struct{} + +// Index returns the index +func (Free) Index() uint { + return 2 +} + +// CoreState represents the state of a particular availability core. +type CoreState scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (va *CoreState) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*va) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + *va = CoreState(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (va *CoreState) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*va) + return vdt.Value() +} + +// NewCoreStateVDT returns a new CoreState VaryingDataType +func NewCoreStateVDT() (scale.VaryingDataType, error) { + vdt, err := scale.NewVaryingDataType(OccupiedCore{}, ScheduledCore{}, Free{}) + if err != nil { + return scale.VaryingDataType{}, fmt.Errorf("create varying data type: %w", err) + } + + return vdt, nil +} + +// NewAvailabilityCores returns a new AvailabilityCores +func NewAvailabilityCores() (scale.VaryingDataTypeSlice, error) { + vdt, err := NewCoreStateVDT() + if err != nil { + return scale.VaryingDataTypeSlice{}, fmt.Errorf("create varying data type: %w", err) + } + + return scale.NewVaryingDataTypeSlice(vdt), nil +} + +// UpwardMessage A message from a parachain to its Relay Chain. +type UpwardMessage []byte + +// OutboundHrmpMessage is an HRMP message seen from the perspective of a sender. +type OutboundHrmpMessage struct { + Recipient uint32 `scale:"1"` + Data []byte `scale:"2"` +} + +// ValidationCode is Parachain validation code. +type ValidationCode []byte + +// headData is Parachain head data included in the chain. +type headData []byte + +// CandidateCommitments are Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. +type CandidateCommitments struct { + // Messages destined to be interpreted by the Relay chain itself. + UpwardMessages []UpwardMessage `scale:"1"` + // Horizontal messages sent by the parachain. + HorizontalMessages []OutboundHrmpMessage `scale:"2"` + // New validation code. + NewValidationCode *ValidationCode `scale:"3"` + // The head-data produced as a result of execution. + HeadData headData `scale:"4"` + // The number of messages processed from the DMQ. + ProcessedDownwardMessages uint32 `scale:"5"` + // The mark which specifies the block number up to which all inbound HRMP messages are processed. + HrmpWatermark uint32 `scale:"6"` +} + +// SessionIndex is a session index. +type SessionIndex uint32 + +// CommittedCandidateReceipt A candidate-receipt with commitments directly included. +type CommittedCandidateReceipt struct { + // The candidate descriptor. + Descriptor CandidateDescriptor `scale:"1"` + // The commitments made by the parachain. + Commitments CandidateCommitments `scale:"2"` +} + +// AssignmentID The public key of a keypair used by a validator for determining assignments +// to approve included parachain candidates. +type AssignmentID [sr25519.PublicKeyLength]byte + +// AuthorityDiscoveryID An authority discovery identifier. +type AuthorityDiscoveryID [sr25519.PublicKeyLength]byte + +// SessionInfo Information about validator sets of a session. +type SessionInfo struct { + // All the validators actively participating in parachain consensus. + // Indices are into the broader validator set. + ActiveValidatorIndices []ValidatorIndex `scale:"1"` + // A secure random seed for the session, gathered from BABE. + RandomSeed [32]byte `scale:"2"` + // The amount of sessions to keep for disputes. + DisputePeriod SessionIndex `scale:"3"` + // Validators in canonical ordering. + Validators []ValidatorID `scale:"4"` + // Validators' authority discovery keys for the session in canonical ordering. + DiscoveryKeys []AuthorityDiscoveryID `scale:"5"` + // The assignment keys for validators. + AssignmentKeys []AssignmentID `scale:"6"` + // Validators in shuffled ordering - these are the validator groups as produced + // by the `Scheduler` module for the session and are typically referred to by + // `GroupIndex`. + ValidatorGroups [][]ValidatorIndex `scale:"7"` + // The number of availability cores used by the protocol during this session. + NCores uint32 `scale:"8"` + // The zeroth delay tranche width. + ZerothDelayTrancheWidth uint32 `scale:"9"` + // The number of samples we do of `relay_vrf_modulo`. + RelayVRFModuloSamples uint32 `scale:"10"` + // The number of delay tranches in total. + NDelayTranches uint32 `scale:"11"` + // How many slots (BABE / SASSAFRAS) must pass before an assignment is considered a + // no-show. + NoShowSlots uint32 `scale:"12"` + // The number of validators needed to approve a block. + NeededApprovals uint32 `scale:"13"` +} + +// DownwardMessage A message sent from the relay-chain down to a parachain. +type DownwardMessage []byte + +// InboundDownwardMessage A wrapped version of `DownwardMessage`. +// The difference is that it has attached the block number when the message was sent. +type InboundDownwardMessage struct { + // The block number at which these messages were put into the downward message queue. + SentAt BlockNumber `scale:"1"` + // The actual downward message to processes. + Message DownwardMessage `scale:"2"` +} + +// InboundHrmpMessage An HRMP message seen from the perspective of a recipient. +type InboundHrmpMessage struct { + // The block number at which this message was sent. + // Specifically, it is the block number at which the candidate that sends this message was + // enacted. + SentAt BlockNumber `scale:"1"` + // The message payload. + Data []byte `scale:"2"` +} + +// CandidateReceipt A receipt for a parachain candidate. +type CandidateReceipt struct { + // The candidate descriptor. + Descriptor CandidateDescriptor `scale:"1"` + // The candidate event. + CommitmentsHash common.Hash `scale:"2"` +} + +// HeadData Parachain head data included in the chain. +type HeadData struct { + Data []byte `scale:"1"` +} + +// CoreIndex The unique (during session) index of a core. +type CoreIndex struct { + Index uint32 `scale:"1"` +} + +// CandidateBacked This candidate receipt was backed in the most recent block. +// This includes the core index the candidate is now occupying. +type CandidateBacked struct { + CandidateReceipt CandidateReceipt `scale:"1"` + HeadData HeadData `scale:"2"` + CoreIndex CoreIndex `scale:"3"` + GroupIndex GroupIndex `scale:"4"` +} + +// Index returns the VaryingDataType Index +func (CandidateBacked) Index() uint { + return 0 +} + +// CandidateIncluded This candidate receipt was included and became a parablock at the most recent block. +// This includes the core index the candidate was occupying as well as the group responsible +// for backing the candidate. +type CandidateIncluded struct { + CandidateReceipt CandidateReceipt `scale:"1"` + HeadData HeadData `scale:"2"` + CoreIndex CoreIndex `scale:"3"` + GroupIndex GroupIndex `scale:"4"` +} + +// Index returns the VaryingDataType Index +func (CandidateIncluded) Index() uint { + return 1 +} + +// CandidateTimedOut A candidate that timed out. +// / This candidate receipt was not made available in time and timed out. +// / This includes the core index the candidate was occupying. +type CandidateTimedOut struct { + CandidateReceipt CandidateReceipt `scale:"1"` + HeadData HeadData `scale:"2"` + CoreIndex CoreIndex `scale:"3"` +} + +// Index returns the VaryingDataType Index +func (CandidateTimedOut) Index() uint { + return 2 +} + +// CandidateEvent A candidate event. +type CandidateEvent scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (va *CandidateEvent) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*va) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + // store original ParentVDT with VaryingDataType that has been set + *va = CandidateEvent(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (va *CandidateEvent) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*va) + return vdt.Value() +} + +// NewCandidateEventVDT returns a new CandidateEvent VaryingDataType +func NewCandidateEventVDT() (scale.VaryingDataType, error) { + vdt, err := scale.NewVaryingDataType(CandidateBacked{}, CandidateIncluded{}, CandidateTimedOut{}) + if err != nil { + return scale.VaryingDataType{}, fmt.Errorf("create varying data type: %w", err) + } + + return vdt, nil +} + +// NewCandidateEvents returns a new CandidateEvents +func NewCandidateEvents() (scale.VaryingDataTypeSlice, error) { + vdt, err := NewCandidateEventVDT() + if err != nil { + return scale.VaryingDataTypeSlice{}, fmt.Errorf("create varying data type: %w", err) + } + + return scale.NewVaryingDataTypeSlice(vdt), nil +} diff --git a/lib/parachain/types_test.go b/lib/parachain/types_test.go new file mode 100644 index 0000000000..b4eb81ea07 --- /dev/null +++ b/lib/parachain/types_test.go @@ -0,0 +1,373 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + _ "embed" + "fmt" + "testing" + + "gopkg.in/yaml.v3" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +//go:embed testdata/westend.yaml +var testDataRaw string + +var testData map[string]string + +func init() { + testData = make(map[string]string) + err := yaml.Unmarshal([]byte(testDataRaw), &testData) + if err != nil { + fmt.Println("Error unmarshaling test data:", err) + return + } +} + +// Test_Validators tests the scale encoding and decoding of a Validator struct +func Test_Validators(t *testing.T) { + t.Parallel() + + resultHex := testData["validators"] + if resultHex == "" { + t.Fatal("cannot get test data for validators") + } + resultBytes, err := common.HexToBytes(resultHex) + require.NoError(t, err) + + var validatorIDs []ValidatorID + err = scale.Unmarshal(resultBytes, &validatorIDs) + require.NoError(t, err) + + expected := []ValidatorID{ + ValidatorID(mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d")), + ValidatorID(mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e")), + ValidatorID(mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72")), + ValidatorID(mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68")), + ValidatorID(mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511")), + ValidatorID(mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e")), + ValidatorID(mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841")), + ValidatorID(mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12")), + ValidatorID(mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c")), + ValidatorID(mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547")), + ValidatorID(mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237")), + ValidatorID(mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53")), + ValidatorID(mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016")), + ValidatorID(mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848")), + ValidatorID(mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a")), + ValidatorID(mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a")), + ValidatorID(mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936")), + } + require.Equal(t, expected, validatorIDs) + + encoded, err := scale.Marshal(validatorIDs) + require.NoError(t, err) + require.Equal(t, resultHex, common.BytesToHex(encoded)) +} + +// Test_ValidatorGroup tests the validator group encoding and decoding. +func Test_ValidatorGroup(t *testing.T) { + t.Parallel() + + result := testData["validatorGroups"] + if result == "" { + t.Fatal("cannot get test data for validatorGroups") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + validatorGroups := ValidatorGroups{ + Validators: [][]ValidatorIndex{}, + GroupRotationInfo: GroupRotationInfo{}, + } + err = scale.Unmarshal(resultBytes, &validatorGroups) + require.NoError(t, err) + + expected := ValidatorGroups{ + Validators: [][]ValidatorIndex{{0, 1, 2, 3, 4, 5}, {6, 7, 8, 9, 10, 11}, {12, 13, 14, 15, 16}}, + GroupRotationInfo: GroupRotationInfo{ + SessionStartBlock: 15657314, + GroupRotationFrequency: 10, + Now: 15657556, + }, + } + require.Equal(t, expected, validatorGroups) + + encoded, err := scale.Marshal(validatorGroups) + require.NoError(t, err) + + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// Test_AvailabilityCores tests the CoreState VDT encoding and decoding. +func Test_AvailabilityCores(t *testing.T) { + t.Parallel() + + result := testData["availabilityCores"] + if result == "" { + t.Fatal("cannot get test data for availabilityCores") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + availabilityCores, err := NewAvailabilityCores() + require.NoError(t, err) + err = scale.Unmarshal(resultBytes, &availabilityCores) + require.NoError(t, err) + + encoded, err := scale.Marshal(availabilityCores) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// TestSessionIndex tests the SessionIndex encoding and decoding. +func TestSessionIndex(t *testing.T) { + t.Parallel() + + result := testData["sessionIndex"] + if result == "" { + t.Fatal("could not find test data for session index") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + var sessionIndex SessionIndex + err = scale.Unmarshal(resultBytes, &sessionIndex) + require.NoError(t, err) + + require.Equal(t, SessionIndex(26895), sessionIndex) + + encoded, err := scale.Marshal(sessionIndex) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// TestCommittedCandidateReceipt tests the CommittedCandidateReceipt encoding and decoding. +func TestCommittedCandidateReceipt(t *testing.T) { + t.Parallel() + + result := testData["pendingAvailability"] + if result == "" { + t.Fatal("could not find test data for pending availability") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + var c *CommittedCandidateReceipt + err = scale.Unmarshal(resultBytes, &c) + require.NoError(t, err) + + // TODO: assert the fields + + encoded, err := scale.Marshal(c) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// TestSessionInfo tests the SessionInfo encoding and decoding. +func TestSessionInfo(t *testing.T) { + t.Parallel() + + result := testData["sessionInfo"] + if result == "" { + t.Fatal("could not find test data for session info") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + var sessionInfo *SessionInfo + err = scale.Unmarshal(resultBytes, &sessionInfo) + require.NoError(t, err) + + expected := &SessionInfo{ + ActiveValidatorIndices: []ValidatorIndex{7, 12, 14, 1, 4, 16, 3, 11, 9, 6, 13, 15, 5, 0, 8, 10, 2}, + RandomSeed: mustHexTo32BArray(t, + "0x9a14667dcf973e46392904593e8caf2fb7a57904edbadf1547531657e7a56b5e"), + DisputePeriod: 6, + Validators: []ValidatorID{ + mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d"), + mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e"), + mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72"), + mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68"), + mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511"), + mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e"), + mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841"), + mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12"), + mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c"), + mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547"), + mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237"), + mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53"), + mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016"), + mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848"), + mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a"), + mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a"), + mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936"), + }, + DiscoveryKeys: []AuthorityDiscoveryID{ + mustHexTo32BArray(t, "0x407a89ac6943b9d2ef1ceb5f1299941758a6af5b8f79b89b90f95a3e38179341"), + mustHexTo32BArray(t, "0x307744a128c608be0dff2189557715b74734359974606d96dc4d256d61b1047d"), + mustHexTo32BArray(t, "0x74fff2667b4a2cc69198ec9d3bf41f4d001ab644b45feaf89a21ff7ef3bd2618"), + mustHexTo32BArray(t, "0x98ab99b4b982d6a1d983ab05ac530b373043e6b7a4a7e5a7dc7ca1942196ae6c"), + mustHexTo32BArray(t, "0x94f9e38609dd9972bfdbe4664f2063499f6233f895ee13b71793c926018a9428"), + mustHexTo32BArray(t, "0x4ce0e8ec374f50c27948b8880628918a41b56930f1af675a5b5099d23f326763"), + mustHexTo32BArray(t, "0x3a58b8f1f529e55fc3dac1dd81cb4547565c09f6e98d97243acb98bdda890028"), + mustHexTo32BArray(t, "0x982bcec62ad60cf9fd00e89b7e3589adb668fcbc467127537851b5a5f3dbbb16"), + mustHexTo32BArray(t, "0x0695b906f52a88f18bdecd811785b4299c51ebb2a2755f0b4c0d83fbef431861"), + mustHexTo32BArray(t, "0x0ec5e1d2d044023009c63659c65a79aaf07ecbf5b9887958243aa873a63e5a1b"), + mustHexTo32BArray(t, "0x52ef04ed449e4db577d98ad433b779c36f0d122df03e1cdc3e840a49016c5f16"), + mustHexTo32BArray(t, "0xc2d4b5973000d0b175631dde5d1657b3e34c2f75e8a6d5414013ce4036d83355"), + mustHexTo32BArray(t, "0xa6e01665b2d8490abf45551088021041dfb41772a9d596ed6e9f261ed1c8ae72"), + mustHexTo32BArray(t, "0xb436c143e295617afb60353a01f2941bd33370a662c99c040984e52a072b5f22"), + mustHexTo32BArray(t, "0x4c4c4b178f1a3d67e5f26d6b93b9a43937cd2d1d1cb2acc4650f504125df2e18"), + mustHexTo32BArray(t, "0xca17f0edc319c140113a44722f829aa1313da1b54298a10df49ad7d67d9de85f"), + mustHexTo32BArray(t, "0x5a6bf6911fc41d8981c7c28f87e8ed4416c65e15624f7b4e36c6a1a72c7a7819"), + }, + AssignmentKeys: []AssignmentID{ + mustHexTo32BArray(t, "0x6acc35b896fe346adeda25c4031cf6a81e58dca091164370859828cc4456901a"), + mustHexTo32BArray(t, "0x466627d554785807aaf50bfbdc9b8f729e8e20eb596ee5def5acd2acb72e405f"), + mustHexTo32BArray(t, "0xc05cab9e7773ffaf045407579f9c8e16d56f119117421cd18a250c2e37fcb53a"), + mustHexTo32BArray(t, "0xe2dca6ce9b3ebb40052c34392dc74d3cdd648399119fa470222a10956769d64f"), + mustHexTo32BArray(t, "0x7477459916ace4f77d97d6ab5e1a2f06092282c7f0a1332628c14896e8e9be62"), + mustHexTo32BArray(t, "0xc2574de3dc8feebfad1b3bee36a7bfe6c994e5d1459a5372ff447ac32dd46c11"), + mustHexTo32BArray(t, "0xb0a8ed99f1e7ab160e0ac2fcfeee0d92d807c8fb4c1678e37997715578926c5c"), + mustHexTo32BArray(t, "0x6c9bfa7c2e0f8e10a1a78bb982313c5c347a018cb3828886b99e109a8799d272"), + mustHexTo32BArray(t, "0xe6037f1fc5b19015b7089ecf90034349e3f5c37cb50dec5356743614f94f8c33"), + mustHexTo32BArray(t, "0x964b85f2b8e10e859e306d3670b8bdc0cea17b97dfd3edc8a9e1be1f127fee5b"), + mustHexTo32BArray(t, "0x44d421ae62038ba15a377cad85e4ecd3c2a63b54fdbb82c47fb3e9c026405226"), + mustHexTo32BArray(t, "0x48c51db949a58fd5f36a19888986275547b0c2fbb0b348ccb85dfc6c998dbe16"), + mustHexTo32BArray(t, "0x0ae9425710301a9241837d624438a5d82edbbd6bf2cdbcc2694ad7db31ef9921"), + mustHexTo32BArray(t, "0x9e47376e9af08b294901b879c7d658c41386453c6baa7c26560c5fd3b164e05d"), + mustHexTo32BArray(t, "0x8af1a51649d44d12dffc24337f0a5424b18db9604133eafcb2639ddcdc2a7f0f"), + mustHexTo32BArray(t, "0xae7a30d143fd125490434ca7325025a2338d0b8bb28dcd9373dfd83756191022"), + mustHexTo32BArray(t, "0xeeba7c46f5fa1ea21e736d9ebd7a171fb2afe0a4f828a222ea0605a4ad0e6067"), + }, + ValidatorGroups: [][]ValidatorIndex{ + { + 0, 1, 2, 3, 4, 5, + }, + { + 6, 7, 8, 9, 10, 11, + }, + { + 12, 13, 14, 15, 16, + }, + }, + NCores: 3, + ZerothDelayTrancheWidth: 0, + RelayVRFModuloSamples: 1, + NDelayTranches: 40, + NoShowSlots: 2, + NeededApprovals: 2, + } + require.Equal(t, expected, sessionInfo) + + encoded, err := scale.Marshal(sessionInfo) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// TestInboundHrmpMessage tests the scale encoding of an InboundHrmpMessage +func TestInboundHrmpMessage(t *testing.T) { + t.Parallel() + + result := testData["hrmpChannelContents"] + if result == "" { + t.Fatal("hrmpChannelContents test data not found") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + var msg []InboundHrmpMessage + err = scale.Unmarshal(resultBytes, &msg) + require.NoError(t, err) + + expected := []InboundHrmpMessage{ + { + SentAt: 1000, + Data: []byte{}, + }, + { + SentAt: 2000, + Data: []byte{}, + }, + { + SentAt: 2002, + Data: []byte{}, + }, + { + SentAt: 2004, + Data: []byte{}, + }, + { + SentAt: 2011, + Data: []byte{}, + }, + { + SentAt: 2030, + Data: []byte{}, + }, + { + SentAt: 2032, + Data: []byte{}, + }, + { + SentAt: 2034, + Data: []byte{}, + }, + { + SentAt: 2035, + Data: []byte{}, + }, + { + SentAt: 2046, + Data: []byte{}, + }, + } + require.Equal(t, expected, msg) + + encoded, err := scale.Marshal(msg) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +// TestCandidateEvent tests the scale encoding of a CandidateEvent +func TestCandidateEvent(t *testing.T) { + t.Parallel() + + result := testData["candidateEvents"] + if result == "" { + t.Fatal("candidateEvents test data not found") + } + resultBytes, err := common.HexToBytes(result) + require.NoError(t, err) + + candidateEvents, err := NewCandidateEvents() + require.NoError(t, err) + err = scale.Unmarshal(resultBytes, &candidateEvents) + require.NoError(t, err) + + require.Greater(t, len(candidateEvents.Types), 0) + + encoded, err := scale.Marshal(candidateEvents) + require.NoError(t, err) + require.Equal(t, result, common.BytesToHex(encoded)) +} + +func mustHexTo32BArray(t *testing.T, inputHex string) (outputArray [32]byte) { + t.Helper() + copy(outputArray[:], common.MustHexToBytes(inputHex)) + return outputArray +} + +func TestMustHexTo32BArray(t *testing.T) { + inputHex := "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d" + expectedArray := [32]byte{0xa2, 0x62, 0xf8, 0x3b, 0x46, 0x31, 0x7, 0x70, 0xae, 0x8d, 0x9, 0x21, 0x47, 0x17, 0x6b, + 0x8b, 0x25, 0xe8, 0x85, 0x5b, 0xcf, 0xbb, 0xe7, 0x1, 0xd3, 0x46, 0xb1, 0xd, 0xb0, 0xc5, 0x38, 0x5d} + result := mustHexTo32BArray(t, inputHex) + require.Equal(t, expectedArray, result) +} From be2aaea235be0a5d0c6218103fce26f2ad06b98c Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Tue, 20 Jun 2023 15:46:42 +0530 Subject: [PATCH 05/85] feat(parachain): Add StatementDistributionMessage varingDataType (#3316) --- go.mod | 2 +- lib/parachain/statement.go | 56 +++++ .../statement_distribution_message.go | 91 ++++++++ .../statement_distribution_message_test.go | 199 ++++++++++++++++++ lib/parachain/statement_test.go | 88 ++++++++ .../statement_distribution_message.yaml | 13 ++ 6 files changed, 448 insertions(+), 1 deletion(-) create mode 100644 lib/parachain/statement.go create mode 100644 lib/parachain/statement_distribution_message.go create mode 100644 lib/parachain/statement_distribution_message_test.go create mode 100644 lib/parachain/statement_test.go create mode 100644 lib/parachain/testdata/statement_distribution_message.yaml diff --git a/go.mod b/go.mod index 987678a1c0..aa23db609a 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 golang.org/x/term v0.11.0 google.golang.org/protobuf v1.31.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -194,7 +195,6 @@ require ( gonum.org/v1/gonum v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.2.1 // indirect ) diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go new file mode 100644 index 0000000000..09412e2a98 --- /dev/null +++ b/lib/parachain/statement.go @@ -0,0 +1,56 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// Statement is a result of candidate validation. It could be either `Valid` or `Seconded`. +type Statement scale.VaryingDataType + +// NewStatement returns a new Statement VaryingDataType +func NewStatement() Statement { + vdt := scale.MustNewVaryingDataType(Seconded{}, Valid{}) + return Statement(vdt) +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (s *Statement) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*s) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *s = Statement(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (s *Statement) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*s) + return vdt.Value() +} + +// Seconded represents a statement that a validator seconds a candidate. +type Seconded CommittedCandidateReceipt + +// Index returns the VaryingDataType Index +func (s Seconded) Index() uint { + return 1 +} + +// Valid represents a statement that a validator has deemed a candidate valid. +type Valid CandidateHash + +// Index returns the VaryingDataType Index +func (v Valid) Index() uint { + return 2 +} + +// CandidateHash makes it easy to enforce that a hash is a candidate hash on the type level. +type CandidateHash struct { + Value common.Hash `scale:"1"` +} diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go new file mode 100644 index 0000000000..8690fcaa61 --- /dev/null +++ b/lib/parachain/statement_distribution_message.go @@ -0,0 +1,91 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// StatementDistributionMessage represents network messages used by the statement distribution subsystem +type StatementDistributionMessage scale.VaryingDataType + +// NewStatementDistributionMessage returns a new StatementDistributionMessage VaryingDataType +func NewStatementDistributionMessage() StatementDistributionMessage { + vdt := scale.MustNewVaryingDataType(SignedFullStatement{}, SecondedStatementWithLargePayload{}) + return StatementDistributionMessage(vdt) +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (sdm *StatementDistributionMessage) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*sdm) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *sdm = StatementDistributionMessage(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (sdm *StatementDistributionMessage) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*sdm) + return vdt.Value() +} + +// SignedFullStatement represents a signed full statement under a given relay-parent. +type SignedFullStatement struct { + Hash common.Hash `scale:"1"` + UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` +} + +// Index returns the VaryingDataType Index +func (s SignedFullStatement) Index() uint { + return 0 +} + +// Seconded statement with large payload (e.g. containing a runtime upgrade). +// +// We only gossip the hash in that case, actual payloads can be fetched from sending node +// via request/response. +type SecondedStatementWithLargePayload StatementMetadata + +// Index returns the VaryingDataType Index +func (l SecondedStatementWithLargePayload) Index() uint { + return 1 +} + +// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified. +type UncheckedSignedFullStatement struct { + // The payload is part of the signed data. The rest is the signing context, + // which is known both at signing and at validation. + Payload Statement `scale:"1"` + + // The index of the validator signing this statement. + ValidatorIndex ValidatorIndex `scale:"2"` + + // The signature by the validator of the signed payload. + Signature ValidatorSignature `scale:"3"` +} + +// StatementMetadata represents the data that makes a statement unique. +type StatementMetadata struct { + // Relay parent this statement is relevant under. + RelayParent common.Hash `scale:"1"` + + // Hash of the candidate that got validated. + CandidateHash CandidateHash `scale:"2"` + + // Validator that attested the validity. + SignedBy ValidatorIndex `scale:"3"` + + // Signature of seconding validator. + Signature ValidatorSignature `scale:"4"` +} + +// ValidatorSignature represents the signature with which parachain validators sign blocks. +type ValidatorSignature Signature + +// Signature represents a cryptographic signature. +type Signature [64]byte diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go new file mode 100644 index 0000000000..8721ff15de --- /dev/null +++ b/lib/parachain/statement_distribution_message_test.go @@ -0,0 +1,199 @@ +package parachain + +import ( + _ "embed" + "fmt" + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +//go:embed testdata/statement_distribution_message.yaml +var testSDMHexRaw string + +var testSDMHex map[string]string + +func init() { + err := yaml.Unmarshal([]byte(testSDMHexRaw), &testSDMHex) + if err != nil { + fmt.Printf("Error unmarshaling test data: %s\n", err) + return + } +} + +func TestStatementDistributionMessage(t *testing.T) { + t.Parallel() + + var collatorSignature CollatorSignature + tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + var validatorSignature ValidatorSignature + copy(validatorSignature[:], tempSignature) + + var collatorID CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + hash5 := getDummyHash(5) + + statementWithValid := NewStatement() + err := statementWithValid.Set(Valid{hash5}) + require.NoError(t, err) + + secondedEnumValue := Seconded{ + Descriptor: CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: hash5, + Collator: collatorID, + PersistedValidationDataHash: hash5, + PovHash: hash5, + ErasureRoot: hash5, + Signature: collatorSignature, + ParaHead: hash5, + ValidationCodeHash: ValidationCodeHash(hash5), + }, + Commitments: CandidateCommitments{ + UpwardMessages: []UpwardMessage{{1, 2, 3}}, + HorizontalMessages: []OutboundHrmpMessage{}, + NewValidationCode: &ValidationCode{1, 2, 3}, + HeadData: headData{1, 2, 3}, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + }, + } + + statementWithSeconded := NewStatement() + err = statementWithSeconded.Set(secondedEnumValue) + require.NoError(t, err) + + signedFullStatementWithValid := SignedFullStatement{ + Hash: hash5, + UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + Payload: statementWithValid, + ValidatorIndex: ValidatorIndex(5), + Signature: validatorSignature, + }, + } + + signedFullStatementWithSeconded := SignedFullStatement{ + Hash: hash5, + UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + Payload: statementWithSeconded, + ValidatorIndex: ValidatorIndex(5), + Signature: validatorSignature, + }, + } + + secondedStatementWithLargePayload := SecondedStatementWithLargePayload{ + RelayParent: hash5, + CandidateHash: CandidateHash{hash5}, + SignedBy: ValidatorIndex(5), + Signature: validatorSignature, + } + + testCases := []struct { + name string + enumValue scale.VaryingDataTypeValue + encodingValue []byte + }{ + // expected encoding is generated by running rust test code: + // fn statement_distribution_message_encode() { + // let hash1 = Hash::repeat_byte(5); + // let candidate_hash = CandidateHash(hash1); + // let statement_valid = Statement::Valid(candidate_hash); + // let val_sign = ValidatorSignature::from( + // sr25519::Signature([198, 124, 185, 59, 240, 163, 111, 206, 227, + // 210, 157, 232, 166, 166, 154, 117, 150, 89, 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, + // 216, 126, 69, 173, 206, 95, 41, 6, 152, 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, + // 81, 175, 134, 23, 200, 190, 116, 184, 148, 207, 27, 134])); + // let unchecked_signed_full_statement_valid = UncheckedSignedFullStatement::new( + // statement_valid, ValidatorIndex(5), val_sign.clone()); + // let sdm_statement_valid = StatementDistributionMessage::Statement( + // hash1, unchecked_signed_full_statement_valid); + // println!("encode SignedFullStatement with valid statement => {:?}\n\n", sdm_statement_valid.encode()); + + // let collator_result = sr25519::Public::from_string( + // "0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147"); + // let collator = collator_result.unwrap(); + // let collsign = CollatorSignature::from(sr25519::Signature( + // [198, 124, 185, 59, 240, 163, 111, 206, 227, 210, 157, 232, + // 166, 166, 154, 117, 150, 89, 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, 216, 126, 69, 173, + // 206, 95, 41, 6, 152, 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, 81, 175, 134, 23, 200, + // 190, 116, 184, 148, 207, 27, 134])); + // let candidate_descriptor = CandidateDescriptor{ + // para_id: 1.into(), + // relay_parent: hash1, + // collator: CollatorId::from(collator), + // persisted_validation_data_hash: hash1, + // pov_hash: hash1, + // erasure_root: hash1, + // signature: collsign, + // para_head: hash1, + // validation_code_hash: ValidationCodeHash::from(hash1) + // }; + // let commitments_new = CandidateCommitments{ + // upward_messages: vec![vec![1, 2, 3]].try_into().expect("error - upward_messages"), + // horizontal_messages: vec![].try_into().expect("error - horizontal_messages"), + // head_data: HeadData(vec![1, 2, 3]), + // hrmp_watermark: 0_u32, + // new_validation_code: ValidationCode(vec![1, 2, 3]).try_into().expect("error - new_validation_code"), + // processed_downward_messages: 5 + // }; + // let committed_candidate_receipt = CommittedCandidateReceipt{ + // descriptor: candidate_descriptor, + // commitments : commitments_new + // }; + // let statement_second = Statement::Seconded(committed_candidate_receipt); + // let unchecked_signed_full_statement_second = UncheckedSignedFullStatement::new( + // statement_second, ValidatorIndex(5), val_sign.clone()); + // let sdm_statement_second = StatementDistributionMessage::Statement( + // hash1, unchecked_signed_full_statement_second); + // println!("encode SignedFullStatement with Seconded statement => {:?}\n\n", sdm_statement_second.encode()); + + // let sdm_large_statement = StatementDistributionMessage::LargeStatement(StatementMetadata{ + // relay_parent: hash1, + // candidate_hash: CandidateHash(hash1), + // signed_by: ValidatorIndex(5_u32), + // signature: val_sign.clone(), + // }); + // println!("encode SecondedStatementWithLargePayload => {:?}\n\n", sdm_large_statement.encode()); + // } + + { + name: "SignedFullStatement with valid statement", + enumValue: signedFullStatementWithValid, + encodingValue: common.MustHexToBytes(testSDMHex["sfsValid"]), + }, + { + name: "SignedFullStatement with Seconded statement", + enumValue: signedFullStatementWithSeconded, + encodingValue: common.MustHexToBytes(testSDMHex["sfsSeconded"]), + }, + { + name: "Seconded Statement With LargePayload", + enumValue: secondedStatementWithLargePayload, + encodingValue: common.MustHexToBytes(testSDMHex["statementWithLargePayload"]), + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + vtd := NewStatementDistributionMessage() + + err := vtd.Set(c.enumValue) + require.NoError(t, err) + + bytes, err := scale.Marshal(vtd) + require.NoError(t, err) + + require.Equal(t, c.encodingValue, bytes) + }) + } +} diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go new file mode 100644 index 0000000000..ed5f8eb3c1 --- /dev/null +++ b/lib/parachain/statement_test.go @@ -0,0 +1,88 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +func getDummyHash(num byte) common.Hash { + hash := common.Hash{} + for i := 0; i < 32; i++ { + hash[i] = num + } + return hash +} + +func TestStatement(t *testing.T) { + t.Parallel() + + var collatorID CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + var collatorSignature CollatorSignature + tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + hash5 := getDummyHash(5) + + secondedEnumValue := Seconded{ + Descriptor: CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: hash5, + Collator: collatorID, + PersistedValidationDataHash: hash5, + PovHash: hash5, + ErasureRoot: hash5, + Signature: collatorSignature, + ParaHead: hash5, + ValidationCodeHash: ValidationCodeHash(hash5), + }, + Commitments: CandidateCommitments{ + UpwardMessages: []UpwardMessage{{1, 2, 3}}, + HorizontalMessages: []OutboundHrmpMessage{}, + NewValidationCode: &ValidationCode{1, 2, 3}, + HeadData: headData{1, 2, 3}, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + }, + } + + testCases := []struct { + name string + enumValue scale.VaryingDataTypeValue + encodingValue []byte + }{ + { + name: "Seconded", + enumValue: secondedEnumValue, + encodingValue: common.MustHexToBytes(testSDMHex["statementSeconded"]), + // expected Hex stored in statement_distribution_message.yaml + }, + { + name: "Valid", + enumValue: Valid{hash5}, + encodingValue: common.MustHexToBytes("0x020505050505050505050505050505050505050505050505050505050505050505"), + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + vtd := NewStatement() + + err := vtd.Set(c.enumValue) + require.NoError(t, err) + + bytes, err := scale.Marshal(vtd) + require.NoError(t, err) + + require.Equal(t, c.encodingValue, bytes) + }) + } +} diff --git a/lib/parachain/testdata/statement_distribution_message.yaml b/lib/parachain/testdata/statement_distribution_message.yaml new file mode 100644 index 0000000000..e43dd59bb8 --- /dev/null +++ b/lib/parachain/testdata/statement_distribution_message.yaml @@ -0,0 +1,13 @@ +collatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +# Seconded +statementSeconded: "0x0101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c0102030500000000000000" + +# SignedFullStatement with valid statement +sfsValid: "0x00050505050505050505050505050505050505050505050505050505050505050502050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +# SignedFullStatement with Seconded statement +sfsSeconded: "0x0005050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +# Seconded Statement With LargePayload +statementWithLargePayload: "0x010505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" \ No newline at end of file From d08d7f1261fb213d9f7a03de816ed70b5faab666 Mon Sep 17 00:00:00 2001 From: Kanishka Date: Wed, 21 Jun 2023 04:37:41 -0600 Subject: [PATCH 06/85] feat/runtime: Add few parachain runtime calls (#3241) Co-authored-by: Kishan Mohanbhai Sagathiya --- dot/core/mock_runtime_instance_test.go | 122 ++++++++++++++++++ dot/state/mocks_runtime_test.go | 122 ++++++++++++++++++ dot/sync/mock_runtime_test.go | 122 ++++++++++++++++++ lib/babe/mocks/runtime.go | 122 ++++++++++++++++++ lib/blocktree/mocks_test.go | 122 ++++++++++++++++++ lib/grandpa/mocks_runtime_test.go | 122 ++++++++++++++++++ lib/runtime/constants.go | 22 ++++ lib/runtime/interface.go | 15 +++ lib/runtime/mocks/mocks.go | 122 ++++++++++++++++++ lib/runtime/test_helpers.go | 3 + lib/runtime/wazero/instance.go | 171 ++++++++++++++++++++++++- 11 files changed, 1064 insertions(+), 1 deletion(-) diff --git a/dot/core/mock_runtime_instance_test.go b/dot/core/mock_runtime_instance_test.go index 0a1f6a7478..e671aee681 100644 --- a/dot/core/mock_runtime_instance_test.go +++ b/dot/core/mock_runtime_instance_test.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/dot/state/mocks_runtime_test.go b/dot/state/mocks_runtime_test.go index 83be8da0e1..4029d21a02 100644 --- a/dot/state/mocks_runtime_test.go +++ b/dot/state/mocks_runtime_test.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index f1a583b884..36b3842e3c 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 369e51c6dc..2701d8022a 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index 7c5eb64806..b1ea7d6183 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go index fc0360e78f..236cea4c69 100644 --- a/lib/grandpa/mocks_runtime_test.go +++ b/lib/grandpa/mocks_runtime_test.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index 26d0ccd267..ce70426c81 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -22,6 +22,12 @@ const ( WESTEND_RUNTIME_V0929_FP = "westend_runtime-v929.compact.wasm" WESTEND_RUNTIME_V0929_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9." + "29/westend_runtime-v9290.compact.compressed.wasm?raw=true" + + // v0.9.42 westend + WESTEND_RUNTIME_v0942 = "westend_runtime-v942" + WESTEND_RUNTIME_V0942_FP = "westend_runtime-v942.compact.wasm" + WESTEND_RUNTIME_V0942_URL = "https://github.com/paritytech/polkadot/releases/download/v0.9." + + "42/westend_runtime-v9420.compact.compressed.wasm?raw=true" ) const ( @@ -62,4 +68,20 @@ const ( TransactionPaymentCallAPIQueryCallInfo = "TransactionPaymentCallApi_query_call_info" // TransactionPaymentCallAPIQueryCallFeeDetails returns call query call fee details TransactionPaymentCallAPIQueryCallFeeDetails = "TransactionPaymentCallApi_query_call_fee_details" + // ParachainHostValidators is the runtime API call ParachainHost_validators + ParachainHostValidators = "ParachainHost_validators" + // ParachainHostValidatorGroups is the runtime API call ParachainHost_validator_groups + ParachainHostValidatorGroups = "ParachainHost_validator_groups" + // ParachainHostAvailabilityCores is the runtime API call ParachainHost_availability_cores + ParachainHostAvailabilityCores = "ParachainHost_availability_cores" + // ParachainHostCheckValidationOutputs is the runtime API call ParachainHost_check_validation_outputs + ParachainHostCheckValidationOutputs = "ParachainHost_check_validation_outputs" + // ParachainHostSessionIndexForChild is the runtime API call ParachainHost_session_index_for_child + ParachainHostSessionIndexForChild = "ParachainHost_session_index_for_child" + // ParachainHostCandidatePendingAvailability is the runtime API call ParachainHost_candidate_pending_availability + ParachainHostCandidatePendingAvailability = "ParachainHost_candidate_pending_availability" + // ParachainHostCandidateEvents is the runtime API call ParachainHost_candidate_events + ParachainHostCandidateEvents = "ParachainHost_candidate_events" + // ParachainHostSessionInfo is the runtime API call ParachainHost_session_info + ParachainHostSessionInfo = "ParachainHost_session_info" ) diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index 6e14a27d80..473710cd99 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -8,7 +8,9 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/transaction" + "github.com/ChainSafe/gossamer/pkg/scale" ) // Instance for runtime methods @@ -48,4 +50,17 @@ type Instance interface { GrandpaSubmitReportEquivocationUnsignedExtrinsic( equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, ) error + ParachainHostValidators() ([]parachain.ValidatorID, error) + ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) + ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) + ParachainHostCheckValidationOutputs( + parachainID parachain.ParaID, + outputs parachain.CandidateCommitments, + ) (bool, error) + ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) + ParachainHostCandidatePendingAvailability( + parachainID parachain.ParaID, + ) (*parachain.CommittedCandidateReceipt, error) + ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) + ParachainHostSessionInfo(sessionIndex parachain.SessionIndex) (*parachain.SessionInfo, error) } diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index 22f87dbeff..9db3aa6bf0 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -11,8 +11,10 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" + scale "github.com/ChainSafe/gossamer/pkg/scale" gomock "github.com/golang/mock/gomock" ) @@ -338,6 +340,126 @@ func (mr *MockInstanceMockRecorder) OffchainWorker() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OffchainWorker", reflect.TypeOf((*MockInstance)(nil).OffchainWorker)) } +// ParachainHostAvailabilityCores mocks base method. +func (m *MockInstance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostAvailabilityCores") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostAvailabilityCores indicates an expected call of ParachainHostAvailabilityCores. +func (mr *MockInstanceMockRecorder) ParachainHostAvailabilityCores() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostAvailabilityCores", reflect.TypeOf((*MockInstance)(nil).ParachainHostAvailabilityCores)) +} + +// ParachainHostCandidateEvents mocks base method. +func (m *MockInstance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidateEvents") + ret0, _ := ret[0].(*scale.VaryingDataTypeSlice) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidateEvents indicates an expected call of ParachainHostCandidateEvents. +func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidateEvents", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidateEvents)) +} + +// ParachainHostCandidatePendingAvailability mocks base method. +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) + ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCandidatePendingAvailability indicates an expected call of ParachainHostCandidatePendingAvailability. +func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCandidatePendingAvailability", reflect.TypeOf((*MockInstance)(nil).ParachainHostCandidatePendingAvailability), arg0) +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostSessionIndexForChild mocks base method. +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") + ret0, _ := ret[0].(parachain.SessionIndex) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionIndexForChild indicates an expected call of ParachainHostSessionIndexForChild. +func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionIndexForChild", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionIndexForChild)) +} + +// ParachainHostSessionInfo mocks base method. +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) + ret0, _ := ret[0].(*parachain.SessionInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostSessionInfo indicates an expected call of ParachainHostSessionInfo. +func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) +} + +// ParachainHostValidatorGroups mocks base method. +func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") + ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidatorGroups indicates an expected call of ParachainHostValidatorGroups. +func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidatorGroups", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidatorGroups)) +} + +// ParachainHostValidators mocks base method. +func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidators") + ret0, _ := ret[0].([]parachain.ValidatorID) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidators indicates an expected call of ParachainHostValidators. +func (mr *MockInstanceMockRecorder) ParachainHostValidators() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidators", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidators)) +} + // PaymentQueryInfo mocks base method. func (m *MockInstance) PaymentQueryInfo(arg0 []byte) (*types.RuntimeDispatchInfo, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/test_helpers.go b/lib/runtime/test_helpers.go index c6a5e836ac..66b65d0131 100644 --- a/lib/runtime/test_helpers.go +++ b/lib/runtime/test_helpers.go @@ -78,6 +78,9 @@ func GetRuntime(ctx context.Context, runtime string) ( case WESTEND_RUNTIME_v0929: runtimeFilename = WESTEND_RUNTIME_V0929_FP url = WESTEND_RUNTIME_V0929_URL + case WESTEND_RUNTIME_v0942: + runtimeFilename = WESTEND_RUNTIME_V0942_FP + url = WESTEND_RUNTIME_V0942_URL default: return "", fmt.Errorf("%w: %s", ErrRuntimeUnknown, runtime) } diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index d9144facf3..becba84778 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -16,6 +16,7 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/runtime/offchain" "github.com/ChainSafe/gossamer/lib/transaction" @@ -818,7 +819,175 @@ func (in *Instance) GrandpaSubmitReportEquivocationUnsignedExtrinsic( return nil } -func (*Instance) RandomSeed() { +// ParachainHostValidators returns the validator set at the current state. +// The specified validators are responsible for backing parachains for the current state. +func (in *Instance) ParachainHostValidators() ([]parachain.ValidatorID, error) { + encodedValidators, err := in.Exec(runtime.ParachainHostValidators, []byte{}) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + var validatorIDs []parachain.ValidatorID + err = scale.Unmarshal(encodedValidators, &validatorIDs) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return validatorIDs, nil +} + +// ParachainHostValidatorGroups returns the validator groups used during the current session. +// The validators in the groups are referred to by the validator set Id. +func (in *Instance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { + encodedValidatorGroups, err := in.Exec(runtime.ParachainHostValidatorGroups, []byte{}) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + var validatorGroups parachain.ValidatorGroups + err = scale.Unmarshal(encodedValidatorGroups, &validatorGroups) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return &validatorGroups, nil +} + +// ParachainHostAvailabilityCores returns the availability cores for the current state. +func (in *Instance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) { + encodedAvailabilityCores, err := in.Exec(runtime.ParachainHostAvailabilityCores, []byte{}) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + availabilityCores, err := parachain.NewAvailabilityCores() + if err != nil { + return nil, fmt.Errorf("new availability cores: %w", err) + } + err = scale.Unmarshal(encodedAvailabilityCores, &availabilityCores) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return &availabilityCores, nil +} + +// ParachainHostCheckValidationOutputs checks the validation outputs of a candidate. +// Returns true if the candidate is valid. +func (in *Instance) ParachainHostCheckValidationOutputs( + parachainID parachain.ParaID, + outputs parachain.CandidateCommitments, +) (bool, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(parachainID) + if err != nil { + return false, fmt.Errorf("encode parachainID: %w", err) + } + err = encoder.Encode(outputs) + if err != nil { + return false, fmt.Errorf("encode outputs: %w", err) + } + + encodedPersistedValidationData, err := in.Exec(runtime.ParachainHostCheckValidationOutputs, buffer.Bytes()) + if err != nil { + return false, fmt.Errorf("exec: %w", err) + } + + var isValid bool + err = scale.Unmarshal(encodedPersistedValidationData, &isValid) + if err != nil { + return false, fmt.Errorf("unmarshalling: %w", err) + } + + return isValid, nil +} + +// ParachainHostSessionIndexForChild returns the session index that is expected at the child of a block. +func (in *Instance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { + encodedSessionIndex, err := in.Exec(runtime.ParachainHostSessionIndexForChild, []byte{}) + if err != nil { + return 0, fmt.Errorf("exec: %w", err) + } + + var sessionIndex parachain.SessionIndex + err = scale.Unmarshal(encodedSessionIndex, &sessionIndex) + if err != nil { + return 0, fmt.Errorf("unmarshalling: %w", err) + } + + return sessionIndex, nil +} + +// ParachainHostCandidatePendingAvailability returns the receipt of a candidate pending availability +// for any parachain assigned to an occupied availability core. +func (in *Instance) ParachainHostCandidatePendingAvailability( + parachainID parachain.ParaID, +) (*parachain.CommittedCandidateReceipt, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(parachainID) + if err != nil { + return nil, fmt.Errorf("encode parachainID: %w", err) + } + + encodedCandidateReceipt, err := in.Exec(runtime.ParachainHostCandidatePendingAvailability, buffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + var candidateReceipt *parachain.CommittedCandidateReceipt + err = scale.Unmarshal(encodedCandidateReceipt, &candidateReceipt) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return candidateReceipt, nil +} + +// ParachainHostCandidateEvents returns an array of candidate events that occurred within the latest state. +func (in *Instance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) { + encodedCandidateEvents, err := in.Exec(runtime.ParachainHostCandidateEvents, []byte{}) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + candidateEvents, err := parachain.NewCandidateEvents() + if err != nil { + return nil, fmt.Errorf("create new candidate events: %w", err) + } + err = scale.Unmarshal(encodedCandidateEvents, &candidateEvents) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return &candidateEvents, nil +} + +// ParachainHostSessionInfo returns the session info of the given session, if available. +func (in *Instance) ParachainHostSessionInfo(sessionIndex parachain.SessionIndex) (*parachain.SessionInfo, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(sessionIndex) + if err != nil { + return nil, fmt.Errorf("encode sessionIndex: %w", err) + } + + encodedSessionInfo, err := in.Exec(runtime.ParachainHostSessionInfo, buffer.Bytes()) + if err != nil { + return nil, fmt.Errorf("exec: %w", err) + } + + var sessionInfo *parachain.SessionInfo + err = scale.Unmarshal(encodedSessionInfo, &sessionInfo) + if err != nil { + return nil, fmt.Errorf("unmarshalling: %w", err) + } + + return sessionInfo, nil +} + +func (in *Instance) RandomSeed() { panic("unimplemented") } func (*Instance) OffchainWorker() { From 4a103f4ac216f2d15932186d59eeba4612a04951 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 21 Jun 2023 11:13:48 -0400 Subject: [PATCH 07/85] feat(parachain): Create struct for Approval Distribution Message (#3326) --- .../approval_distribution_message.go | 179 ++++++++++++++++ .../approval_distribution_message_test.go | 202 ++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 lib/parachain/approval_distribution_message.go create mode 100644 lib/parachain/approval_distribution_message_test.go diff --git a/lib/parachain/approval_distribution_message.go b/lib/parachain/approval_distribution_message.go new file mode 100644 index 0000000000..ebb6b8ef39 --- /dev/null +++ b/lib/parachain/approval_distribution_message.go @@ -0,0 +1,179 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// AssignmentCertKind different kinds of input or criteria that can prove a validator's assignment +// to check a particular parachain. +type AssignmentCertKind scale.VaryingDataType + +// New will enable scale to create new instance when needed +func (ack AssignmentCertKind) New() AssignmentCertKind { + return NewAssignmentCertKindVDT() +} + +// Set will set VaryingDataTypeValue using undurlying VaryingDataType +func (ack *AssignmentCertKind) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*ack) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value te varying data type: %w", err) + } + *ack = AssignmentCertKind(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (ack *AssignmentCertKind) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*ack) + return vdt.Value() +} + +// NewAssignmentCertKindVDT constructor for AssignmentCertKind +func NewAssignmentCertKindVDT() AssignmentCertKind { + vdt, err := scale.NewVaryingDataType(NewRelayVRFModulo(), NewVRFDelay()) + if err != nil { + panic(err) + } + return AssignmentCertKind(vdt) +} + +// RelayVRFModulo an assignment story based on the VRF that authorized the relay-chain block where the +// candidate was included combined with a sample number. +type RelayVRFModulo struct { + // Sample the sample number used in this cert. + Sample uint32 +} + +// NewRelayVRFModulo constructor for RelayVRFModulo +func NewRelayVRFModulo() RelayVRFModulo { + return RelayVRFModulo{} +} + +// Index returns varying data type index +func (rvm RelayVRFModulo) Index() uint { + return 0 +} + +// RelayVRFDelay an assignment story based on the VRF that authorized the relay-chain block where the +// candidate was included combined with the index of a particular core. +type RelayVRFDelay struct { + // CoreIndex the unique (during session) index of a core. + CoreIndex uint32 +} + +// NewVRFDelay constructor for RelayVRFDelay +func NewVRFDelay() RelayVRFDelay { + return RelayVRFDelay{} +} + +// Index returns varying data type index +func (rvd RelayVRFDelay) Index() uint { + return 1 +} + +// VrfSignature represents VRF signature, which itself consists of a VRF pre-output and DLEQ proof +type VrfSignature struct { + // Output VRF output + Output [sr25519.VRFOutputLength]byte `scale:"1"` + // Proof VRF proof + Proof [sr25519.VRFProofLength]byte `scale:"2"` +} + +// AssignmentCert is a certification of assignment +type AssignmentCert struct { + // Kind the criterion which is claimed to be met by this cert. + Kind AssignmentCertKind `scale:"1"` + // Vrf the VRF signature showing the criterion is met. + Vrf VrfSignature `scale:"2"` +} + +// IndirectAssignmentCert is an assignment criterion which refers to the candidate under which the assignment is +// relevant by block hash. +type IndirectAssignmentCert struct { + // BlockHash a block hash where the canidate appears. + BlockHash common.Hash `scale:"1"` + // Validator the validator index. + Validator ValidatorIndex `scale:"2"` + // Cert the cert itself. + Cert AssignmentCert `scale:"3"` +} + +// CandidateIndex represents the index of the candidate in the list of candidates fully included as-of the block. +type CandidateIndex uint32 + +// Assignment holds indirect assignment cert and candidate index +type Assignment struct { + IndirectAssignmentCert IndirectAssignmentCert `scale:"1"` + CandidateIndex CandidateIndex `scale:"2"` +} + +// Assignments for candidates in recent, unfinalized blocks. +type Assignments []Assignment + +// Index returns varying data type index +func (a Assignments) Index() uint { + return 0 +} + +// IndirectSignedApprovalVote represents a signed approval vote which references the candidate indirectly via the block. +type IndirectSignedApprovalVote struct { + // BlockHash a block hash where the candidate appears. + BlockHash common.Hash `scale:"1"` + // CandidateIndex the index of the candidate in the list of candidates fully included as-of the block. + CandidateIndex CandidateIndex `scale:"2"` + // ValidatorIndex the validator index. + ValidatorIndex ValidatorIndex `scale:"3"` + // Signature the signature of the validator. + Signature ValidatorSignature `scale:"4"` +} + +// Approvals for candidates in some recent, unfinalized block. +type Approvals []IndirectSignedApprovalVote + +// Index returns varying data type index +func (ap Approvals) Index() uint { + return 1 +} + +// ApprovalDistributionMessage network messages used by approval distribution subsystem. +type ApprovalDistributionMessage scale.VaryingDataType + +// Set will set a VoryingDataTypeValue using the underlying VaryingDataType +func (adm *ApprovalDistributionMessage) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*adm) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + *adm = ApprovalDistributionMessage(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (adm *ApprovalDistributionMessage) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*adm) + return vdt.Value() +} + +// New returns new ApprovalDistributionMessage VDT +func (adm ApprovalDistributionMessage) New() ApprovalDistributionMessage { + return NewApprovalDistributionMessageVDT() +} + +// NewApprovalDistributionMessageVDT ruturns a new ApprovalDistributionMessage VaryingDataType +func NewApprovalDistributionMessageVDT() ApprovalDistributionMessage { + vdt, err := scale.NewVaryingDataType(Assignments{}, Approvals{}) + if err != nil { + panic(err) + } + return ApprovalDistributionMessage(vdt) +} diff --git a/lib/parachain/approval_distribution_message_test.go b/lib/parachain/approval_distribution_message_test.go new file mode 100644 index 0000000000..122af759b0 --- /dev/null +++ b/lib/parachain/approval_distribution_message_test.go @@ -0,0 +1,202 @@ +// Copyright 2021 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +var hash = common.MustHexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + +func TestEncodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + // expected encoding is generated by running rust test code: + // fn try_msg_assignments_encode() { + // let hash = Hash::repeat_byte(0xAA); + // + // let validator_index = ValidatorIndex(1); + // let cert = fake_assignment_cert(hash, validator_index); + // let assignments = vec![(cert.clone(), 4u32)]; + // let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); + // + // let emsg = msg.encode(); + // println!("encode: {:?}", emsg); + //} + expectedEncoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 4, 0, 0, 0} + + approvalDistributionMessage.Set(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(1), false), + CandidateIndex: 4, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + + require.Equal(t, expectedEncoding, encodedMessage) + + approvalDistributionMessageDecodedTest := NewApprovalDistributionMessageVDT() + scale.Unmarshal(encodedMessage, &approvalDistributionMessageDecodedTest) + require.Equal(t, approvalDistributionMessage, approvalDistributionMessageDecodedTest) +} + +func TestEncodeApprovalDistributionMessageAssignmentDelay(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + + expectedEncoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 1, 1, 0, 0, 0, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, + 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 2, 0, 0, 0} + + approvalDistributionMessage.Set(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), true), + CandidateIndex: 2, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + + require.Equal(t, expectedEncoding, encodedMessage) +} + +func TestEncodeAssignmentCertKindModulo(t *testing.T) { + assignmentCertKind := NewAssignmentCertKindVDT() + assignmentCertKind.Set(RelayVRFModulo{Sample: 4}) + expectedEncoding := []byte{0, 4, 0, 0, 0} + encodedAssignmentCertKind, err := scale.Marshal(assignmentCertKind) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedAssignmentCertKind) + + assignmentCertTest := NewAssignmentCertKindVDT() + err = scale.Unmarshal(encodedAssignmentCertKind, &assignmentCertTest) + require.NoError(t, err) + require.Equal(t, assignmentCertKind, assignmentCertTest) +} + +func TestEncodeAssignmentCertKindDelay(t *testing.T) { + assignmentCertKind := NewAssignmentCertKindVDT() + assignmentCertKind.Set(RelayVRFDelay{CoreIndex: 5}) + expectedEncoding := []byte{1, 5, 0, 0, 0} + encodedAssignmentCertKind, err := scale.Marshal(assignmentCertKind) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedAssignmentCertKind) + + assignmentCertTest := NewAssignmentCertKindVDT() + err = scale.Unmarshal(encodedAssignmentCertKind, &assignmentCertTest) + require.NoError(t, err) + require.Equal(t, assignmentCertKind, assignmentCertTest) +} + +func TestEncodeApprovalDistributionMessageApprovals(t *testing.T) { + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + + expectedEncoding := []byte{1, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 3, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + + approvalDistributionMessage.Set(Approvals{ + IndirectSignedApprovalVote{ + BlockHash: hash, + CandidateIndex: CandidateIndex(2), + ValidatorIndex: ValidatorIndex(3), + Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1}, + }, + }) + + encodedMessage, err := scale.Marshal(approvalDistributionMessage) + require.NoError(t, err) + require.Equal(t, expectedEncoding, encodedMessage) +} + +func TestDecodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { + encoding := []byte{0, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 0, 2, 0, 0, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, + 61, 62, 63, 64, 4, 0, 0, 0} + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + err := scale.Unmarshal(encoding, &approvalDistributionMessage) + require.NoError(t, err) + + expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() + expectedApprovalDistributionMessage.Set(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), false), + CandidateIndex: 4, + }, + }) + + approvalValue, err := approvalDistributionMessage.Value() + require.NoError(t, err) + expectedValue, err := expectedApprovalDistributionMessage.Value() + require.NoError(t, err) + require.Equal(t, expectedValue, approvalValue) +} + +func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { + encoding := []byte{1, 4, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, + 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 2, 0, 0, 0, 3, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} + expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() + expectedApprovalDistributionMessage.Set(Approvals{ + IndirectSignedApprovalVote{ + BlockHash: hash, + CandidateIndex: CandidateIndex(2), + ValidatorIndex: ValidatorIndex(3), + Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1}, + }, + }) + + approvalDistributionMessage := NewApprovalDistributionMessageVDT() + err := scale.Unmarshal(encoding, &approvalDistributionMessage) + require.NoError(t, err) + require.Equal(t, expectedApprovalDistributionMessage, approvalDistributionMessage) +} + +func fakeAssignmentCert(blockHash common.Hash, validator ValidatorIndex, useDelay bool) IndirectAssignmentCert { + output := [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32} + proof := [64]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, 63, 64} + assignmentCertKind := NewAssignmentCertKindVDT() + if useDelay { + assignmentCertKind.Set(RelayVRFDelay{CoreIndex: 1}) + } else { + assignmentCertKind.Set(RelayVRFModulo{Sample: 2}) + } + + return IndirectAssignmentCert{ + BlockHash: blockHash, + Validator: validator, + Cert: AssignmentCert{ + Kind: assignmentCertKind, + Vrf: VrfSignature{ + Output: output, + Proof: proof, + }, + }, + } +} From 4928d459dcb6970a74afda923d2c51909e7ec045 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Fri, 23 Jun 2023 22:05:47 +0530 Subject: [PATCH 08/85] chore(parachain): improve comments of statement and statement distribution message (#3355) --- lib/parachain/statement.go | 14 +++++++------- lib/parachain/statement_distribution_message.go | 17 +++++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go index 09412e2a98..168b8f0f59 100644 --- a/lib/parachain/statement.go +++ b/lib/parachain/statement.go @@ -10,13 +10,13 @@ import ( // Statement is a result of candidate validation. It could be either `Valid` or `Seconded`. type Statement scale.VaryingDataType -// NewStatement returns a new Statement VaryingDataType +// NewStatement returns a new statement varying data type func NewStatement() Statement { vdt := scale.MustNewVaryingDataType(Seconded{}, Valid{}) return Statement(vdt) } -// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +// Set will set a value using the underlying varying data type func (s *Statement) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*s) err = vdt.Set(val) @@ -28,7 +28,7 @@ func (s *Statement) Set(val scale.VaryingDataTypeValue) (err error) { return nil } -// Value returns the value from the underlying VaryingDataType +// Value returns the value from the underlying varying data type func (s *Statement) Value() (scale.VaryingDataTypeValue, error) { vdt := scale.VaryingDataType(*s) return vdt.Value() @@ -37,16 +37,16 @@ func (s *Statement) Value() (scale.VaryingDataTypeValue, error) { // Seconded represents a statement that a validator seconds a candidate. type Seconded CommittedCandidateReceipt -// Index returns the VaryingDataType Index -func (s Seconded) Index() uint { +// Index returns the index of varying data type +func (Seconded) Index() uint { return 1 } // Valid represents a statement that a validator has deemed a candidate valid. type Valid CandidateHash -// Index returns the VaryingDataType Index -func (v Valid) Index() uint { +// Index returns the index of varying data type +func (Valid) Index() uint { return 2 } diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go index 8690fcaa61..68eb3707f2 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/lib/parachain/statement_distribution_message.go @@ -10,13 +10,13 @@ import ( // StatementDistributionMessage represents network messages used by the statement distribution subsystem type StatementDistributionMessage scale.VaryingDataType -// NewStatementDistributionMessage returns a new StatementDistributionMessage VaryingDataType +// NewStatementDistributionMessage returns a new statement distribution message varying data type func NewStatementDistributionMessage() StatementDistributionMessage { vdt := scale.MustNewVaryingDataType(SignedFullStatement{}, SecondedStatementWithLargePayload{}) return StatementDistributionMessage(vdt) } -// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +// Set will set a value using the underlying varying data type func (sdm *StatementDistributionMessage) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*sdm) err = vdt.Set(val) @@ -28,7 +28,7 @@ func (sdm *StatementDistributionMessage) Set(val scale.VaryingDataTypeValue) (er return nil } -// Value returns the value from the underlying VaryingDataType +// Value returns the value from the underlying varying data type func (sdm *StatementDistributionMessage) Value() (scale.VaryingDataTypeValue, error) { vdt := scale.VaryingDataType(*sdm) return vdt.Value() @@ -40,19 +40,20 @@ type SignedFullStatement struct { UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` } -// Index returns the VaryingDataType Index -func (s SignedFullStatement) Index() uint { +// Index returns the index of varying data type +func (SignedFullStatement) Index() uint { return 0 } -// Seconded statement with large payload (e.g. containing a runtime upgrade). +// SecondedStatementWithLargePayload represents Seconded statement with large payload +// (e.g. containing a runtime upgrade). // // We only gossip the hash in that case, actual payloads can be fetched from sending node // via request/response. type SecondedStatementWithLargePayload StatementMetadata -// Index returns the VaryingDataType Index -func (l SecondedStatementWithLargePayload) Index() uint { +// Index returns the index of varying data type +func (SecondedStatementWithLargePayload) Index() uint { return 1 } From 50f2bdbaac6c70ec9f88b2a39ce0d32bc392a1d8 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Mon, 26 Jun 2023 14:24:07 +0530 Subject: [PATCH 09/85] feat(parachain): Add CollationProtocol VaryingDataType (#3337) --- lib/parachain/collation_protocol.go | 98 ++++++++++++++ lib/parachain/collation_protocol_test.go | 122 ++++++++++++++++++ .../testdata/collation_protocol.yaml | 4 + 3 files changed, 224 insertions(+) create mode 100644 lib/parachain/collation_protocol.go create mode 100644 lib/parachain/collation_protocol_test.go create mode 100644 lib/parachain/testdata/collation_protocol.yaml diff --git a/lib/parachain/collation_protocol.go b/lib/parachain/collation_protocol.go new file mode 100644 index 0000000000..8809a8316f --- /dev/null +++ b/lib/parachain/collation_protocol.go @@ -0,0 +1,98 @@ +package parachain + +import ( + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// CollationProtocol represents all network messages on the collation peer-set. +type CollationProtocol scale.VaryingDataType + +// NewCollationProtocol returns a new collation protocol varying data type +func NewCollationProtocol() CollationProtocol { + vdt := scale.MustNewVaryingDataType(NewCollatorProtocolMessage()) + return CollationProtocol(vdt) +} + +// Set will set a value using the underlying varying data type +func (c *CollationProtocol) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*c) + err = vdt.Set(val) + if err != nil { + return + } + *c = CollationProtocol(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (c *CollationProtocol) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*c) + return vdt.Value() +} + +// CollatorProtocolMessage represents Network messages used by the collator protocol subsystem +type CollatorProtocolMessage scale.VaryingDataType + +// Index returns the index of varying data type +func (CollatorProtocolMessage) Index() uint { + return 0 +} + +// NewCollatorProtocolMessage returns a new collator protocol message varying data type +func NewCollatorProtocolMessage() CollatorProtocolMessage { + vdt := scale.MustNewVaryingDataType(Declare{}, AdvertiseCollation{}, CollationSeconded{}) + return CollatorProtocolMessage(vdt) +} + +// Set will set a value using the underlying varying data type +func (c *CollatorProtocolMessage) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*c) + err = vdt.Set(val) + if err != nil { + return + } + *c = CollatorProtocolMessage(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (c *CollatorProtocolMessage) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*c) + return vdt.Value() +} + +// Declare the intent to advertise collations under a collator ID, attaching a +// signature of the `PeerId` of the node using the given collator ID key. +type Declare struct { + CollatorId CollatorID `scale:"1"` + ParaID uint32 `scale:"2"` + CollatorSignature CollatorSignature `scale:"3"` +} + +// Index returns the index of varying data type +func (Declare) Index() uint { + return 0 +} + +// AdvertiseCollation contains a relay parent hash and is used to advertise a collation to a validator. +// This will only advertise a collation if there exists one for the given relay parent and the given peer is +// set as validator for our para at the given relay parent. +// It can only be sent once the peer has declared that they are a collator with given ID +type AdvertiseCollation common.Hash + +// Index returns the index of varying data type +func (AdvertiseCollation) Index() uint { + return 1 +} + +// CollationSeconded represents that a collation sent to a validator was seconded. +type CollationSeconded struct { + Hash common.Hash `scale:"1"` + UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` +} + +// Index returns the index of varying data type +func (CollationSeconded) Index() uint { + return 4 +} diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go new file mode 100644 index 0000000000..7ac800a431 --- /dev/null +++ b/lib/parachain/collation_protocol_test.go @@ -0,0 +1,122 @@ +package parachain + +import ( + _ "embed" + "fmt" + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +//go:embed testdata/collation_protocol.yaml +var testDataCollationProtocolRaw string + +var testDataCollationProtocol map[string]string + +func init() { + err := yaml.Unmarshal([]byte(testDataCollationProtocolRaw), &testDataCollationProtocol) + if err != nil { + fmt.Println("Error unmarshaling test data:", err) + return + } +} + +func TestCollationProtocol(t *testing.T) { + t.Parallel() + + var collatorID CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + var collatorSignature CollatorSignature + tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + var validatorSignature ValidatorSignature + copy(validatorSignature[:], tempSignature) + + hash5 := getDummyHash(5) + + secondedEnumValue := Seconded{ + Descriptor: CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: hash5, + Collator: collatorID, + PersistedValidationDataHash: hash5, + PovHash: hash5, + ErasureRoot: hash5, + Signature: collatorSignature, + ParaHead: hash5, + ValidationCodeHash: ValidationCodeHash(hash5), + }, + Commitments: CandidateCommitments{ + UpwardMessages: []UpwardMessage{{1, 2, 3}}, + HorizontalMessages: []OutboundHrmpMessage{}, + NewValidationCode: &ValidationCode{1, 2, 3}, + HeadData: headData{1, 2, 3}, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + }, + } + + statementWithSeconded := NewStatement() + err := statementWithSeconded.Set(secondedEnumValue) + require.NoError(t, err) + + testCases := []struct { + name string + enumValue scale.VaryingDataTypeValue + encodingValue []byte + }{ + { + name: "Declare", + enumValue: Declare{ + CollatorId: collatorID, + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }, + encodingValue: common.MustHexToBytes(testDataCollationProtocol["declare"]), + }, + { + name: "AdvertiseCollation", + enumValue: AdvertiseCollation(hash5), + encodingValue: common.MustHexToBytes("0x00010505050505050505050505050505050505050505050505050505050505050505"), + }, + { + name: "CollationSeconded", + enumValue: CollationSeconded{ + Hash: hash5, + UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + Payload: statementWithSeconded, + ValidatorIndex: ValidatorIndex(5), + Signature: validatorSignature, + }, + }, + encodingValue: common.MustHexToBytes(testDataCollationProtocol["collationSeconded"]), + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + vdt_parent := NewCollationProtocol() + vdt_child := NewCollatorProtocolMessage() + + err := vdt_child.Set(c.enumValue) + require.NoError(t, err) + + err = vdt_parent.Set(vdt_child) + require.NoError(t, err) + + bytes, err := scale.Marshal(vdt_parent) + require.NoError(t, err) + + require.Equal(t, c.encodingValue, bytes) + }) + } +} diff --git a/lib/parachain/testdata/collation_protocol.yaml b/lib/parachain/testdata/collation_protocol.yaml new file mode 100644 index 0000000000..f312759fc2 --- /dev/null +++ b/lib/parachain/testdata/collation_protocol.yaml @@ -0,0 +1,4 @@ + +declare: "0x000048215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b14705000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +collationSeconded: "0x000405050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" From c88e72db2b511d6d7e438757179c395bd2fa96fb Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Thu, 6 Jul 2023 12:24:45 +0530 Subject: [PATCH 10/85] feat(parachain): Implement request and response message for /req_statement/1 protocol (#3354) - Added `StatementFetchingRequest` and `StatementFetchingResponse` varying data types. - implemented 'network.Message` interface in `StatementFetchingRequest` and 'network.ResponseMessage` interface in `StatementFetchingResponse` as they will be passed into `func (rrp *RequestResponseProtocol) Do(to peer.ID, req Message, res ResponseMessage) error` function as `req` and `res`. - I didn't want to create a new YAML file here. so I decided to rename the YAML file name and variable(in which data of the YAML file getting unmarshalled) name so that I can use them in this PR. --- lib/parachain/collation_protocol_test.go | 2 +- .../statement_distribution_message_test.go | 16 +-- lib/parachain/statement_fetching.go | 81 +++++++++++ lib/parachain/statement_fetching_test.go | 130 ++++++++++++++++++ lib/parachain/statement_test.go | 5 +- ...stribution_message.yaml => statement.yaml} | 22 ++- 6 files changed, 243 insertions(+), 13 deletions(-) create mode 100644 lib/parachain/statement_fetching.go create mode 100644 lib/parachain/statement_fetching_test.go rename lib/parachain/testdata/{statement_distribution_message.yaml => statement.yaml} (61%) diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go index 7ac800a431..4c0b3a9695 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/lib/parachain/collation_protocol_test.go @@ -32,7 +32,7 @@ func TestCollationProtocol(t *testing.T) { copy(collatorID[:], tempCollatID) var collatorSignature CollatorSignature - tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) var validatorSignature ValidatorSignature diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go index 8721ff15de..8dca860049 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/lib/parachain/statement_distribution_message_test.go @@ -11,13 +11,13 @@ import ( "gopkg.in/yaml.v3" ) -//go:embed testdata/statement_distribution_message.yaml -var testSDMHexRaw string +//go:embed testdata/statement.yaml +var testDataStatementRaw string -var testSDMHex map[string]string +var testDataStatement map[string]string func init() { - err := yaml.Unmarshal([]byte(testSDMHexRaw), &testSDMHex) + err := yaml.Unmarshal([]byte(testDataStatementRaw), &testDataStatement) if err != nil { fmt.Printf("Error unmarshaling test data: %s\n", err) return @@ -28,7 +28,7 @@ func TestStatementDistributionMessage(t *testing.T) { t.Parallel() var collatorSignature CollatorSignature - tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) var validatorSignature ValidatorSignature @@ -166,17 +166,17 @@ func TestStatementDistributionMessage(t *testing.T) { { name: "SignedFullStatement with valid statement", enumValue: signedFullStatementWithValid, - encodingValue: common.MustHexToBytes(testSDMHex["sfsValid"]), + encodingValue: common.MustHexToBytes(testDataStatement["sfsValid"]), }, { name: "SignedFullStatement with Seconded statement", enumValue: signedFullStatementWithSeconded, - encodingValue: common.MustHexToBytes(testSDMHex["sfsSeconded"]), + encodingValue: common.MustHexToBytes(testDataStatement["sfsSeconded"]), }, { name: "Seconded Statement With LargePayload", enumValue: secondedStatementWithLargePayload, - encodingValue: common.MustHexToBytes(testSDMHex["statementWithLargePayload"]), + encodingValue: common.MustHexToBytes(testDataStatement["statementWithLargePayload"]), }, } diff --git a/lib/parachain/statement_fetching.go b/lib/parachain/statement_fetching.go new file mode 100644 index 0000000000..1f9e9332ea --- /dev/null +++ b/lib/parachain/statement_fetching.go @@ -0,0 +1,81 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// StatementFetchingRequest represents a request for fetching a large statement via request/response. +type StatementFetchingRequest struct { + // Data needed to locate and identify the needed statement. + RelayParent common.Hash `scale:"1"` + + // Hash of candidate that was used create the `CommitedCandidateRecept`. + CandidateHash CandidateHash `scale:"2"` +} + +// Encode returns the SCALE encoding of the StatementFetchingRequest. +func (s *StatementFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(*s) +} + +// StatementFetchingResponse represents the statement fetching response is +// sent by nodes to the clients who issued a collation fetching request. +// +// Respond with found full statement. +type StatementFetchingResponse scale.VaryingDataType + +// MissingDataInStatement represents the data missing to reconstruct the full signed statement. +type MissingDataInStatement CommittedCandidateReceipt + +// Index returns the index of varying data type +func (MissingDataInStatement) Index() uint { + return 0 +} + +// NewStatementFetchingResponse returns a new statement fetching response varying data type +func NewStatementFetchingResponse() StatementFetchingResponse { + vdt := scale.MustNewVaryingDataType(MissingDataInStatement{}) + return StatementFetchingResponse(vdt) +} + +// Set will set a value using the underlying varying data type +func (s *StatementFetchingResponse) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*s) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *s = StatementFetchingResponse(vdt) + return nil +} + +// Value returns the value from the underlying varying data type +func (s *StatementFetchingResponse) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*s) + return vdt.Value() +} + +// Encode returns the SCALE encoding of the StatementFetchingResponse. +func (s *StatementFetchingResponse) Encode() ([]byte, error) { + return scale.Marshal(*s) +} + +// Decode returns the SCALE decoding of the StatementFetchingResponse. +func (s *StatementFetchingResponse) Decode(in []byte) (err error) { + return scale.Unmarshal(in, s) +} + +// String formats a StatementFetchingResponse as a string +func (s *StatementFetchingResponse) String() string { + if s == nil { + return "StatementFetchingResponse=nil" + } + + v, _ := s.Value() + missingData := v.(MissingDataInStatement) + return fmt.Sprintf("StatementFetchingResponse MissingDataInStatement=%+v", missingData) +} diff --git a/lib/parachain/statement_fetching_test.go b/lib/parachain/statement_fetching_test.go new file mode 100644 index 0000000000..bb315588a1 --- /dev/null +++ b/lib/parachain/statement_fetching_test.go @@ -0,0 +1,130 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/stretchr/testify/require" +) + +func TestEncodeStatementFetchingRequest(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + request StatementFetchingRequest + expectedEncode []byte + }{ + { + // expected encoding is generated by running rust test code: + // fn statement_request() { + // let hash4 = Hash::repeat_byte(4); + // let statement_fetching_request = StatementFetchingRequest{ + // relay_parent: hash4, + // candidate_hash: CandidateHash(hash4) + // }; + // println!( + // "statement_fetching_request encode => {:?}\n\n", + // statement_fetching_request.encode() + // ); + // } + name: "all_4_in_hash", + request: StatementFetchingRequest{ + RelayParent: getDummyHash(4), + CandidateHash: CandidateHash{Value: getDummyHash(4)}, + }, + expectedEncode: common.MustHexToBytes(testDataStatement["all4InCommonHash"]), + }, + { + name: "all_7_in_hash", + request: StatementFetchingRequest{ + RelayParent: getDummyHash(7), + CandidateHash: CandidateHash{Value: getDummyHash(7)}, + }, + expectedEncode: common.MustHexToBytes(testDataStatement["all7InCommonHash"]), + }, + { + name: "random_hash", + request: StatementFetchingRequest{ + RelayParent: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + CandidateHash: CandidateHash{ + Value: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + }, + }, + expectedEncode: common.MustHexToBytes(testDataStatement["hexOfStatementFetchingRequest"]), + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + actualEncode, err := c.request.Encode() + require.NoError(t, err) + require.Equal(t, c.expectedEncode, actualEncode) + }) + } +} + +func TestStatementFetchingResponse(t *testing.T) { + t.Parallel() + + testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + + var collatorID CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + var collatorSignature CollatorSignature + tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + missingDataInStatement := MissingDataInStatement{ + Descriptor: CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: testHash, + Collator: collatorID, + PersistedValidationDataHash: testHash, + PovHash: testHash, + ErasureRoot: testHash, + Signature: collatorSignature, + ParaHead: testHash, + ValidationCodeHash: ValidationCodeHash(testHash), + }, + Commitments: CandidateCommitments{ + UpwardMessages: []UpwardMessage{{1, 2, 3}}, + NewValidationCode: &ValidationCode{1, 2, 3}, + HeadData: headData{1, 2, 3}, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + }, + } + + encodedValue := common.MustHexToBytes(testDataStatement["hexOfStatementFetchingResponse"]) + + t.Run("encode_statement_fetching_response", func(t *testing.T) { + t.Parallel() + + response := NewStatementFetchingResponse() + err := response.Set(missingDataInStatement) + require.NoError(t, err) + + actualEncode, err := response.Encode() + require.NoError(t, err) + + require.Equal(t, encodedValue, actualEncode) + }) + + t.Run("Decode_statement_fetching_response", func(t *testing.T) { + t.Parallel() + + response := NewStatementFetchingResponse() + err := response.Decode(encodedValue) + require.NoError(t, err) + + actualData, err := response.Value() + require.NoError(t, err) + + require.EqualValues(t, missingDataInStatement, actualData) + }) +} diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go index ed5f8eb3c1..cde19fd9ff 100644 --- a/lib/parachain/statement_test.go +++ b/lib/parachain/statement_test.go @@ -24,7 +24,7 @@ func TestStatement(t *testing.T) { copy(collatorID[:], tempCollatID) var collatorSignature CollatorSignature - tempSignature := common.MustHexToBytes(testSDMHex["collatorSignature"]) + tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) hash5 := getDummyHash(5) @@ -59,8 +59,7 @@ func TestStatement(t *testing.T) { { name: "Seconded", enumValue: secondedEnumValue, - encodingValue: common.MustHexToBytes(testSDMHex["statementSeconded"]), - // expected Hex stored in statement_distribution_message.yaml + encodingValue: common.MustHexToBytes(testDataStatement["statementSeconded"]), }, { name: "Valid", diff --git a/lib/parachain/testdata/statement_distribution_message.yaml b/lib/parachain/testdata/statement.yaml similarity index 61% rename from lib/parachain/testdata/statement_distribution_message.yaml rename to lib/parachain/testdata/statement.yaml index e43dd59bb8..8da19561cb 100644 --- a/lib/parachain/testdata/statement_distribution_message.yaml +++ b/lib/parachain/testdata/statement.yaml @@ -1,3 +1,7 @@ + +# ==========| statement distribution message |========== + + collatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" # Seconded @@ -10,4 +14,20 @@ sfsValid: "0x0005050505050505050505050505050505050505050505050505050505050505050 sfsSeconded: "0x0005050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" # Seconded Statement With LargePayload -statementWithLargePayload: "0x010505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" \ No newline at end of file +statementWithLargePayload: "0x010505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + + + +# ==========| statement fetching request and response |========== + + +# hex of a common.Hash where all 32 bytes are set to 4. +all4InCommonHash: "0x04040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404" + +# hex of a common.Hash where all 32 bytes are set to 7. +all7InCommonHash: "0x07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707" + +# hex of statement fetching response +hexOfStatementFetchingResponse: "0x0001000000677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1948215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19040c01020300010c0102030c0102030500000000000000" + +hexOfStatementFetchingRequest: "0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19" \ No newline at end of file From 4d103a3c011a17f33aa46214eeefdd8bd2432d09 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Thu, 6 Jul 2023 13:01:00 +0530 Subject: [PATCH 11/85] feat(lib/parachain): Implement request and response message for /req_collation/1 protocol (#3356) --- lib/parachain/collation_fetching.go | 85 ++++++++++++ lib/parachain/collation_fetching_test.go | 128 ++++++++++++++++++ .../testdata/collation_protocol.yaml | 2 + 3 files changed, 215 insertions(+) create mode 100644 lib/parachain/collation_fetching.go create mode 100644 lib/parachain/collation_fetching_test.go diff --git a/lib/parachain/collation_fetching.go b/lib/parachain/collation_fetching.go new file mode 100644 index 0000000000..44270456a2 --- /dev/null +++ b/lib/parachain/collation_fetching.go @@ -0,0 +1,85 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// CollationFetchingRequest represents a request to retrieve +// the advertised collation at the specified relay chain block. +type CollationFetchingRequest struct { + // Relay parent we want a collation for + RelayParent common.Hash `scale:"1"` + + // Parachain id of the collation + ParaID ParaID `scale:"2"` +} + +// Encode returns the SCALE encoding of the CollationFetchingRequest +func (c *CollationFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(*c) +} + +// CollationFetchingResponse represents a response sent by collator +type CollationFetchingResponse scale.VaryingDataType + +// Collation represents a requested collation to be delivered +type Collation struct { + CandidateReceipt CandidateReceipt `scale:"1"` + PoV PoV `scale:"2"` +} + +// PoV represents a Proof-of-Validity block (PoV block) or a parachain block. +// It contains the necessary data for the parachain specific state transition logic. +type PoV []byte + +// Index returns the index of varying data type +func (Collation) Index() uint { + return 0 +} + +// NewCollationFetchingResponse returns a new collation fetching response varying data type +func NewCollationFetchingResponse() CollationFetchingResponse { + vdt := scale.MustNewVaryingDataType(Collation{}) + return CollationFetchingResponse(vdt) +} + +// Set will set a value using the underlying varying data type +func (c *CollationFetchingResponse) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*c) + err = vdt.Set(val) + if err != nil { + return + } + *c = CollationFetchingResponse(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (c *CollationFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*c) + return vdt.Value() +} + +// Encode returns the SCALE encoding of the CollationFetchingResponse +func (c *CollationFetchingResponse) Encode() ([]byte, error) { + return scale.Marshal(*c) +} + +// Decode returns the SCALE decoding of the CollationFetchingResponse. +func (c *CollationFetchingResponse) Decode(in []byte) (err error) { + return scale.Unmarshal(in, c) +} + +// String formats a CollationFetchingResponse as a string +func (c *CollationFetchingResponse) String() string { + if c == nil { + return "CollationFetchingResponse=nil" + } + + v, _ := c.Value() + collation := v.(Collation) + return fmt.Sprintf("CollationFetchingResponse Collation=%+v", collation) +} diff --git a/lib/parachain/collation_fetching_test.go b/lib/parachain/collation_fetching_test.go new file mode 100644 index 0000000000..83671f79dd --- /dev/null +++ b/lib/parachain/collation_fetching_test.go @@ -0,0 +1,128 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/stretchr/testify/require" +) + +func TestEncodeCollationFetchingRequest(t *testing.T) { + collationFetchingRequest := CollationFetchingRequest{ + RelayParent: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + ParaID: 5, + } + + actualEncode, err := collationFetchingRequest.Encode() + require.NoError(t, err) + + expextedEncode := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1905000000") + require.Equal(t, expextedEncode, actualEncode) +} + +func TestCollationFetchingResponse(t *testing.T) { + t.Parallel() + + testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + + var collatorID CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + var collatorSignature CollatorSignature + tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + collation := Collation{ + CandidateReceipt: CandidateReceipt{ + Descriptor: CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: testHash, + Collator: collatorID, + PersistedValidationDataHash: testHash, + PovHash: testHash, + ErasureRoot: testHash, + Signature: collatorSignature, + ParaHead: testHash, + ValidationCodeHash: ValidationCodeHash(testHash), + }, + CommitmentsHash: testHash, + }, + PoV: PoV{1, 2, 3}, + } + + // expected encoding is generated by running rust test code: + // fn collation_fetching_response() { + // let test_hash: H256 = H256::from_str( + // "0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19" + // ).unwrap(); + // let collator_result = sr25519::Public::from_string( + // "0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147", + // ); + // if collator_result.is_err() { + // println!( + // "collator_result.unwrap_err()=> {:?}", + // collator_result.unwrap_err() + // ); + // } + // let collator = CollatorId::from(collator_result.unwrap()); + // let collsign = CollatorSignature::from(sr25519::Signature([ + // 198, 124, 185, 59, 240, 163, 111, 206, 227, 210, 157, 232, 166, 166, 154, 117, 150, 89, + // 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, 216, 126, 69, 173, 206, 95, 41, 6, + // 152, 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, 81, 175, 134, 23, 200, 190, + // 116, 184, 148, 207, 27, 134, + // ])); + // let candidate_descriptor = CandidateDescriptor { + // para_id: 1.into(), + // relay_parent: test_hash, + // collator: collator.clone(), + // persisted_validation_data_hash: test_hash, + // pov_hash: test_hash, + // erasure_root: test_hash, + // signature: collsign.clone(), + // para_head: test_hash, + // validation_code_hash: ValidationCodeHash::from(test_hash), + // }; + // let candidate_receipt = CandidateReceipt{ + // descriptor: candidate_descriptor, + // commitments_hash: test_hash + // }; + // let pov = PoV{ + // block_data: vec![1, 2, 3].into() + // }; + // let collation_fetching_response = CollationFetchingResponse::Collation( + // candidate_receipt, pov + // ); + // println!( + // "CollationFetchingResponse encode => {:?}\n\n", + // collation_fetching_response.encode() + // ); + // } + encodedValue := common.MustHexToBytes(testDataCollationProtocol["collationFetchigResponse"]) + + t.Run("encode_collation_fetching_response", func(t *testing.T) { + t.Parallel() + + responseVDT := NewCollationFetchingResponse() + err := responseVDT.Set(collation) + require.NoError(t, err) + + actualEncode, err := responseVDT.Encode() + require.NoError(t, err) + + require.Equal(t, encodedValue, actualEncode) + }) + + t.Run("Decode_collation_fetching_response", func(t *testing.T) { + t.Parallel() + + responseVDT := NewCollationFetchingResponse() + err := responseVDT.Decode(encodedValue) + require.NoError(t, err) + + actualData, err := responseVDT.Value() + require.NoError(t, err) + + require.EqualValues(t, collation, actualData) + }) +} diff --git a/lib/parachain/testdata/collation_protocol.yaml b/lib/parachain/testdata/collation_protocol.yaml index f312759fc2..f5acb9fc2c 100644 --- a/lib/parachain/testdata/collation_protocol.yaml +++ b/lib/parachain/testdata/collation_protocol.yaml @@ -2,3 +2,5 @@ declare: "0x000048215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b14705000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" collationSeconded: "0x000405050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +collationFetchigResponse: "0x0001000000677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1948215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c190c010203" From b8ddd9927e807342f447ab818715ab2dc049076b Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 7 Jul 2023 00:19:13 -0400 Subject: [PATCH 12/85] fix(lib/erasure): Move test assertion to non-error condition (#3371) --- lib/erasure/erasure_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/erasure/erasure_test.go b/lib/erasure/erasure_test.go index 0cc08c4111..7a4252ad72 100644 --- a/lib/erasure/erasure_test.go +++ b/lib/erasure/erasure_test.go @@ -27,6 +27,7 @@ var missing5Chunks = [][]byte{{}, {}, {}, {115, 116, 32, 111}, {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} func TestObtainChunks(t *testing.T) { + t.Parallel() type args struct { validatorsQty int data []byte @@ -59,7 +60,9 @@ func TestObtainChunks(t *testing.T) { }, } for name, tt := range tests { + tt := tt t.Run(name, func(t *testing.T) { + t.Parallel() got, err := ObtainChunks(tt.args.validatorsQty, tt.args.data) expectedThreshold, _ := recoveryThreshold(tt.args.validatorsQty) if tt.expectedError != nil { @@ -74,6 +77,7 @@ func TestObtainChunks(t *testing.T) { } func TestReconstruct(t *testing.T) { + t.Parallel() type args struct { validatorsQty int chunks [][]byte @@ -118,15 +122,17 @@ func TestReconstruct(t *testing.T) { }, } for name, tt := range tests { + tt := tt t.Run(name, func(t *testing.T) { + t.Parallel() data, err := Reconstruct(tt.args.validatorsQty, len(testData), tt.args.chunks) if tt.expectedError != nil { assert.EqualError(t, err, tt.expectedError.Error()) } else { assert.NoError(t, err) + assert.Equal(t, tt.expectedChunks, tt.args.chunks) } assert.Equal(t, tt.expectedData, data) - assert.Equal(t, tt.expectedChunks, tt.args.chunks) }) } } From c80589607a6becfb546cd67c1c6ac4b3aa059c44 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Fri, 7 Jul 2023 12:00:55 +0530 Subject: [PATCH 13/85] feat(lib/parachain): Implement request and response message for /req_chunk/1 protocol (#3362) - Added `ChunkFetchingRequest` and `ChunkFetchingResponse` types. - implemented network.Message interface in `ChunkFetchingRequest` and 'network.ResponseMessage' interface in `ChunkFetchingResponse` --- lib/parachain/chunk_fetching.go | 93 ++++++++++++++++++++++++++++ lib/parachain/chunk_fetching_test.go | 83 +++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 lib/parachain/chunk_fetching.go create mode 100644 lib/parachain/chunk_fetching_test.go diff --git a/lib/parachain/chunk_fetching.go b/lib/parachain/chunk_fetching.go new file mode 100644 index 0000000000..e27f52a1d2 --- /dev/null +++ b/lib/parachain/chunk_fetching.go @@ -0,0 +1,93 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// ChunkFetchingRequest represents a request to retrieve chunks of a parachain candidate +type ChunkFetchingRequest struct { + // Hash of candidate we want a chunk for. + CandidateHash CandidateHash `scale:"1"` + + // The index of the chunk to fetch. + Index ValidatorIndex `scale:"2"` +} + +// Encode returns the SCALE encoding of the ChunkFetchingRequest +func (c ChunkFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(c) +} + +// ChunkFetchingResponse represents the response for a requested erasure chunk +type ChunkFetchingResponse scale.VaryingDataType + +// NewChunkFetchingResponse returns a new chunk fetching response varying data type +func NewChunkFetchingResponse() ChunkFetchingResponse { + vdt := scale.MustNewVaryingDataType(ChunkResponse{}, NoSuchChunk{}) + return ChunkFetchingResponse(vdt) +} + +// Set will set a value using the underlying varying data type +func (c *ChunkFetchingResponse) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*c) + err = vdt.Set(val) + if err != nil { + return + } + *c = ChunkFetchingResponse(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (c *ChunkFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*c) + return vdt.Value() +} + +// ChunkResponse represents the requested chunk data +type ChunkResponse struct { + // The erasure-encoded chunk of data belonging to the candidate block + Chunk []byte `scale:"1"` + + // Proof for this chunk's branch in the Merkle tree + Proof [][]byte `scale:"2"` +} + +// Index returns the index of varying data type +func (ChunkResponse) Index() uint { + return 0 +} + +// NoSuchChunk indicates that the requested chunk was not found +type NoSuchChunk struct{} + +// Index returns the index of varying data type +func (NoSuchChunk) Index() uint { + return 1 +} + +// Encode returns the SCALE encoding of the ChunkFetchingResponse +func (c *ChunkFetchingResponse) Encode() ([]byte, error) { + return scale.Marshal(*c) +} + +// Decode returns the SCALE decoding of the ChunkFetchingResponse. +func (c *ChunkFetchingResponse) Decode(in []byte) (err error) { + return scale.Unmarshal(in, c) +} + +// String formats a ChunkFetchingResponse as a string +func (c *ChunkFetchingResponse) String() string { + if c == nil { + return "ChunkFetchingResponse=nil" + } + + v, _ := c.Value() + chunkRes, ok := v.(ChunkResponse) + if !ok { + return "ChunkFetchingResponse=NoSuchChunk" + } + return fmt.Sprintf("ChunkFetchingResponse ChunkResponse=%+v", chunkRes) +} diff --git a/lib/parachain/chunk_fetching_test.go b/lib/parachain/chunk_fetching_test.go new file mode 100644 index 0000000000..a666da95b7 --- /dev/null +++ b/lib/parachain/chunk_fetching_test.go @@ -0,0 +1,83 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +func TestEncodeChunkFetchingRequest(t *testing.T) { + chunkFetchingRequest := ChunkFetchingRequest{ + CandidateHash: CandidateHash{ + common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + }, + Index: ValidatorIndex(8), + } + + actualEncode, err := chunkFetchingRequest.Encode() + require.NoError(t, err) + + expextedEncode := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1908000000") + require.Equal(t, expextedEncode, actualEncode) +} + +func TestChunkFetchingResponse(t *testing.T) { + t.Parallel() + + testBytes := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + testCases := []struct { + name string + value scale.VaryingDataTypeValue + encodeValue []byte + }{ + { + name: "chunkResponse", + value: ChunkResponse{ + Chunk: testBytes, + Proof: [][]byte{testBytes}, + }, + encodeValue: common.MustHexToBytes("0x0080677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c190480677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), //nolint:lll + }, + { + name: "NoSuchChunk", + value: NoSuchChunk{}, + encodeValue: []byte{1}, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + t.Run("encode", func(t *testing.T) { + t.Parallel() + + chunkFetchingResponse := NewChunkFetchingResponse() + err := chunkFetchingResponse.Set(c.value) + require.NoError(t, err) + + actualEncode, err := chunkFetchingResponse.Encode() + require.NoError(t, err) + + require.Equal(t, c.encodeValue, actualEncode) + }) + + t.Run("decode", func(t *testing.T) { + t.Parallel() + + chunkFetchingResponse := NewChunkFetchingResponse() + err := chunkFetchingResponse.Decode(c.encodeValue) + require.NoError(t, err) + + actualData, err := chunkFetchingResponse.Value() + require.NoError(t, err) + + require.EqualValues(t, c.value, actualData) + }) + + }) + } +} From c2d10834040a9558ae2a30845962eb148cce0fb6 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Fri, 7 Jul 2023 17:06:35 +0530 Subject: [PATCH 14/85] feat(lib/parachain): Implement request and response message for /req_available_data/1 protocol (#3368) - Added AvailableDataFetchingRequest and AvailableDataFetchingResponse types. - Implemented 'network.Message' interface in AvailableDataFetchingRequest and 'network.ResponseMessage' interface in AvailableDataFetchingResponse as they will be passed into this function as req and res. --- lib/parachain/available_data_fetching.go | 119 ++++++++++++++++++ lib/parachain/available_data_fetching_test.go | 90 +++++++++++++ 2 files changed, 209 insertions(+) create mode 100644 lib/parachain/available_data_fetching.go create mode 100644 lib/parachain/available_data_fetching_test.go diff --git a/lib/parachain/available_data_fetching.go b/lib/parachain/available_data_fetching.go new file mode 100644 index 0000000000..83ec7c2578 --- /dev/null +++ b/lib/parachain/available_data_fetching.go @@ -0,0 +1,119 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// AvailableDataFetchingRequest represents a request to retrieve all available data for a specific candidate. +type AvailableDataFetchingRequest struct { + // Hash of the candidate for which the available data is requested. + CandidateHash CandidateHash +} + +// Encode returns the SCALE encoding of the AvailableDataFetchingRequest +func (a AvailableDataFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(a) +} + +// AvailableDataFetchingResponse represents the possible responses to an available data fetching request. +type AvailableDataFetchingResponse scale.VaryingDataType + +// NewAvailableDataFetchingResponse returns a new available data fetching response varying data type +func NewAvailableDataFetchingResponse() AvailableDataFetchingResponse { + vdt := scale.MustNewVaryingDataType(AvailableData{}, NoSuchData{}) + return AvailableDataFetchingResponse(vdt) +} + +// Set will set a value using the underlying varying data type +func (a *AvailableDataFetchingResponse) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*a) + err = vdt.Set(val) + if err != nil { + return + } + *a = AvailableDataFetchingResponse(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (a *AvailableDataFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*a) + return vdt.Value() +} + +// AvailableData represents the data that is kept available for each candidate included in the relay chain. +type AvailableData struct { + // The Proof-of-Validation (PoV) of the candidate + PoV Pov `scale:"1"` + + // The persisted validation data needed for approval checks + ValidationData PersistedValidationData `scale:"2"` +} + +// Pov represents a Proof-of-Validity block (PoV block) or a parachain block. +// It contains the necessary data for the parachain specific state transition logic. +type Pov struct { + BlockData BlockData `scale:"1"` +} + +// BlockData represents parachain block data. +// It contains everything required to validate para-block, may contain block and witness data. +type BlockData []byte + +// Index returns the index of varying data type +func (AvailableData) Index() uint { + return 0 +} + +// PersistedValidationData provides information about how to create the inputs for the validation +// of a candidate by calling the Runtime. +// This information is derived from the parachain state and will vary from parachain to parachain, +// although some of the fields may be the same for every parachain. +type PersistedValidationData struct { + // The parent head-data + ParentHead headData `scale:"1"` + + // The relay-chain block number this is in the context of + RelayParentNumber BlockNumber `scale:"2"` + + // The relay-chain block storage root this is in the context of + RelayParentStorageRoot common.Hash `scale:"3"` + + // The maximum legal size of a POV block, in bytes + MaxPovSize uint32 `scale:"4"` +} + +// NoSuchData indicates that the requested data was not found. +type NoSuchData struct{} + +// Index returns the index of varying data type +func (NoSuchData) Index() uint { + return 1 +} + +// Encode returns the SCALE encoding of the AvailableDataFetchingResponse +func (a *AvailableDataFetchingResponse) Encode() ([]byte, error) { + return scale.Marshal(*a) +} + +// Decode returns the SCALE decoding of the AvailableDataFetchingResponse. +func (a *AvailableDataFetchingResponse) Decode(in []byte) (err error) { + return scale.Unmarshal(in, a) +} + +// String formats a AvailableDataFetchingResponse as a string +func (p *AvailableDataFetchingResponse) String() string { + if p == nil { + return "AvailableDataFetchingResponse=nil" + } + + v, _ := p.Value() + availableData, ok := v.(AvailableData) + if !ok { + return "AvailableDataFetchingResponse=NoSuchData" + } + return fmt.Sprintf("AvailableDataFetchingResponse AvailableData=%+v", availableData) +} diff --git a/lib/parachain/available_data_fetching_test.go b/lib/parachain/available_data_fetching_test.go new file mode 100644 index 0000000000..81e2b16613 --- /dev/null +++ b/lib/parachain/available_data_fetching_test.go @@ -0,0 +1,90 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +func TestEncodeAvailableDataFetchingRequest(t *testing.T) { + availableDataFetchingRequest := AvailableDataFetchingRequest{ + CandidateHash: CandidateHash{ + common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + }, + } + + actualEncode, err := availableDataFetchingRequest.Encode() + require.NoError(t, err) + + expextedEncode := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + require.Equal(t, expextedEncode, actualEncode) +} + +func TestAvailableDataFetchingResponse(t *testing.T) { + t.Parallel() + + testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + testBytes := testHash.ToBytes() + availableData := AvailableData{ + PoV: Pov{BlockData: testBytes}, + ValidationData: PersistedValidationData{ + ParentHead: testBytes, + RelayParentNumber: BlockNumber(4), + RelayParentStorageRoot: testHash, + MaxPovSize: 6, + }, + } + + testCases := []struct { + name string + value scale.VaryingDataTypeValue + encodeValue []byte + }{ + { + name: "AvailableData", + value: availableData, + encodeValue: common.MustHexToBytes("0x0080677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1980677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1904000000677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1906000000"), //nolint:lll + }, + { + name: "NoSuchData", + value: NoSuchData{}, + encodeValue: []byte{1}, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + t.Run("encode", func(t *testing.T) { + t.Parallel() + + availableDataFetchingResponse := NewAvailableDataFetchingResponse() + err := availableDataFetchingResponse.Set(c.value) + require.NoError(t, err) + + actualEncode, err := availableDataFetchingResponse.Encode() + require.NoError(t, err) + + require.Equal(t, c.encodeValue, actualEncode) + }) + + t.Run("decode", func(t *testing.T) { + t.Parallel() + + availableDataFetchingResponse := NewAvailableDataFetchingResponse() + err := availableDataFetchingResponse.Decode(c.encodeValue) + require.NoError(t, err) + + actualData, err := availableDataFetchingResponse.Value() + require.NoError(t, err) + + require.EqualValues(t, c.value, actualData) + }) + + }) + } +} From 1104a22c3996d2ee0d08f4bf6758d0d293155da4 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Fri, 7 Jul 2023 20:40:42 +0530 Subject: [PATCH 15/85] chore(lib/parachain): add tests to decode varying data types regarding statement and collation (#3374) added New() method and decode test for below varying data types. - statement - statement distribution message - collation protocol - collator protocol message --- lib/parachain/collation_protocol.go | 10 +++++ lib/parachain/collation_protocol_test.go | 43 ++++++++++++++----- lib/parachain/statement.go | 5 +++ .../statement_distribution_message.go | 5 +++ .../statement_distribution_message_test.go | 28 +++++++++--- lib/parachain/statement_test.go | 28 +++++++++--- 6 files changed, 95 insertions(+), 24 deletions(-) diff --git a/lib/parachain/collation_protocol.go b/lib/parachain/collation_protocol.go index 8809a8316f..6d8810c772 100644 --- a/lib/parachain/collation_protocol.go +++ b/lib/parachain/collation_protocol.go @@ -14,6 +14,11 @@ func NewCollationProtocol() CollationProtocol { return CollationProtocol(vdt) } +// New will enable scale to create new instance when needed +func (CollationProtocol) New() CollationProtocol { + return NewCollationProtocol() +} + // Set will set a value using the underlying varying data type func (c *CollationProtocol) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*c) @@ -45,6 +50,11 @@ func NewCollatorProtocolMessage() CollatorProtocolMessage { return CollatorProtocolMessage(vdt) } +// New will enable scale to create new instance when needed +func (CollatorProtocolMessage) New() CollatorProtocolMessage { + return NewCollatorProtocolMessage() +} + // Set will set a value using the underlying varying data type func (c *CollatorProtocolMessage) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*c) diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go index 4c0b3a9695..9b90d4a480 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/lib/parachain/collation_protocol_test.go @@ -54,7 +54,6 @@ func TestCollationProtocol(t *testing.T) { }, Commitments: CandidateCommitments{ UpwardMessages: []UpwardMessage{{1, 2, 3}}, - HorizontalMessages: []OutboundHrmpMessage{}, NewValidationCode: &ValidationCode{1, 2, 3}, HeadData: headData{1, 2, 3}, ProcessedDownwardMessages: uint32(5), @@ -103,20 +102,44 @@ func TestCollationProtocol(t *testing.T) { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() + t.Run("marshal", func(t *testing.T) { + t.Parallel() - vdt_parent := NewCollationProtocol() - vdt_child := NewCollatorProtocolMessage() + vdt_parent := NewCollationProtocol() + vdt_child := NewCollatorProtocolMessage() - err := vdt_child.Set(c.enumValue) - require.NoError(t, err) + err := vdt_child.Set(c.enumValue) + require.NoError(t, err) - err = vdt_parent.Set(vdt_child) - require.NoError(t, err) + err = vdt_parent.Set(vdt_child) + require.NoError(t, err) - bytes, err := scale.Marshal(vdt_parent) - require.NoError(t, err) + bytes, err := scale.Marshal(vdt_parent) + require.NoError(t, err) - require.Equal(t, c.encodingValue, bytes) + require.Equal(t, c.encodingValue, bytes) + }) + + t.Run("unmarshal", func(t *testing.T) { + t.Parallel() + + vdt_parent := NewCollationProtocol() + err := scale.Unmarshal(c.encodingValue, &vdt_parent) + require.NoError(t, err) + + vdt_child_temp, err := vdt_parent.Value() + require.NoError(t, err) + require.Equal(t, uint(0), vdt_child_temp.Index()) + + vdt_child := vdt_child_temp.(CollatorProtocolMessage) + require.NoError(t, err) + + actualData, err := vdt_child.Value() + require.NoError(t, err) + + require.Equal(t, c.enumValue.Index(), actualData.Index()) + require.EqualValues(t, c.enumValue, actualData) + }) }) } } diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go index 168b8f0f59..8fd7c83f46 100644 --- a/lib/parachain/statement.go +++ b/lib/parachain/statement.go @@ -16,6 +16,11 @@ func NewStatement() Statement { return Statement(vdt) } +// New will enable scale to create new instance when needed +func (Statement) New() Statement { + return NewStatement() +} + // Set will set a value using the underlying varying data type func (s *Statement) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*s) diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go index 68eb3707f2..0ba3a4d4a0 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/lib/parachain/statement_distribution_message.go @@ -16,6 +16,11 @@ func NewStatementDistributionMessage() StatementDistributionMessage { return StatementDistributionMessage(vdt) } +// New will enable scale to create new instance when needed +func (StatementDistributionMessage) New() StatementDistributionMessage { + return NewStatementDistributionMessage() +} + // Set will set a value using the underlying varying data type func (sdm *StatementDistributionMessage) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*sdm) diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go index 8dca860049..f8438a87ca 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/lib/parachain/statement_distribution_message_test.go @@ -58,7 +58,6 @@ func TestStatementDistributionMessage(t *testing.T) { }, Commitments: CandidateCommitments{ UpwardMessages: []UpwardMessage{{1, 2, 3}}, - HorizontalMessages: []OutboundHrmpMessage{}, NewValidationCode: &ValidationCode{1, 2, 3}, HeadData: headData{1, 2, 3}, ProcessedDownwardMessages: uint32(5), @@ -184,16 +183,31 @@ func TestStatementDistributionMessage(t *testing.T) { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() + t.Run("marshal", func(t *testing.T) { + t.Parallel() - vtd := NewStatementDistributionMessage() + vdt := NewStatementDistributionMessage() + err := vdt.Set(c.enumValue) + require.NoError(t, err) - err := vtd.Set(c.enumValue) - require.NoError(t, err) + bytes, err := scale.Marshal(vdt) + require.NoError(t, err) - bytes, err := scale.Marshal(vtd) - require.NoError(t, err) + require.Equal(t, c.encodingValue, bytes) + }) - require.Equal(t, c.encodingValue, bytes) + t.Run("unmarshal", func(t *testing.T) { + t.Parallel() + + vdt := NewStatementDistributionMessage() + err := scale.Unmarshal(c.encodingValue, &vdt) + require.NoError(t, err) + + actualData, err := vdt.Value() + require.NoError(t, err) + + require.EqualValues(t, c.enumValue, actualData) + }) }) } } diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go index cde19fd9ff..4aae3c431e 100644 --- a/lib/parachain/statement_test.go +++ b/lib/parachain/statement_test.go @@ -43,7 +43,6 @@ func TestStatement(t *testing.T) { }, Commitments: CandidateCommitments{ UpwardMessages: []UpwardMessage{{1, 2, 3}}, - HorizontalMessages: []OutboundHrmpMessage{}, NewValidationCode: &ValidationCode{1, 2, 3}, HeadData: headData{1, 2, 3}, ProcessedDownwardMessages: uint32(5), @@ -72,16 +71,31 @@ func TestStatement(t *testing.T) { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() + t.Run("marshal", func(t *testing.T) { + t.Parallel() - vtd := NewStatement() + vdt := NewStatement() + err := vdt.Set(c.enumValue) + require.NoError(t, err) - err := vtd.Set(c.enumValue) - require.NoError(t, err) + bytes, err := scale.Marshal(vdt) + require.NoError(t, err) - bytes, err := scale.Marshal(vtd) - require.NoError(t, err) + require.Equal(t, c.encodingValue, bytes) + }) - require.Equal(t, c.encodingValue, bytes) + t.Run("unmarshal", func(t *testing.T) { + t.Parallel() + + vdt := NewStatement() + err := scale.Unmarshal(c.encodingValue, &vdt) + require.NoError(t, err) + + actualData, err := vdt.Value() + require.NoError(t, err) + + require.EqualValues(t, c.enumValue, actualData) + }) }) } } From e7ab6e97c9cedb210454934086f39d858bfee0c3 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Fri, 7 Jul 2023 20:41:49 +0530 Subject: [PATCH 16/85] chore(lib/parachain): remove pov byte array and use pov struct (#3375) --- lib/parachain/available_data_fetching.go | 6 +++--- lib/parachain/available_data_fetching_test.go | 2 +- lib/parachain/collation_fetching.go | 4 ---- lib/parachain/collation_fetching_test.go | 2 +- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/parachain/available_data_fetching.go b/lib/parachain/available_data_fetching.go index 83ec7c2578..303124bfc5 100644 --- a/lib/parachain/available_data_fetching.go +++ b/lib/parachain/available_data_fetching.go @@ -47,15 +47,15 @@ func (a *AvailableDataFetchingResponse) Value() (val scale.VaryingDataTypeValue, // AvailableData represents the data that is kept available for each candidate included in the relay chain. type AvailableData struct { // The Proof-of-Validation (PoV) of the candidate - PoV Pov `scale:"1"` + PoV PoV `scale:"1"` // The persisted validation data needed for approval checks ValidationData PersistedValidationData `scale:"2"` } -// Pov represents a Proof-of-Validity block (PoV block) or a parachain block. +// PoV represents a Proof-of-Validity block (PoV block) or a parachain block. // It contains the necessary data for the parachain specific state transition logic. -type Pov struct { +type PoV struct { BlockData BlockData `scale:"1"` } diff --git a/lib/parachain/available_data_fetching_test.go b/lib/parachain/available_data_fetching_test.go index 81e2b16613..f875b6dd4e 100644 --- a/lib/parachain/available_data_fetching_test.go +++ b/lib/parachain/available_data_fetching_test.go @@ -28,7 +28,7 @@ func TestAvailableDataFetchingResponse(t *testing.T) { testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") testBytes := testHash.ToBytes() availableData := AvailableData{ - PoV: Pov{BlockData: testBytes}, + PoV: PoV{BlockData: testBytes}, ValidationData: PersistedValidationData{ ParentHead: testBytes, RelayParentNumber: BlockNumber(4), diff --git a/lib/parachain/collation_fetching.go b/lib/parachain/collation_fetching.go index 44270456a2..a9a4ceb811 100644 --- a/lib/parachain/collation_fetching.go +++ b/lib/parachain/collation_fetching.go @@ -31,10 +31,6 @@ type Collation struct { PoV PoV `scale:"2"` } -// PoV represents a Proof-of-Validity block (PoV block) or a parachain block. -// It contains the necessary data for the parachain specific state transition logic. -type PoV []byte - // Index returns the index of varying data type func (Collation) Index() uint { return 0 diff --git a/lib/parachain/collation_fetching_test.go b/lib/parachain/collation_fetching_test.go index 83671f79dd..3a5847069d 100644 --- a/lib/parachain/collation_fetching_test.go +++ b/lib/parachain/collation_fetching_test.go @@ -48,7 +48,7 @@ func TestCollationFetchingResponse(t *testing.T) { }, CommitmentsHash: testHash, }, - PoV: PoV{1, 2, 3}, + PoV: PoV{BlockData{1, 2, 3}}, } // expected encoding is generated by running rust test code: From 59ff8eb8cde0cb9cd69c2535af012191292f4607 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Mon, 10 Jul 2023 12:03:20 +0530 Subject: [PATCH 17/85] feat(lib/parachain): Implement request and response message for /req_pov/1 protocol (#3365) - Added PoVFetchingRequest and PoVFetchingResponse types. - Implemented 'network.Message' interface in PoVFetchingRequest and 'network.ResponseMessage' interface in PoVFetchingResponse as they will be passed into this function as req and res. --- lib/parachain/pov_fetching.go | 81 ++++++++++++++++++++++++++++++ lib/parachain/pov_fetching_test.go | 80 +++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 lib/parachain/pov_fetching.go create mode 100644 lib/parachain/pov_fetching_test.go diff --git a/lib/parachain/pov_fetching.go b/lib/parachain/pov_fetching.go new file mode 100644 index 0000000000..12bb64381c --- /dev/null +++ b/lib/parachain/pov_fetching.go @@ -0,0 +1,81 @@ +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// PoVFetchingRequest represents a request to fetch the advertised collation at the relay-parent. +type PoVFetchingRequest struct { + // Hash of the candidate for which we want to retrieve a Proof-of-Validity (PoV). + CandidateHash CandidateHash +} + +// Encode returns the SCALE encoding of the PoVFetchingRequest +func (p PoVFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(p) +} + +// PoVFetchingResponse represents the possible responses to a PoVFetchingRequest. +type PoVFetchingResponse scale.VaryingDataType + +// NewPoVFetchingResponse returns a new PoV fetching response varying data type +func NewPoVFetchingResponse() PoVFetchingResponse { + vdt := scale.MustNewVaryingDataType(PoV{}, NoSuchPoV{}) + return PoVFetchingResponse(vdt) +} + +// Set will set a value using the underlying varying data type +func (p *PoVFetchingResponse) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*p) + err = vdt.Set(val) + if err != nil { + return + } + *p = PoVFetchingResponse(vdt) + return +} + +// Value returns the value from the underlying varying data type +func (p *PoVFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error) { + vdt := scale.VaryingDataType(*p) + return vdt.Value() +} + +// Index returns the index of varying data type +func (PoV) Index() uint { + return 0 +} + +// NoSuchPoV indicates that the requested PoV was not found in the store. +type NoSuchPoV struct{} + +// Index returns the index of varying data type +func (NoSuchPoV) Index() uint { + return 1 +} + +// Encode returns the SCALE encoding of the PoVFetchingResponse +func (p *PoVFetchingResponse) Encode() ([]byte, error) { + return scale.Marshal(*p) +} + +// Decode returns the SCALE decoding of the PoVFetchingResponse. +func (p *PoVFetchingResponse) Decode(in []byte) (err error) { + return scale.Unmarshal(in, p) +} + +// String formats a PoVFetchingResponse as a string +func (p *PoVFetchingResponse) String() string { + if p == nil { + return "PoVFetchingResponse=nil" + } + + v, _ := p.Value() + pov, ok := v.(PoV) + if !ok { + return "PoVFetchingResponse=NoSuchPoV" + } + return fmt.Sprintf("PoVFetchingResponse PoV=%+v", pov) +} diff --git a/lib/parachain/pov_fetching_test.go b/lib/parachain/pov_fetching_test.go new file mode 100644 index 0000000000..ff376a37b1 --- /dev/null +++ b/lib/parachain/pov_fetching_test.go @@ -0,0 +1,80 @@ +package parachain + +import ( + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +func TestEncodePoVFetchingRequest(t *testing.T) { + poVFetchingRequest := PoVFetchingRequest{ + CandidateHash: CandidateHash{ + common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + }, + } + + actualEncode, err := poVFetchingRequest.Encode() + require.NoError(t, err) + + expextedEncode := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + require.Equal(t, expextedEncode, actualEncode) + +} + +func TestPoVFetchingResponse(t *testing.T) { + t.Parallel() + + testBytes := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") + testCases := []struct { + name string + value scale.VaryingDataTypeValue + encodeValue []byte + }{ + { + name: "PoV", + value: PoV{BlockData: testBytes}, + encodeValue: common.MustHexToBytes("0x0080677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + }, + { + name: "NoSuchPoV", + value: NoSuchPoV{}, + encodeValue: []byte{1}, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + t.Run("encode", func(t *testing.T) { + t.Parallel() + + povFetchingResponse := NewPoVFetchingResponse() + err := povFetchingResponse.Set(c.value) + require.NoError(t, err) + + actualEncode, err := povFetchingResponse.Encode() + require.NoError(t, err) + + require.Equal(t, c.encodeValue, actualEncode) + }) + + t.Run("decode", func(t *testing.T) { + t.Parallel() + + povFetchingResponse := NewPoVFetchingResponse() + err := povFetchingResponse.Decode(c.encodeValue) + require.NoError(t, err) + + actualData, err := povFetchingResponse.Value() + require.NoError(t, err) + + require.EqualValues(t, c.value, actualData) + }) + + }) + } +} From b445405cbcea10f28a89175bff27b1a0777010bb Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Tue, 11 Jul 2023 13:40:38 +0530 Subject: [PATCH 18/85] feat(lib/parachain): added parachain service and registering protocols (#3277) - Added parachain service - Registered collation and validation protocol - Confirmed that we can communicate with collators by talking to them in `run()` function --- dot/core/mock_runtime_instance_test.go | 24 +-- dot/mock_node_builder_test.go | 17 ++ dot/network/message.go | 2 + dot/node.go | 8 + dot/node_integration_test.go | 8 +- dot/services.go | 6 + dot/state/mocks_runtime_test.go | 24 +-- dot/sync/mock_runtime_test.go | 24 +-- lib/babe/inherents/parachain_inherents.go | 3 +- lib/babe/mocks/runtime.go | 24 +-- lib/blocktree/mocks_test.go | 24 +-- lib/crypto/sr25519/sr25519.go | 7 +- lib/crypto/sr25519/sr25519_test.go | 4 +- lib/grandpa/mocks_runtime_test.go | 24 +-- .../approval_distribution_message.go | 7 +- .../approval_distribution_message_test.go | 14 +- lib/parachain/available_data_fetching.go | 5 +- lib/parachain/available_data_fetching_test.go | 3 +- lib/parachain/chunk_fetching.go | 3 +- lib/parachain/chunk_fetching_test.go | 3 +- lib/parachain/collation_fetching.go | 7 +- lib/parachain/collation_fetching_test.go | 11 +- lib/parachain/collation_protocol.go | 91 ++++++++++- lib/parachain/collation_protocol_test.go | 34 ++-- lib/parachain/network_protocols.go | 83 ++++++++++ lib/parachain/service.go | 145 ++++++++++++++++++ lib/parachain/statement.go | 3 +- .../statement_distribution_message.go | 5 +- .../statement_distribution_message_test.go | 23 +-- lib/parachain/statement_fetching.go | 3 +- lib/parachain/statement_fetching_test.go | 17 +- lib/parachain/statement_test.go | 17 +- .../{ => types}/testdata/westend.yaml | 0 lib/parachain/{ => types}/types.go | 5 +- lib/parachain/{ => types}/types_test.go | 36 ++--- lib/parachain/validation_protocol.go | 94 ++++++++++++ lib/parachain/validation_protocol_test.go | 23 +++ lib/runtime/interface.go | 18 +-- lib/runtime/mocks/mocks.go | 24 +-- 39 files changed, 689 insertions(+), 184 deletions(-) create mode 100644 lib/parachain/network_protocols.go create mode 100644 lib/parachain/service.go rename lib/parachain/{ => types}/testdata/westend.yaml (100%) rename lib/parachain/{ => types}/types.go (98%) rename lib/parachain/{ => types}/types_test.go (87%) create mode 100644 lib/parachain/validation_protocol.go create mode 100644 lib/parachain/validation_protocol_test.go diff --git a/dot/core/mock_runtime_instance_test.go b/dot/core/mock_runtime_instance_test.go index e671aee681..a02e92fdf8 100644 --- a/dot/core/mock_runtime_instance_test.go +++ b/dot/core/mock_runtime_instance_test.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index 2f52ae3118..cb679fc79c 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -17,8 +17,10 @@ import ( system "github.com/ChainSafe/gossamer/dot/system" types "github.com/ChainSafe/gossamer/dot/types" babe "github.com/ChainSafe/gossamer/lib/babe" + common "github.com/ChainSafe/gossamer/lib/common" grandpa "github.com/ChainSafe/gossamer/lib/grandpa" keystore "github.com/ChainSafe/gossamer/lib/keystore" + parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" gomock "github.com/golang/mock/gomock" ) @@ -135,6 +137,21 @@ func (mr *MocknodeBuilderIfaceMockRecorder) createNetworkService(config, stateSr return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createNetworkService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createNetworkService), config, stateSrvc, telemetryMailer) } +// createParachainHostService mocks base method. +func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, genesishHash common.Hash) (*parachain.Service, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "createParachainHostService", net, genesishHash) + ret0, _ := ret[0].(*parachain.Service) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// createParachainHostService indicates an expected call of createParachainHostService. +func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, genesishHash interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, genesishHash) +} + // createRPCService mocks base method. func (m *MocknodeBuilderIface) createRPCService(params rpcServiceSettings) (*rpc.HTTPServer, error) { m.ctrl.T.Helper() diff --git a/dot/network/message.go b/dot/network/message.go index 14768f6514..dff8e4e2a8 100644 --- a/dot/network/message.go +++ b/dot/network/message.go @@ -27,6 +27,8 @@ const ( blockAnnounceMsgType MessageType = iota + 3 transactionMsgType ConsensusMsgType + CollationMsgType + ValidationMsgType ) // Message must be implemented by all network messages diff --git a/dot/node.go b/dot/node.go index 0ff83f2f1a..6c73728dc9 100644 --- a/dot/node.go +++ b/dot/node.go @@ -33,6 +33,7 @@ import ( "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/services" ) @@ -63,6 +64,7 @@ type nodeBuilderIface interface { dh *digest.Handler) (*core.Service, error) createGRANDPAService(config *cfg.Config, st *state.Service, ks KeyStore, net *network.Service, telemetryMailer Telemetry) (*grandpa.Service, error) + createParachainHostService(net *network.Service, genesishHash common.Hash) (*parachain.Service, error) newSyncService(config *cfg.Config, st *state.Service, finalityGadget BlockJustificationVerifier, verifier *babe.VerificationManager, cs *core.Service, net *network.Service, telemetryMailer Telemetry) (*dotsync.Service, error) @@ -379,6 +381,12 @@ func newNode(config *cfg.Config, } nodeSrvcs = append(nodeSrvcs, fg) + phs, err := builder.createParachainHostService(networkSrvc, stateSrvc.Block.GenesisHash()) + if err != nil { + return nil, err + } + nodeSrvcs = append(nodeSrvcs, phs) + syncer, err := builder.newSyncService(config, stateSrvc, fg, ver, coreSrvc, networkSrvc, telemetryMailer) if err != nil { return nil, err diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 19e048b92e..3fce8fe0c1 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -33,6 +33,7 @@ import ( "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/lib/trie" @@ -85,7 +86,7 @@ func TestNewNode(t *testing.T) { assert.NoError(t, err) mockServiceRegistry := NewMockServiceRegisterer(ctrl) - mockServiceRegistry.EXPECT().RegisterService(gomock.Any()).Times(8) + mockServiceRegistry.EXPECT().RegisterService(gomock.Any()).Times(9) m := NewMocknodeBuilderIface(ctrl) m.EXPECT().isNodeInitialised(initConfig.BasePath).Return(true, nil) @@ -119,6 +120,9 @@ func TestNewNode(t *testing.T) { return stateSrvc, nil }) + phs, err := parachain.NewService(testNetworkService, common.Hash{}) + require.NoError(t, err) + m.EXPECT().createRuntimeStorage(gomock.AssignableToTypeOf(&state.Service{})).Return(&runtime. NodeStorage{}, nil) m.EXPECT().loadRuntime(initConfig, &runtime.NodeStorage{}, gomock.AssignableToTypeOf(&state.Service{}), @@ -149,6 +153,8 @@ func TestNewNode(t *testing.T) { }) m.EXPECT().createNetworkService(initConfig, gomock.AssignableToTypeOf(&state.Service{}), gomock.AssignableToTypeOf(&telemetry.Mailer{})).Return(testNetworkService, nil) + m.EXPECT().createParachainHostService(gomock.AssignableToTypeOf(&network.Service{}), + gomock.AssignableToTypeOf(common.Hash{})).Return(phs, nil) got, err := newNode(initConfig, ks, m, mockServiceRegistry) assert.NoError(t, err) diff --git a/dot/services.go b/dot/services.go index 300026fd44..3450fbd9a8 100644 --- a/dot/services.go +++ b/dot/services.go @@ -31,6 +31,7 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" + "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" ) @@ -456,6 +457,11 @@ func (nodeBuilder) createGRANDPAService(config *cfg.Config, st *state.Service, k return grandpa.NewService(gsCfg) } +func (nodeBuilder) createParachainHostService(net *network.Service, genesisHash common.Hash) ( + *parachain.Service, error) { + return parachain.NewService(net, genesisHash) +} + func (nodeBuilder) createBlockVerifier(st *state.Service) *babe.VerificationManager { return babe.NewVerificationManager(st.Block, st.Slot, st.Epoch) } diff --git a/dot/state/mocks_runtime_test.go b/dot/state/mocks_runtime_test.go index 4029d21a02..4eeca31e69 100644 --- a/dot/state/mocks_runtime_test.go +++ b/dot/state/mocks_runtime_test.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index 36b3842e3c..004c535d73 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/lib/babe/inherents/parachain_inherents.go b/lib/babe/inherents/parachain_inherents.go index b1731abc91..39004b98bc 100644 --- a/lib/babe/inherents/parachain_inherents.go +++ b/lib/babe/inherents/parachain_inherents.go @@ -8,6 +8,7 @@ import ( "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -240,7 +241,7 @@ func newDisputeStatement() disputeStatement { //skipcq } // collatorID is the collator's relay-chain account ID -type collatorID []byte +type collatorID sr25519.PublicKey // collatorSignature is the signature on a candidate's block data signed by a collator. type collatorSignature signature diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 2701d8022a..9963012cfd 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index b1ea7d6183..476b2a9606 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/lib/crypto/sr25519/sr25519.go b/lib/crypto/sr25519/sr25519.go index 21a2f02476..785143a890 100644 --- a/lib/crypto/sr25519/sr25519.go +++ b/lib/crypto/sr25519/sr25519.go @@ -53,12 +53,12 @@ type PrivateKey struct { func VerifySignature(publicKey, signature, message []byte) error { pubKey, err := NewPublicKey(publicKey) if err != nil { - return fmt.Errorf("sr25519: %w", err) + return fmt.Errorf("creating new sr25519 public key: %w", err) } ok, err := pubKey.Verify(message, signature) if err != nil { - return fmt.Errorf("sr25519: %w", err) + return fmt.Errorf("verifying sr25519 public key: %w", err) } else if !ok { return fmt.Errorf("sr25519: %w: for message 0x%x, signature 0x%x and public key 0x%x", crypto.ErrSignatureVerificationFailed, message, signature, publicKey) @@ -305,9 +305,10 @@ func (k *PublicKey) Verify(msg, sig []byte) (bool, error) { copy(b[:], sig) s := &sr25519.Signature{} + err := s.Decode(b) if err != nil { - return false, err + return false, fmt.Errorf("decoding: %w", err) } t := sr25519.NewSigningContext(SigningContext, msg) diff --git a/lib/crypto/sr25519/sr25519_test.go b/lib/crypto/sr25519/sr25519_test.go index fcd77089f4..c1106b2c2d 100644 --- a/lib/crypto/sr25519/sr25519_test.go +++ b/lib/crypto/sr25519/sr25519_test.go @@ -149,13 +149,13 @@ func TestVerifySignature(t *testing.T) { publicKey: []byte{}, signature: signature, message: message, - err: errors.New("sr25519: cannot create public key: input is not 32 bytes"), + err: errors.New("creating new sr25519 public key: cannot create public key: input is not 32 bytes"), }, "invalid_signature_length": { publicKey: publicKey, signature: []byte{}, message: message, - err: fmt.Errorf("sr25519: invalid signature length"), + err: fmt.Errorf("verifying sr25519 public key: invalid signature length"), }, "verification_failed": { publicKey: publicKey, diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go index 236cea4c69..16324e17d5 100644 --- a/lib/grandpa/mocks_runtime_test.go +++ b/lib/grandpa/mocks_runtime_test.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } diff --git a/lib/parachain/approval_distribution_message.go b/lib/parachain/approval_distribution_message.go index ebb6b8ef39..296bd86deb 100644 --- a/lib/parachain/approval_distribution_message.go +++ b/lib/parachain/approval_distribution_message.go @@ -8,6 +8,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -102,7 +103,7 @@ type IndirectAssignmentCert struct { // BlockHash a block hash where the canidate appears. BlockHash common.Hash `scale:"1"` // Validator the validator index. - Validator ValidatorIndex `scale:"2"` + Validator parachaintypes.ValidatorIndex `scale:"2"` // Cert the cert itself. Cert AssignmentCert `scale:"3"` } @@ -131,7 +132,7 @@ type IndirectSignedApprovalVote struct { // CandidateIndex the index of the candidate in the list of candidates fully included as-of the block. CandidateIndex CandidateIndex `scale:"2"` // ValidatorIndex the validator index. - ValidatorIndex ValidatorIndex `scale:"3"` + ValidatorIndex parachaintypes.ValidatorIndex `scale:"3"` // Signature the signature of the validator. Signature ValidatorSignature `scale:"4"` } @@ -165,7 +166,7 @@ func (adm *ApprovalDistributionMessage) Value() (scale.VaryingDataTypeValue, err } // New returns new ApprovalDistributionMessage VDT -func (adm ApprovalDistributionMessage) New() ApprovalDistributionMessage { +func (ApprovalDistributionMessage) New() ApprovalDistributionMessage { return NewApprovalDistributionMessageVDT() } diff --git a/lib/parachain/approval_distribution_message_test.go b/lib/parachain/approval_distribution_message_test.go index 122af759b0..138d268642 100644 --- a/lib/parachain/approval_distribution_message_test.go +++ b/lib/parachain/approval_distribution_message_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -36,7 +37,7 @@ func TestEncodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { approvalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(1), false), + IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(1), false), CandidateIndex: 4, }, }) @@ -63,7 +64,7 @@ func TestEncodeApprovalDistributionMessageAssignmentDelay(t *testing.T) { approvalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), true), + IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(2), true), CandidateIndex: 2, }, }) @@ -114,7 +115,7 @@ func TestEncodeApprovalDistributionMessageApprovals(t *testing.T) { IndirectSignedApprovalVote{ BlockHash: hash, CandidateIndex: CandidateIndex(2), - ValidatorIndex: ValidatorIndex(3), + ValidatorIndex: parachaintypes.ValidatorIndex(3), Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, @@ -140,7 +141,7 @@ func TestDecodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() expectedApprovalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, ValidatorIndex(2), false), + IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(2), false), CandidateIndex: 4, }, }) @@ -162,7 +163,7 @@ func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { IndirectSignedApprovalVote{ BlockHash: hash, CandidateIndex: CandidateIndex(2), - ValidatorIndex: ValidatorIndex(3), + ValidatorIndex: parachaintypes.ValidatorIndex(3), Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, @@ -175,7 +176,8 @@ func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { require.Equal(t, expectedApprovalDistributionMessage, approvalDistributionMessage) } -func fakeAssignmentCert(blockHash common.Hash, validator ValidatorIndex, useDelay bool) IndirectAssignmentCert { +func fakeAssignmentCert(blockHash common.Hash, validator parachaintypes.ValidatorIndex, useDelay bool, +) IndirectAssignmentCert { output := [32]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32} proof := [64]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, diff --git a/lib/parachain/available_data_fetching.go b/lib/parachain/available_data_fetching.go index 303124bfc5..56669e0674 100644 --- a/lib/parachain/available_data_fetching.go +++ b/lib/parachain/available_data_fetching.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -74,10 +75,10 @@ func (AvailableData) Index() uint { // although some of the fields may be the same for every parachain. type PersistedValidationData struct { // The parent head-data - ParentHead headData `scale:"1"` + ParentHead []byte `scale:"1"` // The relay-chain block number this is in the context of - RelayParentNumber BlockNumber `scale:"2"` + RelayParentNumber parachaintypes.BlockNumber `scale:"2"` // The relay-chain block storage root this is in the context of RelayParentStorageRoot common.Hash `scale:"3"` diff --git a/lib/parachain/available_data_fetching_test.go b/lib/parachain/available_data_fetching_test.go index f875b6dd4e..d6ba61671a 100644 --- a/lib/parachain/available_data_fetching_test.go +++ b/lib/parachain/available_data_fetching_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -31,7 +32,7 @@ func TestAvailableDataFetchingResponse(t *testing.T) { PoV: PoV{BlockData: testBytes}, ValidationData: PersistedValidationData{ ParentHead: testBytes, - RelayParentNumber: BlockNumber(4), + RelayParentNumber: parachaintypes.BlockNumber(4), RelayParentStorageRoot: testHash, MaxPovSize: 6, }, diff --git a/lib/parachain/chunk_fetching.go b/lib/parachain/chunk_fetching.go index e27f52a1d2..8200bd1160 100644 --- a/lib/parachain/chunk_fetching.go +++ b/lib/parachain/chunk_fetching.go @@ -3,6 +3,7 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -12,7 +13,7 @@ type ChunkFetchingRequest struct { CandidateHash CandidateHash `scale:"1"` // The index of the chunk to fetch. - Index ValidatorIndex `scale:"2"` + Index parachaintypes.ValidatorIndex `scale:"2"` } // Encode returns the SCALE encoding of the ChunkFetchingRequest diff --git a/lib/parachain/chunk_fetching_test.go b/lib/parachain/chunk_fetching_test.go index a666da95b7..9a1d56d762 100644 --- a/lib/parachain/chunk_fetching_test.go +++ b/lib/parachain/chunk_fetching_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -13,7 +14,7 @@ func TestEncodeChunkFetchingRequest(t *testing.T) { CandidateHash: CandidateHash{ common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, - Index: ValidatorIndex(8), + Index: parachaintypes.ValidatorIndex(8), } actualEncode, err := chunkFetchingRequest.Encode() diff --git a/lib/parachain/collation_fetching.go b/lib/parachain/collation_fetching.go index a9a4ceb811..91bee29da3 100644 --- a/lib/parachain/collation_fetching.go +++ b/lib/parachain/collation_fetching.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -14,7 +15,7 @@ type CollationFetchingRequest struct { RelayParent common.Hash `scale:"1"` // Parachain id of the collation - ParaID ParaID `scale:"2"` + ParaID parachaintypes.ParaID `scale:"2"` } // Encode returns the SCALE encoding of the CollationFetchingRequest @@ -27,8 +28,8 @@ type CollationFetchingResponse scale.VaryingDataType // Collation represents a requested collation to be delivered type Collation struct { - CandidateReceipt CandidateReceipt `scale:"1"` - PoV PoV `scale:"2"` + CandidateReceipt parachaintypes.CandidateReceipt `scale:"1"` + PoV PoV `scale:"2"` } // Index returns the index of varying data type diff --git a/lib/parachain/collation_fetching_test.go b/lib/parachain/collation_fetching_test.go index 3a5847069d..f4f0057231 100644 --- a/lib/parachain/collation_fetching_test.go +++ b/lib/parachain/collation_fetching_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/stretchr/testify/require" ) @@ -25,17 +26,17 @@ func TestCollationFetchingResponse(t *testing.T) { testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") - var collatorID CollatorID + var collatorID parachaintypes.CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) - var collatorSignature CollatorSignature + var collatorSignature parachaintypes.CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) collation := Collation{ - CandidateReceipt: CandidateReceipt{ - Descriptor: CandidateDescriptor{ + CandidateReceipt: parachaintypes.CandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: testHash, Collator: collatorID, @@ -44,7 +45,7 @@ func TestCollationFetchingResponse(t *testing.T) { ErasureRoot: testHash, Signature: collatorSignature, ParaHead: testHash, - ValidationCodeHash: ValidationCodeHash(testHash), + ValidationCodeHash: parachaintypes.ValidationCodeHash(testHash), }, CommitmentsHash: testHash, }, diff --git a/lib/parachain/collation_protocol.go b/lib/parachain/collation_protocol.go index 6d8810c772..fb75b8bc33 100644 --- a/lib/parachain/collation_protocol.go +++ b/lib/parachain/collation_protocol.go @@ -1,8 +1,13 @@ package parachain import ( + "fmt" + + "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/libp2p/go-libp2p/core/peer" ) // CollationProtocol represents all network messages on the collation peer-set. @@ -75,9 +80,9 @@ func (c *CollatorProtocolMessage) Value() (val scale.VaryingDataTypeValue, err e // Declare the intent to advertise collations under a collator ID, attaching a // signature of the `PeerId` of the node using the given collator ID key. type Declare struct { - CollatorId CollatorID `scale:"1"` - ParaID uint32 `scale:"2"` - CollatorSignature CollatorSignature `scale:"3"` + CollatorId parachaintypes.CollatorID `scale:"1"` + ParaID uint32 `scale:"2"` + CollatorSignature parachaintypes.CollatorSignature `scale:"3"` } // Index returns the index of varying data type @@ -106,3 +111,83 @@ type CollationSeconded struct { func (CollationSeconded) Index() uint { return 4 } + +const MaxCollationMessageSize uint64 = 100 * 1024 + +type CollationProtocolV1 struct{} + +// Type returns CollationMsgType +func (*CollationProtocolV1) Type() network.MessageType { + return network.CollationMsgType +} + +// Hash returns the hash of the CollationProtocolV1 +func (cp *CollationProtocolV1) Hash() (common.Hash, error) { + // scale encode each extrinsic + encMsg, err := cp.Encode() + if err != nil { + return common.Hash{}, fmt.Errorf("cannot encode message: %w", err) + } + + return common.Blake2bHash(encMsg) +} + +// Encode a collator protocol message using scale encode +func (cp *CollationProtocolV1) Encode() ([]byte, error) { + enc, err := scale.Marshal(*cp) + if err != nil { + return nil, err + } + return enc, nil +} + +func decodeCollationMessage(in []byte) (network.NotificationsMessage, error) { + collationMessage := CollationProtocolV1{} + + err := scale.Unmarshal(in, &collationMessage) + if err != nil { + return nil, fmt.Errorf("cannot decode message: %w", err) + } + + return &collationMessage, nil +} + +func handleCollationMessage(_ peer.ID, msg network.NotificationsMessage) (bool, error) { + // TODO: Add things + fmt.Println("We got a collation message", msg) + return false, nil +} + +func getCollatorHandshake() (network.Handshake, error) { + return &collatorHandshake{}, nil +} + +func decodeCollatorHandshake(_ []byte) (network.Handshake, error) { + return &collatorHandshake{}, nil +} + +func validateCollatorHandshake(_ peer.ID, _ network.Handshake) error { + return nil +} + +type collatorHandshake struct{} + +// String formats a collatorHandshake as a string +func (*collatorHandshake) String() string { + return "collatorHandshake" +} + +// Encode encodes a collatorHandshake message using SCALE +func (*collatorHandshake) Encode() ([]byte, error) { + return []byte{}, nil +} + +// Decode the message into a collatorHandshake +func (*collatorHandshake) Decode(_ []byte) error { + return nil +} + +// IsValid returns true +func (*collatorHandshake) IsValid() bool { + return true +} diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go index 9b90d4a480..9d269e9e61 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/lib/parachain/collation_protocol_test.go @@ -9,6 +9,8 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" ) //go:embed testdata/collation_protocol.yaml @@ -27,11 +29,11 @@ func init() { func TestCollationProtocol(t *testing.T) { t.Parallel() - var collatorID CollatorID + var collatorID parachaintypes.CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) - var collatorSignature CollatorSignature + var collatorSignature parachaintypes.CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) @@ -41,7 +43,7 @@ func TestCollationProtocol(t *testing.T) { hash5 := getDummyHash(5) secondedEnumValue := Seconded{ - Descriptor: CandidateDescriptor{ + Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, Collator: collatorID, @@ -50,12 +52,12 @@ func TestCollationProtocol(t *testing.T) { ErasureRoot: hash5, Signature: collatorSignature, ParaHead: hash5, - ValidationCodeHash: ValidationCodeHash(hash5), + ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, - Commitments: CandidateCommitments{ - UpwardMessages: []UpwardMessage{{1, 2, 3}}, - NewValidationCode: &ValidationCode{1, 2, 3}, - HeadData: headData{1, 2, 3}, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: []byte{1, 2, 3}, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, @@ -90,7 +92,7 @@ func TestCollationProtocol(t *testing.T) { Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ Payload: statementWithSeconded, - ValidatorIndex: ValidatorIndex(5), + ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, }, @@ -143,3 +145,17 @@ func TestCollationProtocol(t *testing.T) { }) } } + +func TestDecodeCollationHandshake(t *testing.T) { + t.Parallel() + + testHandshake := &collatorHandshake{} + + enc, err := testHandshake.Encode() + require.NoError(t, err) + require.Equal(t, []byte{}, enc) + + msg, err := decodeCollatorHandshake(enc) + require.NoError(t, err) + require.Equal(t, testHandshake, msg) +} diff --git a/lib/parachain/network_protocols.go b/lib/parachain/network_protocols.go new file mode 100644 index 0000000000..b84941a39e --- /dev/null +++ b/lib/parachain/network_protocols.go @@ -0,0 +1,83 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + "strings" + + "github.com/ChainSafe/gossamer/lib/common" +) + +const ( + LEGACY_VALIDATION_PROTOCOL_V1 = "/polkadot/validation/1" + LEGACY_COLLATION_PROTOCOL_V1 = "/polkadot/collation/1" +) + +type ReqProtocolName uint + +const ( + ChunkFetchingV1 ReqProtocolName = iota + CollationFetchingV1 + PoVFetchingV1 + AvailableDataFetchingV1 + StatementFetchingV1 + DisputeSendingV1 +) + +type PeerSetProtocolName uint + +const ( + ValidationProtocolName PeerSetProtocolName = iota + CollationProtocolName +) + +func GenerateReqProtocolName(protocol ReqProtocolName, forkID string, GenesisHash common.Hash) string { + prefix := fmt.Sprintf("/%s", GenesisHash.String()) + + if forkID != "" { + prefix = fmt.Sprintf("%s/%s", prefix, forkID) + } + + switch protocol { + case ChunkFetchingV1: + return fmt.Sprintf("%s/req_chunk/1", prefix) + case CollationFetchingV1: + return fmt.Sprintf("%s/req_collation/1", prefix) + case PoVFetchingV1: + return fmt.Sprintf("%s/req_pov/1", prefix) + case AvailableDataFetchingV1: + return fmt.Sprintf("%s/req_available_data/1", prefix) + case StatementFetchingV1: + return fmt.Sprintf("%s/req_statement/1", prefix) + case DisputeSendingV1: + return fmt.Sprintf("%s/send_dispute/1", prefix) + default: + panic("unknown protocol") + } +} + +func GeneratePeersetProtocolName(protocol PeerSetProtocolName, forkID string, GenesisHash common.Hash, version uint32, +) string { + genesisHash := GenesisHash.String() + genesisHash = strings.TrimPrefix(genesisHash, "0x") + + prefix := fmt.Sprintf("/%s", genesisHash) + + if forkID != "" { + prefix = fmt.Sprintf("%s/%s", prefix, forkID) + } + + switch protocol { + case ValidationProtocolName: + return fmt.Sprintf("%s/validation/%d", prefix, version) + // message over this protocol is BitfieldDistributionMessage, StatementDistributionMessage, + // ApprovalDistributionMessage + case CollationProtocolName: + return fmt.Sprintf("%s/collation/%d", prefix, version) + // message over this protocol is CollatorProtocolMessage + default: + panic("unknown protocol") + } +} diff --git a/lib/parachain/service.go b/lib/parachain/service.go new file mode 100644 index 0000000000..2609cf9439 --- /dev/null +++ b/lib/parachain/service.go @@ -0,0 +1,145 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + "time" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" +) + +const ( + CollationProtocolVersion = 1 + ValidationProtocolVersion = 1 +) + +type Service struct { + Network Network +} + +func NewService(net Network, genesisHash common.Hash) (*Service, error) { + // TODO: Use actual fork id from chain spec #3373 + forkID := "" + + validationProtocolID := GeneratePeersetProtocolName( + ValidationProtocolName, forkID, genesisHash, ValidationProtocolVersion) + + // register validation protocol + err := net.RegisterNotificationsProtocol( + protocol.ID(validationProtocolID), + network.ValidationMsgType, + getValidationHandshake, + decodeValidationHandshake, + validateValidationHandshake, + decodeValidationMessage, + handleValidationMessage, + nil, + MaxValidationMessageSize, + ) + if err != nil { + // try with legacy protocol id + err1 := net.RegisterNotificationsProtocol( + protocol.ID(LEGACY_VALIDATION_PROTOCOL_V1), + network.ValidationMsgType, + getValidationHandshake, + decodeValidationHandshake, + validateValidationHandshake, + decodeValidationMessage, + handleValidationMessage, + nil, + MaxValidationMessageSize, + ) + + if err1 != nil { + return nil, fmt.Errorf("registering validation protocol, new: %w, legacy:%w", err, err1) + } + } + + collationProtocolID := GeneratePeersetProtocolName( + CollationProtocolName, forkID, genesisHash, CollationProtocolVersion) + + // register collation protocol + err = net.RegisterNotificationsProtocol( + protocol.ID(collationProtocolID), + network.CollationMsgType, + getCollatorHandshake, + decodeCollatorHandshake, + validateCollatorHandshake, + decodeCollationMessage, + handleCollationMessage, + nil, + MaxCollationMessageSize, + ) + if err != nil { + // try with legacy protocol id + err1 := net.RegisterNotificationsProtocol( + protocol.ID(LEGACY_COLLATION_PROTOCOL_V1), + network.CollationMsgType, + getCollatorHandshake, + decodeCollatorHandshake, + validateCollatorHandshake, + decodeCollationMessage, + handleCollationMessage, + nil, + MaxCollationMessageSize, + ) + + if err1 != nil { + return nil, fmt.Errorf("registering collation protocol, new: %w, legacy:%w", err, err1) + } + } + + parachainService := &Service{ + Network: net, + } + + go parachainService.run() + + return parachainService, nil +} + +// Start starts the Handler +func (Service) Start() error { + return nil +} + +// Stop stops the Handler +func (Service) Stop() error { + return nil +} + +// main loop of parachain service +func (s Service) run() { + + // NOTE: this is a temporary test, just to show that we can send messages to peers + // + time.Sleep(time.Second * 15) + // let's try sending a collation message and validation message to a peer and see what happens + collationMessage := CollationProtocolV1{} + s.Network.GossipMessage(&collationMessage) + + validationMessage := ValidationProtocolV1{} + s.Network.GossipMessage(&validationMessage) + +} + +// Network is the interface required by parachain service for the network +type Network interface { + GossipMessage(msg network.NotificationsMessage) + SendMessage(to peer.ID, msg network.NotificationsMessage) error + RegisterNotificationsProtocol(sub protocol.ID, + messageID network.MessageType, + handshakeGetter network.HandshakeGetter, + handshakeDecoder network.HandshakeDecoder, + handshakeValidator network.HandshakeValidator, + messageDecoder network.MessageDecoder, + messageHandler network.NotificationsMessageHandler, + batchHandler network.NotificationsMessageBatchHandler, + maxSize uint64, + ) error +} diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go index 8fd7c83f46..35b705891b 100644 --- a/lib/parachain/statement.go +++ b/lib/parachain/statement.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -40,7 +41,7 @@ func (s *Statement) Value() (scale.VaryingDataTypeValue, error) { } // Seconded represents a statement that a validator seconds a candidate. -type Seconded CommittedCandidateReceipt +type Seconded parachaintypes.CommittedCandidateReceipt // Index returns the index of varying data type func (Seconded) Index() uint { diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go index 0ba3a4d4a0..c4c2f8dbe8 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/lib/parachain/statement_distribution_message.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -69,7 +70,7 @@ type UncheckedSignedFullStatement struct { Payload Statement `scale:"1"` // The index of the validator signing this statement. - ValidatorIndex ValidatorIndex `scale:"2"` + ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` // The signature by the validator of the signed payload. Signature ValidatorSignature `scale:"3"` @@ -84,7 +85,7 @@ type StatementMetadata struct { CandidateHash CandidateHash `scale:"2"` // Validator that attested the validity. - SignedBy ValidatorIndex `scale:"3"` + SignedBy parachaintypes.ValidatorIndex `scale:"3"` // Signature of seconding validator. Signature ValidatorSignature `scale:"4"` diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go index f8438a87ca..0ff8bb303a 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/lib/parachain/statement_distribution_message_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -27,14 +28,14 @@ func init() { func TestStatementDistributionMessage(t *testing.T) { t.Parallel() - var collatorSignature CollatorSignature + var collatorSignature parachaintypes.CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) var validatorSignature ValidatorSignature copy(validatorSignature[:], tempSignature) - var collatorID CollatorID + var collatorID parachaintypes.CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) @@ -45,7 +46,7 @@ func TestStatementDistributionMessage(t *testing.T) { require.NoError(t, err) secondedEnumValue := Seconded{ - Descriptor: CandidateDescriptor{ + Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, Collator: collatorID, @@ -54,12 +55,12 @@ func TestStatementDistributionMessage(t *testing.T) { ErasureRoot: hash5, Signature: collatorSignature, ParaHead: hash5, - ValidationCodeHash: ValidationCodeHash(hash5), + ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, - Commitments: CandidateCommitments{ - UpwardMessages: []UpwardMessage{{1, 2, 3}}, - NewValidationCode: &ValidationCode{1, 2, 3}, - HeadData: headData{1, 2, 3}, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: []byte{1, 2, 3}, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, @@ -73,7 +74,7 @@ func TestStatementDistributionMessage(t *testing.T) { Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ Payload: statementWithValid, - ValidatorIndex: ValidatorIndex(5), + ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, } @@ -82,7 +83,7 @@ func TestStatementDistributionMessage(t *testing.T) { Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ Payload: statementWithSeconded, - ValidatorIndex: ValidatorIndex(5), + ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, } @@ -90,7 +91,7 @@ func TestStatementDistributionMessage(t *testing.T) { secondedStatementWithLargePayload := SecondedStatementWithLargePayload{ RelayParent: hash5, CandidateHash: CandidateHash{hash5}, - SignedBy: ValidatorIndex(5), + SignedBy: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, } diff --git a/lib/parachain/statement_fetching.go b/lib/parachain/statement_fetching.go index 1f9e9332ea..9080916c36 100644 --- a/lib/parachain/statement_fetching.go +++ b/lib/parachain/statement_fetching.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -28,7 +29,7 @@ func (s *StatementFetchingRequest) Encode() ([]byte, error) { type StatementFetchingResponse scale.VaryingDataType // MissingDataInStatement represents the data missing to reconstruct the full signed statement. -type MissingDataInStatement CommittedCandidateReceipt +type MissingDataInStatement parachaintypes.CommittedCandidateReceipt // Index returns the index of varying data type func (MissingDataInStatement) Index() uint { diff --git a/lib/parachain/statement_fetching_test.go b/lib/parachain/statement_fetching_test.go index bb315588a1..d606b25d44 100644 --- a/lib/parachain/statement_fetching_test.go +++ b/lib/parachain/statement_fetching_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/stretchr/testify/require" ) @@ -71,16 +72,16 @@ func TestStatementFetchingResponse(t *testing.T) { testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") - var collatorID CollatorID + var collatorID parachaintypes.CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) - var collatorSignature CollatorSignature + var collatorSignature parachaintypes.CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) missingDataInStatement := MissingDataInStatement{ - Descriptor: CandidateDescriptor{ + Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: testHash, Collator: collatorID, @@ -89,12 +90,12 @@ func TestStatementFetchingResponse(t *testing.T) { ErasureRoot: testHash, Signature: collatorSignature, ParaHead: testHash, - ValidationCodeHash: ValidationCodeHash(testHash), + ValidationCodeHash: parachaintypes.ValidationCodeHash(testHash), }, - Commitments: CandidateCommitments{ - UpwardMessages: []UpwardMessage{{1, 2, 3}}, - NewValidationCode: &ValidationCode{1, 2, 3}, - HeadData: headData{1, 2, 3}, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: []byte{1, 2, 3}, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go index 4aae3c431e..ee7c8e0ad7 100644 --- a/lib/parachain/statement_test.go +++ b/lib/parachain/statement_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -19,18 +20,18 @@ func getDummyHash(num byte) common.Hash { func TestStatement(t *testing.T) { t.Parallel() - var collatorID CollatorID + var collatorID parachaintypes.CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) - var collatorSignature CollatorSignature + var collatorSignature parachaintypes.CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) hash5 := getDummyHash(5) secondedEnumValue := Seconded{ - Descriptor: CandidateDescriptor{ + Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, Collator: collatorID, @@ -39,12 +40,12 @@ func TestStatement(t *testing.T) { ErasureRoot: hash5, Signature: collatorSignature, ParaHead: hash5, - ValidationCodeHash: ValidationCodeHash(hash5), + ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, - Commitments: CandidateCommitments{ - UpwardMessages: []UpwardMessage{{1, 2, 3}}, - NewValidationCode: &ValidationCode{1, 2, 3}, - HeadData: headData{1, 2, 3}, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: []byte{1, 2, 3}, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/testdata/westend.yaml b/lib/parachain/types/testdata/westend.yaml similarity index 100% rename from lib/parachain/testdata/westend.yaml rename to lib/parachain/types/testdata/westend.yaml diff --git a/lib/parachain/types.go b/lib/parachain/types/types.go similarity index 98% rename from lib/parachain/types.go rename to lib/parachain/types/types.go index 1eba5fc375..b4af337530 100644 --- a/lib/parachain/types.go +++ b/lib/parachain/types/types.go @@ -1,7 +1,7 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package parachaintypes import ( "fmt" @@ -11,6 +11,9 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" ) +// The primary purpose of this package is to put types being used by other packages to avoid cyclic +// dependencies. + // NOTE: https://github.com/ChainSafe/gossamer/pull/3297#discussion_r1214740051 // ValidatorIndex Index of the validator. Used as a lightweight replacement of the `ValidatorId` when appropriate diff --git a/lib/parachain/types_test.go b/lib/parachain/types/types_test.go similarity index 87% rename from lib/parachain/types_test.go rename to lib/parachain/types/types_test.go index b4eb81ea07..068a093f43 100644 --- a/lib/parachain/types_test.go +++ b/lib/parachain/types/types_test.go @@ -1,7 +1,7 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package parachaintypes import ( _ "embed" @@ -45,23 +45,23 @@ func Test_Validators(t *testing.T) { require.NoError(t, err) expected := []ValidatorID{ - ValidatorID(mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d")), - ValidatorID(mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e")), - ValidatorID(mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72")), - ValidatorID(mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68")), - ValidatorID(mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511")), - ValidatorID(mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e")), - ValidatorID(mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841")), - ValidatorID(mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12")), - ValidatorID(mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c")), - ValidatorID(mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547")), - ValidatorID(mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237")), - ValidatorID(mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53")), - ValidatorID(mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016")), - ValidatorID(mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848")), - ValidatorID(mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a")), - ValidatorID(mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a")), - ValidatorID(mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936")), + mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d"), + mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e"), + mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72"), + mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68"), + mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511"), + mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e"), + mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841"), + mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12"), + mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c"), + mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547"), + mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237"), + mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53"), + mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016"), + mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848"), + mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a"), + mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a"), + mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936"), } require.Equal(t, expected, validatorIDs) diff --git a/lib/parachain/validation_protocol.go b/lib/parachain/validation_protocol.go new file mode 100644 index 0000000000..8c390289c2 --- /dev/null +++ b/lib/parachain/validation_protocol.go @@ -0,0 +1,94 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/libp2p/go-libp2p/core/peer" +) + +const MaxValidationMessageSize uint64 = 100 * 1024 + +type ValidationProtocolV1 struct { + // TODO: Implement this struct https://github.com/ChainSafe/gossamer/issues/3318 +} + +// Type returns ValidationMsgType +func (*ValidationProtocolV1) Type() network.MessageType { + return network.ValidationMsgType +} + +// Hash returns the hash of the CollationProtocolV1 +func (vp *ValidationProtocolV1) Hash() (common.Hash, error) { + encMsg, err := vp.Encode() + if err != nil { + return common.Hash{}, fmt.Errorf("cannot encode message: %w", err) + } + + return common.Blake2bHash(encMsg) +} + +// Encode a collator protocol message using scale encode +func (vp *ValidationProtocolV1) Encode() ([]byte, error) { + enc, err := scale.Marshal(*vp) + if err != nil { + return enc, err + } + return enc, nil +} + +func decodeValidationMessage(in []byte) (network.NotificationsMessage, error) { + validationMessage := ValidationProtocolV1{} + + err := scale.Unmarshal(in, &validationMessage) + if err != nil { + return nil, fmt.Errorf("cannot decode message: %w", err) + } + + return &validationMessage, nil +} + +func handleValidationMessage(_ peer.ID, msg network.NotificationsMessage) (bool, error) { + // TODO: Add things + fmt.Println("We got a validation message", msg) + return false, nil +} + +func getValidationHandshake() (network.Handshake, error) { + return &collatorHandshake{}, nil +} + +func decodeValidationHandshake(_ []byte) (network.Handshake, error) { + return &validationHandshake{}, nil +} + +func validateValidationHandshake(_ peer.ID, _ network.Handshake) error { + return nil +} + +type validationHandshake struct{} + +// String formats a validationHandshake as a string +func (*validationHandshake) String() string { + return "validationHandshake" +} + +// Encode encodes a validationHandshake message using SCALE +func (*validationHandshake) Encode() ([]byte, error) { + return []byte{}, nil +} + +// Decode the message into a validationHandshake +func (*validationHandshake) Decode(_ []byte) error { + return nil +} + +// IsValid returns true +func (*validationHandshake) IsValid() bool { + return true +} diff --git a/lib/parachain/validation_protocol_test.go b/lib/parachain/validation_protocol_test.go new file mode 100644 index 0000000000..b61928825b --- /dev/null +++ b/lib/parachain/validation_protocol_test.go @@ -0,0 +1,23 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestDecodeValidationHandshake(t *testing.T) { + t.Parallel() + + testHandshake := &validationHandshake{} + + enc, err := testHandshake.Encode() + require.NoError(t, err) + + msg, err := decodeValidationHandshake(enc) + require.NoError(t, err) + require.Equal(t, testHandshake, msg) +} diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index 473710cd99..e31484d195 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -8,7 +8,7 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -50,17 +50,17 @@ type Instance interface { GrandpaSubmitReportEquivocationUnsignedExtrinsic( equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, ) error - ParachainHostValidators() ([]parachain.ValidatorID, error) - ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) + ParachainHostValidators() ([]parachaintypes.ValidatorID, error) + ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) ParachainHostCheckValidationOutputs( - parachainID parachain.ParaID, - outputs parachain.CandidateCommitments, + parachainID parachaintypes.ParaID, + outputs parachaintypes.CandidateCommitments, ) (bool, error) - ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) + ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) ParachainHostCandidatePendingAvailability( - parachainID parachain.ParaID, - ) (*parachain.CommittedCandidateReceipt, error) + parachainID parachaintypes.ParaID, + ) (*parachaintypes.CommittedCandidateReceipt, error) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, error) - ParachainHostSessionInfo(sessionIndex parachain.SessionIndex) (*parachain.SessionInfo, error) + ParachainHostSessionInfo(sessionIndex parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) } diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index 9db3aa6bf0..a2de78c0e9 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -11,7 +11,7 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" @@ -371,10 +371,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidateEvents() *gomock.Call } // ParachainHostCandidatePendingAvailability mocks base method. -func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachain.ParaID) (*parachain.CommittedCandidateReceipt, error) { +func (m *MockInstance) ParachainHostCandidatePendingAvailability(arg0 parachaintypes.ParaID) (*parachaintypes.CommittedCandidateReceipt, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCandidatePendingAvailability", arg0) - ret0, _ := ret[0].(*parachain.CommittedCandidateReceipt) + ret0, _ := ret[0].(*parachaintypes.CommittedCandidateReceipt) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -386,7 +386,7 @@ func (mr *MockInstanceMockRecorder) ParachainHostCandidatePendingAvailability(ar } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachain.ParaID, arg1 parachain.CandidateCommitments) (bool, error) { +func (m *MockInstance) ParachainHostCheckValidationOutputs(arg0 parachaintypes.ParaID, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -401,10 +401,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar } // ParachainHostSessionIndexForChild mocks base method. -func (m *MockInstance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionIndexForChild") - ret0, _ := ret[0].(parachain.SessionIndex) + ret0, _ := ret[0].(parachaintypes.SessionIndex) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -416,10 +416,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionIndexForChild() *gomock. } // ParachainHostSessionInfo mocks base method. -func (m *MockInstance) ParachainHostSessionInfo(arg0 parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (m *MockInstance) ParachainHostSessionInfo(arg0 parachaintypes.SessionIndex) (*parachaintypes.SessionInfo, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostSessionInfo", arg0) - ret0, _ := ret[0].(*parachain.SessionInfo) + ret0, _ := ret[0].(*parachaintypes.SessionInfo) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -431,10 +431,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * } // ParachainHostValidatorGroups mocks base method. -func (m *MockInstance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidatorGroups") - ret0, _ := ret[0].(*parachain.ValidatorGroups) + ret0, _ := ret[0].(*parachaintypes.ValidatorGroups) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -446,10 +446,10 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidatorGroups() *gomock.Call } // ParachainHostValidators mocks base method. -func (m *MockInstance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (m *MockInstance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidators") - ret0, _ := ret[0].([]parachain.ValidatorID) + ret0, _ := ret[0].([]parachaintypes.ValidatorID) ret1, _ := ret[1].(error) return ret0, ret1 } From 2fb51894a7e46564f688c18240213cc6124573f4 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Thu, 27 Jul 2023 15:45:28 +0530 Subject: [PATCH 19/85] Implemented extra parachain host runtime API calls (#3237) Implemented following parachain host runtime calls - ParachainHost_persisted_validation_data - ParachainHost_validation_code --- dot/core/mock_runtime_instance_test.go | 30 ++++++ dot/state/mocks_runtime_test.go | 30 ++++++ dot/sync/mock_runtime_test.go | 30 ++++++ lib/babe/mocks/runtime.go | 30 ++++++ lib/blocktree/mocks_test.go | 30 ++++++ lib/grandpa/mocks_runtime_test.go | 30 ++++++ .../approval_distribution_message.go | 8 +- lib/parachain/types/types.go | 79 ++++++++++++++++ lib/parachain/types/types_test.go | 59 ++++++++++++ lib/runtime/constants.go | 4 + lib/runtime/interface.go | 6 ++ lib/runtime/mocks/mocks.go | 30 ++++++ lib/runtime/wazero/instance.go | 91 +++++++++++++++---- pkg/scale/varying_data_type_test.go | 2 +- 14 files changed, 438 insertions(+), 21 deletions(-) diff --git a/dot/core/mock_runtime_instance_test.go b/dot/core/mock_runtime_instance_test.go index a02e92fdf8..5c44e651d0 100644 --- a/dot/core/mock_runtime_instance_test.go +++ b/dot/core/mock_runtime_instance_test.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/dot/state/mocks_runtime_test.go b/dot/state/mocks_runtime_test.go index 4eeca31e69..92c3e3e7ab 100644 --- a/dot/state/mocks_runtime_test.go +++ b/dot/state/mocks_runtime_test.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index 004c535d73..7ecc9d8dfe 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 9963012cfd..03568f2dbd 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index 476b2a9606..e76671b7f6 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go index 16324e17d5..a28960eca4 100644 --- a/lib/grandpa/mocks_runtime_test.go +++ b/lib/grandpa/mocks_runtime_test.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/parachain/approval_distribution_message.go b/lib/parachain/approval_distribution_message.go index 296bd86deb..c97a027592 100644 --- a/lib/parachain/approval_distribution_message.go +++ b/lib/parachain/approval_distribution_message.go @@ -17,7 +17,7 @@ import ( type AssignmentCertKind scale.VaryingDataType // New will enable scale to create new instance when needed -func (ack AssignmentCertKind) New() AssignmentCertKind { +func (AssignmentCertKind) New() AssignmentCertKind { return NewAssignmentCertKindVDT() } @@ -77,7 +77,7 @@ func NewVRFDelay() RelayVRFDelay { } // Index returns varying data type index -func (rvd RelayVRFDelay) Index() uint { +func (RelayVRFDelay) Index() uint { return 1 } @@ -121,7 +121,7 @@ type Assignment struct { type Assignments []Assignment // Index returns varying data type index -func (a Assignments) Index() uint { +func (Assignments) Index() uint { return 0 } @@ -141,7 +141,7 @@ type IndirectSignedApprovalVote struct { type Approvals []IndirectSignedApprovalVote // Index returns varying data type index -func (ap Approvals) Index() uint { +func (Approvals) Index() uint { return 1 } diff --git a/lib/parachain/types/types.go b/lib/parachain/types/types.go index b4af337530..642fe80296 100644 --- a/lib/parachain/types/types.go +++ b/lib/parachain/types/types.go @@ -389,3 +389,82 @@ func NewCandidateEvents() (scale.VaryingDataTypeSlice, error) { return scale.NewVaryingDataTypeSlice(vdt), nil } + +// PersistedValidationData should be relatively lightweight primarily because it is constructed +// during inclusion for each candidate and therefore lies on the critical path of inclusion. +type PersistedValidationData struct { + ParentHead HeadData `scale:"1"` + RelayParentNumber uint32 `scale:"2"` + RelayParentStorageRoot common.Hash `scale:"3"` + MaxPovSize uint32 `scale:"4"` +} + +// OccupiedCoreAssumption is an assumption being made about the state of an occupied core. +type OccupiedCoreAssumption scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (o *OccupiedCoreAssumption) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*o) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + // store original ParentVDT with VaryingDataType that has been set + *o = OccupiedCoreAssumption(vdt) + return nil +} + +// Value will return value from underying VaryingDataType +func (o *OccupiedCoreAssumption) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*o) + return vdt.Value() +} + +// IncludedOccupiedCoreAssumption means the candidate occupying the core was made available and +// included to free the core. +type IncludedOccupiedCoreAssumption struct{} + +// Index returns VDT index +func (IncludedOccupiedCoreAssumption) Index() uint { + return 0 +} + +func (IncludedOccupiedCoreAssumption) String() string { + return "Included" +} + +// TimedOutOccupiedCoreAssumption means the candidate occupying the core timed out and freed the +// core without advancing the para. +type TimedOutOccupiedCoreAssumption struct{} + +// Index returns VDT index +func (TimedOutOccupiedCoreAssumption) Index() uint { + return 1 +} + +func (TimedOutOccupiedCoreAssumption) String() string { + return "TimedOut" +} + +// FreeOccupiedCoreAssumption means the core was not occupied to begin with. +type FreeOccupiedCoreAssumption struct{} + +// Index returns VDT index +func (FreeOccupiedCoreAssumption) Index() uint { + return 2 +} + +func (FreeOccupiedCoreAssumption) String() string { + return "Free" +} + +// NewOccupiedCoreAssumption creates a OccupiedCoreAssumption varying data type. +func NewOccupiedCoreAssumption() OccupiedCoreAssumption { + vdt := scale.MustNewVaryingDataType( + IncludedOccupiedCoreAssumption{}, + FreeOccupiedCoreAssumption{}, + TimedOutOccupiedCoreAssumption{}) + + return OccupiedCoreAssumption(vdt) +} diff --git a/lib/parachain/types/types_test.go b/lib/parachain/types/types_test.go index 068a093f43..3f82f619e0 100644 --- a/lib/parachain/types/types_test.go +++ b/lib/parachain/types/types_test.go @@ -371,3 +371,62 @@ func TestMustHexTo32BArray(t *testing.T) { result := mustHexTo32BArray(t, inputHex) require.Equal(t, expectedArray, result) } + +func TestPersistedValidationData(t *testing.T) { + expected := []byte{12, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0} //nolint:lll + + pvd := PersistedValidationData{ + ParentHead: HeadData{Data: []byte{7, 8, 9}}, + RelayParentNumber: uint32(10), + RelayParentStorageRoot: common.Hash{}, + MaxPovSize: uint32(1024), + } + + actual, err := scale.Marshal(pvd) + require.NoError(t, err) + require.Equal(t, expected, actual) + + newpvd := PersistedValidationData{} + err = scale.Unmarshal(actual, &newpvd) + require.NoError(t, err) + require.Equal(t, pvd, newpvd) +} + +func TestOccupiedCoreAssumption(t *testing.T) { + + for _, tc := range []struct { + name string + in scale.VaryingDataTypeValue + out byte + }{ + { + name: "included", + in: IncludedOccupiedCoreAssumption{}, + out: 0, + }, + { + name: "timeout", + in: TimedOutOccupiedCoreAssumption{}, + out: 1, + }, + { + name: "free", + in: FreeOccupiedCoreAssumption{}, + out: 2, + }, + } { + t.Run(tc.name, func(t *testing.T) { + vdt := NewOccupiedCoreAssumption() + err := vdt.Set(tc.in) + require.NoError(t, err) + res, err := scale.Marshal(vdt) + require.NoError(t, err) + require.Equal(t, []byte{tc.out}, res) + + vdt2 := NewOccupiedCoreAssumption() + err = scale.Unmarshal([]byte{tc.out}, &vdt2) + require.NoError(t, err) + require.Equal(t, tc.in.Index(), uint(tc.out)) + }) + } +} diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index ce70426c81..ca8d84df85 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -68,6 +68,10 @@ const ( TransactionPaymentCallAPIQueryCallInfo = "TransactionPaymentCallApi_query_call_info" // TransactionPaymentCallAPIQueryCallFeeDetails returns call query call fee details TransactionPaymentCallAPIQueryCallFeeDetails = "TransactionPaymentCallApi_query_call_fee_details" + // ParachainHostPersistedValidationData returns parachain host's persisted validation data + ParachainHostPersistedValidationData = "ParachainHost_persisted_validation_data" + // ParachainHostValidationCode returns parachain host's validation code + ParachainHostValidationCode = "ParachainHost_validation_code" // ParachainHostValidators is the runtime API call ParachainHost_validators ParachainHostValidators = "ParachainHost_validators" // ParachainHostValidatorGroups is the runtime API call ParachainHost_validator_groups diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index e31484d195..b0d7de5a78 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -50,6 +50,12 @@ type Instance interface { GrandpaSubmitReportEquivocationUnsignedExtrinsic( equivocationProof types.GrandpaEquivocationProof, keyOwnershipProof types.GrandpaOpaqueKeyOwnershipProof, ) error + ParachainHostPersistedValidationData( + parachaidID uint32, + assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.PersistedValidationData, error) + ParachainHostValidationCode(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.ValidationCode, error) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index a2de78c0e9..5b9843ce26 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -400,6 +400,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) } +// ParachainHostPersistedValidationData mocks base method. +func (m *MockInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + // ParachainHostSessionIndexForChild mocks base method. func (m *MockInstance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { m.ctrl.T.Helper() @@ -430,6 +445,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostSessionInfo(arg0 interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostSessionInfo", reflect.TypeOf((*MockInstance)(nil).ParachainHostSessionInfo), arg0) } +// ParachainHostValidationCode mocks base method. +func (m *MockInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index becba84778..3f92f6d3f1 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -16,7 +16,7 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/parachain" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/runtime/offchain" "github.com/ChainSafe/gossamer/lib/transaction" @@ -819,15 +819,73 @@ func (in *Instance) GrandpaSubmitReportEquivocationUnsignedExtrinsic( return nil } +// ParachainHostPersistedValidationData returns persisted validation data for the given parachain id. +func (in *Instance) ParachainHostPersistedValidationData( + parachaidID uint32, + assumption parachaintypes.OccupiedCoreAssumption, +) (*parachaintypes.PersistedValidationData, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(parachaidID) + if err != nil { + return nil, fmt.Errorf("encoding equivocation proof: %w", err) + } + err = encoder.Encode(assumption) + if err != nil { + return nil, fmt.Errorf("encoding key ownership proof: %w", err) + } + + encodedPersistedValidationData, err := in.Exec(runtime.ParachainHostPersistedValidationData, buffer.Bytes()) + if err != nil { + return nil, err + } + + persistedValidationData := ¶chaintypes.PersistedValidationData{} + err = scale.Unmarshal(encodedPersistedValidationData, &persistedValidationData) + if err != nil { + return nil, fmt.Errorf("scale decoding: %w", err) + } + + return persistedValidationData, nil +} + +// ParachainHostValidationCode returns validation code for the given parachain id. +func (in *Instance) ParachainHostValidationCode(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, +) (*parachaintypes.ValidationCode, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(parachaidID) + if err != nil { + return nil, fmt.Errorf("encoding parachain id: %w", err) + } + err = encoder.Encode(assumption) + if err != nil { + return nil, fmt.Errorf("encoding occupied core assumption: %w", err) + } + + encodedValidationCode, err := in.Exec(runtime.ParachainHostValidationCode, buffer.Bytes()) + if err != nil { + return nil, err + } + + var validationCode *parachaintypes.ValidationCode + err = scale.Unmarshal(encodedValidationCode, &validationCode) + if err != nil { + return nil, fmt.Errorf("scale decoding: %w", err) + } + + return validationCode, nil +} + // ParachainHostValidators returns the validator set at the current state. // The specified validators are responsible for backing parachains for the current state. -func (in *Instance) ParachainHostValidators() ([]parachain.ValidatorID, error) { +func (in *Instance) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) { encodedValidators, err := in.Exec(runtime.ParachainHostValidators, []byte{}) if err != nil { return nil, fmt.Errorf("exec: %w", err) } - var validatorIDs []parachain.ValidatorID + var validatorIDs []parachaintypes.ValidatorID err = scale.Unmarshal(encodedValidators, &validatorIDs) if err != nil { return nil, fmt.Errorf("unmarshalling: %w", err) @@ -838,13 +896,13 @@ func (in *Instance) ParachainHostValidators() ([]parachain.ValidatorID, error) { // ParachainHostValidatorGroups returns the validator groups used during the current session. // The validators in the groups are referred to by the validator set Id. -func (in *Instance) ParachainHostValidatorGroups() (*parachain.ValidatorGroups, error) { +func (in *Instance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { encodedValidatorGroups, err := in.Exec(runtime.ParachainHostValidatorGroups, []byte{}) if err != nil { return nil, fmt.Errorf("exec: %w", err) } - var validatorGroups parachain.ValidatorGroups + var validatorGroups parachaintypes.ValidatorGroups err = scale.Unmarshal(encodedValidatorGroups, &validatorGroups) if err != nil { return nil, fmt.Errorf("unmarshalling: %w", err) @@ -860,7 +918,7 @@ func (in *Instance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlic return nil, fmt.Errorf("exec: %w", err) } - availabilityCores, err := parachain.NewAvailabilityCores() + availabilityCores, err := parachaintypes.NewAvailabilityCores() if err != nil { return nil, fmt.Errorf("new availability cores: %w", err) } @@ -875,8 +933,8 @@ func (in *Instance) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlic // ParachainHostCheckValidationOutputs checks the validation outputs of a candidate. // Returns true if the candidate is valid. func (in *Instance) ParachainHostCheckValidationOutputs( - parachainID parachain.ParaID, - outputs parachain.CandidateCommitments, + parachainID parachaintypes.ParaID, + outputs parachaintypes.CandidateCommitments, ) (bool, error) { buffer := bytes.NewBuffer(nil) encoder := scale.NewEncoder(buffer) @@ -904,13 +962,13 @@ func (in *Instance) ParachainHostCheckValidationOutputs( } // ParachainHostSessionIndexForChild returns the session index that is expected at the child of a block. -func (in *Instance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, error) { +func (in *Instance) ParachainHostSessionIndexForChild() (parachaintypes.SessionIndex, error) { encodedSessionIndex, err := in.Exec(runtime.ParachainHostSessionIndexForChild, []byte{}) if err != nil { return 0, fmt.Errorf("exec: %w", err) } - var sessionIndex parachain.SessionIndex + var sessionIndex parachaintypes.SessionIndex err = scale.Unmarshal(encodedSessionIndex, &sessionIndex) if err != nil { return 0, fmt.Errorf("unmarshalling: %w", err) @@ -922,8 +980,8 @@ func (in *Instance) ParachainHostSessionIndexForChild() (parachain.SessionIndex, // ParachainHostCandidatePendingAvailability returns the receipt of a candidate pending availability // for any parachain assigned to an occupied availability core. func (in *Instance) ParachainHostCandidatePendingAvailability( - parachainID parachain.ParaID, -) (*parachain.CommittedCandidateReceipt, error) { + parachainID parachaintypes.ParaID, +) (*parachaintypes.CommittedCandidateReceipt, error) { buffer := bytes.NewBuffer(nil) encoder := scale.NewEncoder(buffer) err := encoder.Encode(parachainID) @@ -936,7 +994,7 @@ func (in *Instance) ParachainHostCandidatePendingAvailability( return nil, fmt.Errorf("exec: %w", err) } - var candidateReceipt *parachain.CommittedCandidateReceipt + var candidateReceipt *parachaintypes.CommittedCandidateReceipt err = scale.Unmarshal(encodedCandidateReceipt, &candidateReceipt) if err != nil { return nil, fmt.Errorf("unmarshalling: %w", err) @@ -952,7 +1010,7 @@ func (in *Instance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, return nil, fmt.Errorf("exec: %w", err) } - candidateEvents, err := parachain.NewCandidateEvents() + candidateEvents, err := parachaintypes.NewCandidateEvents() if err != nil { return nil, fmt.Errorf("create new candidate events: %w", err) } @@ -965,7 +1023,8 @@ func (in *Instance) ParachainHostCandidateEvents() (*scale.VaryingDataTypeSlice, } // ParachainHostSessionInfo returns the session info of the given session, if available. -func (in *Instance) ParachainHostSessionInfo(sessionIndex parachain.SessionIndex) (*parachain.SessionInfo, error) { +func (in *Instance) ParachainHostSessionInfo(sessionIndex parachaintypes.SessionIndex) ( + *parachaintypes.SessionInfo, error) { buffer := bytes.NewBuffer(nil) encoder := scale.NewEncoder(buffer) err := encoder.Encode(sessionIndex) @@ -978,7 +1037,7 @@ func (in *Instance) ParachainHostSessionInfo(sessionIndex parachain.SessionIndex return nil, fmt.Errorf("exec: %w", err) } - var sessionInfo *parachain.SessionInfo + var sessionInfo *parachaintypes.SessionInfo err = scale.Unmarshal(encodedSessionInfo, &sessionInfo) if err != nil { return nil, fmt.Errorf("unmarshalling: %w", err) diff --git a/pkg/scale/varying_data_type_test.go b/pkg/scale/varying_data_type_test.go index 0fb9c755b4..9b1090e1e4 100644 --- a/pkg/scale/varying_data_type_test.go +++ b/pkg/scale/varying_data_type_test.go @@ -34,7 +34,7 @@ type customVDT VaryingDataType type customVDTWithNew VaryingDataType -func (cvwn customVDTWithNew) New() customVDTWithNew { +func (customVDTWithNew) New() customVDTWithNew { return customVDTWithNew(mustNewVaryingDataType(VDTValue{}, VDTValue1{}, VDTValue2{}, VDTValue3(0))) } From 4702a7ac8a8cca4250a7dcf759b9e789b30b75fd Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Tue, 1 Aug 2023 10:06:12 +0530 Subject: [PATCH 20/85] Fix(lib/parachain): use actual forkID in parachain network protocol (#3385) --- dot/build_spec.go | 2 ++ dot/build_spec_test.go | 5 +++++ dot/mock_node_builder_test.go | 8 ++++---- dot/node.go | 4 ++-- dot/node_integration_test.go | 9 ++++++--- dot/services.go | 4 ++-- dot/utils_test.go | 2 +- lib/babe/verify_test.go | 23 ++++++++++++++++------- lib/genesis/genesis.go | 3 +++ lib/parachain/service.go | 5 +---- 10 files changed, 42 insertions(+), 23 deletions(-) diff --git a/dot/build_spec.go b/dot/build_spec.go index fd377da69c..b3ff19d24a 100644 --- a/dot/build_spec.go +++ b/dot/build_spec.go @@ -29,6 +29,7 @@ func (b *BuildSpec) ToJSON() ([]byte, error) { ID: b.genesis.ID, ChainType: b.genesis.ChainType, Bootnodes: b.genesis.Bootnodes, + ForkID: b.genesis.ForkID, ProtocolID: b.genesis.ProtocolID, Properties: b.genesis.Properties, Genesis: genesis.Fields{ @@ -45,6 +46,7 @@ func (b *BuildSpec) ToJSONRaw() ([]byte, error) { ID: b.genesis.ID, ChainType: b.genesis.ChainType, Bootnodes: b.genesis.Bootnodes, + ForkID: b.genesis.ForkID, ProtocolID: b.genesis.ProtocolID, Properties: b.genesis.Properties, Genesis: genesis.Fields{ diff --git a/dot/build_spec_test.go b/dot/build_spec_test.go index 63f836cbd8..730e04b22a 100644 --- a/dot/build_spec_test.go +++ b/dot/build_spec_test.go @@ -35,6 +35,7 @@ func TestBuildSpec_ToJSON(t *testing.T) { "chainType": "", "bootNodes": null, "telemetryEndpoints": null, + "forkId": "", "protocolId": "", "genesis": {}, "properties": null, @@ -51,6 +52,7 @@ func TestBuildSpec_ToJSON(t *testing.T) { Name: "test", ID: "ID", ChainType: "chainType", + ForkID: "forkId", ProtocolID: "protocol", ConsensusEngine: "babe", }, @@ -62,6 +64,7 @@ func TestBuildSpec_ToJSON(t *testing.T) { "chainType": "chainType", "bootNodes": null, "telemetryEndpoints": null, + "forkId": "forkId", "protocolId": "protocol", "genesis": {}, "properties": null, @@ -99,6 +102,7 @@ func TestBuildSpec_ToJSON(t *testing.T) { "node2" ], "telemetryEndpoints": null, + "forkId": "", "protocolId": "protocol", "genesis": {}, "properties": { @@ -247,6 +251,7 @@ func TestBuildSpec_ToJSONRaw(t *testing.T) { "chainType": "", "bootNodes": null, "telemetryEndpoints": null, + "forkId": "", "protocolId": "", "genesis": {}, "properties": null, diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index cb679fc79c..7e2ecae031 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -138,18 +138,18 @@ func (mr *MocknodeBuilderIfaceMockRecorder) createNetworkService(config, stateSr } // createParachainHostService mocks base method. -func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, genesishHash common.Hash) (*parachain.Service, error) { +func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, forkID string, genesishHash common.Hash) (*parachain.Service, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "createParachainHostService", net, genesishHash) + ret := m.ctrl.Call(m, "createParachainHostService", net, forkID, genesishHash) ret0, _ := ret[0].(*parachain.Service) ret1, _ := ret[1].(error) return ret0, ret1 } // createParachainHostService indicates an expected call of createParachainHostService. -func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, genesishHash interface{}) *gomock.Call { +func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, forkID, genesishHash interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, genesishHash) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, forkID, genesishHash) } // createRPCService mocks base method. diff --git a/dot/node.go b/dot/node.go index 6c73728dc9..4a6bec6a25 100644 --- a/dot/node.go +++ b/dot/node.go @@ -64,7 +64,7 @@ type nodeBuilderIface interface { dh *digest.Handler) (*core.Service, error) createGRANDPAService(config *cfg.Config, st *state.Service, ks KeyStore, net *network.Service, telemetryMailer Telemetry) (*grandpa.Service, error) - createParachainHostService(net *network.Service, genesishHash common.Hash) (*parachain.Service, error) + createParachainHostService(net *network.Service, forkID string, genesishHash common.Hash) (*parachain.Service, error) newSyncService(config *cfg.Config, st *state.Service, finalityGadget BlockJustificationVerifier, verifier *babe.VerificationManager, cs *core.Service, net *network.Service, telemetryMailer Telemetry) (*dotsync.Service, error) @@ -381,7 +381,7 @@ func newNode(config *cfg.Config, } nodeSrvcs = append(nodeSrvcs, fg) - phs, err := builder.createParachainHostService(networkSrvc, stateSrvc.Block.GenesisHash()) + phs, err := builder.createParachainHostService(networkSrvc, gd.ForkID, stateSrvc.Block.GenesisHash()) if err != nil { return nil, err } diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 3fce8fe0c1..9668d2ccaf 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -120,7 +120,7 @@ func TestNewNode(t *testing.T) { return stateSrvc, nil }) - phs, err := parachain.NewService(testNetworkService, common.Hash{}) + phs, err := parachain.NewService(testNetworkService, "random_fork_id", common.Hash{}) require.NoError(t, err) m.EXPECT().createRuntimeStorage(gomock.AssignableToTypeOf(&state.Service{})).Return(&runtime. @@ -153,8 +153,11 @@ func TestNewNode(t *testing.T) { }) m.EXPECT().createNetworkService(initConfig, gomock.AssignableToTypeOf(&state.Service{}), gomock.AssignableToTypeOf(&telemetry.Mailer{})).Return(testNetworkService, nil) - m.EXPECT().createParachainHostService(gomock.AssignableToTypeOf(&network.Service{}), - gomock.AssignableToTypeOf(common.Hash{})).Return(phs, nil) + m.EXPECT().createParachainHostService( + gomock.AssignableToTypeOf(&network.Service{}), + gomock.AssignableToTypeOf("random_fork_id"), + gomock.AssignableToTypeOf(common.Hash{}), + ).Return(phs, nil) got, err := newNode(initConfig, ks, m, mockServiceRegistry) assert.NoError(t, err) diff --git a/dot/services.go b/dot/services.go index 3450fbd9a8..6dafa9ee2f 100644 --- a/dot/services.go +++ b/dot/services.go @@ -457,9 +457,9 @@ func (nodeBuilder) createGRANDPAService(config *cfg.Config, st *state.Service, k return grandpa.NewService(gsCfg) } -func (nodeBuilder) createParachainHostService(net *network.Service, genesisHash common.Hash) ( +func (nodeBuilder) createParachainHostService(net *network.Service, forkID string, genesisHash common.Hash) ( *parachain.Service, error) { - return parachain.NewService(net, genesisHash) + return parachain.NewService(net, forkID, genesisHash) } func (nodeBuilder) createBlockVerifier(st *state.Service) *babe.VerificationManager { diff --git a/dot/utils_test.go b/dot/utils_test.go index 43e344b4cd..44a84924c0 100644 --- a/dot/utils_test.go +++ b/dot/utils_test.go @@ -33,7 +33,7 @@ func TestCreateJSONRawFile(t *testing.T) { bs: &BuildSpec{genesis: NewTestGenesis(t)}, fp: filepath.Join(t.TempDir(), "/test.json"), }, - expectedHash: "f7f1b82c0ba16b20e36bfb462d7899af2c76728918f639f5c5ef0e91ff3e7077", + expectedHash: "ddedbc626806eb0f2a2eec5ade59a7141fedbff55e27021e3265a5f9000dec2e", }, } for _, tt := range tests { diff --git a/lib/babe/verify_test.go b/lib/babe/verify_test.go index 7e246d01ae..7d7fa28c70 100644 --- a/lib/babe/verify_test.go +++ b/lib/babe/verify_test.go @@ -761,7 +761,9 @@ func Test_verifyBlockEquivocation(t *testing.T) { mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().GenesisHash().Return(common.Hash([32]byte{})) - expectedAuthorityId := types.AuthorityID(kp.Public().Encode()) + var expectedAuthorityId types.AuthorityID + copy(expectedAuthorityId[:], kp.Public().Encode()) + mockSlotState := NewMockSlotState(ctrl) mockSlotState. EXPECT(). @@ -790,7 +792,9 @@ func Test_verifyBlockEquivocation(t *testing.T) { mockBlockState := NewMockBlockState(ctrl) mockBlockState.EXPECT().GenesisHash().Return(common.Hash([32]byte{})) - expectedAuthorityId := types.AuthorityID(kp.Public().Encode()) + var expectedAuthorityId types.AuthorityID + copy(expectedAuthorityId[:], kp.Public().Encode()) + mockSlotState := NewMockSlotState(ctrl) mockSlotState. EXPECT(). @@ -821,7 +825,8 @@ func Test_verifyBlockEquivocation(t *testing.T) { secondHeader.Number = 1 secondHeader.Hash() - expectedAuthorityId := types.AuthorityID(kp.Public().Encode()) + var expectedAuthorityId types.AuthorityID + copy(expectedAuthorityId[:], kp.Public().Encode()) mockedEquivocationProof := &types.BabeEquivocationProof{ Offender: expectedAuthorityId, @@ -878,7 +883,8 @@ func Test_verifyBlockEquivocation(t *testing.T) { secondHeader.Number = 1 secondHeader.Hash() - expectedAuthorityId := types.AuthorityID(kp.Public().Encode()) + var expectedAuthorityId types.AuthorityID + copy(expectedAuthorityId[:], kp.Public().Encode()) mockedEquivocationProof := &types.BabeEquivocationProof{ Offender: expectedAuthorityId, @@ -1047,7 +1053,8 @@ func Test_verifier_verifyAuthorshipRightEquivocatory(t *testing.T) { }, setupVerifier: func(t *testing.T, header *types.Header) *verifier { const slot = uint64(1) - offenderPublicKey := types.AuthorityID(kp.Public().Encode()) + var offenderPublicKey types.AuthorityID + copy(offenderPublicKey[:], kp.Public().Encode()) equivocationProof := &types.BabeEquivocationProof{ Offender: offenderPublicKey, @@ -1108,7 +1115,8 @@ func Test_verifier_verifyAuthorshipRightEquivocatory(t *testing.T) { }, setupVerifier: func(t *testing.T, header *types.Header) *verifier { const slot = uint64(1) - offenderPublicKey := types.AuthorityID(kp.Public().Encode()) + var offenderPublicKey types.AuthorityID + copy(offenderPublicKey[:], kp.Public().Encode()) equivocationProof := &types.BabeEquivocationProof{ Offender: offenderPublicKey, @@ -1178,7 +1186,8 @@ func Test_verifier_verifyAuthorshipRightEquivocatory(t *testing.T) { }, setupVerifier: func(t *testing.T, header *types.Header) *verifier { const slot = uint64(1) - offenderPublicKey := types.AuthorityID(kp.Public().Encode()) + var offenderPublicKey types.AuthorityID + copy(offenderPublicKey[:], kp.Public().Encode()) equivocationProof := &types.BabeEquivocationProof{ Offender: offenderPublicKey, diff --git a/lib/genesis/genesis.go b/lib/genesis/genesis.go index bf81485027..0bc8d2808f 100644 --- a/lib/genesis/genesis.go +++ b/lib/genesis/genesis.go @@ -17,6 +17,7 @@ type Genesis struct { ChainType string `json:"chainType"` Bootnodes []string `json:"bootNodes"` TelemetryEndpoints []interface{} `json:"telemetryEndpoints"` + ForkID string `json:"forkId"` ProtocolID string `json:"protocolId"` Genesis Fields `json:"genesis"` Properties map[string]interface{} `json:"properties"` @@ -33,6 +34,7 @@ type Data struct { ChainType string Bootnodes [][]byte TelemetryEndpoints []*TelemetryEndpoint + ForkID string ProtocolID string Properties map[string]interface{} ForkBlocks []string @@ -210,6 +212,7 @@ func (g *Genesis) GenesisData() *Data { ChainType: g.ChainType, Bootnodes: common.StringArrayToBytes(g.Bootnodes), TelemetryEndpoints: interfaceToTelemetryEndpoint(g.TelemetryEndpoints), + ForkID: g.ForkID, ProtocolID: g.ProtocolID, Properties: g.Properties, ForkBlocks: g.ForkBlocks, diff --git a/lib/parachain/service.go b/lib/parachain/service.go index 2609cf9439..9fb2596209 100644 --- a/lib/parachain/service.go +++ b/lib/parachain/service.go @@ -22,10 +22,7 @@ type Service struct { Network Network } -func NewService(net Network, genesisHash common.Hash) (*Service, error) { - // TODO: Use actual fork id from chain spec #3373 - forkID := "" - +func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, error) { validationProtocolID := GeneratePeersetProtocolName( ValidationProtocolName, forkID, genesisHash, ValidationProtocolVersion) From 9e12f3aae2a89c4388fb38cc4697c74a8453d448 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Fri, 4 Aug 2023 14:09:28 +0530 Subject: [PATCH 21/85] feat(lib/parachain): introduced parachain candidate validation (#3249) This commit adds all the functions required for parachain candidate validations. With this commit, we are able to take candidate receipts and - get validation data for it, - perform basic checks on it, - run respective parachain's validate_block on parachain's runtime, - get validate results from parachain's runtime and verify those validation results again relaychain runtime, - and declare candidate as valid or invalid in the end. This commit also includes tests for candidate validation. --- lib/parachain/available_data_fetching.go | 3 + lib/parachain/available_data_fetching_test.go | 3 + lib/parachain/candidate_validation.go | 139 ++++++++++++++++++ lib/parachain/candidate_validation_test.go | 134 +++++++++++++++++ lib/parachain/chunk_fetching.go | 3 + lib/parachain/chunk_fetching_test.go | 3 + lib/parachain/collation_fetching.go | 3 + lib/parachain/collation_fetching_test.go | 3 + lib/parachain/collation_protocol.go | 3 + lib/parachain/collation_protocol_test.go | 11 +- lib/parachain/mocks_generate_test.go | 6 + lib/parachain/mocks_test.go | 118 +++++++++++++++ lib/parachain/pov_fetching.go | 3 + lib/parachain/pov_fetching_test.go | 3 + lib/parachain/runtime/instance.go | 98 ++++++++++++ lib/parachain/statement.go | 3 + .../statement_distribution_message.go | 3 + .../statement_distribution_message_test.go | 11 +- lib/parachain/statement_fetching.go | 3 + lib/parachain/statement_fetching_test.go | 11 +- lib/parachain/statement_test.go | 11 +- .../testdata/test_parachain_adder.wasm | Bin 0 -> 128873 bytes lib/parachain/types/types.go | 36 ++++- 23 files changed, 595 insertions(+), 16 deletions(-) create mode 100644 lib/parachain/candidate_validation.go create mode 100644 lib/parachain/candidate_validation_test.go create mode 100644 lib/parachain/mocks_generate_test.go create mode 100644 lib/parachain/mocks_test.go create mode 100644 lib/parachain/runtime/instance.go create mode 100755 lib/parachain/testdata/test_parachain_adder.wasm diff --git a/lib/parachain/available_data_fetching.go b/lib/parachain/available_data_fetching.go index 56669e0674..83e9bf71df 100644 --- a/lib/parachain/available_data_fetching.go +++ b/lib/parachain/available_data_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/available_data_fetching_test.go b/lib/parachain/available_data_fetching_test.go index d6ba61671a..73bdfb5877 100644 --- a/lib/parachain/available_data_fetching_test.go +++ b/lib/parachain/available_data_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/candidate_validation.go b/lib/parachain/candidate_validation.go new file mode 100644 index 0000000000..e3aeae0213 --- /dev/null +++ b/lib/parachain/candidate_validation.go @@ -0,0 +1,139 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + parachainruntime "github.com/ChainSafe/gossamer/lib/parachain/runtime" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +var ( + ErrValidationCodeMismatch = errors.New("validation code hash does not match") + ErrValidationInputOverLimit = errors.New("validation input is over the limit") +) + +// PoVRequestor gets proof of validity by issuing network requests to validators of the current backing group. +// TODO: Implement PoV requestor +type PoVRequestor interface { + RequestPoV(povHash common.Hash) PoV +} + +func getValidationData(runtimeInstance parachainruntime.RuntimeInstance, paraID uint32, +) (*parachaintypes.PersistedValidationData, *parachaintypes.ValidationCode, error) { + + var mergedError error + + for _, assumptionValue := range []scale.VaryingDataTypeValue{ + parachaintypes.IncludedOccupiedCoreAssumption{}, + parachaintypes.TimedOutOccupiedCoreAssumption{}, + parachaintypes.Free{}, + } { + assumption := parachaintypes.NewOccupiedCoreAssumption() + err := assumption.Set(assumptionValue) + if err != nil { + return nil, nil, fmt.Errorf("getting assumption: %w", err) + } + persistedValidationData, err := runtimeInstance.ParachainHostPersistedValidationData(paraID, assumption) + if err != nil { + mergedError = errors.Join(mergedError, err) + continue + } + + validationCode, err := runtimeInstance.ParachainHostValidationCode(paraID, assumption) + if err != nil { + return nil, nil, fmt.Errorf("getting validation code: %w", err) + } + + return persistedValidationData, validationCode, nil + } + + return nil, nil, fmt.Errorf("getting persisted validation data: %w", mergedError) +} + +// ValidateFromChainState validates a candidate parachain block with provided parameters using relay-chain +// state and using the parachain runtime. +func ValidateFromChainState(runtimeInstance parachainruntime.RuntimeInstance, povRequestor PoVRequestor, + candidateReceipt parachaintypes.CandidateReceipt) ( + *parachaintypes.CandidateCommitments, *parachaintypes.PersistedValidationData, bool, error) { + + persistedValidationData, validationCode, err := getValidationData(runtimeInstance, candidateReceipt.Descriptor.ParaID) + if err != nil { + return nil, nil, false, fmt.Errorf("getting validation data: %w", err) + } + + // check that the candidate does not exceed any parameters in the persisted validation data + pov := povRequestor.RequestPoV(candidateReceipt.Descriptor.PovHash) + + // basic checks + + // check if encoded size of pov is less than max pov size + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err = encoder.Encode(pov) + if err != nil { + return nil, nil, false, fmt.Errorf("encoding pov: %w", err) + } + encodedPoVSize := buffer.Len() + if encodedPoVSize > int(persistedValidationData.MaxPovSize) { + return nil, nil, false, fmt.Errorf("%w, limit: %d, got: %d", ErrValidationInputOverLimit, + persistedValidationData.MaxPovSize, encodedPoVSize) + } + + validationCodeHash, err := common.Blake2bHash([]byte(*validationCode)) + if err != nil { + return nil, nil, false, fmt.Errorf("hashing validation code: %w", err) + } + + if validationCodeHash != common.Hash(candidateReceipt.Descriptor.ValidationCodeHash) { + return nil, nil, false, fmt.Errorf("%w, expected: %s, got %s", ErrValidationCodeMismatch, + candidateReceipt.Descriptor.ValidationCodeHash, validationCodeHash) + } + + // check candidate signature + err = candidateReceipt.Descriptor.CheckCollatorSignature() + if err != nil { + return nil, nil, false, fmt.Errorf("verifying collator signature: %w", err) + } + + validationParams := parachainruntime.ValidationParameters{ + ParentHeadData: persistedValidationData.ParentHead, + BlockData: pov.BlockData, + RelayParentNumber: persistedValidationData.RelayParentNumber, + RelayParentStorageRoot: persistedValidationData.RelayParentStorageRoot, + } + + parachainRuntimeInstance, err := parachainruntime.SetupVM(*validationCode) + if err != nil { + return nil, nil, false, fmt.Errorf("setting up VM: %w", err) + } + + validationResults, err := parachainRuntimeInstance.ValidateBlock(validationParams) + if err != nil { + return nil, nil, false, fmt.Errorf("executing validate_block: %w", err) + } + + candidateCommitments := parachaintypes.CandidateCommitments{ + UpwardMessages: validationResults.UpwardMessages, + HorizontalMessages: validationResults.HorizontalMessages, + NewValidationCode: validationResults.NewValidationCode, + HeadData: validationResults.HeadData, + ProcessedDownwardMessages: validationResults.ProcessedDownwardMessages, + HrmpWatermark: validationResults.HrmpWatermark, + } + + isValid, err := runtimeInstance.ParachainHostCheckValidationOutputs( + candidateReceipt.Descriptor.ParaID, candidateCommitments) + if err != nil { + return nil, nil, false, fmt.Errorf("executing validate_block: %w", err) + } + + return &candidateCommitments, persistedValidationData, isValid, nil +} diff --git a/lib/parachain/candidate_validation_test.go b/lib/parachain/candidate_validation_test.go new file mode 100644 index 0000000000..007fef7969 --- /dev/null +++ b/lib/parachain/candidate_validation_test.go @@ -0,0 +1,134 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "os" + "testing" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +func createTestCandidateReceiptAndValidationCode(t *testing.T) ( + parachaintypes.CandidateReceipt, parachaintypes.ValidationCode) { + // this wasm was achieved by building polkadot's adder test parachain + runtimeFilePath := "./testdata/test_parachain_adder.wasm" + validationCodeBytes, err := os.ReadFile(runtimeFilePath) + require.NoError(t, err) + + validationCode := parachaintypes.ValidationCode(validationCodeBytes) + + validationCodeHashV := common.MustBlake2bHash(validationCodeBytes) + + collatorKeypair, err := sr25519.GenerateKeypair() + require.NoError(t, err) + collatorID, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) + require.NoError(t, err) + + candidateReceipt := parachaintypes.CandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: uint32(1000), + RelayParent: common.MustHexToHash("0xded542bacb3ca6c033a57676f94ae7c8f36834511deb44e3164256fd3b1c0de0"), //nolint:lll + Collator: collatorID.AsBytes(), + PersistedValidationDataHash: common.MustHexToHash("0x690d8f252ef66ab0f969c3f518f90012b849aa5ac94e1752c5e5ae5a8996de37"), //nolint:lll + PovHash: common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274"), //nolint:lll + ErasureRoot: common.MustHexToHash("0xc07f658163e93c45a6f0288d229698f09c1252e41076f4caa71c8cbc12f118a1"), //nolint:lll + ParaHead: common.MustHexToHash("0x9a8a7107426ef873ab89fc8af390ec36bdb2f744a9ff71ad7f18a12d55a7f4f5"), //nolint:lll + ValidationCodeHash: parachaintypes.ValidationCodeHash(validationCodeHashV), + }, + + CommitmentsHash: common.MustHexToHash("0xa54a8dce5fd2a27e3715f99e4241f674a48f4529f77949a4474f5b283b823535"), + } + + payload, err := candidateReceipt.Descriptor.CreateSignaturePayload() + require.NoError(t, err) + + signatureBytes, err := collatorKeypair.Sign(payload) + require.NoError(t, err) + + signature := [sr25519.SignatureLength]byte{} + copy(signature[:], signatureBytes) + + candidateReceipt.Descriptor.Signature = parachaintypes.CollatorSignature(signature) + + return candidateReceipt, validationCode +} + +func TestValidateFromChainState(t *testing.T) { + + candidateReceipt, validationCode := createTestCandidateReceiptAndValidationCode(t) + + bd, err := scale.Marshal(BlockDataInAdderParachain{ + State: uint64(1), + Add: uint64(1), + }) + require.NoError(t, err) + + pov := PoV{ + BlockData: bd, + } + + // NOTE: adder parachain internally compares postState with bd.State in it's validate_block, + // so following is necessary. + encodedState, err := scale.Marshal(uint64(1)) + require.NoError(t, err) + postState, err := common.Keccak256(encodedState) + require.NoError(t, err) + + hd, err := scale.Marshal(HeadDataInAdderParachain{ + Number: uint64(1), + ParentHash: common.MustHexToHash("0x0102030405060708090001020304050607080900010203040506070809000102"), + PostState: postState, + }) + require.NoError(t, err) + + expectedPersistedValidationData := parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{Data: hd}, + RelayParentNumber: uint32(1), + RelayParentStorageRoot: common.MustHexToHash("0x50c969706800c0e9c3c4565dc2babb25e4a73d1db0dee1bcf7745535a32e7ca1"), + MaxPovSize: uint32(2048), + } + + ctrl := gomock.NewController(t) + + mockInstance := NewMockRuntimeInstance(ctrl) + mockInstance.EXPECT(). + ParachainHostPersistedValidationData( + uint32(1000), + gomock.AssignableToTypeOf(parachaintypes.OccupiedCoreAssumption{})). + Return(&expectedPersistedValidationData, nil) + mockInstance.EXPECT(). + ParachainHostValidationCode(uint32(1000), gomock.AssignableToTypeOf(parachaintypes.OccupiedCoreAssumption{})). + Return(&validationCode, nil) + mockInstance.EXPECT(). + ParachainHostCheckValidationOutputs(uint32(1000), gomock.AssignableToTypeOf(parachaintypes.CandidateCommitments{})). + Return(true, nil) + + mockPoVRequestor := NewMockPoVRequestor(ctrl) + mockPoVRequestor.EXPECT(). + RequestPoV(common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274")).Return(pov) + + candidateCommitments, persistedValidationData, isValid, err := ValidateFromChainState( + mockInstance, mockPoVRequestor, candidateReceipt) + require.NoError(t, err) + require.True(t, isValid) + require.NotNil(t, candidateCommitments) + require.Equal(t, expectedPersistedValidationData, *persistedValidationData) +} + +type HeadDataInAdderParachain struct { + Number uint64 + ParentHash [32]byte + PostState [32]byte +} + +type BlockDataInAdderParachain struct { + State uint64 + Add uint64 +} diff --git a/lib/parachain/chunk_fetching.go b/lib/parachain/chunk_fetching.go index 8200bd1160..c012dcbce1 100644 --- a/lib/parachain/chunk_fetching.go +++ b/lib/parachain/chunk_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/chunk_fetching_test.go b/lib/parachain/chunk_fetching_test.go index 9a1d56d762..7cc73df3d3 100644 --- a/lib/parachain/chunk_fetching_test.go +++ b/lib/parachain/chunk_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_fetching.go b/lib/parachain/collation_fetching.go index 91bee29da3..7a9e5c61c6 100644 --- a/lib/parachain/collation_fetching.go +++ b/lib/parachain/collation_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_fetching_test.go b/lib/parachain/collation_fetching_test.go index f4f0057231..649fb495d6 100644 --- a/lib/parachain/collation_fetching_test.go +++ b/lib/parachain/collation_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_protocol.go b/lib/parachain/collation_protocol.go index fb75b8bc33..53790eb7ac 100644 --- a/lib/parachain/collation_protocol.go +++ b/lib/parachain/collation_protocol.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/collation_protocol_test.go b/lib/parachain/collation_protocol_test.go index 9d269e9e61..8bca1fbac2 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/lib/parachain/collation_protocol_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -55,9 +58,11 @@ func TestCollationProtocol(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/mocks_generate_test.go b/lib/parachain/mocks_generate_test.go new file mode 100644 index 0000000000..8eb3c600ed --- /dev/null +++ b/lib/parachain/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . RuntimeInstance,PoVRequestor diff --git a/lib/parachain/mocks_test.go b/lib/parachain/mocks_test.go new file mode 100644 index 0000000000..6d9ce3b694 --- /dev/null +++ b/lib/parachain/mocks_test.go @@ -0,0 +1,118 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/lib/parachain (interfaces: RuntimeInstance,PoVRequestor) + +// Package parachain is a generated GoMock package. +package parachain + +import ( + reflect "reflect" + + common "github.com/ChainSafe/gossamer/lib/common" + types "github.com/ChainSafe/gossamer/lib/parachain/types" + gomock "github.com/golang/mock/gomock" +) + +// MockRuntimeInstance is a mock of RuntimeInstance interface. +type MockRuntimeInstance struct { + ctrl *gomock.Controller + recorder *MockRuntimeInstanceMockRecorder +} + +// MockRuntimeInstanceMockRecorder is the mock recorder for MockRuntimeInstance. +type MockRuntimeInstanceMockRecorder struct { + mock *MockRuntimeInstance +} + +// NewMockRuntimeInstance creates a new mock instance. +func NewMockRuntimeInstance(ctrl *gomock.Controller) *MockRuntimeInstance { + mock := &MockRuntimeInstance{ctrl: ctrl} + mock.recorder = &MockRuntimeInstanceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRuntimeInstance) EXPECT() *MockRuntimeInstanceMockRecorder { + return m.recorder +} + +// ParachainHostCheckValidationOutputs mocks base method. +func (m *MockRuntimeInstance) ParachainHostCheckValidationOutputs(arg0 uint32, arg1 types.CandidateCommitments) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostCheckValidationOutputs indicates an expected call of ParachainHostCheckValidationOutputs. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostCheckValidationOutputs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostCheckValidationOutputs", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostCheckValidationOutputs), arg0, arg1) +} + +// ParachainHostPersistedValidationData mocks base method. +func (m *MockRuntimeInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.PersistedValidationData, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) + ret0, _ := ret[0].(*types.PersistedValidationData) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostPersistedValidationData indicates an expected call of ParachainHostPersistedValidationData. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostPersistedValidationData(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostPersistedValidationData", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostPersistedValidationData), arg0, arg1) +} + +// ParachainHostValidationCode mocks base method. +func (m *MockRuntimeInstance) ParachainHostValidationCode(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) + ret0, _ := ret[0].(*types.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCode indicates an expected call of ParachainHostValidationCode. +func (mr *MockRuntimeInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostValidationCode), arg0, arg1) +} + +// MockPoVRequestor is a mock of PoVRequestor interface. +type MockPoVRequestor struct { + ctrl *gomock.Controller + recorder *MockPoVRequestorMockRecorder +} + +// MockPoVRequestorMockRecorder is the mock recorder for MockPoVRequestor. +type MockPoVRequestorMockRecorder struct { + mock *MockPoVRequestor +} + +// NewMockPoVRequestor creates a new mock instance. +func NewMockPoVRequestor(ctrl *gomock.Controller) *MockPoVRequestor { + mock := &MockPoVRequestor{ctrl: ctrl} + mock.recorder = &MockPoVRequestorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPoVRequestor) EXPECT() *MockPoVRequestorMockRecorder { + return m.recorder +} + +// RequestPoV mocks base method. +func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) PoV { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestPoV", arg0) + ret0, _ := ret[0].(PoV) + return ret0 +} + +// RequestPoV indicates an expected call of RequestPoV. +func (mr *MockPoVRequestorMockRecorder) RequestPoV(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestPoV", reflect.TypeOf((*MockPoVRequestor)(nil).RequestPoV), arg0) +} diff --git a/lib/parachain/pov_fetching.go b/lib/parachain/pov_fetching.go index 12bb64381c..e840d1348c 100644 --- a/lib/parachain/pov_fetching.go +++ b/lib/parachain/pov_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/pov_fetching_test.go b/lib/parachain/pov_fetching_test.go index ff376a37b1..75c8089ea4 100644 --- a/lib/parachain/pov_fetching_test.go +++ b/lib/parachain/pov_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/runtime/instance.go b/lib/parachain/runtime/instance.go new file mode 100644 index 0000000000..017431cd81 --- /dev/null +++ b/lib/parachain/runtime/instance.go @@ -0,0 +1,98 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachain + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ChainSafe/gossamer/lib/common" + parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wasmer" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +var ( + ErrCodeEmpty = errors.New("code is empty") + ErrWASMDecompress = errors.New("wasm decompression failed") + ErrInstanceIsStopped = errors.New("instance is stopped") +) + +// ValidationResult is result received from validate_block. It is similar to CandidateCommitments, but different order. +type ValidationResult struct { + // The head-data is the new head data that should be included in the relay chain state. + HeadData parachaintypes.HeadData `scale:"1"` + // NewValidationCode is an update to the validation code that should be scheduled in the relay chain. + NewValidationCode *parachaintypes.ValidationCode `scale:"2"` + // UpwardMessages are upward messages send by the Parachain. + UpwardMessages []parachaintypes.UpwardMessage `scale:"3"` + // HorizontalMessages are Outbound horizontal messages sent by the parachain. + HorizontalMessages []parachaintypes.OutboundHrmpMessage `scale:"4"` + + // The number of messages processed from the DMQ. It is expected that the Parachain processes them from first to last. + ProcessedDownwardMessages uint32 `scale:"5"` + // The mark which specifies the block number up to which all inbound HRMP messages are processed. + HrmpWatermark uint32 `scale:"6"` +} + +// ValidationParameters contains parameters for evaluating the parachain validity function. +type ValidationParameters struct { + // Previous head-data. + ParentHeadData parachaintypes.HeadData + // The collation body. + BlockData []byte //types.BlockData + // The current relay-chain block number. + RelayParentNumber uint32 + // The relay-chain block's storage root. + RelayParentStorageRoot common.Hash +} + +// Instance is a wrapper around the wasmer runtime instance. +type Instance struct { + *runtimewasmer.Instance +} + +func SetupVM(code []byte) (*Instance, error) { + cfg := runtimewasmer.Config{} + + instance, err := runtimewasmer.NewInstance(code, cfg) + if err != nil { + return nil, fmt.Errorf("creating instance: %w", err) + } + return &Instance{instance}, nil +} + +// ValidateBlock validates a block by calling parachain runtime's validate_block call and returns the result. +func (in *Instance) ValidateBlock(params ValidationParameters) ( + *ValidationResult, error) { + + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(params) + if err != nil { + return nil, fmt.Errorf("encoding validation parameters: %w", err) + } + + encodedValidationResult, err := in.Exec("validate_block", buffer.Bytes()) + if err != nil { + return nil, err + } + + validationResult := ValidationResult{} + err = scale.Unmarshal(encodedValidationResult, &validationResult) + if err != nil { + return nil, fmt.Errorf("scale decoding: %w", err) + } + return &validationResult, nil +} + +// RuntimeInstance for runtime methods +type RuntimeInstance interface { + ParachainHostPersistedValidationData(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.PersistedValidationData, error) + ParachainHostValidationCode(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, + ) (*parachaintypes.ValidationCode, error) + ParachainHostCheckValidationOutputs(parachainID uint32, outputs parachaintypes.CandidateCommitments) (bool, error) +} diff --git a/lib/parachain/statement.go b/lib/parachain/statement.go index 35b705891b..4b9551675c 100644 --- a/lib/parachain/statement.go +++ b/lib/parachain/statement.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_distribution_message.go b/lib/parachain/statement_distribution_message.go index c4c2f8dbe8..f75bbf7fd7 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/lib/parachain/statement_distribution_message.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_distribution_message_test.go b/lib/parachain/statement_distribution_message_test.go index 0ff8bb303a..be069b7065 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/lib/parachain/statement_distribution_message_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -58,9 +61,11 @@ func TestStatementDistributionMessage(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/statement_fetching.go b/lib/parachain/statement_fetching.go index 9080916c36..2aac3bf689 100644 --- a/lib/parachain/statement_fetching.go +++ b/lib/parachain/statement_fetching.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( diff --git a/lib/parachain/statement_fetching_test.go b/lib/parachain/statement_fetching_test.go index d606b25d44..eadcd59b19 100644 --- a/lib/parachain/statement_fetching_test.go +++ b/lib/parachain/statement_fetching_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -93,9 +96,11 @@ func TestStatementFetchingResponse(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(testHash), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/statement_test.go b/lib/parachain/statement_test.go index ee7c8e0ad7..73c97b10c7 100644 --- a/lib/parachain/statement_test.go +++ b/lib/parachain/statement_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachain import ( @@ -43,9 +46,11 @@ func TestStatement(t *testing.T) { ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), }, Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: []byte{1, 2, 3}, + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, ProcessedDownwardMessages: uint32(5), HrmpWatermark: uint32(0), }, diff --git a/lib/parachain/testdata/test_parachain_adder.wasm b/lib/parachain/testdata/test_parachain_adder.wasm new file mode 100755 index 0000000000000000000000000000000000000000..7dfaccd460a70be373632b97826d6b3b2a757354 GIT binary patch literal 128873 zcmd?S37j28wZ~uGefK-d&18~H_P#eeWSML;2?*p4vWsjgi)a#(Kr$gDnG7LFB*QYA zunB^QiV_tiZjZ$cpC|#*2Z92Mf{KWWg3k?bLm$5UzrU*PdwY^hM4s>e`G4O3LQhwn zsycPfsdG-9s;=&sr5o0G#u#sT^O9i8mMz|vCEgbA!X>^D)mSkK4EUqbu;6jT^2ru-@Ua;bvjU!iV>Ro*H+Oy7Dbz+L^omsPD z&Dx&xeKT_L;#KD?U)8f>S?}UA&pl^ZuSo1&ddAr+Jkzvs>DjB6FYR5i_>8mHE?aFD z6&5chyL9o2bCw5Qaq;4nE0(TXe8$oZD*``1(KA)AIL?EeX{DB-tqpA>8=FRt9XCEK z&)cu#HHRF1#KFfLRQSSk-Wz>W{Il=*TPF1JGu6`Sia*gNLo?MkW#cdM7lmGRmpP`9 zw1BxvV9SnZG@%KD(j{tW3(7%JkgC_+;pYTNg+X`d9bim@R}B5C>F^sOe0`;(EItV( zMKbj1<0rL|LMZ!%W{h9aUHXm+M@b~16^Ud} z>UQF-n&QtrFNozH*5n=u$m_0nhp8Jzp@K+zE2+>6xsDlK@v1o}&e_zK4b|}*%PHF` zPy@f}b@*-Y6mRd zshH|TG}v@_qZbP!DQgLcT9$UcLmvN_$`~BW78L9 zFz3T2mrW(vv~octViU=VO^6u4W*}XI%>V(QgmpFpvFX7k3CE`I*aVGbGZ=(TxZQ)z zpw8wd&)ah-Emvg?Nv<0b7_(CMG~aXv5hy26t_BJ{TL{x3)N6E7-SQcIu6pSjNw@4J zq#Jt0Ot1r zl1iKsCziyCm2qNSoY)j4)P#P5>Q3%Kn5}N=u4EgLWl()B0F8LY`T?}H9H>jzpKX&< zlMI|xGBiut{KEvJiNI#NT5rGAmxq@5;J z5SSOH%bA9t?tIc!FSgXU3k|#*BkRsknT%A(+F1kkRNB)#0qxy9OE4k-Ub9SGsI^S7 z^t6V*I+qGlyQ|k-q|9CVTYA6K0Sek<&lq{VQNfl5iN+KgOxKS8y6*E2Z~f};PAGmY zs80?%O-)_Ik)((SE2-*pwuc5~Qx*O+456DogJZL5;#`3S$Ib@g^c^a96N8vQlZBB2 z?BX@tFtT~vUJ~LCznvtvskE1nI{XEifk;oS@oXnu>wRogkOC>R4bdE7OxHbs`Rj8J zU3bbm?2!kP0ujKN)YU=Ze9&ZT9qv4 zoj3|~QRK=x*->ocnkK?@A4i&(tQu4v7Q%+^3W22*x@zZ&BakW5kh+)iMJy+)-t#U3 z$!c^Wk<_tMHAbBk>O5%k-Ie_AX!8-;tk5DaJZ*B+DRrX}K&r9$ z@n#ag33Et3MDhga*5xXaup&@IV8>Pi&y-QaCI7LzV3~@I4UeG*xdb)GMbI(Q;03JF z>yJ{Y(#NC(#UbS8BQB3geTRj##&wg`UN>OM^&^AvtE?3iy5fv%=<2L->&4a?$$dTK zOwq=L-K8&Cy(_+T0J6F4{l z_HxSu35;@&LGBd;nEMCZKJDc7k2wmV{`h~6w>GQWkpdH%G(@&mlu}Fct>Ku+tAEt1 zD~hJ z#^}oVSvrsD4d~4mgT^r^I1I?Jq`(^okAK?*XgCmVxHQA=Bk4SH-&b`OsA&$linJXITE4R9Ar$pOv?r@DC z!@}h3Mn-cHl4cJlY-H9>L2JQI6%j5i&NO8q`b_a9-%oAvFPwn=(x+@7^2St1se>7Y zrJ-(Vw`E~zl@?a;Z*7%KHMQOnZIw=prkwt%yPxr1gJ5?ga?=fN=^00 zC`M$BBTEayTxUT17G??ExWyqRL5pmVSvPhd=ok}mTlKMsLNa6O+>}#V(!pc0N!hNn zp%*_ArVcQoerFtO7T@95eNSQ!&zG;L?YU{Eg;8M7?SEzWP2n%d<6XR0wEKqgC)@00 z_s#BFX%i7!S0I9QFnJJOd_YQ8_Q=M2YD6aM>tYCNizd223<`t($$c2yK=rZyoqg&v z7cF^gkMZ^vJu2d{Qvnf!C8>Dj-p9f^TVeYD*^vCNZH@h-k=dU0vh0mX<8g)6{i}U_ z_u0XcqhVLY8;weRl#G1?QW{MBNOcCWy>07!5)Rm>l7+CH=yt4w(nqZh=50wB z&9BV6(*_ zVIu?M7B7`zm}AakZIfHym|_~ut*+!XUU=h**OUr8-wWi{|3P=Dy>-X9<-&Xva;q)>yYZQ?whzwd9=sUj(qMbjR5_-q?OCt**Nm5U7RnZdjT!OO1P!5g z49sFVx-nI*X0OXWC1TCGz80w>8+mqxEY{wLog9SNjs=giItIjAE>$+Kw}GvNpvzv( z5Uu^~!xE_}+w;R*b#r$GrAE`WQ8ExoS=BHILSZRG)>eZwmgJD7eaukt*yi;-^oWee zZhm@VAa(_8WLO}P9I9UT3yQB&RevKvdxnQe?mlnglAC>O@eL0b5<4N zT{MnaPs);-BvFnm^hBEUUCQfQYH{gKJ+m4RPpT@L4 z%mu+{;?tx@von>~X-}*`^@d(oIoPdopvsiD^dhC_&H5gwQ5Kix%h8syC5j$$)pfc>d83@x>C&Y|M`{#V@Ysc8m>P}Qsg#6$xJ}?ih>_fQSc)iDHr_MjcL28vdyF5hpJKVLn;b>NJYU9H5L3Iv^;W_ zl7`Yo7a3{$6m2A!kgw0nsOT+YLli4}lB+8$8zlxxH0?H}d^Q5@*kq;g(#f1cjFfIR z*kC_7!W#!0c@4-I7nkDjt+k}oCwAqj!;t-qy#yP=>GTvAZwHoovB}yUm{cIb&|S%w z&A+1<>!|a+prvR_x2ps}Rsr^+Dtj-H3{%Bxy!zUV=bzEe0Lx_7Lrjy}B-5IfB^uig zb{8zrgV!j<9wXq|lboQs{T(~C0~+n*vM!GcC)$;_cKdWZfFQ!|jFHlFAK|ZW?f#2N z+W9Xa=~sT>b8e}2Fr+T2>Xhi1hv~Zd+vYR|4WEn-366FK)jZpLZ2L1AgtkgwCEfM> z?ZJhi*?dbS%d_NQef1f04vxs{tzLF1;_IGe%z;KR^@ZK&%Z*#k#$&jOyH_D_k-D3% zUH3db>rIML%Cw90K79+_{@f@$cVvLCl4AdNK);!Nb%6QEfT{&~mjOsvs|_Sqb`#Z* z9k3V8?k5{mZY#m=@K<9T-PI{L=h8>)I!)>oKYm3a$DzfVjULgkRoSH-;Q4CB-`jif z&phK?oOQy^%q@Tb>i^0)iT}?W-uaZqPg`dHD~ES(VC5J;j;(K=*};sLtW1%pr_@D% za(4SR^q3pH(x)wLf+`%85)m0zc@BwzYK~A$I-sqa(Bx~AZSj%J$TZMH0*i&sCl+W% zb8-}4am|u%+ESp{Q@D6+f>d zYEHK;O$2v}HLPef<42P^(edk;Q`{*ZKRTPi1f>vZv)xDz)?+5*;HLD5=pCi-2sYXI z1!Pak*@%S>R%;@#yPt505cQ;lb-o_3L8)@~aF6!UQqA>qAAbGZN1yMd5(64dH4>4i zKrYo3>!0WWxECzaOolV1g4PV7x4IfAh}6zxDiWVo0on+$Fgr#-AQLCoVKmZq1*8=j zX}939&jsUM;0AmT+m?PQ9xdCGuvXHm-HAdrs@c|b>SVhk;95$+}G^AM-GF=QoU#Zh+v(6W`DK3{pKr%*YhkrL+Gd1dM48Wf{ z^{kg_)T==21@6SaOiOj@Z8a-DDd1DCmJQ;#+qnVHc1(pXeq1dpN){+py4J6 zN-DEo*Q1Ycv^ch;26J{VN*#~6ujzlr;+_12+6-oHQBDmmOhxIE(BEL$5JV>EB{+LFdPFU@q zs;wI@@>QAAeWFqkg)6iN&SFm%M5XMw6{i}*GE?20j`pGeVG*r${)q!13qqnHJqjX} z)}tWnt)_D$>YU0A9hk@s`a*6E(~Rjf&TH@#l85AR9`Q9G!&;ulMhzIJ%O}b7tMN04 z;%9s>J-|sj&uq^HG8T5R0LfJy^ztH~Yv&oGq0@i%aUmgGz*z;~GkTCC>55AhVG+M+ z8%e_oTXi#xskZfo=~;%A73&TKXjTq=!t)wdBo)(H_4k6;mI`k?X(jP$8I-`wq~$iJ zBn2ct7`F_AEsSHEipbJAHOGOTWLx(J8p|kNJ(@jtBP1o|Nbu?CaZEx?k&)3D;1<3t zlQhLi8WEil^Vt3SOjr1h(EH4#Vale0&s1{1xp`$}bBTH`O$f57Z4+~l+7 zw%Z|9o=CrQXCK-#+MSujOdQ`5wxf>byQ|CFjF4HZ{(hV11(jNS z-sjaH>#9-N#Dk$>f~*QrkPZ<9)lf${%xnWuXq7#UMljBJuo=TZqbr8ckLVg5<7%%y zu2#+3b1ED)ni$!I@H|fu95vKtly=}#)n@EaQ$`>9A5GRkvzYU4=CvrLYQT z@#@pfGJ|y8`M=t;{5jM_>Vvk?Tc1~b=@O_decTPgUO<@S3k^YLOK@TB{h-@Yb@9V; zhre08cKDm<;SnAaaCF#omu?rz*hHrKFZu3W7AEkQedKk&k@qIIH9)l7FAXq1?tOtG zgQ@->ed0Rq&vW(%lp#tv%}jOgP0$M=BGPWxtXSd11t`=v0H6yjG+&1<5Ok-p;DQ4K zic&O*s)8~?#gr?>*OY5f%Os*H*ya`aL{qf#BF(i_iZ+YT3(i3ymg=VURZi5f*-8Oh zcs4=Z<4JKl61sIdudTZ)>PorNfLt!lsmdu%yg0Ilyl52##5>TtUvc_YM&mvu7=e)I zL8WhL6v)@crTRoIhK-@>F)V$1Kuur}=rc^+r3c(dhW@nR5&w7_#tA=Sroz=h_p~4) zT|lJiMuI^|!jWw}LIdE8tAkG+Sf3Gaa7H1Chmmk_0-_|G5pi$=rwA^BpYS^a>E#WU zOvzs^Z%OD!s~;;*34rmcFS|~c;w_Oj7>%wn1ZbM&miUX5J*9%>uU1G&xjhSM5MgcE zc1qc<+Lma-r1t5R27H0m&C`NSLg{uOtq_U(AlJeyYm%X3!NRN<1as^HlTLB6H=<>q zWSm}Re*oG=MxBBiJlCB?eVezJ{;{f;~Rz>fN&<%}kS+fwmlkW*kqIK>HtPjA!Y#j!2xmF}{+hx9jod!_orcc?~7RO2@` zcblg9T8$U%q(3^Uam7~Ee_nrUHTo>xQ3Gn+nC#`bBJWAzz<6vIL~{sYPBHm%5|vP% zRf-;cNc04w)UqKvySx=-J=Nb4=y`^=3TeS&f-KIEW*4q^>oz=Qk5Z^?hrgZunP^8z zeNjy-5h5WiE7KN4vVo55`3nNgst)z43HW6J=OEW#uvJ~h!c19iP zC^}0}+aF=aRR4ycdLj6NjpxDn4R$$Ldz;E~IEE#k&(?){5A6eSP7hZ(o6JI+{Af0A zWanDOY3XUXB1?xB|Bb?*5%L?|J7`+vwmGXPxi?9y1Yk#qcpWo})1xx@7u|aJWQ%DT z4V!Ow3->l?QVTm?NY#!`AWlCWND4No;YT?HWo&&Oc-MxT`EdtzqG7LLvd^?B`?Shv zcC%~Np#+tp<`$zvZZseuxAz@^eQdUAJ!9f@(UO&o^azGxJt8SRa_VVEIoO9DQG2g? zq&>d>xgKdR(yCl2AEQ(x!AOr-D8EuYg3tkaWDzCwD0uaH1nRx(kpyRn#vjUJ*v*z6 zp)gL(5~CKWi_?f`{G0|9<8>Uhtj%JGh~D#7ZFcprr2^{%cS zjAKHViO6Io>Y8I!l=*f~84bEI;BgxTy_Jl0RX!2MNHyL_*dQ$94}`mK!t>Hoc1j8~+;&Q`ubuD^LO$A_(xACq31>ji zz%Zqabe3jJNqX0p0!w+CQnH$o(lK6!F=MAB#as$8oWRx17*q%=&|!WElTT(tm4okkOir3s~_9{A7F68JTL3bQgpeh(@Evk@cTvQ?K zaa4hxY=?9n5m=>nUWS@3u%Lr@LLk|og@Hzq*EW%{Ufz@=*e$TI8==(Jgn$4wig92; z^F1a6JBrj)K|Qc=qc~V#p+|}8SIg~J7FZyLy$2Q$I$%Puhv*U$g0Bf-fF4+`cGrVM zU|~mHiU8_pLa-80U;!e1&H}+%@--od|2VL)#de^hv2&hnZzES5F&k>Bjr$Mhg#x$` z!zSG`0_!{WknlT*@SnQ*><2 z+{UE|^}$7%fgzQ=Rw7h|_5m7YaVIn?_3AYW)O*(`$x<3c@Ed6qJcy%6;c5n7QyWY^ zaui;y0oMBpdS}4cAm9xq^)6x$C3ny zNmKK(Za8mW#|)rDLu~6)cS{GILI-o{c>Cv=(H;Iyg4LQ@^Q6X}8ys(6&EgY3QPji1 zU+7fSlX6iHVS$T!P$S<(J?vz;sK@$yO?kDbClyCM1S~{7G%0)d05QcTq6_&EK zsK-ual)!)tMJ|q8v}L1Lf)P6#8g>$H;K_1IVGnwx2^W+cxsE2>jKrB$*hB255wlIB zu!or-3VWRMhibwerT`dL*yFiV3Xl;Uod>hrI$;k7Pqd7)L`5W8A;d%}6sw0lu2aVZ zq_Dr?$XD~o3U&tC7XYvmR^v(ZpBdskhqie61V5*bUPYekAK&5oPkJj%ibVQbyDm7c zv4uoY+uEDfE3mW!i!r9Dqg5_P5D|N=%?5S@Koz%RP&h}HdX7L21aQuo23)JxudMpLZa#>@ zIoo$u#v-kg&0{Ahjk9qYquMe(b;?Ep(~72+qbgY*6@?5p3vvmFH&JWWNM9TY*P?*E z0Q)LE?`}s1iVv*h#G@3GD7%t%>(k`wN{^fw!YJUmK`RPm$(n&bs2r||iC2H-ka_CTKFtV&$+{?)9>37kS zbw(Dr4=UJ5+pb!o5L-7J@w8$~ngLNlFq3SMzt}B+LSj4@h<8w=WL|@7r~nG&HFQJ8 zs|}!XqsuuwX^x`=S%gR!M$KdZrO*jlBuakm_|u-GHVZGk%ObeDZY;eH4y$-N%I%|7n6QG zmJ+7dP{4BJ0fbBs-pJf+GsiUQ*Z^+1dTSrHLjb&Skzq8q91gt5* z#UGYzXCB(hww*<*jfAG9dz)f26j5tP1f;oH~$I*D9N^$I=zRS06Lmm*vCW=V$h+vfoyn4_r< za?*q|cV$mvtujWkEbIES%*=YH5~rQ#h6|5(vH9qXmZmW`I0u^fYHufIJH}q=UOhaq z=0ZUn`M9^_&?)9xIpt*JgNv#~K8V3NWh~har@}Er;ARdxlc6%2K=M}6E!|cj7)^;s zuSmF=jJF-^T(;Uqn#|Mja>^!mz#fv8CfTJmp_&L3aJwxy6p!g`5_wa*PnP05h)yetox~E2F(AsdyTH-3r&@Bu zjzP-?W6&%Wfp*T_BcIh`k4E?t&eAIouqr1lNT?dT>0y_w5rwdQP+MBZw=P79>>OEB zLQCs=0D;mrLceSCa4cj!5MWVYoiPnCbkSc^cUY6FP~1+Da4G4ILx_aC>?VT{$InUP zwzax`E%0*$M*3x?teI`k4MpvOLLZZUsy^x4_g)k$r{YOpt6i%~S^&A}*{T_hZM>X= zHHAL*LvvO&keb$nzT!Oh_`-gH^H2N8|EJStVyFC zM8ecsn1WcC$z_V&Qntd34pYlm?SmHyQ_3NZxD#gu|LsEq@OFX$#*D+*Y&{s{ZFJ ze8(R3@3L?nRPR>*J`2BdkNOW;_|84*KW5?g?@|9r3x8yf`cJ5)9Vdu;hDw$VQ+--B zNBlmQtpCS){pXYQU#{1GDOvwgo>4^j`*27#{8s>v@NWfPqvyJM{i~DpZ>-n9 znIvDRRI7pVxA5(1C3vl?&sq46J?h_O;kWHk|2_-9bC3EDS@_O9>OW@T_wP~vNekz} z|8Dd=W8qKiQQsXuczTcef3)>^vcDU@FI)KYd(^)aD>T)Y_Nae_h4-lkx(ma$Qc^>hlxa&Z)6U(|)^oJPZWL>tA>SWyhq7)$A-Vh8#L%$;EqS0=AB?iEhH78hhfQ?J1yYI<^;~~F>l0zD6jax? zOYwnT=3~t|X<@3SdZN^7UaQX?*O=DEGX*^K&}+WzyEB)tqFvv^d1E;YJDag;sI4=w zAl@dbO&yxNIM;cP4b%{h)$pkQWA}nD!ieZ>C}g`vu2>9X;dDZKsG!+Sh-lg1WSyO9 zBQotuNUvb96O*lzNnJMGYIHFSzJ@|0I4Do?I>_<_Nd!xdH5(Dxi8&f#3b$KaB0}d+ z7~(>0HqR#m)qE7F60xHwiXQR`R0-dug7)K)k*dO^4OAKN=)sCY9y<_vq{VuJ)~|Lf zEZ;1#c1N+BoCt5@kRm?9Y&XEInpQWthk}aIN4p?}$x24!A2m99g^SgA7#*9ETM7f6 z>;7o2etx(9GE$YUWL&2Dt&4W{!I`x*7I@K@#jp*=Oy9f0N$7i5*rNu8E+jgJ7tvOq zv`2?-l)|aR9?BoIPzTHgEe!TmsMG3$w$z&kgBEJ7Flb?RUxm?2etYLLzps{seH9k> zRj9M!gHqeLufnE%6*ljyP#-KDw9g^?Ds0(T;m~~*w(hHN*uDyf@2gNBj2n~{eLi#0 z!jbzb9JQ~)(fcYKv#-Lj`zjo_ufp+z6v|<_Z~WnVO$C=`sz12ISvMIbikfxXsk8Rw zwxD=-KBa#LB()_Ue?RbPefMV~mW^q%v8Q($Qt{EaaugZfmk+P;{&|(Fp-^c zZc|x)hiM&mWucGj)zWz?^idR6m_MTvylPK*e1KP_OtV76rlinldy|cAO}Df1VZJojisd z#!xyPz2{J|Z$~&Nnyu#`XmX{&shjGe-`Z~yw6PAwl2VLdrKt{@in<8YTnC{+S%FG* z&;qtX1sY;Op(ng8oxxU3=sQh!XyHaL9oR;E%B(xI7>!;9vE3O|(!JHen~A(MDNOpb zRA}ilA?R`dc&8yucX^vBRzO#DG&~)2J3qrIXtwL(e?9=g;B~I{c}{?yMq>KhWw^7_ zLsCq>6qBeOe@@~)Rj4-Z7R@LQ+F1Vp3h&aUBiBZJV|SBXbfb-Ak*%#}Z79Qr1tD>5 z#>XH`MTM|L@nhV21kKYmY0VKjmyFng3{rH9pr@*N;>$Q%x>r`-2R^qn%2Y;SXC zU)~}=u2HDjF4fXRubT3ZYPC{nAFHw(Y#l-Z)ILA`qNuOMX6@oO1>Yio0?8j>p%Br8f zA|Y)JR!vU4^r|3N@4kY^9(s$U!#la|^H(T=D)B1(0O@$UNr(IZ>5v~F9r6RDqX!ZY zn~;tidR;no{7yPNm{5H_c@=*z51JTADG93rR-chl-4fZ+Gg7hY)an(M`Rg52qN?7Q z6vtll(N6F8SqfPpIDqd+j&+?xcM|#-9M$L^Ok0zuF~YtpDQtfc9mx&gHu*A~9xZ4h0|lp} z(RMvUprhJn5h7opJrrcbaMyKTPq}$c1Oia)=1+J)KTsL=Qi+bU)yD=5@?M&u_5&1Q zFNh@9h%X6D9*X^iJ*lD&^00@%?GisWPzmXwfkLVc0w-v#?Wvu|AUN?b-_#^D&;~NQ zQOiUykjGEvf{_<`TS^y(W?Hb*!=Sj|;PHf;`#QPdX~B_F$7w-GD9r6nPoq68xSFFX zy04T2RiMlU(X^ngT+r<{LKoe)dgTV)my{cITTpJ&tyFH-ZHHGb>E7p+hvPh?fCKt-KLiLH<6gm%Ok5hFD#G3OEiVe{b6H&*w7yq`zxcn zylr7yII=$+(H{=)4~O+v#`IUlhC}kK+EcI9NLebpcb?~;zgsv_9mxN=1 zcKO>X6Z?^8B`k+|i@zC4x~6Tblu@-Y{asVGRVJx=KT;SC9bF5yRVIhy;h?K|8=;%S z)Y9Ko*jAa+j~u6j&0$-A*Tij=skU$oq;`$oR+-k1Y?|S*t7RKg!T7MLzbmt?G7LIe z`{}u|pX)}JMZdXi>|2e7{jQPQDmja3SU;jqsjzihWekOd{;o0GDg|3O9)Wfpw5`(A zj~*1lCJP)3?_D#tRmS!+?#)QKE7(?PWfaD`zDR_vwkA>?O0!mkY?*BXeYU@A7;R}d z60wJ2SJO6z%oZ0Ywm9IUvA=8oZI$u;sLNOcFM*8fnr=I7rPx&)Rkd%3>z%@WI4F-~ z2XUCKyC36%x=acyHanreQbCud;#H>DzA97DJN;`3zRQk7NhgM7TX|A{WfCg9ACa88 z_4_OPq36@W$u^79glK;`oDfcQZ6{z9V;zgqm$BH&WV)K>O2=SNqX?}kqx7`hgq2S8 zn)`6QJ!-=7dSBOsZThmi2}jzvQeo&0)tSLyB;s^q72pJ^Q_Z+3(uyU4HzV`faqSa0UlTEMPfsP^(1-p7vfsXBQ6N?tH zl|9JhRSVi^uW--WkjXJLPBQ7y05OUpGQGs0sm*;x2xcQDqbOz|lcxSrtnd{|&q~gd z*ao7C1 z5xnRnN-*$ryxT=2}zu9lcARYTJa>)B~gp=8_Sp0WBtHE)Gou;_G$uq{JqRZzV zMCr3vozb(j=loeq&*tANowcE7*{q(W=kX7(E}Pl2VcF7kOP8(cJwIH_Kg4?G*=x_E z%{)^wL%6qbAH%%?WNGe8!!y_RtXbOI%fH%jM3a8H3&)wdowQ1sljl23? z!Zlc(KI(`bA&U9B8JNaN^a_v0u9-_*Oa39h>4oWG;VrS$maXksVFwVQ%{u4YH3*J+ zKLpQc?kl)jx!O0ix3|x3pVL0KeO~+g_Kx<>_66+=XSdIuJ$ugVxwGfZo;-cc%v~^V!Tbds3py7pSg>#* zBrc@;g`i$YvxR`1Y+{|3%!fui_AEW;tQFyg-laV#;W^7!Yzo7*=k|tc&kS7&ni_66 zd)2ZP0-e3$oU?jYhQIOMxcxWx3V2*i{?*&aoqR z9{=j{;-%|WF?5^hZys$mcDnB*uV2r(=k%^xv*JW|3*Em-9In{3a_PAndRHu;8CK6& zixQv1zoG2Nq6bVc8XDI6n(ubHuTP@k-K5m(&fun^wg=2{@r5?-Ra$! zRLPt`A7i=jb0#Lo{9=LdswJp2Y84g=p(F8QaE@MJ5FUA4jb4D#YF?spvN0sYQgwzTK0wX=Fw zoV990FAi3&)hgr6Wj&zTFmu(~869WN?p!`+&KYMcojo6`JaZNUcm9mkE0!%=x_ZW( zne8*@*k&<7%hM(ER{B}Mz)2h8{ITSxli$;UE#J^P69u0-Equ*EAr!bw-0no$$-l(; zkCG1)`A?CTJmd16Na_meM^*N>$AUEJsHm$`eN(SOGDUGDO#XHyqN@VtZfT}CDS zwz``E-lt?Cs!w~Hzg$3I+xpx+i0*!LcQ?~lh!RozzPP`>s9sD*&f#TSgBaKpb0bXj z`_7eKapv+BXPkQ$|JwJNYcE7lDO;5CdaspSGW^x=`|o!5+d%1GmF3R`R1D<2b!C4m z_it``_6%blUb9vGJgrY!AofMVYKadDtf1; zJ;Ul4Y)61HHF23S$6Q6{@5y)$e|2I6sqz-@^23aIgNploZ(!xreDrD@!t%!yAnc1Ev}B|+ zNMNJT{nq$5-AbdcY^3O$nNL%&A7zIM!-M(kmP9@~x0uglh06~|Z zH`(vdW(LsLsLBIW*~A#gWpXpA37pu!f&_#)z|?GG_9rws4FFu4)RgR)+@;j50bWYA zm@JYsi@DS>Ri@>TLk*C5IY3XcUjy_uu1|7hznN;!K9v9j@AUV}JsJU;xCeJ+n85j$ z=`kYMJ<>HAon4#&q)yD|Gk=A;xJIFKMD{mPKBPtJx@MluSH(i6QQ zxvL{ws*qmejn4gr#B6#K8vrJA%MGRUFkq^bdQY~Ey;fu7L##Ni&mNllLK0xb@pkIU zU088kpFKXeE&;IO$cf}Pa*5Z}^;u2G{L)9!WD^NH^tl6#xj;kz?ZUO_%LgbE?f%1g zlfIYC@nm9yxs)0HM%DdB=1WN6A&Svo7I+!1rFDrhXE8Ybeb!j#!%OM} ze9uo1aPf(h=RXd@)Jh_#6V3xN90!!lnACJC+oF5%1IY}uV9!2h>Yc6ochF7hLsM|w zbkguS+uy;IrcQ!}N45dui+KYw@aBJ6?avmM^HHGjJ8%$Mk#BcNKjuW-71U)vh&)ckXilY3_C-YPZvlPkdkp<(I?K)}dZ`Z| zYs@lLo?fJuzf7am&7V{T*`0eXi4Z}3X^}CX>Lv3h@PB!MG1sZ&a`4}JpfMMz zFQAL`77DU?)pZXFLWJ~l2~ktkX6$a2aLw?t_d@IYDZGlp^h~cU zJA{k?{BP2id0`F}tO0W0^2VF&F6916pe?F$l{Yl|Oaf5+S}K?OP7*LGfU0Z*BX<{# z7;Te&7mZ$^P7J6ussUW1E*jk%H@e0fm7AFWG~CWAu<>CSKzU92I&c5nDvER1mb^89 zp*amj8-S7k@A2{`Hx^mNfaqRGlUqj{^)$~Qg5(n%iyN7)?g!QJjTiLJPL0R>asV+%2EJJPAtn9P?zO3R2d-47*G>S z4N#ZmM`<)bmNB5#s0OIZ@)L2RSe7xMF3Tk%K9=S0W8!03?gB7SmLWdC%ChWXpCgipmT`Ob!GobP8_ack5UvHrB8(wZRW2N$fI(*n0o;#7$OaKVlq#yOt`5V#G ztCe}eYs_CrW(S$3l-H8EjLfN29WTJwTMC~ie>DBCO>>Xd1VH;|aBY%1h_IK;}u><7qAJra2Ad6mlqq zxvA@LWBv{s>7rk_ooZt#oIzpp!pSI+omnO@cEx+%f~Tge=}xb>Zj>=!R2s{&%2s^C z0mi%pmZraZ#hcOEHAuDTKRr_%dL*khcxgJ_D{h6h6O_KlEB%G8GzPJ>f4vHS46Lcw z^K~!175vEx4{l+YRvD|daXjF`>-^d7Ik4rT>*JR=&fz-~av5-YHuPBi9oL^FON zQ(mgDj&V?AeXbJv2*MXa6roG?_kZGaT#A+uv zd4*f3HC-F*t7HsDbLN#%n-^9Z^9g0tX7G|VN8m}FZg z=UcG_Wz^5HIEt^sr7ig;vCzzI{}9`&;dIb8$Z}0hp!dR?WH^S!ZtzEhqs8tR7Q4a! zOC_k;XAd*xY5c0cKIH{lQJy=MVI3XJ!Q+NV-j;m+P-C82>q;VfzktfYJ?(C>8Z1=! z@&K%*$INi?Rd8j-OTWC#m|M`Z;AS!-TPZ>Bf@`wqOb655Ib`Oiy-dSt#*9+67rpdD z=yecqos#v^7c9U;)zPPD@-l96E#?$tIUDrUaLkixvCCT6iFT^+ymanyP`yAoqiuo< z(?fH&0~j*G7`cPslJv;@nfT030WhE7()94c7-pgvP?()BOeKF56#^=>h55pJ(f3;Z zov$nWFp(dB((HWWqiozf?r*mw`=t)$1;1vUZtM3^Q&5-->A88Rh2!?-t$X;=ukeWb@9KfLK6eDm|9 z+uli{X&i(tK`!HBUx)~D&mfP_w>j4pd^A0(_yU0YCB56y6Z6N=|9#T97G*7FO8R?dyaK1!F z_!@<;gk}g=eiJ2Mq8)>4#;*0pH9w1VYJj^5XW3A>f=WN5l3Z(WbDE=?a3BCK*M!#v z1O)(o8=(o`LnN7hA8nooqPaf!Sh}2BOceoe1g_250Co*%Bf*s6Y^K`W5(#H6fC0jZ z0X5+WkPwc^9?rsg5+fI@>uh9ptQ7lu>EbuweyZ%^@$`tsFOgY9ru7oP+2o%G&zUq8 zSHaWiN%@~A07DlAbMtR3*J}*T22=7MuG9fTe(L874G1jg#*$D7+N=IJqxL&To)2Zk{G!wj>o@zbYod5|SMQ=4X@m@F>B49H3zVeO6d1q=)Hy}@6~)i1!n99 zPRk!~p>GC5?A{*0VQG^kKfZ7h{7l3i{|&10VM|`FG0qnbN$2x70+>RDo2gLf;r**@ znaK=f4IlF6G~S9nZzqG&kG% z*EF_fO(6rIJ+9C_y!nSwKCWQOK`5$~f@iM(-o$>dw<rlfS-BO^EcD9)}qhr$h|iKs82QxD*7)N|AOYw9;)Cq;HpXVAv|9FgktH%h# z<9D&Q)_a(@7Q$>Em+y9-lxvmpTXBqn3I7i?x1Iy2iA{FPtVjlr(Q=s(9){KhzdbT) zc|8xwjDH$U_i!v5Sw*XI{yl#2Pl#`Uc)Q*ooj)`3GR(_XbTuy{05zs^Gr>1bBLo9) zegR$Bt^8r%gj+p%v-=41aV~{Pgd@KLdaqjCJSsmlksqdKOPL5jSZjvaXG^B^C@YTj z z+{ly`h!sHb*p(Q_^k!7(i-<8e2r&lVI>DHerS*SjVfRhSKcu3TwCDfj8WK;l6Y@cg z?`rHLy9vxvN^1=FtRXbTcLa~7at{K$nnL(B!FN(4i$@{l;~C!IiPYGplPbonBr{^x z5L0|3C4DOSB_)d~xgjnYNAI_(PTrRZ7%EHHyC*le;~UkGw|XK@{GvK1Sj1Z6|AKF@c&vn)RMK5kM=IS|>4})=3PA zJBjnjPM)rHav(VG04E|ce2Qm${&is;U~xV{NnArXA57$9cUL?TG%pG1pHq{Ir+}iA zLJ0}+3d_m-cho`g2OWhMXY)^hC$R|_3 zMUjJs#ac(xY^R8m-(NT#C#ABBDVzL(Ax)pxYJ;JgeB8+9w?$=iY9^mBrunu>uCAu7 z$tMhNzMr~kYxBnsYhJ-1n5U`G!Bk?~!CQDFqa$ z5d!Al1!7WqfWjI;lhPva)c~55<|Y6&vC`aVCN-Hu@d-8NcLA}?-$ueRe-MTxD}?!# z5rY>|7Bjy@1jGD!xH9z`Gyj!;lKE$-ze%`YerAY~lCCkogUr92`Du_k;#$UF74or; z;=yGo$<@&C-KLiyXQ#+{VOZfb%Kw{s+{kz2^e8>fTX{C6Cljct_KU*`y_A-59k%w1 z9XUOfjq|p4!*f8MSM9$a(fA|MxgpU?p>P$s&9t=|^7oG1MTxv^+uTA=mrw)Ur~R;^ z&&Y(dpPc1`U4Nqbf*4tT2f15fWLfm!0||h|mKzVYf1^rH*s{#(V?cMj&;{POMonQW zYv?%nYJNan1qqXzy}7w*34mMZ*!kH`rnIV=t<}$OF*C9M|rc%q_Az71i>>w7ebeMy; zG%M&(F)qXeZ^1Zye3MCyXD-t86u0>5d=_0Bsj@n6{8K4NZ$B==aVtk?{67I2whRK3 z6WGroI`;|wtfN4KMeBo2@V;)A1?z0i8`?<`!9}g3z7z3Xt}w6mhft21*rpcOQAu28 z=wkSn`-uttc7-;seWNDOJ|t?pg|kEO7yf!Q-hUs;HDWV=6rjaTZy{Q~?@E;RZ-q;d zii#x*?{Dc`J9p*z-vkGGlBMzYgC+l3L3)wS4d9*9rC3LW!H3_>_7ce{KUSz}d@Yn* zIC3RO;Gx#-N9M78eLgaO!0AEoi514Y2n(q%(9}LSZtZ^ydMD5>FUX*Lws^dXU!%_M z?m_|RKlMHv&Yg;u_-_-RKczIY2KmT&Wi$!+dWLFswB-X3@-;x#!yUcURZ3rHRpN7$ zRcM@kPrmtP+7>_wlP3M%#zKH%=y?;PW70Rw%U8&sOdiK*QeR|GX?PM464}lNprt>Z znN*fgnz=_<-xXthUt{4U>U@X%W?|j#dL2Q{PV$*=sEL0pq_=)#sxezq)R`u1{t5=6 zZ4>f*9XZR%nbVEY)^K)Sa~aLLnzAXBWfwJ>wy%?UtE(8?G-oAG4e2p^PSaV)w4^rY zHk-CdD8*l$T(UQt>n`U%j z)3VS_^A*5@$Z4?|F)-D~|L zYuPzUBT(yzS|@Zzkjtp+zgY^SR>9GyAZB`O_<%HTj&OetHm(-v#{r;k3v|b%2*hc; zxaBAIH|APM7y0O|n!yx@W_T#FA|w`8=8(W^eFBy0qtVdsc)>@I_QNQ{&@ZYp z5!?FU3}fa}w{4V{X`0MoJH*@e4$s%UwT%8~G1Nx*()!NVV|^;^@mddE#bZxMY_dp@lypk zop`&gopik2wuE$jyG`a&-)^(KojKZ=*IM4@Ti$BS@PjoKOB zNuT1)J_&hRnHtTQVoY;LI;MFM={n6zY^`CCs9(cpLAgm4j&8|cH^G>1LqFqhCVqu= z$U72%mJ2zX(whX#)Y+8$7Z~y%(FjV+#IL5ums_Cs%eqQP2qYK@n4`X(x1Jicq@MaHw zPaz*SY|uloQWJ%*Ej8$Y*i@z8`ML%@48vy{hP9{%VW*JRkT&Qc*g7%{XM-MqZ6zJ6 z>2_6$cZKgK6RYXdN=It?66vIxmhhXkAzwr$9`bdh<00Qpx<2HO*jn+B<7N(ppVlcu zjrlV}J*v%X-E}Mn1GbT#=(S#SCW1urqlS2`591AgsXE1h@I)Qs+w*aUBeB4n!tT4z zWH=NzL_hX$-HRmc#2kEr>0#t$1PY7UN4=IA@HAyMkhy&+v}|=5<8KtM*D~a`{AtSH zF^%_Dps)BFGRqms_o%WmA0mdbEvXjy>!xFP5czh>VW5W@@lTugNW00tb=iK#TzVy? zKlcWtD{sP`5=Km49I+JwK4l4fE~nan%+8q@)TRi* z3sYHssLj*NkhAdM8`TEX{&uutq=NApp)WA6{TV`sWE<_geI%3($u(k4!4RyVUoC&x z=mo!ni>J;bb6v&@K7BBURFyfH%(u|O+pi$gqT`zPrclfhW}-ynG%vHH3*)5Q#v{De zYmw^@P{zi2UhDgr{6-5tbU@npI>+iJ<{un}NnVb*o){~}&zN`S3o_DcO{8g1I{@1H znU$A<{8ox-$3aY3?Uc?;i^CJOT-uSY&RTO6tK61(&=1%Gw13DW)6Nm+fOR_1p`+C z<#w2CeuiqvOk_8Qyb(nL-vqwP1b!=zrP#aO0tuGC22n$L%g2bMk{RRQ4mrWsVC@^I zWXnkke;ya;475-2a?36u3?5*}+hCyO+USgMaNBE{7n)CnG5G& zu;Y*QY%Jz~9Ib6#vY3Gp^mvg$-GS$dgI#D+*E_H<&J_o5K-WKtMULcLad1CNs8iv5 z zUSS5jaA;EYkyw0O?AdqWiYI~f-Nt-#y*oVH7u<<`YOmIx>J6y# zPu4fVq~mZ7>T$C-pm)K7@8PpPq8|Ia;F2@guTlC2oL{>!`>XfMyks2{ESrkoR;8bM zzV5-gxAG7~fS-H832$a0uk>%cB;2GH6pgt^1x*|ofAUb)G*~Sn@J#w8B>x(u8cTRB z3Tmay3^gAi2Kcqw9aZ+|gA|EwDZ013#V9mx~*O1dyj-IB;VNESL zp+!hO7SOCj-nG~XvMJPv1@zfO-Vuht72A9q`;N!?dSvw|THsCda(6S1DsQ37OnFlN z7Vb}z$xLa?yov5qX1o875!s^>dE0&dK5FZ2WdB?F{6;LOP3eoqXIGI{%RI%Y<5D9E zL!nCmJNUCWcFK$yc?zbXDK=ojG0M|nIA-B65HBCiM+G5jntMLVq5!T zLYh9d_G}{WG||@p5_zXeO$oYXYhEHB>G;J_x3R5hnJ6;sh+%7^6ZxpgRZ)}J z*1Do9v90MPD4|f^QF?j;iI1%vm&iv*Z;ptUdimJedlPxb%w-V-IaphJdm`^@ygwpr zf7QU&^w=UML;`v+k#{Y$WL1sW)_$GHJHoKFjt#8IK^fcH7mqZ?&aL>=KLM{&{xR;E zS^2^r7}w*-+i}Jkv%gFL>_Bs%o0D!-nIbAZ-b!e}X$WB3JToktPUM~ZrUTIz5iFU{ z4$W#CKh8Tc3oVpsH;|Cf_!+PsP8|WPg8pe(c3~p#ur2{|dyMt3wkCfIlyvj(A7=19*r{6eDPVw}+h*FeFI zUZzSosD>KS%+`G7I#^I%>Y6!rTK1iZyk#l>B6_oAs7jA%_yy^$q^3&Uwvxu>X za*Oloe10-G^`u60jwC&mbWG=6r=qBX;JkD>{9Wbf)Pt8A&g=5|Mc_F;!a0}prUXtM z#Jo!qf1{Il4?`03e(f8K`PMZK=Vv3F>nYECFrWFigoHE8W@JB{$UBLjMo-(->$2k- zjwOA6+-ts<-191V%W(~7k!~AS=kcuv0vC@FJ)cj|)3gYkyvEUUXuuyNLA47-xP5xvNz|Zyz9iTU^4(zjr!o<16>x+n>f7b9XdKwg{3Di%7@gm%%J9AA~n;_1+PU-+1g@<7avML_Ys#@GKEdc>5LU ztufAgiICtnmC##2`jNN<-o~%y$(KgMX}niKThC&;En9)INZ5kMYv^AnJ-}Aj}k8Ik_e)PKS?wk zJ)f1i6L_wII3$9n)bQVFc*sl+Q70N|s*kyO;bhj+PIkFBM-87`(z$bJ){w)0W@ zqClW83Vd=T-iVkhSjra#;yn>tG9WW}wPcCg3QC;;Ioevu-lSJ$85MPqeZ6*I?57#( zZ=|H=%wZYG7^U?2g(Df1vz5{37X~j`xd`uZUexC6OHe*#)aKQc4BpSbw&c~&Hh7x> zw6!co881TyMKMkH9S_@P@RGr?4JdhqBll|B44`emXule4uc8e~TYDuf z_4>(9U(>vWmiqPnPG8f!>Ggl1

~?^eFwySIN(h2nTP$D_ZFsH_n*PDkJGMAI3of zyqA+3Q|_b6sN^Q%s_AjbUxyg8ATD_|vL2j*!Al0GZSa!8sUEZhf6Q-lbM#G{b?2VZ zd;Yo=8=3&J2IhCM76tqqM7ddLTOD%yT(_H0hxp7w_)PnAtxnnlax;($KG^=QpVb(3 zZ$GcKVG=G`J1N>=JB%y-CE=Y3xW2;iD3?CIp^t6Eza;$01pHPwdx+~1t{*!tZIk=K z`3tUJas4@g|77-;bb#VguB*A?-(c|b2dV!N;9ud=bB)Kj;@_3T-Fnh9BSS#QwS=pe zEB-x8+3&bs6R7>^uGU*r1cGjrKI)J`k%S<%J*Bu6X_Ipy0S~EnyA2$$n zaOsiKh)5k(^eAb)5mG?QTBT{xuZQ%tTz7M6f8j~4UvkC2Erd?jbKS!AC9eCpzReZ? z^cF@t*Bq|nxxRq?f0--({iE`eX`@B<<6M^}+6n$pK!&$!QkJf?r*r#)ct0n{!G%d zxjMKGOu+j|>p+G=+;=D7za{-Vmk#&ZqXbdMgTeJ8>PoJUaD9^Nmt4Q%n!wZA>0FDr zUdI*x1fLwikN6+JFQAXjT$ggagX=1;tGRCA5-nfl+R3GpyWiyc4%cH`PjZRIzjN*4 z%0fe)YY5j+uF+hgaRHZTkAKPj`e~>B-o>T<-p8f>?&eZ|4{)i!?{cZXA9Ja{|K?JE zIWG0r!lnMkaH+qAT=DP!dVi9k#!WKRxJiZ@H_1@rCK+nnBtwmxWTId1omf0%1H5zJX!@lS69-^KL=*H2#|{NMIc|8QcN6S+?1I-M*24F>-` zb)M(?3m2cEsr|-~9?Lb6Yrh118)?0_c^%jFTp#4Rg)9E)b5Zpn6YzUT-ygw$ z`U>EOhkS^X>n5&SxPH#{ORliY<3Fy0xQ^nAe}Yel;II83z&Fvy`CJ!qUBY!a*OgrF z;Sw!h=K2cP*SY?m_TB_ej-xvK@7>;6uWrk-@d1XlEt|vkX!@KUAq$LIgMA z>FGn-c(uFi&PtZClxIE9 z={)D~2#g+&z@Gmrw|AIw(%$Vn(%!3hq`eRDNP8dSk@o(AN80;a9%=7~Jknl;N7~!K zBki5bBklP-`M>|y+Y=nhxCstr+ysX*Zh}J@H^HHdo8VB!O>ijVCODLF6CBF82@Yl4 z1cx$ig2VE-{fOUdFJ!*+SUmZ^a{5`MS5M-5g)*ynr2g*m;xzPDp7-;7oaggAU*gIC ziAzv}XD823^X%q%E>HfiEDMz1^Y7v@`F)$1&4ciUcmE0o>%j{jwkRJb$zz%Q@}`~r)>FR+y1FY_vS&*A)^lKyA#;4kw0H_xigIrRGdY<^$Jb6F|O^63%k#zXh-<;L;abuY|bb9hBgjeyna)~Tz~r64_*Iz zPdInu(2b}=h8sgB+Eue^Y~*Co$DcE^-dw+R&0|sGj#Q2O$2dAYPK)ixLDJ~?L)Ioq z+_mOeYo1BMX{CgUB#fPJ6ggDFD#>x<*%VMeSC_u48rBqzu8~I9b~OrE>!e7#hV{iy zRenQvfybTu!XdOTr+4=+OW)X=9|W$1sxn?cQwLrEu~KxR^0;%Q8>2h2lK1nd|K)y~>tk|{Do>GkvZJsQ(fGs!hLW{en{ zhC$)b&=1$IlRra(Q2y(=$!HD_=AJ~ZH|kB~4xM(^_FY!oV^!oWQj3)0#w2b_>PhTL z&{t&jCaar-woJ8>HULG%lDb9RhPBXJP<;&JaE?qY0DEU+xpoFbWN+YTZ_?T`8!t$@ zS)`BPxLDlLB_5A^7h98{Dl5%xGhF{8KlrpbHPf2h8%Fc9suGBr1imj!MJfS-eqk<& zCX<_6+;t>{s6E;{CvSY44i9j1lKf!U=97iRY4w9I3Y%FY>dvjKguby$K~=PhL@a#` zTeAvA3`k)V=?kiO-y5pdWEvV3Ij_Pn|HT}t46Pm~Tm?6WVN!vcSfn9Nx?VkY#fVuM zGe5EB?vatoTJu+jwvALOmzf_~XsO>HSlgfK;|+Pq`b{Q5kfUbF7-sQJ}-j;I$cTqjLSrq^YE-;k9vKdf{7L6PJ2lEXYce^~l4 zYJP2KoQ|Bwd7Z71vC2C0PXSlJf5U-oboc$Y(fAfJtTO+89gyBLOXo(-f1ps_w^e+* z=k#qOBjz2q+&OCg%iPhC;mYW!`N^U2;q^H}#d2$YDnqFG+~Mu`rJARYwc0tvrfYsw z<+ws&cw7)HaP2yExQ+=(=6mPw&_i5dh{bknzLpyT#wzEnHb1&*Ovd$L<~#P^X^*U_ z7^CLhw;ml?TRDBy{E#^&Bf8dn@1bxXuZnlCuC z3&fh=9@;f({@S5!42Ag$F1=j`cK+Aeu`?;u?*(%pMKE5%*g*v z8D_D`n;*sV1x&0qzsrT*=En{k5x7UqUl_T&QZavOymD@B)O^>iE%Kbr1ln-dsQHCC zq0h~u=3j!u^&srq2V3Uu>ZtiwhtkUWQSPS`QqX))zGT+UAFqwjHx>W`Y5I&H9{>YM_-?4$3 zzA!g-26d>iL=fgR| z>JgT7@l-IsPx`vm7wXj0Nfo~Z^GmAE&8y9?9%9@#R?aa$d=R?({-Ir5Bzla$V|Vg$ zm%NORoKZOodf$|eY=Wj=uy>~s-+ybh0xvdqRW_ykkXgER)O_aIcAY%xE`OG2y_eU~CKYU0SF;wv# z^Ujge&2JeD=CJuCp+WP{Mu+i7KYRPA`Jem8?ix8=xY=FY@htQ3v#69uj5ooo29PIa z=ayK82Np7VS8%oP^(M0dFOeu04)!K5C3kY);DVnsotkR^I#rWNIoUck6naEU~< zruU|Y^Yh^WY)XqWkzAlDvOuRCqh%XLDV#YVUzu(SdFpPpOpM?HQgu^k_EX>FnTayw zZ;K!NXf94obN^s28A<=7n38X}@KN3oQs%giG?`c6@P!^ldsH?)MYA&th1A5-(S+sJ$Tl2&?B}l zQ_Q8dLSW)+N!$Z)6a3LK%dsG-f{h$N${-`!^LqLemYPC--Z{K1+rc#rwQ*6@Wx zmD9JtcJD+CSd@?EOV*Cv0Uugt-n}sF4IvuAi5@vBmo+Pa>J7tp!o)vyXeS{31|ooB zSwDK-I4fhdsN?p$j_>L^zILc8Ty=GP9=Bh`&p58p{j zg_NUuNs?+s1#jL1Zq~0J-hR%g`NM-P;j+Ih>-!H5jvI0#lwdIvxOApsrp|uBrTnWAoZVzhO4^G7Y!ZDQjQ`Y9c1m4MgMkgk`~1N+7Rcf z@%?P`+lR*P&XX=HlJZyiuu>8#!^+$+xIaN zS&FXb?fZ^p2}g%7l6ts2ZdgnX=EH|ZR#(=pHa|Tg)vPhUb8s6A(z)jLBZy;b%_mfm zCfGV09Ah23aT`Q<5sTKkohtR7`JE&@!hHTZ5qr)s|9gH{WsTXSyHDOFD_n!M{YiJp zSYMG=);yGYkQ>*S>kxsSd_-k9){%i^B%XhgCHD=twO9rjg3p@AsrxMR6%5X!$2N~_ zfENFmey*d#AKS->Odl0F;j6|@K0ic#zN_mq?SG5UYs`7FQ}L)7v?1WvlNevdG@yr1 zK2GP!^?O|Xy>awN<;>A56dHc}wj->e>mU(Xt{-Op{V>z-_^nm>IBNdm@Ld{byTIk? zEYo+hRzI&bLc2H z;_-Kln$I5FXsAKmo?*Iw_+VACj+!rFSXY7SZ!aR8nUyom zKRyjS{mJ4H{c)H2_yayN4Q}04xqzbP5!K57K5bj&?01ix2_XMGBn_WwzUk2M$~lXb zGv`S=fGG3?{@x-mZZ!Y==6qWI%IYzoxO*N6g%6|VugI?9d$*2_s}{{&=FB$s7Mogn zrVGtVfDQ7r>}oHljDDbv67}H~75T6@GG-oOE4Zq%+5GogROig^pN7ChLxK&E`Rm*} zuCQrRrp;jS=4Ch-pf?YV zS4PJ+=?$kqF2+FfgKEP$_J#@~U+J3|2(!&7nEGeU{H2gs3=72o8^C8Bd(ijUeAL(( z?OuzibNP+9bWx0;<_Az&Y=BBujUHvlpWjA+m`2UNSS6%l{^reN!jisx+VGYkIQnrG z)}58}N8zPAM;^k`_MZKn`;()L1nlOEs}8@ka{1`4%2~V2-`dAyW-|Q4D&ZNs%s1^D z+a+aLF<1d)vHsN9oh(`FtE$Gg?1vKdj%TOrERO3M|8>=2=H^Z+e^y@cE2Uy)#ec85 z1JHKz`cn1xtkIh*j(JncR6T19UiqWNqdBh>h0H_DkB{CB>%Q~mqrAV9srgXzZ*Hd1 zUp=@>sOSZ&)5^JH0Q|#67-hy}^MZHuDfmkiynO5`DPn%~fGjmZAnEA2| zM=Ix{&v+h_=*O+<$T^jC|j=XPKW;F%gagahx0mhv{d%+r3nIE-U!fao$Mp*w5 z^WR#E@_%E^&ap4B0-P^8l0R>ai$L)DHFvRej#tjxW`2c25cIJPJJ~q@CsL<{Yyq`@ zQ4syzg*$~RkFMFJsyI8Z0{P`tYwxbqMqB2;E~HZa)oYm?Ut1VEiiW{OTeJG`$oWiz z*W4nq(>n8Ar?LK;ue)uGT)@C`3!tyta0eeZo3FT)NxwyA)2Cag-?9oI{dX-MM;Jj( z^L100z&i6|qfq7N7Tc}RrFSig$^aa^8Q!tV{MgVjGTwpaP2OedY&5sYkM9^`XaT;q z&YP z;Oix8WJPYV1fPjAZw+<*duyj?@m{)y{(p;1m#Os1>ah#jELFTp%+GUk>Y0@@R-1

R`a2w6A#r(X5bn}sP?y1{gL*^MU^Yajy`21`>LnTOO zkLL44@%iCqRX$&*KSNJjMCaK;aAQg5ZLhTi;%@OPHkj&eokjVUmiIEx}-y;k(Nu z-y2MlvHK5Uk}qx@sd(_pH9KK)rz;-ER5(e7wnz9G<6h=xk23!ees$G%wzlYP6UPO zIwVY`0AGXrD9&%@X30ACIMfs0L3?m<>rn>#h=Ve>}S4;``8)1;&ct{4>%cp=WOVNdWhmBaSJZMTJT{5PWDH)ZR6#L{LL46 zY#ZM{bVNiv{wme@T{2ljC7z2-#k#-Dv^cTkF5yAV%y*n|$4WBBoq*TUkiB=);Vk!I zwX%H6P#=^MKNs{eDJ&D9vPQOL#d0BS)O^Y|utR76jK$)N{czOV1i=&7xz^pS*0$f- zCkBDQ{KyEq$p5onSSnKJUl?QLU#y&FUL=IRt#Zb$N`<8e!MRmAJuE5`WC`fy9A?04_KSS+92Ux-RfQkL5+Ne$ zIkMz^#kh;+HTE&TZEv8RkYvU0ikHU!2?Di@iby%mDNw`t^2BBb6qyp7Z|&40zbBe}kX^5wO+ z?NV&^H<4fD+voQoDv0E2vL$-?ts+%mm<2B{yY)C3sO6hnAu8LquMCe>CB=OG!EJ0! ztA{ne>JQN8ZbiH*0Q#%@3n1|i1WW!>G)BE#moEAFn+8uT+g+KoZl9v7Y_bO(|Je(SC7Qm-$jw z$J??x{wAyAt*|8-hEMXlRQj+qBz9bB=`8cNvQmGqN}XlCSuB4)KDbkc^`)qh1y0pYz|SRi1#+z zDYa<6cP(mt^D_%$Snp*Wk?-QwZT{;SLzO439s(Bp%`_(}vaNUp{xd@+9+J>se%Z-$mAUt=IKE0b|LCaWVhz zHEJ%*M`C%&J$f+1h+##}Ej8Z*el{Xptp7s=)rr?4hn1dsEpkF>cP;C|?TE1+XqWC0 zz1kuc$1U@V3$1926}fiQbd$8^xo+A_!_ceMY|BqP#|iA%Z`SIKs8+MK^jJE@o_fla zh)K8;$5VUfrj;#jxW`mzlC|xCg`um%w8-zwx<<)PXDkZJ(1}y9`F5O0hAU&M%|ASJ zOcoT`b22kNy-GY~E-+us!aKqY`93pc7%!zixfMppQp?JH!C^9r{|1xoJtjUnCjL%3 zk8Hy~VH`o5Evx6tS$R~h&zif)Fb=ysURld#|D`Nn>=%!Vfb_rdFM7nN`M8}lvT9f5 z{IQ4KT6x$@D)zHsD6+S_m~?i#0?V({n6&dRNZD)IxAOYI+gNe0M@Pr5P-gZK$%u9c zy^6|9hseCj{J3#kKr#Pr7=inDSOlnN)Es9)%=#qWQ_J*8@}LZ56aU-8RkiehLnTTI z4#C=L^DU=s!y9TXOUPZSnctzq8Cu57e?16--+zdP@8SmN|G%M{5fPp&1aN`*je|!p z$gML!f10MMFN-$!ofMJ5g1Uo@=H8XYcYh6xq~;@ zEQ-%V$IE3NYW{8qSfAw3lnv(FZi9yI!2{>;&{&lhyx{aA)YB8mhD1A#*!6R0zL`gT zXwLjH5;invzGJ8$Iq}`krY*euhGVE>$Ia&-lAUBRaUW*>e>dxiD-L=xS$_EtD1PlB z<(T+YmRrEX9)3LgCUfKAGGA5t-)3cgLs-I#z$5j3f9Ov2?Q28Il`{-iXkd}a_vM4f%%j7{SSH7=sH}OWBoy@)I9Aj8 zwfL%yV*D`QbC7bscU#N+q_LB~cgtTrzAuLrRm}Mo`7HBCI4i9~U!-{53;3cFg?sHB zJQ4H6hKEv7Ksd&HK~?oNOEZ90To!mkF$6hXGQ2q2PkQgecsG zPaF|QJZbm@q{_ON0i?8Z2hF4vYxLtkfffurfVjo^-~&v=P3CIm?9tnH!8vyF_ip*C zX;qBb8LfKe%Wwn`rL3ZbZ}MfdB8g&x%4l_vL}+yrWT2aCp;h|=<&WU8w*Uv-$8KVi zCARoo=HD4d`J3`LtNnZEZ=}iF6&!~tun9NVx7>DTIm^*3i&*~Ex{W;|1NHVYs9lK_ z4<&I+s(gf+J0Bj_!fof&*@(zP<7=F1PoBfkw1nbN<9&8O?ze$$N zbz@s98~#kq_&0J7i0rOAXMCwXU^c&vJh{PKhd%zWOnGXCgO6QM*)V)PnqIxY9cF>M zhv~O&^eHD;_nr50-AHfXLBov!aB0AA7zpq`e%tWs>@-9ihMy-p%!@3)anR7OUh!rZ zZ^ernLk#rE@$?H4aka*8+GE?%wTt^MNWCEWH~p5){&wq3mC zFIHgXuYU377H?MjEzef33x8EY2qv@jkUMIX9oQS_h2%_^VDv;anI^ z@he$qP3^@8H!dX+5OqKbh}ZRG6R+Gj)Cr5NH0VkdAL6+K;>C<7toS_d#~(J4w$iDa zjVC2D$^2AwT^REtPMV9mC#PrkC-akglKl7L++6m1Y6eGTWB2^*ej^8Oa&})bpHd_* zJcW<)OtK%};b=C-)m8u^_REN;_8Bw8Y-Isj#x*KVi=_RNO8ZbMZ#+@pxQ3bx2U z&7k)=Ovw;XH1NRsogV^bJL3p#+CEC@s*xwEo5%WM&a3> z`+XbnEIq}6T8+;FPS^`&uW58Jd*`O6!L$@;5$|Rw4UR$Elg$IIWTE|*{gx8pd{X?H zJ1_}c`@{LToYV}Xo62cIGN2OI{&J1`XXoPuU?|nGcWNP=rh`yk>1$El(w}nx`jzbb zmieETyswS9#f3c#`g}Ha@|NkV+L|iKkE47zt;uJ8F(Ov42--7tWbWw(gpp3&ZZ2|Z z&0au~o)!R`C1oT5H%R99C4#iQuroDQd*&x+!YO_z29{3E!`5UL>fge8XPL?E6i{DE zl8+%%c=DEHepcryl4fRSRL-fHMHM@uKP!qco++%k9d^N3DH}2;9+=d46{9q#Ear$*q6nb2+1l-HWge4oMulF-IT;ezA(s$GhF1)zNlY{bA}X2G>#Zu2v_w|PYe38 ze{vte(AS0gpTVDEs23KS#*NqSc=p8PQ?I=4DHGQjlh1ydE4_EnfqM#%R(($#q)rgI zt`&wsElkr1$>!B2H(ztZ=FQh#y%~P8dC{(Iex~|=xS;+>E!C>T16S2ZY<|)Wo2{Cv zQZa4n4PrY@y|^AbaT=vbR$0`3|tb{Qhnmo!W?nVOf#w47ZUef>2&P5O)GZ&X4I_bHTsUK zQGr;kW`Fd=>}@0aeC4!aF|+ehf1nbfJzDUCAZi9t6uYSvTN%ubrCNv&%#^SwQB&0` zXAY>&+wtuF8OXFXiPS+g%|;T2wMG&}jkq4g8jHmsHk3dh@!^h!UHF!&dh-*m-K;*U zUpJ`U&!#C$q*NL-t(qOCmg9SkW=Ey)6k7vXR7H{$cUaab8@ge$VbvPVD6%}SQ3eQd z2=X=Ds!Fv#S!F)mfLu!x zAn$Uu3bV}?#a?>poGT4IRX{E6#LaqKw`%p&sW)p;){Y)yN;Qf_*J0|D-3n`IHGM9v7PbSle2wwztA;u*0EXhRf@12S!18VjnzZuuUy5l9a zxanmhP%HOX#gJ8Vkqs80W6`QttG|Mo9ZckFgv2(3|wk&QwyrckXBHT{}p+d&YgmYt2i<`ktND+>3*sKzN{8;a~n z5TyIQ7y4-<4Q)4wtgK>Zag@Y`<8p zkQ^DWspfP7ooeg}t@2n-Dk1^k&3wB=f>K3fiagrcSlO$0qc)>qPEV<2}ToDphSnhT>9(d>xQ$ zd0Nodvw*Ryi%2`WR~FvbNupX@PopRZtSHij8+uLCw4j8%s^UM;1H7lGrgzc~!m?c1 zt<$m-x@iE$8kT1zZr1Y2t<@PKyk-XVi5Raa*)C*2#C;ha z+e_`BUbC!_l_smls`;g*OmPeyZ(O}DoY|cSQq;y^>FJmwQB)1^ylHmHJLFx8ZC>=6 z2y}}ci+eG-pcYo0OtJ+gx8X&|bg^$Ib>zBo-(*>|N^#OwUF|K(dkHFNxC!es(n7ZhP?R)Yrf- z!>ApLWgZpQva3Axg)WPG;(79%4mW?@-c-q-V^hZKa>-q++1*kK%nUg}zI7z3kb zVwsGu2wOHG9or8*H#3^OzLm@=+OPL)u;_IyK^|~jH+0zKL~fY`;7!Z=3n`w(YEPPW*T&-TE{t90Duobo^*$mLY?P*3Sl1@vFs%a>VOR=!G2wimcwWa9=FSdOjX@RS&}!q!kUs*@qx!WOJf3B#VB)>xsC z(^*fms_JEGR(g?YHP-}HtJh>lQS{<+8QIT)tJ|LEB#lPWVDjiGv-HM9O+isaE+=Ty z{buN+1c~hiBp-NLeR`b$ky-$@on_P)D=ym21{?0Q-i)K5;UsCSpsjm~)oXsD zZNK3}Y0b5CTI?|p-dGCq9D?49XX)E+|X6rt86P%V7ZCs z*ZpQ%_pK6KyG~Z?a#T66QigSUPm}f9angG1`J&p= zh&iQQg&(AGBovgUu5E{1H5Q6tKZ3EKMG4p_)We{Ztv^$T&&2ysGl;nj_-nCkg>GDP zBFB!Woj*a!WnxCyd!jsK{)Z)Y5D;k!vA?S}pRLPG9hr(gINhsx~+D z*l4FB7-m(JhIfSxk`wv_>PfAcp>SVjk2uH&%MHob;S z@>&j;V=L)OEw9xoYelu%y;hWk&2qky{fJ%kToO&F!&=qK$PVNNlvI6o7kW>SZ?UaX zWim28Volq#3T8Tt7(XTKv8N0Ly%E0`9z8@Wgmp1jxb5)0BkHj{=dtbJU7+sIq zKx50OB}z}FYZ_pYu$-ABi5Dg@-ekg45O{7a_1MRt1rOs4Cc%?P9-2@yMPsosUsC=k z3mbZ;re%yc^y`=-Y&!|;x|cOD2&jICM+zHCnW~nR)KfoBon~aYWoSE?PrmuWh!KV< z+L0!Y+eEA^F*}*R^~?{z)>PN-A>TxR2*mI#3KQC{yKIe`i61r{!fc{aRU%oRkj&iH zH`W^tyDq=!p=OJi4Q=?OQp*u}eRALIG)CKLS=s|kVRl@1n`}B!Z;V zpH{8rClDggs-pMXeR62!u&SJ>=P9t9bc5c;kc`$z%h z@~1F@3GzhfL|7DUyV+4GD!75+UGLe~<8PCafo zbw5nJav{2$Z3c6dQ=5F1Nx9!vl){2yH5&OAK~L)*jnxga!a<82o06-w{W55}9y*Mg z%Zp{XCE)b@_VuyYzV^EElp`1-3jPE1%&yTXOk_#IIuP2W!ly8uyeLdP*Q?p=#n6eA zD+R}%vKV#Q)>E;QdI37b8eGcKMV1jOG&^h*&QWy3N15rkE@sj+q&KA&J*6$NYU&T{ zE1hjzoBP|?wWS=1sLr*Ka_UKJ`!OuOqdGO5D=ai>=1Mh<*cB=hOBj2autaN6`ia^Q zTrpWR+1AH?jeUi#EhoBVsOH|+a~3-!yWUKk5WR)PqFj&4orA0u3T(T>B}fQZ*-&pr(!!sP&2=(^rlNfwf&^9Md9rJJ#xy#1v+1zuz7Z?20|MtncH znaz_h5%$@*(lkQMWiZ>cQ5rM)TUh%7W$e2?X=(2y3MLJfQ#SiSDr){j7d#ooP}gFO zUYn`vyEqb9b*feXEi<6mCL6ydta!Q8p^d4b^b^P2lCQB$L@}aZbq?5xCszLk1~CyP zc-i4_!;GudeH40~7_{uNPvOe4HKxnD-Dp@o9w%X=t1?+O`YajOh=cu;F)EkWj45SO z6O*a*-9jtm)Ek^7P(uk{s=mzzEv&FTPRNw4hcJFlA+YQk>dPP-nETTzp3fC5yf_cU zjnJbb^-?E9tQ=VKY46aQbtj1%QRHA|u0wQc1p7@sYpIQEnAXr=+0X$G(JLi7V4Lml z#aya1eCW%uu%o4HM(|0mly4x0VZlcsZ{>WW81}VF0(2&|@9+V$|}e!dSpd;GO6`tw&J7 z=E&-U2ab~pPt55&grCQ2ESZx^farZdXX}b&20Mz6LZar+KYboQorzFd=X}1C& zTauIdshica+?Wj_ZZ(=-y@>+NiQ}5vj5ENL(hKr#i%x;~{ty}?-ot4P(-foa;z)#U zM3-3x2+W6j=O`2UsCC$mLUKVsF@=EpMcT4Lbmh>LXCpcUS<8i_((a*U;|R-sNC#3# zH!&UI;uK&5O`Be`gc{9&^e(Kf$)Rg&q9{SxvBWnpb70!hqi0a2$hJ#Uv9DtZ!H(;t z77Egal{MYD&D6%eTr*Xzw(p9lPO$umNnClHv>RQ*%O$rJ<|cIQxF*JQ0X6qPYd8V6 zaWt%PjOryUA)?d6$Z9MRRRAJku9!*K%2*9dyIJ{u>qg0d=XkItw^6SL&7^@8RbqfE z?QrX&W@ld|)EB0KTdRk7Ygv|GFV&)lQY#A_aUjt0AHBESIU-$TgRIR*R8wODvmk|b z4Oc5KtL9{6xsHtprf&TDFnwDV4#s{DaGEM?SDUhWLxKRzvoW^UIrPYN5DCjwbd69g z)**((_E9Xh8aO}HLiQ8vakFBl9IxQ!&IlrUxRJw-1&e{J`{kXI1zW}-@^OYp@tz9_ z+HkU3Pa?3SV|U`>CE}&Hc*k|z-?LhmVQB+xV?AV-Mu_(WMmpA{3{16dExl2(JVL2t za5EPlaqWxnnn#>WaNxGnAgd&EfzZk(=0NUpdKD^V9Y%A~6WvZR5nu(WpIeO|Nf`wh=)CP}jd=|EjC zjJH(PbTKwI>Nv`?4Z~`n2lC{nI}?UlQT-&Thm?vv4qs?pYoi<&QJPqp{xs8*77El} z^G&D!P5(6&-(q}#u|)f*TUp*pge4$$_$hnJPq`ffNe5z>AMrZi7-=&0b@62;&fw8H z;sStOvL+Eg4LgnO$SVVOa=S(re_}RV3}GmL0!LTaZ}mr*Um#{yU9}}$#SM+%q-N~k zE@@@ut^ZLGABh!5DK7jh3dm1Z83jJ0jZW3SUj50nRkegT4t&EJNfWQ-tUw{N7Hp>+ z4%fg!sLw9|XKB<-eYWYTEvr!Kx2Y><6;4O^C`DlVQN51c2Pp|YQ!1k8Y;H()(GB9w zo2!c`>Bn9}{B7ArqT`B7C1urc*wB;7L++yc0SrhCCpwc?jsa8q!5i2I?udJ*R@eFh zv(XDkEkK}gY90YW9=@riwl&n5>{MS)cH$HR=|K%P@gI-rOH;u7LjS1L6ixD;0IA^tV zc(-T{b-!#H5^-&E;ZlSV;mBIfv1_SzJ0r#EtQ%u% z#z@%+&?lnT?!@c~*+|+7F4SafP(9qpnE)(c?MmH2TqN+wXdk*EWYvfo$PWntpD5nT z)ye4R4#?Q0oI35|%t17cuN%!)<+7`)C@VV_Vnx%{s+tcy4ICBOq1+i2;h!6MFHjEXq*;^|l8 z6%{<{ELFHaZFhzQ`dW{d#kY5Bslyst2<&Y*13Dg3nyUVkHyM7?MA1?QNg=G%E%o>m zEb_2Ev@8+s)OGY*-Ibn%J~l@GkH0BiGe`qoR-qoy^8XoxC~UHFreO`;5kbtlKlh9F z1>-5|{y4Glkc>Notd#GsQCqH*y+jKz^6LN{xb@!wbMUeRuV394x))SEQ$K8?K+ zDfcl01sq*6+1jK0ve5&wT+{l(`FMtPz35Ojlh7M9c)Z5^BnVM-IH4w?|H zA8UW_Qb*!|PC(d)p(*i5@UWSNy856j#rAlx6xxm6cJab##S}(8r_la)KM;%tvlV74$K>HOqK`~rj6rS zZV<9(!c(jaqLRo|{32#lB6&Pk-XIj=tUVP(Z1V|n)OHx$ty1DYa3_iwY`2_`u^1S% z_A1-NPJkWT_iF*0W4lygE}Sl9iY?E{DDuz(4YFU5KSpE(rVZ%n3MyyzjjEHkp zsmtQ<*R@}iDasi)9RD>pi$WWU;M9~bV!hye^w94t&UU{$GSh zGJS6Pi9`5iVud(zCQ%txEajG6zU|qrl}>gmmZ4$`L~MH#q~m|>aBzQC)iS!N4*PwF zNIZaUil9pbJ6xtc`f4@HFrNrJ%N(n0ZWM@ZHjf;7Q72eSjn*;*hljQDegbjm=yBd9 zSe3w}2rY4m#^{2>5oBZ{63s^Sn>wff>^##lbq{RTl#n!^n0*2v*q~unfvu?4mm2XP zKM0OO0q)T$x>^h$*kf{#PsU^4k377Gux>ec^VU0>@4NI{o)~Nyq)8FS*r)&%qJN|5 zNU%TWvgCCUd00AvpnK1YKD1hWYB(n}D5uISb1=w?>jVN(?jh)+g(GCH9$|uE$t@2j zQ-2wkKTo!lqUCov0w%a`bkTDaX<0`qbt5w z-FGv`BJ}YD<6(}*lJ9eJQ788*^}f@h4&^bavV%{7Hzm;4V|xul4gBpz8MiWQsZJZ5@?OAhXdU1(XPK&jiR+xp#pxAzG!n8BMkQ4(Y zHeD}B8Y|S(mbI)-NsD;W(8lQy9Rw#DD7mj^m-L7{RZ4+D_4-Pq(k|Kze&4{z^@RmViRW725!tOF-c6nlClwNB{eXj9Egm<(%CLCfHJ#=c!uA3><8?b@B>nXS3G``;5{0~%_iVx~otH-hs$c5K39HRAkxot!h$34;Pa*`^ zt{MkcOl3MV%i*9OrG~=?29u2%9z>izJBj<~l*%AKGI>R9HsSvs>OxuNFsx@V7u>W> z{p|F54zXv5IUWZUAiFUlRTECDX-|cWAeRVQq?pr`vt7{s;@iT+FBKDKv%bp-w#gK= z27)8IUv?es7KI1~A2NOHAsfk>ZQD2x;a1J5ex(^DkofQ_gb&W^RDW5~bhsr7EEm(9 zhwP27LsqOq)u2M1M5^ITaEhg~a=J`*KKQ_j9BeHP9*?1o=eFn6G;|N}>39T7U??X> zs)?Z)&1oC|mV{yAiLfvgEo4qr=PF=9Y~S)0YQl?fy~cf4^JD!gEDGQQr2SL2QC^i ztYe5^zRW;rvZ%7TiJ; z)9YS}=ZK=+ToNr9fK3n|@*P_oVk|H!;uXM2UTqfF&$?9dl5_(cN*V<0G~~o&M^~^6 zkt?St72lAijR@Bf&h!j8IWQ}w=k$rvQQK06VFVvPbYO&CS^R7FWe|4F!IYj8TQ!#h z;Rs`^wM(~y*s{oc(1trZQNl<*c!;Et&fMW-r`W@UT;;J0>rg(hY8yjVMgm`ST+gsJ z5ug|*&8*6{3$$IFW+Q~&V_Zlai_=8Ikli~Lk*xU1VKfsMeH0KF?tN6xjTFzUHbUBm zW^i&)&pj3N#THjf&uPF)8=Ub_F51C?Ide}b{E9n!C>x0Q?IBwFC?AQ}EHz8nB|>Gr z2K&fHWc%VW8HZ)WX~Ar%^PvRca_Bakl+7$LBMn_H&=X3}Bc-g-nr+eQ5Sb#^(DaVy zK+5mT5dd-C!ZciOVv6Ok!mN8IV;EqE6A~dug>gi@KnYRHZOwcH7S7+6z7wKPq#Ypv z5Ibw0Lh>@Rc938;EFDZ|4i{rU(Jc0@PjP`cIh8ErlIbR^BUV!4(A`oG2GTlpoC1J# zh^gD)tezUS%#OO$@gafsBkAIlCm49-$ju2jmOTyqmp)dxh(RKV6-A3ET?n@zWLjhN z{x+@HF@X_r?Bkfp%*~*<4^Oa<0vt=4917ZKpkT9gul20!tIELZN1ZunTUds(cV(y2 zo{P{%t&YXpAszu8C=M4T+K|mkVF->T^thg>9EC2B%h;GM1E4IM3i*DKYwT)5dl#>rtmE_018+ zeLgeV<*&JZZDBiCr{xDNW*HPZG^R?+#&!cG(+WAH5lWL4z4*3E)T^ZFH+^PG_Je4> z>}_fxdnp{ovfpJ{xLvA!lwOtwzsTN{7M|Itu3pGMMI76@Rs2$B7N@7TZhhM9%+q+; z_MFAwXBdAap2*lwPbX1pYIbH}>sGG0xK7XRY;p{cX}fWswEYxfN(TaRyP+@TxVJjp zRi9!NrK4DKT36``OWO^7cE*zYWjnXW$-JN%GP z(Dlixa6l+@|6I6K^r<6X&e?IQOPp5D+9$`7;z6JMJ@&*tc}pjC_Q|E}3&m;oeKN|d zLt{~v4%8m6JS#IAW7c7J-MaO9^*7HTbFps*H3OH)p&d}SsqOt`LTYvHVCh=wb4BXwu_W|LLiYv?C26c1ercz|7VtYq1+F5@=Xz?qn!AU(>TGY=`sdCWz%sBFwRY?Vg`;M3zW7t3 zw-aKo`N(Ous3RG+ZbjL?2s*Nw0{8_pt&3nlzHYRequ%E$!glsUh|3x|c?Wu@;3+OV zU8wyZhM+RRoa8W-NWOcBABaWsl(_wat}lv!^}sAkxoWk|>AQOf)GU*QL~81tnwR{9 zNesp%9$b|105Y8i7b3R9k{mmrE-W3pfp~e%F&kMbZZh2*a ze!H>M=6aBFqBR-36{WasN6_}|wBBehCm85-rQvRG|3OrT4pa6jhO+9aH;qI|!);lm zXjK#moo5p2a!Lh}E2zk$cPNZSX4NXGrKL{ggV*WYr|QvqY01%vV+EV&ocIE!`#E#v zdHThm5KuT$JM1 zi4ktTe)QWaUN+8ojs=A;TE1A;K%=~@h{r5be?(@PR=}j>%BjP5Ga%$;MIf?F{ej3b ztpJ2-IKQfMuEb1D0i40Wis>X4ldaH3x8+ubnexnM`fg)6E7j z+hq4&W*kOkxbgZO&z_ik>Xp|$W#YP?Z33qGLJ`g(X=Ua7)ymJAN&jf(ex`N*VhN$YeQV-$5bgmMEOxJ-cN-*-Zq{{DCbC^U)SH zL#vS-Y`ajMn!OwkWI`$Aj#3UH;Gpm=(d^zi?uH^#b8%|Ag@y6*TGgsHELD1Xs#%?1 z*z)+^Tvg8@xPmYXPUpwD(s3`3b}v@F2Llkc=RXKgfoE=MjQjEbNw<`-FENxe_9q5c zadsS`4N>p&XF$5NOBwruQqI^P6kFC~!iY*W_r*pzV_y);8T*4^8Eq-z-X)EDx3-g- zb6g5)Y?)tNXhmBH(c&I&jw|5`2AqxwUSp2qIhAcv%6N56Q12!Oj4>`PUgmLBjo@$i>Ur6D-}(nj~>6~nrA$9 z{80+faUvuZa1$A~s&%VcyL@J9_ny}Df$>Kb9T?|qX6N#nd%5jD`q75*;h*17)jpzg zi_MvEZ?drQbi)`neft{nXJ^(Ln@0H|ieTf_ymg3} z#x*pMovLZPWW+F@DV`O^#Wb>UmhtHa*>O|Xz(kZQ!Vme~gVgT^uH$>+?1 zu?X=W>7Bo^WfjG>i;wXsa^RV4T)}5;DlsN`<+FhQY{1X8^~N8NRXeCqrSVzN zRV<`4WA~A{zjbTw2Xq^`Lj*Vq)XzwA9|au3ZbU?`;`>N8PgK;cl0o*U|^<^O=C1XM7qiNhj(o^AKsg6zykh z6!ykF9ycZNB-6?G22+W>qw%Efo&(!>}hE2;&|6xKBJ83qyB5SIh0j?TYuYQmFy@Pk~14hPDjeIDr`^I)E$kz+wQi?O+#(zm-=M^n|Nd7uxenkS4>XtI&H3Eb{^Xp(C z<2eV75o8>dYef&`r_#Dw`8t-iWfwp_;!2yS8ynDdb4g3x{bL@@mhO{tawc7`QCB+)pB9~rg-#ulQK^ApnP z+5NI{6Y;Yu{7sIbFn%S6Z&I9CjVBjHZQ1!5#tY6DrGO{|wp;?$q94%a8KI%=##=9z z#is4}W*oanDjqDP;{5=YD+0cDpkUJBd0(HjjF-`Fp;b2iQX1hNdhuuO zQXmHaq-KydR4q*1l597A`)tMlEd!eo`mmgq;mr@pv1YNCe8Hytm&=O$m2(Td!H!4h zO=js_+hwIdWclsJ10vB;ssES^jI4sbIlDL$qfs;75ArjuM+jgkbRq#!#&3ZN<#27> z19@=dni%Jj-SgqJ@uKl;W!G1x@d0nmNl}WV;ncLz0(|+wxgY9OIGyTKORnnpTj;Sv z)$t}0awBLBfRt;r+(Ku<3{F)Y^noPg*hnCu#6Y#|8dImt#5RH>rB1giH-OlOEONEsCpX{qU>Q|Y=vHn z-QExd3ju@^vWOhRjVzFOFVEi=o!<&pD#yxjM2-)wpBV>*18FpQ(+9L1ZD;80;$1}ddH(@0U z#$#!_kvIkK{C(^DWQwFxjTfE3%X!vl>g3en#}j@+M@ zEXV#nj>go1J;v7ndf*dYnh4oBbuSpdjGUc$Ok4<>mpG2)`a)D>3YBI3rA5W-b5Ki& z%A0$JwcU6@Hnzq)X*rv(2aUg_2d7{meUL511_y3-^-8 zK_y+`-jlx80MM6sFu3(!Z^-I=>ZV$lpQBP_@(jO zZcG$Fs>%AUiekyeZPZs5vVIae#Bp4Db=Z+@%X0y%d(C)Y)@9>x!E!wG2s6dB7jC=q zy#E)bb34c(iI8RGICRU=_(luzJn7RtJ^|^8j^-vRyU*ONuVdDVf62K+ zg>EF{--(*B#xzi9T~3MRb?KVll4Hks8-<=er?jqz;SM~v{aMFaQm z*}5m6iyc4w+grN}n! zf<-KF&fjDRu_Tj9^Ul7-_(GmOH@&!EJOlx(I4JK@gLM?4;R^nCy6xN>F?|mc#UxfC z?vzxr^w}`ElB&lzkk9Up1D}n@kWJGpE5!H6qbJqiCxb# zWU+Y#)LA-%>4inhF7ogvAerTurE&g+B1kQU)0)A+yFNv$%AFBh8b+OG*m$pF7(d0E z){AvTnHU~lAZ5HAm9qe^B;-YhU0pQGB$IvVT4W46i(a1K0Ddz? zF9ZwU!6II$vu2Xr$_%QvqK{-NlbeCpz)@+tUogWq&Wzt~ynk$fj1qA6yYxmI5j4*; zCI@LkqQt($gwG@YhTs_DM5-r2nq&Mc)_=wE|uU;x$c7Cew)=*$Gddl{3o36SXzFcPi2t zy%bm^a7m`R?&iO&8K9_i=Q)G4A$|Esj#7=d*W`%H*CXBTd*MQWyh7guuT`_iX zDFOEpx?d~QFYtb`SnZg;Ut|!~s7uRl&wogTU(frI6F`Q*4@0B_2ML|dxU1@=;lJpq zvHOY&{t0xa3%?AxD@gj6z0IV5bh-_-kCtdcA1T_ z5y(u!@jxO&#OGi)jwe_0c}2zsG@oEpB_HGZ>JB)B`z$ws!TEm-p4m2rfLSAh<*Z8o z+uiNkjBD&3Jab^Yh82Lm$a3_DAUtPk^zyY-BfAsP(&WjSCA=gj{G26ZGI+)uAKt+* zHM=<6U8HBKd@;{a%+JnRzW^9*+x@R<_RtR#inCIwVf;sJL}I6gK302%ajux;r)PKX zR^vXqdol~jnzV+tO+Nc+#gzdkdU?$7Gbx>2n6ml0t2eV)s*6rDLK`p z9W|}S5ow8ozXLAc=Ay-V?8Irr#V-@)HBzziPOE1zLRLdNChCikuhO@RTXJJXGQSU1 zXyyYc2i<#e<7mY7AKV!m`{~5Uvj(f}Bu*%=+<1~0=(vc6m|E_!;G(5iE?aSeiP2N6 zmMIP*xau?EicM}JAxF!OV)f&TlFK(48N~54w^TUA4 zO1V6AV*Lc5IQotz@^gJb$0gusIIGnwXAVg5dgcuz1Hs*Uo*e~si)(XIyD_mLrJDQU z-mlYl20n2 zxzTT8Poi;;JDXt_a%-S#b7lC%LoVwrvHZ0-XFs+#H?sFT^U1o3JVigK%jFqjAT*oJk-Nqz3k3ntFpMMa*z!nTC^Ttm0S zjV>KS(;f;K61R_Y9S+wxbNhHYah@!Yq_>kF3*dv)ZtgXvsGm+mdTK)azDIPtEW!A$%Xal#KUgLffGs!@&>G0 z+d{`pmYf$MM8*Z5KG!k1Ry}1FB@@-@>3w^vfzxpP8rRp=9PU>2J+8|>zv!~Mh(p0* zbuQst8&v1A^IDy&p&Z+4PCUFIvv!$|MX%=$pGVv)i=HhPo_cS{_qG;kmSvU0>M2_~=%~t1K;fx=b z35B*7G(*DwxovggB3boxNyx)fdLYZ~bP81C&Ruy{vmv8pH4=AX^VLPUel4zeJNd=c zrR#Cf&B?3?YutiVZS<xBF2>sF{ZwoiKn-E?vpL5uU4N?n1-&U z{k;n^LR^n16#_MDl*hg9LBg6Gaaa0;{mTmKnx$Y`8WePh5!=wKM41@1lqjvmt^v`P z^`ZliB&@s5hR=Kmte7)VCY;+fAX=Oq1d!{J^u%FVhHN7@w8A6+o-%f`f8n46rMt!D}eZt%OAjm^yDo>|J|TP_v!wcwD4wylQE^~JuO z1cB#qKjuXJDMhDb#VVvaABQq68Tnig#y#Y&&6=9Tk)QJ_->%D5(43P~=c-nVi@x08 zIeBHvU7}}M;vk8JA(>tf;tSFQX5?@ITBGUt?AP^bU@cL3W1~VvlViGO&~Fpx7H_?v zz=1P6yO;W4GI3xhkP#Qha>UQXW5+<@?n%CGJ88k+ZOT2OwKR@kWFdD~UvZMMwWucQ zwwL&D9hOUPV*5!}WO3)P;{_>K#MilBzCQ6dVPW?IZCY^{dtAIM*W7dWa(&|Q>odbL z76V+}Zn`q2p&*{#t4OkVvFcCEl4Sl}DIA7N&Zia#iC}FNk8TEsGm)B+Be#ntfi0I) z>cRF=sW4t#_KU0>*dF8dglh+GCD5UWlhfh+?sii)7p$WPi@2;k=91iK;;Iv<2dGOdhVrdCRQ*pan+zz_!{3- z(o6#Gt&CZ|(z?f`r4v_+>{kp#@v_8-pp{djMlIkvO5tiA2URtYn-X(7DG9ndw~_0S(#p7-RvdA*)8bY`;GlAC$86D@D$ zxhS$J8`s{OBSp9bj(QDYp@DRfhFlaJ#1lI_nzL)1+E-d^CDt^VuO~Mr(PB#mB8Kg- zyNzSEY%UAo#Lk3bb-f5LRhCPVTg2TxHH#%IFLI?Vakpd`B!1#D-?>=UP8%TkDc9%7 zkPF7SdbE255hSZlgXyBza({MqVJ5J1M3^6Vb{KGZq#bza#5K}oHF9k&gynQ-2`I*G z$`QMxgmFj7#s$4k74o$#ViXTd-jqa9c#~awiu$X%D%1E{B`7S;;Z`D8YX%9oRtHJq zG=oUwxTnc}VSp;?3(s9ll3Sz>bf^u>u~E`HPQsyQ6Hj-$>!@pJ()=%_f`-FotyYr_ zPinbgI&tl&Hah&2{Kb%Ro%%a>;CjKuGB>gIxjFTQ6^dae01EVzsl~ Date: Wed, 9 Aug 2023 01:39:17 +0530 Subject: [PATCH 22/85] moved `lib/parachain` to `dot/parachain` (#3429) --- dot/core/mock_runtime_instance_test.go | 2 +- dot/mock_node_builder_test.go | 2 +- dot/node.go | 2 +- dot/node_integration_test.go | 2 +- .../approval_distribution_message.go | 2 +- .../approval_distribution_message_test.go | 2 +- .../parachain/available_data_fetching.go | 2 +- .../parachain/available_data_fetching_test.go | 2 +- .../parachain/candidate_validation.go | 4 +- .../parachain/candidate_validation_test.go | 2 +- {lib => dot}/parachain/chunk_fetching.go | 2 +- {lib => dot}/parachain/chunk_fetching_test.go | 2 +- {lib => dot}/parachain/collation_fetching.go | 2 +- .../parachain/collation_fetching_test.go | 2 +- {lib => dot}/parachain/collation_protocol.go | 2 +- .../parachain/collation_protocol_test.go | 2 +- {lib => dot}/parachain/mocks_generate_test.go | 3 +- .../parachain/mocks_runtime_test.go | 52 +++--------------- dot/parachain/mocks_test.go | 49 +++++++++++++++++ {lib => dot}/parachain/network_protocols.go | 0 {lib => dot}/parachain/pov_fetching.go | 0 {lib => dot}/parachain/pov_fetching_test.go | 0 {lib => dot}/parachain/runtime/instance.go | 2 +- {lib => dot}/parachain/service.go | 0 {lib => dot}/parachain/statement.go | 2 +- .../statement_distribution_message.go | 2 +- .../statement_distribution_message_test.go | 2 +- {lib => dot}/parachain/statement_fetching.go | 2 +- .../parachain/statement_fetching_test.go | 2 +- {lib => dot}/parachain/statement_test.go | 2 +- .../testdata/collation_protocol.yaml | 0 .../parachain/testdata/statement.yaml | 0 .../testdata/test_parachain_adder.wasm | Bin .../parachain/types/testdata/westend.yaml | 0 {lib => dot}/parachain/types/types.go | 0 {lib => dot}/parachain/types/types_test.go | 0 {lib => dot}/parachain/validation_protocol.go | 0 .../parachain/validation_protocol_test.go | 0 dot/services.go | 2 +- dot/state/mocks_runtime_test.go | 2 +- dot/sync/mock_runtime_test.go | 2 +- lib/babe/mocks/runtime.go | 2 +- lib/blocktree/mocks_test.go | 2 +- lib/grandpa/mocks_runtime_test.go | 2 +- lib/runtime/interface.go | 2 +- lib/runtime/mocks/mocks.go | 2 +- lib/runtime/wazero/instance.go | 2 +- 47 files changed, 91 insertions(+), 79 deletions(-) rename {lib => dot}/parachain/approval_distribution_message.go (98%) rename {lib => dot}/parachain/approval_distribution_message_test.go (99%) rename {lib => dot}/parachain/available_data_fetching.go (98%) rename {lib => dot}/parachain/available_data_fetching_test.go (97%) rename {lib => dot}/parachain/candidate_validation.go (97%) rename {lib => dot}/parachain/candidate_validation_test.go (98%) rename {lib => dot}/parachain/chunk_fetching.go (97%) rename {lib => dot}/parachain/chunk_fetching_test.go (97%) rename {lib => dot}/parachain/collation_fetching.go (97%) rename {lib => dot}/parachain/collation_fetching_test.go (98%) rename {lib => dot}/parachain/collation_protocol.go (98%) rename {lib => dot}/parachain/collation_protocol_test.go (98%) rename {lib => dot}/parachain/mocks_generate_test.go (50%) rename lib/parachain/mocks_test.go => dot/parachain/mocks_runtime_test.go (63%) create mode 100644 dot/parachain/mocks_test.go rename {lib => dot}/parachain/network_protocols.go (100%) rename {lib => dot}/parachain/pov_fetching.go (100%) rename {lib => dot}/parachain/pov_fetching_test.go (100%) rename {lib => dot}/parachain/runtime/instance.go (98%) rename {lib => dot}/parachain/service.go (100%) rename {lib => dot}/parachain/statement.go (96%) rename {lib => dot}/parachain/statement_distribution_message.go (98%) rename {lib => dot}/parachain/statement_distribution_message_test.go (99%) rename {lib => dot}/parachain/statement_fetching.go (97%) rename {lib => dot}/parachain/statement_fetching_test.go (98%) rename {lib => dot}/parachain/statement_test.go (97%) rename {lib => dot}/parachain/testdata/collation_protocol.yaml (100%) rename {lib => dot}/parachain/testdata/statement.yaml (100%) rename {lib => dot}/parachain/testdata/test_parachain_adder.wasm (100%) rename {lib => dot}/parachain/types/testdata/westend.yaml (100%) rename {lib => dot}/parachain/types/types.go (100%) rename {lib => dot}/parachain/types/types_test.go (100%) rename {lib => dot}/parachain/validation_protocol.go (100%) rename {lib => dot}/parachain/validation_protocol_test.go (100%) diff --git a/dot/core/mock_runtime_instance_test.go b/dot/core/mock_runtime_instance_test.go index 5c44e651d0..c12ddd7568 100644 --- a/dot/core/mock_runtime_instance_test.go +++ b/dot/core/mock_runtime_instance_test.go @@ -7,11 +7,11 @@ package core import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index 7e2ecae031..2a1b2a5480 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -11,6 +11,7 @@ import ( core "github.com/ChainSafe/gossamer/dot/core" digest "github.com/ChainSafe/gossamer/dot/digest" network "github.com/ChainSafe/gossamer/dot/network" + parachain "github.com/ChainSafe/gossamer/dot/parachain" rpc "github.com/ChainSafe/gossamer/dot/rpc" state "github.com/ChainSafe/gossamer/dot/state" sync "github.com/ChainSafe/gossamer/dot/sync" @@ -20,7 +21,6 @@ import ( common "github.com/ChainSafe/gossamer/lib/common" grandpa "github.com/ChainSafe/gossamer/lib/grandpa" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachain "github.com/ChainSafe/gossamer/lib/parachain" runtime "github.com/ChainSafe/gossamer/lib/runtime" gomock "github.com/golang/mock/gomock" ) diff --git a/dot/node.go b/dot/node.go index 4a6bec6a25..f4d5d2a482 100644 --- a/dot/node.go +++ b/dot/node.go @@ -18,6 +18,7 @@ import ( "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain" "github.com/ChainSafe/gossamer/dot/rpc" "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/dot/state/pruner" @@ -33,7 +34,6 @@ import ( "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/services" ) diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 9668d2ccaf..e42f2d6eb3 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -20,6 +20,7 @@ import ( "github.com/ChainSafe/gossamer/dot/core" digest "github.com/ChainSafe/gossamer/dot/digest" network "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain" "github.com/ChainSafe/gossamer/dot/state" dotsync "github.com/ChainSafe/gossamer/dot/sync" system "github.com/ChainSafe/gossamer/dot/system" @@ -33,7 +34,6 @@ import ( "github.com/ChainSafe/gossamer/lib/genesis" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/lib/trie" diff --git a/lib/parachain/approval_distribution_message.go b/dot/parachain/approval_distribution_message.go similarity index 98% rename from lib/parachain/approval_distribution_message.go rename to dot/parachain/approval_distribution_message.go index c97a027592..84cdfb9320 100644 --- a/lib/parachain/approval_distribution_message.go +++ b/dot/parachain/approval_distribution_message.go @@ -6,9 +6,9 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/approval_distribution_message_test.go b/dot/parachain/approval_distribution_message_test.go similarity index 99% rename from lib/parachain/approval_distribution_message_test.go rename to dot/parachain/approval_distribution_message_test.go index 138d268642..5a05613915 100644 --- a/lib/parachain/approval_distribution_message_test.go +++ b/dot/parachain/approval_distribution_message_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/available_data_fetching.go b/dot/parachain/available_data_fetching.go similarity index 98% rename from lib/parachain/available_data_fetching.go rename to dot/parachain/available_data_fetching.go index 83e9bf71df..8e34648ded 100644 --- a/lib/parachain/available_data_fetching.go +++ b/dot/parachain/available_data_fetching.go @@ -6,8 +6,8 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/available_data_fetching_test.go b/dot/parachain/available_data_fetching_test.go similarity index 97% rename from lib/parachain/available_data_fetching_test.go rename to dot/parachain/available_data_fetching_test.go index 73bdfb5877..72bce779cb 100644 --- a/lib/parachain/available_data_fetching_test.go +++ b/dot/parachain/available_data_fetching_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/candidate_validation.go b/dot/parachain/candidate_validation.go similarity index 97% rename from lib/parachain/candidate_validation.go rename to dot/parachain/candidate_validation.go index e3aeae0213..fff3f7938f 100644 --- a/lib/parachain/candidate_validation.go +++ b/dot/parachain/candidate_validation.go @@ -8,9 +8,9 @@ import ( "errors" "fmt" + parachainruntime "github.com/ChainSafe/gossamer/dot/parachain/runtime" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachainruntime "github.com/ChainSafe/gossamer/lib/parachain/runtime" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/candidate_validation_test.go b/dot/parachain/candidate_validation_test.go similarity index 98% rename from lib/parachain/candidate_validation_test.go rename to dot/parachain/candidate_validation_test.go index 007fef7969..004a48a530 100644 --- a/lib/parachain/candidate_validation_test.go +++ b/dot/parachain/candidate_validation_test.go @@ -7,9 +7,9 @@ import ( "os" "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/sr25519" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" diff --git a/lib/parachain/chunk_fetching.go b/dot/parachain/chunk_fetching.go similarity index 97% rename from lib/parachain/chunk_fetching.go rename to dot/parachain/chunk_fetching.go index c012dcbce1..a6ef9eea21 100644 --- a/lib/parachain/chunk_fetching.go +++ b/dot/parachain/chunk_fetching.go @@ -6,7 +6,7 @@ package parachain import ( "fmt" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/chunk_fetching_test.go b/dot/parachain/chunk_fetching_test.go similarity index 97% rename from lib/parachain/chunk_fetching_test.go rename to dot/parachain/chunk_fetching_test.go index 7cc73df3d3..5fcbcc04d2 100644 --- a/lib/parachain/chunk_fetching_test.go +++ b/dot/parachain/chunk_fetching_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/collation_fetching.go b/dot/parachain/collation_fetching.go similarity index 97% rename from lib/parachain/collation_fetching.go rename to dot/parachain/collation_fetching.go index 7a9e5c61c6..d46e655934 100644 --- a/lib/parachain/collation_fetching.go +++ b/dot/parachain/collation_fetching.go @@ -6,8 +6,8 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/collation_fetching_test.go b/dot/parachain/collation_fetching_test.go similarity index 98% rename from lib/parachain/collation_fetching_test.go rename to dot/parachain/collation_fetching_test.go index 649fb495d6..148d7ae93b 100644 --- a/lib/parachain/collation_fetching_test.go +++ b/dot/parachain/collation_fetching_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/collation_protocol.go b/dot/parachain/collation_protocol.go similarity index 98% rename from lib/parachain/collation_protocol.go rename to dot/parachain/collation_protocol.go index 53790eb7ac..4978327fe9 100644 --- a/lib/parachain/collation_protocol.go +++ b/dot/parachain/collation_protocol.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/libp2p/go-libp2p/core/peer" ) diff --git a/lib/parachain/collation_protocol_test.go b/dot/parachain/collation_protocol_test.go similarity index 98% rename from lib/parachain/collation_protocol_test.go rename to dot/parachain/collation_protocol_test.go index 8bca1fbac2..84dbfbd974 100644 --- a/lib/parachain/collation_protocol_test.go +++ b/dot/parachain/collation_protocol_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" ) //go:embed testdata/collation_protocol.yaml diff --git a/lib/parachain/mocks_generate_test.go b/dot/parachain/mocks_generate_test.go similarity index 50% rename from lib/parachain/mocks_generate_test.go rename to dot/parachain/mocks_generate_test.go index 8eb3c600ed..67e0effb6f 100644 --- a/lib/parachain/mocks_generate_test.go +++ b/dot/parachain/mocks_generate_test.go @@ -3,4 +3,5 @@ package parachain -//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . RuntimeInstance,PoVRequestor +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . PoVRequestor +//go:generate mockgen -destination=mocks_runtime_test.go -package $GOPACKAGE github.com/ChainSafe/gossamer/dot/parachain/runtime RuntimeInstance diff --git a/lib/parachain/mocks_test.go b/dot/parachain/mocks_runtime_test.go similarity index 63% rename from lib/parachain/mocks_test.go rename to dot/parachain/mocks_runtime_test.go index 6d9ce3b694..b5451e90d4 100644 --- a/lib/parachain/mocks_test.go +++ b/dot/parachain/mocks_runtime_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ChainSafe/gossamer/lib/parachain (interfaces: RuntimeInstance,PoVRequestor) +// Source: github.com/ChainSafe/gossamer/dot/parachain/runtime (interfaces: RuntimeInstance) // Package parachain is a generated GoMock package. package parachain @@ -7,8 +7,7 @@ package parachain import ( reflect "reflect" - common "github.com/ChainSafe/gossamer/lib/common" - types "github.com/ChainSafe/gossamer/lib/parachain/types" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" gomock "github.com/golang/mock/gomock" ) @@ -36,7 +35,7 @@ func (m *MockRuntimeInstance) EXPECT() *MockRuntimeInstanceMockRecorder { } // ParachainHostCheckValidationOutputs mocks base method. -func (m *MockRuntimeInstance) ParachainHostCheckValidationOutputs(arg0 uint32, arg1 types.CandidateCommitments) (bool, error) { +func (m *MockRuntimeInstance) ParachainHostCheckValidationOutputs(arg0 uint32, arg1 parachaintypes.CandidateCommitments) (bool, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostCheckValidationOutputs", arg0, arg1) ret0, _ := ret[0].(bool) @@ -51,10 +50,10 @@ func (mr *MockRuntimeInstanceMockRecorder) ParachainHostCheckValidationOutputs(a } // ParachainHostPersistedValidationData mocks base method. -func (m *MockRuntimeInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.PersistedValidationData, error) { +func (m *MockRuntimeInstance) ParachainHostPersistedValidationData(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.PersistedValidationData, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostPersistedValidationData", arg0, arg1) - ret0, _ := ret[0].(*types.PersistedValidationData) + ret0, _ := ret[0].(*parachaintypes.PersistedValidationData) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -66,10 +65,10 @@ func (mr *MockRuntimeInstanceMockRecorder) ParachainHostPersistedValidationData( } // ParachainHostValidationCode mocks base method. -func (m *MockRuntimeInstance) ParachainHostValidationCode(arg0 uint32, arg1 types.OccupiedCoreAssumption) (*types.ValidationCode, error) { +func (m *MockRuntimeInstance) ParachainHostValidationCode(arg0 uint32, arg1 parachaintypes.OccupiedCoreAssumption) (*parachaintypes.ValidationCode, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ParachainHostValidationCode", arg0, arg1) - ret0, _ := ret[0].(*types.ValidationCode) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -79,40 +78,3 @@ func (mr *MockRuntimeInstanceMockRecorder) ParachainHostValidationCode(arg0, arg mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockRuntimeInstance)(nil).ParachainHostValidationCode), arg0, arg1) } - -// MockPoVRequestor is a mock of PoVRequestor interface. -type MockPoVRequestor struct { - ctrl *gomock.Controller - recorder *MockPoVRequestorMockRecorder -} - -// MockPoVRequestorMockRecorder is the mock recorder for MockPoVRequestor. -type MockPoVRequestorMockRecorder struct { - mock *MockPoVRequestor -} - -// NewMockPoVRequestor creates a new mock instance. -func NewMockPoVRequestor(ctrl *gomock.Controller) *MockPoVRequestor { - mock := &MockPoVRequestor{ctrl: ctrl} - mock.recorder = &MockPoVRequestorMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockPoVRequestor) EXPECT() *MockPoVRequestorMockRecorder { - return m.recorder -} - -// RequestPoV mocks base method. -func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) PoV { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RequestPoV", arg0) - ret0, _ := ret[0].(PoV) - return ret0 -} - -// RequestPoV indicates an expected call of RequestPoV. -func (mr *MockPoVRequestorMockRecorder) RequestPoV(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestPoV", reflect.TypeOf((*MockPoVRequestor)(nil).RequestPoV), arg0) -} diff --git a/dot/parachain/mocks_test.go b/dot/parachain/mocks_test.go new file mode 100644 index 0000000000..022531504b --- /dev/null +++ b/dot/parachain/mocks_test.go @@ -0,0 +1,49 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain (interfaces: PoVRequestor) + +// Package parachain is a generated GoMock package. +package parachain + +import ( + reflect "reflect" + + common "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" +) + +// MockPoVRequestor is a mock of PoVRequestor interface. +type MockPoVRequestor struct { + ctrl *gomock.Controller + recorder *MockPoVRequestorMockRecorder +} + +// MockPoVRequestorMockRecorder is the mock recorder for MockPoVRequestor. +type MockPoVRequestorMockRecorder struct { + mock *MockPoVRequestor +} + +// NewMockPoVRequestor creates a new mock instance. +func NewMockPoVRequestor(ctrl *gomock.Controller) *MockPoVRequestor { + mock := &MockPoVRequestor{ctrl: ctrl} + mock.recorder = &MockPoVRequestorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPoVRequestor) EXPECT() *MockPoVRequestorMockRecorder { + return m.recorder +} + +// RequestPoV mocks base method. +func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) PoV { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RequestPoV", arg0) + ret0, _ := ret[0].(PoV) + return ret0 +} + +// RequestPoV indicates an expected call of RequestPoV. +func (mr *MockPoVRequestorMockRecorder) RequestPoV(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequestPoV", reflect.TypeOf((*MockPoVRequestor)(nil).RequestPoV), arg0) +} diff --git a/lib/parachain/network_protocols.go b/dot/parachain/network_protocols.go similarity index 100% rename from lib/parachain/network_protocols.go rename to dot/parachain/network_protocols.go diff --git a/lib/parachain/pov_fetching.go b/dot/parachain/pov_fetching.go similarity index 100% rename from lib/parachain/pov_fetching.go rename to dot/parachain/pov_fetching.go diff --git a/lib/parachain/pov_fetching_test.go b/dot/parachain/pov_fetching_test.go similarity index 100% rename from lib/parachain/pov_fetching_test.go rename to dot/parachain/pov_fetching_test.go diff --git a/lib/parachain/runtime/instance.go b/dot/parachain/runtime/instance.go similarity index 98% rename from lib/parachain/runtime/instance.go rename to dot/parachain/runtime/instance.go index 017431cd81..ea15cb4e2e 100644 --- a/lib/parachain/runtime/instance.go +++ b/dot/parachain/runtime/instance.go @@ -8,8 +8,8 @@ import ( "errors" "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wasmer" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/service.go b/dot/parachain/service.go similarity index 100% rename from lib/parachain/service.go rename to dot/parachain/service.go diff --git a/lib/parachain/statement.go b/dot/parachain/statement.go similarity index 96% rename from lib/parachain/statement.go rename to dot/parachain/statement.go index 4b9551675c..c5e2633790 100644 --- a/lib/parachain/statement.go +++ b/dot/parachain/statement.go @@ -6,8 +6,8 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/statement_distribution_message.go b/dot/parachain/statement_distribution_message.go similarity index 98% rename from lib/parachain/statement_distribution_message.go rename to dot/parachain/statement_distribution_message.go index f75bbf7fd7..9e0f3770a7 100644 --- a/lib/parachain/statement_distribution_message.go +++ b/dot/parachain/statement_distribution_message.go @@ -6,8 +6,8 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/statement_distribution_message_test.go b/dot/parachain/statement_distribution_message_test.go similarity index 99% rename from lib/parachain/statement_distribution_message_test.go rename to dot/parachain/statement_distribution_message_test.go index be069b7065..9a13bace7e 100644 --- a/lib/parachain/statement_distribution_message_test.go +++ b/dot/parachain/statement_distribution_message_test.go @@ -8,8 +8,8 @@ import ( "fmt" "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" diff --git a/lib/parachain/statement_fetching.go b/dot/parachain/statement_fetching.go similarity index 97% rename from lib/parachain/statement_fetching.go rename to dot/parachain/statement_fetching.go index 2aac3bf689..f78537be84 100644 --- a/lib/parachain/statement_fetching.go +++ b/dot/parachain/statement_fetching.go @@ -6,8 +6,8 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/parachain/statement_fetching_test.go b/dot/parachain/statement_fetching_test.go similarity index 98% rename from lib/parachain/statement_fetching_test.go rename to dot/parachain/statement_fetching_test.go index eadcd59b19..b2a2b48af5 100644 --- a/lib/parachain/statement_fetching_test.go +++ b/dot/parachain/statement_fetching_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/statement_test.go b/dot/parachain/statement_test.go similarity index 97% rename from lib/parachain/statement_test.go rename to dot/parachain/statement_test.go index 73c97b10c7..088fb11fd8 100644 --- a/lib/parachain/statement_test.go +++ b/dot/parachain/statement_test.go @@ -6,8 +6,8 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) diff --git a/lib/parachain/testdata/collation_protocol.yaml b/dot/parachain/testdata/collation_protocol.yaml similarity index 100% rename from lib/parachain/testdata/collation_protocol.yaml rename to dot/parachain/testdata/collation_protocol.yaml diff --git a/lib/parachain/testdata/statement.yaml b/dot/parachain/testdata/statement.yaml similarity index 100% rename from lib/parachain/testdata/statement.yaml rename to dot/parachain/testdata/statement.yaml diff --git a/lib/parachain/testdata/test_parachain_adder.wasm b/dot/parachain/testdata/test_parachain_adder.wasm similarity index 100% rename from lib/parachain/testdata/test_parachain_adder.wasm rename to dot/parachain/testdata/test_parachain_adder.wasm diff --git a/lib/parachain/types/testdata/westend.yaml b/dot/parachain/types/testdata/westend.yaml similarity index 100% rename from lib/parachain/types/testdata/westend.yaml rename to dot/parachain/types/testdata/westend.yaml diff --git a/lib/parachain/types/types.go b/dot/parachain/types/types.go similarity index 100% rename from lib/parachain/types/types.go rename to dot/parachain/types/types.go diff --git a/lib/parachain/types/types_test.go b/dot/parachain/types/types_test.go similarity index 100% rename from lib/parachain/types/types_test.go rename to dot/parachain/types/types_test.go diff --git a/lib/parachain/validation_protocol.go b/dot/parachain/validation_protocol.go similarity index 100% rename from lib/parachain/validation_protocol.go rename to dot/parachain/validation_protocol.go diff --git a/lib/parachain/validation_protocol_test.go b/dot/parachain/validation_protocol_test.go similarity index 100% rename from lib/parachain/validation_protocol_test.go rename to dot/parachain/validation_protocol_test.go diff --git a/dot/services.go b/dot/services.go index 6dafa9ee2f..171f81b9b6 100644 --- a/dot/services.go +++ b/dot/services.go @@ -14,6 +14,7 @@ import ( "github.com/ChainSafe/gossamer/dot/core" "github.com/ChainSafe/gossamer/dot/digest" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain" "github.com/ChainSafe/gossamer/dot/rpc" "github.com/ChainSafe/gossamer/dot/rpc/modules" "github.com/ChainSafe/gossamer/dot/state" @@ -31,7 +32,6 @@ import ( "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/lib/grandpa" "github.com/ChainSafe/gossamer/lib/keystore" - "github.com/ChainSafe/gossamer/lib/parachain" "github.com/ChainSafe/gossamer/lib/runtime" wazero_runtime "github.com/ChainSafe/gossamer/lib/runtime/wazero" ) diff --git a/dot/state/mocks_runtime_test.go b/dot/state/mocks_runtime_test.go index 92c3e3e7ab..dec71be5cb 100644 --- a/dot/state/mocks_runtime_test.go +++ b/dot/state/mocks_runtime_test.go @@ -7,11 +7,11 @@ package state import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index 7ecc9d8dfe..bd8b74f3f1 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -7,11 +7,11 @@ package sync import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 03568f2dbd..1f6c7d242b 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -7,11 +7,11 @@ package mocks import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index e76671b7f6..7820deb958 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -7,11 +7,11 @@ package blocktree import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go index a28960eca4..8b5abb7d9b 100644 --- a/lib/grandpa/mocks_runtime_test.go +++ b/lib/grandpa/mocks_runtime_test.go @@ -7,11 +7,11 @@ package grandpa import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index b0d7de5a78..3d387ab054 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -4,11 +4,11 @@ package runtime import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/lib/transaction" "github.com/ChainSafe/gossamer/pkg/scale" ) diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index 5b9843ce26..d36c619641 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -7,11 +7,11 @@ package mocks import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" types "github.com/ChainSafe/gossamer/dot/types" common "github.com/ChainSafe/gossamer/lib/common" ed25519 "github.com/ChainSafe/gossamer/lib/crypto/ed25519" keystore "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" runtime "github.com/ChainSafe/gossamer/lib/runtime" transaction "github.com/ChainSafe/gossamer/lib/transaction" scale "github.com/ChainSafe/gossamer/pkg/scale" diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index 3f92f6d3f1..8371d4e969 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -10,13 +10,13 @@ import ( "fmt" "sync" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/lib/crypto" "github.com/ChainSafe/gossamer/lib/crypto/ed25519" "github.com/ChainSafe/gossamer/lib/keystore" - parachaintypes "github.com/ChainSafe/gossamer/lib/parachain/types" "github.com/ChainSafe/gossamer/lib/runtime" "github.com/ChainSafe/gossamer/lib/runtime/offchain" "github.com/ChainSafe/gossamer/lib/transaction" From d57a322e99ad3348865dd22cd36fe3e7bb07ba71 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Fri, 18 Aug 2023 10:11:45 -0400 Subject: [PATCH 23/85] feat(lib/parachain): Create and integrate Validation Protocol struct (#3382) Co-authored-by: Kanishka Co-authored-by: Kishan Mohanbhai Sagathiya Co-authored-by: Axay Sagathiya --- .../approval_distribution_message_test.go | 12 +- dot/parachain/service.go | 20 +- .../statement_distribution_message.yaml | 0 .../testdata/validation_protocol.yaml | 13 + dot/parachain/validation_protocol.go | 131 +++++++- dot/parachain/validation_protocol_test.go | 303 ++++++++++++++++++ lib/runtime/wazero/instance.go | 2 +- 7 files changed, 467 insertions(+), 14 deletions(-) create mode 100644 dot/parachain/testdata/statement_distribution_message.yaml create mode 100644 dot/parachain/testdata/validation_protocol.yaml diff --git a/dot/parachain/approval_distribution_message_test.go b/dot/parachain/approval_distribution_message_test.go index 5a05613915..e4d9982d4a 100644 --- a/dot/parachain/approval_distribution_message_test.go +++ b/dot/parachain/approval_distribution_message_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -var hash = common.MustHexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") +var hashA = common.MustHexToHash("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") func TestEncodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { approvalDistributionMessage := NewApprovalDistributionMessageVDT() @@ -37,7 +37,7 @@ func TestEncodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { approvalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(1), false), + IndirectAssignmentCert: fakeAssignmentCert(hashA, parachaintypes.ValidatorIndex(1), false), CandidateIndex: 4, }, }) @@ -64,7 +64,7 @@ func TestEncodeApprovalDistributionMessageAssignmentDelay(t *testing.T) { approvalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(2), true), + IndirectAssignmentCert: fakeAssignmentCert(hashA, parachaintypes.ValidatorIndex(2), true), CandidateIndex: 2, }, }) @@ -113,7 +113,7 @@ func TestEncodeApprovalDistributionMessageApprovals(t *testing.T) { approvalDistributionMessage.Set(Approvals{ IndirectSignedApprovalVote{ - BlockHash: hash, + BlockHash: hashA, CandidateIndex: CandidateIndex(2), ValidatorIndex: parachaintypes.ValidatorIndex(3), Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -141,7 +141,7 @@ func TestDecodeApprovalDistributionMessageAssignmentModulo(t *testing.T) { expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() expectedApprovalDistributionMessage.Set(Assignments{ Assignment{ - IndirectAssignmentCert: fakeAssignmentCert(hash, parachaintypes.ValidatorIndex(2), false), + IndirectAssignmentCert: fakeAssignmentCert(hashA, parachaintypes.ValidatorIndex(2), false), CandidateIndex: 4, }, }) @@ -161,7 +161,7 @@ func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { expectedApprovalDistributionMessage := NewApprovalDistributionMessageVDT() expectedApprovalDistributionMessage.Set(Approvals{ IndirectSignedApprovalVote{ - BlockHash: hash, + BlockHash: hashA, CandidateIndex: CandidateIndex(2), ValidatorIndex: parachaintypes.ValidatorIndex(3), Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, diff --git a/dot/parachain/service.go b/dot/parachain/service.go index 9fb2596209..dfa5294d3a 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/protocol" @@ -22,6 +23,8 @@ type Service struct { Network Network } +var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) + func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, error) { validationProtocolID := GeneratePeersetProtocolName( ValidationProtocolName, forkID, genesisHash, ValidationProtocolVersion) @@ -120,7 +123,22 @@ func (s Service) run() { collationMessage := CollationProtocolV1{} s.Network.GossipMessage(&collationMessage) - validationMessage := ValidationProtocolV1{} + statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} + err := statementDistributionLargeStatement.Set(SecondedStatementWithLargePayload{ + RelayParent: common.Hash{}, + CandidateHash: CandidateHash{Value: common.Hash{}}, + SignedBy: 5, + Signature: ValidatorSignature{}, + }) + if err != nil { + logger.Errorf("creating test statement message: %w\n", err) + } + + validationMessage := NewValidationProtocolVDT() + err = validationMessage.Set(statementDistributionLargeStatement) + if err != nil { + logger.Errorf("creating test validation message: %w\n", err) + } s.Network.GossipMessage(&validationMessage) } diff --git a/dot/parachain/testdata/statement_distribution_message.yaml b/dot/parachain/testdata/statement_distribution_message.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/dot/parachain/testdata/validation_protocol.yaml b/dot/parachain/testdata/validation_protocol.yaml new file mode 100644 index 0000000000..224e4181f7 --- /dev/null +++ b/dot/parachain/testdata/validation_protocol.yaml @@ -0,0 +1,13 @@ +validatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +collatorID : "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d" + +approvalDistributionMessageAssignments: "0x040004aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0100000000020000000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f4004000000" + +approvalDistributionMessageApprovals: "0x040104aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0a0000000b000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +statementDistributionMessageLargeStatement: "0x0301aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa05000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +statementDistributionMessageStatement: "0x0300aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0101000000aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27daaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +bitfieldDistribution: "0x0100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa80ffffffff00000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" \ No newline at end of file diff --git a/dot/parachain/validation_protocol.go b/dot/parachain/validation_protocol.go index 8c390289c2..4dc800dd79 100644 --- a/dot/parachain/validation_protocol.go +++ b/dot/parachain/validation_protocol.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/libp2p/go-libp2p/core/peer" @@ -14,17 +15,135 @@ import ( const MaxValidationMessageSize uint64 = 100 * 1024 -type ValidationProtocolV1 struct { - // TODO: Implement this struct https://github.com/ChainSafe/gossamer/issues/3318 +// UncheckedSignedAvailabilityBitfield a signed bitfield with signature not yet checked +type UncheckedSignedAvailabilityBitfield struct { + // The payload is part of the signed data. The rest is the signing context, + // which is known both at signing and at validation. + Payload scale.BitVec `scale:"1"` + + // The index of the validator signing this statement. + ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` + + // The signature by the validator of the signed payload. + Signature ValidatorSignature `scale:"3"` +} + +// Bitfield avalibility bitfield for given relay-parent hash +type Bitfield struct { + Hash common.Hash `scale:"1"` + UncheckedSignedAvailabilityBitfield UncheckedSignedAvailabilityBitfield `scale:"2"` +} + +// Index returns the VaryingDataType Index +func (Bitfield) Index() uint { + return 0 +} + +// BitfieldDistributionMessage Network messages used by bitfield distribution subsystem +type BitfieldDistributionMessage scale.VaryingDataType + +// NewBitfieldDistributionMessageVDT returns a new BitfieldDistributionMessage VaryingDataType +func NewBitfieldDistributionMessageVDT() BitfieldDistributionMessage { + vdt := scale.MustNewVaryingDataType(Bitfield{}) + return BitfieldDistributionMessage(vdt) +} + +// New creates new BitfieldDistributionMessage +func (BitfieldDistributionMessage) New() BitfieldDistributionMessage { + return NewBitfieldDistributionMessageVDT() +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (bdm *BitfieldDistributionMessage) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*bdm) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *bdm = BitfieldDistributionMessage(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (bdm *BitfieldDistributionMessage) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*bdm) + return vdt.Value() +} + +// BitfieldDistribution struct holding BitfieldDistributionMessage +type BitfieldDistribution struct { + BitfieldDistributionMessage +} + +// Index VaryingDataType index of Bitfield Distribution +func (BitfieldDistribution) Index() uint { + return 1 +} + +// ApprovalDistribution struct holding ApprovalDistributionMessage +type ApprovalDistribution struct { + ApprovalDistributionMessage +} + +// Index VaryingDataType index of ApprovalDistribution +func (ApprovalDistribution) Index() uint { + return 4 +} + +// StatementDistribution struct holding StatementDistributionMessage +type StatementDistribution struct { + StatementDistributionMessage +} + +// Index VaryingDataType index for StatementDistribution +func (StatementDistribution) Index() uint { + return 3 +} + +// ValidationProtocol VaryingDataType for ValidationProtocol +type ValidationProtocol scale.VaryingDataType + +// NewValidationProtocolVDT constructor or ValidationProtocol VaryingDataType +func NewValidationProtocolVDT() ValidationProtocol { + vdt, err := scale.NewVaryingDataType(BitfieldDistribution{}, StatementDistribution{}, ApprovalDistribution{}) + if err != nil { + panic(err) + } + return ValidationProtocol(vdt) +} + +// New returns new ValidationProtocol VDT +func (ValidationProtocol) New() ValidationProtocol { + return NewValidationProtocolVDT() +} + +// Value returns the value from the underlying VaryingDataType +func (vp *ValidationProtocol) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*vp) + return vdt.Value() +} + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (vp *ValidationProtocol) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*vp) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *vp = ValidationProtocol(vdt) + return nil + } // Type returns ValidationMsgType -func (*ValidationProtocolV1) Type() network.MessageType { +func (*ValidationProtocol) Type() network.MessageType { return network.ValidationMsgType } // Hash returns the hash of the CollationProtocolV1 -func (vp *ValidationProtocolV1) Hash() (common.Hash, error) { +func (vp *ValidationProtocol) Hash() (common.Hash, error) { encMsg, err := vp.Encode() if err != nil { return common.Hash{}, fmt.Errorf("cannot encode message: %w", err) @@ -34,7 +153,7 @@ func (vp *ValidationProtocolV1) Hash() (common.Hash, error) { } // Encode a collator protocol message using scale encode -func (vp *ValidationProtocolV1) Encode() ([]byte, error) { +func (vp *ValidationProtocol) Encode() ([]byte, error) { enc, err := scale.Marshal(*vp) if err != nil { return enc, err @@ -43,7 +162,7 @@ func (vp *ValidationProtocolV1) Encode() ([]byte, error) { } func decodeValidationMessage(in []byte) (network.NotificationsMessage, error) { - validationMessage := ValidationProtocolV1{} + validationMessage := ValidationProtocol{} err := scale.Unmarshal(in, &validationMessage) if err != nil { diff --git a/dot/parachain/validation_protocol_test.go b/dot/parachain/validation_protocol_test.go index b61928825b..0a958bfdb2 100644 --- a/dot/parachain/validation_protocol_test.go +++ b/dot/parachain/validation_protocol_test.go @@ -4,11 +4,314 @@ package parachain import ( + _ "embed" + "fmt" "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +//go:embed testdata/validation_protocol.yaml + +var testValidationProtocolHexRaw string + +var testValidationProtocolHex map[string]string + +func init() { + err := yaml.Unmarshal([]byte(testValidationProtocolHexRaw), &testValidationProtocolHex) + if err != nil { + fmt.Printf("Error unmarshaling test data: %s\n", err) + return + } +} + +func TestMarshalUnMarshalValidationProtocol(t *testing.T) { + t.Parallel() + /* ValidationProtocol with ApprovalDistribution with Assignments Rust code + fn try_msg_assignments_encode() { + let hash = Hash::repeat_byte(0xAA); + let validator_index = ValidatorIndex(1); + let cert = fake_assignment_cert(hash, validator_index); + let assignments = vec![(cert.clone(), 4u32)]; + let msg = protocol_v1::ApprovalDistributionMessage::Assignments(assignments.clone()); + let val_proto = protocol_v1::ValidationProtocol::ApprovalDistribution(msg.clone()); + println!("encode validation proto => {:?}\n\n", val_proto.encode()); + } + */ + approvalDistribution := ApprovalDistribution{NewApprovalDistributionMessageVDT()} + approvalDistribution.Set(Assignments{ + Assignment{ + IndirectAssignmentCert: fakeAssignmentCert(hashA, parachaintypes.ValidatorIndex(1), false), + CandidateIndex: 4, + }, + }) + vpApprovalDistributionAssignments := NewValidationProtocolVDT() + vpApprovalDistributionAssignments.Set(approvalDistribution) + vpApprovalDistributionAssignmentsValue, err := vpApprovalDistributionAssignments.Value() + require.NoError(t, err) + + /* ValidationProtocol with ApprovalDistribution with Approvals rust code: + fn try_msg_approvals_encode() { + let hash = Hash::repeat_byte(0xAA); + let candidate_index = 0u32; + let validator_index = ValidatorIndex(0); + let approval = IndirectSignedApprovalVote { + block_hash: hash, + candidate_index, + validator: validator_index, + signature: dummy_signature(), + }; + let msg = protocol_v1::ApprovalDistributionMessage::Approvals(vec![approval.clone()]); + let val_proto = protocol_v1::ValidationProtocol::ApprovalDistribution(msg.clone()); + println!("encode validation proto => {:?}\n\n", val_proto.encode()); + } + */ + var validatorSignature ValidatorSignature + tempSignature := common.MustHexToBytes(testValidationProtocolHex["validatorSignature"]) + copy(validatorSignature[:], tempSignature) + + approvalDistributionApprovals := ApprovalDistribution{NewApprovalDistributionMessageVDT()} + approvalDistributionApprovals.Set(Approvals{ + IndirectSignedApprovalVote{ + BlockHash: hashA, + CandidateIndex: 10, + ValidatorIndex: 11, + Signature: validatorSignature, + }, + }) + + vpApprovalDistributionApprovals := NewValidationProtocolVDT() + vpApprovalDistributionApprovals.Set(approvalDistributionApprovals) + vpApprovalDistributionApprovalsValue, err := vpApprovalDistributionApprovals.Value() + require.NoError(t, err) + + /* ValidationProtocol with StatementDistribution with Statement rust code: + fn try_validation_protocol_statement_distribution_full_statement() { + let hash1 = Hash::repeat_byte(170); + let val_sign = ValidatorSignature::from( + Signature([198, 124, 185, 59, 240, 163, 111, 206, 227, 210, 157, 232, 166, 166, 154, 117, 150, 89, + 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, 216, 126, 69, 173, 206, 95, 41, 6, 152, + 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, 81, 175, 134, 23, 200, 190, 116, 184, + 148, 207, 27, 134])); + let keystore: KeystorePtr = Arc::new(LocalKeystore::in_memory()); + let collator_result = Keystore::sr25519_generate_new( + &*keystore, + ValidatorId::ID, + Some(&Sr25519Keyring::Alice.to_seed()), + ); + let collator = collator_result.unwrap(); + let collsign = CollatorSignature::from(Signature([198, 124, 185, 59, 240, 163, 111, 206, 227, 210, 157, 232, + 166, 166, 154, 117, 150, 89, 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, 216, 126, 69, 173, 206, + 95, 41, 6, 152, 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, 81, 175, 134, 23, 200, 190, 116, + 184, 148, 207, 27, 134])); + let candidate_descriptor = CandidateDescriptor{ + para_id: 1.into(), + relay_parent: hash1, + collator: polkadot_primitives::CollatorId::from(collator), + persisted_validation_data_hash: hash1, + pov_hash: hash1, + erasure_root: hash1, + signature: collsign, + para_head: hash1, + validation_code_hash: ValidationCodeHash::from(hash1) + }; + let commitments_new = CandidateCommitments{ + upward_messages: vec![vec![1, 2, 3]].try_into().expect("error - upward_messages"), + horizontal_messages: vec![].try_into().expect("error - horizontal_messages"), + head_data: HeadData(vec![1, 2, 3]), + hrmp_watermark: 0_u32, + new_validation_code: ValidationCode(vec![1, 2, 3]).try_into().expect("error - new_validation_code"), + processed_downward_messages: 5 + }; + let committed_candidate_receipt = CommittedCandidateReceipt { + descriptor: candidate_descriptor, commitments: commitments_new }; + let statement_second = Statement::Seconded(committed_candidate_receipt); + let unchecked_signed_full_statement_second = UncheckedSignedFullStatement::new( + statement_second, ValidatorIndex(5), val_sign.clone()); + let sdm_statement_second = protocol_v1::StatementDistributionMessage::Statement(hash1, + unchecked_signed_full_statement_second); + let validation_sdm_statement = protocol_v1::ValidationProtocol::StatementDistribution(sdm_statement_second); + println!("encode validation SecondedStatement => {:?}\n\n", validation_sdm_statement.encode()); + + } + */ + var collatorID parachaintypes.CollatorID + tempID := common.MustHexToBytes(testValidationProtocolHex["collatorID"]) + copy(collatorID[:], tempID) + var collatorSignature parachaintypes.CollatorSignature + copy(collatorSignature[:], tempSignature) + + statementSecond := Seconded{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: 1, + RelayParent: hashA, + Collator: collatorID, + PersistedValidationDataHash: hashA, + PovHash: hashA, + ErasureRoot: hashA, + Signature: collatorSignature, + ParaHead: hashA, + ValidationCodeHash: parachaintypes.ValidationCodeHash(hashA), + }, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{[]byte{1, 2, 3}}, + HorizontalMessages: nil, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{Data: []byte{1, 2, 3}}, + ProcessedDownwardMessages: 5, + HrmpWatermark: 0, + }, + } + statement := NewStatement() + statement.Set(statementSecond) + + statementDistributionStatement := StatementDistribution{NewStatementDistributionMessage()} + statementDistributionStatement.Set(SignedFullStatement{ + Hash: hashA, + UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + Payload: statement, + ValidatorIndex: 5, + Signature: validatorSignature, + }, + }) + + vpStatementDistributionStatement := NewValidationProtocolVDT() + vpStatementDistributionStatement.Set(statementDistributionStatement) + vpStatementDistributionStatementValue, err := vpStatementDistributionStatement.Value() + require.NoError(t, err) + + /* ValidationProtocol with StatementDistribution with Large Statement rust code + fn try_validation_protocol_statement_distribution() { + let hash1 = Hash::repeat_byte(170); + let val_sign = ValidatorSignature::from( + Signature([198, 124, 185, 59, 240, 163, 111, 206, 227, 210, 157, 232, 166, 166, 154, 117, 150, 89, + 104, 10, 207, 72, 100, 117, 224, 162, 85, 42, 95, 190, 216, 126, 69, 173, 206, 95, 41, 6, 152, + 216, 89, 96, 149, 114, 43, 51, 89, 146, 39, 247, 70, 31, 81, 175, 134, 23, 200, 190, 116, 184, + 148, 207, 27, 134])); + let sdm_large_statement = protocol_v1::StatementDistributionMessage::LargeStatement(StatementMetadata{ + relay_parent: hash1, + candidate_hash: CandidateHash(hash1), + signed_by: ValidatorIndex(5_u32), + signature: val_sign.clone(), + }); + let validation_sdm_large_statement = protocol_v1::ValidationProtocol::StatementDistribution(sdm_large_statement); + println!("encode validation SecondedStatementWithLargePayload => {:?}\n\n", validation_sdm_large_statement.encode()); + } + */ + statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} + statementDistributionLargeStatement.Set(SecondedStatementWithLargePayload{ + RelayParent: hashA, + CandidateHash: CandidateHash{Value: hashA}, + SignedBy: 5, + Signature: validatorSignature, + }) + + vpStatementDistributionLargeStatement := NewValidationProtocolVDT() + vpStatementDistributionLargeStatement.Set(statementDistributionLargeStatement) + vpStatementDistributionLargeStatementValue, err := vpStatementDistributionLargeStatement.Value() + require.NoError(t, err) + + /* ValidationProtocol with BitfieldDistribution rust code + fn try_validation_protocol_bitfield_distribution_a() { + let hash_a :Hash = [170; 32].into(); + let keystore: KeystorePtr = Arc::new(MemoryKeystore::new()); + let payload = AvailabilityBitfield(bitvec![u8, bitvec::order::Lsb0; 1u8; 32]); + let signing_context = SigningContext { session_index: 1, parent_hash: hash_a.clone() }; + let validator_0 = + Keystore::sr25519_generate_new(&*keystore, ValidatorId::ID, None).expect("key created"); + let valid_signed = Signed::::sign( + &keystore, + payload, + &signing_context, + ValidatorIndex(0), + &validator_0.into(), + ) + .ok() + .flatten() + .expect("should be signed"); + let bitfield_distribition_message = protocol_v1::BitfieldDistributionMessage::Bitfield( + hash_a, + valid_signed.into(), + ); + let val_proto = ValidationProtocol::BitfieldDistribution(bitfield_distribition_message.clone()); + println!("encode validation proto => {:?}\n\n", val_proto.encode()); + } + */ + bitfieldDistribution := BitfieldDistribution{NewBitfieldDistributionMessageVDT()} + bitfieldDistribution.Set(Bitfield{ + Hash: hashA, + UncheckedSignedAvailabilityBitfield: UncheckedSignedAvailabilityBitfield{ + Payload: scale.NewBitVec([]bool{true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true}), + ValidatorIndex: 0, + Signature: validatorSignature, + }, + }) + + vpBitfieldDistribution := NewValidationProtocolVDT() + vpBitfieldDistribution.Set(bitfieldDistribution) + vpBitfieldDistributionVal, err := vpBitfieldDistribution.Value() + require.NoError(t, err) + + testCases := map[string]struct { + enumValue scale.VaryingDataTypeValue + encodingValue []byte + }{ + "ValidationProtocol_with_ApprovalDistribution_with_Assignments": { + enumValue: vpApprovalDistributionAssignmentsValue, + encodingValue: common.MustHexToBytes(testValidationProtocolHex["approvalDistributionMessageAssignments"]), + }, + "ValidationProtocol_with_ApprovalDistribution_with_Approvals": { + enumValue: vpApprovalDistributionApprovalsValue, + encodingValue: common.MustHexToBytes(testValidationProtocolHex["approvalDistributionMessageApprovals"]), + }, + "ValidationProtocol_with_StatementDistribution_with_Statement": { + enumValue: vpStatementDistributionStatementValue, + encodingValue: common.MustHexToBytes(testValidationProtocolHex["statementDistributionMessageStatement"]), + }, + "ValidationProtocol_with_StatementDistribution_with_Large_Statement": { + enumValue: vpStatementDistributionLargeStatementValue, + encodingValue: common.MustHexToBytes(testValidationProtocolHex["statementDistributionMessageLargeStatement"]), + }, + "ValidationProtocol_with_BitfieldDistribution": { + enumValue: vpBitfieldDistributionVal, + encodingValue: common.MustHexToBytes(testValidationProtocolHex["bitfieldDistribution"]), + }, + } + + for name, c := range testCases { + c := c + t.Run("unmarshal "+name, func(t *testing.T) { + t.Parallel() + + validationProtocol := NewValidationProtocolVDT() + + err := scale.Unmarshal(c.encodingValue, &validationProtocol) + require.NoError(t, err) + + validationProtocolDecoded, err := validationProtocol.Value() + require.NoError(t, err) + require.Equal(t, c.enumValue, validationProtocolDecoded) + }) + t.Run("marshal "+name, func(t *testing.T) { + t.Parallel() + + validationProtocol := NewValidationProtocolVDT() + err := validationProtocol.Set(c.enumValue) + require.NoError(t, err) + + encoded, err := scale.Marshal(validationProtocol) + require.NoError(t, err) + require.Equal(t, c.encodingValue, encoded) + }) + } +} + func TestDecodeValidationHandshake(t *testing.T) { t.Parallel() diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index 8371d4e969..1a2a75110c 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -1046,7 +1046,7 @@ func (in *Instance) ParachainHostSessionInfo(sessionIndex parachaintypes.Session return sessionInfo, nil } -func (in *Instance) RandomSeed() { +func (*Instance) RandomSeed() { panic("unimplemented") } func (*Instance) OffchainWorker() { From 9ed0b707e6dd978b8095c7f16ca142025b32622c Mon Sep 17 00:00:00 2001 From: Kanishka Date: Tue, 22 Aug 2023 03:07:03 -0600 Subject: [PATCH 24/85] runtime/parachain: add ValidationCodeByHash and copy tests to `wazero` (#3427) Co-authored-by: Kishan Sagathiya --- dot/core/mock_runtime_instance_test.go | 15 + dot/state/mocks_runtime_test.go | 15 + dot/sync/mock_runtime_test.go | 15 + lib/babe/mocks/runtime.go | 15 + lib/blocktree/mocks_test.go | 15 + lib/grandpa/mocks_runtime_test.go | 15 + lib/runtime/constants.go | 2 + lib/runtime/interface.go | 1 + lib/runtime/mocks/mocks.go | 15 + lib/runtime/wazero/instance.go | 24 ++ lib/runtime/wazero/instance_test.go | 329 +++++++++++++++++++++ lib/runtime/wazero/test_helpers.go | 6 + lib/runtime/wazero/testdata/parachain.yaml | 78 +++++ 13 files changed, 545 insertions(+) create mode 100644 lib/runtime/wazero/testdata/parachain.yaml diff --git a/dot/core/mock_runtime_instance_test.go b/dot/core/mock_runtime_instance_test.go index c12ddd7568..726f06623f 100644 --- a/dot/core/mock_runtime_instance_test.go +++ b/dot/core/mock_runtime_instance_test.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/dot/state/mocks_runtime_test.go b/dot/state/mocks_runtime_test.go index dec71be5cb..da9c5668a1 100644 --- a/dot/state/mocks_runtime_test.go +++ b/dot/state/mocks_runtime_test.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/dot/sync/mock_runtime_test.go b/dot/sync/mock_runtime_test.go index bd8b74f3f1..a781425e7c 100644 --- a/dot/sync/mock_runtime_test.go +++ b/dot/sync/mock_runtime_test.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/babe/mocks/runtime.go b/lib/babe/mocks/runtime.go index 1f6c7d242b..ec8b0482da 100644 --- a/lib/babe/mocks/runtime.go +++ b/lib/babe/mocks/runtime.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/blocktree/mocks_test.go b/lib/blocktree/mocks_test.go index 7820deb958..edb9777d6b 100644 --- a/lib/blocktree/mocks_test.go +++ b/lib/blocktree/mocks_test.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/grandpa/mocks_runtime_test.go b/lib/grandpa/mocks_runtime_test.go index 8b5abb7d9b..bf3d096aca 100644 --- a/lib/grandpa/mocks_runtime_test.go +++ b/lib/grandpa/mocks_runtime_test.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index ca8d84df85..30e2148324 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -72,6 +72,8 @@ const ( ParachainHostPersistedValidationData = "ParachainHost_persisted_validation_data" // ParachainHostValidationCode returns parachain host's validation code ParachainHostValidationCode = "ParachainHost_validation_code" + // ParachainHostValidationCodeByHash returns parachain host's validation code by hash + ParachainHostValidationCodeByHash = "ParachainHost_validation_code_by_hash" // ParachainHostValidators is the runtime API call ParachainHost_validators ParachainHostValidators = "ParachainHost_validators" // ParachainHostValidatorGroups is the runtime API call ParachainHost_validator_groups diff --git a/lib/runtime/interface.go b/lib/runtime/interface.go index 3d387ab054..4b92b8f935 100644 --- a/lib/runtime/interface.go +++ b/lib/runtime/interface.go @@ -56,6 +56,7 @@ type Instance interface { ) (*parachaintypes.PersistedValidationData, error) ParachainHostValidationCode(parachaidID uint32, assumption parachaintypes.OccupiedCoreAssumption, ) (*parachaintypes.ValidationCode, error) + ParachainHostValidationCodeByHash(validationCodeHash common.Hash) (*parachaintypes.ValidationCode, error) ParachainHostValidators() ([]parachaintypes.ValidatorID, error) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) ParachainHostAvailabilityCores() (*scale.VaryingDataTypeSlice, error) diff --git a/lib/runtime/mocks/mocks.go b/lib/runtime/mocks/mocks.go index d36c619641..d885be0cab 100644 --- a/lib/runtime/mocks/mocks.go +++ b/lib/runtime/mocks/mocks.go @@ -460,6 +460,21 @@ func (mr *MockInstanceMockRecorder) ParachainHostValidationCode(arg0, arg1 inter return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCode", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCode), arg0, arg1) } +// ParachainHostValidationCodeByHash mocks base method. +func (m *MockInstance) ParachainHostValidationCodeByHash(arg0 common.Hash) (*parachaintypes.ValidationCode, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ParachainHostValidationCodeByHash", arg0) + ret0, _ := ret[0].(*parachaintypes.ValidationCode) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ParachainHostValidationCodeByHash indicates an expected call of ParachainHostValidationCodeByHash. +func (mr *MockInstanceMockRecorder) ParachainHostValidationCodeByHash(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParachainHostValidationCodeByHash", reflect.TypeOf((*MockInstance)(nil).ParachainHostValidationCodeByHash), arg0) +} + // ParachainHostValidatorGroups mocks base method. func (m *MockInstance) ParachainHostValidatorGroups() (*parachaintypes.ValidatorGroups, error) { m.ctrl.T.Helper() diff --git a/lib/runtime/wazero/instance.go b/lib/runtime/wazero/instance.go index 1a2a75110c..58af3fa74d 100644 --- a/lib/runtime/wazero/instance.go +++ b/lib/runtime/wazero/instance.go @@ -1046,6 +1046,30 @@ func (in *Instance) ParachainHostSessionInfo(sessionIndex parachaintypes.Session return sessionInfo, nil } +// ParachainHostValidationCodeByHash returns validation code for the given hash. +func (in *Instance) ParachainHostValidationCodeByHash(validationCodeHash common.Hash) ( + *parachaintypes.ValidationCode, error) { + buffer := bytes.NewBuffer(nil) + encoder := scale.NewEncoder(buffer) + err := encoder.Encode(validationCodeHash) + if err != nil { + return nil, fmt.Errorf("encoding validation code hash: %w", err) + } + + encodedValidationCodeHash, err := in.Exec(runtime.ParachainHostValidationCodeByHash, buffer.Bytes()) + if err != nil { + return nil, err + } + + var validationCode *parachaintypes.ValidationCode + err = scale.Unmarshal(encodedValidationCodeHash, &validationCode) + if err != nil { + return nil, fmt.Errorf("unmarshalling validation code: %w", err) + } + + return validationCode, nil +} + func (*Instance) RandomSeed() { panic("unimplemented") } diff --git a/lib/runtime/wazero/instance_test.go b/lib/runtime/wazero/instance_test.go index 204266e1c8..d2c49815d9 100644 --- a/lib/runtime/wazero/instance_test.go +++ b/lib/runtime/wazero/instance_test.go @@ -5,12 +5,17 @@ package wazero_runtime import ( "bytes" + _ "embed" "encoding/json" + "fmt" "math/big" "os" "path/filepath" "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "gopkg.in/yaml.v3" + "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" @@ -28,6 +33,38 @@ import ( "github.com/stretchr/testify/require" ) +//go:embed testdata/parachain.yaml +var parachainTestDataRaw string + +type Storage struct { + Name string `yaml:"name"` + Key string `yaml:"key"` + Value string `yaml:"value"` +} + +type Data struct { + Storage []Storage `yaml:"storage"` + Expected map[string]string `yaml:"expected"` + Lookups map[string]any `yaml:"-"` +} + +var parachainTestData Data + +func init() { + err := yaml.Unmarshal([]byte(parachainTestDataRaw), ¶chainTestData) + if err != nil { + fmt.Println("Error unmarshalling test data:", err) + return + } + parachainTestData.Lookups = make(map[string]any) + + for _, s := range parachainTestData.Storage { + if s.Name != "" { + parachainTestData.Lookups[s.Name] = common.MustHexToBytes(s.Value) + } + } +} + func mustHexTo64BArray(t *testing.T, inputHex string) (outputArray [64]byte) { t.Helper() copy(outputArray[:], common.MustHexToBytes(inputHex)) @@ -1144,3 +1181,295 @@ func TestInstance_GrandpaSubmitReportEquivocationUnsignedExtrinsic(t *testing.T) err = runtime.GrandpaSubmitReportEquivocationUnsignedExtrinsic(equivocationProof, opaqueKeyOwnershipProof) require.NoError(t, err) } + +func TestInstance_ParachainHostPersistedValidationData(t *testing.T) { + t.Parallel() + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + parachainID := uint32(1000) + assumption := parachaintypes.NewOccupiedCoreAssumption() + err := assumption.Set(parachaintypes.IncludedOccupiedCoreAssumption{}) + require.NoError(t, err) + + expectedPVD := parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{Data: common.MustHexToBytes("0xd91574d9e4897d88a7fb40130cf6c7900b5cb7238036726cd6c07a2255c8ed1c32a018010915879f32707df4a034c9a329ca83a80fab304d1a860690def304379ac236284091930e2b657bf56c4353bdca877b2c8a6bc33ba1611a5d79b2858b00bc707f08066175726120f4635e08000000000561757261010172b799cfe3e2ba2bd80349c7c92d1d84ff01ad6b3d491ff523ee2759e81dc22d58a94cd968ed300dbbc725144a04fa3622a11b2614255b802261d03c53af6f8e")}, //nolint:lll + RelayParentNumber: uint32(15946390), + RelayParentStorageRoot: common.MustHexToHash("0xdf650f4c6b9bfcc8f768c4d3037fafbd6831ff23473e090d443684fb5e305bd6"), + MaxPovSize: 1024 * 1024 * 5, + } + + actualPVD, err := rt.ParachainHostPersistedValidationData(parachainID, assumption) + require.NoError(t, err) + require.Equal(t, expectedPVD.ParentHead, actualPVD.ParentHead) + require.Equal(t, expectedPVD.RelayParentNumber, actualPVD.RelayParentNumber) + require.Equal(t, expectedPVD.RelayParentStorageRoot, actualPVD.RelayParentStorageRoot) + require.Equal(t, expectedPVD.MaxPovSize, actualPVD.MaxPovSize) + +} + +func TestInstance_ParachainHostValidationCode(t *testing.T) { + t.Parallel() + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + parachainID := uint32(1000) + assumption := parachaintypes.NewOccupiedCoreAssumption() + err := assumption.Set(parachaintypes.IncludedOccupiedCoreAssumption{}) + require.NoError(t, err) + + validationCode, err := rt.ParachainHostValidationCode(parachainID, assumption) + require.NoError(t, err) + require.NotEmpty(t, validationCode) + + expected := parachainTestData.Lookups["validationCode"].([]byte) + require.Equal(t, expected[4:], []byte(*validationCode)) +} + +func TestInstance_ParachainHostValidationCodeByHash(t *testing.T) { + t.Parallel() + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + codeHash := parachainTestData.Lookups["currentCodeHash"] + hash := common.MustHexToHash(common.BytesToHex(codeHash.([]byte))) + + validationCode, err := rt.ParachainHostValidationCodeByHash(hash) + require.NoError(t, err) + require.NotEmpty(t, validationCode) + + expected := parachainTestData.Lookups["validationCode"].([]byte) + require.Equal(t, expected[4:], []byte(*validationCode)) +} + +func TestInstance_ParachainHostValidators(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostValidators() + require.NoError(t, err) + + expected := []parachaintypes.ValidatorID{ + mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d"), + mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e"), + mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72"), + mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68"), + mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511"), + mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e"), + mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841"), + mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12"), + mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c"), + mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547"), + mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237"), + mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53"), + mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016"), + mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848"), + mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a"), + mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a"), + mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936"), + } + require.Equal(t, expected, response) +} + +func TestInstance_ParachainHostValidatorGroups(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostValidatorGroups() + require.NoError(t, err) + + expected := ¶chaintypes.ValidatorGroups{ + Validators: [][]parachaintypes.ValidatorIndex{ + {0, 1, 2, 3, 4, 5}, + {6, 7, 8, 9, 10, 11}, + {12, 13, 14, 15, 16}, + }, + GroupRotationInfo: parachaintypes.GroupRotationInfo{ + SessionStartBlock: 15946156, + GroupRotationFrequency: 10, + Now: 15946391, + }, + } + + require.Equal(t, expected, response) +} + +func TestInstance_ParachainHostAvailabilityCores(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostAvailabilityCores() + require.NoError(t, err) + + expectedHash := parachainTestData.Expected["availabilityCores"] + if expectedHash == "" { + t.Fatal("could not get expected hash from test data") + } + expected := common.MustHexToBytes(expectedHash) + encoded, err := scale.Marshal(*response) + require.NoError(t, err) + require.Equal(t, expected, encoded) +} + +func TestInstance_ParachainHostSessionIndexForChild(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0929, tt) + + response, err := rt.ParachainHostSessionIndexForChild() + require.NoError(t, err) + + expected := parachaintypes.SessionIndex(27379) + require.Equal(t, expected, response) +} + +func TestInstance_ParachainHostCandidatePendingAvailability(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostCandidatePendingAvailability(parachaintypes.ParaID(1000)) + require.NoError(t, err) + + expectedHash := parachainTestData.Expected["candidatePendingAvailability"] + if expectedHash == "" { + t.Fatal("could not get expected hash from test data") + } + + b, err := scale.Marshal(*response) + require.NoError(t, err) + + resultHex := common.BytesToHex(b) + require.Equal(t, expectedHash, resultHex) +} + +func TestInstance_ParachainHostCandidateEvents(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostCandidateEvents() + require.NoError(t, err) + + encoded, err := scale.Marshal(*response) + require.NoError(t, err) + expectedHash := parachainTestData.Expected["candidateEvents"] + if expectedHash == "" { + t.Fatal("candidateEvents expected hash is not defined in test data") + } + expected := common.MustHexToBytes(expectedHash) + require.Equal(t, expected, encoded) +} + +func TestInstance_ParachainHostSessionInfo(t *testing.T) { + t.Parallel() + + tt := getParachainHostTrie(t) + rt := NewTestInstanceWithTrie(t, runtime.WESTEND_RUNTIME_v0942, tt) + + response, err := rt.ParachainHostSessionInfo(parachaintypes.SessionIndex(27379)) + require.NoError(t, err) + + expected := ¶chaintypes.SessionInfo{ + ActiveValidatorIndices: []parachaintypes.ValidatorIndex{7, 12, 14, 1, 4, 16, 3, 11, 9, 6, 13, 15, 5, 0, 8, 10, 2}, + RandomSeed: mustHexTo32BArray(t, "0x9a14667dcf973e46392904593e8caf2fb7a57904edbadf1547531657e7a56b5e"), + DisputePeriod: 6, + Validators: []parachaintypes.ValidatorID{ + mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d"), + mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e"), + mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72"), + mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68"), + mustHexTo32BArray(t, "0xe61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c511"), + mustHexTo32BArray(t, "0x94ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050e"), + mustHexTo32BArray(t, "0xac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841"), + mustHexTo32BArray(t, "0xb61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12"), + mustHexTo32BArray(t, "0x382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c"), + mustHexTo32BArray(t, "0x0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547"), + mustHexTo32BArray(t, "0xcee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237"), + mustHexTo32BArray(t, "0xe2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53"), + mustHexTo32BArray(t, "0xce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016"), + mustHexTo32BArray(t, "0xae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848"), + mustHexTo32BArray(t, "0xa0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a"), + mustHexTo32BArray(t, "0x807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a"), + mustHexTo32BArray(t, "0x4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936"), + }, + DiscoveryKeys: []parachaintypes.AuthorityDiscoveryID{ + mustHexTo32BArray(t, "0x407a89ac6943b9d2ef1ceb5f1299941758a6af5b8f79b89b90f95a3e38179341"), + mustHexTo32BArray(t, "0x307744a128c608be0dff2189557715b74734359974606d96dc4d256d61b1047d"), + mustHexTo32BArray(t, "0x74fff2667b4a2cc69198ec9d3bf41f4d001ab644b45feaf89a21ff7ef3bd2618"), + mustHexTo32BArray(t, "0x98ab99b4b982d6a1d983ab05ac530b373043e6b7a4a7e5a7dc7ca1942196ae6c"), + mustHexTo32BArray(t, "0x94f9e38609dd9972bfdbe4664f2063499f6233f895ee13b71793c926018a9428"), + mustHexTo32BArray(t, "0x4ce0e8ec374f50c27948b8880628918a41b56930f1af675a5b5099d23f326763"), + mustHexTo32BArray(t, "0x3a58b8f1f529e55fc3dac1dd81cb4547565c09f6e98d97243acb98bdda890028"), + mustHexTo32BArray(t, "0x982bcec62ad60cf9fd00e89b7e3589adb668fcbc467127537851b5a5f3dbbb16"), + mustHexTo32BArray(t, "0x0695b906f52a88f18bdecd811785b4299c51ebb2a2755f0b4c0d83fbef431861"), + mustHexTo32BArray(t, "0x0ec5e1d2d044023009c63659c65a79aaf07ecbf5b9887958243aa873a63e5a1b"), + mustHexTo32BArray(t, "0x52ef04ed449e4db577d98ad433b779c36f0d122df03e1cdc3e840a49016c5f16"), + mustHexTo32BArray(t, "0xc2d4b5973000d0b175631dde5d1657b3e34c2f75e8a6d5414013ce4036d83355"), + mustHexTo32BArray(t, "0xa6e01665b2d8490abf45551088021041dfb41772a9d596ed6e9f261ed1c8ae72"), + mustHexTo32BArray(t, "0xb436c143e295617afb60353a01f2941bd33370a662c99c040984e52a072b5f22"), + mustHexTo32BArray(t, "0x4c4c4b178f1a3d67e5f26d6b93b9a43937cd2d1d1cb2acc4650f504125df2e18"), + mustHexTo32BArray(t, "0xca17f0edc319c140113a44722f829aa1313da1b54298a10df49ad7d67d9de85f"), + mustHexTo32BArray(t, "0x5a6bf6911fc41d8981c7c28f87e8ed4416c65e15624f7b4e36c6a1a72c7a7819"), + }, + AssignmentKeys: []parachaintypes.AssignmentID{ + mustHexTo32BArray(t, "0x6acc35b896fe346adeda25c4031cf6a81e58dca091164370859828cc4456901a"), + mustHexTo32BArray(t, "0x466627d554785807aaf50bfbdc9b8f729e8e20eb596ee5def5acd2acb72e405f"), + mustHexTo32BArray(t, "0xc05cab9e7773ffaf045407579f9c8e16d56f119117421cd18a250c2e37fcb53a"), + mustHexTo32BArray(t, "0xe2dca6ce9b3ebb40052c34392dc74d3cdd648399119fa470222a10956769d64f"), + mustHexTo32BArray(t, "0x7477459916ace4f77d97d6ab5e1a2f06092282c7f0a1332628c14896e8e9be62"), + mustHexTo32BArray(t, "0xc2574de3dc8feebfad1b3bee36a7bfe6c994e5d1459a5372ff447ac32dd46c11"), + mustHexTo32BArray(t, "0xb0a8ed99f1e7ab160e0ac2fcfeee0d92d807c8fb4c1678e37997715578926c5c"), + mustHexTo32BArray(t, "0x6c9bfa7c2e0f8e10a1a78bb982313c5c347a018cb3828886b99e109a8799d272"), + mustHexTo32BArray(t, "0xe6037f1fc5b19015b7089ecf90034349e3f5c37cb50dec5356743614f94f8c33"), + mustHexTo32BArray(t, "0x964b85f2b8e10e859e306d3670b8bdc0cea17b97dfd3edc8a9e1be1f127fee5b"), + mustHexTo32BArray(t, "0x44d421ae62038ba15a377cad85e4ecd3c2a63b54fdbb82c47fb3e9c026405226"), + mustHexTo32BArray(t, "0x48c51db949a58fd5f36a19888986275547b0c2fbb0b348ccb85dfc6c998dbe16"), + mustHexTo32BArray(t, "0x0ae9425710301a9241837d624438a5d82edbbd6bf2cdbcc2694ad7db31ef9921"), + mustHexTo32BArray(t, "0x9e47376e9af08b294901b879c7d658c41386453c6baa7c26560c5fd3b164e05d"), + mustHexTo32BArray(t, "0x8af1a51649d44d12dffc24337f0a5424b18db9604133eafcb2639ddcdc2a7f0f"), + mustHexTo32BArray(t, "0xae7a30d143fd125490434ca7325025a2338d0b8bb28dcd9373dfd83756191022"), + mustHexTo32BArray(t, "0xeeba7c46f5fa1ea21e736d9ebd7a171fb2afe0a4f828a222ea0605a4ad0e6067"), + }, + ValidatorGroups: [][]parachaintypes.ValidatorIndex{ + { + 0, 1, 2, 3, 4, 5, + }, + { + 6, 7, 8, 9, 10, 11, + }, + { + 12, 13, 14, 15, 16, + }, + }, + NCores: 3, + ZerothDelayTrancheWidth: 0, + RelayVRFModuloSamples: 1, + NDelayTranches: 40, + NoShowSlots: 2, + NeededApprovals: 2, + } + require.Equal(t, expected, response) +} + +func getParachainHostTrie(t *testing.T) *trie.Trie { + tt := trie.NewEmptyTrie() + + for _, s := range parachainTestData.Storage { + key := common.MustHexToBytes(s.Key) + value := common.MustHexToBytes(s.Value) + err := tt.Put(key, value) + require.NoError(t, err) + } + + return tt +} diff --git a/lib/runtime/wazero/test_helpers.go b/lib/runtime/wazero/test_helpers.go index fe752d3238..700b7bde67 100644 --- a/lib/runtime/wazero/test_helpers.go +++ b/lib/runtime/wazero/test_helpers.go @@ -21,6 +21,12 @@ import ( "github.com/stretchr/testify/require" ) +func mustHexTo32BArray(t *testing.T, inputHex string) (outputArray [32]byte) { + t.Helper() + copy(outputArray[:], common.MustHexToBytes(inputHex)) + return outputArray +} + // NewTestInstance will create a new runtime instance using the given target runtime func NewTestInstance(t *testing.T, targetRuntime string) *Instance { t.Helper() diff --git a/lib/runtime/wazero/testdata/parachain.yaml b/lib/runtime/wazero/testdata/parachain.yaml new file mode 100644 index 0000000000..f2e7dc8de2 --- /dev/null +++ b/lib/runtime/wazero/testdata/parachain.yaml @@ -0,0 +1,78 @@ +# taken from https://polkadot.js.org/apps/#/chainstate (westend) +# at: 0x03458e146407a2ad41a87b18298540cad7d48f931f4815ca1fbef281e5943ef5 + +storage: + # parasShared.activeValidatorKeys + - key: "0xb341e3a63e58a188839b242d17f8c9f87a50c904b368210021127f9238883a6e" + value: "0x44a262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe7274c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68e61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c51194ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050eac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841b61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547cee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237e2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53ce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016ae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848a0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e36858936" + # system.number + - key: "0x26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac" + value: "0x9652f300" + # paraScheduler.validatorGroups + - key: "0x94eadf0156a8ad5156507773d0471e4a16973e1142f5bd30d9464076794007db" + value: "0x0c1800000000010000000200000003000000040000000500000018060000000700000008000000090000000a0000000b000000140c0000000d0000000e0000000f00000010000000" + # paraScheduler.sessionStartBlock + - key: "0x94eadf0156a8ad5156507773d0471e4a9ce0310edffce7a01a96c2039f92dd10" + value: "0xac51f300" + # configuration.activeConfig + - key: "0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385" + value: "0x00005000005000000a00000000c8000000c800000a0000000a000000c80000006400000000000000000000000000500000c800000700e8764817020040010a0000000000000000c0220fca950300000000000000000000c0220fca9503000000000000000000e8030000009001000a0000000000000000900100008070000000000000000000000a000000050000000500000001000000010500000001c8000000060000005802000002000000280000000000000002000000010000000700c817a8040200400101020000000f000000" + # paraScheduler.availabilityCores + - key: "0x94eadf0156a8ad5156507773d0471e4ab8ebad86f546c7e0b135a4212aace339" + value: "0x0c010101010101" + + # paras.currentCodeHash + - key: "0xcd710b30bd2eab0352ddcc26417aa194e2d1c22ba0a888147714a3487bd51c63b6ff6f7d467b87a9e8030000" + value: "0xcafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b5145337" + # paras.heads[paraID=1000]: + - key: "0xcd710b30bd2eab0352ddcc26417aa1941b3c252fcb29d88eff4f3de5de4476c3b6ff6f7d467b87a9e8030000" + value: "0xb9a9c9a758ed261aad10014bf0aeecd0d0275133e6b76703675898c7aa330a512ea01801c790de66e2f1c478bd632ff8bfe39354f43ce9ec0201601dfc3e730e3eb9826006a8441e6d7c2fe12a2612c3a064c9c5c6e1e2d32e50811f9619d6fd61f00b1f08066175726120f3635e080000000005617572610101147e68472ba82f29d61c6b67914bd6ec6ee2e7279c12ca24130282ca574ab03d7a4516ccd5a2ee5e7be0558aba3253942c1be30a8b534d43ecf941c46144e28d" + # paras.parachains + - key: "0xcd710b30bd2eab0352ddcc26417aa1940b76934f4cc08dee01012d059e1b83ee" + value: "0x0ce8030000e9030000ea030000" + # paraInclusion.pendingAvailability[paraId=1000] + - key: "0x196e027349017067f9eb56e2c4d9ded5a2ee677da3917cc29ee3b29c9f94c865b6ff6f7d467b87a9e8030000" + value: "0x0000000058b29da15fc66c19ef0ad4e66e95c8e42718d2455001ec6876f4173489a705fbe8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b5145337440000004400f0019552f3009652f30002000000" + # paraInclusion.pendingAvailabilityCommitments[paraId=1000] + - key: "0x196e027349017067f9eb56e2c4d9ded50433e4ed72dccf0edcf58c3192f16e4bb6ff6f7d467b87a9e8030000" + value: "0x000000e902d91574d9e4897d88a7fb40130cf6c7900b5cb7238036726cd6c07a2255c8ed1c32a018010915879f32707df4a034c9a329ca83a80fab304d1a860690def304379ac236284091930e2b657bf56c4353bdca877b2c8a6bc33ba1611a5d79b2858b00bc707f08066175726120f4635e08000000000561757261010172b799cfe3e2ba2bd80349c7c92d1d84ff01ad6b3d491ff523ee2759e81dc22d58a94cd968ed300dbbc725144a04fa3622a11b2614255b802261d03c53af6f8e000000009552f300" + # paraInclusion.pendingAvailability[paraId=1001] + - key: "0x196e027349017067f9eb56e2c4d9ded5a2ee677da3917cc29ee3b29c9f94c865adc7217647a32b0be9030000" + value: "0x010000000571a387d344bef8a77d3a99895958644dee97345861f7747b1cb8186a14089ee9030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083c2d011b32106cc9030d8d22770c632b611c613e4ed351c149ba59e7d61cd6d0055e865b40a8dcf60422987c985c6022231106e0a4556c0270ffcb99e656dd7be25c5eb2a93cbada931620b5bceebe187bf77aa6dde41a6cbb09f0cbbf291faa5a33ec5ebf7fa730c95c84650a68d432ed53fc77b6d8bff3ce50f19d48317c30aca695f1737408d7dd47052c3b3016128ed3d6ac5b3b46be0cc9537919b9f594fa704bc2e304492c6e835db7f2bfe17d5da9102834f5be31865468357055164895cd3b32bdb452067ab9b91421ace94cd58599e42f287eb47663289375824a5f02dbd44d6cb9847e46210a665f28147957290a6d3cf758aa31af9d3ba54c01e6744000000443f00009552f3009652f30000000000" + # paraInclusion.pendingAvailability[paraId=1002] + - key: "0x196e027349017067f9eb56e2c4d9ded5a2ee677da3917cc29ee3b29c9f94c865b2b032492225337bea030000" + value: "0x020000009b716202cd9fabb93b278a41d9344faecb1300241d255d1de0675a42ebff6b6eea030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083004c606d630584c98d4165a9dfbf9f2a60890f303686b7e5fcb428773a88ad6f08db9fcb4c0e4c4883dee0f9281e1de995b0575ef784a9fda4d58c55cdcc326dc8c838bfbf21eaca646aa5eef8e34d5776310f51b2e1eb1d6b1d86f05739c208efde19b42cad56b4c1e50a381a480ea65aea7c0b0e9f254174bec6f36ebccb70a69233b495ea5a88376ca43e908bbf8b921398dc491d94d1f8ac6f4f4ca6f02d4d71b3b7a81d9a11d797928c060d8c992f908005c1f1460d3ad4fa284dae0b8d9a32fdfc8217ce1456dc87c592ae674614dfadbd1098a3a2889e9c2ada44dac40d6b0154b039a3c7dfead7371f3f1d406f3a7a52307e99588a5d1743cfc529964400000044c00f009552f3009652f30001000000" + # parasShared.currentSessionIndex + - key: "0xb341e3a63e58a188839b242d17f8c9f8b5cab3380174032968897a4c3ce57c0a" + value: "0xf36a0000" + # paraSessionInfo.sessions[Id=27379] + - key: "0x4da2c41eaffa8e1a791c5d65beeefd1f028685274e698e781f7f2766cba0cc83f36a0000" + value: "0x44070000000c0000000e000000010000000400000010000000030000000b00000009000000060000000d0000000f0000000500000000000000080000000a000000020000009a14667dcf973e46392904593e8caf2fb7a57904edbadf1547531657e7a56b5e0600000044a262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe7274c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68e61d138eebd2069f1a76b3570f9de6a4b196289b198e33e6f0b59cef8837c51194ef34321ca5d37a6e8953183406b76f8ebf6a4be5eefc3997d022ac6e0a050eac837e8ca589521a83e7d9a7b307d1c41a5d9b940422488236f99646d21f3841b61cb85f7cf7616f9ef8f95010a51a68a4eae8afcdff715cc6a8d43da4a32a12382f17dae6b13a8ce5a7cc805056d9b592d918c8593f077db28cb14cf08a760c0825ba7677597ec9453ab5dbaa9e68bf89dc36694cb6e74cbd5a9a74b167e547cee3f65d78a239d7d199b100295e7a2d852ae898a6b81fd867b3471f25be7237e2ac8f039eb02370a9577e49ffc6032e6b5bf5ff77783bdc676d1432d714fd53ce35fa64fe7a5a6fc456ed2830e64d5d1a5dba26e7a57ab458f8cedf1ec77016ae40e895f46c8bfb3df63c119047d7faf21c3fe3e7a91994a3f00da6fa80f848a0e038975cff34d01c62960828c23ec10a305fe9f5c3589c2ae40f51963e380a807fa54347a8957ff5ef6c28e2403c83947e5fad4aa805c914df0645a07aab5a4c8e878d7f558ce5086cc37ca0d5964bed54ddd6b15a6663a95fe42e3685893644407a89ac6943b9d2ef1ceb5f1299941758a6af5b8f79b89b90f95a3e38179341307744a128c608be0dff2189557715b74734359974606d96dc4d256d61b1047d74fff2667b4a2cc69198ec9d3bf41f4d001ab644b45feaf89a21ff7ef3bd261898ab99b4b982d6a1d983ab05ac530b373043e6b7a4a7e5a7dc7ca1942196ae6c94f9e38609dd9972bfdbe4664f2063499f6233f895ee13b71793c926018a94284ce0e8ec374f50c27948b8880628918a41b56930f1af675a5b5099d23f3267633a58b8f1f529e55fc3dac1dd81cb4547565c09f6e98d97243acb98bdda890028982bcec62ad60cf9fd00e89b7e3589adb668fcbc467127537851b5a5f3dbbb160695b906f52a88f18bdecd811785b4299c51ebb2a2755f0b4c0d83fbef4318610ec5e1d2d044023009c63659c65a79aaf07ecbf5b9887958243aa873a63e5a1b52ef04ed449e4db577d98ad433b779c36f0d122df03e1cdc3e840a49016c5f16c2d4b5973000d0b175631dde5d1657b3e34c2f75e8a6d5414013ce4036d83355a6e01665b2d8490abf45551088021041dfb41772a9d596ed6e9f261ed1c8ae72b436c143e295617afb60353a01f2941bd33370a662c99c040984e52a072b5f224c4c4b178f1a3d67e5f26d6b93b9a43937cd2d1d1cb2acc4650f504125df2e18ca17f0edc319c140113a44722f829aa1313da1b54298a10df49ad7d67d9de85f5a6bf6911fc41d8981c7c28f87e8ed4416c65e15624f7b4e36c6a1a72c7a7819446acc35b896fe346adeda25c4031cf6a81e58dca091164370859828cc4456901a466627d554785807aaf50bfbdc9b8f729e8e20eb596ee5def5acd2acb72e405fc05cab9e7773ffaf045407579f9c8e16d56f119117421cd18a250c2e37fcb53ae2dca6ce9b3ebb40052c34392dc74d3cdd648399119fa470222a10956769d64f7477459916ace4f77d97d6ab5e1a2f06092282c7f0a1332628c14896e8e9be62c2574de3dc8feebfad1b3bee36a7bfe6c994e5d1459a5372ff447ac32dd46c11b0a8ed99f1e7ab160e0ac2fcfeee0d92d807c8fb4c1678e37997715578926c5c6c9bfa7c2e0f8e10a1a78bb982313c5c347a018cb3828886b99e109a8799d272e6037f1fc5b19015b7089ecf90034349e3f5c37cb50dec5356743614f94f8c33964b85f2b8e10e859e306d3670b8bdc0cea17b97dfd3edc8a9e1be1f127fee5b44d421ae62038ba15a377cad85e4ecd3c2a63b54fdbb82c47fb3e9c02640522648c51db949a58fd5f36a19888986275547b0c2fbb0b348ccb85dfc6c998dbe160ae9425710301a9241837d624438a5d82edbbd6bf2cdbcc2694ad7db31ef99219e47376e9af08b294901b879c7d658c41386453c6baa7c26560c5fd3b164e05d8af1a51649d44d12dffc24337f0a5424b18db9604133eafcb2639ddcdc2a7f0fae7a30d143fd125490434ca7325025a2338d0b8bb28dcd9373dfd83756191022eeba7c46f5fa1ea21e736d9ebd7a171fb2afe0a4f828a222ea0605a4ad0e60670c1800000000010000000200000003000000040000000500000018060000000700000008000000090000000a0000000b000000140c0000000d0000000e0000000f00000010000000030000000000000001000000280000000200000002000000" + # system.events + - key: "0x26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7" + value: "0x1400000000000000e249433d551702000000010000002c00e8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b514533734cd6976981877d40277ad627ec04b2fc4ba04f558d9e4b41d911a6f41610350e902d91574d9e4897d88a7fb40130cf6c7900b5cb7238036726cd6c07a2255c8ed1c32a018010915879f32707df4a034c9a329ca83a80fab304d1a860690def304379ac236284091930e2b657bf56c4353bdca877b2c8a6bc33ba1611a5d79b2858b00bc707f08066175726120f4635e08000000000561757261010172b799cfe3e2ba2bd80349c7c92d1d84ff01ad6b3d491ff523ee2759e81dc22d58a94cd968ed300dbbc725144a04fa3622a11b2614255b802261d03c53af6f8e00000000020000000000010000002c00e9030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083c2d011b32106cc9030d8d22770c632b611c613e4ed351c149ba59e7d61cd6d0055e865b40a8dcf60422987c985c6022231106e0a4556c0270ffcb99e656dd7be25c5eb2a93cbada931620b5bceebe187bf77aa6dde41a6cbb09f0cbbf291faa5a33ec5ebf7fa730c95c84650a68d432ed53fc77b6d8bff3ce50f19d48317c30aca695f1737408d7dd47052c3b3016128ed3d6ac5b3b46be0cc9537919b9f594fa704bc2e304492c6e835db7f2bfe17d5da9102834f5be31865468357055164895cd3b32bdb452067ab9b91421ace94cd58599e42f287eb47663289375824a5f02dbd44d6cb9847e46210a665f28147957290a6d3cf758aa31af9d3ba54c01e67514f16481063738cae77af97e154a475018c89ac13ca21452572447ae3bea823e90259b97f6f68683641a4b1046440cedddbc2054805e95504e9813b7c352d68ce57ba9357001fb3b6742be5e417621c0fd5f742e9f1d142518605af00a244dc61ebd42d63e07666532e9e2ab468d56fccb902fa801513a4dddc19d9e4b1edaae85ef1d1758908066175726120f4635e080000000005617572610101ba71303b6313ffc193e07bd26aeed8754fd02e86d3fd314074dcb2393a2eae08d32f5ec38737493bbcb09abeb020eb7eadbef00102bd3802b0545d5bca7e6a8d01000000000000000000010000002c00ea030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083004c606d630584c98d4165a9dfbf9f2a60890f303686b7e5fcb428773a88ad6f08db9fcb4c0e4c4883dee0f9281e1de995b0575ef784a9fda4d58c55cdcc326dc8c838bfbf21eaca646aa5eef8e34d5776310f51b2e1eb1d6b1d86f05739c208efde19b42cad56b4c1e50a381a480ea65aea7c0b0e9f254174bec6f36ebccb70a69233b495ea5a88376ca43e908bbf8b921398dc491d94d1f8ac6f4f4ca6f02d4d71b3b7a81d9a11d797928c060d8c992f908005c1f1460d3ad4fa284dae0b8d9a32fdfc8217ce1456dc87c592ae674614dfadbd1098a3a2889e9c2ada44dac40d6b0154b039a3c7dfead7371f3f1d406f3a7a52307e99588a5d1743cfc5299687f044d87495f20844ff334fe3ff1fb62568167d343d8f39dba5673d75452c06e9023c1bee939fc42a875b2921ecda1e413a5bad4b53bfa7aa314ba9aaf7f66d272cfe563500c93f096e27f359c7834c3b56180169d6055dc7d002bbee58e5b300004a6bf77091facd6381ad90cc2dc3ddab148eede30441cf6767ba2b3ce03b8401a52fdaa208066175726120f4635e080000000005617572610101960963f3ebff0b6b57bf99454febef94f04726a2c94abd401c5d3b568ec3cc384b10dd3dd7d96d456df0b8ed5cc6bacab809e163f85ec0b96529a9f3fa28f5870200000001000000000001000000000007559ff8970d26023b00020000" + # system.eventCount + - key: "0x26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850" + value: "0x05000000" + # hrmp.hrmpIngressChannelsIndex[paraId=1000] + - key: "0x6a0da05ca59913bc38a8630590f2627c1d3719f5b0b12c7105c073c507445948b6ff6f7d467b87a9e8030000" + value: "0x08d007000049080000" + # hrmp.hrmpIngressChannelsIndex[paraId=1001] + - key: "0x6a0da05ca59913bc38a8630590f2627c1d3719f5b0b12c7105c073c507445948b6ff6f7d467b87a9e8030000" + value: "0x08d007000049080000" + # hrmp.hrmpIngressChannelsIndex[paraId=1002] + - key: "0x6a0da05ca59913bc38a8630590f2627c1d3719f5b0b12c7105c073c507445948b6ff6f7d467b87a9e8030000" + value: "0x08d007000049080000" + # paras.currentCodeHash[paraId=1000] + - name: currentCodeHash + key: "0xcd710b30bd2eab0352ddcc26417aa194e2d1c22ba0a888147714a3487bd51c63b6ff6f7d467b87a9e8030000" + value: "0xcafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b5145337" + # paras.codeByHash + - name: "validationCode" + key: "0xcd710b30bd2eab0352ddcc26417aa194383e6dcb39e0be0a2e6aeb8b94951ab6cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b5145337" + value: "0x3a99370052bc537646db8e0528b52ffd0058ccc7040eca4504125110500a291dc4eb2d3805f64e7b2d7e45c285e723146c105016c8fb0c1c45f2067201a7682ff2f8a38efef7bc1e5b2b5eaf5f07ceb77d26bd997c142e033019db14abdf2c0cdf36217b4bb2e5de52ca9452af13c210f7109a5cfdfbe15ecfc2661175fb7b10677ba57995bc831b1fb4ae356b5b2740f3ebbcb5ef76a41d7a9276d8c1de56ef606ecff555555efa5ede81db160f699e977e7df60fb581237abe5f79e9eb6f8b07369e9f9d3718b8ca6f577909fa5229f0d7215582faf547778d54e83b494c0f3b23d4afef19eff375df7c7ddefb4bb7574b99051c3bffb601d679337223ee5f97e2a5f6624accd7a78e9038b88b3549e913d5133d974fdb66ad74a096cdaa98670ebdcae7cba1d722d5676ef29951d9ab7693110ef3fdf6e8406da2f0f765749f3718a4f0cb6e7bc58e754f547f7544d063b7db11d8b1bf4eeb157319b7108f1fbff7de7bec8fbbdb611af059e0a243ef865fa4fae79122f527e121730436452af8ec1032eb80b5f77c3eba1b3b50cf8ed15f7c146befb70935f312e67a1df2d56783aa0bbfbefd0383ea96f9f55d23fcebab66415f87fdb3af877d7d85fee1a5f5b93270dbda81cde3fcb67470f3bb3d07ea163876ae59d1d9739a7539abbe16a91ef3cbd9799a7575ec58a4db2b3480f08a7d5e545ecebe1b1225a45f0ed4fb7e64c7bd1ef6ababbcfd2383aaf6d7d5d2618bbf1cf60f8fa0cb73fa725f23fb57578b54bfbd8a7e006eb9f7b3ada5a5a5a520f6d8550929d72bf647737ac5de34d52b76a886d3f96de900e6737e5b3a6cf95571122b61fdd2e860a58d255c7b95ff9ebfe7efbd67c5bff7de533d2a56b87c95df0ebfababa49525f6f755fb2abf9d95e6aaaa4f0ddcb67000f3dbc261e90bf0dbaa72e67713a2005739a0e6b866457f3e23dd7f947f6e118eff7a5aa497f326f42edfed05a5bf266f49a25f5d8d1e7df626047b057d3725a88f3ea1f628ff7cda45a3f3b6bdaa39cf3ee76e575795fef247ab8cfe1eadf02f07eaf96865c03f9fbb615d9df2983f5ab7f5cf23addbab2936d4c044867f5eb75735867f5ea36faf6a03fe39d08b741fa346efcd9b376e3efaa3d585873e597b147aa4fbbbc180fdbba811f6b1abfcaf33e2376fdebcf9aaf4cf2195bd62edf962a8c3dc25619870dbaa62c5af9a55356f565d7d7bfb10aeab74f9567d7fe5792108562c917da0d69ab5deeec354cc4d6109b7ad25342f535c7e9d6bd6b696ccfc3a4fceafaf6a578539b759cb3f51b059edeb93276bbb382fb0e1a557fb0ffaf3f6de84ba5bc679810d30bdda677f74366fecbbc10ecea86db3f6abfcf5f9b4a6fcbdb1f3267bf57cb706ecb36f42ed40cd3e98fbddcedd9d127392c9ce94025c85df2ed9a5dcf7b3dd53b967afdcc366b533cff0f8713f076a7eefbdc7fe1e77bbfcee7694b4436f4ec2de5d6d7fcdfcc55c55f5f5e1e616e1d69fef26b4cedb91294b4b4b557ebbed5595df3e5b83cd6a166fafda81fa51d8abf60170d053cddae79ab5bdaaf0d7db739af5bc3dd52cfef6dd849e033550432a05c8475192cf2e9fbb1d25ecd0d96187c4ffbadd4d88bbcaaaee88fc3788d263f662eeafaa72df3e65b3aa8e103838f6aa7d0b80637fbebe1b0cec44dbab75a09e4cfb5b9335a6efb7352f453bcdd698c2d7d6c811b77923eca5e478f6f66d8d1cf1d21b29e0a568cfde7e5a2347bcf4460a7829d7b3b7a3d6c8112fbd91029ebd7db7247069e9d9817ab724dcd2b30335506bcdda6f9faced1a61afcf61afd665b32afff3f5f9a8ecd57a00b8e7ebbb21d95eadbf4ef66a1da87753b26f9e1da8817ae9f66a7d55dd1171bf41949a9b07737b2e0138f6f5cd84b0e11f2841c624e40b214e103204213410d285901a083123a48b102f2a368404a14a82901c842ca9902004872066a85a42aaa882a07213a40cd5152a2a5467a88aa0d2428586aa0a15175510aa2d5442509911840d950b543da8b404b1814a05aa21542b5011a1124205449036820821081282fc200811543fa87c0852045596204650b120c817aa2eaa2d41d05065a1c2a28a23c819aa37aa1408d121480e82a409a2039597203708e28510218400a15a43c546c8102a26a8de50a5a1baa22a43c80faa2604d9423584205da8e0509d20080d542850f920881b419210a4062a1d544e50a551e52048199511aa2704e1a2bba2b3a24bd361f1e38b8e1e7459745a3c3362950ecbf3c2c7164f820e193a663812ba1400210248971e430019625b42f745e7066e041f41a082d05102f5834e0b801801c4cbd6a66341d743b7036d438b40dba031a061e81274083f02901f801c017b802cd029c30710538ba3000ed14ce888d12993238d1e4f0812870f228004f183cd8f2f7ed8e0c70d7e9cf981e647183fd6fc00e3470f7e78f1438c1f38c06183e3032039f40ca60f93cd5403d50595c4ec411e31b7a4ced01913a340219132c39f1057e0630920604e3f984de0386156d163063fac380f80c0607b3375b0c3841c51e880419020fc8042d5031c2d70b6d0210344079e1dd02fcf06281fdc8d37c195b031e14cf0256c4bf8e8c2e385a7c5e3c40f264e46f468c2e9881e6ff07cd189e29483eff003091d263c2d74da4c3374b4d091810e143a4ee850a1d3850e1174aad08e982ad07941c3209566ae0008171f2cd049e247175b0b3617e848014488ed4b44c186442fa179993ca4d000b24567063e7ad05942074c2442870b1f3ee87c712b3ddeb80e9a0d72dce0c70b728091e30b1d2b3c46e854d1967472f81146ab92c38b1f49fc80418e333d9cc073044f113f94e0d94247071e2c745090b3821c15e8eca073029d2d74c2b00b7e8c91357835c8a1c37c62e6c077b0c5816ac149093a4bd019c2fce195e143063a613c1e74949961a00f6f08938ad31a409850ede00306405ca0fa014e138038f1b8f8b1439b71365d845683d30e2736af15440927379e0e417a10840740becc3474800992c52b83fa8272418e313da4786b78b2e85801240914113e86401901e40528258034b183436c418e2a7438d1230dea08d40b3a3241d4001246a5469037282f3dd0503511e4083a3400b2c4440335c4ce12544010b283b442c74d8f2b82b0511d21840b10248028a1e2c10c4235854a0d9016685d0821220816130814971c1e72b2f4e0d223081d3f48bde9b1948aa34795141c3d72e881430e0b727cc8e921674b8e961c2e3941005983d2d223091f6e80f4606ad1ad80a7ca49871c3be8b8414e0c7286c8f19273448e13395f72a4c88922674c8e981c28726490f3448e0b725e905344ce123acee480c921220706394de42091c3444e0b7484a1e30b1d60e8f022c7889c2472bae428911346870a74f0a0238b0e200459e3e1a0b140c5438e1d041943a707f6826fc03660335c03ee8269c065980bde82b5e01667c158f0156c0557c154f0144c8667c052701432083de0c8629055f1867845bc2f2f091d2c64173f9c601f742491230c9d14fc58420ae1070b7e34a1b3a483438f387a2907193c59e41063aea153034886dfa08ae801850e129d0a74c0d06942e70b7743670cef810e0c74ce6c6e6c49f891039d233624745974c4f0c146c7063a67002982ba2095039d20f8f8217ad951c196840f2e900a9d2b7ea8e941c4f4818e10505ba20c4e48e830a1f3840e2eb6231889530a1e153a59e8b474a6d88ef09e7862745c20a5985c74bcbc297c080104086fd336d0dcd0d068626838d0c2d0c0d0ccd089818e192d8b8665769944e460c91142dbe107181d6b78b4d052a09d0025840e1a1d2f362f282066191d2c91071d1c4c16fca8729046482f7108ded26f7c689159e450a3944521a409199aec8b4c8a4c061914d9139998ec059912d91219974c87cc4a9602e804e903190339852423b9c819483072094985ac426e218190612413b2090903f94522215d205b2085904fc820e410718bbc227e9158c82e520b2946fe10719045c41f2218f18b78267a116f106d10cdc41ac42e220d6299c805ec128d108b10cb8864442ce215d18a5845a4224e11c9c434118d98465413b7885ac456cc221221b611871085108310d98840883f886b441dc430620c2214714c7c22c2208a894e4426621371899844ec12858840442e3188880474039b00d78066401dc033a0195803280626a1aa12c4cc83e325012ebd37af09b205d9bc36de0ede0d9e99d7c5a3c1dbe29d813e784b3c183c304f09f98207c4ebf2887841bc16bc173c2fef0a24e2e5f0acbc2aef046fe9a1a0e3e82cba8bbe224ba2b9e8328d45d3a0bde81b4018349996413f0197e829200f1a0ab806eea0c13412ad046ca25fd02b681eb88ceea1b5b40fb0c54a804cf012780c06036ec14560223012b80d3642b3c0022c046e82d170181c830bb01838050c03d5803dc8c6b017368281e0206e408a7264e4a40211602d13602b1200c1073ab0010dc4c03a700f0f784109021c90a40148841610f4402eb010fcc3901e222b0e605c26fff2494bd66f284a130f2cb1a1c9500d4b964c7e343c195ab2c4e43480f28127498a8aa02c600102f0321b656886274d8a9a7820ca5091079c80e0c90d478238175b10712d7cbc5554d46486a2a2260bc8a20624103c192ac26287863eb0040ad212a41a40e06448043d7ec5462972d2046988862543431f48c1ad58281f78e264c9088e866e305ae2c486477a321482113489c1bd0a1ea7620829ca08a24c11022848516e20b343483778c0c9111252131c9fc15051119419522ec51e0d3d114114a40f4060f32876688627509e2c69b2c4890d3f34c3132810407a7203941966781f3e860628330cd100c465b051463044b4244a94110c297902c54994284313a00ec58ac0890d7f34447404e50230507f62970cdd307443042840c3100840d084020270311b856886a11a6688c146417a32743454c31327ee4e3c71f2811d6f62677012e5899323a429434b465064830d4d961c0dd150e4e48620a219a01405f178988da2e34c6c93253cbec486604913a4263434411a7a008ec380044e8a9a384172f2c4c90e0773c30c43538672b8127b0465019abf60a30c3941727204a506a41b9ae8f024f6091427484d8e6ea061091294a2284b9cd8f04d8e6ef81d5a114b340394220f2c19426a428393a226524ecb43451e6882f4e4860f2c419a61e8c8090d4f9c2c59d24348060643732608667e95e6b696969e955e8f0b130213826d600c06893e7cf8f01124b6c4b0f7e0eb6e6c4737866114c0300cc33008b9c6babb23d65877ef62d8bec6b0eec530ac1beb9d00d6fde6930de5ec1ef9fabd7749093cd9b1a56c4d76af26995f4328bb65cb7e1826654b08fbc1071f5c1c6ce26c6fc353779f22d06f03b28bc500f7ade975ec0961c36688ea1dddb0a9a649f9bab76180428a485477b7ecf7bab18d6f76bf7eefbd9ebbb071e67bef690f0000c8b26ee69384123e088b3c5437841078009c0fbe00a1920965778e93ccd96e0927dc6d0cc60863efc607e12e7cdb8dbdd7ed80f7a484b01fec859ac625e134c6b0d67868f03d6e21b66ffb694f46b8bdcb6f1b80446be695bd526ef09403a16cf862b77c304743f8248c11c2f8a084a9542a9523c7c3b4c5b01d47502dd4b43b05e17b4f630094bd1836b15eec7aef65aa5e9c00e8e830cfc6deee4a4b61610aa7dd4da15fc3a76317003b0ddf7b107bef35ec9db7d3cdad412825c70d60004488bc8742cd09210ab5a1504284f0ecf60e11c08e1d9007f2704338d50e1e4eb5db6dc2a49cf2611826b783bceea7716fef75edb57beddb6bdfc2d5b180893327ef3233776f6310ceb92ba594f249084f27783a41d873be27a59450be076563a7d39c18eff6b6a95b3eec6d8cfdde7663dd5ab75cd81d2461f7ea74b7d602780f4e08a594b29bbb9b1bbeddadbbbb1b7b0d613fb85132f36e18ec27e1621107c3a494bbbbf0d40fc3fabd86f04109216c29a1dcee8861d862bdfde093999413e690723bc5868df5623b8fdf05fb9aef6197ecb7bdb7bdc6767b1fdb5d88edaedc6e89615067df8332001806dfc2dec5fa450cc31eeceeb8bb8d61bd2de5ee831042b8afe16bd8bbb0bb5fc396db582f0661f7ee62bbd8beee7e40af5b016fb73109bb77f7bd6eb8b09fdcddddeedded858c6db75c4cceeeb9b6bb7396310c6218c4de83f1417e303ec67ae546b8dd0d253c6d12ce1cf2baaef76437f6e0849df37677df7bddfd1edc6d4c6ebf86580230ac93f26198648e18025c68a1eb8c441c0cc32286ad0ec0c28b38304a0c3bbdd7af1bc3300c7b0fc33c62d862d8f5209cf041d86fb17e0db75f77f7d53062afb16d216c84fce0e56c8fe0084a0886427065abc29094a1a225539a8400085d154000a56809089e38010108a014f52840519113229f9f14582b03ac7a0ce991c20a0128800f0f1a8a9cd430c312a4a21b9a7c6088861e9f3244c313274b9e386952c30c1ff800948e43f063878e3eb0e4080a529319a0141535e121000d434743341c4159c2320001869c1c2105e0a8095294147844e9e14b0028508e74886668e281a22228470150a1c9d091ce8f21a2254853869efcf8813404821e2844418ae264ca10cf85344393284f9a1c212d41721282104059f5580238f9c0d0910d9d8ea326434533ecf058159e0cd960439325432068720405290a0f017c5605a42647434f86407024c4b404a061284a94a1231b4200658625474352a0cc3074b4a449141a66185aa20211cb0004b06186a1283f4046f00127474352961c400503188000340c2d818264c3088e6608b202cbc714282240fac13200011c0502a8b0840043444d8a3cb004044f9cd410a5490d4b90a60cb9942743efc7aa30140227484f6e38ea58f800d20d3ea43c197a3a5685254fa0388972342465c994a11a96203d717264c39219a23809c112a429433c529e0cbd95b6462b715292d24b4a322941a5b9179355524a4ae22b8997c92a2525614a4ad7db9828312a29093e269b9404992871525227bd6692c44a49498f8912272925252525252525cd961854525262264aac94a4f49824252529c5a4a44d526a269ba4a4f498247152523359252513934d4a6a264a9cc44c36292963b249ac249928f12a2525252529614c943829e96292c4ab149928b1925233d965a2c44a4a90c92a29293d26aba4d44c94588999ac1233a940348377974310a5c9061f7d5b4bb87c2e65e5be86b99d492c93f4894f26dd84f9bcc9a34f13955d38c94ba6ac93dd139fc73a22e897fc2186607f3e3be0218a7cfeb9fcf749762c5106133e6a96b082999999996a4ba49708b344124fc46dcb056e7e9768c13fefebb7b50411df2c06c29c00377d180b97d790e6d440bffdab1644fc6d21c1e5f5acef69558090dac011414fe16b548a490fbd660f581f7cec9490d1974a8f14d2f89e681324bba505430bf49afd73a007690a3f79b70ca4f03eb15b8cdd57e55f577909faa352e0c3e7bb6f7797c83a77993b014d00d388bf8328f39a476f1a85bfbdb706ec671d117bd647bc44a223c937da373fbdb7239a9b4cb3bb21f548a9e76e9748ec8cee15e85a10635ef3cce78c5a9b3c6b5a95bebd3725d035df2d49e69a033512cca7339d9e51cddfa6443a73ed8e97087be693d6cea4c8377a712c21b1d2c54d0476e802807e39106ffce5d82a0619c0ecbcb773b9ab564c6a00820c901f6dc438a459f5f2d5eef600d49bfe1957fa6b8026039366c535f342b3f8a3fbb0d0ac6dfdc0e623f481663efa366be7ebb2f97884a46d30d8f098f73ad2f5ad6a56f4a3e64dc992c75c62de8b75bb43b6db84882e679fa66b6abc1db99c2b63de547618bd6855fa8ba862be7e6d4930ccb1ae9ae0dbf7bbe9f3c9fdbc5d095f9d12ecfa61e92f1a2f8fce55be4adf6da5800d9733df86447a354112f6abab5de5a5cf5d9218ad97af03bdabca73bf36d996e9c2ef0fca375fcf6996c9779dc755cd927eb9d29be8e6922acd391da835ba233dbbe2e5a16f29b87c8a99cb57b86d6d693df4097d4e25d63ccba64f9e6ef47efa74a21d259847889d3025d8099b2e7d6626dfcf4cdd11e999676d1ebac9d73c248a9e75951f73cd25e5cfde76c46432993aa2e8157a142518a228bde64497c3001debeae55a9739d0fb4ccea8eda67595ca6b7ed10aff72147dbdc3ae0a1c6405e53becaa703907013d8dcea85df4398a6e90a44ab28e6873e89bef2624e40db7f9bba65f5ddd7c763bd3318f4aa06fddceec9448bf5cce8b9a1c23921253223199047ad6d5cba7cf49abe63b99ef4fbfe84ed61d893e7d5ebebf05cd439f3e5b2332f9ec2a7fbd5cf3f93413d5bcf26b1e8f4ccf7cb724b3db2552a73fcf7689d4cc1f0cd067574d7ef9e4bff8f9f7e30a4f6adc4257f9f5d31ad95ff7612189954cadadc007369e0f5cb0b041010bcaf009b4c9ce74319c29dcb2cf5defed04ffb46d5e4a8edfdd5dae572cbb89d90e9bb2cf4877a26f7774bee84e747ecc27d49a2e86033d857f8ef9d2e8bdd75e7b61ec7b31bb9a67da8ed1cb81de45e3cf48d927330e1c7b95a745a8c5a835658f425c85639f917dee85ad4edcf97c6fc7f52a857f4df9952e7e61cc651e38e86f055cc7e0b7350417df47fcb6b87cf9d56f2b08329220e5d0e6ab9215d7d71b7ae8e2d751bc02ee6df1db3a8287e732bfad226ef0437e5b5d90c01b12d3176bde88c9b731e28d924e5f91d4d841ea88af48608e986a7c45c2a246fc1a058d280440059aaf51c8a0f9954b4bbffbe6d77fbd7f9846fcd0cbd3a70fbbca3b03b72d24c47c3c43e3ed01cfbbabad2f651e3ad31d25f18c871ecff81d766fa11a45e9a1479fbc6fdebcb1e23996f1ef78c6432a65077748adc4bf1f9ddd1b127ef346fc26763b49d89f23617f1dd2dc1612ad47ea8f5d6d97c2ff5ce9e93f4abf91e6af2b45ee36689e51dd3f67f6dabe5998bc29bb89126d48dab939076e5b3db45e7a3b92cb3197b4624e94b9f4d9b44aba93042e2dfd74f82fbae6cfa789b6cf13dd39f97e55fa936bb49a4c9d11eaa520f59fbc37ba73727e94cfa8c14d09ea4d7f9cde1b92936f14e51a3db9893e6da3f027d434ba4af88dbe9f51d3e83ee137da3f61f3460c5e2807ea768d2e1cbf6de486a71d25cbc59b37dffebe2a59b1b4a4d155f326dad4e428ba01f546e312b54b7f2f286521fd6d48d8a594ed7b84bd9db724edd2e7894e9fac9de816e1676b27ba48f8f9b4135d377e42cd44a79f6814eca5f483e0887f7aacf2d3e7b5310edcf4ddb80a37e1f4b9f1315f414ed34fd4e74d74cb2ce1f013cbbce7e6bb44b0b9b966aaef33d7320dcb7cd2236d3339506f26d732c73253b73b24eb30076a6f5f670c27765caf60d7fdc6d80de6140efeb67a1833858bbfad1ec8ec1209b26ddbe64d8bb66ddb365fd5668439e7f4a64573ce397d553308599665d231cfb22c73b946325f55d61171ec038a05885fde9acc6faf7a8960ec98efd6cd542e11cc9b161571d0b33522d48e39946b248db9347015721385645f35aff7aef75eb7ef5d985f545b2298cfdd209444dcb67af8f2d1571874a0c62ec730bf28920b36eb72ac899848f6513e976efc6df100c52f95416a892cfb527ede96084a5b22286f4ac49d7c73cda7137126cf5c3ae644dce5b1ca47a15c5b23ed28dfd6c83eca9ba2ca98a8241792d75be6ddd25fe6f23d99cd4bfaa352f8a5f48bb7dfe93d7f9d3f50cb8e483ae6bb3d40665d55fa8c332af2bd271de81951f966976df4be653b504be1da79a2d6e1ce73f6e74d779e3fda00a0df1d5231c7fcd17dcc317984a4bd91029e6d78e9bb2561f2d2e522c168954ef41cf329697764b46d96961e29c79bfc3d24ed4d2e7d37186cf8cc815ad22323057c2f91e748d7cbfee7193d928e74bdc97753b2e43307f2994b6aa480e72592bdacdb1df27c3b12fd396f425252762c5224fcef51ae57eb4a92b12ce6469c177dc6e793b7c570b8f66d0f9a587755e99ba960bbbb8eed3335a2f28c19bd4771b7b4422979e97baf8db0376fdebcf11206a646ef6567f4a2efe518ad984be1be082e55f9da6e84bd51e5cd4b91352382fe5cfa7695fbf5a65abb14cce8480af65194d681ded2cb1ba9dfe4979b3a237ee3614691faa1773b49b8f5cf7b137a7ed1adec973f2aa5afae9777d588ca438f55e931c66457a9bcf4a6dcdd2d9da32819bd23ebed97d17bf8b0237a4aa012f6d711adf7662c5930031cd309d79b9635ce8473c2ad11f67edcd2332ffd2e61b24fd43e4fb32ed77c7d7d3e5b9346799ae5f3eb579249e9f409d46c5a936728feb99bd062385caf4c9ef93a6f42dc55a5e7a519e5e9d5ba894e2a85bff267ce540a7f96c1f99adf3b7167f47ee7c87b76ee7c89acf34f9a7544d2a7c3cd48fee5539ba8cd9c371170cfb36ea7a4557a66eaaad29b58c834bb4ae5a733cde86bde21ecd5014a59fcee10f6cc819e11957fd93351a3f7cf37a35238ee9f0375d37d8c12f98ba668e58f35ee7067c47f75a9324b4b2f80c7f81a623e2d0ffd511b38a2e8297c8d1d67e9d57e9666ed431f5a23473eafc4bad2fec0d9b7bb3a2b3b1009bafc8a8ea02ccdca79e83a004001578b9eb3f40abacf1a11d8202b97b303f513ae7296875e778d874e659d7b3d4ae83283236620660d0c62ba0499e001091c5c704590951d8804b15fd111b46e65870ec2fc4a0282d82f7a250141549aa0003b95185e0f7446f3d02fa7d2040530bf3a2a40af073ad651698202977347c55f0ff4abf3d9568bbd2e9a5dc2096ca0041172b0440fc4f0870e802bdc0629fd70e60a238cd0220d144908daa0f5eaf3d077a3802f2d05719666a5f0d0594b37f432778d541f25949ba0470893fe451f3cec62bd30e858f7e4eaeae545ef89361fbbcbebeb2a95878f2e182bf8ab534246af495fa4d4e5af2708b0d2fae8157675cabf270b86968f5e49f0f0ea9e2c9b8f1d1a30581439630ba805cdf3fcb6b440f1b3b54895a0b29e847af3e64d97a04c89251476f0440e415882ce46efcd9b5762c9870a92d8e2e548889718985942b261d9bc124b45ced862490a02acb4a62c2d185a82d62b093efa6e0bbb45eab67810c223f12b81c3b209cabaa56513b41d95f5a42746b4008d2a6f2c2d05c96ec94aa5e2afab36fc7349d7217dde9b058a9ca1c59ba0f5dd2c80d3831dbc208e1cdebc095acff9d7d715f34148a33363f28ad19f4bcac5d81a6f42ddc4dba2f36f8b872920e59f4f837e75a95ec50ef2c36e32d6e51a79fca4bfcc1fe6eff277f9f3ec72793d7f97bff73ca67a75d1fd28d4510075fbe57cf90e74fefe1d3883cb23d6a57a7541ca0fe512d906c2dce8708d5cbe1ed7c845797a1599f854afa2eff6aa7492f2f763f2e8abf7333dba4fb3329e5e498fcec21af125223dfaa45cf7483751d93d9287f4d8e221cb47e7b146f8a35fa95e497f602eba98f4e72a70f3edecd277307fdfbf837546fce631df160f31f86df1d0c46fafa45f46f0b32ed52b89d1b844d677834b641da82fca9fd44ab11f98b9cdc21e0b96e66abd1d815de587fe287f44121d7a476ac46f8e3c878e29790e5d3aecaa7c88396f4653ace05065e98daa089e00c79bbf3c8ad25f5d14acabd03bd2226905872a3f619dd29b12d9d529733724d1a15fdb91cb7789d48851fe8b1af19b678f42b0db49f26047d40e3b232a5fdf476e1b4ccc276f42d3358f2ee9e6bb464e7ef93cd1234a5f359fbedb11792292beaed12a9d7df26f8b872f1f97c8d5db916edfa87413e59f73b707b467996f3473135dce68e5dfae669e51fee9403d976a3ee9fedc0dc976957f5df3d9da4462fa5d22757d4ed6a6e640bddaa4b4729a4f377d9549e46b1d51e63d69cd9c1de8eda6a43de3cd88dfbcc9811e509be87e46f7e5feb5abf1fb61ee120e7a4d7d94dcaa59d157889e8aee6f7cf4f938e463eaa373cd6a8f31465f55ec88b6c5c3dce55d6e3217cb89a159d5c7e4f8f8ba64408f8ec9e88f5d0ed4ac2a6b6816e62e346be73197a159fb983369d6b65490e6b11cb869b466f17ccdf9cac263be9bec5574a0e6994db13638098ff5a794f098cf6dde7732e237bf98e7340b3a8639e6abc23aa26df3cccc1de2b6d5c396afb285c4957fbe2fdb2bf79e13ad9a95e54ab3b773299e9559b87c737b045e2ac5c3bd9c7f11d88d63705553f3ed5c7b6a8df02b604ee1b8b9a7398583ecbb437e60bed697f76fe7db9dd378d39180120ac6d0c10e3708634d146682d671deea9d85954fb3b695e5ccb7afd0acfe7617b41edfae71a7fdb6b258f1705b2fcb5c0f702d642e06b8f679aa01fc6d61b1e2b7c7ac468879b3843898606346d06a937d55d587f2e455c3d073ee86e4eaaaffe572311cd8abae22bd3779aa59d39ff3340b9b5896753573936718c4327f3e31ca3fb35e69ce1b92d46b5dd5dce418ddd728d2fbcc27f7f23b61747fd27d13e57ad52bdf2552799ebb7a79f4e7f3315f745f01ec9222c5e76eee6c6dca5fbeb4cab72d86b370db88a3f54aebd573cc65afb00ea881de425af9d72f8af47e9d29d7ab76de84b68b9dd62cf67d7f3f70e9f96a8dbc37710ce2cd4727c0b1cbe892c332e6db731ee54fa580cdb7f3400a27c0b12fff94b93de76a4a4f67465f555d34540dd3cacf3e5f1616b67cbb6a080b3c220b2c34aba61ad0accab900e4e5db19f042eaa9fac787f40f5c5a5a5aba12d4bec21a7915f5ed2824591ebc11bff9cae4e37664d57cf479696f130a03358c4cd464994bcbd04c78718cdb31be20a7e8a83512bb5d22cc7429c0d5d4b73f870ed4a966edb652b0f4ab666d0b056f7cfb104817cd6f10a5c8d715739b70db42411b1f7f5b28d8f28be38621449a47b8b7423d8600b79b03941ec24e76cbcc002916b0c40d1befedb6b7f7f682d2136d6794f4ebb59d97abe28a9bdfd695345fe54317c06feb4a98872e2f2171ac5f14f66ab717941ef68aff52faab934d85c0117dc24d28bedd2edfb9ba23fc975f1dffaa6047c46d994788d8ebf366a642e260de237321c05df1b577271dc60b49fb73a0de66ed1c697ffeae1a9de839f4eb72d9acfeab351702dc4441e74ef60a3af489628051d2c3859d07e6f643c9663d6f87be90b717941e52feddde06e636ab97ce2dc2c1d77ea2b659adcd47a16f79a647fceded8f42df4dc88823ddd6152fbfdff4d1fd4837a845ba2d145cf96d56436797cde267ef4d88b5974d9520330de6aaae58793ecdaa2eb8d02ce8cf87344bfe732ee586ea5fce265a3331263262c654e6f2b19fa732e6d22cb86e580b6fa9dce5d94b0ccdaa97c7d0ab49b4bdf231460c14d389d94e28ec07766a9e4f9354f66afaf018a2bf201dfa3c412d1fbd6a0fafbc9ee83c046e87a3e03c3ce5cfdb2b2c83a69d4cb3521e1db6601988a65997f3f0aa3d34f3119ef901e21592f1f14271500c1403c740326f43d23fbb5aa441efe175dd48871d11ec64f742af7c74444a5cf1a01f8ec4afc40fe72ec382abd0cb47d79cadf0952ccd921e7d8a0f875ffae78747875e80d036f37aa2cfedf20c0baeae992b488862e8557424fe177a15fdea8860962befe7b9793d115ef9e8f344e541de78da51d27f39fc6a82bffa076d2bba278279cd6ba3f9e8927216be62c5cdfb99f27aa28faecaefdce8d0caafe357d765c270ec3bbec351f029ef8783a2f352b3763c3a5bc9e155e278e53e87f39afa1cdfcdf1dd845070a0e6a1500c85647a15af6dcafb91e1f5449772ddec74704cafa2f3f0945728e6a34fae32a559278601a504e5539a75f2e85478698dec478f9b129411eceaa643f77b50257e788af2a0d151a03b68f41c5a39cb47d7a89b8fae831e6132e4e8aa34394e97e6722ecd476fc0fbf9d155893a691d37bb2aa5a9ab1c77753595aa9e7575e5f3ecb2ab3e5fb98ffbecb04d8f4ee18cb9aada6dfef29983eb55c4f1cda1b79735f2fcf2d95f1a0c0eb875a0e6ba7d9473a64c66e775479e73ce3dcc1f85649a8585b93c35c6adf8ebf2da65fef29d1063cc371ccabd1f9fd773b994dcc328ec950def67876bb3c73f37f90ecadbf33c07e79feba0bb3d35c3e2eed021a58eaefaa432cc4d262edeeb86f40af31ae69cace599f38961a658e604d9806c5aa713735c27851f6aac85b5f4eaf28b1deb2a3fe7976bd1fae7397c4a39b51c94ade8d5e53eef875bafe7724e934a5dd72684754ce6081ce7eb393c3aacbda6db68167775f5ec9773ec6da6d1749a66e5f0cbbbd52cce2fef32cdc2f1cb7bcdfbe181c290ba42abf9ab361b7fbd1f1d5e650e0f1ac3ebb93c07f5c198b9bc1f14ba1da9544ec75c5e47d755e8c6638eda5e50e29c731ccae531e7e296c7609b5e61a8de90f4ef12795d2d52fd8e113f8e4b81ffdc08f52f8733e9d5f53c4727851fa7abec9cef7684ebda78cc89b8aba15797d3d0ab6dcbb6e02a6c3de66db879d2a0f4abc1fc757a30ee7b1d6cf370bcbdf49766617ef96e95bb9b3ffa56bd7add0650b4f2a3bc378c6a12e7e4dbd202cd5726f3974367326aac68d36a565d3465b4b0996655ded25ede8f0dafe7daa6576e4bb3507e39dd3fd19c5e45df68955fdbcb5fce97dbf0b4aecaae72fb4c6dfe7226976f20a390c6f03a2a6dbdc3c5646d6e4ab2faa733a0592f6c60fa9cb3726dfc5cfdf4ba6efce4b9527a0bcd6f1bbfad2cd8fce48dbb2969856c9ea9c356afb63c7bdc8424ad8b06f2c316bb4e3b4af8a5c3af492f3bd8a6572c71727a75f9b6b240f3d5e7a7479f2b346b3a0bcd6a9f0ee49cd65529b1ae72dc763555b99f4e70755b3fb9db0ea857534acc17eb9c01cdba54cf2ce9d37dbad62cc94bd7165c88f9e502ccb7b02d2cd4fc6a1750d5d7e67b8633567ce66f8b2b6a3ef3ed81658f64397a1df2d2ab8ae77757f5b5515f1adf39fdf3c8bc9ef697c6f6b4333bdc841e99b7667f78cdeb69efc706ecaa945de51e996b13c2bc519006fb17260cc779e535925b13bd53dcbf6ecdeb78cd371b6b848d18719baf1c847f1b0c99bf356cac112599bf97d9a3757ba5619df4817085d7668bc94590bf9eeaf1fc7677bd4db3b0bf3231b894f396d3ab7e1eddd961e71384157e2e67e1b69545eb4deea639dde4d726646a333db6f599579c67e133afd9b3f3fef8bc9ecc99fd5ae17ddee69766f29a32cd6d1cad57d2b1cf7c05175ec85c9291653ed32bb7653ef35e65ce74d5abcc55bdca200e7be6d17ddab3abcbaee9d9f4adabf2b5ae729f4d377535f599bf0d89bfc9e4d34dce267f260ab74db7999ab66ddb868169c8c518379c5fbdf7207cf0bdd7fd1eecd7ddfbb6a3e4a6a4edf361f488fff2ab31cce3268475b75aaf608c317a89912f8e103a5645bc566a5c9cedcd8ad133d7a2a6c508afeb4dc68e607e5a6f42efe1d4d0ab4c42871d76c54c62fb9ee42b8ee509a3614388b3815e49c75ccbfcf26e084d187dcfdf7befbdeef7ba4ae5a3bf07fb75f7f31903c63510c218e92e9166c84f8c1c32b4959713105e9894f2921dec15cc39e1ec99dfc75c7a8c7c5d51d27d91a117bed82f251e775d916e74e1b7570c218c31461877e383ef9559452ee600b241b2bf4be44508214786643c1a85ffb11bef3ddabfbd6c266218067bf57272a079d8abab9370b99f9939e58597999939c50e031861372c136384f1ba628c10c6082195c73cd20a3f6eb36457a19448178c10c208d174f47775155e970cbd925e25e69a3fa1ab2f8c3e785d114608615d7959b9bbbb0b9f7c3def5d58ec6b2dc69538667e7b251f4bc8fc1e848f1f33435ae1c3ae42cc31248efce0e3c7cccc8f213f1d5c91058544d5ad92f45d9c5e297d4b2bd42c6f5d91fa5fb3dab5fcb6ac78e3d98a36de0b6f0f689f5aafa83c8c1ebb0a95fe3def056a2c967e5b55b479f6d46fab8a343fdf56097ba5b438a9b71de1376fdec4f1cd118b091d522aba48e112792f04dcb6cc94f9aa69cd826b247296163ce5ca2c21dfcf0d454073c49a1e20018513412c58600a30efbfad19b4c1e57d7e5b6480f03350f302f86d490106154baffd0e6f3354987903fcb6a8d0f253ac9982cc4fd1e6e7b3432ed1a776b9e6dae59c8ed76603d555acc33adf3625a6ef0dc96cdee7c630875c9ab509611735753b33bf309a390f14763c1c94cfd95d854c2bf7efb1432d8f89364ffaa6f5bd3f612ed965b7afb177d20181e0169e631529c77574b57ff39caef6cb0ab93ce6dd8f56feed796dacbbfb7544d237ef9d4fdb0dc9fb8a751536addc77a64425e931e7aeb99ae0bf2bfcf235f38b123d4ffa96ccf033bf1a835d30a1ca30d4fe77ca329ffdd22b73572197cf1ca975e438bbec9f1de717f9457e918930cf7478ed35395e9bcd0e67eccc98bf8b4ac971bb3ffb7ae203b8abdced6c7e7996721e5d953d1c856e475753585793386f30b4635bb805cc1f26040e0ea91fab485d2197bf7c3ab721d9f11cb58356febe78380a5d4da1e03dba1d9eea30cfda6bea6aafd9755d98a7323f6d48da89b8abbc35b3f3e86a0a858e31cf7c47d7985f9966e6a78cd76f745f62986392628ef9cbb848855b1ef32ba3ed402f87a27c87eab80ecaf9896edd412dfe7aa2b7577979e51e2b879c18c571de50eeef07082795babaee620e7a9238a78ec8e4dc559eade5a8fcb2f69a66e3bbc33493b757241d21717048f1b58ec8e4d2af8cb3ae72459292e3f632390e4eb793f906f47e98bc9ee8a9d4b65e4feb72cc75207ce927afbde6b74e081c5c458a2f1d73cd39babdda1ca54365f76c5d45827fad79cce586849d48769b631b0cdab333919e4a554e761529aef9c85ddd5c73a8a559fb9a4320b8857dcdaf4d48835c7ed235c5ebb33a8237f95cee3ed512bcc94dbe5b92f8a62cc97cd94120b808d6c1e7a51dcdc384148238e02aecb709c16d861076b057efbdd32e91ede2941f6e42db3870fdf871f1cc10360e5cb343e7a057cfa248112f7399798974d2b733d6f1ae6209be1f06aff7d1798b50f97effb6885d3eaf0bc33aa3f757b74b6461b39e507750e9e2ce621abd376f3c2fb137e5ee087324f947485c17e1ae42bf9240d9edae2af4cbdf06031518a1f396c404715302ff6d51e5cadb90283d7c5dedc7b4eec77c4aed6d49e0633ef935bf9374d919bdc7ae6e3718a2eff6008c5f0afa8c1cce5f0f54b11aaea21e7af57feff960178639505f18afe1aa3f8671435cc5e5a4ae78f93401089139422f3ae66c47fabb5b1e70290861376c56c310aac155ee61aa59d0a1733950c21823167c69bdba7a7509311b5c4dfdb56ad6e5a966b55fcee3cd827eb9aa59d12f97d7e54b85c08161cc185f1c86c47613026aec72cc2fc73a22ccdb2bd6ed7894184cbd94524ae9524a29659665b2c97414e2f998d33d51884b83abfe317a8c1e7da91038aeabdb7dd4687d007e5b63b6e8608c1519a479dee677da23ef3708dc916906f743838b4605d87055fee338101deea3b0571176cb39e02af791fb412ea7075c457da3d608a7e926927d46d2cfdd0e38f6f5c9d1bae7f9fea04a19e6aa76e3353808d4d02104a0e0f2aadfd613622e1d2170703b4960ec88a2b7d788c5ab2f1f9cf9c199ab89b92a1cd92bedfa81db960caef8af0c96b670db82e24c5dfdfa83a2ccdc0682db960ccc7c955006ad87d053621e3a4ffb2f127617f2861bf2fb7cdea5d8b9a2b66ba4316db985ae9269cb2df0d47e9da9d6ab7e826397ae69bb464c8d028204c8cbce08c86fdc1b719b9746bce6a5f67abbe229bac95f66822ea9027e3725d16bcb0d89025e76460a78e913a5ed1ae1b45d23b1e383f014a55f8a907c8e75da06438c1dbcd078228b98377e5b62b470bf2d314b3f51994f2153b3307683abdbe63387fedc6d68d6f3cc919a053df3292cb4672e43b336cf9c49b3a2675e43b3d853298e5b163ef3ba6a3e5b3699cf48350a7b75f9646da3db2b934b0a7bb5992eaa39e6bd6daf306c53fae9265ab9cfe60ee6fb47f0abfccc65b3a667ae659e79aa5926cf7cd52cf6cc7d9ad59e7595e7336f5674adabfe99eab3d6135dbe0ef9cca729eef3127c628b2a5cfef21d259747473d7cc0f52abf79f9e633760c86cb39baa4a8c7e8f6eaea207fec90a0f89530e629e6769b365a07aeee9a5f5f22b3ce6f5f87db41b81d3932d74cf5f96581005f53fffdfdeb40ab35020f391043891ea688419520152a706569cd124080820b3441ec738b70f2fd7763ff16cc5555d846dbb293aff39966613f352d1bc35dd5ac2324f9d09faf86f8f09a6699fc39b369d6e5cf990d6ea3599c3fe736ef2787bfeac23f5fa15938fe9c8566a1300a7fc24d09745457fd37c7a19b9f28bf16515de5c1e91e98fdb8a576f3e76f53023789e6db6594f2da8e608fb946a5432a45befcf2f59df9e795df7863e813d3abe71b7d64aca0afd5abe738f495e9d573147d667af5b8dfbacabf39a4557be80f0c12ff936b5ee143bfb2ae9eba9d93cb4e73ad7b60b65327a518a17e7314a41bdd7f62ac3043c69a369e1b2b5aba8019d33a9306b26956fff399835a69e2e43b785013106e4a36875dedfe728ebffd1483367ff26b53f2be7435a757cf4f1d47f74d1b77a6b7249bd01192fdcc39da6fda8450dd6e917a393b6f4ada4fbe9b1213fce50f4cb34efefcd4d5fecbe3a664ebde975e3dbfba2a85dfe41ae518acf976d3a604f5ec1add37f9ec8cd894d1f7c412799eb9a40f0c5c5e86f10429748cbebfe81bd32b131c890fbb2766893c1970f579f9e7f589f9e7cfcbfbd22c30cd5ad806d59913d1e7fa70b05742905fec2a7f85914a305c44127d5797efd5c176a077618ef1d680fdd8216d4aaeabdb49d21ebd3d7652fab9b5f8f2450d52f7d2d2d21252a7f1c5857feec2db9ecfb7310d739bc570839a0e0050c03ddfefd7143a6fcf776b8873a50c12cfaf2b72e171ae9471c1b3c3df8dc1c4ebba62901a23d0f05244136d10e11346072ecc0fa89c54e59e5bcc231cd207ebaafca4318aa085186ad89c6146d055e42f36fe32fd8584bf66d0755dbcb4fdb69cc841acb24c945922cdef56c4156b91cf631e6397d32b4e51eefd281dd1c5185cb869011741fc10145d366b001f9d23e34f1f9950e3a3f3f4cf15145dd52cf81b14bb5ae4f3394470eab7e5c4166ffa6d39414609e3fec16cc2cc86515379f8086e5b4e80f926d238e1250c6f14e8a520ccf7fd5cde3e77c39e2b81759703bde790d6290fbb4884d40f3b29efd775fc6e4a52f8ed94180164c1d7efcd9b6ea17f9d9fe8887fdfbc09ba64afd608f4ba0e7b2585e0f8d717a95f89cb77b3800282ae6e5fcfeb1cb6f1f3b33070155a955e28fa45afc8a0cb23a5402f0525fd4a811f7d27097bf9085f579328c005ad911a6367847ad829712d2ffd45f7f52cecc766ee1007bdca0a03fb55fefafebaf6c4e76347043bb8466bc8664a894f84b9f49d7ed1e9a99c5e5dd5ffd262a0823427adabcf68918f76e4f2f8b32de8dbfed37fbaa4d57964e3784dbdec32b8abc8e7519e7973de99c9db33aa7128caf56a3a0e4d75cff4d346737a359d3721cd44b9ac8358a7d1d855d9824d349aab48935ebc5d8f5eaf6bfd5a27ba76fded43c3b1d7068363671e7453f8fbde7bdbbb05c915a3bfe84bf4a2431e082164d26270ec1029c6aec6182398b92a97d1d7d989b671003b8610fa8bf009c1bd57ed3cbdea2ee70cc72bf97e64f7acd755f310ca0d40e832c04773aaa821938337d480d101133e076da0c18334b45cbd258d34697851d3468d9934967e329b9f28e9fba6d39cd77635c5afab7cda9498feda362439bcfd7afbcfa10cacc626c44d77137ad745735c5d858f56ee1f2b5191a1b90d25caf1a4bf9e0eefbe2e76edb9c6ad43e37aed8021c777b079e6aeabcbb9ba6bf31ce7e817addc5f981215be30be2ab7f98a3925d23ce999d6f797c32dc9fbcd65b8bc998d07d450f30bebe75d65368f39523f3fdffcf97bdbbbe2f355f35cf3be2ee7ed0139ecafbb28773b39de8ef380cbb9ab499c656856730bec4f73a0a75566c3e6db73e470147744ec9a9faedee11c5fcf51e8eb3192cb895e57f93718b4bfdce7a1b0a3bbd87b6e4273cbcf2d7ecee7d0fd8d19fe469f73362497e74039af617f14731d743a0f9aca4173d0958187479caecae7d19d3a54c7cd5e978de7cc4fdc1d51e697b31adc02bb56a4fdf45aa4d5a2f7b323babc5ddb58f3adab1ae5e1d7c933675a4fdd8e74cd31f7f7e3f37aa21769cf5e8bb46fdfb4eebdf714d2fbd63cc6ae166975d53ce6553eaba1f97419a6b31ad3e1263499cd9b329965599675c76a7011ace34173f48e14a08164e4e4d8d2fa81194e7866c0812a62061b98194698b3081a58a0f102346c50264d2b43230735ad2084d08232547c29421152b0461a2b3ad204d153c5ea408a23981943851b4700c1e68c3438670461a70a17cca8f2041769c2d8c1133e38030a9c33b8d85145fbf0031ae860a94d11d438230bea0c22785421e4b7956607696cd055e8f039f476a22db8a745e61fb057ad575432e183129890e60938b0a007b20c2b65e4e0b350cadf16194fe08c58a38252146b6cd1832a82c0c4962a905832ba7cc1c5152c5dc451040fca6853860e3edd2d339cd0cca0e2559161d8961e4de4a42af7dc5e98194b3accf081abc0ccb8629a614656e14ef8ba622d9af0d173523e3cd4781e5e6058e636abbe9fbbacf6fa98df7792f5faa8fff5b600bbffcd77b302bebb557afc4ee03f512d2577553a94544a204f24bbaafa862cd9cffb75526235553929bb8a755208ce7fb97ffce43f2c638519323c41468a35c620029926d6d881920cb40883030f6cd00821441a54f850e1b37b619247626590f198e7e020cd633941188661189639e659c73186e1a08dc7301c28e1b10ec2308ceb6803329e781dbf2d328c900dbb48fd44491678851e73611896b514640c21318c0c201e834118865d4d5c3192f1432a0d8e8c153c317080264d143c9890192d56e0040edc58c20735635c918dd18574a2c818419cc64822e584184dd819638b137bc60842d86189149c51c5151d88c1850e31cc08e2c4c5042b47cce08d3492c0821c88c14587184ca49c58e2041fb2e8810b1fbc10021a3860024a8c1d7238c1e3b735869b3186e044130338c21927665084963659a4f1e784173131e02c61180104208258fa7286d21450b8400c1a6e92908430664441620d24a6a852450c96cab840983061c2d440e78262c77585d1850b23cc15469627bc384eaf881070c078c243d9408621038c35a6341eb735143166690d35c8586243061862cc341e94cbc54e881738210e2088900516578c0186161b18f0052504514414423842999618608cd9c06022432303ac059d44bcae2b16bd000c971a38e1881fb400075f1c018c2a010003072dd030220e3168b608bad07cb1267eb186e6e38555f8828ceb8b1c5ced6587115107343dec1765ce9cf105cd952ce808343bb4aa8fe8f1db0a8308affdb6c2e8c1d3c186b14f2823cc1626a4600b3788e08276410b960852b8a00b1916f480096782160c197c8c30c20866ee4c162fbcfc6e4470aa665dce45aa97436477e1b8dfd6991abc9f892a767757adce80f9ebb775c6055ffd6561795daae6b2d875a6cb153b2904075d364b0593e8213a08954f07a1b2811537ccb861868d193260e0183e9a80f77e76cb98ac059730dfce0066f55bd1b6064a34370edae82c5db060c182050b162c58b060c182050b162c58b060c182050b162c58b060c182050b162c58b0b0cf8559300cc3303779539363b4aa1eaba993574ef3dadfbb2d147191117c0caf871d73ce71bcb170db2b1c1bbcf15c73bf138e334eb74b84ebea948f1e033b7b11b7a0e2228cea6a8a3b7595db3aadabfd75dbd88ebc21bd627615b7b05b24734ea538aeb9f657d5e4857daa496ff4bebbd5158e4d5dd5be8ff8ebeae5635aafd68b543b4ab88fce7d84ddb6c1e1a884d37bbcf27e3237afa7ddc1a4bc705c525aa4e4b854cadf64a159b15d875b5a5aaa12145bf19fc33268cea421630d54f38f0d36ffba5666e6fda45e4fbb941c379b859494ef7547faa3fb9b19f3cffd5b3968f96de1c0a68503991dc63c74cc67a6a5d992f47c5cea55f68649eb1db89ab9c136a1d7a552908a5de7c069bdc313020707dbfc9e247c83b3b29e71e99fcccceb69975e38302931df991932df599bd6f79fc83c2939ae9ec8d4139934d00d4c63da903874f37ee49548b9d7d39e95c1bd2ebe6e07c3ba23dd553ef3ec2d33df9e71cbcc97c9dabc9f0dcadcb40a38c806b2d1ac9ab981699a55b9f56ad6646e3a07aea6de6785664598e6f5b44bc971a9bacadee895a3493d6189b4d702fc3a0bfd635a52615bc89ab045daf775999b25d2de57b8aa3dcbb8f47e3628d5ce42b336954a3d9742e2ffb6608197df2d7a28f315a6f97601fc6ed1039767dfb6d89db438b10d06eccd9b374af8a7f58a953e6634e01e86617e79cddc7cfb2585ffed28f9d8113d8fdec90e891f9abccc9306db34ab1fecf73a3ed32bf6d39aab738c10bb3cdcdad4b82566e96805bef1ecd08dfe9169e073aef0b9cbcb34ef4786e99e76f886ecaa5c63858d2cdfe63b6ef9fac84837dfc623935961aec1f7cb863cab7836884c15fb231f17b091c87f9d04f3c8f4aabd51c0492663c5da2a086adc6c7fcfe57bbb7abb459e2ff587706b732561eec62dccdb62866c7e9dd799512bf82c11143bc1b1c345f3eb3dbfead56aec0417d9217cf04dbef6351699d771c6cc855e609737dca421638c98a7e56569830d3365beb4975e7a434d1a3263780b6b6953b4671a70026e7ba55a2227e078f99dd8e82d0683e94cdb799b422dbd6208d99b5ebe94b71d727968e6817ac2f54bcbd2fdde8a04e7b509adcf09d4ddd7d55773ec0821105c641dfe5276c8a557bc5d55825cf67a7648233f8cbce639d3aaf47cf1fb13197f9bef3ce72ccf563c504f145d2ea2f586393fca7913e26dc371a07e6fdbdee6408df3b68b7366e750189defbaaeedbab82a5d8ea21c537e6c13ba668e3ae52b9547396af38942f9d655a44739601fd5711631b8ccb9f57274387bf136b9975fa9580b1b3d7476bca698ec501dda83aee91fd6c29b10efb6c3819ab5e4f03e793b7761f27e76bc77786fdeed45cd42790faff2e77414e5333b3486f7a3d36df768dea3dbd6766cbec351a055e951e038ce91ec739d09be3ce728ba71b4b2a736ca030745535d6d35bfc337aaf44cf77178ec70a02777ece854dc7a3f3e32a7ab92d37474359583f2ef2f12ce4f391cdb84726c5e7dbe711c870a79c39d1ce5ecde2bf66c13e2935fdb0b4a7ff2dd844edd46a1462bf79569e5eaaea943b6ab70557e7748fc4297b7aff6b14ef6ea75ebad1a12bbeab3c26f0dd4fcbe35bf41929e128fe6623057d580222d63d2f01b5eca341b59c490f1dcc02e4953838c6d0d883a26e7e76eeef54c269323d937b9d233ad6da2ddb15bf627842acca02afc5aa47df4e6f7b0f75ed2c38bb37cdd6dcef6d82d17790e371838055c843d52186b70a68bd6cfd6e00f074d4982fdf399a5574618ef3b412d93bde1254dda8963ba391a532adc77ab4c9aff6d95f1e2999999fb9571032184f1e2c183d4300cc3648642125333994ca6a999aa3869dbb66d27d445031c8de3380e27470e2f72341d3a74e8c8d9714a838786020a28a0c0230587a0a3f5e8d1a387ce0ece6f8b0646f0a1f16c426478d6797a2c91769ef5b1cd1fdaa3295ac46dab0c1b5fb51e6b643f5baa4034ee020599c4ae085ff36a9de34d14568c414306175264812aa25302b3c6129260868b1f10f1061752a8408a223a18280183065b30610c1f5a456c1185107a4891830906019062872a4c344107483823e8d2228a2fb428d0c08181017e5b658078147e5b6596ae1f5e84303a8448f6778954089999999f1a36b10d37de9bb9aa3502d7c8d31efa0ee6fc901ee97f0ee90efbfece117eeefabbffa239bd42b2b49f37d83177bf97d375b0572d0634abae10030b402b541f0a346b856faf2c7c3f10566ccfc2ee8cd7d5d2ae81544cf97ea2afb733e5dfed910f835eff6e72cdeb96418dbfbf3e1bbe9cfee178fa27b53dcf77f55a90229f1dfb75a971cdd276f56464bacb42d01906ee6b11a73d774654b883efbd07b968fde342ccf70ebf5a133a82266ed5aba681090e8f8b27dc848e8608addefc756e6e61f20afdc3e4f5b4af7eb71dbfed95e7d757fdb37a3ded99afbf35228d60beeeaab83f12c77713e2e1403dbd861a2e5e3369a861d730a9e1e235bce635189d1ea90ed3acd9398e479d1e29d40e45eacfe1bb09e570a0e641fd887324f939357012dc7e0e6f9f281eeee3d32b205e59f8e155051f5e7bfef178a59dd7f8cf975b985eafa75eb59f14bcc7feb0f07adadf73b846347f9e5a233bfc757548af7c94e09ed7159255e7f33a6a74c445da2945ea55bbd329bd6a4f8152e99e7616a0b74307d255203fba9af2d1f174bda3632b4ba45da3363081d3d88a35d2dfaed1d6f20457190c005c88eff8be1f95ebf8be9f20dec399d090f2f6cb510f00fa3f58b143ab1442ff8732dbb3af4355f47f38b33dfb3d684d7d10fa3f90b13dfb29aac3b7f3a0488d023d42e27efa91e6ab213ed027a43b76bcab7b3e63cc7e9d875cdab509f515091f6e2f47478e6b13c2e1ae0bbe01841520b200b1056ab409f50017697fb4864d08d5d1c06489b4b31bee311816d3ac6d2db4b3f4ca861fd4fcc0e687367e7063bb7c57cf4f14eeea1d1d816f44e52f4f02ffeac644ca617ad51c86c1b496ce0206a95975c54b7ca55975d7d8b09a4265057ff841fb61fe002713f97e18cceb698f5e79745de52e1eb1ab3cdaa789c1bc9f0dfaca597c0b5d6ea2bb44a26794b7487449df12894462e4c962c7c7dec4bc10933582fd3aaf91e7eb432e18fcb774964d28323373d7597ad57ea57d6be868e815bc82ab7de5dbebaef9f6ced22c2ecdaa0cc64b5f6956d5d2ac2dcdaa6ca6bb70f9da60be1d4cb3c0bc9f07a2cbf6ec97edd9071386cc0331667bf62bb796bec5d0064266aeaa76eba1b3b0820e5c5d3d7476d34bcd62170207d74b0ec453ac917ee86c26b5864bf38c46be9e2b52cb43c9e5a1f45225181f31d06b93f917d8b8f2db8ae2cc5766e32167d367b7d380fde948f6a72bbd89567f933f5a99fc738c56ee31cf6865934bdfed45cfa8d23f8a145fb69a8fab38444a8e7bd00af7bcca6e4136ec984b6fdfb9fc75d025957485781382bece38e0661fc9972329b744a26354c712898eb345a233131cec5514fa82ab524b5dd7755d5d4e5445b882b9aaba3145e457c8af0f29dce5bebe6359186b5999af73cbda162084d0a13f0a9db70874de8ef4c3ed81be02102229210fbd723e1e7a958b03aec29c35d20ff7fdac366d02a9403e3e2a45fee5abf713c3eb89bead5668523f63783f8b66c7b3a349e25f89fef9ece53e7597b344d8b95db167545bb97095f34ffbfeeeb653f39102bd8a10c27db0a340af620cbd8a5070d1f76357f9ca635ee6f604f3eb5d8cf9ed900a808a3e7713e2f1092f18c44778651f1403b1eccf141f1eca4b670ad4b4874e70b09bd2ab089dbd4d74dfa4418e9a06bf78f1296ba43fc301271da82514c3500c7377845d3a84743a4fd30fb7e5637cdb0b9d8972ef0792793dd1219a8f500c147304aec7b3f2d1df52afa26b7e6d9a75b33d3e36933e7c789c8269539a354dcedec3794b8ec75ed9a50ea91ce6b9369a1ed72664a4c4dfd4f970cce7a473379333856ea248d8a177d2af9943abf63e7c3850cf6c39f3e1cb4b53a82481d3fcf2da68bcf0e6a3bb7c62943fe270b5dff8d8c37b745ebe5c898d464a8e6b348d66b5fada68aa0f4539a39cfde29573fd33c548b3a66bacc93bce9b108fcbc02d5c1e3d036b047a740d2c6b7afce155fe00f1ca19d1e1bb3fd8f6b0ebf049dfae8a2c910ae4d981f078f61f28d6a81038b45e459f54be80db976ebaa6d478432203a380a8b4ead4f5b9a04641a7109a99994910900093144030381c120d47e4214d54561f14000d9db85258a0cad324085214428818630820c40010181199011a4a13448300d75f358570c11dc090ac10253c47bfb835c632549c56d0a8f8e6c4519da59c8b0f04acb391062ed9b3badf13f81f136d0d967e9abe0e44ed930193823d8145e8f1390f5a5dbc0b8df4affceee8ce8d58395443a6c8239e45c67d0c550a3de20334aff068b3525f4cae6b73f5c59733cd7d5c1660c81b8117e836cc1dfe5ac7b985f80429bbd88de10cdcd41343b572614b591aa56a86777f3271ea4c3efe460d3b29bfe6df41fa3aba234f99adb700b3c2a5a7a8c4a9dcce2b49d15c262d937243f52a7b28ef6d4cccd8ba828cb327f11a7d9274adc9e5dff3ecb3a9bf3cc0a4912fa6bb1c256d6e184b2864f11df3b63bafafce3df2b20e61405bca3a4020c217f77c341de5edde0c69259119becb702bec0b917e4ecae96fa6e4a0310a8bfc985f6d6561f504ab768740fe7658fdd192b426f15e1ed19e358c53f92da556ee54fcf8f193be50519a068d3e31eef681c21802e8bd9a6370c5cf1f72fcc57ac066523809eadb5f8240739ac157fc79c9375135d9b26ecccafef83a6444cbf21bed23f2ebaa098f843a6ddb82b5d56c0d614ffc483796817f314e00b96516aa46e3a214ee4b591632d145231d610b9ca44da618247469b87a05da46cf4706df8237ad4a6a21a43eb2c7cdf56212ff0f7629a538224f4d9cc1835564d41e188f59ce8bb246b27cce1853a535759d406892e3b1f874feba9a7cdd8e9bd93bf3daa19a68d3de1149ac6ac90e9b6d4a2f68e844356aa18afe35b1ebb6c716f0e6c61d66524fe6169b6d6aa61a66506a8b2dd4b06e82dad1912ad46fa53b6aaa39a91a2c29baec8f2887fa8ac1a072de7ae1cb312c29407d6aea358a2b7f2f57f43a153392e6657e93728b1ed55479966f1bb9761d8395c2c0c5dcf7731be90a0b44568f17aa3061b3cd8d84993290dc259e3c9b04dd3940989352168e3123c4d17852a5189cef66014be9feeb30e4704a7d385b544f2b255eee6e378c53ce2c41f70a0ebc6a9260ef29b9336612e2b98556700194e314d8bf36f82c373bbc8650a72fe2245f9f826dd0ce23728ae3752536e4e8f56008f9ae8cc31b48c4037b0a08d1e86af83f38a5b0120bd8180a83ccc060f885723bf72ab7a886eff85f7d794eba31c83499ebbe8001a8784c56a21b650af049a288a176fd9df39a40d15083d70ea7402900c641110145177a001577d84e4ffe35b01f200a00f8247c925f51fac6bc052a9e2095d8bc40ada3d3dd017fa9016269090daa74e4c4d8d92c1fdea1262a4d9acb070d871928af85b09abc40f35c57e7ac5338abfe04c8f55e9bb1c6fd1832e3d56d7db8499df61c3d6b0eb1204c9f7a27a3fe8c4936a711cbe4b5023e45b243257a7731790108f571544e3b58953326aeea0dbd37e11f24406415c27a1922446c6bb975f259152c1455ad86041b950799d2f536ad029f34b6c26ee0d1ebcc2991a656016369b33e8356fefb8533c6f2b47255ac7ebccf9a52ae4bc4182dc97143d317ed6600318ce891937cf1ef6e434bce95a2459db59ee5f94dce542a3cd714f2a01dbdadfeb0a21151415f444c2584b32877aff53bdc0ef0437b815d85cc4b12163bab4a864d66c03b4ebd324aa4237784a02ac4c5d83d9b574ed7fd557072dab2969240749a572edd7d6df71c5238a4e412f311e291299db1071c4f743055ab7324fe332cc2296abd226a2ed327edfeed84cfacae05cfcf59b2dc352992d61c90c515b9c1e0fb39dce239b1928095c57afca37bc9e03acbf5933a9a7c66e96d5fc48a1b4ac4ff251b326dc692bc41b31c51118ea7272845aaf92dc5e1473436b013306b6689df8f59e281bc3443067f62bf39928496011e7fa33718110ca326a8a106f559b0185e24154273ef7dbf8ef1339a3c89dbdcc4cd9f45ac34cfcb9ff5753a8cd04339282ac5919632cf060ed1dce783a2b4614c78b18df041516471f41dc3b58ff00fa942f124ac04fbcc711b3b30bd65c4f2d144d7cf647800ca16b527c13e73dcc6036f888aecc36613b912a2ec1340d0b38c251982db2bfa955a218ccb737e8469150c1ae137870083969846ba5a84dc41137c540f8f9cccd23bbdcb6df9bba2681cc55ba7c2c2437c61ed42080a2cfb9002bebe1410e15720da4b90b7090f694c0ed5a1db4be3252492713db93d50368c20c4caa8555417cfbc39256e0ed87fb4565b3483032fba5a84b14714007ac5e6327f11e0d6d536fdc42d5ae43a456889f018f3077f87e2e5361ea0a70739574d50c05afc014f850eddb4ce22df982776b64ef56fe8e92ff4005cff40b8b1f8e3c1af801a42e266710131357803f2b0c5d2d88ac5270c999d3d5c68ecc854b75c20964e4aef490e492d2c4d931161e94c7c5fc35f56c49cd3130f6c32d837949847aa662e964981eafb92ac6de907df89c4fd5a777121230168a055670d66ab98ec2c44e000efdfedc1bd50ca9f68662e40dd3139b869b5b43c80f58c3b1aacc31a352e546d3ef2627ee61039a258bb645b3dc9de39b2a5107dc3fde770624f8ece56042f1ec4ae5bf0a73474d135ce3a748bac1b55b8d3190562be436c9dff15645ae2229a914a66156fe6a76d4bf39fda432d41cc1b97ed1a68baf49cf6ada64dc5a4430469a64cad6f7172919fe431c23758b0928ec6de5efeebb19507b0ff5744ae034d966b635f44e9f46b1014f5db4770cd64af7331666c243461a4a21b0e88045c4c785a15ed00dab98cb6dd1f590fbbc2175f8a9247da1334eb2db0232fd2b8419f8b521d0f63f233964a88dfa8ad9bad66deb4e5e427cddd3229c98f87bb5366260cba314b550051de642ef195aa5da089f0f9b2522683a9d9a49d3af99a055c40df91a2dcab344e8ad7985ea2b537a88321d2470f322691bd2ca97f42ad3d59e457f10d2093e01aca203985b9d0f7763edef605a3ba6df3b93832056c12f4cd9ea768370e0da73fd3627f15437f84ac820e6c8112cfe3578cc50c34f8c8d722e96da03c0ee339f034e180eda0cc83e7e1c7e355cf2bed004812a51dcee00065b4dacb8e74c65b975f67d45c0211476c98cb8ce476892a77be70c3542282af57aed638256a52bc82634894984d314632ee46a107f99a0ab3a39c32b8a0ec4093f75f828398080fc157a3d429329642dcac41b299dd8842a186500a0b66a25220c399b90a0f1c1cf1488c83711e0244a74b7ecd0e6ca16be55938c3d011c4d6d957c118c6f45454494fe0400acdc258382c926207aeea034e28467dad460909757055fbc4b78d3c544d8fd0ae5340ef089822c5603e40e1dc08485de8f22dfbb6eaff83023e58f48d30d69b6e16e897aaa1af40a88006aefcc0296861889bee2df00a2ba3ef06b79b5c376be1c640dfda5e68ea9d5e6be4f48743fbbb7c5282414b75d981b291a617d9e961396564b5fc2e4d075456115e15664aa25d6c2b30b8f7fde3ab087a61011b291c1c2ce638f9803cd9c1145f1e404b061ce6ec9621856fef8c0e94699281cb52fc87b7808125fa604382d857aa0e80ada38e6d7f7e7ce4d3a8805ac77e669004aff140dc4964501e447acf496e42a82874ec67db5096473f67b04b68c92293f1ab501af9f6bfa1b31e3a1ad5a095ae95f811cdcd05267e19701383e0bd73cee3ac0a85b72f50c7b63c5aa9929b3d0ad41f06a93cf5f08ca8f5b66cee514181db0e0aacd7d312237469c67c0dc34c0d14e8fe6cb0fb8fb87303fd779bdaf808e5af9fc062e182cb90dc27b0b5d4651db80c510a37872246817d3bc75edf9e5f21a557368aacdbbda15151ddc850d44a4fe08d2be16584ecc626f38fbb1b6284e8ed1528d1221e2201b1be5bdc37b57f8340b42dfd355fee2475f3f72d27d5c72a447917d0f446431ca3550f70c566f4def3e06fd65909cdc69bc19227e095503c7129a72e7769176980a77b1b0229d5249c98107aab0578ce638e95667c3a917e7d1d86b0d1149cf645387f5a9d4a49f9ba4e1d7fc99da29add332d9b71a02177daf949cdf3ac6b03eaa95d2b4d0dbdc6865cd0cad05c81b123d0e304952ecde8002db7f5888f00cdc793a9cfc26af409de47c8dfa60a0b39a19bb8723b6ce2befc11f6525b1b1ad38a091d1d6ae2feb2058b6c455ffa6140d7698c26fe87f3176fe9adf02ba959110e7d58c1796d73f7269e2d624510a5d5f116bad856a497feb245a5e2769a08cd0a440e289710318c57dd3e3c8d5f15e29cf4d5bf0d405fe96ae29483d875159561e600a00b38b552c68c6235f709d03bd7cd51b28a8c58fb5c6460c71172711e977ff8f35c374063d02bbafcc99c777969577e9d088b9277775764618777adbc7d45319f615c9ef8a5f839566030e4d044b881628bfcbff5ac7f947376ac5e876a912eb54339b6691d64cef672eb447ad92786c87799742c796bbf925ab9fd5c5ee6fb7cb85f26f7f3e3becccfe7e37633791fc6c564da3e1ebcf5b82e93f3e971dd4cdf87e33a99bccfcbfd65761f86abcbbc7e0c93c996f9a727959807c5dd1d1e00b109aec410bc41668cdb77258890be64f6c9bb8424c88e5fe33439948f9a7988a8f23c1c088b84cc02ccb3af96aa320b4c4e72bb0e25740720eebad9d6b041999a6c9bc2ed9eb6e4da53996f1f42da5ef915e1bdf56d695914b49eab51d0d920ca7c3ed09c3bf8d867441047997b18137a633bc5b8a29da7d23154854b30b191158c621faf28d92dd3f37d78e600457dbb902e3909ad277467b220b78b2cbbd764ae020e871739d12544dfa5ba57a1dae4d27ca1dba04bfa510aa41b9ea3a68eba149a8c28837320ec151ae80973ff34fcb27e430a389e75e27424fe49341dc919a922e65003b3294c53d699b54013bf58b34f728f688b6b754cc4cdefa6f17cf445991af949d22e6a1c06a4a07a3889d51be300ea16bb7811b81c63c66dba5511a9d9a3f7fe4c304379ad95b69b2328df993e1ce1e380c9cab3ce2d8910f7b4fe9efa436adbbe6b3b238b7197b24db76fdc64b181d73581e0c467c6f2dca7e847ff9923dd7165d75c88d9bcfd70c02cf3757ca59869cf9562213de99baa17913a3aa93f7bd4b52152af4549ce2a0d1fe703a15a7c1a6864b8590ef8fd53413aae2b1468af5b249bbfc5b1ef755bff562b4c673c775926ae987bfe5a88b606819f568dae1e7d0a9e28ee3688940612f1040ab76a811e38aa847aed9869c08177ec242a8c868abdb75184fa3fa873d512aca973fc5cd6ff3f9f5d637c82ebd87a0af99a3fe4aca859703a99030a3dcafb155630427046233f78d227d2fe65cbc3ecb79cba70ea73d4a2e92d0b2e6d0b56f236f6ccc8975fc5441ed43e2b3d6cb744dd3698a1dd345f51db90a83573094c605e829602e1805f6f11372a8844429a2a62fe02d1690713e5c13ac0f198280beaeed096afc4bc9068a4e02dace986cfdebd7d247776c20973ecaba25751a348ee1fcbb05816585b773658faf06e5d92cacf791f2197acb6706e5355cbdd166d35474ab03a7bc63be262d6f18bc63fbaca6685a9d08ea6a1d3d2c4a3fa6efcb1f1dca44acb214b074feecb58a49ba0aaddf01ca8f7487bfcb1b79d4660d5a8670f4be43614338c566f8338af66b2386e83e57d5d659b6c73fa2bc283a4992cf37930c76ddf1db2c89b215f46c526f07892866546a02437670067cca9815309c247cde5762eb9470801223763fcec0620f86ba4267e7877e318b393d0b172525c123bc84c4ac12471ea921c54db73894303928957c5af43647ba0206650ff589a27c080dc5820ec949980dbc417a8c52b96e0086af14e7af1d062516d465bc7ed8f5983c8e005dd9582da5590c2027237ae389748aa30937acf26fe72269de0c551f8334d213ba873020ac1c85c796a425a0466bdb09d08e00e67fb47f104d1949bc8ee1219282cfc20eb9790689586f66cb6ec81402752ca9cd844ae7ff7b2169dc314a90feccbc34be8ee7a4dd4f9c88dffe788acdf060e3e1356db41a0dfbaf68471a3387df544a50ebb9c005a29bd56cc5c20dc43589f7e658a78328dc1d42793ee60046778244f55ad06de7110e0ec5448a5150fc477f7d50b440726000f6cd69fe12174bc050b75512a7d27247f6948ecb2cfc6c5a7e3eeb3718b9fd160a0832270a0135835f2fc815ea08036b0039a5ef04b0c0d0291345a977fd1395f3ad528550c0c5bd6fbce9f768ea5d726956d9fe82d41989d3aa69bae6ddb537a273332046edcda5e6168936733fb50c3b42354d9eeedae9d31b9f4d73eae3d124f5bf61c4fe92688eed990dc56732990acf35aa8daf3ae79f84388153f4f3eac81c47311880da52f84e63cf9993223968ab1d48b3d5c5cdf73e380ef849ad97fb4652cca368a0ffde4d94b4ea4442aba2deb6d7944819847707b325c356d64895d38856a80e026b2916e8ba5d5c978129c61e96b1aa4e6c6695fe45c4c543171c5c93c505252cf3437aec04252add6c51cd42190589e3ca9dfcee89c7270e121ace1065d0a6ffaa158ba939407e0e5b06e92e0529c852b9763e1ceb9700bf828efcf371071569d90b3790cca84cfd215770ce65ca3116a2dfb998791d77a6a909464a2ac4011c5bb537c46855ac7332bba4b507337e5a46c599d937408a2576f7dab16ddd3af492652e0e6ac71f86ac4f4b55a36ef08e98d21bb36b1dae0afcc6b607005527b212a4b10570b8641b7bd9047c92eb881dfcd72505ee9d80aca7fb3e758f58112c5e8000703c990726e5b8955742062e1d10bd8c390fb2fed1611000fc1d575e984fa6d2ad44fbf4d2829c7ce6c60228888bc07dcaa49963fdbb495b2c9396842e88b311a10a6a4acc09b9c3fd4e8468ddc730b8d68f977312eacbc5654d65591d726908d1f75dd49dfd2419bb8e971ce3d9c9b1a0b43592c881ec3b25f72c328261a8d073abb98001ef0953249fa9d671c1ac85fd798d24aacb653ec2b360df102e014a3587c3bf5e232838c3679c212af8cb579f20012fb6ff471fa85a9d4c4e5add81998bb016d6ba2628593a4be5c426c9019df63cc873128d32506aa27dd60767c011ce0d06603145710d1a362756b11d92512a2abde254f052234c2d08af9ab94629cb8b325f99e6602a2fe6405b50e3a5e16da5c7d6eb7288c7b2d83a317ec4dd7a58b5ca7d15cfb20f582e0872e496e8833a7ed9b5c01e7b81cf0cf4cc91350e08d63782d353195e6c0b6bddd10b5c8abf38a4e4f1f3a6d6efc3b7bce5bec0e7c6eaa64b72d6684f98907a431efec5c929343c412459976bf93487570978dd693b68cfe616e4e396a89c0aea5dbd33a123507968cf202a760f72a5b0374844f1541d84fcd153f00673f753560833f2a7f2822e5953f498970025bda1959d7c40ccf1ed4d3b3f36b6eaf83aa84ff590040f4584044a4ebf46d2c1bc1e133d081231fd51e58adac426c4853dc457bb8add01ef71c56f3f2c7184db8ed51f079b3ae95681e07f3e35b160d9d0a160729ab355911f4636a218a48efc178223cd608fb83a7bb337edb0ef0d668cc0d642229ec20279097c293fdf8b0be0d65774cbeb5d701de19f50ec60cf5b76fd8de336c61e4483e4beb1bfa87353ea35f5c10d364b8fc672484fde8df9e360fb5f1b6ce9c3aaecc4b78d58c0afc1d08a551292905a54129299557ca251012bdb939b844b90f7be76e9ed6e4ab584c5a2d40e2c7bd6cca7483565c57bf623d30dc81fee673ac2eecf6af5415388ea01f6529c03cbe1210bcd13fd41200318ee500fc427ed4d500639c0a0071077e682b00e338d7807009fed4d700a0f15482c609fb555602d0e3aa81c133fc53ad0348c6590fc22be047d556c24e40abc97f4698d8120ef67b5f44b8722ab9ffdc1a5e4a9dcb7f1e1e7731e075c6b78fbe2c383b223f46bbe726f6a0a4a0d95dabfe5247f3ed4fc43db979824dc4631fff3b0d6e4c4b4f4f061251cd1a0fa58bb346956d434aada6a261a29f6a07d136c41ce540f85188b5c2be9f77290631061fe89b93913f90e730434c71d85fa0895cc10fd8ef1cf6c88c259afe04e5db2f8397cc392c8a8e87ca5725a0ebae7c82a10192152538cac02d2fd81f29250764b25c7b4049e84eea079917040e3fec41b4d9ae11de4586316a3a04f832a21a7f04b85afa61ab0ca099411791fccacb5dcd0f5dbf0c9a1ac61af59e2d280b523e44522ef8197a4f173992d82c80bfa40f5ea7bdea1433d51851d44376dd6a63e034c199b40902885b06eb0ce34893215b560c6bc7f558f93284bf541b81a07742a086c1c46f2ec6db0cab4e64cbef29e880e8ad187189b2ea973886b2865dd755f9cba3ec3f64c585b2331c81b496f7334e21ff4502456ab167ebf3446e57022c094e3751d524375c1eaa79bbab778a56a6481abe00d233d2a3b171e666420612b24413655c6cf08f72b6e23483ce285ffb541a9bc72c8e88e1fbf3995239d52e9a8a747e86c1b3c2ebfd88592a3c0d5ec26af97eb30e232e6263a06e1a07a126709322fdb10e1edac5a833c106c46a0a2660d6fcbaf66b29874720b95afbe65c3c968406abee71060b45ec3a82469b5058264fd194b67784bbc38dc502d5731563b8de42f362476d2b48a6f6ded1b6219c5b56cac31860e3bd7ce6a010009ac53f3d28d89c81d59cdc7415721e4071aa29dd44efb727181f09c11e426b952d18950440b0df2b04eb85fea59a7e807d7de2be44a458050963a6c90b884b48a943b02387407d949d773694c9084b479f4c1ed1b89b4d2b2b3fffc09714be98597a50543e6c4e9fd9b17cb6a1ef01aa8c32fc918ffca6c449578a2a5a440b7f752ad3884578e0a8fc3d9ff23f53f643944a0fab832252a19a786e3ce856fca3e0f4cc58d18e30c7a6f12f4414bb2410763ee9f2cf85558eeaf95cca29fd5054fa8058670af487afeae56f4050cf327403d01bf3746d5305cd9545c52779141251463e610c6232755f82420ca94a0eebf453aef0dd5bd87e357a25791eae69312da12c81d4e4d2d861b95c6be327056df69b1749e993b45bc80a2355718ee1437e44ccead763df4ebde3b38564d254399ad11112d2d89acfc75c91ea49dffc6326754102d5eb33436d152fcc0c81406ab7dcf43365d270073e07ed314fccf47d684d06da78f1599c4908547a1ab1bdc485edd2af2a69c44cebd183af4efa6746202da8a6ea05f0eea8ce47d31f7628b60c436a7f830b6c85d6fe382265a214cde976daf0b832b29f0b1c043ce64215e78e405ff47b5304853646f066eb7667df77f4c16c5875ee6b8be093513cab97feb461fad7514187e1dc5c668c2abb06fdb34d50e4e39fe8b40baea554bf4e636c19c097aeb35b0a3d098b9b1812f71b710f7163a65b2c1a31c5a29d79a00be32a106044593034b297361ad0b042cf200f35e6bd42b083e158faddb80f2203fa4daac1480689e4df279b50dd4aa06b60b7d1d35d3016665fa01fcd91d2512b26170fadf5cd0cab94043cc759ce601a3c01dd4b900aeacd426a13f4faea7b906c70f9243384e1a915d6543f6c16ee3767c64779ada62922724c90fc96b0b15d4b2538643d5ce2c7b25e59d5f8e2e26fb85f5f5e004d3802c519c3203d3d543e218e79720736b1238a3fbe7bb4337781d16b4dcfce43a5f6d218ad62d890fead2396e1dce05216bca7025638cc53fa4e85ed20351ab74c32137412e598104d9b8ab39d05376398772699e3d0b2b30899bcf1deebcf83e44a149f0c7db2c3a004e9a289a1993205d7df6ee119afc3f497a58049d633443b1d3f7139d74cb401c1b52e66a6e1b3f6cd908bbe9f14b727854e1979e66a6674812ea347e7646c4815a96afb5cfbd661d205ccc0fa2011c31b41ec07eacefb66f03603689d4feb439a20f02f62e251aaec52bb36edb755b1fb94a3a0604441487f11ee194cfe2e942140f4d5f20bdf8012ab00238664886ab9cb0ff2d492482a24b0c1f59dd451c7b5a41b22909da7461a6d6d20f2d377844b4ef50174cf566831a9a40a7f5be065b6f91a0ecd62d0c3615d98eff27241532b0642e6bbf1fa3997c45c8f65757cbe04064f0ed1bb89a462274da39b4d0016caf2e0b25aeb1ca4b0dd5cc6aedba673822814ac8f2e6bc2e3863a64068b5cd79480a4d258c39635a6b24a6ae9e1064adca7c9cf4aa3082624b68786e5a395b1d130fc2cf0fe6598085e6680c3e207177de9fe0038697980dfc44347e7bcca0e97604d2d49b1f584f55259027999c964408cdffcea0ab8d39a690accb280f202b9094d796ead11d32bdd0dffa916e99c0c2dd48dc8a1b4d3c7e7ef126261ec03af6c18605f6e0b799e749ad2047e6b5e16d4ba98d5da8de06643e1d51072466163a31698ecf61de7e739bfe78f0c0b1c1962e873a6461c2309c91fac52ac497a73d00b1656595f468ae8cd6c54fceef4a5c07eda1763580f1136e40b307652f27824f66e06321f31e5741663200e86e4427a6d8b2eb0504ae1b7028538003960bfe43fe2c8dbdd3eb0c752e66cd63b7538f411d9f4a9797ed09a485a165f5adea979b03e94588ca8dd8e578f1c3366303cbcb3c61567ff73df77d2760b20b41848ce129494d6dfc820bfa294627109d7baf2b023cf585ac50e5c308a4c90bc7d2d352333f905476ceff916ba9275b5fe8c90839197562ace3c3832ff32d14c673c34dae322609500d8ab6171870add5214ad8dd20f56d86c8fddc72cc306d3ed3549d6a3b67149e89569987eefc76d3c47b920f0c39a26844b6112dab6305ccd2b26fd1674ac57d0806ac0b654f8b205fd850eec23464798083ad1dc4c1a876cbb140544695918fc1a8a23d5be4c8f901aa0e36b28d5d988520b1df7a207d779b9f73b652342fb86b9d7cbda4733a65e1a0f7bbd5f842d296034b935b26be415c5574422a67b35c12aed5698658a9dc57969c988a63e7927df08e5fcd72e039f22e314f1f5869e45021e0b939ac2a412a7d01b8aaff04116e40c70ee4ab321849b15c8f253e983438a776330740c62462590c3a9810fba6d6c8144f13530b6e51da5332002345dc9c71c5cb8f6370b2ce0f65afc0c4060671278c8dd3766abfa108fc18771155df7e53d257aae6d02b70f170ab548927f64148ff1001a666bbbad2823b69b381abdc916b1efb6093476b389538c09444087a82141a201d423988c947c1c4983620ac7b19b708f91bf06818e52f239c2d4c821cb501c1fa47e5db6df17960f1d7a8fe9d7ebca2a8df571d7aeff0f5010039691ae178c6c5878541f59cfd7fd303ac7ec4cc828fc34c5d5fed12965ac4fb8d2a9ea0261680fb2bddbda4e35b7b15e7819eadc8f62b2387420f07b699fd5ce4adc6822f27cf9f3e0c79e9efee6f6a841a3a5b98976de48309007047a04926b6d6880380a141768d3c7015bcc37c921a280e72941c895e2ee37dd9a2307d81af377d220da9d009cf44578ebd9132e6179167a73e9b02bf235227815c2c361fe786d760f25b3f446c384278ce20ac6db5cc9ecb258075ad63541c91074f27cbad90b835cfe29918f07c860a9a78f14650345293cbd15484ae0e9d0e3b7a4afe1e649afee3e42cdbf0b2714a60757613db63fc8890d143dbe76748e10eba3700ee6ee10feb5cd45b61a3040a9928c5c040e16184b0bfc36c632f24d2227078c7efa039be39cc7e9372be1673bc4d4745fce53bd9f3572d99cc186dd1c478673117bedf47a22e862c5ff6d70544aa132e22541f50db94e1775435128d6e0591cba16085967c6b6a7084065e9e5fdbe37c2e9b25f3c154269fd90652b1d3d4e08f2c4dddfff270530fb2b9a3f0c17dd4e4d7795356d5e5dc61447e6d6b70b1199450eec8ddf12ffe028458ad4b922ae4c5e1fc8db5dd3c6b96cd76889502bbc713793b724759a14d7a4443443b41dd446921e302cf32197e5fba83ea9b012df3fcb285d1760404a128555c6d2dd8c82d98de0c03584305c3e229f573d4c4c36fde795cb63807b74d8ed8c6399fe555a43ff051a39328ccd8e6db3ab1be0ed8a3d916074e4962b678b328b0e3b03fc75f431fac06db0d11ea8eb0218ca2a0cb510cf4383458dc4bec7a151cd4e1986d7d6bf2bfc7f76a6e1160c3552091a22f8507a148a5522184fa9d7ead923fc5d2c6e21564c40a86f6771e35e5173e430faf6ab4982f2605ad57c02b515d08181fecf797ec4a2c9f0075aefb469d3c16fd7d81177053fa0ef481aabdb78ef0ac7f201aa66dd7628b44b1752ae219c1aa86cecf59a27cd91ba5ac35f186d506bd62280e14cbd09a338655ddb02ee62b4154dcbe0efbf903ece3a1a1354a142df59f164ec726997598b5846ca84dc6f68d2e800a4c3f618db0dbdbea7440863893fa1e4f703cfe89b112a998974722d8ccf2fb5b92f4be04270ef4f2bdaa1fb02d26f06eb4dbbcf2c61e9d27ac3af3982293ec56542f3dba6466c43f4252a7c23964c87c50df781ed7e3ac5171b268764359eef9c3568ea68de4f3ce172fe5c68073fac0d25c4fd648c9b5bb6899201cea72ee8ee0cafb8a3b24f069b31ee1ab94a0be398d65a5385ce1b5b105747fa1f9fcd53b7dee1eb50258334df8dd645a36e190e52097e95c8af89945ec5f24479a917577f627f92d9c68e519c310d3b2f3e7246bf832f594dd538761f80f03d8bd162d838bee098c52a7e6e916f26b54e376db3b52eee926ee703839948271da0540c92b79dc66ec8f23502c94584fae3aa872b1a6cd177f0d778bed804331dc5bd75033e46a6a678de07040aec00c7a88ae8af3576fb4852eb69294e3504e7bd40c556d808faa63685622c768e61df16c0267acc43b43ed23878c3edf14f8ff88241a7288e50a30d15c417b70996e631e8e096c7d401ac3c55564e91f9cfe49e2f8fc7eacb576120071fc149911ad9c6089df55a58d88fd4e57478da16dc93de4b98a56c34a35d4e0fa5309ed275f128aa2a55dde04504cf9aee950c32fe36c197cba411209182faf4a4fb9048ddf027c3af236c23f0a46be4b96ac1f4a0397eb1335ed71b0e135fd03f61745efc55bca588ceee6af8912be94014cff62f78b4cddce548396ec0816b034c20b7a3f2b49de277b8a250b182906ed6b093e09b9a9af51d9f2b5ada7ac21e7d00380babb810ed58a8584860bdd28799754429c3ba450d326425b32aef2608706bdf2ba0508b1c947a3862ba6d77cbe7093b19e7f2e8cc34d5257795d74eaf6d56c1c9f52907ac4ce488ef6924bc79b81bfb84b58fd293fd64cedc06f02c95d7e2aef1fb2784c7a18453b3f5124d1a1bda4b6f73f5497a7f94fe67ce726e4d15637bf32e0252c0f1028e24d0c1fcc299457400a92391d5cf607518b7c8ef6d1bcce01207d92bc4f6458417d4434f0bb5dded8492a64dfdc78cd2296e0a101b72bc7b54e8000dddba524de77bd94bf3d81a917226e3c1682621959a474c50d60ed0809d462f582b36d79aa7bb46ae20fb8d4256d9773687d89ce400680c4ea830f8218fa61b873609ee3c6c2dcbc3ba0dfa9b9290ac568677ca45bb78026e63e6cb0926f3e768fc582ea2db859204b3695955e5e511f6c6300bb63b79687a05d2fce004193e42996dc677c9a536da0c155a17265c966a97e73b41192a1e7b58bde78dee34d4442e0e76019ee3d8fce932f1d5c14799eecffbc289f75cbbad45a9132d7d30dfe96728e4a71f1bbce696dc9e7b841d7d08a98f778a9ac0c2fd18c04806e5e58e1e5e8fbd24c8072a4154d5db4ab165314ac84d1aceb6f3e1baa0f90473b785d75d49a0d10e04daf2b11a0aaa97debfa6f5f0b7e194023490cc70ae86cd2983b381f3518f778a626232c726a10f09d789a50cfd8a8cd7b15cbd6ececf690ad4b07ac8bbac6556153d3ccb75c367dad330b328a251c82714476136f181c81384c7140d991fd609b29328bc8ff2f5cc44f74180cecc9f44435794892a091b1b5825047200424255524b3db06ed62ca1e094c192204fba7be71bd3c5447ee9bbc98ea4956129b6c00e095633f2fc1cc913c82e5079a8fd1928b181052441d3cd1ae650a737a998377528d2272172c953790b1067c54cb37fdd4e05a5f720f2abfedc07d68438168f64526e7410687b5d47db534abb199c267166d47a499e6d25d5da2aca0d7a123eac68a5032cd9c0ccb5975d644be889667a36f9852443c6fc432d654c9c39db1915c2bd0f5bfa5f5b412dc171c89872aa293818c66647aee00f5e050b838be453ac3ed31cdf311d3fb7d2f21fbb771c3ef85375fffe09387a935a544b529d1d8b4a7a709d05ed54bfa39d10f9d9513dacffb8818ed539096947be8563bdca8ede69c7371aaabd22a28a459beb499812493b265fca57ac82a3ecbc636c9c48d7ce0ccabff9983b66c61074e4ab2e30bba7fa7b927eca67df37bb844bee9de42540c37f3b2933b257dd6e3c4005fc6e95de9a6616faac131e1c17939bb271adbdfa85ed5576f59c305da52c6542b93e068f9d6f7b106317e2ba1e6474cb4933a55033ed66a0678454f9017382bf8ce6db638813d4320f6c63193c1094a297bda6e94955a66260d04870b81aba0edaba540d9efe0253b3970567714e45e80dcfb255ece69c12c0f50931c59ef93c204a8abe8ec6c0e1502802a68b619b8d2c1b6a21e8299885d9febaf379c916546a879044f45e6518eda6af3dce574d91091c0022254c944de6a616f9ebe172504db1fee614dc0a7c3a4484f67832741eebd77e3011f7cdcdf1a267eeb321b430c228b7e2d2585970e50631aa4254570fce15ac8ed4853a315e8a1c35606c14502a843bde84150304c8665283291817bdbdad0e19d2597e80932b016853570d53369ca044a0ab142155562e1f9024ffbd7782d951efeb402c65007ced2de76dfef7bfd31d2f550f57b4815a7adf5041cabdb6ca9dc98736a7c38e3840628d6a8a8b75a4779c78c2fa53975efb150098d3fc34523292bfbb84cf20efa2dea71113aca5456d81e2a803d61f6ed3e1a368eb92578b87818367a8a5f84c2399b180a4e43f23d2baf96775bd33d8b1d278303643c66ed6ee5a51a49beb4cc25aeece784cdadf6f660f22226367948f40bbe2d995d7627f182dca38df2ee8ec220ae588f005597890c94fcfefb1d0378a1eb0cd07cb449b6b03a957147f18087111250e9c901bc79c80710f1daf5491b7afe8e7534c0c956aa2e106c77a3d0c7d19f32e134dd538ecaed0c252726c33846309503c6d7a85d2fbcd583bc8ea6a3214fd871a8977bf4dc3066f88de9fbd7107fd662b6d18ea095027e08c08d6aac65aa75ad319082e8c46604d996dc345197b5e4bbee2f1a377726180e9d80c9406981e9a0871aabb4b35eeee910a31842c0e20c38d3e7676bb0e2faaad1b03803f83199f061f00a76dc3408d675e04fad36cd7ef017f6ce02790bc42f9a1fb15dc3da41f504816168ef3a23d89837085e1f6ffefad4f613244b0c47fcd3185b784c115abda180a91e7e114f71175086a1e34c6b0a57a06e970ad05817a2436897a29a8e129f38b686ce6c1e7f7f27005c3b91815f25496d1affcabd21502dbf03695ad6aa040b03114bfd1b7e1672a6c1c639bf5df1f222782785b1f2d2cd2d03605ef58690723078f9133cdc0c29aa247824800e4d35661a3da977e4ed5b82fee70190d882c707cf18d0860bba1df3b4d398b4b4415598328a89d413c9a48331028190ab45f62111e7a3b2db2542197ff6617f3f4739932b58a0ad4fa1bbea0e894aab0aec3e5ed0138f88074f488628df87722c2025eec7ec2049ae83fb9715b95082fad8086db6c9cdbc5d8a63a007f2030b23164789c959b0b58fbf6843a8db1d34f2dec96e8a62cf30b14344d6797d8e41dcbb4646f94a00db0d122dfcf5dd0b0826a60327b91e14bfac6c8f74ee877691b4f92bdc0c6eb00c541d0248e08083af9a9d720d5e601f752b82a9dba7ce8d8d1a8a6cb35e1706db00e5157c8d1f9eeef3370946570b89438575986065cfaab17b628cad7eea5cc239951a6f5512b3e4ff510d40002ec4aa1b46de1242054cc3095ec391ac5d18c4e38641beb406554b6c0fbadf8904c555d7206ad9303c6b547e535e4d6b7553045925ab05e263fde52dc62e330b8aadbc60e3c4eebf3e8d5faf4082e585c7de45fa5373d31bee949b162121cfa5a216294a225ba231a2a52933c49ad343c5b68680275f82794a07331d75ce29996d314a2b0cc5d2c80d77d3486ef512449005f5df83ae171fc8ff9101af73b682c3ca3d3502c2700ceac0ea13ab68098b64dc7df48fe47962502b0bb6f32f22049e6ec783b7510e222382b1c4174e6707e284ac8e2ffd3fc49742e0fd5b4e9a08391a526cde9869b493c8f8683b4e3750076bdd3e6c1c61da47dd038c5101926a1120ac5331826bebadfef57ae3276a824ea0c7c0fef50214bc3c044ab27f1e670dcbc187a443c4a2ea8dd84885cd827e609166212199aebfe21d7607175ffdca0e4176593ca10e50d93bf3b3d3b10ee186708a2ca3f9f16925078071a59e09d62a5754a3dbd3137460ff0312a3e72a668d6c602f17b4cbe78c2b7db61c0016b29c0568246422d0ca298d3dd8c2245376aa457c54134691df923f61b79ee60f896892ab9d50533120a7a95140d271e8f469d874d0741f4b87a381fd5dd68511b64aab8b91d551771de8fb2caf9a3113b80601897acb29a151c205eb067da4df339332b8f60c632321670f37c08dccfae4ce32427dc681be94b651e4907e6a57cc908a0d61767038bd8fc6f2ed7c8282f0a7becdf2224f1451abd1edfb89d40682db44d8fd692d86e82ff29ba3cfb95501b833391e0b93252d1c63806c95f06ee6026805b8f770f1f4fa9f050634c74245c2d42617d4c3490c1cfcb6aa166440eeec76e001086f9253cc22b5edb9821453888a03c7adb2408f68306ca59308ce986813af8e9eb4442b02d5f9270691c7766dec53aba9b10b792dad88c2a00fdd654779011e47c2cc3051e7f51a4169e336eef82666e41838d0ff6077767d23d70f49a1184c570a0415a95a074b4724c249b566336ae8ee27305dc71b2d6d5f37c984e6244d78f7a5de1054a495d45c0ddecc39043d4e3056d2cf1604bc5a12d245a3808eb68e13ac056b984ba1f44150341baee1495d4c3553e2505fe704f06d715223f9eaef9398ba695126ab3e4b46220daa1ba3d0babc202c772c559fcc2e9d1c9e2f62e745e6131cde22baa9dbff856d04820903718d54f0e5ade3e47e33714bc99862616ca67052fe1ada93b407567e3e9c783eb6fa149f2bd1379333b61c9d780405f05950b858f48f566334e17d0af2397684a34e7dd35c46998b8e4a0fbb37d38e8304e9557a9628e18f99f7c0270bf081182fbacf44564f638a56e4b2171a5a9a3848128d2566ce49a98585a7e51b7e283c7dea70356b6103e622f957c8e2280f90f14dd5648c48b549e6ccb3d761331ea736e8f61228a748cdb604cbe9d569acaf624ecfa74ca52052e2afe3a30ca8b28a6cbae080490a098585caf883e6463e667e8ce17c40e93b7f791b52a8d8f83f16f038ba1828ee4128c130084f7bfdadd543eb1dd6325347bbdb045f68c82928aec779478f191dddb39f17089929ab0ad5314984a4952a3b9272a52206a66ef035790b3417e03464db03e17bf6e595a4d0f1f051bb212026a77d0ddb5336b8790a0d8e0c3a338ec72757c2390b7cc0fca81edab9f1431150000bc43611c80a4021a3e6aca4124f2012de186df5d478e12616fd00a1cc4856221ae16da26eda6bd818942a2d0ff5b90802f2854ba27b35ed6f3cb25ce9224e7b241d49949210e59d851ddb4fb09250a0890b261afc00404ff309558d45d5e6417ea163af18b6a77cd4020484e208029a0c5185a9f7e6ccf416a085d12061eab2f208864ec1af79de31b968d16935185f67c3bfd60a592200e401e48950bd8038bfbc7a11f9fe4f8e22285242b6c438fe2e674f738bd5a2a1b4a93d9136d353e79c7e67f19af1c95a5d0097a38b9be9cb88ab4e46e18c50f12d8c4c94fa18614dce2f776f1dd90bfecf5a563087019633871b2276550410c83a092d5bbb53493c1a587a38e20c6dc2ae26487ea2d2577db18f600479483b3eebf1f9f62e36d26fa617d9a37e170eda01b6ff6eb49260cd260112b9630992790760dd27c7713645deb32085219dac1d3b235729256c42fabbff6c5d0aa0deeecd6a00fbd1d853ea0958a0a214a1e7f2d5ab4a7de272a1a93e09802baaa4a5fa3a6be3e4e3728272218ba3e39f24e48ae8789726f2305f346336cac44b0e6c431d99a816a850fbe41e7623c255b8bdb217b9c2be6c931d9910ee0acaf7f56a816b55491659a227b22edcc41f0cfc413a507ad1ad2b6fedc2f545cbe0585fb6facd305d821772dea202d8667e899c27014dee2aaa874fb6cdce31175558859123ed8c926da454c1061df420f9464537d87ae40eab552f9645561d17f10519c13c033594054bc3900aabda7e99c68b18ec44480b47de30e4cbf36630dfdfc0c3fd62f0362062ba37208412c738a1211aa23f16f54b2d970aaa8265054202f56daf57f7e4d3bfa3608adf8cfb55f99399b0f0150801daf7c5945473d041bbaa59487e8e21ae06f1c162594425dbe838733f6987432b9417409aebd24a2ab41f8afbd4b5fc484125c669862679f1646f151054b21ef9d4374c8ee88ea9832ef5282bf3f15d900005c71ee54244b6914dc093261ff46553e3547e8a0924f8a65c72a4cb7523e811243abc2ad806a77d718422af0b5b76f08cc7001e372bce1a2d2b9868fec34c4da0818abfc92a44e77ab00d58ef8857da7c0c2720d98275920e9b69f8edcae57f4dbc46dd0dbbb47e4a0d5923ea84fa19e06fab8affb6f3fe89fbeb4f800d9a4556dc1c51c1f2d629f152280fc60e7f03fe1fd61750f28853fd1a8cc68b9d4008420655065afe8ceb9bc6b81c70b3aae7baad021aff7439cd062e50a7afb2adcbe9a4daf12bfa1691e96431bbb8ec7807acfc793497e32988dbd7f71b080442f0cd674ce0f434251d7678407afe76307161423ca28a9bcfb140526cb53e1bcc47c7e99f66b906aee1d115f644c6da628e7e3a2e02733c0a9946f89b1854ccda06d6f7bfd3b162c4d81a7d6f970558976b61d87686dc62d0bef0d9b550bbff2045bd65766535c3c031eddca741964988b15b2e5757411b13bd15843c2663e835bdcb67d4b3f5ee1cbab3ddd9c8b0a7cd88532424a6411def013e35416d6ecff7dcbe2d70681cbe48b978171e3875b4e901780a80716ece03ca1a86d45d20d6d14ec7bab8fec74ffd86d10a8e4e8706f1f4c361ebfc3d420c4050b1ab278600c3826dc29be51e735b720ff5d4d9def65dd044189154def5c3be66c57d302f5fb102e9d798c699cea363e4ebd422a453c1bafec36c2d4336f96101a3a60fcf64d35c15e3bcd3ee821c7272f3fff7520cf10a7efd05c34faf838f56515176606e4393df835efb1c8e6b2fbab8e9c35f5d0d216e3c7db5be7e1f1e743f50e8d6c27b10d8454f4505f75df09748b4ad5014e3418900d543d1afac5901d4e0999b987fa9c4da61fffc744b6fd7e431ad1a9e86dd18d786c935a411dffd2aa89ffb16307ca302073fe257c877b7c3a674b0c38936336eb47c4e14f8acdb482b51992fd269233f5b22934f773be9aca91f18ea67a2891190d68930410100020525842576124d8ef5bbb1488db30c60f30bb9906868e6551dc2832fd595605a5518b69b7bb0da9cf1e07b923f1a49f0c49c61cd1199c70578e93f380ff1c522f33c65d92f17dd1dd68c9bfc61573d88447a3cc57afdc05338079b554e87f183f279f41e25effe5281612d6b07650ba85d0ff934023eea6373ec1cd1934ca567e4358bbf0888d8db115fc35f2f71556ca7c61a3f2ae993c764972a6cb315f862b19d0e8cf9a996a7cd273a2d8a5580246a67a0c32f269500103bd6b7a39e61b3f2b5b650a241c59b14e118aac3e33809dd58b5c50bebe569f64f436b17cdab26f2895e1894b84f7ff32a4cd445e96edd5a976c5c8eccca78c6d0da30ad0fd58f1fbb6d2907957f0cc8656192045922e340eba5cea65a6e3564ac98b41af31719e4dc8bc4c98c5f2c78e9b06bebc9fc6890c1ff558c4c282ae9e6a3bdc552a05cb601ecdae9836dcee9896ee3601d6d88f48e4dfeda9a1dc3aabf7931790ea4c7a356188d31203eb5d33824bc871306d96c3acf898cd4c4e827fe0976d170521a7c91eead701f9c68223e69b3ed9507950e69fd006311abd6f715ae6aa466c7fe8a06abc7452899c43d4679d447607744a98a12ca26aa68c5dfb00cc5a47d9175c9169930f70c82803f49abf97885bf199571477c002ccbb44bd0e2f63a2fc2356624f8f040bd0e948447703425a474b4391b1a8733324c17dd8836feb2c62791443e269d7f384a5adc4e062916b90a03c087958a18adc482244116dd13e250872b80f8d27386307cc0406d604463e1855245f26328b92115186884b2863caddc9c35b7784a4fb2ce0c03a9b0f05508e00439a2bb8ccb46b27b72407e0448db8386b9a51253a490b29a262419c36bbb2ae168d293c182474e9126ab695346513e9f675ec7e520981bfbbb60a88a51928d986bc9a6b5fa303c7d6bd6a14fb099225c5a11f6c646d538115385ca3e5b17bbcf93a2f9fd3dd6546b4ce949c4a3f2dc6f617a9336ecc12d39b97e8d5aac6c6d5ba7982d677fd1c77e12f74daad8397978cc6e7c7960c101946c22e20af369100873fb469814c1ab43ad8193636c08cbd9a6086e5ee25b792896a587a03aa03c6bc3c74a9b5c6698635aeae3531ed04d0da982b652e962ec3e693618f06408c3437860db07d6aba549d0ccbed9e06d2568dc044484c96aa7f644d6206f7b41eabb7043fdca30f3f4d68d19e85f629ff200030a6440b643c827b2d9fb00ed978e208e897154caacad3a32fcbfdff31f671d112984e97f40e48d3094ebe5344244158d5871e05d78ad1d69cfde4920d1f4ec28d24ad5a733366b931681b3425a119c69fda5a06b90a5fc531bd144ced1d6ee8d29bf0c131085a0e77b2ccb9e0a784f6a325c0174441686b736a358108e271b3fb489f61eff7257b906d0bca7cc022fbb1681510cfa22497010f53e1ba2a0a2865d745851467003d3f96f526e5fa2c32dbcfa788b7d44a080cc45a8288d90286a106330c59d10e5f1c276127767ba53cbdf0a4655d3f8ce6b8b380b236e1f06ba48de04b79ada5f95cb220e04bc07d449bd76ea8387c3073b363f0d998fd12333cda9881490924876a812fdd4d22c2eaca5810e7bc58764e6efda0d7acba6fb57501ec2c9f31ffdffb09a2d816ff3d29f5150442a2b88c848d23bf0bc65f5da5288a7d5ce9eca2c6d7a52922ed9bad134ee00d4e024db4ac8f4c218655e3995c49c175992957189c8779870469e3835455c9ea371c5e817c265b0a0a75000825e9ca811a2b45ecc0f8333b3c130da55bf551bbef52d2867630e623dc4484ccac159b6a8307e2c54f628a6af097aaf362c70f47bae6bba00be8420dd6a578f8286a48a50ea9a8dafe34f5361e922f96d612a5d477b72314f93551e29730952e74c696cd47e0a64c12dc730269288d03a146ce54b4e0127471ff6d3d83c4e06e747e0790914bb2d40a305ed03bc3802e0b07e8f74d978905cd15e6eb75ad26100a0fe83b113f94c59eeeaf1f97218185db5c19bca3241c489d7c385ffd9199278d189793bdd2eebc483380c254f073c5b327a352184b0644055d0833b77ece4541fc4a0ea39caeee5f35d003335806502b3850feec2b0281a38f59c83978e69b8d2505ea55fc2918784760a770cef0bc8f75275016c4a551112e8a262e8c9c34df3a5804b6c4a15a7c59967e74bbd02712cbf2cc33c671e94084ae91bf6b3b96e77afd0e441ea4548c96d52517d54dab0789aa3dcd1d3c642e19907d84a970ad1a669fec6a3218301182fab725e4ac8e0af2a751b6d86c5c927a2c91647f20ea66255684822de11182b719ae78ec09a3585efe2161709bb73a4bc12528177c24f7edacfa22c7c8b0ba017a2e7a82f924cf138581b41c79d262fc0eee5322de6426ba87574a788d423b236102ca6f21d9dc611011804c20e21a790029952b4c63318fdf3aaeb8fe548cbc122e84ba4f5ead3f011621a7483ce17fba794cf2d3337d8222aee3c78c78f8f706ae6ee025d7c101ca1b4fe13d49490b24fe150e66462624492a4941d2e36a42775aa920a605f7dcefc314494cdee6b04c24a7c9f907f3ecf919ee01e875dd0daf271f86479b1045cd3019ec5265cdd3afdd96fa093ec48e648b30bb0cc58d71b1fadad908c22375dd167eb776b89aa333e1820ba79ff017a69316435ee8c4de2c58b35605bf76787d56d1abe9b1403189d56566c31eea568dc3b7d4df25381961184a725074cc8de31a1e2b26e09d5e3e9758b682144441a92b012f7e8f9e7278eed01cfc26a3d0828460cc5fce63ceff9ffea40723745998834f7d0b7f52563759432e3868ee6d94cb387668edb911b5c0f340bf38b01798a2edbeeec0f049750231bdfbeeff18945b0ccc0d552b916f95bad4db0caae787bd3851287dc358a337cca1d06925f847dcecc682db407d7fce548f4d5e6655a5c29ea1d42ddaacc0ecccc21d70c73d32f7a80e4120ba25b4add2f98ccd41ace76696b9653ac31cbda67a2330f097df98be79ef0bce910c5c858bb9df57d42644799d5a5854e8cbf3abd985360737ccf18ab9b4a43314882607170bb6707458f33c8225a0dc4f5a588c0084250067f36941aa8d8d1402ace0a56f0b1bbd39ef083bb1d4a4ede89a1770c2442415e8127a371a4b5a418f2a1c1dd4f310ee2474a11a7baabd8db79a1b49ff6925f0a943b915372701212c5477d213419bfa7a8026c6b30fa09e85ff277afeaca2b51d1aa79a953f58488a5fc40b330217b3537604f732ca113d4563fe4fbf6eca98971065fc09f08d0d48c27effb84ff87345abe9fb429a663c257c9c249f7721f430b0bc8df81117115055c7e6a39030f1eaf3fde5e84b3fcdc180b4f1a20777274a1b8802e4a3f2c3b858cf211c7b757d8db1c289da46c6587293e62a8729543bf57cd3a216b7823657728079de0f78d9652d070e90c0e8178a9a4d6253fd14d6115c71130b7b35a46b0e3fbf5465f385af53ca9189a6d8adfe889a8d5a7ada9f4a67be6b46dc3ce6a74e18174bacd59f2bfbd6b7c2e0675b591339484fb34607a660b9b2a4b4d2f736ab47eff196ba177f76e06980ec38874537d368b0b3cab3efedf79722d0927a9cd87b737ca6e2632858f716bb9bffd60088a3bd2b7d777a9cc1f208655e0bd8874fd9abf7f0b03781abd2073420e69428cc22a2805a4d93fe671bfd8411c66dc9c2861fff8d65b15aba845236372d1b4a3a10af5bbcb4900f786375a78931f44b83b6e72d46a27197c048a26fa3384dce1d6742906cec98cf66ec7e39fee5e958e4a3d0eae20247427133129c93ac58cf63c039d360c871b894c14be8e5bfba4ce9503d76dc3ce385aa1eafb446cfa6c4116104ab5145fde1583e70e61a3e341c630357c2a0cdb662841f39dd8d1b9785eb49c90ac99d5bb88c9e4dd737fe64cc0ac08f51f51f217acaf6aedddf188b8718c05367ee2534b0c5bf07728d2c3ce714cd5ba2f00f6bd547da0ba41cc16d0cd2fb63ae5abaa54f6dba8292b0c417739897c78deef1547f17b199c8b762536e2f4d23f14d2f96ff95665c8010e69bdd1353d5b245c03db3537bd9231dab411d5af6244bdb4397e965d774032aa61631a91e3aae5a89fd1cb5e157c424fe6b5f53ef33ac7c3f78336460308d3534ccd6e2eb2f5952fe90cfb86f23ebf59a212c549c4555da6fa30870b674e50f9a72ddabd469916b7694307e6559c0244c4c320d7a7cc8e8ec0baf2f458d52cb586175200788dae82b430ee06e008c1cda8de12e094878469e6210c9f722cb79cd6a2da7cbed6595ba2fb35d41a76547018b556917f3df6fb42885e5cc8824b4599d200834c9888785b2d5c8e5538b9be5f4ef36b9f60a9ff52f6a0048a2a808c6ba95b2231de234871f42d4f23c6f639b9a7f81acd2fd872d2f7b33b404703d3123b9d17cf3248c91defa477248a72ef8ee8a8a0e8ff3aea0aa0ab35ff28add742869003d3e081f57221c3f628ee62f1b14261837213cb833bbe131c795ea3ce82cf0c0ddbe1cd6b9619173ed6b55de671fedad8f72db9fc9a07183fb00b93f9b4377e106d21808d92cee37cdde1158b2b6972a164275ade93ac5d7e4ad77778684d4190695f8d2492c8ac3a1aca9d8e4a0884f80864409bce07016ca5329298e4241144c7057f53d7048fae5b4a6f79fc3867affd8547683868605a795a3975a260f4626930b57d303531a8770dcc61c4941492cc591b8029eaab4a39b7fb073561882bfb8886dd55de6ee7cb4c9266612b8343d67d8c9bf9a6dcab864fd6014d598b7a30d6e099be9b610da87f7ff0f92c594dd5205a2b445ac3c81804b3686bf8813cd5f422187d36af91a591be03957f936ca85aae7fe5c7a287c7c476daea110ae7ad9ff8a618a5b7df846f065554581cd9904a4b8ce7ea7b7d3167714cbb994f285dc7479f2efbdf5b35641ab60073ce1c5ca193b6b56c0c2cb34dc941fd1ff80dc9a9bad1cd82bc5415d146ff895af5609233c89abb0e76193aa178ca9056dc91f34d00598dc6e5b476d58a7edd8c47a52a89c5ecf328d45c9c5dea5be67ea1bc3fe396a8bc01c7c0e367ac0be9644401b987ac20a3daeb89c519967744eb2de376e0bdf5a63b9e51aa237b98a8d3bea1b78662778d3d0570bb52251a367aa07d369c530e388f1294b70b9d3f55bf71d6b9fca054e822dd9161aaf38033e789cae3b2a5549e3260d2d95122aa1f109e6e77b9875d6669e2d10cb5820383faa680272e746dbfb05c4665c4118f6c18c7d193d6b7f334f11982bd69c0ea9de560865fbdba27ea8040cd3575c53b1c427a9a9948192f20e91216d571c95a947045246f62e15899bc9117c2d793d9fb20ba602b8895c422bf2b4cc844d9a5b36d16873454f65e329b419d10c9982f6dec51c55fd069b40055b445124184361feba82e395fc6bc400bd6b0f2a2ada40639e788d7283b4777d747e8ffe1f1e023f29f56c9496d78b4a35d872acbb8729b107aaebd77ae486a3e63fdcb7e2869a13c4e628faa7b782a12b06a712ed46bbfac2dbda7e901bf2f501d7221c126f29884d99bb08ce10b9f703691ac08f5fcc990fdb551f4f80196a9b747ffb26fe18e5b6da0d13e4ef3d04f851cc6e59d50dfeec711af04f3c36e581a3003ab9a4a758f2e72597bde9792b7aa350fd237a0a062a324def9df1adce914c024f9e63e30fbf5e6758698e440824c125a02a772fd02d099054cc532900554dbbf7eeb63e1556350a200168be505422295e139481cae03d398ac38e45929156047949514da302aa1dc0b3654f09dc12892f839828e68eebb22baa797db86d91898d156c84805094899d883af0ec5b16953013ca73932e9a09aa9eb487437223fe4e7c1f789622d73b7f2a1a172c33e4a8db13f603d875283a71ba69bc38c16455c02f024a0840d0c953593d4f1704d40cf83392103247a6c4f7dc0a1f35e99ff1eb4f02de46a5fefd2eb59eb064553dee13cbaaf5e0dc930ac394ec7089a7d8822cae272fe80ab0869ab735c081e04efd1798ac4a91d7c3e1bc4e91f9dff1934205b40addc6c6aa3d4248356ad2cd858985015bb893ab8258b69d1b19b41a118bd6b40be0c8bd73576e47588510f54a2a27eacf8a0d77a25b12de80f3e0be8cc8976c8b317c4f29041fbf09775f58adfb365602c4dde50697cf99be7ed824682d6e22f1c2b278d1e432f732a79be4d8720c76510385f660db5c70904b52c05ad12c6d83b9cc4246f90cb9053441fdc08806130e5a374fd483ae91f98652e3aa5c5ff4c4772310cd1da5c13c7c60f135f5201368d233be5de13efa8bfc543784fa1a910ddf4469b2417639747aab8c31d1f7f9c27fa4773671184042aa34751fd077a47838f30c143dac0d1337bf3e0f81fe87920fee1695fcb66f2aaa4ac014fe48a29cb00c167a5c42b47a06df14a4570a5675f2575793983249bdc84b70efc22b03ec0574c3ca91c0d1ea292509d82fc38b0823488a693149d0f49bde2f09d622028a551e33edd9231e34d7988ea74e2b936044931156854cf956f282c5cb100641b5e60e5d2248c01781e32b4d07455ff1126e557ce6c72cf1ac9bdb1d67558fa87e1678c22e9a77e14c74ee22f3807feca8c14c37a6fb39e4ad8449d2c0eb35186eda434751f923c496d0e65f280390010c7ded2cadda380e9e2eca757da94e636242855a59091326e8dea06ad10c91e9c1b409923a4b8a7b461eeb4bc465c9a6e189aec6d653fbe492eae63460646de3b907b2dc6e8ab5a645142adb77b156ffce0aab01adda4f6bc4a1f2a1a4554874556ef3f44b4aa7927b763bfb9382fc7d6e3142e0427689fdae88e89bd5af89f4c8a1bb712dd23f098e083aba0a923ca425f32130af077bcd2b2916dce3d625255a5abe8b048622f540afceaf7624683a2088fe5931b46fe96ca153f333e4aa3914434561eaa777a33d280ff2ea45d90250d60a603561bfbaa61bbd9129150cb0b5bbe205d9cddf7801c88a96e04eb6af1a2050afe381876d59e40855c298ace2bc8d85e44e54757690779a7b3f37ae0640c61e35ef33b5bb31fa2cf332ed46a37616a4dd81272fedb656777404f5d2e94db66f49b01b595f797cd863473654354a9b087a1c7a1dff12007a056db1e70ab2d065058b5540bef0aecab92ee3766bd82d715aa78610d1016117f0439bf0fa8d380de9c90a66f7373ce0d404164092cf1b00af083cdd42190baf16de8811b36b8b0f1b603f45aae2941e4d3f30355689b6089c97aab61650106b26299a68e9e628b66ea0acd05df6e9b0140e4fb6341c56fb3f2b49d2f5300f85c14bf9a4514fbb1026e009c71fb6116064ebfd8adbd926c6b1477a93df4a0fe8e3efe437018ce24f0f3e00e86efcb1190388903de796ef2efaaa17b1a3a6408d4e2ba6d2cd1bdb00b93b30ddc0a51182ffdf68ac2b6c4fe328de899b8780a0343b07e994d826ea06c95dd00b220c978849cbb9654035e9f79abc4cd784b8b0081894cea302ac52925d1eb01c8129874533bb3298e625b0f6361a613bb822dbb00c78fee995eb293acb6b2c9586a24d8867f4122061e3709132ce5350cb44925e0a252cbd1ae472fccb6fcbda3f7d750c817459fc9b0ff282730297cfe2fe38f18ef0cd36dd992b8c6aa94ffd176a877ff54e9064cf1786c1d0024670e15a8b4b087319361add69f617e2e13da19fe71b20408bb824350b0cbbcdf607c50a98f9ff7056e687ce48a59a2bdffed8cd3bc1d9c0eccfa5519a527f340c313050a371609769ce100d97659419ec00519c68d31bbc3702e370e6629ec718f79fdc7e755513d2997af30a6d7161542b5590a4e347805363a4a01c7186c403e0d38bc5a424f496c5b8e9498560e8dd0cebfd6d8e78299cccc3bd604c7a08099bd62cf07018641496cd83e70b66e295ea9a4eeaf73bda0d42fc31349460ee82e113a833e563a73fae64f3226eacaeb718a47c5a854a6da887ee54394c1aeb0458a4714fb5c1559f6a200305a20d8f4adf176d0b44a115c20168449b322161fb86cc6b0ad4826e0c8496c0a556d954547a0f617b78aecc848b383e4cd812c29a108609c8cdcea9c5040d2f9383407be2cd6f81f4303438d7799a73cfc9e0663295d5823386b7d70c0c5a5dd20ee32e559aaa851c66101523a7322c510cd9f872ad7c69f8624df9d28af606af6f944867bda596234c8bf4736c8d4b89c2eace1d1e8a4b70baa93a916835ab7aa22b4ae5f7dd7264c3029d889b3902ec6dffbcc0411d893894931e714a4393856008c239813d19f34ef3c07d9b41efbe304a97672c6cbfb539791bd88946e1a6621df6ed30066558d2645b2d51b2a186b1ce6e5ff7d01e8a2837713c11406cf4895a0d2aac24b51b4ed750ef86d786139e01edda3534d4cb03c7e231807a7126696069433a978a2b11922db85a0042d108974b18faf05736199c0318b34d5776054a10c1dafc20b9b4764b948820fcbb9183322213a1a47526176a50f7f258f0404de6030a9f488599200a5c86c94aeaf706bb7ee30c4b99cdc24728fbcf3ac5a307e7e8e115ad0a5d07039da6dba8f8fd67c8b02e3a3edab5213d5da7fd461c5970211b0ad77566d2c1ecb09651d75d1e21c366b9c3a26c17e6ae664e039f34400cd14e696261e74e01178280efb52b28b25749f929540099200decca503e18391dcfd72cd5bac8006bbd5f06c13464e36731047098ea193f3fe68d4dea202ae8361b62bb5674fd3c54bc699df4ac68d4c1f5cf191f359d2d19ee49d1e06e3b4f63877763695b939c175fbf412b0e90f3516a92b994fd9f77665718a47960e34c46a8e56d9fdb6b9785d7d45e8ab100eed588baf44ce92e073e0971759426f0b7d3f512394756e82a53a9d33115804023f886f20c455053e940b5815a74fc342d593263b679d51ed40476ac53eb8aa6464986d14a505fa2c29d04c469d2a759bb5e48de69a38519acca709491bc4a3d9b81ad6effb6d19a7cc0040b42fe2ee5c9cd7f8938c16e7f10e8634bc9b03e0e6c0f710b63d27ab011b281fae53f62ba35561c2ecf31211d457042c9906e6d1708cf905154b4ad201c8fc124b559209c1ac3be569981bf45c6aca6721818d234056d868061bd29648951e530a176f67c48c8decfc4ff9d56738486bb18428169d8c8215bc3722db039bbca1ad9a29bf28438e8219b3a0306f6ac181004449cab1d36f6813cb073c97095a5c7d362d713c89134da78318145a412dac285291d71b46dbe78420aac67e9bed45136f3ae6953b49c4741bd6bf27c36b2b4bdd919396a10f4e044c192442d56c777a7e996cf43408813ee8ed042146327ad1a74054998cad6df38c6c2b83aa69f7bd37e2875c064deff39ac7ba8ee4f8f6ade81b0d60ee9375890eb7f7efd518244b50f6f945d8931b419ae4c5810161d5edd5f817361bd2e006272f8b2cf6d0ae762b2a4988f44fb040dc038ef55df09996553c0aa9e43897516876d546214dd706653e75ea193944c9a5dc602c11efb49d4d6e95b4c1090de0ab76618c7adae93acc3223f7961e74670d4d42bf4fa025a73727c5b30d03a4a070e8a87ce2448ebcbe1e84ba7b9527835c3a47c414190c406d557572e248f1eb10f1f9c5e1920744bf8b68be13a137f7b1ca7a1190022531c150463f760067de1421529f2f61c188ee4db2609d97bef2da59452ca9402850732089207df6565b2bf8544f0081641a30d7a1d88df6df56dde06ab50f3f5fbd5fd9095109bbff91bac027d9bef974cfd4dcda7e8df106d45d9bf73a3ec9ff256f2531d808f8267221d203204f5f687687ffa1e4fb2272204687bee512f04a8fbdb0da9f9fade0da16ff34086eadb705fdf06afe6db809242ccf1aff184f2c4406ae0a3ec4f035349794c64ff1b6f25319023b2bf23c58bf254d0eb0011143c13214242bfea93d02f9b27326425447bd4a3b00aaffa435642504f9f6215ba477dbf6cfe83ae79fa5bcd7737a47b6ae3ade0d70f7a6db123a15f439a90edb587dd90ee511f8770af7d77432e46e155fc53d2c71c7fcedbbc8e399ab78a1848863be658ac54bdd80a62201d73825e2bd804496893d81ad21c1d8b7274e112630d4e58910657a481cb90e5222405901bc90b2dbcc87e6bd7f50209d5ac78e47effac963c73b5ef11b4cf7eccd7f0377de4bb4ffc9ac9ae878d59175cba18e2e28cfd0c7f3ef1ad8ff4b1bf7917df7af233cfbdf899e7192586728a1934f0b7ee96fcaf9132fc552ccf079282ad15c58d348650961f6448dc8948f07b40583a22c317c184a40cff2785a2d7d75c86a4d0d2d2507734a591b810ca5f9f11a10b92270218573c11c06002ee804e94618f91e197fc799061972bb0d5b931eb49c51733c1d67fb7ff8ba2119ec31770c5162972050d776c52ce39a79c2fa59c36db9cfe94b9d6ff6296f53f98a5e7fa9f67095fe28fbeb5de379fcea73e907ef15108ca105a8f5f740c592c4126092f9430b50084972d2dd82d48a419353b480103162588d0e289a62d62738b319d195b70a15b8841a9194b4a9660c860669571c656866ea18104b373fba67756d97b8b2c5a304519b63c01cbd812456495b1254b96b991b624e5effe18546ea42d16f6cdd6b0eb7eef2d4cb6ccd0656b5826622953a60c0b7e81f272fb4fb28b9cdca646b37ef7f6ee6eb923fe6761ab7b60cb855e9d2de1daaa533825907c4ec618234c829fdc6a09ac29a86f9693c1b73d9d70c7c8f9d55a6b941fb7236f8824290ba6eca797e1af99f2eca4a4fca920975bff6b260d18d9eeee6e26262626978b4986ef4955d392620c66703f1b7b697a8e0780e6f695fca960d3b5fff512742dad2c0f85aaaf540f74c09cf9d5da7b55aa5676c1d6946f9750fc24fb691be62055d99af5b926fd5a6badb2f6d70db492d74ab98ba4f0d91aeff8d340cd7f22e4fe80e42099c67f4372976042eec740eeff9803b58d86cdc79d1a2ad8aa0f9f8657c37b98537ff35ab055ffe10eff9bb7d9e48dc7c1567d1bafc673c156fdebe980adfa284f02b055ffe481005bf53b4f04d8aacf79405a8707e6d4fadaa68327cb149c4aafe2b6ff3cd5f65add34ee87cb30833e66439ab4eb9e7377c75ff7dd799d39eeb5cff571f87365eeebc7baf92f85ec8edab1f9c6df4f7613b2ffa781ec7f3d0f600bfe8da703e6d860dcb0756bbc8f391d4b75fa6ceebabf71a7e6e1ab6abc8e3928fc7127efe336ef7f5859bee67d40240a4a217654fcb5726deff45f91b627af15734e0fffd6e7ea6f27cfbb1e1db6b055bd4f951d1791597e66b1ad747a1c44220c3192b8d2e50a74414ff318d23afe466419953ce79c73d61fd89ab0c8e227f1e7fbd15c417cc6fe7c13e2ce8d39f3edcbf9200c691ef4e7c331b26a65289ab3ce6cd68c5a18e67ebd4494e79b8001fb436590ef418fed2b23f041064c4bf9a79764a6734eeb54a8143d4c5f2818103e9e2c1f6e71fd7f787277b7c084e7a2c8e4815aaebffc20e2c84d25cb8f1e5cbafef1a793b29491ccf5971f619c21739f94b317a17960a07534effee372a7583b435afd576e99e719dab73876bfabe1cdb670b832a83dfc71f98b70ba31c7b5ce9362eec793e3cefdf83d97d33aeea4604efcd87d0deec793e3b7e08ecedcc777f178d0a3145f027087fdf820e070f8fb0d84ce1cfee1c9dac72e04a0269438a1927f5a296bd2f2c0963f6ce9e8a40e522a3615dc61c11cff9662ee4330f7592d1b7f54d9a6541927b31f2edbccd2088f688e94a98b7e5f1c198498b3c31373a80c736506246b70e51708e13c9202774cfc243e7da7b28298de9be1ef7e0c77cc9130cb2d32b37c08675b31973e08439ac7189acd5c049a35fc75964c2d2e2233842a9cd6819f7957cc850fc5dc138aa4007740813bbe7b0477f84b23b8a33271273c1ef468f8a793b21391031450f24f336532d3c781f4bb99c6a78e545ba0c5fda2bb2315a175705425b0b6fcb0f29cd383d44d722921183e7fda1cba1f47a6e255d2f5bf18639cb5e79c2e1cace772cb6dcaa8f2ce661dad83271839e7ecee7e48c5f58746480e26f55ba7fef421c516ff84494398326462ca626b1701b6a0bbbbbb7b3faed9e3f86ba5ec7ec5039e1e4a393ee4e9a51c1f466f03bd947d208e5636577208639493520e7ab5eb11e3e7d891d28fcbfdd179b23b4b75c56dcc59711b773d7d296448c6f59762745cc9f0af28b9d253759490fdbbfb840f27b7f7c3caed41958e3f1e1cad9fced32e1c0f5b9d642395ed5d9027c38fabdf3fbd9eac3cdb66d9c755ef677bce93a2ed51dc442090264d9a7c3709a56c1df8acef813f3cd2663e6590bd52162836fcf5fbffb8f2065cd9877ee7bf7d147faee97dacbc6ddbb6412faef69fcb15774ed9431ccf52e164ff93d75d8f4cd20f08ccfc8725822aa25451143d9c964e4c867f8a9d5057cb6dcaf06b986ca48d4d846e634f50a975e04327491c01a14027b00aa4028f9ae0d09932b1a6336598bc0a144c5c419255e4950c7f2265a772c69778910b9d71199c2805a804635264c2fb3a4bcfe610c628e5a492d2089d3a8c51ca396995b546e8554e4a6bb536d3a4a645e85ab559a669dbc6755d84de691bc775dde984baf2de08fd762714eade9a1a9b1b797313a1dfd8dca4523468d4a861432555aa085d55c345af03327f968bdecddfa4525cf4687034b8e8d5e83ef0ac6a1bd2868d08dd863dd9d6814dc3c6886351ebc097b1d639733f2b63cd43cbf02713cd6326e9c020f2881d7fc6f799d16394f188f4288f1c81dd2ebb7b4a1a32b7b07637ec8edd4d85d8e4a4d56696a7630e65091a2545223969b595a7630e65091a25c9492be5e9984359824672d2f9f174cca12cc929793ae65022cfd731f8d0dd6319ef0f0419e248d43afd11a9756c96ccca176a7777777777777785f639b03bf26d8eefd927da98d331c6ac63cc3408eb19dddd2e618c9c9547a68c90cccd7224732d995bb948e6d21cc9dc19c95c999dcc8dd98f5c2773617623d7ffabc9fd91cccdb848e676f61b63eceee700e69693ded48dc717361983bffc40cbf2a594d73e071dc3f5f72c6d966997894b48d57a1c2c33b761301ce54f054351feec0a08fdce41a0d02bfb1b776ab5ded0119e177d22fbca326c7b623c6101b9bb103a8cdcf8c94a3e5023bd42403f0455ee90116221314d1ad841f7cbef5e3506525f6212fa55b10c9270c2004654945f7012cdb80d238ac1888e60444cb017a47404dce9515b410225082a2a38620a2e326cd1c12f2cf880848a1978e88205b0178d2153d89129c6f8a17b6747f6afe8f710917dd9cfbedacfbc233caf3a571403a9df39c87d594a311154dbea71604289049411cc8d11fb9071e1b75f8b043b2203cc489331d20598111318c1e1cb4402337203cc4851246322b58ea3a40cb7df2643c9d43a2a801911820dc5e40c496c77b7fc2fc608434b1a394934ad6c6f1d99ba3775133d4655962f7477dfca597b6f7fc41edf230dd75fc6546ecbc923dd1d6d46b4c949abcd6c4605122dc523641153b5d55281444bf108592427adb45281444bf10839e9a45420d1929c72528144324a2a5f2b4924d71f420851dddd365b85907d1fc8104b26ebd6055b378f6e07c46a6085172f2e2f2ce8b68671230dc0b8b04bcb8f3019190ac3051be24289344567f450801740efdc036497518c9a7d71b9dcb0a12a6217d8d013d7e2fb4a7431a5ba57158b37eefcdf1f5490d5fa6fb170043fee7cf50a1b32cab57eaa5e1553cce94c23d3bdb9614344f9fb21a139612eb090676e580b672a152e1229e6b4a594d24ecbcf39279d73ce39e7ff9c730a1165a1a279e4439e734e495f6a37e6c897d228cf09133a419e734e2c6544e323b0a10c1844a18310171b8e74d11dd48408871ec6b802cb16188c74b183759f5ee41696b2ac634ad068b9a24b1429560c3151580b5f4e5d68c105172c2ed0249de1b2050d192d4c272dee029aa589e60b2d3472239d31c319425a64e1cac2a5a434833366c2b8c2cc175898d192452cccb8c00c0bca9419ca36fbe7415f6926a8b0d6d62c57cb8d64864a76a496192831a7f39c363fab65c60f66ec90bff7a49b37940aa6e00e983b26f5945106e6748f194230a76f3683c80c127058a0183298620ad7142c255a406da556903909498a12bb81e9a868c95b8832e68c39868c0a869ebce8d205cdd0051830c0d042182e60e12c8cd962b2ac8ca9229356c63ca1412b63ae54b732c6882ba4a0734e0a821ba29481c28c2f7c90e23585aab05a7063a680e1c42682256610c64a18585260441a28515c6865cc0e9c5b19738314b31e91a74d9e31090e72238921e38b6e29aab5958c279426d1689644c1c20b21a0b820e635cf1043c6e64612f3257ff781f028b6a0ae143536091b995956c448b92c2b624c50e36290d02a6214625ca856c2a491d10a183231322e44294ea0c5246ae0621268c8103a9131434cc2263752982df9941b290c962124904002893e62043145606181063394e182d73c1386898c4484894118196a706444982bb14a182459258c9558a5466ea4304f72cd8d14e6a80815b0e2102e561cc2a543056344c1c4e0352802e38314302c6842c4cc502162644421625ec494ae9832050531267257913ba926055b2b187f05b10f982bb5aab873614efffc9f9935ffe38139f0e15b4fe5551a935c8cdc726b189b441278a1c2cb145ea4f0c2040790ce0461460e35801223438c2362d090e98d17a57404587809a28a2180f852e545c974e1ba84b9f1324419de64ce599d9c604a35edbcd38bd10ef79a73ce39675c724fb991bc2039a3a90ca1ed7b22235e614aff149f9f9f7e883790fd5780410e7d6c16ee809ef597ed1e04dda5a83e92724892440c59bc3079cd3158a20ed556c994d8989aa04c566aa537475399262ef2cd8dd4846546116d884a52aaab022b5c9ca08ca628c690a9010b96f0baa420a35d9e684d2fe8d2c45adae5862c034d66be106212a509126098cc745d884e352080298c13d318aa026a43131193933056a0039d735230c23023468a1c901c21f49ab13c99a6c893490634620da9dc484d3ce42d3752130e612465f8d1e82ec51dcae431ea4931870a29d1324b445e9402142257dca92fffe3995bf5fc25bd8f392ad0a4e9ad84c89f3fb10af6e5ab5f9b73b507900af8ebe703ad405f7ec52bd09713688a10ff528413422ffbf28156c02bd05761858a85a2161a2c117ac997f3e1abe28e7cc800a132505642925642e4d3974ffdd531477a5826415871d85c7107befa371494ec36dd0d01aa3d90d6742ad87cf7fda25bfdedfb556353976a98b23f8dd1b0f13d9ed4a8a136dfd5dfbadfbaed04853c29e6f8db781eae24f059d306cc96d85ce8facbae41bdddde7ef6f663ed9eb937eebb29d58d3bd29e240a1563c4b16d9c57398fe6993bcf5b67f3e86f503ffbcd763db2dfb2cc6ef4339b59996bcc9b27390fe6cd9395e33ca781e5b8cca39e4a8c2bbbdc0f47a35feb6fd5eb2cb4b848ccf0639a3415e2499148a53c77dc81d9a7a2b8d97f5ce652d96718a789fba5f267bfcff1b38f19fe6cfe1d9dbd675fc2901e59771387830f20fa177c00fc99b59f5eb4418ec32b21f0bb27017e878372e67d36679cf7653febdbad9572e675b613470d4aa30d47cb9bac1cad8575c209e79c527e9f7143c7b993043faaec1f0d5045142b7e5431c61863f4b8e5091e1b274dd8c0f3449109e513456291a8c50b090915991ca44993c6e7918de50d35fb37fa5c4cfe520b8bc7a26a22d63f39965fbada5f625d8f4abdee7a38077f831b2b0178cfc2f9013cec7c6c9ba594fa648da2d4e2778aca501fbf7807f43dfbaf08011e8509206303c05f8e2516fe5a5808007f8f65853f55133770fdde00d75af1f7a3836e5fad78724d41404d4d67f2079ff37c5092c6289bc4e40fba447d84dcc32ce6caf16ed4b82b53ec533fc345608e4b2d2c1a4b3791bd610dc8fedd09c81bfce74271d173efb3b808ccf5c6d70ccb2fde87b1c3d503007f3600f8bb1ce04fc5c13ffe3e7f3cff1e4ff61e077f39f2c713e3d7da44e98713cb1caafbc0519153bd87bf1c8fbf1607f87b1aa90700bef915ae0ffd5738187fb2eb3ee89e8b6f237e0d1adf712715ebaff0e7925fea77dcf7976d1e6559cd50f02bf462f737b877afb3cab3e17dadfc39141a5e0aca1671df6f65d47f39bae7995f93a3f5af52394fc4eeeeee2ec735fed8477bfafec595399e8ba4622272df50c9b5a423d39e7b2ecb38ca610b5b12ca2f9ea3e6b5f74fe61abcca4e983972b45aff2ad58d41c95347b6fffde8c8a83ffd06034083fafa9dd7ade3f67d50f61d8aff745d9193e4317103e908c7df8f0e17952fde81fdf2fd25f605fb0b3171923f37ac044c4bb22b37cc045ee41228e503e486e500c599cce5fa130976822b9901b961279001c6440b8c49151893a6bc652fba16f59ba6b516af33fb5827921a679dd3cb26e6bc3ad1ac4e34ab13cdea4ceae957c8d3eb599fbb89eeb3fdc9e6e7e96df007f3667383fd04e13723f6d9fef4b3de3ca97f9db4e222ce247ff4e39c4ee41dcc8f4b68dd26fd4ffbfa9ff5a2bae18abf132e02b3866bf58fd638bda3c00957fc6def7875faf874ce4954bfd4d3f8595673f42f856fe2ac7e33dfa7c689bfa8698e94b7edb46d27ae7eed3e98fed0a3f1365e57e369df136786ba993bcae73bea37c85d6ab79f73faaccfd96969dc8113f5f6dd3e0aafea9f9ecea7bf01f7db497b1a4562e741ca61c7dff69af68ec43be8acbdec7a684539f3da4e6aed5b5b2b76249e83629957f0a00b6d7a88f02fe04d5eb0c4123954318a010c388043184e68d102247058410d395cb15750b9614655b2cc0d335ac2bf59fed9b1176d6ee4c4e8a893a06c12c61915e5efe63f82e160d4144b4121287b80830ec8d09c40d0c79cf8d38bdf37e6c447c11ffe14ba1d73e27773ec6253ac600112454242af7e4df1918f82c41ee020fbfb43231b9111e243fc49887de0b73782b7fa31a1943f3d1f213e7cf7563ff0e7233f1594b7e89e4f7c4ebae7233f7a0e1d9631614ba8904086fcb5324f5286ef62c2031d73461965fc8fc787bff9dbe64ea4d6472128bb6f4f7f939ad434fc6d3f5fc3dff6ddf7c4cdebdc6d73caf9d63e0a41796e34fb4fe6ed35ef93598b11c2cf73b7e14f7bf91dfeb4e7be276a5e47cd3f4a24fbec5108ca19c67968392a8bb8ee4adc968d38604b898bc60815741274ce49e7b4614a9e73ce2a36b9613698000c1a24312362589cd061e84569505284556285531284a684874c8992ab64e8544319ee2637cc06221b84446249b5d5c082550d556c351861312c2d6c304163881a441cd944228a6ce88a8aa22402a64429dfc80d5332c63605cb35c41043b8b4442bf77b9e77bdeb88dc95f2ede5602bc61c604542301a94b84c02054ab228499a4aa650d24493ac2289903fcc60a4c294ea76922f49ac50c9234ff2021a225c220b5812a15311b019cac06690413ef11960341055d92400b9614970c836372c090dd17b7a5c0fb75f88dbf1e1fca49472ceb73f5c766f4927b2bbbbdbe3ed6886dc41f22044ec9b734e2904f5e67feef04a96d481257570c90b77501942fc219ffe80ef23f1fd661718922da4d19c343ea51f94b7c94daf7347014f931d739e03beece4920bbd83ad727672b0b5e4cafc71b379d89ff46b17c208be1f32fbd09ff6b78cf3ee87cc76f3283d9e669e9848cc56de8f3712d13ba6943b6437ec0076c3ee86d2a11944f3254c865896ff9bfc21bf1842f723bec41ffc01df27e20b5bdd58368fced1e10d77d65ae99443d7e68621b142b680848a67b5686e189228f2f72e148c21628348250b37951b86a4090c09910dff0283c1467fa1911b068316a616130328228cd618a3af844932068a275c8a48f2243fcc206a8c71e608131e281d030683137983dc3018a82c99675ccf71499412a144a3b8243a8947b1488b985813d392962626a59e28e59cf40aad565a1ba15b7bea691df893d25aadcd52681df894abd66699a66d9ce4b8089db35ca669dbc6715d99933c9d22f4933d3993c66d1cd775a713ea4aa721905a08a5294d3a3ca184063b186982861f9ce420441a5bc3941c72060419aca2222e376c4a12295c822023c9ac744a1113670c52cc4821230b1990208105c104160505936856da4b2ff0e1054a60b00013c58d8290829a470a901b164452104e68315f98465cd3883cc3e54ae39a43408839a189d2824513054986a6cb0d8b028b6203ec072a301fae4c177e409267604008e52c370c0829f56ddc81dfaaedbf56f68faf6d8d7ae8014d09c26d1bfced331ce4f41103f99f9efb0ee27b634efdfa047841be86e36f1bb4f13568bc8da7f1405382a41ebefbcb0fe22fe5fb673888ea23068aafc24030d0942035de31902be6d4aff136700d0cd48369400cf4296f7b9477facee330d0945505b4df3e88f61b069a923d10f7da673808ea4f18c88b624e75cc61a013624e7df8db86210672a198537fc30edfe1034d09e20f31903fd09420dc3b063221e6d4e71ea8fbebaa49ea25f89f0bdd7cbfaacdf7cbfff322cdeb17caeb97f5eadf784053826c6f8381fcb53dea6b3c15b8d7fe7a2a4c09c2613f8a39f5b557817b0210a130762a31a7d627c00b1c5601680a09fd9a4fbf5f1a5661feebe4f5abf3fac50d3da9926fe4863d49ca5bc386a727b35f4d42bfe80bf1d77c35ab621f470545588130e3fa688665c68c0f4e686e980f3a74dbdc301f6eb8b9613e5491425bc3cd8de624e24f895c20fed08048fcb9778e7df4c76ec18f0d829ae4b9254bcf4a3e2cc9fe3d816e1cf3fba317f1f708127f402b1f2d730f8cdd2d19df06e9323a8fd3e6e17106452897b7d9f5b050666a657c80f6365ecdc69c30ae09eed256b88345fcef84222998e0434ac08006e00ef829d514d7bf3f8b2de66e67c79c74a134ef3b29c8743f7be54f68ef7b0ef1af20c1167c26b0e59cd7ee7a40bb596bed56b95c7f58d88d9c096c418f3a7577eb7d9da2ee24c323ff23aab8940cdf87cd8edd08cc8d20c41d4bff6bb970879d28e690b9fd9f1b811073e05b7b2f65027f9fdd4807f53c736e94e177f7f0fd88721bfd5165fa1076c63104045b0fcb64f85fab86cb723b6e7033d254c6f23ceaa72da9b8b1d89758d1101daa4c2fca8ed43f87743d52c893ce4965919445d38ba6852ecf681e94524a5db23d5ba84da04c7fe24f5aee72f84be1640af1d7c2317fb34fbd4f47a6143b713f55feb8c34179e18e18ab5ee66def1d05e07358a33f3a7226bda33bfd92af6aa4b9021daeb8f2d7594a2ba55c214f18cb5f11e89e433ef46253863d4690d4613e0cc17a2833fd2de7d0839e8cc1169c12bae39744a01c73b0052d7c5a1feea0995b971f7f7a3e12bb50e433b6b9bb3b2be9aefc8380441ff3fd7b62e4c9d3632e97cb45edd5b012c54ab49568bb7686c41cf834f2d8386942498e782573c4abf841f961ab842a2e0e6cc1d89d4d9f3714a411a8e22f3e0bcb752ed3cf951fca293d1cd86aba1e831e9872dce46f52c793006cfd681efed51e1cb863beff986f7fbbf16867f3d4b35af2fcd4cfd8d9bcf53efa9ffd53ebd0cf68581ecee4f99587a6bc3db579eaddc789f4b387dd08f32bc55bc7b1e08ef611946b1e677bfbfeb27be9ad6cfee63b6b4fc3fbecc7d752bf7d0aaf6c7e15c2cd777ff3dd77356adec6f311946b708debf9a0f096338f07b61c73966b1e9edda3cf350f147ca3b5bead9fa7e719d658ed17a1d5eb40f645e80826e4fa3d50cbacc53ef4e56f5a07424f942fe5f744ae75648f1fade39fed0ca7f9f25359aab2c4c9526af2e99fb897ac93f450dfdd09354f73669c6f1d7fcee6d9f5c8de3b1f36f3e2db1f568e4e3d2ae3bbc43fac8c7304182fe2c9e397fba8a0cf958bf1a6726cfcd59f8ff20680e6a23efb88ea0e7541e88cc228af4f9d77392d9bb51699930394c72f7a50faf831725ffb0bcaf665d7c3be773ee453f8f2e38ac80c7dca2c61d0e6100a8905ab6577ec43ea33d33469d224e5c9c11c6c5d4bd20658927adaa66ddb96795996616dcaede96ff4e7b358ac1c2ddc21c75cfa5fabb5fda7cadaf6dfcddaa66daf655a4b96a152c940056bc225d7dcb01dc6346c072e3b9cc90d5db0c089d32c3592bd83d9b34396a824c757ed70254768217cf7f93ef185ad31eea752e144170eb01d96e4861d41c90ac80d6b42260309e24779be4332aef4a79e43af62e95929ae7c2f777a079ee703b1f65e95eabfd572b97892341193e51f7dd91ade2b5b735c045eb7d2de9ebbe66d595cdb5e7a445adb346f03aefc01c93edafbd4b71afe5c59fb8afa0f03d97f8ad4b4aea78a3928ef6edc1677500fdfa24e3db1f33e55de7ec31fceacae0eca25b666f9777cf89f7d7db86df2b9df545e7c1b5e7c1be9a59ce4faab10e4d797f8cb7e83af3d6efc279f46eb58ed51efdd08f53ff9d9955c3fc29a54c9dbcbdf2a47bb1e375e7a353e7aa7dfbaf3d1ad334236c2f6dd07f5539f79a9aff17cb6af7994f76d8f7a1b9bbff15637beb38dfbd65bdd78d5775e6df02b6f15c2f61bfcf61be0d50dec2328dffc2a04d5df57fd05e282bf889e3e4e9665aff26ede8677f335bcaffbec69783e35df79935cac9fc2dff6d98d678359b0351fc7b331677e8de793fdc55ff6f4358f07b6e69f3c9f2e286f18763eea6718d6244bae6f3729a7d434ca498f88a7c99a16b3eca309b2eb113d8c429c5d8fcae5d9adf39d74049bdea8640cb8753d2a95aa1101000000b315000020140c09450291689e46323f14000c7fa848603e16c822912448611444210c04300cc230c600608c310c29258ddd00f0cbbde00b05df6da2877a0008d4d0c89f70ca5e1aaf65a8987b000a8c3d9debc7fb50d0693a074139ce07e42ab7818fd65b00a95e30e54182adb52793f652d260ca4b96ce473cea6d1c23bff2167badb51471215ee5be370269d27b875f7dcd84f7e7f9afff1be86629bf3c44a11e671cd2d1581171b44c485da9322b9f56af5857ff9ae2714ece745cbb060e8c707d87788b05c6fb435176ffa1789b54add0f299d8425d93ec0b2ea8409fb594427a48b5a76031e4928f276e00b99c86ca2a71d7e7a2727113a54a4df6f8722b126a29447c68a68b6c07967999e72816ea09035c3479c0fe0d65536d9404517a1ca5a6d0ab1c31cd4329988b07e08f628fd02d716cbc1ce61b326778af09147fd700d9087a646e96db863f7f12bb336e4aec0b12765cac08d17d5136f650dcf11d29eb247615c6ae3d961760b53308f5404509f2aed30e0a483aec535579b54d1b491ddf4eaf8f832737da1717d20bf1662b4468e464a4dc5c8908afa9341b70899016db70d4be913a5b4f53070fdd8a01e94daa9a79c8772d91f3555ed407a1e890b88890200e42473eb912cdb7d04aa5c85ccad51dba65627b99e7b15e025e78b6d783e29212ebc8336802aa2e8fe5b590c3c5ac1ee0ba81da714b880472a52e727c37b0646bd283f0f84936ae2a6011c50b0d27b5fbd31c7d04ddfb57cff4281135ceec727372dc5979e00dd5db1ba3cc9a073df5ceb2f633d4a27ec9770ac163742e57ccb768f29c55ed8e6e595cea1605461a2f4b58c5d25200172f05a8980a5546cf6317ae6bdacf000c095e1b96afcd864d53ca963813dd2af6224567704d5931793c743ec09cd86d899ce84bc6173a3de02bf0e0bbf4ba28c7bd7efd2b9778b55a134bbd0f8fb127a0cbafd20b0e6a73aa53f6aee523f3d9d1c2b27fe2f07ea3564d417e4e3a6ca2b6cf99c355aa8ff246fe198066e62b6ce6ad31ccfed881c69a0b0513c000db08ef849d3dad0ac230db00d2703db377c26d14aebc29c2459ad70d52efc736cb4dbf3689a60d6f420b25ac38ada0c36e09fb58f7be197752a28e7d75d0a56aaeb0dfc448aa052916be03382ef341c117127717263f9718792d69094baf84ca7fe714866f8c6943c3e854bf6d0182d27becae8c3bb7165b8c5755a680a9a3d0ca74ba7b8a050643a6c7f4078fcea19cd2c7d91b8f5b9a4d9925d06a485dbea7a201f4b5f71a468893675c3e7b4fbcb2e6f63d340634da994e2c5161ca3aa2da4eb46a4ad8fb84f3fbdb5bb105f69064d6956422c241fc64b533df043de52439c961862556d6d4fc0b1dd3965c5cd85d18e54c8e766c5a5b917e78882c4e0508d8bbb0b92d7b4929c6bf296fc6cfe0645a068f0255c837c7863a29880e59aa169d8b84bdc90a0306d0823389f73e7244b5d214253d048dc44d609eb18fe89fff9c2fb5eea54fdc3293b83a29553e5cc46d7188f33fcbc52c2f5f2a781971e11a88d1d81bb8b3d0c5d67588a41fe4ef3b4d7200d5e26fe16a9c4d402a6bb6b725c5ed4ab716846cb9e2b05ad25d4cf504763ccab5d083ccd1ec0c91d6b7880002c9264247b488f784773cfeaec81ed364ea6936b349c2ab8f3e1306a14f58556f0fb2f72e8d699fc03ee8c82911d63913f5c1cd495598f194a15d91afd83d4e0e5dec6eccc61b87ba8f6aec44d5b23a60e3f6262d59afcc151807a7398a9a07fcda0f37353073b1e1bdf07d9f320a49d4c6f76c5fcca137a484b49f72b56d4f6797cbd5755d33bd95a9e55d94d2279409743c3797b4d6bc2cfdec5d580d69ea106f4ada4870b5af5134e0bbd8b6521f8b657284d18ed3ee5f2c2934ca487134cb88619f12ec09c48ddc9af16f0ad8442425bfda4f2c39aa6d30097e84dddc752a3588bf08030a603f3db1520899af1d5208b87aa35335f25dfd468642c0f4ea55420eefa3f28b0f6616307f78c9b460efcb2fb2a93f216df788a4d2e5173fd8a796a373d941098a27cd2c95e411cb4773e0f81f33c0013e9d4b57d5b92413abf22934d9d4d201409e6751b93ec3559e89f0c026ba1055060a3a635069ae80005e2acdb1f8047eaf2c5cc0ff26ba24f08b2e57cacc4647bda7bd27c37c1673450c61a7469ab45c8df3c595090bd46759c1c8060a1726b8895b30d9988223bd37bad886a07dc4b1d7fc2cb8c23ac69db97f87b069ff01c434c7dfb56e86837b280df6093180740b886830d0b37e6ae6cd26507b4a708a386d2dac48802715df293668d6ac8b6510c264d6273bd55dbaa4eab6a9aa13c2f8901959bdb55b7145fd289dc588f4f06f87be6f22ab21a35ad4a5d1ed0b5d86f1c522e3b2aad3450efdbd5c4c64745a3c38e9e8274f5abc6ea273c6a9b848582cf51dd2783797137c51930b17b42e1ad0a7a60a7d5202e40fcb8cfdc3c47d5638c3cca3d2a6db0c30015f3147ef643c102634a0fb568c2dcccd30e854c32ebaefa8beaa413b42354806d0ef3158e5c2bd59e703bcbd6871bde159ed13607d7e9dedbaa0c7727256a25913fd09ba45a1179b8e4a3d5bf3ebf7be9c8bc00f7370f919cbfaa2c104b885c719fd32e12683ba641339af68cd025b8a6ef91f2f564ceb56f9f1d7f8c8a205f0d8caa8e726633c8b4881d4d55e5422eb3f9c6e438cca158c27dc30d03a3d84adcd3a5310d1b48a924941bdf7f295b8e1d4dba8ae2d655ce8b376dd7384ac9d935ca3dad66688ff7d18b8b2174248812c67f285fca05fa88f375f91c8177f4058be001708281db35d2c246944b9697a1ce4adaf122e5ecd000a7d8712c99325b4474c5161eddc0774facf4e13f70832686ee61c4884e6329d0d5caddf71e712929a92b23ec4084b489f316c623d45f5ad9e3a264e2d9f52696555bcd88ead51cd4eb3d8c9d82cc85f28270537d11fc588326a95e855298898c95ec81cc71a0707f59716b2c284828ccce1a4a9dc4885e179ea1d297731c8871232d9b952a663407be457479fbcdc50c20c011f83384fe46a588a7b62471b4706e38c13b0d633b28dcb9a3bd066284996fcbbf870a8f323f538780579a539ecb7a9240f82812fceff040cb88f6b381cf3f763d62aa39aaae280fd9c62fc033fa43b277181398939c31fbb1d097ffab6d1e54700c7315283d53a9e92a474265ef71febd4dd65379ab23fe4ea386e6fb26b7fdbb8b8e0670b0502aa46f88fa8e2f20989fc2783d746c7b620c743a5e07a30774b1a59a9587a025383fb63e13af0c3d257d4cffb7034a14be2ca75ab1bde9836e4046e96bed47947ed528201143762c67fc86ccfb62a8f062e27974d6b297bab36607ef3231fbb436aa7e43c3a4ab718379438060662da25ef7099505db0ad3605fa1077f031e2845d082228b131c6938f6d3526174d25956d6c0a7a3f329df668776975dbc2a6d53d45323de537ead807c1fc28e78a95886c8549f69766f9123e7b34582caedfcf5239e50fb749133959873d454d573786f37a41b4e3c94137d7a73831b6447b6116e383b6bcf7412fa2a9feee6220b90c63ad44f0d9dadaadd9559f9e3d7a337859f85fe04c2d27aeb6b5220c9ddc73682c5422d127704d865fb34e85bbd23acd9c2f92ec73e9731371ae78663709789b13d367633ac7cefefbeeab119b0722e2a6c9fd2fd14c970e1d587e641517a3ce74072d4d4479e3a53268d9d002ed2e2679e28a5cb4739130857e5994b117f3abbd1f0dc8798ec52444bd5b7af82fc2dd993d29a1e5babf623c0cb72dc77fea4f73b3acc33fa972559e0d3bbc3b57a00b67688f43fd3ac5be433e3530d2ab31500668c65719d2ca2defb084c7ad54a1a501596f965e1e4fcc6d01fb4590dfdb67d5db1a58156d4acc2cc1559d9aee71f62c302697c6b66d824852854f9d63dddaaeab6bf759722af1c822ae9b7e657210ddaaf46606576a68f5881d203faf8d21d689146c630976c43fe2f758213864627530d58a290e03e287326d13050e96e83e4757abc0fd9ebaf270f7804298559cea7bbfd4f3da6e3b66f7d4b0ea4607dc854b5cd88fff4a99a56ea7a8424db65bfc9c51b915c50199f7e11f5645a409c1df4cab9f6489ce56a2b508008d5a09b6f4d871ab539d1497f3ea847573b3e91c6f91cd18b7cf8126fe672b40daddf5a19e2406cff7ed4acccdd5b1948105bd4ad8e01c2057288c1cf5db31023e577a4676e86ffda0c116064a19149b1c8ecc8ac766f6382b8977e57d9ce0d9eb93a02a3e1d486bea082f05a7ab1640c1d34b9d804329fa95032e9d3a763f6eaf94ea2b4551548d8479dc3186918da48bcdde3577adbe948ca3376634774985edc8f81883cb245756f1e6aeb63597bcd96229b26084839b17b8ac738f73d947145d89d33aef2e158746a976ffc82e03fcd94e310867e6bf0f783982d4ae0a61501269175031838127862d9f5d72ac63f3afe733f13846819e64f74e46881b0db4bb45b95d4178f009ce4f52e2e21939819d2294e6d1261d4dbf3e7f4dbb97c431c8094b71deb48b7000038e59252a8de1b8d05c72d7f8622d6dbec96872eb937c2e49ca1254eb85252a47c81bcf0f4b75d4ef521696e5881f96bdc54f720b0cc193911ad392fe196529d8f6d08104195641a4be5c087f0e56ec9a377fe7e61d17cffb8960057188ecbf493ac0e616415fbbd881494a296929e3f027ae780577e257945095bdf79a4cb1ebab22d09b818ddd82b593558ebaad3dd4ed56ce2104e8eab664bbf154a9752c31318e17b816155e44ff1f98495d8b4a181cb51865274555ae8a0feb4a6fb7e51058af056c8cbe2b5458e6bb2298758c1dc49aa28517c7d788ea627464b2ef15041f6fd27bbf8d37f3b6a53d08ad248b778c345903ef2e617040dd2fc0f4dcbb1044f9ef70e1d7d0ae36f9383a5757080af55457825d8d1e0a3388aae6bc141c38587e6b16e1f2b70912b10a9afc98ecbd0ab79e9892a10476a5a6349c5a638e10b5f3cb3827e5db5d6d083b7a51847f30a0e9d5b71175874bd4d91992b3ad1d2f258e9c990e1fd6bb63d87d4ad25d128f3b098076ff3f486e1b7731ada873df7c490696f506bcd4f7843223d571c5d5e838d0635e2adff8992868986a3ee106e016b77451dd12cf7475ea528cca7d2e47b932b86f72d767d85b1b5e6a516ee8784377685484bb912a90023cbb5369ba5352039da0ee0c9b29357587a04741d3710599e84fdd6591e524797cc82db44ad1e3ce9afb523c4f82454d13455397d68dcdc0c954a46a19001fd6cd2e31030169d47e87a3fda5349ac126fc9e7d2da558d795b871f9315d03a31bde6af495b0d69dc65c8f517f9c37c6bd147528cf17b1074a976d39d48fc5f7d349bdcf2fb978abf4eab3238520a03ce770fe824f4fa19c8a55ccbbb318bb9eae45a23c6427bfb3239df4efb2fb9720e239cce3b1820c1ac76f3b7803950a8dd8abc9cdd609496da16b4adc10e73374828542c8b5b7b99e31c120e74a437d949d786005f3edd81fbd3b0cb0ab9876342a3c8f4a36d655d30233eb288dada0b8db685ded92315417489b4168296999af891fe8f992822a9155b6f885eabb0e3da817dac3b4cc77539f61f1238b600218b39fd5e85c7fea56cde4766ee33f7653a53e63cb27dc90b6b51e0e897b234e09c13393782820f2926071e74bbb0b2db6c8fa12b51bbd61655d6382f901eed7047db7d008d47a826d92888861b8acf92ffffd9f1227f63667916568800630d4ba1ad04615f0161b3d778f19edd22ab4b8ece3532c5decec966fb08805dd5a28a70d704e4d2ff009332f6a9aec67fec60d8e7dbb70c39010215665d9200bf4925177ab9c1983574ef925e56019dc44e953588a90e2d9deb9d08202a847a82b438c18590a62802857383f82b9de51a029cc3868347bda98a36dd6aa0dbbf4ceb4a9857f43296ee907246e0adc03b6e596095d8cf1d64a90d137a3402249176d747cd21fb7bfba455dc9d1fbbd8d698b50480d728b0e8685b6927ef0f3047630bc59f674b0a3d88875f95553687d618138a65632a5f3669d212e8cc5e8d70d6093e105f6e8a5781bd27ec64da5c3757b2ed3c769a4f60bd05c04b9197495e95c866eca96eb75ed220d69a0eb48c2d2009ed2ff240dbab8d61e284b4efbc98e166f37ada0b10049eabe3a2d2230ae078453411932635f02a75841bc4290880563310b266b5344fc3f1d3f2311631865bad5fe9b5e1be21f7a06d29b67829fbe91577b938f78e7638ce99230914109ae36c13eb1ec78f507061db8c70e50f3a00a8d73a459bef22d3db64f68d1f8bbca0db3f540dd534e49b892139ecc508aafdfbde478b2307013fa284543a151ce9dacda6356c64796edc53e3590a7252d754969da55841d45ab4bb5549c17077ff30c60b41c2c5b0b7cd054b3d44b0f899e1fd1375b6d10d4de55e54d6a8d0dcf0840dcc5f0e6f90ade307e995964e01aad884de8944a43fbe8af6fea47e008e6d2bf7afabac7e0373024b5b7dc4ecb7449374329785d12cbfa76bdf6593bc14f5284e0b86164dc3e838e42241bdcd9f466a0a51ce1532735e5d528080c1178f8bffee2f519065a57b746db16c5ecedc44f05603b343faa5e2a1623a332982eddf18ac306a4bf7a7fe3162c6c7939209671d2fad902a8a3d78989a709982b1196e5cd76d41211c0cdc8edfea8d5ea6686e64fc9be26267cb1055b2e5d3dc129c22a51a5ebe8c6259b8379a1614080e03e5dccea6d2684c2599bb113fbe2432fa19f8fadecda4c3bfa95a2a4049f2f53ec2a2b98b1215172a0904bd9b7015ac96c33e9151126a70c691583bd1abf59e968d263d7d63790452c59e78baa606ab866bfcd36c0d9c8097b1a0f4b84a01bb5562c81e9d51d95a334347b088a144a035ad388746cdc6675ae1f7df9463ad6348c81811fdc661d5602e3e17119864790bad0d386cc9cfc5a9126c8c84502cd8a2b0b8295e03c5c3540c47f5c2deec18269f0ea9633ea6336e6a22ace17360b89a4c02ae6f4326cd6696eaa6e7f466c16aa0e3dcbe2921393d84c2bedcbc22b965719f503f8b52fb11929a31faf0e05c0149ba1ef86bf48e8961a2a3ee204b762331a55a8165806d4aa71866c561ecdd47bba83b9f3519e8ff67ca4cf477ec6db8572fa548408a505b9ba15653af99df8f30ff969c9a36ab8665ea03a4ecc440627c5212c80695ca6d509cda033601517c326f952d9715cf40b6b77a854ad4cbb2b2f9c9f66572d2211fb5184ec7e17fab8ed9a33725a34357365def4bec767e957b2a13c54d31ed115497a545584b0351533fec4dd7c296b6a0200bb342b9d9c135436060fccac7b6986dee93594326185659a356feae224b2055f909d051a281785c8a3994115c11ddb4a2d2c55a4389dc51630666343872fbeb0f7427674614c36477621d0158365ed09b3a315a417c66cca01824c8ff10e998f464526463628fc36cde2aac1c3bad48bb14e9ed830aad864d165fdd05761b5106d96d93bf81f10a278d1042aeccc144dc28d01cd3b2ed6a2a3737866d032997177c06274b97071f2338f5071b1972bf84714cecf508fcd2ede3464e8add18ba8717fd606754a0e981a86aed3df498accea9d0e3d7c0fd74896d0f5828523997d8b0b9a22e111bda9932d409f946c1d43416a2fa12febce7fc64b8da7c80c968408519516c4d42c3a95fd51458825ac68b0eeab6b2c912fd7906778aa046e94cbbeac6879060130cd2f6101bda76989708d45a711083c9061e618d040799c61167e2a1231450b66a638a902b1e6b39051b85b011af7bd0c7e83f3909cf1502a77740b9b60f65b2d58ed29ac6c9211b3edd19dde65b41c7dca0b11a011771c4a34fc12798d702540b5f0240473e25b62e972e05b8eeca7e5097478bb743343164232f9c9bb4faa9113136bf2d0a64698a52c3581015c325c406e612622992ab6573c398c531be099e2a150a5684bdc6a3995abe7325edbcba3e1f52fabaf008cf3aceec64fc47f19d932656447b73bd07447d45dec7f15a22da04e0c4fa68a726eb77281994e4d10ea8c965fb7f455886e1432f9bfabd056e5d3b145fcb736be843ba86372e68ae87340c4bad099d8e98b67a15b3771d21a780cc30246466f424f9c48a69c66e5a31d192bf047bd4cc0159fc1a83526c686cf72aa8530978264cabfa505ca679ead85c1b311fc88216b6632656f874093f54fd132a536aab6674acf8003f076b267eba1ab6e0c605a7a51f74c494c3f3f9a16adbdf4f12ad31c48d597186c1c8b52ff423a267c1e159c121649e7443643779409359390d5ea8a2d935046a0b28153062a9fde09281000a72415b6f51288f5bb15666285381f4a459cc1654ac43404a794b09218b6a835ec0b5d45bf582c07bc70af1500667c5344be2f1eb55f2c9f721844c232f7aa8accf61326082b541e634fc1016a67a8ec9527cc8497305432003159deb91464160554d3c4ab7eb7fd2ef05ddb028a7090ca6625fa6554a123c364a432c03c6397b687c28018b3509b46a25eec14d8208449b84899175448472a52a9e0fa2a4d2685612848ffd2ad816b79bc8153df53a58c6016cbb98e5a306cc611f861b517e0165ee8a4aeec58e5aa7c82c95dc101c07e0e05287373345d2a0aa5b50602800faacc17ae0545d66f19b0208b01ca00d9d998ab7509be443c1560e4ca8939464a035d219982c1d3b0122872a5aa605075b23f93ab72cf93ece9d8e8c7885e19063be480df907d962faa75c8b66adce3b99fb04f07a07b75a5f9007ab0f4dcbe17026079d16f5f8447fb5397a085ecfbb27f7978b8c7a32eb92f6402b0fc2b38dd94f2927956480a4f7f54b5b04c677a32480bfb359b5e352912ebd2be87893b875cef1639f84d714f51dfe485bb07aeb3a283ff024ba7bb121b98c0f4848cf4f50ad08316e207f7051cff8da55ba1c104c3980bfd53a29f85c0e44ddeca6bb658bdbc99658ce9453f13e90b25cb3ffc764ed186b170021e0466d83f3c01c1820cbbcb6a661ea8f475bd436d64d86347ab6b4d3788d3ea8ee0458ab1a6c8522f9cf7ea4f4a86ccc39d20b14d2a47244e27aec6e0d948e2474a6a6a416b19c818e12201059940417cfeecc8b0c50983a2a164e004e7354360cbe940470a3431cdf6cf5f23f0d88557f166602b4af0d6a113284c2d88a3fc2ebef42e4401ab4f2822cdaa4d51fce728afc92ac9803277de42bd1460e13d2bbaffa1ed487239376764432d07db9fa528cfd6c5bb0148101d4c715ace04257c5a8ccc3ba97bee9d52b372bf8d9513854476ef891b61eaeaf0ce6c9a122729cc2702818c65d6853fb4e61f82253adb9fe78c3c23f249390695c0bbac251048fe006a4ceb58a9252e74fdead5e6a50b8b10782b886557743f8e967f431a324ebb9ef12bec6455120a71250758c4388200de82badcd1f741cfcc0d2795d71b186035ffd283dc19cb6a8c7094345d2b16071e1c76e5485e4f1bcf9a1b4772658019c48330bfe0633293819c15625800e94d0086ceb6615898ba537573a63bc4dbbac8c343b80798b96c93f0ee65274ef7af0af249848bb463b16428cb302f28f4e61d869a11380c15c943b99e7b740f4f6253e4c02fe78154b5c0d388197ea08f9b101ec4156bb627ced6be2b87c698838508d9687f31481e24aee168588a1e73a152c92575f847b339147b6703a44b89db58e17960c3cd0d5fb0339257750c806f4cc628a221cfc12f080a982928ed83239404336b876b5b9b59d18acb25f1817f802052d739019859a7379804d33ed4c8c4dee1551f38a7b1bd0552958de6bfc432c8a57b5c3d6b17b85f1695650dc35c33972cbd53e4c5f8c4e44fef3d537486e69f2f99ea324781b24efd50cd65da729d04069c435cc618efac6f8bd42921658270460f6032bf7749dd3368d19872a18f6509f98f32d4cb57d7251c70c744b36279a36ac98e1dccba51deac98252d6850a5e6ba3b155146c179ab11ad99458943fbee308abe54c7458cc527ef9487252defda77cb2ebd49b2fc1752e9378217c738d0d4a658d04f293587c08453e6b0df27f21f77a49df4b2385cc8ac6e7764a92546215c5a537beecdab3fb87ebcd85ba048b0d6069cba60bdf2e1f7a15143bce89418e1e16954a42e199872c8f3750383d766197bd125332bbf2fe01189fdb543c91ce8695fb73746e1f56294e30799321257d0f81dd561fe66cdd56070af23a2f825006590542a932b31e22f792441888237993f8474ceb5e9345ff77bf099f04e279a0e635d9e34e92ee124919ecea808602d39936537c5b7e133a42fbe91988257abc10ba71b1b829b3bae598fd7b33bf3742e68ee39ec9547079dfa39aedba5f78501b0bea92096dd6219c0ef35362f1d785158e19d22db86efe113d481ec931d8fa01b942c8ce88eeff6ca44943b4fd89b92acb3134a7cda27358a7d5c8e1b0c132c01a948e7a7a8cb1666455a66f2665c336fb17b5b1c5c073c74199c2d893a11367989e66ddab33368ada4f0312cfce4698468c38bfc4ebdcc54f9e84ee4cffbcf1772ad1b30aac2bb5163dd8b0d67c6abd0291c80407687e925cc1a1437530993824c2727bba91070340d2042938ce5b253ba0b0f6a8c0fc53abf4cf65f78a8adf384b43dc0501934c05f19a8cd3e7af039c8999757daaf78c7aba8a57ec559247120d1fbf0df475d8a0471d495c666d1cfa0d6f1a3b4ab0fdb0808d585e9e64df77fddf0c6871da6a9f9b85a52e7e1f6b1cb268c9f504e55fb9c783d095941d66c5bc6307b113856aeaefd22526ca80f3b748994c1374a4efbbeccc6d886902122552fda0bbc21ae6234135f56dfcde662794ecedc65b09cc6c83ffa87364aebbd446957a0fea5d74d3743da5564ec79a4d6461fdfe597982663d10683ddd2a227ed4345d870158bf1bc78a3d7725d653be200e7a59d8181fc3e91f28aba33f2c7c5dacb4e8768871f6101433c4463b657349f16a99245578285c052120924230088f514587729da7ab63398ab2444e7f035fe69234e1d2565865033f2fbbb82a30cc0fa1a2cd3f3e48a61c2d31907520cbdb2454e405c88b252badd3724b30e8d77f0ca0882164fe303f7e2bce3d3a76fc1619ce59e959991c5a5d863a771407a237930d8cfc3467336f2f83ee2c649590b4a5bc5befb6821fca8937e32544e132af9ae40287fcb9383461aa1f1238c7b4585833e598d08bb54794844e2c983dabcb6c4d97708c7bcef8b9386ab67cbdd692f2cf3b3619c72681926b444396011084f28ba88925de2ee5b6e52d83ecccfc8f5d3290f6b0704ba8497a1ba3826496d9c294ed326625349995fdd96e3978c6320acb52f6508fb24bf12b1677392659bb0673bfe4bfc5b3736046bc279be10dd5229bbc34893b053da04a7f8fe8f3a3e1349aa946d21d373fcc895e38ae94e770a3481304fa18c34c65fe03afa3cdbd182717d92c21cc92195802dffec51691909ee38a777457ad0b874fcf22514fc8a42ba1a21a1321cccbb284fab205f13351eed0361f15511dbc46d4d44c1e22e0bde8787729c2d35502048c7cd6c615953f14a341bb5940a09794004f5d6dbe65b4f5d3b266d066b79d329955b82a4b0e78316d334125cc692f466c592b6f06f9664b218050c12966e0318dfdd9254c5e94295ab1c64933c72d684595ac4620f55f74765dac02d475d9e39ff997ee77c2041e3a438df2b79afbce7f5b7ca54ca2a4a2a7e8be5e0bf8efbb49609c306c31bec65eb0af4dba5b5dbf1bc0c47e1fd9dec9c6dce2d0e1e1988dcebe3a8b9bccf73bcf5417ba7d8cb9116221fdbcbc4acf4a802de62ee35cca1ee1e4df5a83168c0978530cb29c53655bc3e0e8e60c59c6edf0666beb64b7b52ef0497aad40dcc1724b8c9aa426adc7398ed1c490133df3985af7fb7977555506baceff832982c6295c8452ef4c517f71be387a559a51566c61486ba57407c5b42b5e0045d12f843fc61324d533b3a9e4823a733a42d45da4cb57b7a4f837ac4f06ccd40b2d1b81aab66668c4c45da913614775326964425da48364bd143b2140434f6889e62b7a8b41a1428f68a7d5157c45b091a16f1457c91530ce3d56727fe6b5b4d942d00904689d5e9682eee47e5143fb34fe4025d39433c0bc9513212e9fd107479cbbd0c7af24205fa56a266a5e12468098b4b0a784f74cad626ee4facf13d092b75d43d08c9a706fd4a3e4ca4f0a56a070833f1f857b3b71ad6ed77d27f10ca848031158f5b6a52a075203e0deef8c8c6dd0f45fd68e9dd7b0acc7521f0f4d4ada73f79ebb848b684b631e57700c8b1b75787fee30a6f369bf24ec5cc3b12b7e54b52be8ba81c9c7f7044438f107707e497b959d500f5d00abc6d373d7d90b0fec5a81ec305df270b42d596a47cca1528c484caef5797509f3e8ab7afaa11b29549d706938afc53e90ea8ac0870e8e55b3901544813c90f5a39a250e9197ed2098d0ddeb4611b0470c00f321c7951e9d7951a91311ff0793a010e25b80fc553425a6082984ed297f23bb18351232f95de4a53c3d280e31c46c11908c843de1274fb70f745e62909bcd4a9a66a60786166bf782000a51a57ea12868f6bb2542594c33e4632b92afdb66176450bf68c4613e32dfc549de273742b06ab9c666016b5a86449b3b06dc041a8a3250cd664b7893848909bbc2843576a7550d4d0eab725cc36c451b350a8b345765a305697b683079dab83875a6246a7e64666b54c8b454bd2e7514cc37d8717391807a382cc87147de7c216255044d0c613a8747f31334e301451c5ef239ea6d8cc6f99babb07b4dc2994a0bc20219c2f06ea07261057753dd597aae9844b27298df3c9522a5c6d4c4d0fa7fe0cf5e9ba9fe06b39a071335ab330920f3dc8a895e0d8ac3d43f67b0a4a9262f6d967bd77005eaf9b44031be440fcb1120c73e496e0de9e85e1a5f61d8ff6068ce4a6b5f7fb5693f3d748f34cc88db096c91b9421f8b4d3e49ed26aaecc1c8125c223b67450974dec8a809f8b2fb9836f19308c8fd6a3310b6a1992308a4f9586a4a356c99ab52150d190da2710ad76a415ae597a54805941ea4bf77064b8872053fa7760e34fd3ee702254099bf7a6e79533546db64176613797dc2e52c0ec6ec8c5032ea08820b161cc9c43096aec2b82d69b40a549f6560d1af63ba037d94301b87be664a78cbbecd60191ed4f2ac61d4eeec96dde439353225d42fd853a49b5f477de6cf9d3a9be062948d173cd07cb0b4f670843c058de267bfe4c02134e7482e02ca29e6e5beab3da5f6b3a72831cc8b306098434c321f567153abf7da59407d3b981a81883f22f239f2939254a0a1a6e4b2580f9ff9f7b881c40c8fb949cf6a23c4e6017f6a3ba3a92d05b4c43744e10653803d8ee870d2a8921a1fa68124e6d8aac360b44c2cec1acdfcadc5c15d070dfb5150da41f5277fa908b104d2757bd23049b5915a50922614287e4259cc252cf17b8669c8933ee1e34aa80137ecb41bb979bd82e796630efd93581e22d0dbd8bc69075cd239ddc5570e71a75212bd708d672c02ad7387c6122bd035d8b44a84564aed99a0584b598805c53183e80435c737804446e9e5c344d7b434178f4db81e252103c77410fd64a0a6d7b01242d2083116ee399c2c2a8fd594260b51f5de9a921127cec6ba9702b62693465ca2b8131bf399e74a6cad3b6d518d1acdbd0d1369bc89149d7bcfe39da0159db6695b5d0cdc58e97219582bc62f87a50a32a6a14a8ab70a69841722ddd2acc8c81658d2b539a2eda197e5fa5efd1373146d606248add5426718784a91f4548dd290444660dd966075dbaf18430d55a5bebebcb319dc2b31f34fbc82b325d8acce309af988764ca5e26aaed6b5795ef046d553999251b21d264970e144022cade5818a695ef8561468347d8cc7d2b74036a7cf230394b8ca7e02a21baf0628869a812d5dff8881996f2fccf54f690c43889638f913c43229c56d2d3345f46b88b00c0ff192e240aa31f42b98aa2cdabeec411ca62ff0534394bf15adf1a77a0e0d4a1940ea0fabb09d8c2229146aa4ae6491bf360f4f2181575240706f09220581f43ff1eebfee9a4fa9149d700588cd0083f03c881b584805a231b0c56cf8648c1023f637d31ec1ae51837f898b8845d3349da18727f815e096ebcc23ea50ec868aca4fe3a14b124884237b09c10d21fa3d6bba538db37fd0f3e3f37955a59b93b39f12526b990a4e7da600823fba2a695f3ce2dc3f184249f18723994896ed084f06924572eb020a245cf31f1c4fea020d255a229dc7f0c2fb204c42d0c25b20b65e3c736163ffed60bec4930b91e919b6894274c9ac9f4ed106bb9df6e21010421644c22f70f239b07f21fd81693f41ad54a3f07ee7b0e7d58a11da0225d5cebed1675fbbadf0689ecdfb7ab33dd40e9f223d881d00994e2ba6fcefaebc0440dbc2dfdba2a0678a81c2f56c46b1f36ff6877dc5897899dcd9f60dd88d8dd4a26eadbb557eedc6518bc84d5f5d4aa5c88166d1d1d46b815a49ab68f5ba29062b59f97db02029402e6cda496900c252ac451580591ceb07009c323206e16242cab5086e773dc0923ce6f535c8b5d11bd971f970bb960af590d4439a42ac899cec1544716d3c9834d3eee74b8be899f5a2d77602e9ffff4a03c5c488d23cb3d1d9f02b2286e33a31603a81b8ad2303952345acfe1ca5758a6d5804fef3d6a7ca3299d530f05faa8ba6120a64eace052168f28adda513798c0614cde93203cd746ac00d730107554f05394ea37e9db1fdc303e642dcda80d4560265bd2422eee8ff57f54a532baecb28674ed36c6345f740a59154f82529558c7a6ce2513d564d3ea65fd0fd253de5adc33c3062a9ae4aea419419c52db194fa9924d8be62af4635a0db2824085a9fcb194a0199a834d3a407bdecf7b4411f720aaf5332b73cece02da0bb25a69d0373d0ff20ef705ca5a8f91b7ba463284285662e85b47442558e650a34b3242011d15086000996f98ec5d51de3229ecc1974931bf4aa2591010c7f8e9d3c8eb25443b9dcae4bad77563c118f17ea0c07aa2769b86c307ab098b5073cd0a7c342bc684f911b180cd1231d2ac1e1af37a6effe4911da443d8d14107fcede82e2a2dcab1202e8cafb1fc25b5111eb311181a40a8c89c43f22d3262ec1e687b35951d15377babf41d81b06567628d739b955999b9ea0dc90ef7b03b57398df6818ed1487e8564e269ad70464c081a0ebf1eaa1b0d2a1c8109414dd816ef5e38e22c8cf112b9b7026f0f61f740de8e0d428c7631d6fbe9970c5bf1ae5f2c66024c0203a2ef88698bd0fed03c58279a4952ea2050b9cb3d2c2534218ba781a232c69fbe68efb36ff4033f148a6e9d153ff60d13b7085279ac45e5753312d2f8836f85cd2bcf571decd0ada19c3399a94b7e474d88a9cea587db958fbbea9f74b0441831668d5873b0bee2fcc43dbd2536b35b605e43a86f8f0e64d4109c52dfa2d50541319820510b9d43075ac9f641c5fa6eea6e2059f08bba9f1d8e9f740bf2d73e29cdc897da17b2606dad1e40e7565301fb70094fb02183c2e64956f1f4964a0da414610b6cd388e563a0be704fd87281bc3f70decc867686025505bac7fa4bb012c8326cd12a2e57a8ae73993d2180e52d1a929ea06f64a9737d87e9867c31a3941ed2ae82e1a1ce977acc484e0eba3521c59c5eefbe248b9a2c3166efee4292ce5429fa52b98ed529abfe26d401b33835a201af68174d521cf0c6855f7b3b8e63ae43dbbe4a02f20f0928194a91dc945cb8f5fb151043962ecdc033fc989156c3b42d4b810ef8d649a8aec22b1ec24638f2da77d6c33c51b04a24770f74fa9462ec5153173e1623a3917cb956c2c95132563e5b91139ec65176b2c12581e10e4ddd5e402e37f74aedbb1968a6fa011a13164c4036259dbd4b752e6e359c24e7f4d1a9ee8dd712c78d0234f4cd850aa4fda1de9abeacd0e1e48478156a179f0a709d11e25e346f73bafbc00f8a22b572ee53e96f433c399c21c36717730c3e5d81b6112fe893e0dd476412a4ecc3b707ed091c9047e66c91a4e3716e0df471e8e4994432dd462cb3f61ed5f0923a848074d7dee4ab5cbcc72bffca1427eb55452b294bff0f99ff294414ff8d04f90dc0211a219214cac56079a26c9a25ab6a0e54adbbf91629059d0fb1c073434fb462cc518b8fce46a6575b71fcdbf136469ff59cf077d81be61244747caea6a48e1c2d799b1b04dd8c31e3214771f5a8339a326bcc140d554bf037c064c8148a034f0198f9fd5514272ff6441c39b2148c0ea31f76bcd331cd375d3e95aea9382bdcdeeb8c7770ecac9f3f9f0ccb2cde2dc3e19d814b319e53b45743e3907ed586821f56e172f387654c3a23d8d034d3b725e1d751a04c0e2232c817d30538a0bd32d0e2aea950a3a053e68ea0a59326af61144162d1b2a3b3aaaeda29189aa9a0e71807235d697e801de67bceea4739c86b17423731557a9beedf814db40204048789b7484dca58ecd58aa482e93048bd48d3436bb00ae0407f7cadedf14bd3b5375e185b3befc07ce9c8c7db1a1c2d888af902571eb37db8be4db57ed31cae3114ed59871fd80f4c68a335edcec0f4b19a1677e0b849cd07ff818c0ac26925d1b57b9dd29645c968b09399154c7f97bd8263d536ac0b71289402c09bd2d964a38674d2efd4662589ca20237c1192d12523b10015ed10351eafc696bd1c48cf5c7a7e1d14278c5006e02100a443d9956dcc7dfaa158e8d6d3f1e4fb46ed2f6a4565e6e89e8c3ccca327ba0f7ad0c07a7b74742da7cb24c4982eb4e08d7aa765462992b3c8789ff6b92156d31172dbf49b9c5fadb7d7ce5f46ad6735c916a0e4aba14819063906e2001b7795242233ebf0418236f7f0dc22a2d8170343316b33b918d4dcad17bedd267604c2bddad770e571989479d58a70acb522ef34272d23cd2c7228c9493fc1d4c7aa8e4849a8b972722a4693b15301e9e40d487673004ec585050daebaa85122e0655d736a4a6aeb8bb0c59b3bf37ee95f502ae50b981b533f8b34701b1092e4818d01e3a90ca3bcb731085ebedbead4b32747a77fbefc9e51023864bf9521322988caf004d698dc4dca461fb6a78c14a1d41ddd1480e898e93462f45f2eff10e4a21b9a38b918cce26f8ca85f84f26798723cdfe410a25a420bc4abd203b50068b878cd668c6e2ff58048618c52299a9c9e9be8a48dd8441033db9ed0a274a9c0bc40f93510d56f86ba534ce6f5802be8483d8802152410a7b55d35d35e7dd09728e850b461a0582361199b2ea5a94732432bf66a13803c6272c1a9aa85d729ca7066005b35eb0f4316bb33bf9c9929eba064fc589327c714c6b6f7a27a2b96edb3418b7f1e61e262d64576c37d33d4c70699c24f6942d1b8ed93d15daa507ea9a198b683b937d73319dfe9fe88b02511441d44a5bb1aff972f2dc3e01f11ff1f12f66df2411a8411e3ca00597b5f288994e9362ae4ff5a243d55c6908f04df0c6c1664173331cf6c54dd017d40b79740baf38da13068b5093dc18629850e3371a0e80eeed05ef37329d998da17b97675494f918a9748782599ef77491a869da070bac0ff45ef5f69c8649712d0fb0198111333c27a0310382e73c7be422cd250acf50feb53b6e8070eab7db4c56b38c7078cbfdd303740d14c40450c239777ee2c64ac43f3d73898608f0d590eb69292b1df0ea7e7ff9a03aac94274a90ed21cc6cb8fab33cd1107f69f4571c6088ea28ae57c1a2f1c03835c10cbef1740691dfd28f906b72027b6543fcacdbb26c10b9c767ef44115d2e8300afb3bd51d37fb9cf8afd2ce2d9b30e25327d2799f448a6ed38081ebc18ba1329bc7de847e7de0af3d2006920826b9e59f28d8d342b895c3f3988fb45ec40a8be16abc514e652791c5025697e26997ed408c4511ad92240b2e5e46a00a3a47dfa91e819f69a3f55a1f9c2c554d523e49000d3f7585d8ce407c50a2f13e7cb50106a4ada84f8bab13d23c982cef4842808785f3401083a9babc0cca2e0c1d350ce657414626279ce9edb7fa06b8d2886d25201c45bdd0fa67450aaaa6a0302bf354950dd0a241ce080e3fa0a1bb04a64e8d439f8dd7ad648265e8a9c7212ab75f55b870d020e8105b0df417b5d21a91e9e7159494bf4c7edef7ae0f563f4ee290170a84353d90002de3d8043c1560458025ef1ec5e7fc072df07628507d7755bacef43af8b57a73c1bfba466bc613fd52a7c0672db8735b1c9f4e7e0440e2162181a9fa2f2211bb895e38ed90accc8491b91099ce0b1a5990f26901f784135d8736fea3840c9c5f5f05464618fc36eb47c8ee1d91ff9acc75ffdb7f62c5602e113f952237cf318b7ff2893019e87532a8ff32c88e92cf42b1397f841fc399781439f8797e889849d563e86cfd911c817f080dcc632371c0b97e36a00c948f28fa65079aa06e86ad4351899db31149c68233a8dc53ecdccd9122ab8ff7451dd4d20e1e9ac4f8cd6191529d68e017a50dbf8b4a1d80e81f7e1e0b1c10cb2729f500ea3ec93d06002bcd0015908b2188543f9ae489158211b9ed619d20808edaed1b8ad118ca433404aff10029bf9d8cd312b34254b37cc934303d38e08342345bb57db1791cab353f94c75c6d3d7770dcee2e5b505fdcec63e772360b4a1fce577317e1197f8e08fc90cb87848444754e9ac241128f42a2ebbead9ccb9b6e681f89733eb79dbff2058d08dddf8a033b2cb14423da9ce24371b9b4b502c6a89b3908a767456406f5a9834cb98aeb963ce36046852995c439ee7ade31efa9187ecddc2d602a0d81a914084e5f1ad1f7fdf10f72d6f5b7f91af5190244257e216e8239d4ece7be509382d2b0cd7fb2f1958e4924ad6afc8e2f3d0ca27261eef9edf9c1cffd98da1acba3c06b48e7c9727a19d4fa1c7a470ce7729d830b709d51ba2e2d91723a8bfdc27f509169f9e79a28927f68d3aefd20a069f5b3ed6b5e8b67c1f48413ab6b1f8b740687c5a0cd088b609c76ae8d3957e3478cf54d7424d37626771861bd9322cb3a64231f40ca3957090f468a80ea8ba06a694d39bf72f677965a45c11141414c10e008f9c65891b77d614ecafc291125a7596414ad4198bf13cd0a0f3248337bed14e06f30146ae32862a69911ff4914d21ffb291918f2bd3b8fbe9f18c8c1d2bcf3c537a074bcd9fa2791c7c6521706e34e28eaffa93be713b301e329c8a449677b8c84b85311d00fa2b7489aa834dbdf92fe693fd6e4e7789b7508843ee8279e3f0db39815f994624e057251ac9c79e28fff549377220bd0577d558b3cad6f0438c4891c7c2742e367e2db419f6d033895e3e8b1a136767b65926faeaa9aadd628f33e606970c4196f1c82476f3313a8f240ebdc9d26bda72169d9818252bcabec00abb08616d071b0d0127ae3dc9c336bdc79201453609aef57b7e7e16535118da533d8191d696797215946517bf998e7005d7b7b08007e47ebc365110f2caeb85233b3c47fea5d96ac463f92f11172a2d012ea667974853da645573aa2c14f41b076c71a6e4cc0bb313529498f92553d7a312975d735b335e89ae5bdab8afeeb1842742508a74dd75238c0fa6ec4b0566cae5e09befe4c108bd554ac9eb2292bd3a340d53df1bdbc5fffc1a52a8b058f036fbdb12ba2850fae866147b8f383d27454a1139ca425e3bbd4b1d94530f6c97f70d3538dc73b8b666ed40ab94f90e2db0d258381cc37d95857ebf58c509aafe65056a5b1b4941b60142e35ddb2ddaed31eb7afe0571ae88a3eac77fdcb801d290fbe5ca19fa18247f257b9d44bde228b137a60dc65523d8fef33e6c708684fdfa53478a49303e82a0244828fb4a54e132c928b301243f84506187844f1619c6585e40e105e0ce80ccff20cef238bb273b95bba052b8830ed94865b5e0e99dd6ad00891db54a97bda266e41698a78e722cc012a5769f2a527db1240779c3cb12a6a16f8dd1cbb5b0c152ca0950a260f95dba9165f5c5405636f9632bdbcc7f81a9b3b01e950c652cd760ae083b9070b5800538f4c9fa41d302d2e1257032881204b36ef9c4b3a9afcb413b38d9bb716e290c05eda49a18a554ba20d52a8c53ad4d481d523ec71a1adf60a0e4a5b827c38fceee658dfd61297899dfb85924df55586063de834ed73fc9849d4b260b83899cff67d8ad17f2729acdc19b113f6295a9be29c3e9d5c4d29cf3ce159ce4a094b525d017d16324ba8a2f74aba4d8ddd8c38decb007c584d86485238ba5c851a10ac3826de892f9b05975a583ff882d3af7c5b204ff33abf2ed313447af5af8b54a902aa00468d6ccb0ae9ba62f5ddca616a5d700632b84e3a0e6ee24feedc560faa2f4404d89b157d22727db967eb0203753960c3399f41e635c10f3b1831d025653059728a14c136f692fb49b3887f6ea5453ce233cb5bed134f6a57bd043e23c2940a14c51b62a2e2b91962433180708082d0f3e50594dba40a131501881355dd3ccb53910a20bf5d1448c5088de4295978f219a471c5eed9e8bce74179027ac6fddbf8d379de50b75785b1be940999903de29b886e742c87d9b433f73b5c78d617c6aabda5ad710c8eb5b84ae649986725d09c668f561b1d69594890d8a8466abbaa48031fa60a44e734cf31038e9c911e8adf7011917835c68b62ff6fe4b033f7bceb9e3c21c07411ade5736251efb5428dcb84379c407d513c3c3bef9ed2fd20186b850a4f18e9116b4e0d55ab7a00bc47079b85815c2d467447167048d0f20353efa5ca4733c1bd820e250f2a29bccbbfc48c5adc5f61f891dc5006f5050fbf2f169be01d7c1acc68dd900289d759f2a450994487dc85fb6513e32c8412515b76423c6b88b64f244ab1aeb689cd93936cf0f9b9f69e3b2f2abb8a1a1cf08f4447e453340d4e6ea436304e5b3a3a29d1da039c7b969803c4116272f1097e1ae94fdf4fd210b942d32773c333df73eab7df01072c4d48967e24654391e5f818a78ddd2c6e1eb912bb96e66588beae3cad8e8ec8608a485c14b85704e85959a6cdaf891308a37e66ed93b501a28385d095bb4f8e7c9736052d7420fc5cc1351c4b206493d607557c5687db2bccaea21acdecf039e4de363642e2db3a3ec41122a037276b9033e9a9aabc23a8b6f7ed780f20762f08decbdbc3a9c3adae96d6c52bd28da1fdbbde4ab3863ed0ec331902b8ccfab09571009380cf2a73f4924a15c6880b139a17260e01d95cb9a521e6bcf977e8ba92817d42968537520521d44256af59369a1ba423da9498988f8fc0cb4f490906f2940feb17a92bd11bf09a98f67dbe1d78fd7c672948c953c615100e976486b551b4fd900643c9737f574e5e13ed15d30804660ce7f0cf19020937187fd0117abd311ed31463e6de1e175c7847371fc055d217299b4529d4bc2a95fa7ea17087cd388607d205ca301373fecf03be0f81916aae462c5153166ace864e07e095165f1e93fa82e514ef5a026c3a0b8360dcf2972682cf6133b4bad678be05c274c00233a049dd9b29e08ac91bcd7b2b5efcc91a2078568e26df00f85eba7bd975f2fc35f885b2ce3705d5e862b7c16932ae2d7e677d3567edd5960f2728a6389ca4855fbdcb0cf23c512e76c4b5ca9d3c9baf6ee9d6caeeb6e2c0384120d50b33b85569103b4bc92f65f970dea1af3e40c4999bf84928b271be6660e5285a3ad854e88c371e20d736ffd7be342cdd14200f5bce23a4982760b1fbdd1def43e65353c13e56379f2bbdf1034142a2383dfb121586733355f2881f519354b64b87bbf57ca827bc7ecdfd28eccc1beeeac64ecac417bc8096a802901e10c1fc88a18917a6f1053b2c97393340919cedb4de332acbc82e2e96fae92fae84b9af72d812d85c4ba0fca8ff88f9a480417db6bd9d4c4ea1db421db75a4b721cf3664a5809f8ef70a47de024b388741339c70a283d1a65d5e0120360351bcb57184f340c4c1b7d10550d61f34f99c21147ad0e853ca926f9435f416b4dfb9988294f05fc503d180ca37b3c69e7bec65bb5215aa39ce5e4665e17323ed88e9d39cf2bd8c130d6735cca397fc01455dc1d731e1a33f25337a22f99db7c03e6869255d5eca392fc54c6952e77ab7d429aafcb1b3bc28136d46c81a3cae35e8368d4f6e4fe40f1af83fcab3a6654a22b669842715613040ffec3136c28c1b950b1329a57748cdc9fa26ee986ee9153292c0662d688e8f34aa2f8cfb8027c2414a4a687ea4d1b616eb7336fa2f48a4748373d3813af7102afe1a350922780aa7ab078dfbd34240a366bcecc670e183252651515434de956137eb9cf242d06e62a939dc1d00157c12cefb5e00d36e8bcd4747debe0660cb62fc2f9a781f25c4e0ff81f2e2421dd9e1762d46a784b176f2aae0cc5441c63ef8ac1ccdd7c3126d4a1b7529d8c4e7b9a41347db195dc17367531c992f0b6b8fd6c510726504e951da35c5bf211f1e00338d1f24bb210308d02bfd65fcd81404d8c5ab60657ef5b89dfba446dd50025b0189aea32a4f73153c39d3682572075904aed4f2f8ebe18e5a82fa7576ff533b34999a2f5cc632d57500dcb8ca17766a1e7d212602e6bcd9603c4a096d01f9c0a2bc4ef1fd9ea6dff15346a70a440bb7344c245326d3e6d2f51dfc3cabcab183d437365ee13d75de2670953855b6a753def286550e8759b21f1af11347b1642bd78a30901de515a866df1daf8a2eb491c7826362011003954ce7ec85a0b475bd191d5fbecdbc8f2da13ea51c64d08fc03e044a148a05a52c3796fcac28f96f94e04720a1dbded8202ee1a457e339b68b2d1ac3ed32659b24ed795821997c5cbfa746d4aaec31a4dd6480769bcefa0c4126dc0d05d17afeae387c6d3480a5bed433aa95b85a4b1882713c42ad05b9d39e6c28f65261ca54a2c4339373682f55cd4cdd096dcaa911bfc74f1da85f5b3cac3e91fd3472ff73b78999e763d297260095e09641a656c1a305a2b222be0ad04ccd1259c55bac6e692cf7700e45544e0f8c18e8dec8742eaebdd1aa0d257177b26354a9aceed76d856e206a8852c4cdd81a434e76718a1cb4d03c08283ccde17c52c0a3801794b3eaba11ae7639299a2d5c9a4c8d4c69c5b98a87f145407a9b9d96704353c0df81ddf0071dd2bb499ea9a3179489a3c1f05c3c8638b272a22b1c1dd3d768612055c8201da31894159400cb1930e24669951ace730995b7f419cac5d9ecda8dcebab5eb5fb25fe565893d13956ade389e4e409737104afcdba23f36ade157737c073a19c3c8e8259e11bea64100a4644539b30d04025c8228bc876db5b4a29a59429cb03cc0366043ca3ae253efc5d2e3aa7f313415dfcbdff7c13f87befbdf73b50dd7dddbdf7ea3a5011f8efef2890fdc49bc5a300f40694098c9083b903a0217ff95a9beff859e9cf1f6aa0ff878ad0f99c73fe0d576704802682025fd40945103f2882614ec782dfa8e1e64a6957b8c108ce267764870a9ca0bf33aae14e368db2495124453ce27ea88ebe085dbf66e27e7d7d4714f6ef033a50201c5ad7bf7a24130b7d1651ba6da54ec709c6529ca86f4674976a5f579b82bbfb24d34ece2fc19eb99bcbd8dada091524ec485e1a76bd5c2f1a28a04018fafcc03e55b173db71196cdf04319fee39dafaf373cd04fdfab8c6818a6d2dfd5affd64c58801c776ea3b04f79e8ce7d4b2b08a1d370045562d4e9c61b709926f5a61b3ef78189f17b8dc4d37c9b9bf34ce83f4144966250c56aefbdf78efe73fe25d3b5d68a1566c0697fc7a1fb673b611645753e55b5e7be58f4152deee6cf0f418166903de7b4f151402bbd4093dfb6d65a2b158e1fd540df7b5343b66fbba3a2ce0ba8701d791f9b98333fcdf9bef733bd5f3facd3e94efd39e95681ddc74a73f6af7d9a7bca1587c3a0e96bc0e337e2eadda2ef48c58417b4dbd0822682a2f5712268909e74bce9391910fa68b7a1ff7c5487452eba47df2782aa276c55f7a1832e8c2675423ab7a276137230378f2ddcd16e6f15adf4eb17f1fdd5df58d34450f9c127829acef5d381ec67ed20bc8e1aeacf7d77a5f449284e4d04355ffc08a71884d0f1eba02682bae38b1f81a31854fd45d36fe2b54e0bc8dfa4d655a804ddda3d342084109826851c2054835c48eb1bceadf869fa938b120939983bebfa16f0cff7dd0f8ec35909501feb87ba81566d6def552141c0f65ed5d1f61a09dc07aaabafa33a309ad409e92a0d35d0a44ed7818ab0b557b562d7a7a21f6147aa89a0269d74c609a72682f2f9bec3c3a36dee6da5800031347dbf4fa94f28a1eda3ba9b78b934f97db8477173be254042e5ccd2112e245ca65312be213e49b908ed0ebfeba838568a85bd6def4ddd7871879c80ac0413b896b29045354565d937caab82a7a90fa4e418dd18b24ba0a5b6b6ffcef69e94923d474a69adc4bccdf79a7b4f4aac2755c6e9f4a7bd28273d225e3b4aef10fc6532492ea698c5e8398dfdb84f389e94101f5241c6a4caad5a33beb6520e68ef11c9dad65623a1d30fc8e4a53833e205ce1c193da48cd026c61c89b96011d574357b3839e9984324c9a7a1367e5221048dab449836a9b3e44d6a0f81088cbcbd47840711a4f9b696daddfdab63151a5789d60e6c84d08534c44753902b1e68324f96320f37a997cc78b2ec50ca0af602bd4a3180434c91ad211a4f5aacec70f30132a2300d0139fa52a2d3f469c0cb72f1bdf7de7befbd775ac9c820c865df22706c440ec03b2b09c0c4d7b8293f2e27b41c28bc7873c6cd962c1e271014f0d62099a5af90016f266929b34a91a706eb791e2549a9dd4c8c5969418ec7a3cb74c205c4f32b5e258bf203ef4ce9622e6f677ecd6000cf9d4c191eb86205c78f154b2258d16245a6a7690a96d24b09f2c6ca0d09e0dd90c212522e953642f0269b12ca2f25e639584933f01c8d869a2a29508a8cc9c97205433bf958d1e2526294238f4c85ac49c4dd2c29a1bea42ddf8c7266391132ab5320fdea6732b24de2ee1a3d821a44b3b5d34e3bedb4d3feb46816cda259348b96ed1c31cdf8928d404b102d45fb7e1c4c3723eb114e0fc3300cefbdf7deefde1c8661ce39ccdf776fe8bf757222b82f5220a73a6bf745a345f20b63138531c618632ce64431e7817b59f7f2f2dae42754836e4fbf0307e1f33d3aef35a803d78d26e70af0f16b1e5cf3c945c03de0bfe57694037e0cd4a00674003ffe9e11c43a47eab6f83c39f2b7f83b3912b7c5afe548bdc5f10376937305e94c7c2baa87da34b773bebbbbbbbb5bfbeeeeeeee3ee7bb9db6bafff4a739d28a2aecac5f400d7430c1374aa1ff50309ad43a0f87824f69089d3a8b7e3808f0e9c5ffe1fc7df5cbe1cf18db0baea03d7033fa164ddf2850fe726c6c66b6c9bf2ae8a21a44c130bbaf7ef5ab5ffdb0bfafdfa7350ef7ff3da9dba07ed8f77178444326bc28ee6320f0f3875e9b73bb7bb87fef087eb9af1c421e4c270af37680732ecf627b7fff1314480cfa76369d07bb8d227fedc35a6e67fceee314bb0d3ef8f9c908ec0f1cc9ddde1949f033f88d29f228e64e84958378d3f4078e9489d0d511e610f8debe7befbdf7ba981fdfcf37d333cd41d7209da78febe020f0670f74fb07fc0eb66edf6b1fcc91754ca1dbf5edbc6d700ff36f1c4deafab7ddced608439e361b50206c74654a0adbb7ff5593452204d54df74ce9bbbbbbfb6ebb631c823bda707ed8c9d179ea1d726efab52ca4f8eb8d0665f82a86350f761b45ed77be6a70f49ced3160f80b2008822389e16d1872be2ff0fcce6f40775ef3fc53200f701bc58507dff6b6ff2850cfdb5e53200b6ffb1b05babded7114c8e76dbf81025d78db87600ef7a1d7308ce4063686077313bcf0b81c89614c81db18fec2f85467bb306ea03adb67c879e5607b0b394d75b6bfe56e54677b9f1c8eea6c3f6b247ac68fea6caf73e46edb46ad73b5e7c99111d8e0efe448704cb1dbe0d734d581b5510c731afcc011fffc8f83b759e17c2a34db7b5368f687edbda923db0304d0ae415841af8a6c87806e0fe40d820e3f03d04450f6fe188ab0ef1f34aab662c4d95a91a7b8496306ef10836d8899e9ca71a66417bdcfe80a631171722a43c04c3588b48cb214ad1621ac00030d9113b0982e55b40cd9528295939890cba4a080e0147a602e378aa6c8883285684b5a66a3269f40e6091946c3854515394fba2079a2c25482fce0e5a41c6352660891f2a2481c0c860d94b32f3039eaabc7554294ac45093ea1080ea2c121d94c834cf9c5d207430983906594db640de5ac344cb6c6cd59b6529616866774e1302d2658d9cd912c02156e22a48b29c758f2a144248a11968a3284dfa48559f3e40ca92c3b09cb28741569f5b03092232b50652521f8071356b2448252ee02c5a80994a12c6979694dd3598d32ec240c4fe972d2ca2cb0561c89a2ca4284e0214c1f8ba5164a4b4f8cf28d2743d82a6dce9a8c7686d794e12b619f8bae6f8556868365e308b642d525247b61ca3b965e28e1224e8c7012274373d2f2d19ab3331e65d984302ca50bcbd0ca54b0c48e6034aafc81101d4cd86829f394321696910958431f511a90356cce3093b231fa0bcb41f4d78545d8d2f2245f2949323242140a23603325465ada99a9498d4dee76b53639dab5d6fa614747677280abaf49ca570387fb94701f0d2f27abfad27b53268b47d32c636db8fbc40a7d53faf51b2150c11d7a7ead45aeb6ace50a9dbef79870656def31d9da117e453e123419ce0cac8bedbf816e8c9e1505a51f0512d0de6312b5c99f4d71ce04c89efb0737c16cfa9f96124effaab678b1bb1925804d6ebb19523ebb19dae71723373e2322f8ccc05669433edf0cad6f38bdbdb7c4cc26f5e712968cf9967cb94bc0b2bee19648d97dd964937a7b2d3982dbde5bc2b415ce0374e0d24b927ce6f7961089ff3f3a256a36f93b20b34899222bbe2932a08ef62d3ea0fd5c4f113f9dc39438d993ca864bdbddf1591bb7cec03f49182b29da580991fdedbd24619260115be8200d1210bff6b551c5080d12a8fdced77ee71de73a0701bebf2ee15be71ea87b98b5dc8f4efe1f9d91fcd9907f83fd9a711488fc40b0230a3c6a52fb47ab56e854ac776596c1ee703f9f8c14a26b09a245480af19371dbf52fd0b175c22d08085766085b4a30e59cd2003c53ca90f2020e3c01f05c05a552c7cbf2c5698cd9d29223cd0f24485d8c94f53879e03997134a27f3b4a828652d2a23076cca8d1929b33946153a591da931a9480000cfa74c11c2f89bb0e9555a6c3529d1ca1f5ecfd160c84e33053fe3cd5e09a73ca1aaaca087b51a429d5cec4589e361994d33c734f212a72c4345abb4f19890712f27a2a1124a6946036fc2e14d2b34244d5d535d63606246da38cd346e66c7193093cb519845de6484d24589810eafabfcc1a65c00af8d9a928754f6f05698637ccb07259552c4006e33edee7805e04d2efe64965d959e06cc8961b3b4020b8e1c2f61572634f9169a00f0e61b0b3cef723961cab292c6110aa502848210c02ba263328559817281e763644a2565ad034facc4e16865782b26d8134f8e8f3165c88311671af1142610ef324df0365ae554e95f5e32f27ce2e40c23a50f80886931058c4b4fd39cfc24494eb66d2ddf7bf18780a210c37b33bd4e7e3469cff0febd378be1fd9026518a34796a2674369dbf532bb1f3be2f124daad548cc2cd648dcdccefb850f43518509e8d3227a4479883914fa4fb027a5b4881ecd0ce88bb486ed6fb6bd469a19e4bf53db489ab4afb573d4b6d7b6bf26f41d35683a92d3013568d7e343e9cf2fa24797e6dfa9d54a5c102fe4b7d0f3b74cffc35a09fa28ae0ebd3ee31dc914b89e918e2408c2076b2442ca648736ad73be6d369b7e40b867cea705243ddaf7bf5a09fa3b8ff1f3509d0fad48914451bc9f5f7c2c8e3b73a44554871f5783328cbbcac17eedbd06e146af1c440ca3570ef9edfc10b751807f7f825fab8d1a76f6c531a7c0edf95f161d905fe3f67d6c9f22891f3e2da24898268120087e14695a80c97ce86d086e6ccd98591a37f366c904b12e2c5e601971679b7dad6aec6b9583670c0c0b7bc3dd7037dced37a06e4675be6fee389f62c6b53abf0c8a6389e9baf9d3759ec59aabdba9edc40968a8cec7aaec50a746a28239babe582ba1e39356bb53bf8ed8084bd36fd22a7e3547187e6e3f7aab9d39b2fa74d2fd13b5ee1d2bb26f8611f7de7befc560fa46651c6343f29531262c633a346cef89c961a3f52383426adf95ce615938ac0bdfb02fd1868911abebb034621cb1185a677b4f4cc726f587675f4f4251fb17974ee8fa0b0cd499a1b5f4167d79805569ac1dcc4978c1acd86c1a0d8606d2301662988c5bdbdef3c271153ec97d71895f60b5567bbfd0d4b6f7becab28831c6f93bea81add01141100475c209f6038c87d6553671e6c09cf48813cc8a1679787878b4ee81f5c0c87a6068bd3016db2d88b71a89dbed66c1428d441dc36a84c90875c0afb613d2f8f2917f92be9ebea86a9b9b33b622e15ee118c0d3c654df7053368ef4944dfe3682b549bd821712ddffe8bca6bc7e90854ec359a7b54202edbdafa4af22b44d7e68b6f7bcc836296ef702f3b2b2e78b1910fb21b6e52feebc35f8d8fb7adadefbf2b11d01bbdd051ef497da0cf6fc8f4a93bbbd13a21f84e6ee8350afabdc757bafabc77e7163933b4ca64b060ed34554425a0e354664b20fb24cc6056f55217a929b902851e1ac207809cf2a870c2c33a8b0bcc847e2643036f8cc1917983cf4c5858d44c92a28e951045f0d81c14463479eba02427e73a5c8c59c1e2a50a5804308a3262c8fc152460a7e62824930926b90f01535e1183cd294f2085c1f6c71b1f8d0a4cee00fc68819cb3cb264305a78ac09af846f280a0f3d653522642436beac1ce1292b4338c79bfc254de66256c5cb8a2d6da8e0234ea21cc967a6b0971168f800014759152434555c603970729635390514b0d8171d5e6bace01650729924774e550b211847f91de9c8505492f0172a30f0999c256db256990ca40cc3018372c52b0b9b2dc958ac3219a82fc68fa51b018e93a814aa5c1162c8d0d00800202802a3170000200c088443821cc8c13814fe14000b57c4304e3a22250cc290301046610cc32000c2000cc23000802000c33008074150f30340af2e80751e1cd215782719c10c9ec895376ff95b36599b10fb1c86edfd89ef1e47ced678b4a58bc1f29cac039a1e702e0cce276c136b3f1f9f2ae9a8e7513b152d1eaba280193d3df4f9b6fab85263c81afdc40e499742e529a9f1c20c1097e580de7cd9fddcced540da0c6a53d4f39432211dec076d04423d6810351a34428a0e992a51d093dc92cbb626692bedd8d43c413f29a9b3b66b69b60ff2fed46591e67a57a6897d71744c5e42718333d325a02abb38f8d5df70ba72526e8f0287730296b493245b4d3ad324176907e7e120f57cf9d1cef2b460b18e765a326c350dcfa434fa4c2e97a1a0609929ef616d812227d74f6fa31d1a8560206fc771996ceb74a621d539b6ee6e3119bdd7e9dc1cd7bf5b4de786ff6d028b670639bb8a85f508eb4e1973d9362d6b35407292d149bfc6beb82dd0970dd196e561afaf48db7822d1111559dc678f460d7b646edae3de76811591c3e93e75676407cd5e85a176810f1435666e75ac342d17cba6a0535b69a7350c4ffd39b132ec6ad57b354d2db4da9d9b0afdccceeaec22a3500de546cc7205ea5d1733758e311292a6a086963f3a79336fe1c87370975b4a86716d5c82571f105f765678f0cd95521d1e7a9f6017d0fa7234abc069f56e8787860c909ed715a6c114d65548610eb713f32b738810b1ec0dbb18331539a832b91357bcd302f5226ef90c68d3e748801cf2d39b594b8d11960f5ae01a1d0c36cf5ff78d26369eed9a673217154ff6cf875932f5089bcd0b6ebda406b3186145c5e4682d51ad85107dbc486c68e2176986d9a29b72ad742d9baffec10ea9376284aa56c760b7a45871a6650c5cd52f4ed1b3fee6c6175ea6f8d5226c4ad51fe63ab0dbdf28d87982fe9125794a6c2f5b3e03062aa7f8d01385043bdb5505b3640bec149d537466f46bad6e993c6c34735b3bd66bca351f3ecab1fdd23bbe398ea4254faf9a7279c679dd95d3831c137f8e15c16b077118fba51e9ec4e9b29d7321428c28d68b8a12031b3b23db6e7dd577eec40599217b9ca8e19c717133eb75f626f632af09a4e66bd2c106546db0754d29422d7beb3ca9cff0c83debcd08582d88de1c0b031b445d114924149c2e4c0f224780af8312f7a93b92c7c31f98bef522871b89157a1b9c8f1bb897d465d8b6262e7f706afed6dd2a8b5d8a61e446ef0968e206eddc41ebb32071946c020acb821440d70f8ed12c41703a1929396bdedccf773c420dfff18e7f6953278e5553b34b75c7e7c440d2d0ab8d1e605344e9f4d17e6d4fa4b5e10139dbe27d58b7be901b828e3ac7a1729250a75ce187ab29890f4a131152eaba318d2479fa2637bd0cf89a784110ae1f47b1ccf09667d1aa5866a49507c5fa3a050a6cc0d0cae2ac49979390577fa0b8e1015a1da50767d36d95b9e451858edb0a4f9bed8b61dcae6b48c58767296480efc24c0ecfe304b7ea2ebd8395a00ba502cce648c32e3ef4e6f2919f075677e1404ba54dc88428a6ca86d0363feee7c53d6cd9e047551c446fa63ea1ae4a13c1365fa3dc33b4b4b26f6f70c6b6c106030216a60604c2dafd7723c187ad7200babe65be8b08012c740963f6fb316a547ad42e84245e7a23c85f760aef83c67765781bcb03435107c68dd7f7af90977caffa2f5d8f579d980477efc2f0f658767fe296e3e8334f2090a0bd57f5c7a73432caeeb5e58d50ef0556b2e113d6add57895ae53fe38300a5eeed3f76ff0b33ebd96f1235882d373c5bc51cdd9279b7e75119097e325374d293b072a8ec7dbab9e4d6bbe5885d4fa5a1b79d4e7c194bed6afdedcca7df8c37f4c18f3697047323bab2e8a82031b0781bf8c234bdd36b83d48869de1678de0e397dd4c1c1cdce04afd6339a093047c74de7fd171c25384c7e67e51df2ad5240b6af1a5441c58c3e1a1ab29fbdedb614370c11e5dc240490a4998cdb9e332459d1feef1511915fa41df292795521a7111b17d9098072cc24397f4118a1a31d240a2c4121f9693e6a593cc5dbf2e18f162b4513595f0bf8294f652d6cff9e154e24c748651f51fd58757a3b1abe168bb8296c3df87b038793aa15f8dd80c0dde4a2940611d8b9d7cc91680b89386c4f8f00c962ddd8800510b4e7b764d87343735898432307fe81038965353c45495b36ef22c6625fab71c0c2b12e0eded55abced516ec976d27e719af4f7aa5c33ef3f44a5d5633128149c2b9865b90c5ffd6caa95a28794726dccf1c265a19a86be050cb67797ead71a727b5b24f4b6208b0f5fa0f673aad525891d11d4657ea091a5efb48cd5bd60ed062de0b634bc3a960c735384f1946378129ede54196cfe06f3f11377bc2de1712f4f0a79ef1a8b81bc59e85496190ca27b8ca3e8702210ac527bae4c67241430ff19c8ce402c4431f468b44d1b27385f3820133337d3bc0b2f5af9b8f3437acb1a4275c158b8af697feb3322a353891fa0dd97c1faedc2ebccd8e25a2aff325ebf887b90cd752092a29125ce7544ab141aca6c02487248abb3d7db44000dff014d9446496bee922d8258c98a6905846bf6f478f060c60191c45c23eb6a1b8e259f171723109375bf2b4d6ead148d4a40aa57281e443b5032e8a43fd0299ac980e0b9b6006345a3c2f70b33412c9dfeb681ef90824b6252ae4f13ebb544e7c0dbf181ab9f81bbede0e2ce2387882448b18aa44b43f625760c0a8a2086015a3f4568ba223054531316925ad7a260b8687f904da6a0264b9bc4dca9329bfc70a3dd11228579aa621014a416759f5a9d9ddefd99316309c3aca22148ca371af2f18cf4d64c224e58c9800ab37d5d4e1b2813855024a45948ce32fb930692eb68511d1ede9d137bc8981168cdab106923e03a16dc1906915a5b8c6d933e377649196462610a3746c553f0cb756f2728e62394fe05d77c9c64c876087acde330c9cde13b1ac082572902c160a3fbd264ab5e5142c3e0ba9d0c0b3cb1ad0a64c9c0de855020a85d4dc873816dddc123852e2ae4a12f2e8d92c1a8cd045643f4a58cdf95e5b5fceb20bbec06d67b82bc4d1a880548b0fad8c71bbd007b4e37a0559fa69b764ff3c08488bead9ba3549d010bb27c1a9d5758729a65c6510f5a8e40bc4e12bcb938dd7ad5834d6946b89811309cee4d2dbc74c168b87bb339fbf851ba4f08932ceeabaa45bb4f713617bf9c917bbb824bb759d7083b708aae27e1a7e7b24c06dde4e453283be5ecaca6e907a9cb5e2a8ea6b6f08d11d96b15113322a8a2b63fc2677e8b47e0ae35f5e9ba755d148c64a9b55a946f18acb537f506782fce27f8cf07a96d8c76ad79ce066338d52097745ab28e4ff0d5bc34a0595e927ff86806805bd19d175f790f0a6f6334fc5db1c7f8337b4efee50265326b578b71e5dc526e9bf45527fe535e94f44577575c8598e5716fc30c8b97ad0526d0c97fd9168d5b1a46ae3da8d31ec0948770a51a50f41ff9c0a5daa682c448cb6be0f024a0f941bcc9ba3297c50b27360fd18335b0c2c943f08a072af8a463a7ad6432d33bd2d2ad250b70275c7db50e669df9abd71e02d35cb3da80d45320a886cd307ad1e7212e0bb041424a5f2c9c0b5687e6ece8b858e93ca731a8f002c9936b8edf3a0f2557e193da86390de18c4fde075fde1fef08579e37f34638b7a170416953d3b82232649eb47c9ed233b305be42e29c49c074aabdde14d2b15b56a52f79a94d33873025b8d377f7966e2fd94acf38dca7daaea61c3bb4f293c380951bfd4ec43e929cb7fad2e279981d45c2ef37485fe05bce0b77da90132c8181be06f3f525f74346c591915771db4bf988e8762e27e155e13153ff38673debb1ab1f9c27cf8612f83cb8981b3c5aa7098f4e75c38a96edc818ce213de11e8372b9b428f13213ebb9834f8d3610f0fb336a5343d58f6fb0124c5dd60a18e58c85aebb48258ddf58612145f54b4d9fc9ae25258bbfaeb2d3d12b08021d56ae4e4485d6d5583c7998b4ae4c370733b35901dded39754a148058d0b4bc0d83ba71d44f97ea1150d1fd4381659d5aa795002783101c8a011deb82c5d97e9fac0ad958cd8fa1c39b3e08b5123b70feefe16d91abc59fd3aff8f850c1fbf6ff9089ada04e6404ba49988ecf61759ad66878ffb7b094e42212047db3c0e2aecf89d042fbff471afa5ede0fe8dabc3578680eb647a50ad78df77c4adff21c77830e381a657e2c4cc00a99abae277fcaff11422d8f66f0e2e92d52ee55b9d8410781ac9877fdbc9155ce0beb52089a62adeb4b7ea5e1b8c3c73056bb1124bfb9b998ce2053a986c90905045bf2956c294c242867b2a0943ce64f952d1e5003cf577dcefa23984f70d5481aa5e8643fb25ac3f28aa639cd7d510e76363dccf38a4d7f814cf87f20fe41bc408db9678a9cd421390cdf3d398973d14a045f7b6ad0c5221dc4441ea205c1f590759d24d69730ae999d2f16c4b4ea1a4fe61ee831f10da5ca78c32b10f13414ac8528939a5463a15249228a9cd4813658a9b2010eea3beba49f32be026f95c4da5127f4a942483903be91ff84a943f3e54100c83eca69f524915b0e2ac360a4772af64fe4094206095644901c1df875b540abb5cfaa080b07f1440fa8f9a203c481d84f7414e98969454573bb35ba1941aa5c9cb2793a8ee78667c083e704b251b9b5496fb686c45907f3f2a20e03e50974e142ad0f311f79400c142a3a61d206b435350b900e27ce0128756207f4279c76f6fe91e860c3b648976922e0071db4d458f3481a56c33ff43d087ce870c047690b7b314c93440180a2249321553d2644bbd4e3948cfc705a407811690132ea59fe9363e659f817d2821a1d4f4083645de0fc409564ca9e5ad60e505fbdd4f5a6cff3fc01407d017b2e2471f0405e283a0fbf0ff40ffe1f5816890933f15b0d9763875902eff3019a6923cb53da7d6a6af206ed1298191268b543f210c926d57aa9d8e548d4fb7a5544649932df59e723f3a3eea0272924f91e784049eb2491c15239f1feb1fa18fdde3003d1f9526f054daa524722976f9fc43e1030402bb49692b369a3c3f42c7d1d21d57e6fa5545903fd5e864d287122715b81ce187db2ee551c6f880166412624aeaa746dc52dadc021124c8fe6e0a20853fb88fbc7e98e4539e190361fe2004e140682ada09f78792714a8cb412c849374580d976983296817d980853499ada3aa55473e9030d023e074984de85a83cd5cea71f0ac8803b50eaef8951429e62a703a45610db40292d2d4daaa9ec54412a7cb47c14f8e0faa8f3c1f7410361dde964a5299e72f3936ac548cc680cac4a707f26ae9de5c331a4a830fbef047dada6aa824921299c92ca407ab0cfee673eced1d9bacb4bf930f953f129b08a717ee3098cfd8399dd3acfbc20ac77c7818aaedbeba65fc412cf6b88b2de5ecd81f1eeb83e04bfa718fb1c95a5c20e901e54ebe83986c76f417d498a3401a5b43e25b1a774920582f8231f906ef129947ced54a58f5121225faf43dfabf4f5c3242595990a48ca1fec2a8ec4d00f25dd14a30c07c8892295aa599b2545b0d24498324e7c1533c7f1a14499522daf0a22493a85916b1fd420d080d45648d5a5bf3e4cc429f12c0371fac0abd013761f4a4e296bd2129093b414d126b6f694ead2a42d15a0f3a498ba2e21fd685b4bd1a4dd871864141f6e219432526e25c9df1fa80091dba066ba9f9b4eab7a8ceca997190ac846a4528a9d4a9629e1a449f014d166b6f6942ebdf5619248a54e1da419441c90134b4a0cd32d7e8a99eef850b248ad97650179242ab5d9a96439a59d6c1b4ed19061930f49034365cadb86837dc9a07cc4253f21db700332ff4d6a1bfb01f7bf10997038c976a510a403b1fc811c886dd7a9749426f8143b1f200a08880ffd0f084820124ea214e141edd0360547e4ad47248240e018bbb9bd47e50341b0c33f86d23a5551941ad692662142ecb15e02a16f907c1303af3f2e64d1b85607ea9bda619be34a9b58d41bc9a3770ab59e7bc42c64c52e10f31c2fab4d6c12f9840e0ac432538c441f867224766c14ee401137198284caace5772d9688c535ddfb9a17b09ed9a50806a542499e1eb780850e42180a41c3e2330b4c772e821ee21eb8839e2ba3efafacd8c967ee0e171b2ec1a5b98c4b8ce27a6261840bc4bc084211d66afb6f4c9a48f1cbd2a9983c856a209a55c1468048fa60e8f8e8bbaef9ef0e410264027d36132e80253d1264d636040fde48e3697e809164d4e867bb45f2d60cb7d004256f4cdf8823ebd1879827190a942ab6d6e4c1a989bfd647910db8edf93512f6c09439f09fe4a9d23ac0ae920acbc5b111954ca0c8f64d739190f2876555508667d63a803ce0b7c6fc263a1e0d7dd2929a4567c9452f0d10915c940da3bc987e3b33bbc9e480b82b3dc4012bc431cad49781aa4f88e3c16da083ef8a8df4da2e35a274d430530eee20375757a452d87fa6036399dc39dd803c95ec90aa0e7d4d6fe50e1aa65b1dd4c9ff1c563889060e3e8121a9552488c19d7599d1d05329f8efdf94dd713737038db40283a5f28c304bbdc8b9c417d8057205c1ad8809873c68d8ca6ec6fc222abf3fe4b5778ce8064ec9cf08d09ac7cc0388d34a197652e2a29f31bd6ab98089409180ec9225c6513d310be49b857fe311c13a968f6b940edcf44a6f18b3befc6cb394731cf4265662f037b36cb78201a1ca4d2e3183fc996e2d39c31461578bc4c0d10331a65870f3073659b7c0fa9adc14f560516722cdbbcee48b993deba47ba1848bab363fc76e5c5dee209576f27487d84e4a405fb66c8299bcb1afe504c869b18802454fb6b4f5a28fbce3dab0e6ec8cf5e3458aff39dfe8c3ed1ce3ac36ad21d3e7721cf15844960c57da75354dda4b5428d5aa4a51b057c9d9759d9d25279ddafc21e644420b42302afd6f374d660258134d7e2f5d70a9b58b43eb107d46fa8f2019a2fb0c7204b43564481c0d86ea31defe16f08250b898ce0b234d4a5068319f8d5c2a7b5c13832fea1cff7f28dd2a237a419563685266ade3e7055945a738556f5a68e0422a5ffb0e5f6ce646ce8edac141d72a41ed53030d7d2cb374bd3091e787d58408c867277b3fe0ef9e1f3b693315895f7236691d65375cfa630025b4956d7f8d4570361004388f067daccfefe4f7e8498bfcd00ea4557c5160851e0ed633c287733191719a41df9c4dec280735ea15a47fdaa06c1e41ee7e88050cccbce8edbb1788833a95a435fa089245d13d15270854218ca13b52a4b8135f2a8e0e103596e7402ce5ecc4ce5f827445e70b12aa3e4d8915ebdd99688b37c5e39d38cbdf716e65b9b5b41f4d6762865a2337d1abfca7b958b75c764c2d018113b81bd68eb9a22d484ee839503ff3a4f7ce2a3e2b7f05cf19cc0f15289a85407c985e80099104e3f109949709a8cdc43388f80b77a5751010ab9516b6c7eb7bbac0802464382a7eb2bf01833e56a1f11ef8e464a2e417a25482a291dc2295f4a6a63f69db4c5434e7dbac668fd37d58a483c884e9dd88aa8b8a66ae350d7fde39082a1eeac6b9697f93cc347fa1c126f0551efc4b627f4aecd3e80c7dca5cc266feaba4e520a61c98e48bff3b3bc5f1ce9caf9aff06c6a80e05632a26e112a08ccf30e9889b92df88d2290cce8dda67e76926f040e20f8a571bd5481ff81a04b2134e9508c4baf5e9242d71de42bfe306195ce55ecf24d73c1fc4a594680118ee46cb7856c67f1438614c065151b6ab4a647c81eb092c28c490b6fd4f0a1a91b58917e2fb3716838f98a7e8c8b57eefb7d8349ab2a9c654f299ca19c7200943d213c6efbb47275c295dc6ec2b011a4a8e8d77922aa93fc607e623473ea537dcc8b903a0ae6700bb8e71a09696cc04fea9812fb594fcf7da4863614c0cdd1eef4b739f808c01a129246bedb312a0ad452fe87f4fd4d4723447380dcb7df002c0ff00af3e500451fcfba808a979946a38fa4215d775a9a6ab0a701651101e2945712c361bc8b3b67f46c0109ba536a200621a1dbec1b441f65fcda4157e02065a841dae6c5e7ec84aa65c1ce5d5bbee0fa2e418083bfe802d11b42afd646fe66eb246a8e94812bca1490f34c4729efd700ec45a95718fdaefc37469ef5f76cb67c0aa2beb28487398054d9ca10b36bfd6adf45e063638b587235f6e0f94f7eaae9d389b7ed01e50e8892855bead5c2ebb619b0a2b4ee43aa0026fe63d03c3a53eb912b0c7ab9499988b0bb6cd55df344cf6fa5cdcda35b0d74e2ea2252d75be8ea0d000e0f25f7ad24c9332a2c94a5dcadb8dd1aee2c8b7b4cba4e4ea03b1ee6e2fb57296e3dd20a1e282ba2c19eece49493a9d0a377335825bb5fbe705820130eac5b4a0b547bb5c971cea7013e0b5d89e95d7a12be7a0d2eb680ff5c9b6ffb3fa0ee1c9165fe9e030da10c41db2561c8e7fc7146aa0bb26303e028c0a62714137cf70b9fd1f0f2ff25255ed76f47ad6cba16d6e041ab25d2e5897c4f564b3d4453508774f3f3b82de7faa754a48dd1a39eada16e4a158b00057fb2aa781dc60f12aad6e95055ff3fd71a64b72ef2e87bd2ed30b1b67475801d83e82f4d5aa5fd2f3f8e1733d8b6ade554509f05065d5e39b656e6bc77b6eafdaa48a777180ca389dd712825db82061eff20dab1c10b2852fa9f9dccaff9d15f9f8bfc185dadb6a4d8714440c815a735779e4ee374b066a023a1a3f4a035f42a59d0dcc56d35012dafd942c8fbe9d047c5f94f42e5bc1664fdadd44ae5af55259b94197f5c4fd13e9b02a0623107717cc986bb033ca6ff5471c25f6efc3fe6774c30061cf835e21e8d82601dc02eac3fb4780a80d0c08e3611c85e95e402b5effa07fb6767babf092fe4f0d6100cc78264ef904b51689b8d713be73a08c7380be1bf6ec02f6f11bfe80d79c477099af74abe3d66fc32ded929f21a2702cd819fa21acac089a3724f6231bf8b76da34b2838361baea610412ffc093d1ab0ee771f575e711163012f18ab02aeaec8c7a13bbfed310bb7988b1d1aaf77cddd4cf00593bcb07ba5a66d926cb37792e4cd757b7b90a7b8629536824a5d6d24c8134fde39f008dadfb4811e90a69e83bba2de81a53a870ac33d1bd76cf772b88dccffd5f59d1dd2b7dc9f3dfb9ebe0d94bb8bf29bf643d01a27463c58ea62644ced7d0a71759ddbfac8ea3d3a9f3905ce4b79da1c0bedc1fc9c323fdda62660455376354727b3a73dae6dd345f4f96070374e8b3e8a26a4e7df0382291b97ca1f8fd01724f1dd71a9dd8f18039eb5e80f64c8262910f94c309ea9743064d033b0dfccbfbc27b2ea0a42ba6c2f57915bf91b42ce974c28e8f1c95252bf0054c027f1b2459f5502a83ee88187a81e5e9b9db4a46301c8de86a47cb884bdf6ebe5c335e871825b33600665ec60015ba1a06eaa195f11b0efa13968758917aef568da4eb6c0f264d38ffb3ce76ae6ab834a52775c6242e3f66f6be324855897f42c4123480ff4ea828e2a061d34cfe770d6b2bf0fb2d2d15b88761916e236a69882e823809b552d10fe728b0e4e220d0ef700202ef0c32836b61b6606f926439c52ccf91c794bcf56e057168598d987ae8fea770223a5f578392f1cc5d690d6460287323ccdfaa3f9064f27ba7ecfa12813e40c8ce7b133fe8dea85d836bb9d7d4de16867107dd57b4d9b8fa7ebf2c0b5fb10292c0b3349b062e23aee353b4529d13320cc2aff600fb707f4825937caa509af9118d51f9e37300515570a5f38f130304d5fbcb1cc89d6d4b3de273659234c63518edbf53e917dd33499a763c230f5a8040a5545e2587cea9cf720914eca70780e9f4a34ca31ec4dc12ff2593df37d71d671f00255a27c1a472309ff03a415eae11ec0b5bc42fde006c107792d5bdbb5aa9fb65a2daaed9a2bbb10d2e17d428a81ad99f487ddc7f1ad86778dd9fe070b09f562e334f1e293ea5ae7bea8dcb4df8a1f8a9e2b7e22440dd1e5822b99d153e07fe55fa303ff884652ccb3cda23910e80abb319fa27cba9cb4b68e538b0b92477ef735eb7b601184b70699c00c2d5fadb02b8e54f4d8e1434ab6b1a5eb68447adc84af16d187fd84d386002cdd8c4e252b9a4c414b3cc866dae1b4fe6d0e5ac2487a76c05c74d676b6b8648d3fb96236861991858666cf5baacc8646ebb11775783a641835798b9ce94b7a32e790465d122ef0965e26477e538625bf5f9b63a4aa42bf934c880ed9015a6148d440632a439380627a74c6b317f8f2f6e1412d769bc8f7dcc09634f8e56ac12a1d097f06e3fcda30d4732701dc3bec098154fe5c04016ae4fbe2c63a88c443b7609d5f832ae5c270c4343490f07d2f1050cdc22a2af1e17bc01dd72045a4ea70bfad21f0fedbbe9badd587b03b62d60e619f8297da6dd124b05c2acd203bec2200832e15758b8bf0ff6374f6ec8a1080821dc92b1948aeb5d39f2602221b959162a5f44d4257d1eed93f01491895ffee2ed26b6b5a6da5950f4ee0a0ed156d549d02779115e9720a3e76b2f8ae2e7eba3a3d0775d0100b8dcfbc8f1532c3f5c1bf36c87007a14ff56ea42bc530ec98b4f513c16dad59624a527bba7e985ef67bd82d10a264716980c4a3f7ad80e1c2e15ea78641c2e1a2526e51327c5cbcf32eb58a37513d8d2821a2e87f819703797c7c6c46466698ebcb13f5e65e2ab6c4c4d7ea84aa9895785a97590364f1ea3c9d1eedb4919ff4928ee042601ab3166f8f620660762749962f6418fa1715fb346acee4c1c39a0b83963a2db24aa96db57a53c0fa1dee58652a3679e506ec3cbade89f5943ce2d42bc1ddad046c644346767a0d0a4309db84d4b1d3ab4f37def43eb60a5acc9c986d7b60c88da72773b774f18b0edba2612fc772c19af92511ee6ef2e51a73483bb84a026256ce45f1a7ea326ab974b92cc6b30efee1b53cc3e9bdadd98719cd3aac1a6c84d425deaaf04d555b56cdcefe4fa9a9c6b2347e0c06a2aa6497aa6d0e91aa9f45da83f88ad9c66698658659dcec069b50cadaea6a662e3f7db0d9c59c00d2f3c157780367347dc512c9ef6636e91850df74814d1a68eee8ff6a3e59342538da30831fbc741026ff3ef3585033b9e3742ea802836a02a87af135ec01f671c743dfc3349b176d846deef0c8741e19ad1d915f93f0ecde28c3a8181ca64aa1999096e6502d5dfc2c9326c1a07fd26fbfbf948e3b91a4c1a20dc277ed2a150c0c1a1c110f0c3b0085fe0860e942bb66b0cc123c11def0ce1af32308debe249bf591706df421748903290d30b78f3d31f8b856cb134a5a2bf2443bbab6688ecbbe4a80fed13a9464c60cb2198b5a79189d92c6b48f4064f82436026e610ca640ab79e4c5155fc3f5f7fb230e2f6735b36307d3cc27447599c798a59979faf3223f72ad7f800623ef8066edaa5f984cbdc0158fddffe607609de4a0080899f6c2202aa66bc40304dae4ee8a54309e15a503777a2bfc04d56b6301dcfd9d44ced1661d07132ac8841f9ca08247393a0dde37b7427b3bbe0ba18a2a1004ad58af7420bceff382ca6379bdefe0681190d402200367f1bf24444a49b69452ca94920c82088008ab08bb61ecd8dcd4e8137d7476d666e7bbf933e954f9ac563252672732894da8a8e2880dcc82aed0d8240bcabe18dc713358cf9631ab49c81f3e3df9c3a7c7cba6193e8cb50f74ead49469b58d7e75a75594f4d5e99749fa306fe9647d604b9830167c6a97a6ead2abed12ef7cd17481325d1fc7d3be43b8253a840f560aab8458084122f2a7c1ca580b946bda463b843d5cb337388d486344181126055c08dda537b2cd13a020a027415cd0010ac2828e112b5a648d0b4a807a9400fd328957700b8bcda76b7080ba06a76d9e700ddb744dd7700dd748278d6d6a684de7582048d57c87a0c758b9e643d017a81b9280a4931c99839a6f5f9b9b4a725b5728bd4268a5acb04a865ba01507b58d760869b541ebb340639cb7af3628e44150b8651d0c6e0909619b90097694060386b6ed8461efaa4bb7ba3fa6f5e113d0fa704b7406c42d8f17885baa8557c8294205cc8946b8851dc1823e5f300b74bfda1df9d57eb55770cbc69fc5825b76875b62126e614a7ab86589163d1507d5704bb53e1cd435dcd2ce4abe7d7fb80500dfbe41dc12855b16122d8c7c73d006c526eb83ab9101e918d4ed41df0c521e0a51b5625e6dd037e5ebd3391ad60a9d4f6cb23eb0ca584dc4a151624526f1c98b3f8d46891599c42734d2e8b336746d58084da14f45524f49a1ce646c129bc426b1c91bc3e6f705bf906c01e559e5b1aa44a7d12975e9139daa98622ae7d097f8e828651f5fa43c3e48a9906b4f143d56f4699450a12a2ad45d9a461f9962244d5151714aa38fa88fa24a05d2af425546741429a7157d2ad447231a9b3f9a425524754a9d514f6134fa8885fcb9c844978cf143b5724e79891d56d93667b4300738dddd1d9bd028b122931999449fb5a16b339d482445be3a874b199bc426b1496c82f3843224f902fc4252062292bd366a3e7ab062bfd2895373349aa3d174979ed474698a09f99abe44a391cf29a74b6a5481f48faa253e663d34dd8547d26795d44d9ba339a3a768ca399a0ea78b66adb2b64713954ed607aac25a3d57bbe63c2dbabba10f33338a923ebd4214c5e40f0b400cea58d4fc463d474cf294524a66e6d168c423e7d1a856a22a43fa104fe944328a091dbb9b82ddbf5cb8fb9f47f6288af2c8846a67a698e6ebbdc79e3306a47f8b6015e5cb17e5bc8a3e5b48f108f885e407688b1f6a0b2cb63042514ec951ac64249b3d625e48bef9e47befc9709e2e32c94f7191ab98526a45eb488668ca176ba524835e1cf9c7e40f25e13a6c4afe84e44f3573c09157347af3f3ade91c4c64542b2a2b1f617ed49f8a53b5a229d4832ec3b4d806a4524cb03a1b54adcfc42e9ab2567cd326018441639521fdf34a86d4218a6f563a914ea49323227e4fc87966cbd4ee96b1845dc50051e2a1af5fd3476f57ec67b8da76a04fc02f2464a0e267c9cf918f3da1ffe43c017e212143948f2cddaa9e876ca0c8a7535e2d1126d236d859546d2a2965ed9c6767229d43055622cfd0a9493ef4149726e92aa6c536526a2512bd2ab3c0f8e7d145b55a1bca248030e8acb2474513136ba8598d4d6accfa73f71c4c642c87f263c22dc58432d242610ea5f376a76fc54cbe2d938c5053c40773a8292823dce2789746cad95dcf7a81f6eec21c4a67a92ada143312399843e9f491271a89a829deca8e9ca8e6b06f77e108ee08521a2528aa8a18d12ecca174a8230efab253cf452b32f294afe610583996116b470eeeaa0c96a1e872a3a3e7ae560b6b69e4ec946f4fe7e0f5c2cac1bbebcf610ea5e320a48c50469e9d6859a58fe8009dda61be29ecb83ee2dcf31d28d7a1f611a628261f7b347aefbdf746a24a4db13e504ee9fcf3e8d23998a6af98a00c53252bcce1b772f00aa2beb3074fe79dced13b3c8595a3a7c06405a538af968fcf28859a62467b25b37c64c81fe50f16a1a6804598f44ee7a03d8595638765a335eed99eb6d178e4cf252c62c4c2011d8e76584b1e4260cd583eebd3236191df9e6f4e4d414d0173a8292823d414ddc3431274e03944feb49a916ca085d049ea103ae944fe708b8371be383a6994800b65d0641910b2cf02e17c36ecb9325cbbc8cd15f9b3a25e6e17f942e79e93415f3d688a99ac9d933e12e8b9662c7fa03f289d3bd7ceabc7374e86c42b2b5de586fea48f9bb39a61f9f3d0e968f6d041249d30a0b6d17c0726c1eaa6834caa15df00396f3b30a06f241d2a038252ce211fbbbb45d2093bf52a18e6502bb85273a4cf3b7fa6956192b40a92693d8cd02bbeb1791e39e7a074c23722e6c93c4573ce2943a2211f4927ebc368c857dd5305a97b3ac752da3d373f2b9c37ff9a6f15d079ab6019d256f1cda5930562a5132752ca1fe9a4d5ac2c0f2f48696bc5deec881e3373cc8ccc8fe7f4b6376d6f38729c1e6bc5debdb73715fb26258f7c365fcc6b6fb885a79c2fdc6be44b25ce39279146676d6d6d16274e5a6362f3e9dcb65610cee8729baccf5da5359ef3e6f968ce1e29d365ca739751c9c86af668a1c6545852e6cb5b2b3ec618a39ccd47ee4cd4f47cb44fcc29653523cb2187eafb449bbe3154253fee3e21aa25e8cf679d9185cd5ac9c87d22be7de2bdbd49619530ac3889137d186587709b8a6facf3ad62dfece6f28a4da20fb770f39a1ea3a8bb5b648a916bd42d12b54886375920da795744d1e7b97bb7cad8ae312297be90f9717c12d3ceadc804c61f1675f481d187eb3ae675191957da3ce65e910c6f4548765757544b920977575e459f87a3d963a55db40253d891dd81eb8adbe108730e2b104a9c8f22294deb452291ec1a9b4c15af16a773704e4e8b4c9ed9f47c6482eed22eba8aa9929fd27321add646c8bb1a72ea9ca8c6845cfe36596b339da57365518d710e5d247d7a5c29124957219fd659467ec81df5903b53c59ed612e5d09f0dcbd6e91c9bd3365aeb2c307e5de423c81e8ac0f84a6681f1ce47b55a9b9c6fbe3922116be24d1688157d6293f5e155b623637728e2133486223e3e3e477e0c439ad83079f93a38ef20214311244e9e207162051227439038b98271aa5464adb52028a63c544b5485694f395593e09373904fb45a4b56ba9d6eb8b67c6aa26b45e80a3d85415928e418d000075e4a2187a19087a2374b805b96cc57eb6b43720df4857cf129d6a641826ba0af22c1de6f04c1f2834b03b8854becc2a7e56888cc578d2470a015fc81a5b5219aa901541c29a243a52072839322a466260915190e5800031230a2014358e1d205ad584a8d89583b6f8cdb854f2aa66a4179f694226be7073e71c8c4c2271ecd1d31d68c31d1bae842512e5ceabab3d2232f358dcd3b5f8ce6e6dd1b68b576da5d9078e95a6a564b5d456dbec8564b43ac564b238fa9cba9996760bc1b995ac4689eb5909e2f91855b4275083eb10b9f98aa0d6803ade0ef40489294869f7652a810591735bf59fc420245e793c03850660e2cca594b804f499a8aad426444478a1c39046785accd5422091910f089db5fd51e92c0117c6219d277bf78412b1d2598c65b62255661da27c1a7aeddac356710c2b69e3c0b7976c92e32554bc5f30b333374383da4020abd79c7b4da3d4d5460c1b3bb5566446bb5e200a9d4aa110c4d2ba6243b4395964aaded9792e0537b1250687323d8917869b5d4fcf90baca566013eb11f6104186a03f8b45c47102323f8c47eea1cb4d6c5367a24c006cabeba9d9d75a9bbd4feaaf3055304117c5a5e8269dfdeecc5889e4c77c021d8876fa09543f7b610c25a8225081d9aaab5a9a292a8407707c29c287a2226f2f722eff922da191d32187aafe7cb8b23e96c4b291e43fd790af5e7b43e0f39cd8b4cd5cc8b524c15830fa7af29a279d18c0e5d3a468adfeb715c881b4451ebd452ab04552bca6548542ded5b8ada253d452d9c2fbb152c4d5f1b97dab56bd765481fdd85dd72ca57695598f6ebebcb04a1d06ae6abe532f29f8ea623093e513e4dd58af25468ae5a8a5ec50ad33e7aac727baee053f31fac40ab1f5af35db24c7e16287a93b5e29b9f4334804f2d083ee998cf855b969412ba94d2e90ab744e98fa768e4cd57286faf257a4a8b884a6f1e47261d41f0a939657269009f9a3fd3102622f8d43cc5b4424d140cb4d27192a3e8a2b95a03b8257a5b3bbf6274710ea9c8d43ad668e6ca2660e1062a7e81f10b091b74be5670c699281bea7cad996ae65dad96cb90562db924f8b47cc9e8a073ae71512a3daf56945fcecf4bafc24479e61c07be722e43fae72e5c625f25ae30ed9df3a93126c01c982301b8056d875fd04a197932b0611b2943621c38c46e6eb7f385bbbbdbf5ae16396ed78e7b6e77cf976666e7de73cf49177eed4c0ceeead7ed5a5766724d76bdec5aeb85450e2c46fab55ef47989b3fcf1f25ece18e01f090cb11304cb0f7caa41a2c1639f5bb3cd735df365bdc7fcde73cb1b7bafb5535cc861834d483291a9922ff2157258ab94d0fc113f34431e43f32514127d8ac8e4c231ca357dec13b5824e9d9942225f9b0522c54594c3396b4ca9d5c20913fa59f789f5a1f99b3da63f8fa2f56fbe88e2cab9c2a7262dd12422b43f5d9a2d0e0ccd9765d39e6945a17bc3279a9d8db9ebc24e4e911c9893136d68a82a7f1a0ea8e0236c844f459c8973bc40abc5414ecef7deec906f5f1cac0d0443db1922e44dbf9068e1b3d8576cc338faf089a560e1e89aaf18e7866d96d06a53f0eded6b83c32dd2db37058fa2a8d61a3333c3bad85deb1c56e4f53c521568457271a728674cbd359b574876bb732ec6186b497a4c74e69279739a19693255f05f88a279f84c2ecd50883f28a1d50f47343d7145435e41ea21d3d638dd586d375a8097058e60b202d946d0e3766304e90a5ab1f7663328aa562d468745383accd98f3b9b5392b1e7ec6525cf1738e7f41eeb672d893c66ba73d174e7b33a7ff2673e8a66c8d78c341f4d547397866ef5b033c4c56b6ff686f1de60212ec62d8b6db8058cc338cf3b6cc3388c238658c9701123cfaa05d60efee5469a71c7c6de939db166ceb1b78edd6e9a69983167a6669491a798bc0bb709e62cf38fd7ddbb5ad4ec5d192293157d8e40e9c09c20292823508c1a2736313f94dc42f9838f5a359e0e6e7e504017923460f1cea9342479e7ecce5f636b5214a5109a2a2823297f2ef9e46a0c15f37c50ee5ca51569670fe78fdb93787026f624263998c5a433ed93d60e2268de2d85440d416af0797e3d9e53de1eae1dab057a730b12fc10ae1d6bcd385f3e43006144419d335717a32a43c6d6e10281c47ef784596df6a0fcd5928f85fc211f8f3ff478fc583bedd9eff4b13535bf769ef2f1fc5536990c58688cf824c9ee931e244f925460c9eeae0c488fd15d06cafe034403651b0de1f6efb41ae2c6307c42d75a8b094622ed232de9944414708043846d75ad66be1dc6819298d972ea32f2dbf3f2191e4483cf2f2468e87903fc4282861d3ca4e2fd179227433e32d5dece5627ccf3f69c2c829dc3d8b76dfa70df9c4d3e08d525a4104180ee4a308d4177d04b3ef8d9836c80e6d7b31fce9bb722b1e70b6ce9d23b5f98c758a3499a622d450f392b452f45199b47937c22040d411f5b4238501ddcd24cfc7125e999171b88bc017e215943cdef7c593779c028ae47ac1584ee9d6309119b3b52c979c8d90f594bcedb438f1e3255ace42a4c7bb89c533ef172d509a510fee00c56bc011e1679e6b394113e31f82823cfcdbbaef5c13933513acfbc316fd414ebc35bde89ab465cf9e55dabfd105c594f4128060a734867ce1e32ad8ffcd0217336b5c70a1b954d68fb73d244db06f3906985f2893128065ab9d76066b0c20c5410e18049c4fae6b0b9b75d2c6acd2c7a40240604f4cc790a600c0abd4773e8f1f1b2a8f79f09cac6e2e010bbdb7bc4685ed6e44cdd3b5fdcab5a4cab32f259bb212e672308a1f7580f6b89720a7a7358973ba7f9365572e677d9a37928239f5553878171b8052191bbb0c8a349b64964eab15e247dcd07302672e6a25a45676f229148e44ee4ce4534efc23b4d21c91e69209b68e2321871c52597298af21eeba95a0a790ce5cc43550063d0920fca9953de4ef333215333399a7f2619f9ae088d0b371ecef963bddb9b1714e54b51be3a3a3d8bc5da812f669df23761da2977d3c756aa9696073f4f98765877a72e7c6277b8057a926feedbce335fcdb73118d360da47abac867cbd0bf3e0677ee788388736446cda5b15df1c3ed11cb63b18e8bcf327637745860c158a8a4972907b05b7f42fd761f2d89d6f10d6aa55705552d6790a9d3e60293a8c9507d777e9b624a9064b1043480003324908c1914267a9d8a14d494452902417c7406f95bb09b33c3050f2edadf9f401299fa0e955d6cc7675139560be32c1cee807bcd34415df8c9fdd082e888263433933adb51a0b79d750ed1b30a69db5eff3c979a9d552ab169f96770e94dc583b3568542b0c9b843f9a437f3ea257d299499a6af089c2154779c8458e40f679fe90d59d7b94531e63d57e481e17dcbcf4aa350f7964a61946f325e6b388cac9668aa11c7a732e31f71f54750e9fab433c5b5ff9108d99988d07b24f95212e8ecd785830e417cf8d119e9b9ac7d30222efbf785810e523b37536be3d8007db786eceb9f9e95b3d5911b9c6676ace16ebe7bc41f6168e1ece0c9d4d1eeb25e8ebc7ab25e8a77fce603dd50a63bc07cfdd687dd58851a0ed2be6394505ba6b0705434e99808c7e4d55a816e17c7d387fcc04d97a11e4f3cf61cfaf870ef401f8c5c302253eb3ff5e339576df4a13a3a2cbe64fbef79ebb275fe8b1a63a08fb65b388ad5b2be80e4d55490c1ae3d57a09c2fada9fbf285020a3df8563dd5dd0ce3f7f7ee4d7af5f414ebbefa22b747d7969976d48e45334fa20eca1cfb06fa4100d525c9ffe268f5d1112412615f252f3e540f62b5644a806a9a5e694afb5037aa44cce5988b112a3dee9290f310abaf45bbeacde78384ff8fc28dee0965562de5eda5ada0a736237b8a5f97a2fe6f9428ed91094604ebfee6a8d5966e27ae389d8def3f517cf134682b007b2416b02327ae6cc2beabd074bae994acedbe9a153a787b5ddf0f75c3bfde9d9ef0d323c21c5eff8c5a3021f1e1654c182220b89f5b570b457edcdf974da31e31cb62aa92be8fef25233955af3d3975a85393daba7beb170ac232cf6edf19cad69c778c7a3e463f97a8f555dc8b736cdb3f9a3f9aa4116ac3e9e337f35c6c534738d74eab999fd7517826b4d2f85a8e5214a3e08e37c4d19433798890a7a78cc3881725d3c4a542908fbe54046bfaa04b245c01a647bfe60be6a571ebb4c25ca9fb3d9a7af37f8b46b7116e2cac015cfdeeccdd4ff60efd1aa8c999c6b472b8214cfdcc833ef76c6bece9b09c8e82b576b11eddece989982b45d21c4b6b9f9760646d51c091ff443c0917dff352ff968a7bc6b4c8da196f865aa286fbe4fbe9d790a988ca0f7a04a41d8af0319fdd6e86cfe7094770db2bfea6a9232553bfa10388efed195f2c62853101fcfdb5f8d613eb3ef62d867f681ec57185af683c8fc59b136cc610aae78e82e420921a524923bcb8905e6f4371e7ec53283c28202d9676749f2dd577cf78daf587ea9f9e2da7afe4356ac31f3c1952d9c2f8c089ab7f15b639c3b670e1d7a4f201b64ff395390fde631adb637c4c52d4096b202add82e1e56834c4009ed08acab3881137ef48be7044ada0b252421098e85023c426070021c213a58021042ed7cd101a0210b5e889205245258c24e9c2007f29c4087aa82c70931defde271220ae4261e00872bd8600c29e8c2093c602684e08c1ce0075d6ca1c70b15d8fc806b9c7842ffe271c20a9e26a4e061820894109c88c1744207cce0a24e34216309224382de5085a0276318c30b168787063906c9a4ad90c2a479268b118aa789234c233c4e14e145bf789c2812521204200081b44be9ac182e0c268e3c8987091d1bc41597ac9a184c9e875f48c408fa388a2d291dd0d2739a7fb5e4e3398c107ac8290f550f40a77ced52fe9eafafefb11e567eaac7fa1899e79443a7aa079ebf0a9fa9e9ece17c86fd5a389c3f184b7efdf3eab9abaf967c406f95b9e7b72a178ec663fce2966a0926967325f66eede077d43bb876ac77afc285c3d592e3c15f7a1efdd5ca9368a87534a07210c61a03ca182aa074789640816233640a394300434888a7a60529544e1842c0885206e73886bd9d0e5c11ba9cfd80325f4eae1d0ec812cfe0dab1cf9c5a3ba0339f6bc773f6cce37e009e1d090fc0b307e0d919f07125f531ce5f8559c0b323b176ded5928f053cfb029e7d8df16e5b0737a06ec07203ce0225c572c6ce2a8f0172b16cacaf46393e0e5eb001dde1d773077e996b44d03cdbe19715818a8297f72ae1d3a9bb47ae7636ad14596b865b96772f3076c98060ed3062e13011c1a7d5a18385e574722791683bd4825eb17c6dcd22f93436f07b9806cafc0b28bfc4a0cdd757febedf6f630cca3aadff2fbc58a7f5557f730d165f24f985e40b2a1ec92f245182d6489405e3d7c2b43762ed5823160ed958b36eafb6c82f5f9db563757e795b8612d9570d9f1e1d2f724135505f21d528b1f5d55a02e445942e88f071c43ea91aba0fc1a051767e21e9e2c9ebf885248acdc79551f804657396f069b1d882c22f28e49e85631dba81561ce4b33941bfce3efcc340407cda1fb8727bb37004519627b5e6d24c2b09c504ddee31b28b355e23467ea9f8d5cbc82f87b2b976caa7edba5262de9c9598b7677fac3a1a7c22a98156a7bfc1c25c4bfbfa10dc52ea75c79cd7e0d3b22b39676fe7ae1eb1e497cfafe5d3d37cb6f9aa51b1504d50e7afb5d65a0c511052109a240a9c897282326fd199a7872a76ce7577bf2743dea148b19b21aafd3de79e93ac0908c7a08d9d9a01a5dc32dde98a941042ea3d91af495122e9cf34e3ca150fc12929f8541caab44a055fc42d0a66e6a589a77de714d018336f67cc5937c3307cf1f3cd5db370d02755e8c03128e313d502babc9bebf8c952dd63a35b85d65a6bcc92992553e99232f3e3ba52bae430486fd24d774e992ad1e8f9e8b9a8c299971e0a992a36a5bb30cdcbe89c9d874c1506deb9bcc86d87880d9138d8fc42028728cfe0b04591b6ef5a53e2f9aa15e969e750628580ac800a2e3e717bab758581b63393e2d72915e262147477f7b218281430a98b1c7481d37ac135d030a83b673d041146b4166e3b490f59e98db3bb5db986dd3d44ce9786cd1036ccf9aa2784ce1700de901b0aca74bbc2eed2740abb3b877d896fd938cd3d04cef08af6907f3ebb75ebb633ea0cbfac31edb4cef0aff355cd3ac3df3f255a1b0a5305803ac33f1d762d51875d2b1fbeecd3380b4477e53de4a1f3ae31edeca0f30a1b763b8c493ef6ecf6663f4dddde5074e374bb300fa39d693e1ac18602c2b51a9594ce5ac15af14f43b13e8ce0a896285f7f6b9a31bde29f972e32f1cf3f77ada15820b6a168287a48434163494c66e023832133e879965f3c33c87919087903fce21912e58a970f00ef223c3418c21305139e288cf0442184270a2b18caca39799d79ce1e33d8070ab738e7b96e67c03979764d694e76e8fc03390acf12642e68947c2bfe91976ac53fa757edbb3e791f8d12eceeb1bb9a18e84b3c6fef5c9f519886570c858b05622e43f9e740ce39e77694d81f84709a5686573407df561a2d1e483bf8f0651f6e99ee9c7f160876575d1a4261283f7d347b00c0b9c6b0431701c054ed4f1d5654d8b94fc7eed325065a0f35001500757611763f6266e6e7d1fa0cf74e919183f3cf3b7f261a26e92593688629b24f0d771e6a7552ade8b71d6a45aa515db8a8a45a57db58a932ea6a1b2de4147a159a947231bd9a23af26d0a8c64c29e7a474c4c574e81503fd688a18e89fbb39dda7a2930b97ce3ecc85940c85b9581f5eada1c13c50d18478a6fa81ef5e219a69d1a669de2271adcd8e4aebf7e9daa7e915015c4238d35a3644f0edbd8468a61562cd1fb0799f389580315e0bc4b60f553b33bb09be9ded0ffe98669bd1ea61e1586b060000344a337820eda0430e2b325452e84834650c51f0b98563e158387cd0eec2bb766dd7c53f50ba9b9d75f7eaeed5ddab7e11d7028279d787b5dba39ab16bb1b5d65a6b86df8a46841970f8b49e2dc61863ccb739748d31a0c6cc2a654fc336489398b34d601244ac45134057f4a0871b19c4c7840b8017dad4c24e5cd0b54643488392925a0d4e995b22af245e30820ad19168eaf88abaac53732356926f7039e80b19742bc9280550d06225713120431374c0569a12f65a0ddade7bcfb44ecd439400beb177d5178e66fad65c63a76795b11444e868045cb8873038282963020da9b3032f68f71192ba23180b7504961033c2a04b6ace311d1fc36ed05c6bcc19638c712136e79c73dd96c16fb5972c1c58e82f00c5232438d268f48032a0d6c355d3c20ccf9c0139253d4d28f3055719de79d5b460404a647cab2ccac2d11ea5c0ccf015ed41c20996f47c3c120525350bdd45391bdb429bb242e6b0ba0973816fceeed2214851b00669ef6a4c771bdc12d580ee6290778744e85a7344686c4883f21d8349c0d79a73cfd120f8823aa87b9ef515cf9e1fc87bae4a188d1e50e66bf248b1e9a067eebabb9b0a2b076bd65760adb1c61a6bac99ceb7abbde40ab4bfb018ec20b6c981900e7a41cd481c51f75a7303500175befebde71c4cab2ce4f96041ded5e8cee1dab1768418ec360941018b106b25807221d151e042485ffcd44ac063fe67027e2ac49c27ccc2f19e0298f3e4c1982bc1b3659242665244d69cabc183137a8a12578d8aa519fdf76a35d39c8b21aaf9f077cdd87bced95d0dd29e0c901e1141d0afd3e001c579c6d884000bb1da46651988f3ccd9242ba813f4cc39d0399eb33a520271ae803447e7db678c9440a08578757bf80269057d458292421c319c8802718e8c11842e7c8e7876c60c04cf644e3974b48d1ee24486219a291dada49d072d4e271eaee0c208271f9684f890d0904a88a12982d4eb114d197550cc41c79460b543cc926cecca66c0e2328d17c6ecdb0b7586056f129f7ed8824a3eb140988583b1c656ba0521bae655d4c20e9a59def1e086864573ce39e79c6badb975157eabd473e01874422a0dd2dadd6ae5590577adb5f2cdf9652bf85cb1b5cccecc95b5e715a59eb995670c0b94b153e718fde8ab537be73b688bc19504447105172bcf78769d2a964ac78c5b429f0c3f8d6a756211d19fb5d241533c86a85ab9119d434655219152285d105214e5b0f26f95ff8c1bb96795319ac96d0c380ba7573a9e1de0998d67a667d5ca331e560e52db583b9eb31c2e6da383d08294e388b6d1de3d4812d53b3247fac418632b0ddf8ef906f1386a686ddbee32b65a23c1b642a22421546b8d31d61a6b4d01adb5c6985b0c16e9437044a9e84d46c73bd7439da3a6d38845d68a14aa15ad8658ad88d1879ce589589e4f16589d8e78c7d6a98542a7e6eccde9d0d1921b15347440db97c06570cd6d63950d40058d7d1b623506a4c710035c3b9cb7dac4401b0cbfab6ce170be9c3ba6047bfd6e0cd6cf9a0d7304382e88072ea87a4cb8a02fb83530d1c30a5cd00ef9a20917348370444149f4bc23407847aa87e41d01c31bc243424306e11dd9279e58f18e888282f0866e7539636ceb2a8199bdd65a1ce8b225af97739585b6e09c738e017f3db8f6ac1ad9411ba0d8016766e779e799d3080295893b5c452dc8c49d2c3ce19d57510b2cdf2a5d38da798042e3cefac20994c49d8fd901870874adb5d658850a8d3514bc251ab52632adb641f934adb6011f64429f34f9b2d13c9ad6b241bb1bd558be810003fecc5dfa99d6bbf7aa0c4b0e53643105fc353dc0c202b9bf58946b8f65d19cf2a6c127136bcdbfbd3003590c92f824a2019b61300c78a941d8b32c44c828a53958241d03859405f6e04a834f344e4022152129a900594c910523b58b41df1e7d39a0a6c5b3f685674d0bcfbc5161e570ac39ab4ea7053d63a12e5056e33c542b48facad5f4f0ce9fab694adee97cbf6fb52d5199025d6f5ff8f6668b41de1604b342e8010e6c41d18ae8daeeba6dce359b2fce9c73ce3dd9fcf5730d3a31983bc6273884d718638cb1e65a6b7505488fc1484f622bcd44a23d68ab245ab509f3dcf99b3e1af3c6de7c818e311f9485c0a04b9b8459594c91c5145bf2c1ce3c203f86766301f399214a5e60f30b27ca2f1f52c52f7743809e0d59f2ccdfda1184bdf95262d5b49e2f13cc5a77388b9845bcba7c357b564b75bdd776774d95f3d23a5f379ebfe75d4445537a375e5ca9b43c4afbf6c62f0fa75da8f4cd7595450a11cd00004000b314000028100a0704a3d1884491936c1f14800e90a6526c469647a31c486114420611600c218410024000606488b6010055c5e7bbaf3aac32d268ac6a18c654a7c258f44cec16369d9e287282ce650c0c25ef4a42e29d0c2a80e2f88c499e5aeab00f7072c3ce50ea405037996e0c2b0c5bcecfea06f311a70435bbf98fa5e337d0bf23981e3b2ac66228c3fcdcba124210e9cb4ddd60125169365eeb59d643dde0ee616a0e0d112552da3f3e509d1cf854d96e6fb73af9db6f88a22b3041c37bbb6167dad8057e815546ce87c074c81b0f7cf4a22ad7f950f6e307d8b5f2743e92b055c25e24799f0c04e28c5a157b78d2c8d6c6c72c25d6e02fc8de9c6e5b4148a765e0cd74d2f34d7eec09fcc7777c8ede0bbef62417fe20b3740ba310e308bc34be1bdfae4a3e10bf59088c343573f48c7cf8ca2becb4507f1a632a01cea6e65b9de30641acf236413f8aba6fa7c73a0abdb702ce60366ad1b81d096619410a1b1ea0ccbd1d65974f70f716567de441ec86fe6143fdad469d515885e0ec35ea22c31a11b1f0ceeb83eb4884c89c77361250e83e7d2d0296c8c1792676cf116855df8295cb1d4b3990cba3aec41b245f5453fae60151065368cda1ab6f5b939646ac0fa470dbcefc46dfe8320583a95fa6de5ba013c2ccc43c02ea8d8e374cd0f6e6dc0db11395bf3752244617da9b5be7e2e6158f5872bbead82e0673bb2425c30b4ff94758b553cf44975bb77613ec64256e35e3f8ae2325e2c7a03b0d4a1064e89b962da1568f9cb92d47088cca4c4db214036752e2252bdcad5d93543e8217e11db93406df1336ced61a5a41c6b5ba994a636394647cdb3e17a2d49f1035164f41064b53d458c7b58ae4749295843917dbd3b582854bf18c408cf0ed611d082ec2ace56c1a00459201e540b8866063edd0c9a0c02735257e1a5d9851dfc96fb88fa239b1a4f7c4b3931f865252604512c493bd0cd38df1ac68ec9a4485c70f0e63d470695cb054cfd2caf0f1d9dbc2595ac68c85e49631d42cae101d1197333bfc85c3c979105f5ac97e095a0e9f87c33ac47d69d9d8913329b7501ef95fc3f63ccf672923882ab7b350cec2ac2ae6a969e11206149d55e24688e5ffceac2aee07089952faec6c208da7205531b6023fd50888ff43ad70584e38f91c008be343ad4a961b44b7330190cf1651a9c06a7817536bc077aa7eda2b75ba47f0d7c07cdb9418f10969ec8d74eb67a4f6a519ff3ee8d0e11c3901fc3188aeb8d22472238982aabaf15f4ae277748b21d0d5b5b47cf8d9aa31fee54f76f6f2f8eb81dabfa59df7099b3dab711350172a7a0f5a781295c10fc98177ce4f72bf83b29ace4ebcef94775609c3ca4f16a4d962dfdd044aa28fb3a438e33f4825570c1ce2880189c18a465a26782b34de27c7831e263844b85936f2f8f2b357d69a801cae125819dfb4f5845aa9a073b80733897658a50362f230c6926e2c0a42f0fab1c4ebc962c25d706f6b71452be9fc8a576b2bf81612d0423c447a0f3abb794cf40b5ec46d40aba34f54c58107d731c65d2c82fbae080d63450431d85a7c80740d790213955d55f523916ba13ce7a4b5c7d834a09dc566e68c3a3c678afd461cfa901718a72ce3e5de98f3d44a21724e23b8b890c1751904ab64506e64fce6820c7e871ffb846b710e9c165bc10760492f7db8f48722462692717728d438df8aa2db426a6b203ead341bf4aba951af068e4ffed4a6ef551b2b3f2ac1e87b388e583c5b0020d1c7d88bbe6281f1d51c3d96b19bd3c2665bda59c543506dd8dd5a1c2cb8729d62f1ffe16f9434156cc6a7c56a181c1f6c7744a560b7e4633cbd91de3b5f7a30428cb04ad41432830663e375e7239331313745cd6a21628beb5369f588fdaeefc5c95828276df52c28f86d1687a4cc4e7aeea58c69384ff84bf91704cb1f4d88c94059e7137160f99c31c114f1a4045359a70a7f8bd5862d62dec1d42e98b2fe9885f547544d6433021d7a82931a5b6ac85923b20fee82b966c3791611d819f320085a45dbc27df106f68d0f0657c0277938881fee7051ccd5cf8b6cbfbd82b03ff78ecca8d424adba5492a92309bc5b130dccf4b1b23550f8263ab8826d26a499724102a00c720ca43f775571662c77255ebc3f2cfcb683242a5d5d334b636f6dad8312b4d3a05681c9e14366271a4d9a9a1822aebc6310dace2e6313d1a510b1e0b23140ca1eca7e24f29934b8046f68b01bc900ac810fd7c7248cd53620d2001a89118e906a2127d8f2eb1b4db5018bed16ec45b2826fe5bc111e263832b82761564299ef0580aef6056f79b907198759bebc637b8444cd7b4bb51aaaf54d832c2250e4c9c4c33593bff988b946dc260c254f704804098ced4480e0eb5dc3925f11d6808276d0ca8ea2ccc147e0de6d1952e376e4b267daad062b1386ed8cf51a8922b674bde90349d14cf36375f9626da2a3f458de922f0820893be7119880805402c90925928231eda138056caece38cc307e46a4c942c2c388bd80c5382968d06d3cc44df69541d6e5ddbe096cff1ee01340f41622a1f8cacab7ade540addfc873c39e17ca77b9d07a5a65bce24262ccfd91d0ffd57513038665ff92f1b48ebd92ae12f252ddd2233fe668a364522276026220d4e5af713d1833a35bce0ead207f04c0a5a701e24e01f05dcea4824ae8078abebbd9e02b3dc6d1d9c39b9f5cd05a4e1e31fa3f1a3ee32c48d68130b09d4bec07c114f1819a9ade78f69b007e63bc01808f6879fc595129a5e41ee00f4e406e1e71c61f1058607f6fdd77e49fe427e1c2df9cdaeaf2aa654366361f072cf16c6d601636b2f0950dabeb993b6fde787eec9dd5287a379285f4ce5e14365554fb35aebfcd1408fb3d06174f3339a8f9cb1fad071e31980a9a627ada57b4c1737020c642aa3b76ad962600658ca37fcfc9d8c2b149df190bb7e61c355658de582cda079c93d3d595ffcc9b0b9f965a729cdc90054cd58f612e5528f21403c923e00f840ae25aeb8807e57cf38a4502617f6db5e296ee4df489d285d6cc732b0368fc6693d4e9c9d910422601a7253363c41d735c8ce516f57cf65be6816cd4ce2fb463902c6dff163aff0bbc832b3aa08131f8ed7028f88ba968ee3c70906702ac1a71e41286ee49c448354f3614598a3a7fb619a124b54534fbe61a9005e5986a4a24c2e838597c20c8849f95c6aeaea519441d0088db2f145114615463369731911a59a86e2e643dd114fa311d27310df227f0ff41b399f3193fd5a0d9dc53723f25de7b65ddaa904395b69deabf2d1d952a982f70f9286917132b73b6d8299ba575b2bd64442844fd6dd28fb285d45f09d97469dd0f508a741d00998fdf7a15069cc7f1d23aef9503806d6fcfd5f2238f3164a49f015c71978d73a25b581e65ae95bf03b4ae087668390f155ae65e6789b72c3b9177b8f54d1447c6b3bb862c2fb9f83f0a7276fb4ea91229063849d2a6a198e7861a5ce94421800f6da860d4cbcf0d866a1ca4837af90edd7a7f30c20a46a17233b8dd9a16a0de69b173cf150404a453402ecc9e00362abb424b2798d7f03f267cf38d272a9d7a8a9be0f0fe766947c7c017ae926f6626c6df3d8250f161a9eaa0d1f732a2a3969eb93dfe5ad09006a6d1770942d74dd496517e4f2cb20082541a8cbe097b6b00cd21b138ace4f42d585525889ff25ed5fb38441d97aac7d68b5c28cdb91c5df2940e489001635f7c08d9c0223adaf5aa810c6692ea8bb40f2eb4ebc6f3dda2acc51a45a746000b75c56d8835a6fd0ccc7d5cd1c68412809c49be2fe2ea3c17181440a625d9ec86f9239103f42a39c015d5eaabb826fcc478b529d291a238447bc98eae8710dc15981705244985eae66088acac735c32b3ad08bea58e02adbaf162029c20a0d5a88ef5f5d6d1df56e0118907fa587b9ac33c0bdf7181710244c76543102709171f4e08d40ca9a46def21de0bdd156f71d7d42a9ac6b3951c4c65a23989720c739e2545ea666799079fca963bbcd85bb18c410bc9b29d12c39c27225449b516fe03ce0f9620a9ff59b6e79e70c1645995fc7147df61ee3d10abe922012f2fe37fad00fd574e3146ae9179121f3f27bf119648e8a2e9ed4ffc7ae828e61f7a52d9793e7b12bd1fe26965c42f92c06336f381ef3de5a131ea168c4d2f9c5f40b314ba0c2c67764d09a7223e0653d264f8537a54e08716fc0b4ec5f752a04d8ed758313386092f12a7e679ce6c69a9cf90f7f22a5cdd23367f570641db3dd6c4eee9e6ecab9ae95a4e94b48ea72b25be85fecd9fb938026b260d8661970d157c88ae71168868d9af5a5226d210cc1afe13a07ce463ec9d85f17b96250782cd9270782e5ccf1a14f5149e5856d2bddaafa6711a8f1c1dbd7d043153869425f2f6ee75b8faa8cbee776a9618b9d01488f88a0178ad657ac59d91ddab78687f07717204e88d80dbb0fec1eaa2531a393a5cc59c4c82af5249e8ef5d414792d12e632b8ee667fdb7e2726dd513d994cc95473ca6ada480bae90630a23260562084fdce7f74c6c25503e85c1db4c3c5cfa94ebdbaef9f91478cdbdf71d0b01e632d9b29f47795654615cbaa53532bd31d623b8b609bbe0eff873b6ce5462fea237eab90b95847738d10cbee4a4e615d961a31dcc433c1de3fc15c492e3fbc4b64394f0cef556ce600313a807d9968c8fd6b833fc6bfd518310d883c78e450fec4bb9184b970e0a472b5b56a1238829cf83048fd45bd90bcf75716e4ec70d06f77ab539558908318ce4bb9d1fc1f85bc6e8352bf264647c69e559a39d38997e4cdfe30faea395dbd89316e94b9ec0a228cbfd9d26fb813255ca6770ffdffa5d9cf670bce811bfc57db2a8a0c5bf6ee83aa258cea57fe8b3234894f368cfbc2c132ae2822d2757fd4c09ba618d76cddb21c2315503f96abe371c3792747e6bd37822d9be7739a3d864006887a163419cbd6d4c768d63e383981c0180cc64f1e608c5f6044f805e3aa63b984ba3a46985eb3562276b3b7daa94f1009b410ef66608ce115dc727ec647cdb293fdb657788aeb15bfa782c581e1cf14c1a2090e6eb4ac2f258e7c79f0d64b98378cd09e8ab847bccbee1f34131d20e571b3c7c7e11b75f4528509b4c4ffdebfd6552b5e3307eb812c857705b1289409554a04d55475c12440f4e2f3d56f2f0e54d2b5cfa6576379befecd52ee8886f476b3c09ca23ca10d931bc386811b3ce41774d7107c4dae42343edb10449b16e56116f367936bd047f4e22d6010256f40aa66c32c21b4bda811d174abd1a3499c1140d19735439bf92780e2a69436c692cd6cd353ef354679803894f35d74d5503f93e379a6279d77db0ad663c1eb5800401a70b820155ddc661a6050cbc220782550829106b89018d7380943d3335d1aa7c154d35085336f9e0ce9c2a20b809685f1fed1cea162244f584fc3a9d51e732e5a64915c698c991134a919cde5636537f2888c0173428b9225cfe2d2bd5c642b80bc692c87d8a676e1a419c73de77799a751039730993ff4857e2d5ad100d72987106e06deacae715334203e2b34e64c740ec714ad7ea4316c17b1dcfe8b6a5701b60e8284b5352f04d57d296f97e06d7d616399f09fe0c211db425f032b8fa7e059f14c23b27f84cb13146fd112aa42b1be3e4d02f2354f62891ed5416d52d5a651807c4ed3139d9f0affe87eb9bf56c436dee8ab556271e93e8c751253b050337ea9b64fb4ffc1f669418a3629bcb523793200435bab30dc03920cd68413e1ffce334d582d7dae6460b9584bead7861c4666818d597f63881749fe1ae3d6e6d42d52561aca5cd3c3fd6339665a0ca34908201f7076908d8219d3a8c5d0077809e15dc136059c048423186fd42d8afb1b35370213aa7c991ebeb6e96b38139e61cb370237a9ea88083a71b9bb1a9859be7d873c0c97763dfd7e916bb670848f432eddd207ce64918f3e46a9a848a70a3f700f6ae91f66428cd3535bcb90585f4392a69fbcbb928d5fc0c7d97e61802f79aa6ce063d787b74ded3bf1572fb19010d3c3eb937791a068263a0134862ad46a2f9d14d696262c83834b2168b9a86964ec675949dfbb1896b27165e5c082bf8c524f001ce88e69104a4e1c44e2e2f2ece8763c5f24e7de21571402f8834e3b64e604bc9acc1b330c933d26a50cf433ffd14e720a11689b65db012e53199d5a91bab0893b3dde6af9d7192aa3785e1657d9da9503c31f76d7633c735de14ba870800f53d91453cd1f52c9d146ee37dd510f6ae0218c8d145be53bba4d5a2d81fb968ecad6bb33bb08674feaef96528dee81a3a45fb42260a4205f36670d794d410a642d968aba43910619756e358a72f995a553f320585d91ba521e1f4cd22100ffb96034fb26b9435a02f9ab18268a0c76fe6617df95678d42b36f47f79833cc5159e5bd2edcdf7857c2962999243d5f51b18129bbb4a5d312627e8390869878fb19744b9f052146c39fb19dae9af417b7386e6fc5a4d4066d4c421397d1d4ef3c13aa747d215c6c1353b4eeb383dd86e8192884ff101bca223371d9c434551349fac5b21a34a257cb5d9f4ced607c8e002fe7d08be494644170672458990760956c7baa2141c5c0df2759205c36f43c685eea69bd47725a0a82660c42a26f037431e33a2fd6e0c5660e4166b5cd1a8bbe1b21b7c20bb665e6e3e2e05c2d765de0849d5143cef2d9d5a2ac0b269abd761434d658d370cb4c0484661373945620e5658c244ac073a32d099643dd934c0ad784ad6ee4ccd3054f3e1f570ec4fbe2cd9979f96092d6f78601a9c6323f810cca5f6cadbe345957af8b997a10d4a09513f80068d0f9f281133e0a7086f4ca2a2ca5b55bd59023c51282aa5b6c308721fbf55030f58a21d8ec5932596d2d4bcf409f713b0262cf6af6287599a4f2da869043d510954019c033204453bd3b09cc5efcff220f917f767c2ddc19b74a97ee302b409bf138ee6df44c22511cf3ec4d316a3e4813f346542ee3f036593b56a8fddf28e735a20446d691bac5c14b8ba5b445ddf6aea84076f921a9e24d8de2fd55a4c46dcd6570921ba9606a73f0cd02875b5c38286b389585365647f88789ca19f3b0401d1d69c261e804c662506fbe1211cac5300eabc184c9912f4fd441311f35595fab80d86d0e662fa772fc5abbc40e77f007e61d70906a962a5138c4f2851d888b6d23e7132af82cd5520104636562c4380328adebbd3eb494de28d60f43dd1dfac3b3cebc53dc43386a44c898c0a6ac648758d728dd28a4f425850985ebb3ae522dd9a413f439b363f4c4632c7542d90cbd4ead5a86a98ad1963737e61020fbb02576e768b60d4399a64c813cba5c9a0e65701cf06ec0c08ce0f50b491eba5acc2669734170b6bde7b9cf744d0eef60a9b309e6975280575853659b9eb591c8a862f3f6151912dceab5cff27ae8bc4354492a7e24d44713e2785d4f185b3de380b30d8aebf732e1e506c34c60eb8ef2e1957541cfbc9cde03e7ce532f9935d4c4f4d79ace57f973eb4e7bbceeac2738479ed1fdf0b3102166e7c3433af8621e97d38d16864504130b36c9ae2a33e971e7ac951f685f10d43b30ab00958cd2dd1b3f67581d9236fe14f880fb5bce041d25c5e4fef1d5a28634628d52627562b1213ce1da0d0a224db1ff0f169fc57cf3054171b1e8ecc120cd924b0916ca29b0600a6edf72167d18f08506a2404bcbf555b0c15b4e58d134caabad8d230d3b74957bb2308b856ae5f0b4ed7617a5a5d14034d805bce008532c142f7671590e4ada5e3acce191f3d18ff520597bed68714cf13174cafa3701ce544b935a2f3d1f90f97d856ad51ee20c81489d2ba05717ef8f25cdb3e39344f4fec08a24db93f2370224e43b702877176103447b5b09222179bca6054b1a1256e9426521961e81ff123e8e7d86c8ce00933027a1abff983c258e968802d569835fe8acd570a8799fe0a93465dacd27c8f187e164ce9c8d96efb9e3a2aef06802ed5180c1f23552be5700e7759adcf5c3863c3b64a24f8825ecaf0185ad1092e109db6d25e6c1c215fef07132fb86eb190fa9ad6300e58c47a168d8388fa8bf1c48d7fcdf9c9bab037881137dda05f6de0ba0470e87d37a36e262bc9ddad1730b3f1fc0db1813b4c49df098f2bd5aefd03c23bec03e14b627f4b89c3817694c780bd9ccefc4bda9aa553834bde57e9234dd800f616c0acc4628c4eb0f650df6ec789b6ca07ee02de13dee676c59a66e610cd8fd85f5fc706cc56e406a081252b74cf2184549e711e539526148b3c6876e81a789b7949ef806385afa6bbd4e532b6448f246aa73496e3756dbf6823415ce5947ab83ee8c52a51e457d2b40e8cf0fe14f84053fea7835122f788678f1502a49f37919880f8389dd3cd8351c714eed90a0d8a2d7cebf494d05591aae31ee3321b670a8e6446c5a83916a29e4012c200dd3725cc489f245b2456ea299160667914e44a595a0d78ea5e4e57c78917a900f881583e5842440733af1cac10746cc96d18ff5450fa34ebfbc8db0361d51cc104223b0b42b6ce2f0c208711a9815bbf61d5878e5603829b8e91351c092b65628c61725f349d5406cb4097938b75c95174c1cc59d03a10cf2cf19e54ddccf634332659ce09954a5faff26464b14640d73924fb83ee909599dda92c0f0d191721264d8e0b749b89d165b584c3e9e962a473a93489d7a640a0ba161e01251125195c715b24b28ca0285ab43c7142ef0e053440a0e0449a075043f6c9226331e36105b7637ee71cf501cbe9489cbf5a933cad62d1146f6e166b3003b2f900a76219bc3421a541d5fc25fe7248690d6d8f94920a22fd232a01424cd91019c145c61da221d13e0769497e3fcb52e021d979dc3ad5d448b668d31131ebbc5ea55aa03c9aae55572fe9a0f82327ba08214decd8e97ef01557d091bd2c898cdb8c6ae1a8a1e16129432e8be2c94261bc60ed3f9c15f8b5dcd22c8fb713b2f068d53a8297eb01884509db2c68321c2dd6b0fab6e09f935cdb30f792a44cfe5cf111db29bc44f85c94a7ab3707e4fbb920ee753a52aa685c8cbf891a58d8c981aec549e8cd6c78f901e18c06785acb5f338693e4eee14ff10792866098a5b16ef26c88eceaa8ed7bd501de0eaf68a6939589d8b7b090ef7bfe5f001fcb96c3c27b3186a80af8c84f5096580a408025acea75ae4bbe39459fccce7468d384196be09c2edca776811b2240e1813e0541bfa4b3087020c96b93ea68eadb688ad048a2f7db8ae610da32c59cf016e6668bb8492d5dcf25340d7b4870cb3db03eec15f122383ce1813d8257a7f3da49e6600cd0cc5e0eab2159072554d8196103d80cd82d7816b2b125f19379763d771dee8c32fbbd8a4b9d828e0afab06068fc009fd6c0eb922d88f75eb6f828e5d345e995f84fd290768061474b748482a66db0bea32a10d161d68d696fca10de4c8da01f23c75f4cf5f166c4d16380b308f36e3b3a23b05d29d6e24de8d62b5edbc61a49a5d50b97894255fd05ab74878f9709e08e3420a1d7c162bb8cf64701c15876f4582976279c33fdeba256af4b22d20e01d600a1cd4f5efb20a2f28cbcc4e58134b7048ec18a44f039fe8112cc5691b9f8f5171df7905e80ba541bd2eb6a3e7d349d4c5f37a205b6bed65dab29c8c7112a55ad22a377c411578cfe3ca1029c3e6910794d3d01a7106377f75b935ada3f993ceafe01e70961d431499da984c7f28fb5a018c3c8628b96b33e29d2ed191a687e09df354d14da733d2b0151707e6ffbeccbc5262c4f940774014f56d950b3148daeef40557f4071d5babc80d6021e70c24e1feabb4e636c86b529dbc282b81da1aa8dea903b5b509a0c44d56d06e3c501b53d73e2ad4d644755dbb510155390d59f8b82cbd1530f4102840df4a4ec6dc9455e23de6e159df38bdab4354abf1a8fb403528984bcc6eb75f9c42fb2cc2844ea7bebb24134e4435e146828e41a60f947ec247198b9a64d43bf28aa2c87b373063d903cda19904330c142f2a9eb04133c2127dab80ba90dd1fadf465b0b117102e4b1e6453f7ec9a83ef1fadb49ea5c3ad69f52df6ed8dcb8a592a1abed7cfb32161a18c57739cc56844954633ce521cba26136e569dd5302f21580b9477a3ed0b2c5fb1be29562d821dce124237e47b403cee97c4c15a17cda45c5169de541a5dc6bd590f3c3b9e2bd1f342b83d19f8b4eeb81379ee255f426890e18e9ea8808d71289e0e63758299a50532ecb11009eb3eb174cf29b3928729b7d5a4ad2911a3686d9a4ebb27202d8e053e2b01616529ebfe16a5d5f46802d31d2f9f10c3a915ccba2c8b248e47128fe715609eae820436377f0c8908fbf480f22df02a21734f3ce4fed4354943f9bb131c6cb5f49eaca1d934c66062eca6deed45e39efa051047c0192213c82ed435caa30990975416f4b360aae40af0ed32ac96653f8f0f3de9d57b665a304bfb64b56e816fedc415a0c10383da47d89fb4c5998f4f5e1570ad7be92d44cd5499daed164f3b8a4d91eb439acc5c102b678bd06a582efb4a75ce2df721369a2b5f9401bc78763b0eb4d6279879bf7438384c2063463b02cbeeec4c5acd205f45cede144c77900e6ba1747931f307500e04e4c2d0fa8001ebb69aafa406444385c90701dfba30c3901d8700d7157ee97c2d31d5777360b6fa4e79de4bc6c2f028c1fad46116bc38a8adcb5f44de5c0821fff54f6c9cb4af63af5c45d15419b3a98cab2ade7cc3ac4ae6ff9a918b862c350c8324ecfbda5ba9e4f343b8872efd7c44570aaae3f67257339b74ce6055141ac3079cac21509edbbf5f2e36e42c72b377c63b61b34070d4bbe9f880c68cdb3a0354aec1944acfa7fb3f00be4147e636a8bb87ff54e81014e4cb85b6a51ea774f745c94ffd1f8b972b0576b244fee65e997c06cdd297a35b66100682999dcb18b616dd23ba855a1ec528c40a0d2508b826fc22c907ff211e7b3e560852324418de8cd44b0797c91d21f690ab6e9e96f5e571666c586a5a8df6d1088e679531369c0f3406b640919b70610663bd9d0218ea3a1f2aea221dade48a78ef122d1956efe181432b0f372189f67e22b53ef17aa2f12b913a5a3f314f8ec6be6932f7d7f816e3d03e28bb65e6d6f7d86e86664d84c8e20a116b3e43e892932d67b9de664af7db08497640527f799cc8fb36c56a345c1264995a9554f429aa589225f71332ea07f43f210d792ef395b0167598066004012a716a8dfd5c6bb3e65a10908bba4108175ee2d0d21a68ecce6626f3bb6d6c9cc3abd6ed312cf0bbb5a680abc39f84b2c184ae0627b1d824fa1c6546f1f307863f9e94907f0f98cf25e44fe4c129123463258133400e11188acd90f2ac92aeb35d855a571cd2384b4b86051a605af0d263a6db99543b4f27ce93b99982734bf8c705c998cf19512262af2f6271f9db38aa3b9d6257b88f977a1e3654c2c9fe10e4f7dc7325cbbfa3969466b1ada9c84741da4be4edd650edb8e92278cf079fc27318bc0cd766600b3e030a3c0920db61e9b29ac79d65e699e01761e8de1373d11d5df8dea0afe1a024670591938637e785774f85ba4773e4b9fe73362d0aea6c5712b6a2d7f2e8e46fabb292e2fae6da7b4c0f5e16f9cb9d3b964becb1a2229c37e00bd7a5d58de116b23c6005ba51cc1dc57def5aeac3ab562b5591605a35fe3361fba6dfd5e519fbff6c94363e2f80d6fea822176ac630b47fdddd50a5f46c025d83cb9a797b116ff164ce27e8559bb293e3d3a664aa7eb88ef29f370a246bc68ac4c3e8dc1b03b767921bd8935e6934c289391165771c3011616752ffee03b12ac82f2303019f0dd8ae512f09ec301becca082417e57b1eb19e7aaa935c510e3c097d0b8dfcd47d06c39dfec0bbc49cac7c162d80cf932cdbae036dac9fdd14a77f3762def1b6f5f2933ff84d70ed1a46e001d90b10ebafb5a9c37d2f9ed2946dbaeb04fd635c99baf4b6a42ee623205c2a195f8df53c97aa1af9aadd8bf7dc58f44538dc9e2befc7dadd565a3ee1f509906e65ee7b67028ddce83c7c25548b74205f7b57b114f4177895d0f86e4d5d058d2f65cc687dbcb1a5e0f218c1b870d553515a6fb832cf0f74a45f7e243772f3af7a7eaaec055e7351ccf3748e9f41694d99bb387614e58ac6b3fc75a7ac902aba86e71ddd01bcb253194ff956f53e554128860d45061225de2b6fd1823b38d9c58b016b92a57856e720713aa30268769b357f6151e4cb729001c798bc8f104ab1262d7f52519231fdc72b73aee5ec9768612fc17c1ef7ab4206a744804f6c287dbb143bea168ebd7139775eebc0c3f2a1cd0f5b4d1d74fe551335aaf43c2e071d01bfc46b49c9848f3668d5eb7551449ff97e52d976ca32e79420c062d4e59248ea5adb802b64aadbceb0166cb20ce2ae5eb302a1bcfa2f500f8327dc2378df9e62907d45fc6eb1660dfb593ddea28bded6a4a533dfd45802b40642754304b6da0fe5011465495360833d1f13ef74b73f1b3a986eca9cc24fbcdc7538c3a5317be5bbd442c016b7508887e241751ba5b90ff80da95304fccce32cf73ddae3aa04f337a1b01f7db5fa46367a0111d68231db3b09f0e0178930319908e5d890c04c563d3c894fac59ebc62d9ef01fe09a97b98405313048c206178e69cec8aae6b595ee09f98e5b33611ffa85b5dbeb2ef7ba2f64a03c5663f544e6738c960b936fb67a70e5d025f44656880e9e4723630d288742569ad2b8136ea5a71222d671c8be0c75744bbfab2f42fbb20ad908ec467006147fb962000e75067eb509d05f5e86cb2da0aad36b3fa54486e7d54953453f65f5a8fd15c9fa49581e4307ea95f15c763e078967ea6bf76d13453d3e321f1a5899f9f083f82cd2d79e874c4c8aede398e7d9d487048f318f41d941420bc865ef5a669eab4464a61af35cb4da38bf7c2c136a0ab4a708c3ceea67fceffac90c06b5ee806911ac5d4d2dcc84bd7140cb57160fa620b291aac907d672f6ac434b1f9e1b3afdfc47af114c51104116135cb6ecd5bf20553ad472a9c16725ccc8a533c8550ca0918878182d014eddbcff945b0fb411e533a513147eba3bf0f9e6a063ad487873cf725caefde3e3ec797ed0bc071c7780e580ee69eac734f6131a83ed2425bcceb778505a793810b9159c962f4d9abd5a84d3e00c4b7e59a2ac58f4eb14a8d12335383882c9a091f67c619fa93a499c5b5993011e5fbbac865968cf0902caef8a6c2204cda14dffb93861379d6ee359f131b81fb95e17fb40dc022005dd475a97d9266e34ab7640a0dc73ad885e227a7053efc042933f2ba1c230fba92565129eec36967726d33ff44818e122868f0fe22562e8df927cc475082e5a90157b45b9767e3f4c84b7022c7edebd00811fd11131f62b96141dfbb09eca2b69e6f3b3b4f08284663963d3fe2cd3a122a360bf0330b9df522ecc60d1432b4ad9ab8b5a0e27fa2a29d1e973eb96a1b0901074187b0a5c914d4c3a9b33cc3517bae479b17ed299b0c2335436146c577c041e93b2520aa04e47c6b596cf6bd93132c56ec240afa43e840363dda3fc9904c64fd825ed6bbf31214462637cb765a4807e15e8a86b1054eb45333a21de5721851b3700095ca1cab312d7212eb6326c4b517d55b04e83469e8302b98c88f59a9078d35981ab7a12aaa67aea7b573dbc7e2663b10adc26c4b08e969698e1bc73529750e048b7b9a09f251d9db9ad879d7360ecaba16941855842db8c845c853b756877d66d7f17a76c00e3b2e12acec6832a1010fcb757774c331f61c550d96662f278dd8cfa6a35f212ce67a11391bfd8e2214d13231c6c60a25964a6a995ed56a5b4d55628585f0aa01bdf8adbeea78870665faf72b001955160a0e81a51140e702898c698a917251f9ff2e8eb09586a51c08854fdbaa3adc9c68ef4741bce8bb4c8900f50e15fca4cfffd655c3108c55aa9fa8f4f0a6ef0e9c172a93b7f4a1e46a8bd8133ca25c2b188c48ca8b9656806b9f201d5c972334501a20c2999138ae72172a31f20d8239e8edc2fd04586c31e45d51b799156fd329b83b0bda22a6a90d52b8911ae1a7b2f1dba0b95f3af8742d4adc37d46919d5608a6ed9bfab99ca2cccf0a589744d46ba8e8991144929e851a71e3d50af8227c4457f4e529d0a924f09520a011aadafe04774522a011cf8de33b10694270b88ff8a19992035e6a55244e88dea8795f60d6a3f2af54a4cb812f85d4ba2f1ab0eafa35b80bbd023bbbf7f39c9f01d4282a1049a05705896a422de7d3b92ecad696220f1ce521d12aa5da9a21190123f13b3ce9b663afe1f0ff60fb119d27cc63354974458f1bdc0faee7d646f9defc04e57d06e3c17ebd3fc75fe93e69b7345859ab5cb44272559fc9fde18f6e42054cf47a2f68a97566b6bdfa60818dc03595fd294c5d977bd2476f6cd4b5e7107016b98a7e9db3fb1cd545b4274b7569a5a224572d12b5f7f343077fdada738e30c423f3bebd339b5d950e29ae01a6265970ebe21e3637bc29aa97e694093b4e36e975495c6423af29cd7d7682622f003c231be5ba2f261f79f8fa96579ba57ccc63bb7997e7ea8359feaf497b748743bcea1ad4e76ea678fae59b8285e6b6f0a3dce320c6fc7f50abacb1e920f7e0f87e2a2722d8ddcf35f2513bbbb51db6a3a81148c08385c4cc142b58b00a90af953a684362b1340726b587ce8eac849cf768cbd6782c37da32be0cc5fed718ecb4305871d6f5cc4d52e362ce37432fdcd7f700edd203473216f49c280834ff50a90696ef922268d47a53faf4817f515c5c6a972844e2a8fb3345ce2949b91d24dad7c61732a750e332e627ecff8ac19edea7e7ec40d84c2173fbb81921fc19476242264a1021b04bdf4f96b46db516f8f6bbf7959e99bfc4932f1058ee80af9b5499c7ce4b6ce79246421174f8b1fe365c44e79ad21a3011d547fd7d842c975315a5990d6cf04fcb3254c06d941cc1e8464deac1d17d19e5c682a88916c1717d121ac339dab4785efa374f366204ecf3e221fcdc165138ec8691018260608bc522e9258d7800f52a4782951a842bb077dff8c65f4a6ec8588e397a9e1fb288b5eda164e1e2233dd8941b069018b6b6298fa4661746801d653230c86705638b11463ee4656ee40e567fa2bc8dfad5ddb82c8c48087f1c146b96ef4c2a4fe1b136a963a98e3f06060152ea8e1c0d737020312f0ab709baca33b05fd95425f7807c24adfe83846ec04eda254c20083718d1de83f1e2d52e877a2dd5bfa58fa9e89196f9bdc38805db391b4b997a80651b520d7d2bb8a8913e5f3114b259a99b69d9c315498fd1c4bc5f5a9338fecfb58d9df0225a6485a8ca714d622dea5441d6d2c1af396d20d8e5bece6af56e4804bd2193186f61ccf7abdd3b13984f32f226f6671778c7c8ee2672dc4b02009e4960320bee1cc9c365ef48ad26c28ab31ae79af17ffb091a2e50a09855e8b23e5f9a7636c72d13e516645c3209996f2e16d9ae86dc46766fbd6df5578eda658fb16e50baa427e7122ac49d9af5f2a188d24c6c80e0f9838700095e260156e1cbf22ec3bb40642f2cf7f5f7c4c26f927b365ea92c9b223f6ccdfb5861b82a6bc98ac136c4b7550250086ed1811d8f419f05590ea226b8992cb231c9d8e180af885a321ccc77821fe98f8b610c1f4bdda435d72928e2fab156ee6520ac90a2eeeaa05473c9bd4a809beb8c1c491d4541a1dc210997785e8bd38d280f5c4557b2a3d86a6545c782622b0aa0954921f813ca14f01e2255d4ee23cae6fb290ca2143aa58c08a68c98f51818fa3640b6d5f055aef4517c3514c6ab5b33c1bc94853b236450a2607a1822082095c60f7fd425b021deb434310de581b6a2e4b81028e0efe0e9ac6027ee0f8896a2e321095510323f14de91b1d64ef3d269e8f526b21f8cd566d20cee5ae11d495998851f941561f3c100dbd096ceb756d67cc630bf0fa0398c39d500b07c2b7bb8bbf331a03e343b21ae1165079e9c42083b353dc70b65bd675272dea8aff2dd54a5651166cd19d17cc66d11ca6d577b572e6ce0e4f2a01537624349fdfba2853b200724e32e2da22b1ea3536c90d816a5408422299ea14d11eb94e0e95bfd474374f14c456b12951b8bd9f58bd05ec1055f84b2f3ade47481c084fb49df67909a6ce16131dc791721a1c1812ff225d153c57d832b7dcd9bd8f4ed7f009abad145a05d654f2b855f035457ab286af4d2000c6703f562362717ceac1d5a2b217f24445477b78d32552aaa0eca2642be56475beb8fb7995d5634cdc28fe5b032a22fa2755094ea6add8032137fb5798df1d4d4089e4a9740dbf4978062b82c7e0bf9bdca6b4fe61f734feee4d5822462d8acc8f2372b2178e89a2089f6de459503969ae75a5525a9cc5a1ca2c691c8393d289a9edc007066251b436b1dab7d69ba7fbbaced143a93da96f45a1e0042f3b0c806e0175a4548b536610d8da915a2bb2e8a4680c8419f6ec2b1253315b28a8c47b7d166931e942b827122da01d971c5b99814d5034ea7743c6c470bf57365ef173b7d60b8f3cbbc2a055253c6081a3704a7ba5ae42d56bf9ea3661c6168b652380af662af00654b32cad33ca59a8a2b1e45174bbf2b0887c34ff186a06668f27f24e0ad798c2d329374482b4b86758449d6b720d38f1d0201fae0c4699bc9baa6cb1e4d474cd307cc5047cc4a7789c247d52fa7de9ad3c21a544c2bb3cd8167e6efbfbb326233d33582acb072ba0cd74ebc000c978e9114e63e4da4d8642428b576389e04558a836decc9233e545344716d634792bfea6240d370081aff18332e328e5c6e55159d8ea6f33ee801f421182d7f471f28ef23dcd5f4af15ab50eaa3713db6d6ef7a044129b9370c739776f5663b6ad2f31422bd13ffb2667bd910207d39b73700598dfb914b9a1f27dd410177441995ce385961ff05c99e07aef1d865efd8ecf6f79cbc19335451b8818245521c426f11ecdb917e2aa71d4bda9efaf38bb1a23975ac34c4a071e9237d586bff6b85343a913f32e33fd7e57deb8e555b9df879776f9b551d6e10487d9cfb9445b3e0834a684b51928ff13ef6139776e0ea85d6bc20e2e87dbf1b213471f90c004f25be7158e511ccca3217da7a7dbda5713035b23cc29a25fcab150b15c4815f2c0cfb4d20b28daefcf6655482eb75dca4797b12024283e93d06e3dc76c210f25a0e1f4cbf550a6b374b7118713f2e63f781087a2209daccfb2ee6414cdc81bbdfb15a08034fd0ee2b6fab65fcbbedf693113ec4d3adfcd0024b10ca047a74fd2ef8e92db37d6366415142c9032fc5f75cdce24fe3b79f2bc6c6ed6e78755cb522c8c16155ab8f8f591ec85113c7dff42c3d1abdfc4fa55b4976b3c008f3e0d50575653637d9182555e76e37eb3bbe2d28e4c5b8c910318f3a34b674ba9b6f12a6c585cd2176e21d8a252fffaf82317806ee156094e5a75d2a0796b3166c1acb968c9699403544244dc1fc0a2665889ec0e61ced7696d727c04083c3cb63ade48856450451bef649f7ebaa7b77148adf997392337eca3abfd31c339ed3d82ac48e452f4012ce580a3e67091a949a8bd12cc415822224cdd00c916e2047a6e200f6c5006a3971ed4e0ca0093867e0161e52c69511a9e460645dffd6cebd67909f618c39604bcded820f5bc3feef6593442b67bbd5445d3b7d3357565ea779d1f755d599e5e39b820f62f229bfc52ea48877b4d375af001063bc0ac2994cfb3e040e583cbcbbfd9503cdb6d964034a267f04b229dac864a867f80b269bcd8399f7c038171c12898a63ba16fd0186b05190f8792a1a1bad2aab002bffb2ba62082cbce72ba1e2aa90f8d9af59e99d218ec6000201b710d98d2514de4d6c88880fd1bd37820d23f12c6dfa3f68b6ad818ea2bd92e1ed9a5406ec064267d9be88aa6351b96fed38530d65e4a80f1c11e4fd46981f63a3d90406cab70e61763f0372081050191aba386ae0242822e1b15fdea2a2468f8624e783d3803234def536414d3c69fcbec7b56aeb4c0a14e978ebbfe47a597853165a8f72a5814e4ac2718055b7e7fee1a7649e0024933fef15f55e5199ba5c81ed26081129ea06b38a9860e2a02a3506b1b35f4ee881ac38f7e457e7b53c3cfb9c72ddc9654414ca3f55625f9f3364ae14c8a1428a5f6fcf3ffb19b533488bbce8e8cfb178c12832689b7ce6f20dabbed1f1685de1f600413cd4becae562239170682117ed7c91d54815873d0152c61bd48743c1af805d1b2e148059c5e73e56f6cae5ebe07bf8529b6f0c6809fea83725dbcfe1b8b63e859063377b3360c34c0ba9175aa665b761bff2d2eae91c884312cce594e333714135e63e4c755db60e9cc6b534653b37bb3936897585efdcdcbbebebfb67ab7384a99a2a21068e69850e30e006c30fee363bf7e38d59ff70bd756663091db4e779b18359252b4b72c3528f2cb2c01555c046ad84f71752266f22afb0fe1eb86e9818288147fc0fab40f1628504d2fe4ce0c8348396c629fc8b7d8b9cb590f4f3ec3d8455c44505c689105ee197a91e1dea503b58aafaa5880c6e708c0c659f0aaa1518c5d28e27efa55b3a3fb99f483da323e152f5aa6f302e805b1d6ad105e900c0fc8dc23ff4d74b8e232b964905342383fdbc015995c51a426851a483ace2d67d4d764a3234a3d0e656556c0874c88228c386d339eb25ea623b4802b42ee9f65f28cf970a8c13e650983b7384e4ef22e4dbf37f9b6e0829a07915cdee11d902a03b67f748cfe1341cef19c81911902e3f7162fcd9f12e512eb4200df6efc8c716aa6bb809666a83a87c4d03ae01136e7fd5bba4b1d1cf7c50b568d950a22ec2ac727dd854747345248ff3965e7a9e3e319dea311da953f371cd7f7fbaa6cbccf04724bc2cf807bf591f8eab0189e6cfa4e22ffad6d1ecc7d61c8e3f8a4070c8c8c96879865129a33e8965d9ce4a289b5726eb1693442da1748509f8eb3d4024343703666d1cefc9a8fcd3b502edc4ec03c9ed58310ce930a0ba97da15e5ab820ffd8a8f4ea56b94b753994e47e9e1e1fd9a6756555c50d00c8d58c2bc1080823a069520908a2be330c1e2cd02e8c92ddf769dada7d24bd2a33170ab6cf3a961abaa2bd9ed4169eae5374ab95b6c68f3c11a0a6f0fc60ac519782f081ccf69d2c290f9c1905f5e9f6333c28709da8e315dafd829c68d159c6b98e9e5be8c0911b6ccff69abd0b53f57dda545ae91a728961ef259f068f97609879176af1c57d2d302e5f1edf86e569c4a89899a2b74e2116ccc48558344f785bcb98f59f67369f24da46317aedf3b8cfe32ba341fcc313f241ae04c2e7cf41f26bb96027be452a3b8b20a393172901644a2d8ebf5045ef8cd922a22a04c579bdd6147b3245759a9cb151ec4ff485460e510c7e2d15d965dd89a87f7e893444967ce692e0135d3175f15ed11b4286d01885c2472747822fbe9abf177efb8de545e5e69b632530d8b0105dab38fe7800792b6e56777ea2ac1fd261735b296b26ccd0b488d701612e57cd77e4963964a5d5d2297ff233db314e8f9bce6d58283e175c976ac89654926744072fa3293dee637f637304cebcf19859d421015765286d908aa799c50e73f920d911424eb4cc7d5dd93d5e1557fa8621dfe1024fadd208c8d5bea936a91b8a8b3b79d9ad1fa816665c2b370a0137326447428836030e6f07fa2376eed3f8f7b4c273a3c7d5cd391e942fa9df26f1e9c81a61078cc34eb41e42e7d859b33e54630904f945011fb03c92340a3c465b5ac4095cac5b6f476054a9fccc238801ae6463730636a3f4c02c002cd354fef1899e042203332a193b4e0d3b2d132c463788ad193236c556f4b26259a437609c31597624ab4d790cfc7722fdc557752a1b204fb81b321ee661c9d5b0d4b6ade061175d40ba4f3a69ad87fe669b64511a13138d3cf91d754ac0b0adb84df9e7b6bbbae469555bad73d2abc0b274f3cc433fbd8210296b95e1d65673122cdd804004147d09dc889ced91ccffa5f567718e05b7f53a1ee5c92beadd631fddbad62a33762d0847478274c2d001a85955d2b414c631a8792719a17fcfcd9b221c6a65c1bc99fea9dea1154852208de93967e8ca78a2ac7195c04aa9fce4ede0d641a8cb37a0428ed41d3e4a910f603992932dc26b8c67f896e9f3bfd4307388b40b8d53b2cf09b37cbcec88464b266cf3c11614c6b0c065839e654a745ff2092fcb7510bc4315508e623cb863cce8b1a1a5e8b1b7327625c4741effd8b2d3c9b133ad286b2cfd6fe0f07aa8bc2b9e5e52ecaafdfd437bb54a0834b175f25ca31d8fb671bb1409cd1d799999be9c2e3cc53e3e434ba296b25d6d6fd606cedaea61407c3ef23df8db140c50d35cc4bdf6f7b126899bff41b5ace0502a772cdc4d24f31ed96ba6d4c9d786287138d71f673dbf584a8a69e4c6182130079910a8a6c0f4c27b7f50c613f8919269cab45a6321aa257a9e3fe0d7d2fd7276e4d16e0f769ed8afc358633e50d6eea86f8a48f7965b0d8c1c0f351c2aa4f96fa8436414d370d17a835e590e254ff39a65ec03c5ee5689b3c9e9fbdbd733978e06c0d8f75cc3e3b966f778d97e4da084802546552aa12a367aab873c2f0e63e122a81b86ef442859cb8ff93dbd163e3c4127b7d2a1567845384aa8d6cb7014096493b3e74a5a407228c0b0af52d65de8b7e272e9458cf92e77e5fbb11727071310b242e5008a277a6a954c39ce691511ca92cc101614d0efaf4bfa2061e36c2a0747619339dce8094770ea09db43d7f22533f42d6833f1498d7fbd4c48d038db042fe6b56c75d87971caae0b11934d6ba4ee1310958fee8e26fc76453d6842a6a194e3d73179a545e930663cecb2cc4e850b60fa46de30ea3176d366b43ee87d721e01ba043b68b510765525e15c91b0a83eecd93449c6bea6ab00d519a2bdab1abc21506b8343bc8ae8ec8df4cf75d4068ac3055dbd5ea1b6151714a7dd15e894eafc78f910d233433b68b54782a162a4c33b0ec19879c46b308657dc769060b2790fae1d3a82f160dc3c0fc4c8da8da8d2084de790ec7cb25acf630153486e9f4625e1a653e797a85b3de8115f099da221c3a26fa3f961100b44a164189c0f803e2b7ed45680689075109b09bc220ea6ff73b0c47abdb7fd3a868398133e28116b74668f7f1031f6f645177d315fb8f3a9d42a508c9d558c81df79f4b7bf23b78a4d478abf14887c69b9f8b458429e43c9ee6684d687d9d7ee6addb5026206861886b405380682553c52b6d2aa41fa879cd01e5e78ed795b146833b715414d34b88afe31438adfcef2364d45ac683dcb663317f5ce23dedfc4f96d210a91b71f4cba3b95667c3850d3cfd4a391e380659e66789f8460216c21885dd0d10821e30da25bf1ad52ae3ea958928276d8d0b69565d1883166457184404277402b178931ffaa7b5a4018419641b9187e96702d44f6b87da911d515e5ab3cfef5744785eee86294840477ec8bb9ee5cef3ae879f290b86a6176656b02458dea7408ea0aab6cb7706ea053ef70be22709a9724209687fc0dc34e7f0a2d4390845beebdb2455afe7ecd7da1a9dada01a2659d63b9b57bb11fe63faf394e80470c89609f032c7d9c9715d2109c5a4cb205ce22db2a477a7bc3f39aa02c6a17aa6a055292dc3eaefb5ebac2b316aa963a99cb7952945c365aae39c70d5de9631466b56217895c1631e09ab220a65c66950e3d3bb82bd5bcb1a7d6ec04fd16332bf9109c9b213937158ecbac14723794d179a3a4bfedb0830476ca8648c64b79875711b66ab77e4364cebc4ed35517bb6ed3dd34d1caa3ed9658fd52f936b0c2cbe1721ef53a55d3c093ebafadbd22fe558c54e15c7512659d3329a8095e14f314b716062b707ef64d7020fa493a4a8dbeca633322d92bb1f1ceb9333455f4af6d4acb81e6f6e7b4510c7f951f01865d04624d32bb07a2396feb29f130774512f0bfb8128cdd0e0398ab59488d82549aaee54aa5cfb937d6888bb01325ff80d3eefb7f705cbf6178baeca140e474769d9e17536a22e264d76f289e559886b70ee9680edd52581ab0331a0c1abe658e5b6d4241bec9233aa43e40e6088a4fc7806aaa7560ac2e95c58e76bc421a08d2d2564e9e6837cdb341b177058b80c9d0e45d4af1825bdb368ee66005b7147e7d386831bf8f8b1d03770464cbf6d923d5062a4b6400f468b0cea706bdbd641ec2dbbef0631ed30b68a2783e7d0f545aa3d86c79b5a6140d32870a15eeb327e268853be5d3f5352fec343f7c72efa34a94bc889860341d50f866b3adb7d0c194dfb52c0b4aaa16452286d224f308589c27eb4b6c05f9249d3ae8c65b5a60f2a1de365a9e4f375f0f65f2b8f2ad1b381af2f4e548c5314eadc878fa7cd32710b15938f8dd7728c68ee54c613dd7b3a13b16c30a23fb86666dcd43c186c335b565156a4609ab7d0516d2c370f8fe247a6b4ed3f49c0895d8ce3194dd5e9f9b2615e630bb0a1a907161c441c51bfa0966f778d3fb27d69bca6306c53e18c5e5624b689f18c0ac21d051599b6615878c2aab126fb7c66623d6d2800716b3bc6cb3d9891fd606606309e16dcaf249bdc232bd78f05c568b3781476245bf32c9b5ea4df3f55184fb03e7684ce22e2d4f2f34b6929bfc4b03f7c8abd78e24f5da22cb788b800a4edab70f0cbaa5d211564db959419aa0448ec743030e49c43fac2fcae70849ab03397e5a05cc95d3acf2343e82d224f4d3fbfc96bb5748754e98bd88f25beba0b97704f8c587f9f4b4a81d822511ef5bc3c418e2d2998f54e1cc743ccb6ade81f3206dc6fea2201f5e602192b93fc29529f87c2212872fe134475c99135d3212b7f526e39f90c4b509f0579e377d74fd7dcba8c8404ca10c8056cf9dbbafd9f8c300b79e23b7005e52b1af6d5a21166497ecb6e8810bbab2a74e87c60deec566a15dfe786ce45053be0b6cad991cc6acf4622fd17a15400920f0ce705153aa08e3a8416be654f609ecb139204b6716e9c985c08a69f5c52ed54c4f4cd9becf232942a200276fe506c2b6a1165882ea95651fb49f67ff8311d67f2eef5871d61e4b3f05da9fc4251e6105730c93581612a01722edc68e3db91168bc691d8d6ea862799b532168ea4abc82638dd95e42047d35d50b53e62bb4aed5b8a33848ce3734460c8209382a8bbdec0546316c8f3acc9b0c8d35d5d0ab425d867dd5c1d76ba6b1705465479986c56671963a6442748a73f2a69536cba3e8e4ed8685ddcf2d2be9cd719b70311dc103cd477a5e445d82467a394591c4762b7b5c60c7a432cfe23e6ea3474da9314c54102a011eacc1cc3d1d69045ad4255cdc3c0c6ff1f11c818464a9367be985e042959bda757e82fa81e2f39bb6aa0b7a19825d2f808a73143ddd6bfb855faef297556195c76e41cabb57d4e19681d1982a91b82cf66846d4c9ac499222af89fca91c9ee6aaac11e0f5ea013aa3dfab2181bb298a6dc9028841215f145accd29682556b3331f0a92cdca16aae691dd4d6b125863537ed0564ab94c4bdfe885825a2fd512fb5768a7c15123ed8e99acc5481a769bed865180698d8dbb311efad4535befafa404285476100c04738a2b57e33cb363addace583326d1b83df9bb88151b2946e2d9620421cf4ae242943c5fa06ea605887329f1f67c2a18d192e988e51c632808c4e0392f34db3464b20e6b9e71c0c5a508aa1229dd25dd90e92800b56c6308c50bd567b5ff9e4cdd3a561faa470569195b6d31ce257313d3c08da4ce7ea4647833d7ac9684b25e9fbe284a7da16da7db80133401a528f61b090d144b938ab707d48f14051ec1d79d02c91e7a2bfe52f3b1898a3c88567523efc6a20dbd80d33ad5025ebed30baffe27937244696956238a4d4d535f1a4864c20a3a527d3fcab780940a63575ce126a21635e201be338b379b4a6466ea148ce183da2caadc5a016fd1b88b1183a10e5ad01821d4ebe888a096ee46a4dcb7294174cae292a29c0159b1c74a50ee8304ef684b295a89cc2019a50d3640cbae05be6274b4e8fa9e39a89992614d0fa1d08a1ec586178b6985d5b193877bb387cbb8c4459a676cf11167b024a052b70531f8530ac6b55220586ef3ba32f5806474eca3835a872b72fd35670ddb8d00fccd215881e8ba337813b2b23f465ecd3b5317e4adfa608492b28963eb4079add896a041e7e901e2714a630691212d56c2650c911dfe24e97bca19ecb1fd176913e8da9cd396cee782f4017965ec4e5be1f66d6a3413398e9762e3ba52f83ff5b56d221fdd3a1b66a001a40c6a93ec450b942c9ea59fa64da0b77dc34d591d6cfbf9c45480ce2e9dc613bed1b592f9f85e5938ea4eb6a03e32707e06add05c2e0504fba4d78b20932088953f0fd3a3f446e579932d0decd65dfa6196cbcb6a875949d49d471ca84f7304725602bafeda0bd094e6a50b02421a23aa507e92693acb943ecf3142e73d79fa8dedca02018ea4604c260ef130e1e9e5148dce2c01e826ef97cb2e95343dac9039d36b4c7512401f9a89dfb389c48be06051ee88183fec6e125c31054a528a3d3c2955de431b65789c6c8b4169c03501d4886ecbd2be8826cc65320a0414cba13fa658cd0dd7ff5a67145b13078236cbdac4b1e0e2e0b2d22b39b6d46d4e93b9936bca676f118b3f910d5d9fd59a3f88183dc6933e0e955277c45e6a04abce9df4af1413345292162d6eb182c8db44e24e0e7739b464ed1927b8392b0d5e2e848a470f5274c4ff6118af624420c54ac82e8b80714560ac8d483f3533512810bd48ecf53fdb27021f31ef599cdb13ca98f2e8663655ac4326bfcc52a43d2f49caaf2731d94e8f410882dc5c5961604e03c8b02f1197888fbe971011000f81c2b10a79290ef686b4298457f95d09dd830f639d1b58b28ed118ae4e43e4908a54460b92b17b7065076469dd3d098c219d064a766c4cfd0549052094f690f18461902824e3cc9e87e2cc82cb51506d8f366f791e5c5fab7a744f4f6072a304c2d325f38d53da76113d64efad8509eace4d6357ee290b1f98db664fbbe9d24e239d96a558bcc2966d24173c4e6c6a657658cece66366ad36e7ba39a80e0f47c142412a823752f24b2a32bc22530db27bf2ddef38fb2992364f4cf34c6e25efe03637a9d2f7b04b2e4ed045d8d9f15b15a91095b20246668716e73db9adac3a3c4de87586cdae86fe527422f2a9fcb2f6d18a886e1a70c108c56476efe848a0acaee98509becb8c3bb4edcec03b003b282edff3f904f516a48dce9e8e121671f1243b34c93caa0eb9d9be0a6df0e95cd6c616346d6fd1f6c698106588c4abe0445e0b9225f948486aa59c978497b4e3a90ddef09541a94089ad5af35fa93db112879bbc47927f70c5b88c0fe8acc84b81a3c454fffa9b3c0fa1add3f467fcd97aa5e948242c0de235b7a3aaf70ffa0a13d5290580ee15973249ee971da476b9ed4e86667c6042fb136477589fdc8a3139ccdcd0f2643635afd6184d9cda077fcb0357134e3572bb66eba8d3195ab045f65c383b9d63f1409b875f35cd1e929f759b740f1fedae3ee79576913065734a49c98efd0446974e4735624db5b3f8d811bac49bb95b208016090305701b6be3035ae94f6ffeffc523478546dc401f43c5495c4c69c0a2f5ecabe1a03943b0c5fed8b2950f882c2ae2d21f192a32ca4bd83dd892bab40680d660e7a9479c02382c01cbc72b1a179cfbf3f41ea12a7682ccb356d68f8ea82d8af223bc2e131ba0f9277d8ceec08b40cd7b434dadebf7670f78baf8f11d9d4a3364d651fc091cb360dbc6ff7af934c02610616b0f680f3e0c4fa2602fe051d45791cb058be46ad1355fc23fdad469d4d419831c5a178a36741a09f2e97b355e14ce1a3ecb6909adae4a612ea0589145cb246501cf48b8a4793465d0d7e5081a98efb0b5d7b3ef8141fdd379a27f424ab4916dcf42023a3710e4906a0c3293916708ef99c9984a96df68fd56dd7e3b93504e8adb27c52cd058e437ea972086580b6bc6bb96bfb5df55b3a54812da9c1bf39a76e50360054303d77b3274029cc8be326e7da2262aec8916dcb0d324fe7be17de3c66d5866b7591f5bb52dbcf46d238f95a53e8e5c012dcdcd46c4c49bc5abbbff5327d8f4082527771eb02f44b20a31be8da48cec92e4af631a86d41188c7e0de557c2d1a6955406aa87385e0873cd2222e11eea419afef013a8acfab3ae3793afb90b81e2bc9517a0c6a739cf8846f65d9995490b69097304b0154849ec317a45e6f7559d8fc21e0a737ce202cc81f107884739e6c4f51ee44822164de620300de89fc8e02f38f30d443cd02c67f37eb1938501383f81a0bbe526b559093509166aaf9dc50529ecbb8f9b36a36ad99cd540785a028765b0f68959283409d8a2155f39659109957d181b53d1b767fc631d9594a9289c5864b8d1cdd7701585110c6c0b25bfd3b6c2808ce0f6b027b7e7dd1fa0abae34345ed02869b2be08e87d903819ea378712c5ee8744b49a0ff01338a064db18aab43c1e75d89a75480501fd64f57ae29f71a375eade2194ff5043a233fcb679e0b5785c337156b05a13768697b917ab88e4c66ae2a3032bfc8eea237985303d1d4d799c50d4ececca7043c428415e75d1a3345cb50c2713a14cfb20fc0c13c94fcb3c12b39c8d750e8b025c20d27e1355f9d675af79d6c2dc43121c591260db632ed38f44ac2321f6557964420f75ac6c9d7cfc3e28f97826fb251a740aa4aa5c3c5434546fd6e1ecd7303984f71442b34710b0d61cfdc02490b60df26eb48390d66905d844b13de5ef8aa1094682c404dd54bea7b6064b13ed8635cdca41d59bd8241d77c907d86d5dd7d73f45c5223fc9cfc094e72b64c9a5284f2618a7886f6dada059923575682daa464af1b8fb2faa775678a8645314c75f9c3b535490a24a357bbabff750bc4b4f2888dc406f7e450b9d074763dc38b1b7aaba16b5ab4b1425555b27aabe78fd465a84025a215e0cf07362e2a8ddb3d1f1eef3d5e009b561f8adf83dbdb7baecc5aac23c7156f1b790787e375964710d77204121082322130361f3aa7ee2d1aaf07fb389ec207b86a49f3199a8aca6addbdeca7b0395051df1e3937545f0e54e350c23066685f0faa68a82c37d57b548a5429d91d5bac21f012bb8a40f9894d6d13324cf74f0ad1cbbae38c70814e26161578c54d1dc916008ff29c76c68d481c862e83dd19a5a0efccf5fd31f9ac62ceeb957461326adfc698007ae7189f3670a56e81ea38080ec0fd65d3020f0f7ea186413a0a94c2e7d942f0cec6c61896f80d4ede4ef4f902b09b8734f22c3107915e5214566076c2e73535b9311d8fac6df926cb22193540951304e04a410b0a530573cdeba8918067d0a3ccf316e92c1790695dd34daa557d084b102560f967728c0a066752dfe0b9ff8fda7201ae049013170cf9e5b28d710bd57ddc7f8f48c835c471a010a9d02e8c7abac297a6d4751831aefab229657af19d936f1b83613f817bdec12d6c26706700a5c2bb2bd631c177a691de19017c4990fa7facdfd20211b1bac1d9c74339647659dc29a0c96b9014441d867f2ca2098d4142159cf603ed3bd2de52c31c28286865618a88a9236816929d2adf865b2c593b0f24245533d75690d6e9b7d44258c8e26b8e14258a0dbad73d37766dd68916c40e02da845fd3aa4ebc82ecf3f7107d9143a9a5cd86b0ac0be5392044565dbd4b9285a6ab069bf09107f5185027cbbcb5e131dc0555a61c48dbe4d1a0176abc9ec1e652826b954ff5fb00970f1b18702e1a6d7479f1f03cfd2888cb381f5e0949bbf5645d70aa33122e23062632af37040d95b141ed3a0b9e4217667d03f3c2fa2053c4439c247af446b32e499c4fff92818c141a53481dcd30008084d50cef3ef14afe7e87b69516174692e2f60621f31867fe1aad2d8b73031b31fc13ea97d044cc10338591002c78cfaa4af1b1bf3e27fe0c502e3818207520cef04890f14bfb2cb3025ea70864f55899343e638399e31fc080138b5df116dfe90eec6320a7a7a93cae4912b0bb56f2e87f17ff0daac566fb1e90cfb454a37d5500f1bfd5d92069b5a97529365f49d9229fd9dd7d7426ce2018f903e1e81090bf0554acafc7bd3f411b043c9ff29ee991cebcf0586a41a14e471e3b949f11fce395442c837a88516fabc99df65a144f1e16cdc6fdbf0345cca329fd2dc442e30ab64ef2e5d3e3e8e1bbc8d81379138fbb01f62bd6fde77a13e8cbcc57ab1abb7c2350ce6a993741487cda6c99fb0c363698447b90da242b62f9893d95d11dccee6eb812a2ca60026e20b2d06ba92be9cdbab40d76d8faa394f877f06468938ce9a6b61ba8bb62217228b4259a77f31dd45b79f0656d8d86ce383298b29c7040156e61f130a1115d4dccaf668b477d5cb519387262d382b219928ace834cd0cf02cff17f2e2df329e741c2eab99c469beb261468e6592c5a12d58003e4ffb1d130ef3316ef286dc000ee213d3f91ce4e7e31371cec155638fd024434027078f29de72264a030b4de91e44269c2eb70f51e178eb0de7c8db315329442ca7dc09fc86a2498b3f867ec5b4a281d7fcb2f0272876159ba185533f7a8ada41d0de0b91c2b3440850034c6c8fd0d3c644721194e4c806481243f9c123fde11a7cdfb04e87ad422ff431a90b590e437d38083c959411035d9d48537f1e74097c0342903a6ef599b3056157087b8c27dfcdffdc4076686357427697aac91e0d2061dbc0b450df2650fc9c7f41b32925acf772b7aba2d6609bcd89583bd69faa16931310ce7384e343ba7661eb9944ea19626151bc709aa05eb4ae02eb166c4123e9240838d77f392cda481845b4f366cb84ef5c22a538a0eb23b81651497922fe95c8536b41253660f655d58e70c34a06d4b065edb73c2709c637cf16432f9465c9c345b8124115b3b01e6cf90aa41ec1f3e09d05ebda898ef3511006a51258c462619d630ad96632bfb53db85631c20aec8346400f386be903b65ed0b24f50aaa8070ec9a001373dc4557a19545da79415ed1c0804242a3994f1641da82bc74ef96bb72018f24d2eb5fd526a294c6419d70e2cbdeed958717a63ba9fa4cdc6927ee9516b9a451624aa0dc504db81473a28f7d9820b96deeeaabc9b279e79061d10154fc8fa22d34c912d3df2450dea6513cc969e362dbaa65210cfd94513a716cba6a3e99a48bbd5397eeebb746c8ac87b99322b65d178cc8f77aaa5b970ee68f7a513a8afa0c4b886e210dd4c015842502ab254cd95f44b67cf59d705e349f6df36b6fb18dadbce7cc693c4e8c7a813b6e146169796707e21ba11d46b2d03b51df232fa21b2e83ec7ceead3803a77fcdf1bf1d1cdc0a728768578e677328b4a48ab87573027263cff26b6157c064dbb14aba4f1aa4aecd7ddae787620fd8de3a5fc0276ec8837884d2426ae790e723dc1de4342d9c918f2b5beb49406f310af1456b0266e7c3e5e48b44039f7148c5a28c884d451d495242a0bb068464504a3b2bcb53c69348a282efebe39cbfe3fec2494ff351d397f4369d58ca5fb14f42043e53bdbb4a110a00f5a5264992c63510553f922531365eb5c26afe0ca6a9bb8694e0f6c02952126a37a4c3db7166810c830512e9f40c0c9fbe8b2600d1896ec26f329a281b72a077cd311089e9672ef18796a10f59984f313adc1b4292c0c50e22a33a072b83f722d3ba334222bafd1e7c80973c25530a59c658b2dbcd3f0713964c04baa251567d4299f78626dd3ddb41c91b0f21e0e20cce94b892a15863394fa0cce328925989b0f9b8a6d15b7e67447ca9db250a5e8b12275fc89921c2dfc8a0c2368e87714de5387c6404d9376fc210e39bbf2c63924a650b9d5de019f9a11290e5a9baeb5b66f165407e240bf1e502622878b538582a27ee5d3ba540219812c48b8dd28c826fd46e76cc534bc2890340e2ec386a5fa5071132998f0a08a43d8189c009bca5bd07ae43aaca17f546492b0a904bf086007ae881ea2ee8f1dd4ff9655dc9be8b1cb4aa3f819b64879c91d6fbecb292820c34afe2b6441556a4c8379c458d6161413867482957c1ce90582e64e1e7678f8d68995acc491ccfbd02b878dfe7e6fa92a92bbdca5204c2638b3879546c5d04e717d9757a8595bbe54329e90dd5706c97e51c67ad610d18e78df8de7f97f80c41cdcdbca2fb075d01ea11e4cf3ef99f7915da99b0dbb8f7926129c26c8d8c70a8b3339f4114a65a1e5874cb25fe583c482c5521f6bb7f2833c7657de2ee9aecb25052e4026733a7615b632a93625005c4e3ed55ab11931d551e31dd152c77b7e27b1dccc17b813d8779269942f72bebc930ed8c2e20b2077bb937e9d2d2c1041cdd1bd0ea74854eb48ed72aeb16acdd42dc42b726495e61aab0aebfd3abafded2707a89d088ed4b7b49ec758bb5a62021702119414ae64dc86a3609e22bedea56b749389ba5ae1f10616a539ab3ff88d70d47ecbe129114e2765c96ea06a198547aa5aedc10aa3eab0e36fd7a98d64ce655a48483a0fe984d77f59221edb437608483d04f8a3bc27d2209cdba0cf0418ee5b613d1a16a4a0df2a2dedde65b1610dd213a5a5f9161ce41fe9507b232d199de1bd7f115ead825f82986993a01abfb7ef85074ebc37eadd104fe649c791660dc2cca6198737c90d67cb37e708f1903716681ad57f1d58b3e7f986d827bfbb9a3e898c895b0ae6132f1d8c31fc94c702d69fb41485f09757dcec3a8f550a07c2cf2f1c74ad936b587408f843f12aa1bf1f0f74232ead321bbb53c7288e29a0419778dcc4f27d31593f9b8f05c46eb00dc29201a51bbcf6c0946f079326f0f3ab0dc8bb1a67c8aa058bc7226c9834025122e9076ec02d9d607f8dac9eb36da346089753fb1e4f4a7d91ac2a42a07dd85a74a1da60f43f078673e0260e58a2c2759872685767b127346135adadef51116378a286fa73a7b5ca2c4bb01fa8a4dd456dd08e03de0d4a4c61ea4768e785082839975845e603c4e36ed9c87fce0517af45080e3f27ed1899ac5aeb04659711e32ab9d2d284a392ac25a32518576770570d8eca0cdb833c4699de280f8628326ece977243b6c6b42adaa8ca5e3dfc37e59791d4c702c200e18484f04ca2e93f66877d445ad130e6a385668c4b665beda2d192b40477bd36115d7ad0b3aa7f437e953b4e66dceee65e38c95639b66bd7c163de45d93cee26c127c0d855b12b76275b4558e3ba20a50b6afe60a2eabaa2014289cf300653682d49afbdc762fa531962095a98e060327948bc875c45b2ada14eb255b3eecac79905d69205ec5a67417abb1da1f5d3831c2d6e34d4eddebd239810a9fd65a28d1d87ccdc809f9c1ca79a61a854014d853341b7d754a023b9ec642f42c2906ce8ea5211d784490e68b4c324404d73266778fe5b67550c00b5b504664b190a151ad15950580058ba50277135ab77edbe9cf021c881cbca0fe43c51f7da1b510db682b559569b4a64a6452f1c9a5593542f510f710112b9a9f458c39060aedcc625a145aa9387d1cf47b6db2934bc54b51b0abd9a5849c257c34b5c0809682b99b61b1c2edaaad548948f5981a8fbe791ae5c9e028c130e72758341c52cbc420345e968082757145d8e8224322fdf64103982437f89f1a2d08d0ba2ac4b4eb258757397bd60289c40aa6c023ad5496949c309987409b8214c2330f896008d7e08901290d195f3cac4eb62070aed3939952aa08f10e487341447abdcaf12be17b1912390f8aa3bab39577f494a90b634f4124b4cbd576d51dec97cd06900249ef6f94de154f490095186feabc68f154679af62d038ec78530ea7123c95805992af742f110347a7e2f1ec0728d8e561029c4ae2a4f1a91da1e726329f30e8e67e21c6a5ecc83266c4dc15225631c4f6ac284b74a4c08b9878d86afe551c65f4a9948b5cfe9d3b384f9aac7bc2365e0a965063e461254a74c9af0a76e9dfe4572d0c0f6c5158d8be0a03da6a5a8edac558f1d440f85f052848c008e0c714d7544843c32837c00d884d3cd50eb32c378a3bc3c99476c4062551cf80935654351af1b1c1a3427270751573d367859268399df994e2599205bfc9e80d54bd50444ff6a48ae6c8b1b0c42174b350af40a619c8f8cf56dcd4fa1c2c8d4a68c458cd61b683e363fc0a5f8a072c3826418112575162c042b1b75246e0fbbaec3d8df08ce9ac87a664bb9665dda3e74752848ead4d793c98a36c31fa21299a88256f498b60b30cebadd6aedcc2a16854bd1037dd49412f1a84ce79223802bd48b1844611f428e3f6a0bcf7319f64951fc2e1840a45dc43cbcceb74e72821e2b115d2280803f31f2e8a2ecbbb51dbd2aeb1a458e5e10f133e3d3d21e895a7f55a097e068153191230b2c5095631c41d885096b2ac7384ebe842db8e37c586c4b48f2ffde2170da8050bc96d2a8cd847e3530fdc17a10013ae4b74cd8509597a5174a1866fffc80b5d254be4dcd43069bb1b12b8ce8bbc88041801427109b0ff92cc5fb46ef24e700993f41970fc9537c6c1e18b42977d9b932f7628526c5c081af8ab7634c975929a197eacb6f6aaf62164f17c2c269f277d511f92f82763283e1b25d69a5dab60d89d537b7501f7ec0c42ab81980535a7b438caf5b46137083280e353fa1b84856a6bd50d21a8481c7cb6715314517cd44ce262250fb5ea877a91c6dc10640a21a81666d3edefc685c769dfd18e0d657cf18cdbe648ad96b22eaefd4489bdd4113de89d082674f7a251e4eb9276f9d761bc7bcc32dc08bc344c1d55576eb2382d5117b86bfc6856c9fca81c18e5692a6c6fa397dc0dfd944eea59ef50a4b30f30e6ddd83288ea37175cda9632fe173d7cf71364813e493eb5e2b058e5b70e9be802e23c56bc8c5a968366bf632e68f9f77cec06874c5918eab895c711c662f985b9651b1b715aa449de068b011e98b66c7e9b7d1d415500da7452562ef8786af11da697e367ec6a0f94569b5812d4d7f1a48c539177def964d903820dc07b57146efe7562487c530be1a7b7989a994ccdaac4404dbd378f3e0021aabb18d21db5cddc60c118bcf6e278659c5cb0c0f949a67837eaea7eb729e7a792adbc4c81162717c84ae5184e3b6ccae58e930b3ed394dd1d76a8851536277c729d5cb093b8b29c2e477d3b783e8ac47ab6b75de0f3d6699c2aa2db0ee4e8484f94d2b9ab0d0d9f83edc8604af6c5765a3ce40f055a45bd3533c634dfc846ea56a772d0c484ad8f45c0aaceeb34fe62ec753e2904e5725ca46fc1a3cf1fae07b3a8ae3091469ff791d6c2664e26018134e608c735547c60f9ec793bcf624d3e750ba2ba64f6d3f5ba4983313b33d1fc1fc14982cb74f6bbea16ac846a36ac392c19d4e20885803d321d3b70b660a14d3147ccf497ce16c83fd9d97dc4b5a7252ad0c26020079d2d68888f3aa32d3d29c262cfc0ac35b2c7a983aa221760f5aaa2fc47e4ddb7ad3b377f52001ceaa019abb12c909fddd271a50552764fc567a8b6a0cbf1f5e1deccae4d5d162e9471c5adf5353d5c33559994b583b2565c5a5a832c56dfd680602d0caabfb580ed962ebd0d7b24300150dc203c5770815909786203546da86c85cb2d4eb6af5039ff43e536223437215b1ec775c33822cb05c114e19812902d14d96842aa86f9863da14958157f9d16a7cdb4608ca1b1218c0484d68e9a17d6d06943af58200f8925dee02d59f0d44eb097f30d4d4be1bb5d2c70411ed7d5188d742e1654e6bbf247f008d462969248941cb902c09df16c227246536bbc276009ee303ce6785ec09f2fc6bf4f332269030f181c56a9cdc0681584c403ea05a3975930da15a703c25893be83fb4c618d69157bca6eb910618fe84652899696b333757f0567164843f2a2126e4017166b0603102afe4ffb39095d71ac0940dd0af80318a880025e06f792c33249fb6e4292ec2de5de524a29939429f4088009b809ced35c34151d38b00157d5e02d8ea530c58080e1634090d12fa44fd8b8580eb4cc6a9b88b34d76808160da635a66b5cd5a2d83411807a75c1874ef4e0e84b27b777600a7c02dee8553de8d7761d085533acbc1299607b66dd43ecdd0bd64d1c95b2867c2580719cdb611469dc036363acf46c7e6e9d0ad33c60403b136d9db9f81ef7d8683bd63cfe7f9601946eda31c0d3130e58e6720cc8433980e7c1c0061deb39c7f0d37d0401869b127ba166222e43cefc6fb20fea466031cd0a1070863593d38ad1c83e39dbbc70895253bd99f069b1e9c233a3a3636ce73394ce5c544daecacc44e57a5dcfbb154449cdc5a3c76ce168f48344522f9aeebbab439d41a9ab33587ec5f9b7da2e73beec477faf98e48e43c5cd6d5fc9ef3887cc779b4bf5807cee33c6d1bdb341da2d73698714098393ad980a9f9a2ed616ad63ada22cc3dd4f8e0ea5756a5dbdb60397cac1dd7bf7fd970bce72de7d1a0e633e2ba9a3c7ab93d96d168a46384498f31898340c5b856fc95e5491b0bee8c47b8ba110853711d8dbc8899523555f98aa823f9efaa18dc8f1553c95b46f8f49ed4894a780e3ce238594a376245553e16a9d6ca75a3efd7fceb6562f9c7fae1aa18cf62e22e8ec46177c6638ef4d8cfd8649cf0c30dfb3ae282c83ca2cd752292263fe61107048ff077588a17817f34e2bc08ecc21c1029392fc28d7811d9cb7d6154fbabfda8b8de7f5b842d9345636bb843fb795fa46df5627a722fc2044942b5568deb6ab2f67283254dd3b46f26a3984c7ac9241c4d9aec3ca166d24d764a3fda4add64476b262f4737d97942ad694d5c4a0925faf813269389933c99ecc0d4c45dd6e1af9c346995ebaeb7a41f7dfd4ef4f5fbc23771250edc7c95b8ee65b4c9000626715df6b8d1c501b99de5ba97c805811de6465c179bbc1c9703a2fdf5948ae64eaeaf89ccecabafd62aa526a5e4fa56fbb2f7d5e4dd908f37242b2fda90d8fc6ae5edddec5fd7af6ca6c7ae62d136960d7b8bbbcaf4f6efb36ca6d79e66a5ce19e4e9d76d87e681c4f44856be9ab4ed767806ae6ca6df61dfb4ddfb737b5b77f3fdeb5fb37b693c769509b6d4af3b4d769a8c20a9c7b66dd21c7d7d527e040129a3918f5a493fda483e5e16b67dbca027a5fa80290943674b79593c1ff2b78beb362595c8850fc12d5eae9c5096f26be59e94ee49a94fcaf53eea6baaa41f6d0d53248e6bd8427aed4db4adc05f49a3277d93b8aead905c17c4e7fb98330e888893afad7099e7880382bd7cfa30eb212906f21ef7b29052727db05e7606d290b5fe9c5fefd7ad03a60ca4b49944db559f9a22507a53dd304ccd2f6dd836e358f433d08ab60b5359f6a6ad618b29675fb797edb89fd10d7bd356fab965534784a9d266822defb18dc6439827e565f118e21eb9cdb75dd7bc2eaa3d1592af7110d038bacd29337b6dcbb82038c53684913f8384aeebda98292f2967cce5300eb78c0c5566e068b4e14db4611e97f5e12ad1bb7778d42a12d73de2ba455c77ce5b2c0718487d99fd6d90d91dcb7bbd0fafd5f60eaf0a6e976b94fd5f53e1a8dce094cae55c983d61ed107944509691bbe78827d9fa9539f188e340232f47eed970af084c452e72fffc6bfd8ed75fecb32de618ff72f7bb561e40e8c75c392096ab516679f5a85c27b3739ce31b2fa52f3b703390c845f835f20d91efd94c0e0292529f71cf745c3fb7ab69942fa3a5b7a12acbb6176ff18f927b71d57b13ecdfbdbbc78d9bf119df10ef46fc287f88b684094a315113dc4262930906412926bc01b92591c9f4a552e9313661132ebdf71eb6ffde7b0fc329700a9c42817a6440a5e788145c2a95de9d9470a944b13f29c912c6258c4b3fe325fba60dc8fd2e894a25fba6d29bb0e967a0a864c2a5c7f667c0a020bef21da6a6d2e317fd8a085b89313695b8ee31fe889f146310c9e33ccee33c5f9b7077cfe448fe084ec946231804a58cb2d1e8fa0c7f968db2f7f8bdf7b2ebdf7bef65700a9c02a70cc18fe21cc1240f2077cf1136d9dd7d2429c62f65263fc3ce33cab22bbb7eb401c130c623fca38b47d9675746ca46f8b32c88cc322dbbd908739f7d432d88741ee7711ee7d962baa84ed18e3b115d91c8fd894824f2964824aaf6ad7dcdf4a28a33e91a71dd09c77811fd15d1b388585ef43144a2a7cf22fa15934896322e88e7cb91b093f3dc8861bd82bbdb0aa5d82135d62de4825ed63ea5500adcc25a3b8f14c906c8dd6304d09128f6c8932347ecbfaccef99d5f1457cf307d6ce9e8ed932c1559aec3b8211a9c72e385f58739a9b726a5944e8a790baa4adf657f557a29e53afbfe4d69a7e1cb527afaa693e9ebafac7c7d96baf2a6d2e9fe48d44182f85f16c3aecb799cc779e610119c5ab090d20985e883b7faac934e2874f903e503e4ee89811246f8189193ad67f48db8c902c8dd1303a16ce17dd9c931086de5b66ed1d7c7f8bba572ddf4f126da660b6d8c0b22339def17f70ea73867e1b4195a8143adf936a39fbd86abf36437e3719f3edf711e6f41984c47a5d69ffef38fc1b2e2a4785d4eae20306e40b01e5d97efbc8f5cbd4622e77939aeafa3171f7b22ce14bd95abc88475b04208e1c501898f7dfd5abdfef3facfa1e4ceaff721d7b8e34e3a7f923b879db73c5651fce7eef3f97c3e5fe48054d1f3f7def570b3f04e38e1c42a87c29ebecf95eae4e5a8dfd95b14673c56bed2d99da8739ec11963b2cc15ceef50b651ce58638e5bad3f03bdba935cef3b7ed1a6fd681bfdbbbe0353f527753f95aa2569a453ebf32c6d57e4244a29b5ffe02d752f3af60f1f36a40d69438a7140ec5f95524aafcf2cb595524aed93481c0afbd1d7b73f33ff749a937efd194f34aa788b30a571a20ae4e2b479cd6b5ef4eb7ce7d13e7bdf81309db3779e97a373f633d391b5f2dd306bdf5aece29ce7dda831a39e30c291128742212aa59cbf01195d2929940f0abd29f745196594514679ea117c504a7cf1bd27455ac643f4944a2994653ae07b6f137dc760b99452cac5068484ef1659f6a43705934e0f493983421d8323654f7a49921c8af45cf77eb09856a4bc57e842213992a7184fa57c201793a6f84a514e099a82936d462241082184a4d1940fbeb43fe30f4eb9f75e3fcdc02994eb4e8feb307cef3d104ed33854bd543fbc25fee9745b923e9047ff87ffe934239d1234c55a2804a73cd6ca6341c2145ccd5bd5f07935c25a4b74f2fb203d49386998d9e0d9015a3cb15d0cb556f26ae1c00a9f27287800cc3e5e8e06bc1b386a6c814752c05dfbdc5083ab76f02a70c7b6e3e506bc1bef1b8efc51117e0f10e6a122b703e63883de6f408723f97d909a778326f2cb61b158285875af487e3c78ac1b2021774f89fc3e42f7f1e40784e95a08f9fd7b0bbc22f9a9a8620b2aaa2822f7803b5e0ee75047e0cf2bb9a9c882111a60ea09c5c0548d2d70035e8e97975c5278924413b9bbc8fd440826ef3329af0b46b6b0b37adf83ab5e98acdebf7edd839251f03b7b901e60eafd2809b8eb1f27f873f77c61897cd335bcc037b80abf67512fc030dbbcd57c2240306035dfe6ade43746dd3039547fa6812bc9a19a43e5e0565481055556f373904fada88211d66abee450ab1c664d0d0d8c6cc15a4d6ece401dbce5bddc5030c03e381a987a3f72029faec05d0fe9e0aa968213403708821146c0e267f51e7badfd7e2294395346f7d6d7bff6be6b9b421fb25fbbd52b8f3c90fdb1b452facf7b69e26b044812a070c8febe8fecda1d5e952cb9eed15e7517a05758e743f68f735e4ba594fe9ee79559fc6a7dba74f79841bfa0f4c10bceb75716deebde77037e852fc7db90e16e4a78800ff02a55f2e366c0c0c031773364f86f4386ec66341101d9d6abcb75b788eedd322eebe181910df88520cb173d00936edc5038689fbd0f8dcbdec7fd001001229487ac86ace0dd88dcc71b91ec033004dea1a9e4202b2dc3d90ef90e47598f0364fa76746f7c30ab172da2ba4a09bd682202e03ffaba255e5d0832fc776d4a628c2fc6582fff6b23721540c221196ebd8a5b67d8a3f432087876253c40eaee556a95e301c61bdb3694c46f9a880012d03c168bc5b25c774b277f86e0ca3503f19b65e721b9d60bbb3dbc4a95483f6e3e6c94ec109381a4f290957ddb990dd8db8d887d2094ec10938378013af9f3edb8d7f3f557d6a394e95b7ba392ebbd8749ede27880f1c10bca9ff9e0130c0c0c8c0ea7e65471393d3438524313193ecd8f20565d43c970c90e7e9428429ef229a144864f03c24022321c227736726755827c5a867efba068f695433d0e35afcfa1fec5095142e37fbd10f8f573b8fcafef15f68d037ced518f7a5c0efef69d43ad2c87a228c9e5005f7bc8a126d72b8d43510e85437dff1cae17714230214a68b0bf5e48f6f57bd54d1b540ef0fd9d1312937295c60999f196d55ce22df1b36d67eab44b7c0d06b8a70e866d311b9542bbc4bf1bf5a13f39ce8b0353f1e670f7084cc51741c192bb393075e9e4f8f52ec938ed121feb02b6432d233fde9b1c6fae4d0ecc02cca441a7e277968b1c2d955cbfbbac9f3ade9203612ecea441c35c233a157fea4cce4e9d9bfcde8b6c84fc22bf8f136749aed326ff7bc94d9db90361ba8b335bae3a0d4d9db9640a9d1c7f32994ea6cf8c328326941c27508e3e4ac8dd947271204c17738f4095c70805b92ac65b0a0025bf9753344c013c773327c7efe493fc5e42d13033a753f1df4b1fd96a991f57d57fdf49a025bc8f8f8b3710b9bef64adac73e07fb182744094d7dec855c6f650e587d8cc32b2117e785dc1cec2d35a2c017c755281aeefaf837c755281aeceb63f5669b38de12bfe1487e777172bc3a942e71158d9bf8172767478926eece93fb13af931caf4f8e374a8e4fa77c08d3cd9b1c3f06f1806b83f3c655f0e34f1c57f9c7bf7548849a0cf296890205acdc4cc81d451124596e31db11616a5e18134369bd2e78618c615c2bcc70b0b55ea311869d4ef674bab0d71b4dd95e190f1e589e32b2fc4ec64c4500663c9e6738b4b774dfe2190fda31c6181fac1a56b13cb953bbc43ff1f02bb31d32e311efcc6e20f2c30f35e321f37c4bafcd5ebbc4a7198f79ebcd765c1ef5412c462e66190ff9dda96e94e3cb76f080fda0d60dc937e38171bbc4af198f7a5d595ed97cab5d2cd3115fcb2280653cdeb5190e190f9ae7dbdbf7de7b1facdafc2ce3516bbbc4bf321e579e6fb17e18f42827ad9836bfdb45fee4ba5d2490fcf116b880ebfb63a60375030dbeb8ea2d32e32173b7cbf39025275999e46c28819dce6cfdc64d661b3ddb79e306b3ad53d27a6991e321da5cb30d267077ca5046cb78cbd80cffd432402dd319e21a53c01d2665e9d32eef65503664db467787628ca60b53b19fa444284bbe8cdd5cec68501bd6edf994f592ed48e5c9bddc6817f970f4e87c943eac957324a70915782bc70759ce12e87c2f3784b031e5ce7accc01857f96fa67ca365342a68604ad2bac11b604a3ead35c094a4b4bef7de7b134a94cd64ca95660b803ac094fc41e57d3759f975d326bf6b9b502e027c64e88273281ce0678faaefaf3d2a07d77e32aa7239c08c43d5ee02d5e02df4eb0fda45beb6d51c9d2c5149e067f38888a0e06a03e94d96afc100d7afdef2588fb5016f913fbc457edda80d4cb1da453ed6054cb9f91d65f50053320815b89b42320861aa04543dfa244bca244bda44964f59b41aa1388f75c91c2019be80b1b7330abda61559ce9f6d42993e30259f264a96564c2835c09403604a62994a7e249f263cc7c7496cf9e41a7c1aa8a3e080aa1a608bfc97e74b3669544c3a67a546284e969a10b07dac521625426d28eb118129f9d8267177caaad16395c855a8f9f087ace86357a12687c2c11f3e8d3fe46a94ccafc9b4770ef78fe66a36e02df29d1bb27a3fda1e11d8222fd73dd6cf63c9d0052cd43258967f5b2de3f3f7c755da5fa02c7ffab86a0af99cbc4614983eeafafa9495af8b3ec571d58c20cab28130dd2305ab88119a0361bac7899d26e80f8bc52262d55125b2844ba2c81d5d92e5bd61b158ac9592ee714209b9a34eb2fcabd2c782305d07c1c0203210f9c706a690ab28cb5be4539650963fa9641e3cff4e2eb28c2208289900b9a338c1122250d52bf9033cbfa3ac1c5f204c2acbbfd1327d87b62e5e2410931f8c90215090bbbbc72760e1eeeeefdd1f0c7277777fd009eeeefedefdc1a09463e1eeeeefdd1f7482bbbbbf777f30e8e45b7eef1cd86c80e10443407082e9e4eeeefea0bfbca4644099c224d540d6083dc001c282062b7e9afcc0841f2cfc0a2baca04b86d8e20774c934e2c509e610167362168bc5847e220a228a2cfa091672e8dda0062b904358c4ecdd8a570cc5245620c41016d08a2032bbb4e1592c1613681450c8c2b389f118db6a26fc30856d45b1c287a00d123ff810161ca837d91f761827f0a2de447998cf154f033308e2809c019a0102ea5694999f1925ec08d5fab28838734e0ae3902ca37c2f4a0f75ab5bddb2f1335048422199fd755fa46533174a8141708a14a11f30e5ad6fbd09b730fc8eb587303885ac0f7efd3652deaadeaaf19b6ef15d54fbfed1866b70cdba13df092998827433c0bb91e9785919e115f872bc6f39e3dd80bf9261348854cb7a906085beff4ad66366d9a5db4ab623c6c9a549c68c193330c6221950c6f8f098192854a6c31fa5047e10784abfbe96e9a8d38b0421a132633ea120191f1e43e3d2c874c8a711a10ae614f07b164cc1f7e1aa4724e6e51041df806f24c3578404313d20a182ca6e8500e0827d588d7999dd0d1b385c3a07cd0ca68bc321bc62f6309216308da189f98e22f3c5f96817f8329e70e0f018a39c979472d23aef9cb45e879130f4bbfbc63dbfa3b95faab2b7aafb5d4c7cd8e4e8a4f1532fcafd276ccaae84261c067e37b8787d7c5b7bbc7c454a331d947befd1cc06981fcd1ee059ce1cb9e8f6de6b617c342aa5ffdede2ccbbc87679a23d7ed12a3b7ccc36cccc831fdf8ea1ca77743d3321d32c74f017e6f5f06019a31eeb5cbe5f4bddbe5a2f91a9229b5004c69009e5e8e09f40df85cf416b9023077a70c7d6435c02fb398bd2a9d98d6edc2427d618618424f8838fd3c29c14a0ebe72f7444165e509515821c60dce72f744111405508f90136c2ad608133dc254caebc33065ec7eb8c1f7f0dd8010fae9c441f831c68f306523462812e93c29aaf2b8c654c419e6cc999ba8208bc6cff012e36409b9eb56110de5e727776d457effa3857eb044ee7aa8b9c8afbf60e56753e4e1f0c021564de4d7bd25f9ed3c272d1f202cc9ddfbe95e94fc9e152f28bf27f4b6c8efdf50778f4aab8bccea20115824b7e04d7e1027bfdcc123f97df6dd9962c81d8d9c7d4b911190b30c085fe4ac7584176161a4b5badd99b2b550b2b53c4ac816083ad9f20c91ed63990ecb841013d60ac3feca7460d70f7090afaf998e2b42181a32fdd622d39f0df3331201480139f6a08b2cffe4839e2c9dc4a00742c8a9dcada25818226715634f913b1ab9fa6325d9796085ec5a74ab6448c24512fd3ad3f172b2cdc14d9ac17b3b3c042035814f3eb9bb4f2c91e147f8950b19c22a5f1ba3178d1410ade27345c1cea0dae40a57ee08238736f1c21559ad7902eb373205d074c2a71d5572a20beac55d570976bcbb7a5defea79e2887c5dd77fcbb48d6c7202eecc8385e32dad839e7ab0e92473f74021945171c21d0d935ed755e969055894bb07899616aa8851052ee5ee41228b952b70cddd8344172c3040020c2fb00582650aacad20460bdf199880f095bb27092656aa6099bb270926a91514416f23d5a343448eb97ba04801106ce9866549cbc064ffe944abe264d232effde793ecddfc41f6c84d5f82c375a2dc4d9cecac76f19f4b5c354bd02dfe3e6f80907d0a2183bc25cb30b06490fbd77802b6d12e5d230bdccd25330773c9e441f60945f63945f61fb297ab94f068574dfa726c99f75d801c93af96ce597e5caa5dae4775818528a1813f5f883f7d213085e2020b5142331fbe10fafe42e6eb40e1e04f9f063e0a077ffa39f853ee6b72803fb99af8943b79cbf501b0c14aea77a7db52028316b8fbb7f182e3e5f0974d9ec8f267a8f9a226ca09c6b99ffcd0ca5dc534604a7e0d2f708c446cdbe81e2bc76a430039c678e54e002e43e788ef46fc7e3b816e89568b1ca190636721c7a8a3be0dd10ef6974386bef13e0ac0c78bab302e7ec3ef60b0df61ac5a0ee3e8c3971c1f67c3df079e8fd40ef9f43bf311e18bf045f822cd94b3d12e6f6e1c48e5897a9fe9fbd850f13bff80a9f82821e018988a2f391c3015e3e323f07b5a25868686a9ef3d3c7a9ea5e43a09c5cc91fb73023ccb2e98ecfbab67f9ed45bb6c118c4e41972a4d90b978392417bdf06ec0877205315e4b4772ee71f10b2e1c390261ecbc93869ee93e452ab005c63f9db0ac398ca7ea55a492a50b52208180b25ef1ba1b8afc3e7ebf6c75518b9bfedc0ac8fd09985940e7a8df1fd332d70be0e55bba2df777322e76e5877116fb2bb39ac28976e9dcc9bc9a22079dea1c9dbb1afd9da6ea55ffc040d931cf0b66768e8a5dff6df68df7d8766d31db02fac67b3a37b9750d457677526e528e3fe3452dba05be2cc1c98b0c6316329445e0cb22b07958d9bad991798f94bb6b28f2fb0ecb1d399998767933edf2debe7eefbdd754eb6af20ea25cedcc57b7c0efead5d5918cdcd5f702fec083a9b7c3abc2c3be6e9786b0c2aa3d55af7274ee025043d5ab8e25c3ef97c3bf8bb96122b79a6249a73a3bb79a8249dfe8dcc58cc78b5c94c2cae9fed7ac3fb99a547d09dd6760e39692e1035a4a760e8e9ee318af212796e49763e037e0064843861d25437fd005195e1f42c1c8e608fc61b15830707b6dbd6a57554a431fe36618c173beddea631ba5b15fb99ad4c38c067bcad5bc4da23bf56ace8f15364c71bdbd957db0475a378208a1116c0769fdb55d4fb99ad34501ec2b5783270ebdaa36090cffadaead5752fe5bd155c787eef069e1f4dc66d575cb87d0a0ca0b9224a144172748d282308e6457f52a5e197267fa0cbf32c1c9904afe0640c92fc655d85b960c810422a6c00410112b23d9c1a842f6ec1c9605f67f1dce8f068469dcb3832b32fc989ff043175a105cbc168bc57a41125a0467b158ac17b0581086879981e000b4eabb0576774aa03a60b158ac143c5b37d47dfb39609f714232214a68845c8f7d0ef52d27e47ebd5f5f085687acaeed7ef63437cb1ee350f1b11fb2aa11fde5509143e170fd7d9aebefc7c8d1d4c7b89afbf56bb0bf3a5bc16c87e4fefdcbf92064e6b5b71c2afe7b8bff9095684345ee0334f745f5643f02bdca5ec4d5d40c59fdcc38ac71182260fefdecbb179bcbd51bbc250041e05ed9c7beeb9fd33664856d4356f1dfd6c3f363d5e8e02dfe18d783b7f87f2002bdda80b7f8dfeeb1309bf36e7270726eee635bf6f2eddb24c7c3dbe1b5bcc51f661fa0c9fe723535f72ff796788b7fc6b5b744a05744de8eb7f8dfeeb5be57d77fc6eff63853bbcc9f81bdaadde450f15faf3a8b3f24ac37e82f247063be1fa6702359c032039620c0229e09806841952b209912c60a880cc9000a27145104224e2411e388151674d76320ca056d82828924a61d0c73f7308104de3051034c05258ca042138200aa02aaa087892586d4808828941c810428434fac5e7a98980112375e108adc048188255e2044b5e08b20a400082a051121504a8530450d32f54c83500449a6d78a523a312b5e2b91bb140ccd438a3451e209282ae041184fc014f41cc1886bcbddb36449ce72f71c21055ca8ab2e0605ddfac2550ebf1fbf1c9d7594a0d2c50a6e6e822a56573471d36405b759822756075072456b65fdfa5603a38427a6ac7cc85880822c56fe70abb96e563e94bcb072ae2102ba1b5b6add90dc0790016b6805b75ef900dfbb918214a20a99c362b18ad41e02104be4f7866013850a0ebd53eeec61a027c90f1704dc3d49867a9204f5e804f5e8007151e0ee49d2ead119ea49b2336be84ff9f52b57537dc8aa9f6ea52cf07b3b3714cd094f0e15815ea5bce5fd0f59ad66af6a6e0833d0b76f97f7a2c0044f8c5cacefdb25fa452f777f1a6794f163ac10098e02dc7d8630f03bffc782a9ce10003290f05f8410be28d42e2f59b0415a32aed0f1dfbbff75f27230e4e07bb76c8530d2070032902ec307bf7ff02b9462bb65bc933f9918d9596aee56c5c7ded22bd2344b94544717c64456bb5976352bc2f035aa245a9a26b912593c067c322e94719aa1751106bebb95228798e1315e19f99463e4ba9d668604b3e4955cb75a3324bd047e0f638c31c61863ac5baf628c91e5c61823ee4167d6830440e20e13acedadf322b91fe640f71b2fe24770dc26ca1642528280a24809fa893a443963daa5bf27bdea65319bf5ed4c13dd28d916b95f13d2444122a0dc8fa3e4eea0dc3fedd20f8fc01c7804360175208e942fa870312424858ba1a0d7446ce239793acd45e4e211792c1d68803fd0b7aea1c4f483d03d46d90fbac728e58c724e4a6bbd68bd2e0cb3f6661966efcd324d133d91e83e3c22b5698525c63b957084a96e977e4fa85d7a06202c4529b70f84c9febd6cbdf7bef39dfcde4322bff7de8568b58c0d976559267de411a216f1671228f20a5a457fdc7d5aa66568951d4a250718d7d1a12417d751212468100c28d00a7257ab8d157991e177f2081949ee342364f8dd4ca2fdb40b7cb9b54f1528a8f06028074249821c0920528e9c884a8d9c49caf4c7551a0cba053e124049827220c4832187824a869a4f95ec36b9b33736a32c390d470615f6c69f37fe698115f555cdc8bdc9c8a71c63b2e4954c379981a06636d43c4dd9d6eb5b29dbebfa46ca16bbbe8db2b5d7379c45d96a5ab6daf52dcb56747dbbd9e2eb9bcd58b6a54baf6c4bd7b79a69b62bd7b729b38d614fd737cf5686a5118365c55422594c066a86e69104b2064ae858f4443992e74f23d8e4eef9a1f293bbe767895c0384a917f69d49c1356e822a56353e9496b012bd0f2626564ae8c3afc155353d052bc0b859f9d0549ce00a595889382540b006d40efbf910074cc55801633fb11b0361b4ee478eef471bde44db7cb9759eefcd779f2f1cca7e678d8b8129f882b2ffb2e55af8b3174c90b5c818178383060fb00f0bdc006138d47be750ef6b7e9a4d46d5eaa659c30bfc12830309f85fb0dc3d5ed842766e949b18bdc4b880a11a6e906228c3a7b140909b0d2c4162255b36d8428efe1a0061502798820f1f7c016c201b0f678030efb124d82e42df39bfdcafcb2ff71a6bacb1c6e831fe2584564e40ee9e254359084b5e204c8f10749a8697bba5888206ac55f604c3cf7c30f699147806cec0792d0d28b8c35b77cbc5c3bf12310515a090f3552fce3b0e74cec5c9a5608a7b1b30051f2505fedcbd6458c505f9bec099c43ab6323aec861042086104c3c6dd1dbe0803101edddddddd3d76bffbd790027bf7cb9ce6b62c67bf1b70da80c65869ad58158a559918fd028b36de228f653a2215c25efac58def350c45b0ac47d643489639661aa546bed0e695d980d9c01294207ef428c39033d2d823c26043c3a0d1ddbd6b2cc1a1d70b634118d7c48060db46a7c99f6cddee6057b98afecc744b3deb33242fdec27297f347108dd329e65aa1ff641ce46a723ac010c450f5dc7fc38f09bf1c30bf1fca2f1bca1f78b9eba1201dda053e0c44e0f72ea5db009b0b19fe53e9f03ef054f3dfe4e88379dfb517320ca2522e293428f7cbd1746a3f57135e6bbd6aad41b0c9b5d67a82a91a96c0722eb0f860072729785896b0c4142cac25a29ca658e20a162996a0428f0d5858b061fdadb5d6ad5f7df5755dfd727c607e900c532d03e3bf1c91fbc1b5cce332d02eb21a81bb972c8568980a5312c6764abe0e41a26d198a835ca50d59c99ff11a1c70038d8faca5ac3a5608f3f96a91a56c2fb2c440b7c87f32d37161f6a35de48b6e805fec3c63d6b65ecd47a487c7c2ae2d880ef61511a253f2e5b36917c9eaa01a27c0f187f27c379509db42fd9646153846133e99e8a95611d6c2f1726f96699a4884311e9148a592c9645a596161b931564ea793e9543a9d4e27d26984455a764f278b9dae4a4fa7d394a7e827f802e06e8a72ea3011a9d785597bafa68944261a3ab06934229144a552492b65b7542a956474587a2d03469771d259b76beb1e2bf763fd45eeb727d385ddeede321a62705cad3711ee6d84e3d4a45189642aad985856629c64c890212393712d2643c62543860c19f0c120e59c94d6938e8aaf0bc36270bcd029af99e9b0344ed78ba39c5ef6bd776d7077e5bbd1f7d280461231ded3cf3b610c536d6928c12f8bef3d2b05bea34aa71c89ae2b8561190f6b7196699a4824c2d8768f4624928907ccb55dfaaf487b8269608db8b329f083e27345f0796c970c6b1efb42d741a30afc383c228974981ed5ba21b9669a2612617c7b1b8d485ba9756897fe718069e5f6c6727b8be180978e713ac990717b9bb17533e47e54c603e654bb74a368d00000006ad4800186ff6dbbbd71edd26fdb548a21861848232cd262c862b8168b21861862882106f860d0445324a38be01369b9bb808c14b66d74a2a11cdfbef7b5627c3a3dc4e997de010061f8c3165c80cfd56ac11fdc6a992cc7873f1e1fe240a01fb893e34700ae208eb7441858b8bb6a6481453e2df35e164dd1aa2a32428e2226e42882c2cf90010c3c87f2fb1b5c75b5bc257e05c241574b2868287e0d9e72159c41d7f84f9c7090e34324d33e91cfcbe11cfc7937a86819c9c123b44bfcf816ee601e6a96ab1d2225b88b99cbdd2eab96dcc6c2fe10cea055f8630791e4d8411ce4d8c12772fcc7a2e22acc897c6aa471c6aa717d8a7fb55c05a9887c5c057fbc257e4e0ae76f4e6e835fae56c5dd29fecb70f0d5e5e090b7c407001838fe6b799c43ddf2fee52595fa3f9d5a934a65e5f794ebf0a337f9898628cc7a602384e9cb3ebbcd8f3254a1260883719586c5efbdad1bfc396f9e3be4cf9e819b0e537e0fb721d883526cdb8847c8874fbec48ea04104f6fc5eceaf10e6aa33c84aa98c2ebbbb65942ea5948e1d41b31e5777cfe9515e29a574bfde7da194524a9925c1ef5d4a296b4881df9780c892482965372443931035d259658cb24e16d02dd459a74e9475eac428e725a59cb4ce48abce8c71d27aba989576c3a494928b33d2aa2367ac5f985f787d637ecf7f42b2fc37636c5540a7caf966dfa83d403982ecffe253b7f75e1d0be9b3af53b5c6abfea573b9b3e0aaf11fa5b99feb3c0bf5c37b8fd2fa4332be80a08f49b9b0412348fd189ad128d3217f94f180b0c68b4e1f4120d0bf1a4343821189442a5da652a964aa2b2653a6c3dfb4127d7efbf016f831d87b96e501f4eb938020a1b22269150a92f1e13136fe67c802a3b5a8b4ac7775412a15a21900000000b315002030100a08c58291248ce34cd43e14800f829e466e4c9ac8e324885114848831c410620c21068021233343b40d0077d64a4f079db0f91f8544b67312cf262598887bbf777eede8a45700415fa3016916e7a08272cb2cc8fa578a3af3a2e298a4bdf4c6a79e7246ca4f66e6d6e8473e1c5509f6b5dcd907445af4991c07c6752204e26a3356c24c30bb2b6587abee45b12f46a2a8c22ccdfbac5814b392d9419ec16d5cfecd66c026505604792b4fa34635394131013c11d987bbf66bd94ec69c9e84d18f7edcf58af296a8b4ff7404f83f8d6121746a46b2ee60c762d98b34e0a8b4bd468c74fa0516911c16719ec816812a007f518811941cc655edd609e1162d4e26a27ef70fc5a47129132887889a94384b50b125abd68be210e1bab6810c072bb371c033c5642dcd9a11dd800d57b1e8a7b398c6e5a469fae46dd69d3e53140d55ac71151aaee7f0a83f36d607e2800037101fc6e8ebdbc40d6922d82c276e8e9002acf35c604503e7b3755eb3ae0f68826c29c63f10403b6f593c39b9d2ef11f2c44a62bc22871ea5ca94b3595852d0847e37754979a3531d5712148fdf53d4b017e472dfcf8845b60bb27d51ed446927423b11da8b6847443b22da11d18e88f622b517a5bd08ed45b42ba23d51ed8a601db21b7c29babe950fb821901c1459768558a0372b9976d29dc840a79a4b4e5268b5b4122f09a10e45809a904ded3b5057ff22667d0fc4e6f990e0fafd3764bf276b37efb036125d35204fd574f8c81fc369ca0c3e80d5c8d94e3e30994340e61d68444e418094f2ed370ef180c86824fde8fbae55908d52667f2bb4131d0192d1143bbce5fee81cd0d4400af8fdc12bbefdcca2c10042b4d6b9ebe7b45c505c9a31248d184f08a4282003cac46ef722ae22e4112d6feb9de52c1493c34fecfa938a9d4ffc2e546ec029b3dafa3b5cd4564d44314d62408e5bd88a9d3b1dffbddd5624ecd08921d742b94594cb5a16d4c261470f7cc7e412dedc7793e14323fc6702592749c0b6bac9c141fad5faaf51400f6bcb43530d4b57a99d95d9ca7716747acb0217b3f2b44154fa29dee0c0bcbcf876136d189250b82ce06e6ef1ca631aeb5b8746595721b14e438866ca2e9fb35985e1c7568d67b65a599ca6a5ad930b2bec9be1a070d0d67998a0b26a48cc6b4df2ace8345beb57f67157785990e96dc06568f307fc43c69af02748f416e8132cd9448547477aa7905fed5102dda515d7a1d7174355737f58bf28f4ca4baa5ec2b3cdd6d688670d526c68eb7549d5ab7cb210977fa98a6f233133a33a9810c34e4b902ba0cef490a1ad9c867da81a1795a1bf3d2a58688a506203c90929f199e3541dc56cf3fcaaf2a141d60a42ea6655a580eef996a1926816150b66727c5d149a715c074681f41cb6992a375bd90af731649cdfba7a0c94ec909257543c984621be9b59715d55c5f672c263fcb32052bda580e89472a5259ba32a0f5c9dad0857c9053e57a26bae7978b612ba92d4a65c8aea8d8bb311e74da59ad605ff92880832bf56f992ea3e7025fe4afd5e10d7e1fe7bbcfb970e8bcf37d056767115044a2a00e6b04f1ccc3757fd9a436fd161f8c184b67a73c6bc98fa13e4a17a3bee826df3cfb74a1db4041b081eda2a9a5e8686ad6a7f0660498ab6f99c1a2c825e0f6d9d697a8001486121dae83da2746e7ec419561f320206537fe5c0fe794ff08cc6f40e7420782c2cce80fe90bec944f85a698ef991264375d45dd0ff968730f2bdc611b788b4aba41e768a53158052e3f5f7c01e24734778203c012657ad9942796392f849449853f23d1e7dd71b8ca2ef7a6542933322091b88fbdec47bcbd70074a171b75fd7d201f058a6bd81aae35f2d083b9a2f78f7150b193d40be658eb5ccce6c993db327b649a142bf91b77d4bead6f4d48fd1b411247e0fed97ff77bf86bfd2b9d7438fe46771b1987b2be50326a2ec500db3cf17df6f8b59306986ef5e756c7fd7964bb397ad2066bd5340021407261059c33fdb84b6052a07e817725919efc0afca7501954ce372939157b02dd298069b112f18b081feb971891500f8f04b3cb34353858e830ae3b0b0e6a0555bd1cd0caba8423e282e7872f785ef17ddc515d559ba9d551209ec0f32a956edef5052082fb6db5a158205bb4e7ebb47718febdd108b941df709fd5b9caba8b24a788a4eafaefe0967f863a721ab1a16e430a742c817686c6018e07b030f61bf6cef0742e5882df15a182e3e379d4aa42984b69784ae5fb7f116adacf41f6701ea93df6850fb8d73a5cdb437896f25b7fa0b668cb8ed927d43d923a65f7ef01a93b2dc5793fb1af3331eb8011ca6b770fa6c0abdc5f0684cfaf2d634d95bcc8c1871a641141e87db24a3c38e9bdd7aefd7f075872dc026f4a3b773cd2cada1a3845a3742c9a89b9df42dba949bdca0c3357a6373da1669f0c8591df1688913638b4e582ef0c0a93c8835ff56c7e84bac9c9ee5b8b61df342ebf38d92870ba3ce2d8cb2e212ada93aa707e84328ee56220fdd0858a80765b9b8bf58d4fed695b2c2df320053c5c46ea916b8d4bc05f4c526965800314fcaba4946c5fb6c5db0864497a125a5a72b7a8459e7e63ae4d35cefa728ee7748006b07e2628fafd6aad722ba37ad98876f8c9eb6a13401232e52d079a52c247dc9b356bce633809afe4869d22553c8c3ec48daeed22483efb95e1f97ae17eb4bafec92daa0599b9968db8bc6e5d3c5be63fd8ccbdb7dd28dde57bafc4ab7775ba7bd9bb33b2beb4ee07805227fb85f939326cd1596f2c3ab6acfe46d88a62ae5f2ca128899427fc40ec099e25472b073c89c2415f0206ada001079fcbd18b454845931e29bfbeb3e61ae5a48411d2dae5151c850b4789492b44c78757bbff19c0825534ade9d428002116a087d141354b8575c3b41564a0481edbcca625c729226f0818ccca47c1ff4d45e99d53580fc8d490b34f45c0a3eb9d221143092295c1dee4f48477145ea1b79b034f0fae6218c13a6a2b9cb30c4134c9cc6f142d57b34136cce843315ac17da05a892ec3388a8a75421d8f4306349a97009b453dd8a1da46096fe562cca804fa2b2f616f75bc78500dc0744a7cb12900442c4b89cf90bd394d3ab77ee9141f4bf031ffd49c8b57f88156df59fb09a3ab106a97c36297548bc20c831ba07000a406d968b0f178eacd515fb13e614c5c0a9d1642514d2994d6345ff33a073ea7fc769db82905e2bbb987e0e021f3a7dbfd5b4e8e95eafcc1dd9203ae4b1ea3ec47d464b4655c9c5db5d57bcb0044858760f8bb1e5f0dbc6719f1bd3e7922109688980ea6ac525c7fe55f4caaec3c88f18c26eb7886bbb108d2a0805a0164e45854f239887a42ac9e4b99ffb091a83a47752c11290acd949b8fe9b1108e16dfc455c5195e216fde382cadd94b3f4a7343406f6a842416fc264c67131f38cad522714e499fd3a2439143d0393f36b2a091793211321b5fb8d3e95fc9cc1503b56f1073616896aacf36a9b47fba0d059089e5eb140076b374a011c163a22212e5bb38e0fd8b04414239d3c16daeea9f55566d07e588535abbf35493183520cb3eb8113fc6cf982268bca3cf78fbd9ab2ac2c622f2bb6df7ebc3339a5a882e35dd3be6df148439ce08a282963aacef9ceaf52a6120a874d60993515e1f119200982f8052d88057c647a2b48236edf6b3aea46128df66b2030878b3a50c453556601fd79d6b88ffa955d597ccc789acf550bd05b197a8c55346e7be4474b7dcd3662725d7b90ee1c4ffd417b469d84c72a1277d0966b1cbe4acacd7a7a6f5b2cf4f519cf20d326cebc1836370acb82885e61eb6439f956ac9977ded0ff79c562ba0094743a63e06e780a59f603800dddca6514fd3689d7eac7b20d243de5d845c0cb4bc5003775818d66bba14342d2ef25844ea24adc8da98450b25af2d197a32d202ad228def2627189dc3c301428d8997d9a213a32a3c582646ec9eb721aa6efad02754728fe13d6bf9d4429faaefdb9fadd6fe3b190ee3ac27ae8100dc2fe211c74328bf17c76e3189f3d822c81f6619995aef0d863c35a6c293a05825c9d3996a47a88e7dc27c31ba9ebc093cfe40538c2a58372f2069c4027d8022c0050ef4d17c48a4a19e15a4aac4634521afc466554a3f3aa4cde8d1e5a46e96aa98b4273e76f80b5fa6aaf0e607a3a2a5866d757c83d2a841e7f92e995fb38d25bf20688222bb9e4e51cbc8b1632ca0643ec956262b98ae11e09347d5e15689ccfbe11f4cd504ee9f6dcca174addddbc287a102cd7906077eab9c0572a98810b511beccee5c0ade00968313ed7febfa526c01686adbefda66c165bd8bb55fc7fca6c2a8aa25c97cef26ae6e0f8a4582e99056eb01903bd10e7063471c734ea9e02998a89eabe73661f6cb6de00680e70b299ba2d87d05b4a0a704a592624a1df75600b4a4fe82ee8016d7ecb5383b66ac4a099101d62b821806804cd2a383ec9fa37c2cbdb76dfe2aa13e44de35bca0e872156c281b6d1eb06be11e37781bdefb55fcf1d40eed24515da02db0dbe9226eb769c3bbec7da67104665fbb340e3a707d911e68133e9c290d3b86e6ea5b54ee4cf11cacf38918cb7804441564301c19854c48cee452930219ab42942ff26e2358a5315cfa88610cdcbcd3b5874bb65e09ec4a3d3f1d0fad6edc663073ae443475fec7ac64ec40d348278b7da7abd7ebfb9b880d5e581ada89636b4731f477b59d48450254d043808a403586ee27e00b476c9fff69dbfc16375ba984864409169b6e1186bea980a3fc8470364382b277e4f40e77d87f908f71e937acdbfcbef62b8a1c686618c801889f5af8b9dfff4313899aa33a10272bdcb28fc00f8bb35cf64412728349b41f8196f09d2266dd1368b9263e4049a859760878bd9cde6f6157d3a83c5dd1523e565b568be81de6747be378ed47df837e2581f962975482ac7799afe750724bacffebd48fdb8050a57b60cdc615f1f6386af1523f78106c2bea033a6d5b281e51158c4733cb3ee60505dd71f8edc2d79099416fbbfb42e748ae8a11ddad38d0cd7f87fc0529c6a241cb7f773a37a3af2c6dac88382b78344aa3fa77f26f20e16b1889f91c3201b5d4ae5083d36be30de678a590b22b54c9548550cf491446e258b1da4c402a86197a7f45b7bae1d90cb6b3de9341aedaf1252acae2a6e456fb62e85279e00d73da672659384768688bcd0f0368a57458e313a12140c0faaaa3768aebefb3e7a04048c662c78566a290c361e8e5d4c63302f7222e9a611cddd8f546f30603e2a3c3a9a571bab5429435535c123d572faaa1704c915ce1416dc3a52dd466a5ef6dde1a8e64ba0428aa77574971f6fcb2412f6378ab57652bdf2c270d2f7a91a9a49b83149835c9f2950c5a907e275ba4110c01bfddb60f872ce3c45350e6ceb4774c9303aa67223bda7192e91e1cb6e93c5c44fcb644c91ef855c6072322544dc8d512531a290c022720439dbb9ab53aa7a7ef9eb4e5b209009cdc16b577ee9cef8745d1782757478bd949de0decf0ae41318c608b20c26229915b8ac631040fb09e32c3ae5a44a78c42db6dd377d262d17cfdfd65267ae0aa50cd6323e95c2e3f0c3e271f6390a54a1d87fa2d5bff79ec779c8f4a78f05c8059045c0d57faabb51c1689fc59cdac44c48f382ce28a5aa42583270918cdb93c3fd60c3d796d180a51badb42180a7f07c06504171bc4aa19f3b912da3a71289602a178373cab7a67f45c0d4ba941c61bf5212306a51b14e9a291c3c86149c310bb167307f42417cc2646d5ff507d0509397b8ba24a42c0343ee6df2cb4d3e7eadce603da0e27dfeba542c59e54b5c0bcb83aea82ae3e1c725e17a217adf66d4077174fdec9bd656a608a3119912fd3372af110b5712b1f002fc2f420a95f64c087d4a1a58680fba147f8024da8a12754378b270e5db8771d6b387990b4a2da01af7303bc04d15defcb956efd36e300e69bc0b14341f0c222b946a553e485ae1b473068cb8307fac1007f784792994222ed287919957825740061ce8e919f225767d56dd6c0130701c59713c5bb0e0460df1eea3ceab9122110af474099e98c52e91a0acf4d500b8d3d67c14f28b0cdfb2514a68c3c6382d4a122f9df0f6b317e31c3653715d3d601ab6ea48155815d13a9b3fb94586904c4196c30b382d9ae6e927febf93902d383dea37f09ace25c71f8741bfa8469e8a62ed9952e048f56322972bdf2797a89a3bc4ba4f9fb6ce317012d4f7d1fcce48c1ecf2fe8e82a4b2b4b9bae2fe5f8508ae14b0c005ede9e45b7ee5c711e9761cfd36c65e71623ab6cb8c82b9f95e8777c680d71a910bbcf9e0ca7260b03a8341f713ea352035e02edd938098eee492e433e629140c47208c21ca180b784303addef7f265d17c49cd23a1b954bca3aece66987623de66c7af4859c27df6cda358ef67f1c94b24e7d5f5a8f8cc2574d05e632188fd4c130a546a185c6edae2ab99fbde0c34164befb63377beee6ff451d4101c2b6aab6f649874f02bbde67f9eec3068397b2c24d9e49fc25c541be84ea6f2a54b356a4866bac3d377d565e43b453a97bc1e4fdd87788fde2f61a78a097e53af73c4bdb225d762e68652ab010375a1e3a264e1f45965c35df78335a1052bde5a4d4c223bd478a4c74bd027ab13474de89cdbdccb72cd29eb4af3a815e252c41f637b7876ac848486f7c414e0ee02fc6ebe46218d1a1e3b7dcd16f55d439cad886629050ad881f142aa315a1cb4f00903fe035ddbf4bbbe8178807fdf8ee5977deb5c622f2bdcf95e9a5ad8ff2b0dbe95c6c44e19a1968faa70b4b55910a33acfd9967f292fbc12e7a9702f593eb6126b4a21a4a3ca0cf17af7b50ae52ac6b5fb1d145240fbf1aba86cfcdf7590621551a1b96f50d22698ad358a33ecd82960123e29076f0bddca0fb1a455c2641b9ad7908a0a2d367f509b608da7e971904160fabd5410a715c51bab6e1e29b5642723c056bb101f8d4cc84f49953620badf67a28e7d002b65725db148cfd11c103c820e2e2ad973ec63bfa25504606c6542108245199b80afb404bb1219d1ebc2034ebe70e5e29d8335fef67148cf40244c84ba87bd63cb77a4130d2574065b149891ef6171f2f3376ee74bcd6cd695aeb19b33ee95c1f30791ab750bd35807e040f3cbd6a18c7b9822c5be6e8b65b1c35bf3fc55816315ec66d55c5ab0b4600709630bc3a64f534d081ee1ac644b8572c209e3c7ee9f6b74748db0598d9b68452c7bab439b41fe1b0ad964c432ac86386d64940e48b54761ad4aa378704d0397ff689248ee60d311ef9a5d94002521355f482ac101d5035aaf43391e633810cad71910c8eedc32c11d457091240281a42e7568e5c4fe0a722502f92abf2eb03ad412ceb1eab6205c94e61a9c8c911a0cdd0c3252d5f4860676f40f742d1a42f414b7accc9d503375091b4c550fb90afc6545779685d4f566eb8915a12535b2b91c8358e463a36067766f62666c7e6dee029b7975cc287f777bb9a3336738d9f40dc73a596c39288d382bdf21a537cfeff08c134774c7fef10e89e985e98b47651fea48d223196806ceb9e35930767d9bf31711a276a9d0e7be544797082a1b322720384390dd10f53e043659492e0e7d337f0bc463c0c169ad519a0f884d4328e12455f80a46f69a2ad7df2a0519fcbab37d1a90153c8a0cbb78461ed0bfb34f129306d36603a6218c7b24a3cc2cb1ba066ebd9aaf18b30d4b1619948554d5b600271e57e58f7318c4c4af020911ead08f84adfb124048d56efcb0c2e477072b8976833d4e5847e5e29c63b6bbca9e36eab0a3964d82df21c9d77db766cfdeeb56c2cc2fcc5ccccba6c1899c89d7ca7fea3d9d6d312ac58868ef5a96f38d3db733f9d48aebc99b950d9404c01b1a672e8593ddaa4714e73e5c57e8ff9fc98e9bf47f94c6334a96f7dc50ee900ccf911d68032759568611e34e01bfdda1f6739c73e45dcf6ba1ebe74d5dff1f63effbb445d36efc5415e8c71a4ed42270714014394dd2f82ab31e1fa3e0a1c373fe5e5675aa0abb82633ce28d3b93e20a4756e945b553cc99e7678d1bd6f0b6ad780bd84fcbe223ab16344eff444bc99b3939bd510d4a3765aa3d8efd504e5a9f273894a60b4852e9f1aaa0fd0abac166304508e73b5361b12762c0fb5d069c914164723623cfb744639869138a5e1871ab6293eae6514a70164a7c51e48c472f990ec72cdf623ca65704daf45322265842259b1d1450faf0aaa856943d0c1a12cdb575ebb4ed74d555377f444b12ba0078d7a5db2cb8d2ddd7d44ec1be0d9aca03e74d1809c18b2912f8f6c2fe8fa123b86a8f6a28c5a76e0416f2aa8d842b159f59d65a56293fb831a642bc5a6f9931bf07baa7b7e8418d4e6ecbe9890fd984cbba7bec5f06238304dce4e0ba1232e4a96b036d578a39ab08a24718fc50cf7662906ad7eddbbdb187904c088016751a635d5807407482a241f2953f9c6792b48b0b26df70fb8d831070ad80220d945e122af1464063576114e7e62abf9079433dda3166f4baa2f7aa7bee853fcf47493331f13348c2066b1e05ee84815f4f3b9b2cd2cc959cc2d7d0843a92c4d4bf7e68cd34f45a3e52a9929602d836e230bbcd800ee0de63c3bbf3c48d58ef4fe42229ad46a59b9185b1394bc52a437540f18813c309478fd95a14c1c627f989073169e7e37ded54e24e9eedd66961c6a9b5c2ea1ec6337677301f0e0e5c5c7aeac1efc48320649ba8e11f3cf76b4ff52426689bf72726e29ec1a9f2a71dca1ecdea46e1df87e6942c731213253d84d0b3ca932be3989f07a29a531fca2b9ff19993fea0c655ab56cc269b1b79f681d25890c4885895db0614626da3f5caf67969307f9ac09e8b98a529b20eb3d82bd4f65c3a35d8e1766d3aff6fe35f89ee52d895104f00a639bca2990d1c6fa09831090f217a034e809d8e912fc392a951c03298dfb716d1963a619a5e558775283df50cfd2c21c430a36ef6e78487d366bb0480c720d45d2436857cc5e464ab4e2eef5ab5ab48d8364a519e54415d9bada636a55cb4740b55bdc002f1dafb4a6e0775591c7646110e8eb2d39b403baacc0656f2318e57ede63beb2ec483e2632723d6a6249536caeb2a349c4d32a40e52ca8ab9753a8f4be9dfbd9079a0b48074df4e8d80c3f53ae56d734676302e33d0e2057141f45c5c3f97ba329e7194fc725c010a5a5c67a335a4a42f3400cef65c2e8dc8d2e1b101567b18def17064fff9a4069550fe34430db25b84543a06204400f21a7f756455632d07482010635b6733b1e269c285cf5fad4821067f36822e82bc020e6dcefed14a866c0f566fb9fe39ad3a1f918ae2ecae5ff621eda864c62d08c431f87b96442d60a28500ee93d7641d753677dd8e114cf7cff6ad06b3a740dd1d51ecafc58858f6f7e9503037823c83745ed22e018bbf93b13f361ca30dc2785b50f6001b1b5245262f147cc26a6af3a09e2e961e6492aa4ea71ff4be74a5a7457f842e1fab419543129da7fd820bc4b4c9c474c831d98d417e2760f50a53ad9224b21daa9157f9cb001913eaea9d3622cbba28c1e5d2813cb841037898b3c95d21310373169735da50ec8f5db11890f5b0485489768eab8d4e5e2da9cd3373b450a4f1f7a3f671a4ffe2f0a6e38bae2f1983d37550a7653a22867566dff232f80ab686a49617b1b4171bdec56b8531fedd8f61aedc497b5efec52334e2b38005b4f7f6ed55ed5347a7270248fdce697d856fbd354b16e0cf14fe953c4d25cd29549d8d08f07d5175e7ff3115b7a3ed567ce5f809b7eb73525b4f671a02a0348608260840a706bcb201a883dd107df7e868b2c14ebc3086d64760986f2f62bf54689b18e6906d3724b18f4c718f9aefe5c10ef50251b8b6223901edad2490fe96c2d297a7b298a92ecbc73c7c26eb233f972a348cf7041704c339eee4377c579238a6c3700500a7253f84d2a2de25498b5538a6d0be720a437e20f7eed96a949ffc2054bcc028d4188c239c4890c32e421398ce565e315aeb6332f8b70a3d3935953ea14f51caa53b0dc6538405e1f0848e0fe4f9dd0889889520eca906cdb93af448e31e695e2789c52437901a4abc8da545bedbc24acc8cd0d90eb8fb03ff6ed06cdb16c3b1f47266866f215de6ba993e1458f8c7b027b47214e4b1ae2a32746eb4dda24bcd2b0fc6dec592896aa7b15fe3e16fe5b2ccf372479ecda77c0b8785d74370ed13c6066fa7d0e29957ad8d7c1258fabdadb951ba13f3d201ac526704f2899d6872d33c084d6cc15004842b919bbd8f4d37ed88f3f8bf9bede6ed7771f98e426d1a0ac281f7ad1df531db9ce52ee7dc0e2dda55e4386129108e4fa1d4b9fca2f351240eb52a70d6a2c4f09fd1de9d4aa71169fd4f87cd6fc551efd3848454744680982f57f2f91f7510e593718a5c1f0cc9efdffaae6d5259f0ad3a95f1bc2368309b21be7636d72c87db467a6d921b0490465306a4e2d4173d3470556eb9e82dad963dfb71d1d054ffabf3403c63c9da18d695062531cb08ab680d99085068ee4dbe07d635203718ab9158acf05826c171639464fe4563390c42abc1b140dafd09add2d03e3c5f52bd9d6bb791b544920624f85821d4e7e9c76de48285c0782e3018768e748257df64fb3926d1986e65a9ed7cf50a08aa273e9708ba08c198d98afc1c1a742aa00c7f768446e776a9c22abb8b1d11d4fcc284436e2c7a74219359559dd354ae66061619be29c00d0a1e65c0c39d0232031312bca682bb28fd5341c29d02289741cdf78af34f2781e260965781fe281ebacc503dbee6819cf0853e7f97ab08728f2831d658c6a696ca805b78b12668532ad44fcbf25252f9822ce573a36c83bc796a755776cef4e3dce54585210a322bf394da6fe3972ae035c1a3451aaca8ec09ab44776ab52c06149da0e25aba4b5b7e1a89ecbff5d385cd2b5ff2ebaa9c1e2906fc23b098691ef9ce3e1a6f74df1065c70596f08a47bdecfa038f34ba386e5949fc529334bac1900a6222db382c60371059c6a1fa69802d5c78df283d49d9b3629659d6cc10bf6a201c92f0738d2955d2d5b8b969012d5ecb6a5461a34943f42746a8c51e70c761eb41b690a18985eed728ce0d1bc6d61bd4d83bf9da55dbd35203bb7ab7f60d8452bed8cac78d1581fccca71c78d892df644ddf0a4f863bdc89471026575f75e6d970d861d635bb46ef3c965975083652a7eb47a45edb7c8dcc2b6ad681b4b00a7bd14718d187c7f886769f196b8a59cd009e3130336b4cda3e622e78b0b533fe3208d4feae347743fa59550f0db97993ae6a854e5483ac84067ae604933928991f25db868e0a050955680a6e9d276b3b82607db81434e85af626422a78d994a4dcb4b9ec50e270b401237026041bf851dd783df19d749751606cda23ff3ca015460cff54957def7553410c99cefbd5a51026624c7c6fd5b5e420194912db5e233948dfbd72611916d8cb6161af01950b335885206df696ccee54d695272149de4a74cfad40e987d6c1e01e32e6e4c9a6aa80a1440888a609ac12e5185e8cf2e150f5013eca7ab44023a777349c9d33af38d7515ecddf29d73831fbd49519e45c78356c4c2917e1fb1537ee7d66134293a6f8da3fba98b268251fd34694a422346d7fb2c74180a717c248c12fdc5da484c2e6735a7b46659f12e0ce69f5a996d4979fd511ad418cd6d98919122ef74ea417719f41a0f7c6960c378bb298226df31f6483d61d5f5c2f4c4996e511e0ff10ebe40e6428c70e5bca148db25329ff4c6bee2d6e41233a5cb69d037b2d02b25b3d17467e006295697def04c1808e305ec43a3e239c8ff858eda84d810e8f4e59ea20db5315d735cfa1a8185e23e12535a81ccb13566f4c6a6edc189e5b5a797c2e01d070e2bdc973c12ac787c72c7450beffbd16da7d5c039c9237672467e9be969fa1561ed28eee734ffdfc936abde00d3c87fd704e770b8903bf7a5586eb24f7d4592d1749d218c669b06aa6b17f1e0d5cfcb90c12721154d6abe9c9a725700780ae60136b1a1fd2380d90f54c28834b2ff25680bf993a3087a3bf13376837b59ed111e64fec2d1157447f14af2bf136d15f36c0018ee1e3d6ee4bd04ee6410241a3222e621df5672922a462e1ef8c3e75ac3bfa042ba84fcd86ce67092ec52f25780ff8f1b77c62100f68354edc743fe3d93ec84f8aabff05fac007caec2724167bfc10af3056be6c57d4a687596e8ab962f592f3ab3800bace6efaf8bb518a0b9d39b4eea87e72fdf11ba00c538d28059d2cd19179ba7f10fe46f53b02f7e142c80395d3e33411920ab22fa9a33908948c680bdccc3fbaa153ae16e0a6122f305f65ea9361367d6a45ae06c0467d23d9d439d1fe60b8ee6eea05be13f69dc187ff2d719dddda5eca9da1fef5411252da44528295b4348ae09ab818dbb7b91de609fee048d593e2fbfd7da210e59998fb158edd5f0da746f81fc4c49cc174ed47a0ad016d6a028431a005665bf3e1b08a55d3b2189ed5b09a671d5f1babc1024aa281178dbe9d09f308ce1b0709275e9643b218b70846870e45e578095f3a5a98b0c036b55d720bdaea30deea29857544d3abe69811dbaa59e7c4323a191ce14bd0f2859918fa1a7c220c59d9341a86ad6d2de1eeea710035c4f1e8d26d759de4177e7c8e243be76ae9c1fb74abd1bfc973c69a16507a99a3386faea60ce71f238787c4257487231d25f51eb16651fd119b794dda1dcd4c562281d02e55d41216b526357b8abc9dd07cd757c201bf3d9ed519ad7944e3580f54d1a9677f6158f5503f241467c48a768dea2846b7fd84d7ad9ba17748d4a41d60dc0ca191b8e67295e665c7d065eaa268e1b650e468918b38753eefeadafe8d2c7173be4b947140a60efbf6e39becec82b17bd134dc04e39f44e453c34d0d64919842b2e3ed70923568f17e47dc03a9bd1302aa70fe57d1b1832a1618637c02610da7e8875d5b868d95b3dfe30c59102b21b9e2bcf3d59eb958a7508fe71b321db7158e3460b5dc941adc57c06cdd5de093acb6ac42162a8c9d0b33dc170ffe56d1f43e5e5270dbc054e353c108a10e5b7463fc6892c7c6dab08a3c8d2ea3477f32c7edfd32a9d5cb6e93aa50cf3a0c5bbcdf63e67509c551c2d3cc2d862e9b91b8c8e4b9ef534efd19791995af6b5e79f116152b5b2cdc3aa3da145e44c825d2d744fd8632149a035ac3cd264ea0da54344cf8331a164e12765d4a4b1bb84c09b1a446f313eb5ebdace5b444d3471f00d5fa763dc388b52aa0651afe30e2178575b8662f035cc597e9a192dbe8ee8a453813c037f9b47d85c865cd495e3b0c274a64cff3634fefdce33466745a4dac2214c8c99cfb71512399ad5e0088459e4513e0521d2038f4fb99e238ce4dbef1e39d1d1d3c8a377706d1ec610cf5e35953bb1eff81aa618059f3b6a02bcd6a6b46f838eb3eba1015366e350b59889f43f4b457c56300070acdaabe8c566ba719ff944825da349a9a8707ac0342492e7ed6405b2d6c0b990604e5f2744e4241e3f758b0e618d6292d1630e00a987077d5f04370f09380501a548e7b6d561d22c58569d1e7b3dfe14fbb274381ef598f6948981dd6af7a7a8e9602fdb3d410e22ccf9958fef0a21b367ab45f2bbe70b8756f03c052f0e48fcf88798f1ccf02d0d6954af17a8e2ba9e3738ae40c5fc49e40ff752fc6bc60de8614acb50c60b608c76cd6242c9235424f5c0c4d80146ee33feb9e2a6bc1c90a05f8fddb0e041433fa54f6a0f849f42cb3ebb7fe8c9a3d9a6b9f33ed165c860ec68dec99d6d69d5a41d3b04d55eaff386de685c1733389e6b18e3c9fea99c13587845165b93e63664444408362fa78219904eeca3a5ee3dd9dfbf5744794f470e19b0aae4084455c4ca3f5f178df98dfe03f539c57460e0e6fe114c680a694499d3c9080c815a0bd475e8d245521b996edcd74c038f04986e57ac64bffa8b1b6941357c625e1d6ed02bc0c0aceb651e9c9f7d327e2345e5d1fb8d1847a96fb1cbbe5c4dbfbed5a274520b2b20016681ee16da9bf8dfa346b1d501949ad7c036d8b3404c6ffae1ae5417ab5d85130735a27d6f4d777ef5148d4397983ebe254ff544241c4bbef7a4b81e8ebabd53678fc935cc4577fadc5047510d84db610cf33eaacbd0f0cc084a43b7ba9d03c9f4ae5d844cb49a748473ec24ddf76fcbf08fd9f660fe994939512f0297934261dc52cada33e469311bba996a3be74acc7bca88a9d295ce16ea705c4ec7506714e496f18023d354c0677a0fa48e7bdc1ba82f2776cd17ebfcd08b45a6b858001de8216a692ce2032e8b6ad653b37e75d0e3ee5c6e60ecec645a89205f359e459c32f5fce4905ba406a6727e2af8f001720234c31fa494a750d64f02f5df9f7d1e8cca15df4218d06183f52abb4308320336c454460ad26d79431680b537b3089f002cec3b3eed0e86b554532c351fe984ab9693517149627490057ac6f35691134c40816a603e911f548449f07246d3ec625fa090d3771c9750ec1e6449cd070f39210a4d01e4528a67410e6eb50d547c7051baf65349a3c7facb03d4a3df438747af30c86a73724d57d953ea72eaa37dae19401a464c9b4cd2640831c721de30d2565853796e3d2cf9b4f4f166d085439a9147b5871407493bd5d7c036a7fd984a5f4e6f22383b187239c342577c21bdd01801fe729e4fc98e017ef3f01b27fcb8185b141503a261bbb2ee1a3e8a2c8c6025db4d7f92497a462e2061010d5e9e97772a3227baba0520156f338e28509996ff2f3085db5457a0cd720477379792d7c706749dee74661ea7067b57cf604ff574595f78b7d8a2a8b5e85cecdb567b722b8067553e46bedffb9a125a7524c747181965f1d76bddbf46410d2622f94c9ad17e98fe291e750c00ead22e81d55b5cbd4d1e34ba340bae4fba7029f8db4b70ba0a22b70cee27c89ab815a7ead39c8372d67e744cb76ced28e36fea85d610deff9d3908f3ca95ad01e7f6505a921ce97a665e4a5c74e3f17212dcf20d708b6d60c4087686f3318903288946b6ebdcb0a156309c046e6b6ed9f55218e9d0d3626815aff4d6ca9d3a9f023a84305a8e604022234f31217b0f9ce7041faec48e9bc08997a32101f9a57f9e60536ecdbf5c5679bc5560e2041ff484bb3b595a5fad476472ae2f581b2d29e9ec61165da8c3a35cd1c084e9eab125c49f49f21bee247db6fcd6b3220bbf608cbfe84472dfdaa04d2f86c2cb53002834494025a9cc2c04a9490f01db4a2fd476c2520ba0e655f3b23618239d808c35f144041e34d97a1d106a3208f63c71f6ce1fd4aed0aa74192147e0f9894a08801dfc4526622339b833d75bcac17d5a18fb39e02ce73dda05e530c3fb66d5f869f0e590c60bdc5f7e9e046d061fd4ae7b0ac14a9faa0838041331e2a161a308ded94431b087ab8445d6e6b79481d59cb3193c34eace37fee4146380000801f89666488c4cec08764a5a169319f4de47f43480f816b19555e3bbd7fe5488f1ef9ca8e2cdcb675bd3c3870615d9c079ea0692b9c54331a148dcb02a8b22b57a737a949c357094ee0716e4faf4b097b8f9e63e257e1d811798fce0bb811df229f506a687f6dda17b7572e796c53b1f0b0edbdec96540a0b59f8850f3cfbfcbe63cbf66047c120320724863cd7cc82fd5e50f0d7c3555e409233507ccb546c6eb94452144263b5e306d04994f3dc8cc127ae489acfa17401868062f84d4a6f1756b786d340486e913baa0647751b12eaeaed10d645852750fc17e50b9c137c6ab06e4a315daba89f82a3e34524fb57c2b827d6c955313c37854b43f49477fd60ce8e578b5674ed91936c0975bca2f5ac4f2a118cd9c56c2896c16b2db1c6424589df6bf17e2bcf871d98c4e1258dbbe4ba7e4f094efe3d7d74fec623e0af634ebc968fbf062d44e36215c2c1bb469e5d1a02a4b1853687a1a49ac958e6029d25ac96c5d663d16e6eb32c77f13af960e1ebace2f11adf49bbf8890344b40637c9c8f68aabee8092fe5c83be75447cd553b522518a081a1b0285fb6a3afa34b169e2b9530b220d43491db4342d3f3671c21ff63583eff19efc824538296f9609b69da6b6416b130e2f6da4059ab4e395ca53836e3d49b7f00b4ed13bd87400f65a64a930f89f0d4f8c0ec88166e7e1b2b621beb442b83e035df163c00503b41ad5c829cb6c29c8fd4a5bf1c6ec13068a9d0c7dc6a3324b218b8317f5bc1722a2d017a71d5aa9014d9669e99711cfb93fb56bbf501bfdb388b9217eb8c6a71da915b5c51eeffa17b85d2b6f52c3d96360ac488d500ece238523a3564e7751b3955e1ef759b13c5d902aef948e1f57258d63c9b0307d2ab3c8b5143f98b016a3b19fce98646b4d89712c2c5cea7c50c1a2e2d20ed406fe67f095ca2a234098b52afdc16cf6f2984fe477db0965e02cfc889c4dfdf54b886523121283ee66b1fc159d5ec17eb1dd9106da6fb452fb805b965e2d736801b1c92533fe66cbae7a525b9cb9a29f9ce0d749e81d2f9ad077072eada138f52b5def1707e1b2f86e42298fd0ad4e7f3a79af0964c44357e0160512958264896e50e72689def06282a5ee4e9e36fc2cba58fe98d53faa57c1b68912083d9818e28ac7f566585e04e4ea16794c8556da27a6e38cb738c59d922499b44803c8a8e423075e64a146abb09c3caca0bb43b4e79aae10af0c179aa722362b26ae1c380fc24329b4777930c2160d00fd44be3e217798d1078a927fb96048a773baa0989ac6ec5a9c42e1ea632c7cbded88d37dd49d1d67d67797f739d201e7b37b846ab194145f2579793fcacdd8570d707174d5333e82af44b82b269fc8ea0a018642f680e4af4d9bcf097751fe077c36a8a1198f1dcf4852f7f64b1227f1a39d7f0761fe8696d833847bfac92906a268ee7f2aa838585480f259b9091f3bf0f798b0e33527388f2eda0396a80ab7acf0504af79ef824bce3c96ffc96d47a8636b52b41f9726672d59e7e47a2cfee63a52c1baac4f05561007a2a137c676161a60594696d21c8b6a73d84e5e79e8169e1fdd1a8100870fa7e86265253e80f3744510a9d6967c179322c6d8340061c943bc930c2e4600c47e00fc14c93b52dde1c2bcfc2a3106aaddaaf65875c6ba37058f276f84c1c273524a841162179be01cd96dca0c5b36766268167f6ca6033e60cda4489cecd2f6a88c69f17870050d792244094ff3eebd7913d88a4b0b1912b95533928990c0cda97f41338a22c6da65f52caccfa7146c9728c50a215e290240281b87552985bc3e231d0631ea5a8698570f9ad509f86b8d28ce72c4d6c8bd421e359aed931ae2ee7d653c6c7aeafe0e7627bc8dfe9013bf59c36752d296752932b6fab41b34291ab852eeab82296e644a5495b3cc192f9a01a1890b41ea42270c78536c82b0506c299d5a6a3c42303170b5e2e1d2b7286dcc3e6b927abef400603be17f6076e86555e5bbd69a8190a54a5724d10689dcae5f0d4e742629581342b0dbeb8173af574fa54fda6f549c52444470373cab342f688c3242a2f572f3478ca06e4ce5e415ccb2bc6fd8c8a7c36ab49687e9edd82aa7d4b049e3baaa7b8b9660e2d3f1ca2cc6154fe8fd367710a1599365bd1589d2c2833541140d0e8b83dfef8455d918a7f4bb57e51982d8189ec7247348ef1bc3649ac3c306669e1b520d01b469fc80e57d69e2fb94d4491f51be2c015e22646f6f339f7989736900088888e72cc707b904f79becf017f98c39ebff2d515ebf15e18f5959130dd0b45d05ce95afd52e4d9082f9ce6c738b516c98453a38c931c41859955f00f6005c46904e8bb599045b07412448f342d969524c07d3818068481316afba8e392dca657357250c700e22caab4705df36bf32b5d6fe8389b64a233874b4a0a5df4bf9ecbac4e77748cf49df4bbd455ed8ee618a62e27f74b5a9651ffa590c3d8bad6775ba62835b44b4a4f13ac90180a154d0155f53ce705bf77ad01a2bb2504e4ea7f97ead2514e7e66246a01b063dcbd1b59d10f8bfff352e718342880c819239a0c0f3590340821f81994ea5237a73c500df19b4fd3ce97c00eb1aae471033916364ea39a69398b561e2ffe00d96b5aa119e9ec3ebdeac50eee3ea1770ea6012230d7a34788c931c680bca45727e5c4ff8a5e27a636efbac512362a23f5ab9e01f9b1e7d4079e47eed0e8cc285055564d810d83aaf37b110cc8b6ce9100c24556811beda8928b373b549b20a39263fed104bd9b25e55614e66a3e02c3e0c8ddc4f143bd97a17e24d56c44e8c4bdc3b4bb1ca3ff308a0fed2efb09eca5485435ddfaa39140d115ddc61a36e2df52790ea6453ed59f72832d8f14c0f1cc56b94b25d4c3ffc35c264ddeab78100fec6956461174f3fe6857ab94fa5fe71007b17659ce18b484ee3db34a4470ba555c006e18685e03f3e17ef656ca631482b21eea5f50d7f67e7facbd02244e6196778cf8124d8908031171fe54ceaef0cda23b127309f11b9997307014f2d78f6acd1de8d442eb47122b85cbbdefab780b1c105889ffbd3e5b5ed6c5a1c328f233c28902288cbb27a306529d9ab65dac761c884e527e3301b40599c0bdb790ad704e6a0eb04a454f7a2089769c3662550d41c01e30342995644f2d5e9d9ca8326c7ff87f6df77ed820511bb156208852e31b6c5ba121ed08f34bfc65c4ed5dbabb6e8eba3c6c632891e9b76b6dea6f0c4d169db5c2217b015431613635eed252b3f30ab68922c9f36bcd49a0a71cc5eb37ac2378becc911bedf189496d54c63a146f02a0749e4514a81a557e23a3ca158cc0025a6447a795137f54e6b069573b1bc25a0cfeec2e176cc81ca3a4ceb7cefc8d0445fcbc448f979bb94e8b89bdfc01e25aa9e29e3548058bfa404072216f690f19630a09954c2f8c70344c957a16b4652c11527e8a114a9800da7a99668236e4c9140c53547d20f5882e8d2c4711135859f95362c090afe41298a7876754b4e2e7f02b729520d4c2c7ac22e81b29876e66401d14199f8d021a451e8b4f527a07e4ce561935937c69cc79be586911c8bc52a6ead945e7d4bcb14bf4e0b59cbf049bca96b7992b1341057a8cbc61271ee1ad0feb7c2cf8b15e61fbf06a2ea345e65d2852c1fde7a0d9c55851dfe06ff19c9caff0e2ac9be249a5013c9bdf769edd3535f08fc92e14235b15dcce51e0ce9648fc46f9b822dd4fdcba1d30fb0828c2ee53b8130eef4bd13737c543fbcb9bb6980414a2c884062d72820aa1d66a75bb5930f38c6a5a3ecdb1a036e918ad4c48f0932798e98ce88a52e023016fce225da8672886b9540adb709f6814c0cc3c44da5a0160e6e41a3ca5396253b37433f77858c7fdc57d71cfa78421958cf965ce0cfbd45add5e0a43cb3d37f261bc06594f0eb8c4e62fc00f0ede8fa480cb15863f362257e3318f8b70a56f7cba2fe5128dfe50a0b63be10d8365f8e287c19b0cfd2bd172aa71b4bfd0e96e959572a8a76895038626f3ee0788cbdd3c333fc6cab9f03fa2e719d7df327afe03b9d21d2910718495001f727c68c400c330b6e953b4f3f9632b175baf278ca56c4c12ba019fae7c475c566810308b5a0359186ce6f381ed09cb1802e642e2bca24f96de61fbecc715db93c73a0c1a6043e50932825b9f7f8519906c3419963fc1db25e6c0abdc6c984f9b2ac0cd8c3e3cdbcf27b895f82d616101266a1897a679c98bb60ec8fca0808edf08ea4b38cd39828d4880cf0ba7502676446f8236e14017b46546ec8acdf21c1046218d0ad16d9c166c5124d7d8e1715d18e88b621dceb7922f8ce7296a29b1d49ffde0e13bf8c8aeced502f45b06f3ae504f9ffce8dfeed12969b0d7851b3bc35ccdc5fde110fdaac0c0e29530d753111d2fd81dba0faad073f3b6a521269e1e70c89798acd89e1352e84120d2ef8718cdb78300313e5ca5bcd214f4099f7f5f59c982622d15c4780bfd1d1bff9226bdec890abe0dce8fee377ada4ee0ca8ac58b2a7417181cc738cc8fb9de80df6648b6fc306ef47bbb1a02d51f5c9bd6bb60e2046edf0d2e891380e19f9ef75782fe709c4a3fafc8c991e1c38c8fa746cdf29df9ea1ee4b51a108a3ccb49b79f13fe5295e2c561fac776ed3d787311939056779b5de9bf2857d00fe8687a0d556fcff682f6c15cc29b0b473f3e7abfc1e23c967df6e7e3fe2b55afa17faad74a0caec70720dca45e03887d4246ce10f8d50c820989179bce92de458e2ee4679521007dcf3b9c8f237a8faaa5a853262e0c579503adca1653fb5590ab8f0498d4273fce44039f2dc4b4f8154c7b5c11769b7b3d5b3821b428619d2df85ba7518e64cb461ba27223c436a0b7bd4247a873422cb3a1cfeeadf34955765fb27da51624131cc66c18878a620099c48d3e1b2c0c1f44bad119f65502ba69e1715d1a27f2b9e9d820024eeecace33da4805bd04dcc1f9d0b673b337d1de33395dca869307f0ac6bd7337e71e5f7c69d3c35fdd407b073e1311718a835c22804bb2d5800d4862fb57abb7747893d2e3f50ee0507d6df860fafac609b2c8863383cf7c245107475d359b0542c3353e0e029b1bc00d2c370b93785d6953d22bc97886d897d1199650c4e390fec9248ec763a3ad0ef47608ee69b0bb5066b11d2fa519b2803e3c1b331587b00debae23ae336270fbf86fec6315c81ab12d2db6a6fdc03f46d96c02c58c2db694b77ca56efecc7118fa170e74ef6a3e8a71f41d0eaadb194f14128b6e9b7f3b4a2dfde70235f61d7f93adb4c8b2664a864b0406463bce0b0c1779a2cc00b9242b0413c29e0c4dbf0f7a94e972129b031621dbb914d787110e76830510d9049c3bc7029a139c67fb0b2b03096218895ee1818411a04701bd770c074223b9a2443f15778378557069d75e29b17806c847560a314b8cc496e7420b614e636f969ff843d34e6117c8f046c4a2e45137ecc6bc113cce132ee89b21451a91a70591ad949b89cd99e0e4210f1216ba9950396aae21a2250be6b31c5347874982316860dc28bff8669b0b14b283173f89cfd2680ebef7f34f7c59e153ae370538df4a37c0e77d04fca7f59b8210883924309a2c5763623342acea60789ff1a05a0a9274cc1d62a509ccaea67b30b6ca478ca57783b5fde15e278132a623e35da5df4bc280bf02e3b85d35c3e39d61ae25894edec993ad397c42b3cfb5376aa20faaff17760c26f4287632ff2b76041c1d847a894e5300f33ae05fdf2c76732106e550fe260076b59cde3524fd900a449f354f60b796ab89afd42359ef30f39752d76a01fc8236ab49daf2b702654918efb26caee5645f5b94eb29018907206bc4240f2a84f554a0efd4d41722ad51f4e151fb7361d273b3526666ebcdaf54b9fa270728bf24874dbfe2246df04f717cf09f28c4de5adc8f742f91636deefb91d99104de694f0be0b80c855631a805e24cb59075c59d15023608cd6d8701644a175868dbd7ab351649c302451ddad7f41103a1ebc0b9d893440a017ec599a6510958ef5ca15ee814ff1e2ed19610f620c0d6e2bb68bcb7df17182aac3a32355c7420706031e7da23bb96804b05293c5f8fd4497d2a70af65c9ecb5fd3bfc82b2e06829d594486ae276d54c58acd476f1a5d4476a24eea09d9e28f70828ffa137e1f579de1678aeb750934f6495e0fe599535a87fe8bad3b3fe5f114423d37d0d8901f3b3751b7ceac821e05e09fb0d410a1a4ffb23d5f7e46cc8a239cc74e21bd70b4afd3e10c2988ffd2a53cb9fc9e4bb8c2f3cc2ef84cbf5285c8fc19bdf31e1097dd1749f5f96556236c22a49d5f41b4969a13ff0e823c6bfafb0dd043480425bb1899f19dce597136c12546bd0dcc99720227b2837913bd45abaec1005a569ba782795320e20b7820d8f7c0a9811d7c7eba648c1098f9f88ecd9c06b70df500fd39423b4917a4cb89c249e3cefbcf44cfa9bef5cc8708650de00e5cecf73e364a00836e684b0351e082b672cb1fc512974671e1e9529819fb682fc6c31e9ac5b14ee08d460549f0f0307197cb0dd3f4c0508fd471928f9d333685851ea0579f08773f796d03a06dcfa00262ea2bd21c6fb0d3d700475397c330ac2d98a927f878bb86cb192b1fcb4cbd2207c1b0b66426622bcb8ba0735a290c986e04cbacd5965aa1751976807dd98312b7822d008fe594e47e51a70b744225f81d54faaa2f783085e835bf940d7629b5951e6c4f9c392625084aa4ab62f0c4359ea0aef1fbb7ec76e060e533c638e5e2cc186c5ba8a9e721d70f080c725649968940efb680a0b68c606f9dabf96b431167a692579b21def4e3d967f6b12885072fc99874106d720cdc4734a531607e36fccbd286bdab8c44af0982a3f3a4644dd04e5681dc8a685bbbba78c5b895d012d3bd9e58f9a087e13b15127cec15f01ed24456c51628f611f07cba2f04c5236caaa89f07f106a10fe247d049588ca083b370c3b0563e6bdd9025e6b208c70a75fb3ba3b4010f2ebb7b8cf1c98589a457b98ac7e9ee8015af6bd0d909d0373c025b00b194d0ad91d63a2eebb3f86ccfe078abb23066298a6447927fa347b68a77deedc043b60d4bfe9661a8b79528598f6f09f12e3b4832a911d8fd81ff5e5c9361e33dd7ff13bc17929647d0274c5c674e0a525bc8c284757ec43fbe0019429c4841649824ef898dc79146cff2f509a8cf2dbc89ea77aad1e3f1fe827a0886084c3da1f3b2bf9c38266939796f0b75c15cc2388158d9c84c1ce0dab7f812f0346076191fa7f3a42c959df7e28711a2a59d969570748b4d1a3a57a3a45b26ab69c58060c3e6ed92d71dc3c302c04e1e842c3b8667a74d2a1a04af2888fe06e36fa5736413866aa047104c0afbc7b9904f6ce3a033334bf87e9cf792813efb0943b35474ae2a5c68013783e198a07ccceef69000664e2cb66562739eeae76e8808397214b2a4dc5fcdb803b6b77f4ee4389e2227b5d5c19746d907a69345f6906c7fae3dae7385a210af4cf058100ad2f17a883687f96f5f14da9bc21d847718d4b10586d4ffeccd546ec64927886b65fb17e06d1744f97e185bb90d78b505296c89ce79812b31b516042e2dd854f27516d225429060b162b938a8537a04929ae9aa9ab19fcdd70c03d8152eef8ccd575e0ceba92f0900d2d24c588bbb0896b8a7901550b77aa09b51c4d0dcefa5069e23534cb727e04861ab64b4a60124c71b3c418decf21bebc8cbf876279c843741c84efcc45dfc516557244c098b19886887cd19e75830e3055b237541ffd143436ec5540d38783604a39364037dd18cd70b0c43da9624738d9c99d9b7afab2463eb556ea0e6dde4052c979935136c2e24de35fb0131aaa16d9721a66047b53b674e5e86ecbd96287fa77d795feb054e652589d01c2c4cf4c3996683b4ad6d190672e380829973461ab83a127f6f96520da7e1fce6e8689be2cd1ef82ce6bfc7aa94a331146808e9f17c2ff50a9b7e8bdb2246cdb60f3a018811e772f6c2fbc1a763709440e8044110273665178257d380d655c7d7d5c0db8fad572cdc16b0b14a183562c792043d6743d19531c978d43c8b659200aa9c85bdf4759d9e30c5a7f1c5bab53c7bd1313d2830514e723bcf2809f937027680706a4938b64213c8a4708c94ea86e265e10d0999a3fdda7dafdd0f218764a30646018868f8f9de611a37846899d19ac6277cf408779b9f4e069056b009253f35e0c91b567e36db8aee87401c8e9f214c0b715052ae8b35410682f7776e74c74fa876291c348e8f70afc49d6a4b379020aa823f7f0471a6245876055304d8f18c98d017ac060ef05d92a0e8ac0ae425235c2fc33f13f5209d82323eebd08e7de8cdad25e46c3fc11493b7ed73e7bed2fa3a3d07334138c8e79b8b846906ce0106e12f46c81432435f52a7b235879fa6738269a1707a14ae0e9e27d7ece9c289d2b54c963d9d23c910ba5a09ab9036efe3bf82230a9843c2d6c38411ec75b7fa4d5a3d6b61ded98ea15364625fde1a26fc4242b51cf0d5de1280116fd890b059ad1dd0c2d0964838f7baee386d5cbb10008624bbdc783b3e8a7baaa49a9816966a10a0bfc26ffe3a811a200dbd0dca9003b0af0306bac2fa9e2a344f01197642164fb373bd336b15e3a079094f24dd0843c457e40ddf5f33220f32a2e7d6dad77d27c6e72a4af83ea8d0665a3cca698a72c68c59673e3ac095a75d8b40aecee6d2f0f7b3706c30dc2190bc94cb2d7bb7cc35524d0a53b23f31936d9c7215584f03c102a73a603a8bc0ac5533a7ed0d802e5bf682f4738475d2fd2524e2c102aac807c2457b4ec438facef9f8beb5b6886380316eefec8dfdc512a05d5f324191b9c9818c0c6b9823aeb00e7900553deda4dd37b081ddcbf9c28139d3e3b0cf21117911d28d47aceeea1daf5cb91b0143cc5bcb6141dd7c2910e0fa04dd06a14ffacbe2fe05305e9e0f537e7abbb1b0b9b2194bcf4c15dc15bf799d3e4b1ddb21a4ee4ef1ed58997ee648b643826e6aa6da869a27f09ee6d48ee743685ef6890de1a2c9d2473391a438c537b02a03855d917f220faa10a9e74f0ef7ee93101d00eec781e19c3cbf4f3e588ebbc97c80d3eb879640c7b2effca812183555b5e164e42af743633c16e13171079fcf25a1661ddcc34dc090e1da9df69ba6b61e3cc14ffe2dc2987f3401d5fd689ec179652db0b25b08e3ec8a73a3e744cd3d721e0a0bba8dfdb8745b53ae0ba67a007c6adb13f7901dcacd012ae8a5d03add804f3c419c17378dd95ec0bc3e947f50be07bf803a5ec3ec6820d90bc003457f9ff4faae5da4d7675f03bac22ab0288f5405c32fec33c4dc8339c7fd9b7746e16362c6f0fed693422e8887d019a715f15b6f78754bde42539c475f05a82f7887ca4d6922132419e78a6b7900cbc78cfa147861f641a3bfad99951d7fdd0813594dc793a6c742038d8017f1e4ccaa332854597a80d6f831d494e03b63b1fa62693740bdbfe0e1ab0533c8af3e3e1d7586d816de91f6a15746d6dd60913ee9977a6e19b1385ef5f4ba78b35d8e0c9a1644378c66303de11a39849be986f0cfa9b2cc9a10fe0b8ed4287e8f2ed15f99fd5f26ae51ac0c8e402ad2eb1786134c49c065fa77423f43168bde2ddb578817fc5b42d026051f4a820fc0fb6ab0a0fb4337fbcd4570ede34398a38e91482e09e14a1cf480b4afaf456a8e034c48ba38960f25925c721c2a041017d8dff24aa907cfc36c9f841adf7a042cbd443caa79b6e24927026ed16ef5d2166da9289c11d155c7b5321af4ed73077a04020aada00d1e9b0857e2061a05086fbd44a50e62a96168419d634a2bc6bdc6704e73f06c08fce44c6841d58561bf0f68089c0ad7361bb52509c2ff48c1fabb15c82597781be7f3055a032c21c48620db53ec7585d04bf76de8afbbed6a1b75ff6c2a6c914076e4c5e5fed4c88eb157162ee6ae72448ef83a4c0ad5ad52c428168880154abaa61069a67f0cdb6e5315bbd3b1af85e4f28070eed26bef682ded46fd683a0296549ed5e6b6175579a75f6730e095f56fbada9a90d8c5c8a3eca408bd0ed36eef3527f4677ef8b1ee83794236ff27d0a115c1d581f5ad15a0ab49f0962d8dbed8e62c4aaf6a418dd5a27c30bb17286004f95adae31e8ad9be1ae42d0c6d6db60912d98096d9ed6cc1de44523aee007e63a6eaa058129580ac97b545adeebfbd1a42bf895bf207098f3a9b8e64c0e24afa1a6f0a3044f8d3f17a4b96ad970583159448e43e54f4033fbcf3d2e3ee8f3f0d1d5cc8eab60480321d7407d2d37bc60f76ff3a1af81dbcde75990221b083222556fff639a4f52db5e17cf9b48d034acbc54584766c26fb3f766c6b734d9b098627dfc0abdf065f4ac3ae9b844208b1773fa4bbdbad34c18089a762fec367b431b2acf0aac7a13bdce5c179fdfa0ee6ba1ea126989498d297a7342fe43c181f1fa7ab73c3f096b925491eefabe5ec8e7836782cc462a9bb477db11ecc4cda9a4e5b1341d6ee2d26e77f7c64ccf6628c1ddc500e3fa6be86058244b31c761d221ba074f58200851695bdfcf86b800c74a47bfa81470c67bb0ddc99c0a46b0932b05bc1974106d250b45a7aa3c68ff3a7c8fe53fba286238a8da327820cd882d394642035bae2c2839656232faa94403070ece61ef0e83be2d1c4af736b97f2897919cadc0a32407e1f82d1e8c88300539208750a4865b798bd446a7cf441fa2345fdbe7581ab382e22c75fe8b7b1e4ddf23611f90794b887d133e90890376aff9f56d3ec0de9094f108053e1f66bcd993160a46cc0922d9289426663c0fce7be97ebbb1406edb4f289c652e3cce993abf37de42f2ad202603487d4d8acb0ccf76563aae01bc536598d525039af96576747b1cfb30eb4ae24b27910c49831e7b486f46bd779344ccb00379568655d1bb38733a9d6b38776d48b32e59c3f9fc3a028f283f7a502da3f97b90804f32a02338e2972ae51d3704636da65e07027974e34ee82793b9821cd66dc997e6106e365ee3260cc1158c419555be1d850bcfd969f1b0037ce50ff46e7078a7b68c232802960832402fc15fcd8b1ee43079a3fd31c75898ca1ba0c84b47b433a4ede0aff39e2d27581369863ea5d59f1274a74a161c6e28e6d0bc1bd783469f7e0026eac9f33e0b6838b383bf1414c6b3ec54e37b7147049e6f741c562442dd72641772afec8e472958d7caa9b128d9837011f4056b51a9788882165c06371d942af510018d743f13f67568dcbfbe660716e0f5a58c0f08d4f27e680c2821f8022116634165d8fb480af73b62581e33fc16b05e3b52844986e2e8331522dfc1cfc259eb4b3542e73681a8978f56680b1fe1b59028cf99b0a141771c4aa1c0a2c24b69018dfbc1b41d9e367764663daca38c8c8c41c914af57065ceac71df7816fdab3e9c7cce3f0297035e77b904c94d8ef8051eb0548bd18d07665e8e812d9b9831635a685874a0183e3918f9b001554072dc1122b377c7624ef32c9fa853b0957e8ba57800b2ede7413156a0ee6748a7018668ed28bf1d57bf646d2637fd2e93707250d380f0c5cb36fa84cd3b5be3d28092a51539bb331f4b039b7aedefa0ed3c70bbe24a2d8eab34b05fa32fc2dcfc7735188fc82f50bc3764122f0f74346d8744738244d1c359f14b0aaa0633a02e495d031755e2ece92448092bbfe18c091b511e388fdf5c522df8e3b8a6973b4b1ac74b896c140fe277d28ed52ade16b3d658e539590d869b83f13ca17337bc5f6cca595622bfb7131085a580293a35ebf4f9be03b5fea0242ac6f13b396418691ccf668e22ce681b5f91c1f14f72921f81f972dbe5c28895c377914b59032c832f86e212a33307ecf5810f000ee9d8830e9b5ddc14465a603d6adccc841a35dad4383b5bd893a7bab139109fb7b724a37a74ff0b48d764df1580cf02af4aebc0c4b2ddccce12c44c520a14c41b62457e74edc260b0e1840c4b56c88680958d988cdb39e07b9fb401ae38e83a20e67b9e0f26a29e9d2cc0391d996accbe3a6b0dfff524b154ddbae146c18a16096d957520882a2c603856c89071c6bb5514fa6d0af16e9375c10d1ef750e5ca5790402c13c5cf2e05b18cfa42b751278f03a1c71366ca86e151bdc5cf30b54eecea25cd0bafe35918729e6efddfa50b6421205ed424383bdea7b6e33df144d70d7c9980bc9120aab775ac488c81e5d8a498dc329bdf10d01a9296cbd51ea08f32ecf88e7853b4124cd532429dc18c520480bf592d3920b7bc72203211e42ff85c7a3786d0726d23d8071cacbddae93710572a55c0d054c199bb45c43fbfc454a311870eb78cea87d25ad62cc62219d256771c215c3111f965a069d6931effa4f399420a18ca89dab02dfe50bd7f951aa31dd1caf543c4ca8fdae46b1c6aae3906c3dd3bf70890faaf06ea6f2779d8dab039f6d4d997df73c19843f898a1bc880f2bbf31d61dfba29109990c83d9f6d5115d255740e50f8590988c6370d52d3d20378ca314d26c1d0346cf8c6e88e71b37e42faf187d309074018b75a8df180119a35c8d7c5ab211fcab0f20a377cd16aa755325f01f171b43cf95846712fa45aa07a4609bcfdd36ef69d0f4e49ef60c4849393317572625112d7570aeabd7b31973a8754f5b2f82944414b8ba3ec8f720eb113f0a635a6d97e5847676eaef35cc1f4d32019edf28dc768801e79a7f13a1944cd49acfb98e5253b63ca99fa7594ce38a6b2c982a0b1d7aa7d6f17f3ead437d9b91d0de6266cc00731d48947e34abeb3e07d7bba36ff5c957fd528a341c74e585e92a3917c8de592dc83ee12f88d9b140f8a063c5dee4b95e328c7c38227fcf844b7dd93df16f2a41af08cb5dfe89857a3c9ec1c8fc3434fe3a4cf44701d029093c3711309ad415592a79ae0b8e732bd26d90f25caf101265f4e5ebef48bd991879f6e09c24dbad27b29e9909bdb1bc60e4bea5ad276495f95c7bacf8e042854c93f4e9f4e247d3739849b07c98d02484f1602f7b9e24b87bff76768826f4f4e612704f6a071e0392f476498166ec2439ecd9d9c694cf35debd01c48610566369c786811d191311888b8f30c83cbf2025e928dcb83a3e88c9b73e59ec311c4c8ac63bc1d271cb55cedc1c10e3f64b27ee269880cc454b3ffc2910f1fc5edfa9fdfbe4e72e31b5a2d7b76b2873318160da239177abc6d6de05808b42bf2681154a531a58a77b81212281a97d5eef4f25dae2a139ac26365cdc902a24e2a29a5444450a9b6d0b656b9719824fa4399d057900c9760abac9c4d53d4c177ffe7fde7e62b4f7cadc4d9dc1160fbbbd1e9e60c0892885993039b9e58e2797a1eb551724e827ff825552317e34901da8ce952c59f2aea2135d9bc0b0b2e44d17374a4907a9aa28bdd496ff8471c9f684f2385abc499fabe3b46e9835f497a447eb3ea7b63c4080a0e7ffdd30250490bf1405486a36c63a37e27eef9d69024bde9c898492595014a84f815829192a6036def4d79a752c161d3adcee57f6420d36138f02b958d31895f1d67075622d378341a49a0bdd84965619890a216236bbc6e483d113745276dff32dd10c5d08b66479c7d6a2f146a8c49a7b1b8907c7e4c6a780cee36f2357c3cf120c1bcbaf2abc411162c9954dc882e9226130d4847043142a1dd561409fa0ecef3e8cfe90cccdeac57704736154becc671521b893d248e45bf592dd6415a1c86508ed979ce4d07c355cc556753d6e4a641f59ff00d7da00b064d341aea2b368b1e45f9a2e08d77033587bf6937b3c801062809719a602f9d02925034260b0154f8896abc7837ced596740086fec5ac401b1bb61a8f6dc68655ff86f6bbbd50d82d0ba6c2d26f98b22ec06218be5bcd13d669c8618178403b30a7c65019765a3c196fd84591edca156d20bfbb80777854ac7d44c2b49cda71e7d0b38ba203b02ac25c712ae7383a624dc1af366eaedc4847c82908b0568f0249f89bfae99adc4ec5b354f4ca36d860a3cc05a0e04384b9f6fc28957e28387e34040e9d56f926b2324478e8d7e16cff4ea5fab57cc030129033a08d877ea831eb856473e42a640e038d2ef27d58f1a1c708cc3b73fea131308b03390a8c98c94957f225b4cb3ef8f93962cd4374be68fbec6dfb46dd50287160688afe82dc66735df4df614db411a18ead92612237c34321363e420f5cd9682408cfff0ba8f1e7e91be8a71de3dfbfe1ecd76ca0613411cdd0dea302fb0c043a38c41bea27dfd60abe5c7b57d2f9adac7bd76f4d6a7e70acad101f81eda1e07ab980514d627c86fa5e1074e0736601fc43dbce7c775c17997ce19c3745cbba97be068fbf16e069c10039a44ea775e305032e21c880a464d44628a10faa15aa9741bcfaf1703a2f4b95610dd3736b777cd68b709995a666d847b790ba4ac61690d691117c3508400b0c2746532da6241024ae43591333bbe5d2a7436f2d714410d74da049b888888102953923241076707610736164948f343f353450e881ce8929468fb3feb25d7352d9bbbb6ae5fe9685171b25fcb6004f6f6e7f592fdd65bf01d56a5bd605750210107a83bb073650c2bcffc8182dd814d18aa8dd715a838af59fe93735a75d4d8af61a83eaa8e7bda50b5a5a1ee60bf367d8c2ec829c0d0c36e13eab6353d5ca8c1d6dc2994354ff20a2af8f9bad444f6f15fd3cefcf7bc9ccd3f372b97689d9630bcee97283b2989ccf2b6fd7f12fcaceeb150861532aee8bad6a9ad6a01fbc18f73c005f31785aebffeff17637cbbeeed75fbfff8729c297aadd5d66d3ff1e493bf7df2c9de3ef9e46e9f7c32b74f3e19ef934fbefbe4937f8323f0ae0fd96a96b82960b7df934f8ee19a2cd3c92783b07cf2c9769f7ceebdb7abb5d67beffd7befbda10df77af7de7befbdf7bbf7de7befbdf7debff7defbaefb7fffffff86f7de7bef0d8fb2d57fefbdf7de7bff5eb1c778efbd55d7afb55692042d702586e4a7eb68be8aea5777fce6a7ffb023121fb6e51a0405ecf7aa0237b6c057d4692f55bfb1ec8fd900fdefffff6f2b48baa6edb8bfdce58ab8a2db157545d72b2afa9ca29c378b6c5507771e67da8a83efbdd76cc07e5b71b00e3f0e6be07e240984b4d70a3114001d9c1bd4ae1a06485df9ec555eb24a4ac0030e3ea6174c3ef399cf7ce6339f7df7dce31c9fb5bdca3deb6592c4b6b7d9966d91ac6dd9966d09a92b1bf4ff426cb748b65b1fb6db1db65b106cffb7415e2649adcffd8d2584db5fb278b914b980600b2696d3791fd87560288e24d0d6aaebed21eb5d79de64917eabaac7053e2f83a5991e0e28357edd7bc31726fb7d9fb660288ea318825f910986c37f750d1ecfa7ab0e3906ac0063f43bc608c718b98c11963112811c738c7266b808541cd7a61106bb5be001c6c85daee33a66bc3de4bb59eeda32d7ac3c2f5e4c31329d85197974d7f4a2235fbfcac957851b98b155c78b179a9aaef46dd97e03c08d9aca9e4c630f17f6d5d8471ea6edc2adbd78a939e1fff25766cf7b9c6f09dca4f1ba9c509db53ed8978a2cf34c8ed730c07c35be57bff6bc9c49db1571e0762185a8eb3b3046b1f3192a9f579de3eafb776acd42087a573f582f106ffd6fcea9d22539934beeaa0e82ba02b5a7bf02cd37594ec4f6d01dc9761fb6ef40f2bc3167912443ad59a7bb4c0672dece7d2658d7e0f1807a73e50358405f68c1f69b2b554881c6806e5c810024c8503861841c39de6dbd98746b207998c4c08d3eeed5ef55074918771e4993bd478c6dc30f2ccfb2be2a88ad3aaa48da2a03c00defab995e30d9da57f36a5d8dabe19a9863ff8d525d5958ae3a9ecc9e85751606c316762d6cacaa5d67be1f7adb1fbf72d9ab5c7b9f2993c9582efb60bbec573e6456847664bb27d9eeb1ed9ae5566cd7e05802b8df7457c5711dee205bcd729775707fa4eb3b5f8f5cc0078ac9e252618ce065ffffffff96285b5c93597db1be6ecd6b8361e45d55c795bd0d55e57ac89022458e1c4992a44913f39aef82012e59b6aae305c7c9aeacd64fad7dfbf6ed7fb523fbf6eddbafe1e827fcff078314fdff833914c9dec75a1b2936df157df37373681b23edfe4f9e88c901696d1525645c907d57a12a5276959d611528dae6ac72449521ae0867983c0bb2d5eed557551d8492236a9db941a8dfabf8245b7dabb65e1d4207df166007aca0c2c7befdf4c8befdd6d191f05f0645ffe00e456fc4da2f06ee96f4f3ab22dffa6f9dc4d2c0f6295e152680c621163871899860f2bda0d6434756c161934597881cf72cc29a450a8c8e35664e50c10d5de982abc1c566d13091392fa8d802e5859522bc4e74025321ad2582d1e06be66084c004342a5e3005939911108c44548ca0ba5d994801134af4b6d69af7defb6f31c6f85e4bdeebdd10de5a6badb5ae386dcdc7b8e3caedd7bbd772634fadafabc6f5be5b4c14e2fb5ef5f1f962ababc5dc773be2b01b7162d5bef35f296b6edf1e9def90ae4d2090ec71d8ebfcaf807214b34426a5304ca0525091229324e75d85a83cc9d7cbf140e9e78da27880cbb41a4b34b10492ed1a8f3d4e06f9a32c86f0ecd47d63b05926184a445162c97e3141b6dac6775e5b257cf6e76a8b71c14309a21cc1c2030fb400dfea0ee42bdb3dcd448850910f742088102021021e6c609c52840cfb5df144293bc47ca11426321e2865e88594274ca9142c78bce026a58b0c344508c8832868ca908e4712e894578e4d819578cc684e0a6525303b50b2c4740325413238e4975d85944ca141c9144b72695721255c7021c5861d6357a12549f8842523b2d539af7a557677bf5f6bad6e833349e78e71bdc23a8db059364437b8898243142ddbb586c19427419dfd73de0a8ecc9ccc400752d8501eaec8e688906ce138f24594859583288bcdaab20f106111630435a0614a941de880073c7896da5588c805442a380aab8dec722b8eb519f8c1e9befae505b9ea8bd3bb350eff67db745f3d9344aefa7ebaadf65aebd555ef7bff89d0f39d5af599f3ea5a9fc16ac0dd7dc25a57f7dddd5d5b212b4f7e95b55184d54626afe48a75d556d75aaf6b8c4b206d7720b5d65abd566e857535bdeab25db8357fad351ac543c5719d25dbf509f5ba0901eaa5b77db16d69db8b39102c77b876b47a38734790201905f4f29d6b04f50368e68a63bd0f00e297a4ed522289d132650ae85524dd95a957c521e91725101f31fa914819491501cb1b5bc52169afbc412b0e4987e54dade290f4cf48fa8bb0fccd776e66504f203757364993f46df9ce0d14f5247df081d6247973c56c93d0cd22499a755d9ba4634a137d15497fa569f393646671154993cc2d3689f4b0cd4ad160ca94a94cc95ee5a924a926db9d88ed1ac658c26fd14cc16aa8970e0a766c60bba7f344a749ddb1a36575625547978aa5607eba2ec79e6ba696784027a6e34427e8c656758cda750aa8ea10b5eb9b2bea4ad5a4aa5c0f1952a4c89123299889a6602c134d99e8768d1a4b28ed31eb91877b9d064b91b6adbe9e68c3b5cfbb028514293135f6743a359a7037d636a309a50d9aaa58c5712d830599a593826dd7498d3c1c5ffd9533d96661d40b5f7c05d7ac6cab77b45898cab6daab6cbba3b55d873bc42fbf11c42fbd3000220962ad3a4ed985f4427a1d872f0d9c01bcdb520d1b393770847022bd00f78e9f924fa015dabf1685194ae0aefe7f1d76a2575ff7d05eb62e08ab4dfebffacb17bce29c82328becb67d21946d6dd3d91e904f62b634b1d6ead0e4a5c6e4841447aee4bbab90143d1421050e5ee5a4b0617332314c727824b34376d1d2995150319aacc8a213323a64318aa90539af408b284a108b4207d3511440b280a20bd31150649941318510145642d034457e118a41417e21040c943cb3ab10143ac00491bd5d85a0f880c94a8d074fb27082c25c518bb2591966885a0a62a2d460612d0442359f1dee2a54ebe1230aabcd167ae2e6aff7637d478baf3b94276ed8554805b3d4ae424ec476e8a27938fdc34feb1ec3f56ebe55a316fcb4eeeeee55bf99655b8d1d050610b12fd7b1fac232cbfeb0add52fae64ab6f7a90eb9035e2e7e91ac60e99657f8818b92505ecb69aab01fc38724bbe20fe2080f0408a6bcac5378b0b6a5ed755286a68228a289ac09d0896352a7c1ad4a02983008ec062c6f399def79199334b788df55361c5874706506bc0a51c718487c9d533f0845f099675ecf134d6404ef999352a7c1f5882668d0aa00c20090f83ddb265cbd157561ed000e4e660b09b65b70448ab430cfcb0b874730f634bbee60be4bea0b7bfbbadee7c54e296aa8e4ada2a486869d11072292198c17380080b23aecd418b1342f0dc9286104f8893a0e2e4063c214764830d5cf0845d1237d0400b9ed013893820640a9ed0137159a30296a1d272a8a1053cd8f46e59a30296c1860a1e6ccec003a3ceb4885203cf2d6da8e071772d617762e530b5d08d35014ea848c6f1891c621a00a83b58e7886183138f4c6416a97d87336b788ca6f75e0896d94f4ed7f8c9b9739a0029fc0a0544c26b638d82d010b08df5037c605daf6c6c6343a4608740489a60eddc8935f600d0c61e00dae0a7391d7ae91ca777a86d29963a7f668e9f98d3b68633699c7e625d7344b861500310a71be42ae40491cdb53c9ceed7612863e2b5baae9e9f1f82a2253fb09aac19434b2eed8b9defcfdb1e2e7f8756bce28f9f0e654cf4c7e99392ccbada2b710d5fc13894617c3d8d3bed409985b3ecd7380b465167d530fa95f84a89d11263f1f371cdcfd737c82c3cdbafff359ef98e145f8d669051d7a0793cd771fd7aec49612cafebf8c5b28a53a7d8afafedca6689dbae468bf7585e57e1f42302637c22ad1d7b4c18e393e53ccf33ebabdc347362dc20b348f257a2f51b2ab1bfc43bb422143b74fbd52f8947c5718dabac0a15fbcb6fdbb1e7842132cbf32e950ff8e8b083901341bed907c9ac8f06e581cc225f9a1083d0a084e497264422df0c8571b5b65b2ddb518b5a5b85c562bb11ef5add12ef5abfe2ac58b4fbb168f7da984807b41ded96608ee6a75b54cc21b3c49745bf8a68c45763fa5a7c0d6d9425beb6eb31c563badd8aef74dbb1b82acb9b162535c916c51cbeb7e50911561b96a71b6b14fcc4fe71d5cf15fcc49a012c7c5f8986aac23a24b9b7fdff69a83b57e317fd95be2a00178bba0a7415ea200a6a8fb99326ea66c177ec75d184154c063ca08705d79e6765268bc41f0bbd45017c42b9020b28d4cd8ae1fa0d1beb96b790c82c8fb1bce5b15f795ef6b26ed186ffffd7ae7c710921271211ab2a1454cf888d3fed29f80e064392c75e8551e04e48c1cfd893ccd0344d0fb2fde5d6f5ffd6649d1ec44f22638ffff8893d4828b35fd79187ccf61b9c0257ff90af6679eb4d6d24cbec2ad444967d733a7d738ce03ba1f616006ea8ca6a63dbf156d90e4bb7f989b5b77ee54e5c85f513770d6d2c8564794cfb95733b16f34a66d55a6c85167e3451db2c1bbc7ad1580025cdb2d9d8c9af5ebc9226b319db67cc4fac4d2a19c7f67b0a7a0c07c25aabb54bacb5ee42569ef80f85d8569ad72d3d178f588c4319137b5292ebd5b7baffad210c35cbe54d17ae894d175cdb5b0017de1475946c4dfb1a818bf553053f4d52496691263f4978f2adc9cfff107849faf95abff6f2e6b9b607d6a036016b50cfb0bd645d19b6d5336c5bc6e0b7bbd755b58e3c587befbdb7fe0c6f6d0a42dfae6f358173ed4218c6106d0883320d3f91f83f753cce13b6b2adee3c1a0843319fb67d228c514fac2b8cbb22ff7c950823d0ec37c359b69a755a9afdd974be264f35dbf39d18f8dd5e990d0ba6baccfa5ff187cc7d60ae214587bd0a890339d0f6a058ec4433b3abe64a7ff98fffd0d0d49c48bfc15dae1a19548c07f196c380f96a8dfc747db330ea92cc1a51ae8b117d9573a6b5bd4a49ba59259ab7615e52c819d17ca1d9d94c2806851991d89aadd1d4c8289d6c32d6a9fd21db8c568c68d5631575f5d5462cbedaaf3edb7f666d7b961d62703fbafc80fbc1b2bdeacff6651992c94ab1447bdaae1e95277ebaaea30d0aa2fdfe6c4222101f88a505b22ffb82b1b1118985c13e38c5249ca66597683351c253f50569b5ec0d2e576ab43f20cc10af0c7efef07c07770f600155c5a01a7d14a59feddb42d3b0420a3d7ebf49d2104520328e4873d3836ce376e07eaaca4734cb76cde15057cef1b05d671359fd54cb259aa9e63032f799efc0cc60864a3459c94aa986c2097e7a5158a3062ddb104b4f6c2fd1aaca4b55ea0a064656926226a6546064be834deec7cf219899ef701c11ee07ff68f3d34774c442801125012cb2218a26c8ac122d479f25daabbc55a2fdeaf33e5bde3469926c35ebb3cd8c487272726ef889246fe0b0261ca710aa1d6d233a62f96ca3edcbf2a5be53aaf9e99a56b2924389b6bd10c413aa8666863cc1d8ecc0456badb5d6bab5ce32fdbdf65a6bda2bc2509fea8bf762cc715de78df76d79c350044371ac358fe3156baddaa56a97ece78989a9f4f232e22ddcddddf50be7766c64c246b72d62626c0c26d5f4d8ac056c37d35d5fdc9e181918191919d38b171999529691917191218d3232321cfe800466a13125741181f8d9f6adc5d85a6badf924ab49702f41586d583b7eb66b7bbd47449259d886c79e6c0a2f37f6907e76a30977779ee77d9fcd150704c1300c6bade2f49074288ae268c2dd5ec5b13a1cc791441a7b7ab20e5dc4dbe3a2c32c82e31744ae7feb0be63aef035f423b925c7269861482a5dda4d2ee702ced0e83c856877984216c01ba65c730f62077356988486157cca0ae1e8684152a75e765265df56dfd0a4657fdb5ba92d1553ffa342d331b9422be96e4485dfdec555f64a448ddf91b907a7ad53fa4eedc16d0bffea7ea0875bd2e168d1d84898b7eb1e26caff2d799bb2497b2c63807e489a32866f185e89576873022e8522221f5c8719a1443721cc7719ae334c7711cc7719c26398ed31cc735c95c6977ad3422e6beb41c26799c58e3e2522291d163890446c6d489bd4ae789ce508cfd993a4136686671b7eddced57d8f62aabb9d64fbaed0d97a074afdcbdbaee3bb994363cd132cb44dd24536a9aae957a56715c3fcc7734144a5cbb1422f0c75571bc089121376c1b645a6cdbdff391fc32f2834b998af94932534bbc2efc8614114d192c106b266a62316d20ba4da4166b2fc470c72a0eacc99224f53c620514940c56a25e2820d40f7ec69c3c093d93b85d4a2426dd959a5671dca4ad682af574fdfaa9387ec44811224366508a68928444bd5e850242fd805a029a1ac83bf92953a22811b4fbed1eed1e6f6e1507d45e7993561c5087e0cd0eaa0ad4e0cd161bd4e00d0eea096ad0d3a007a680360816d9a0666910043328c3b54150c79466fa2a507fa579f313d4a4d2cce227a84750dfd2c4021ab90ad4a08c201b045f9b95426bb457a58252b15411a924a925f5d450cc14acf32ef80d19522405d3a4d184bb3b1d8218bf8c271818995295beca56711c469565abae088ac12a8eab8a50395135512d512551a1506ca782126d574149c152b157c196f819e4e793211410ea4592aff37eb208146aec015d4abb31c6a58dd895ba5671ba2b1a8a4e7ba506aa385df74537cf64e923db75d7e9aed39dd7e5cea52b752f5dd7e998aeebba4e775d07b459a91a4da64cc1c43215ab38640a56dab7346d26eaa7698e3d9d364713ee96219aa80806855f68aa623258900e6d96c9fbc2bd6303b8f6aa2c5f18fb40fcf20558aa64aa2055cc54324140e531c5a8a0a89ea88aa84038ce64a99a6cd7fe5abc9f81b54fd152432929a9a0942ce5c4cfd41214ace2b8962951402854918a44dd4119a9a76b22504d6888b9b45b4ce1882ea5558533668c3d9c3ed1328d1aa24b89044683a58695fa55711c46ffec573a89fe61bb767da49eae5d1bd9ae5d17a9a7d3b41069283f237711b5ed4d5c6fd730250ae855ae47528982f9e9a8252827db5d5bcfd440366cdb7d87c3d7347213a4b361c3c1b9b415c7eab1242fc9a586f865cd0e6d885fd2ecb14402a34966ceb911761d0782630feae5a7ffa09d94641410ca484f0a760a92f9cec52ccc89b0b333f6842bf14608d6bbb3e15736661e2dd76ca0aa98aa495de923a8570a867a1d3992e493a05e456c560af6b2cd4ac150af9db184978d4d0de4c2da04936e6cb254b7eda4d24b96a9bd30c9d45e0422b3502f6c9a58c04e03e95ad5116ad7fa485ddd5c61a245bec89192c9fa23495c4cd627c926ea857a9928cc8dede515dbde1784827d1ef979dce7cd781f1776f785c3374497d2eeb0ac5bfc21b352b07dc2e6cb135d500a5675a496a462292729253a274825a93b7496581bcf42bd503fa0805046fc74adb3c4ee4015a9363c7ed6ed3a149f70777737ffb543068980af5f71face1ee8556ea56c96a519f1a4f80a04f266be73bdd9c358de6cbb06816020d076daaffe9d5cf3610f03b31097c8d5da53075a84fa43fda1fe4e875c64ab5f77df7df7dd779d187e9cb65aec6a74cd68f638f7bbd8756147eeb3bac73a6980bad3217803346bc602bc0ead05416db50ecd1a10d4206801bb4193c689ff8b92b9aeb3d8c5450e5a1d48b7a87d7eb658d3cde95007b9fad99de0daf37226b99557da75667f4964acab1699756eae86a4adfe740dc93c393d9a2ccfcb9c696f0dab57b2f1f30369e4541d5edacdbab1397dfa8ee3b100568ba7efd484daad16cf330c4d1a7eba0996a7e9fa8b92b1befacd6ac361252ea12a5e3b6ce0200a510f3ed060c90c4c60ed8f2ab630d1458c68091749689004ea7715aab98a8442bee88bbee88bbee88b8af07f2cfca33ffaa33ffaa33ffa3faa29b822d89c9c1172e4b0fe36af358d33bc99bc22e6f0e7cd2014f2ddaa390c1a85aea44611c2b25939a20461b47dbb16210a942bdbb5d79147b9dfd49f16d277407014afcf114774ae5dbb203ed4faff1fc5d64b73df4df9ff6b7ad0ecd7be5910d86f5ea29a5a696abd64adb57238a6abff6f0a5db6f5b799245e842ecc752f2c92f5680896c410ffffdffcb551bca431ffffdf1a89bc75e605d9ea91fbbf62e8e26aadf56bacd733b35a6b05bf17b5d61a0a11120a81b9a10bcbfc7f0c50bdffff18d558e669f7fe7fe81259905d4f33e335347f92e167bee6ff4f27193d546144436bb5d466a3cd662ef3a09313ba34b9aee362e4149a1a1baeeb86c825389913eac65a2396901982112365ad114bc80cdda4accd4166a83465a8ae11447e7e6e70b94ca88f166cad1f2a106000c28cbfa7d19f46f734a28163ad11cedf19463fc3e8ce30aa61e372318e6a68ae10356cdcc061c41239e4e4dcc07197b839dcc0c1757fbbbfa71370355b7084c071d6e61082081c676d0e22e8709cb524ad0a8c9358ac542a95461b8aded2b446a3d1d0d49c48540c8ac2d0606829e6d2990c456fe82d284643cc66b38de957abdd6e29ed7693dd64a0365da45a8c45e8988e5b9450b0f5dd3ea3dbcdc8eb5522f2f3f3dd4adfad54fae1232d993e386f5b501b117aaaeea78e6eeae8534720ccf83f9a715f75745547af3aa281f3186cddc020ad9cf3961a954a25144d51141dd3d4468e1568157535a6252cb67fb73cb6bedba7463adc6ca03998bb8f7225f457dfedbb95be5b096c7d374d9240403fe8965b515de11208042d95607cf0290a4ad334057abd6230d88fcb15a4858a31badd5030609ce6339762b748d3528beb662d1b8a9241693a93c966e92c86d6a2a5694aa3329ba533a159465314951234a35199cdd299d02c2d0929b54a40ea2ad34a3ee9ccd2a4ec309ba533a1d9e82ab5465729484a9bb994274141a6d4492ce63353fafaf931a53020a0d96c8974768b926219f0833023063f7dccc09daae885168a6cdc6cb625851366a5d66c04c1e82ab5a46cd7a34f5d7950100e69ba45509acadc4c4124ea4f481b89c594a2a62d4c3702902023d12e522dea8a444d45db7d96318d447d079ba3cb4f2c36df196f185df84bae52ab146484568e1c29d7e1f455ae5f654a7fe5b3593a33a54ef3d979e6dc7053ce377058114708f54baeda0ae2b3d676ed3329be43f31dd3cd946e614a6f66600b7398c334dc0a24acf75e8f84d5ebfaa6b66e371062ec826bdfb34f9e8ebe0764ba04e6a7c3626448cccdd46aa92da4c1364383716969331a9a9a1319848a4151140c9246d24415c08891538d390f7a91b640864d1b94822012a684f962815e2f590c0c0d3435324a288a661a2d95d16498509bdc454eb316759565455926de722adeb6d82edbee41fe44bc6d27f2f3539a1fbc09f56837b88e5c1e64423d48f68148fa6801c6b67e78d017e42df882883e1552481da54e333c144e98713b11727246c8918300241cc9ba56cef6a00dcc90ef24db9c7a90b845bc15d595c36040b6090b0a4c68ba5dc606fb950975588dbc458db127a739b59163b2c2849aaaa8ab9c9ab0d8ee4119cb6839fd9cfa131d6c98d01cccdd37a1cf99d05f79900779900975993884079139375eaf1b385c2e1c21780b3cc88da82b8c4d13ea48bc09b53713161db2d52c137a2253193f1de7ce1356ebfe3cd8796c108fbc67784b01978eeb6e77899c66df7b8dbceed620b65d3bf1d3637e7a4c2693c968e40c4d8d45517bb329b8c5a635275ba359dae9848241a260c48889a12ffad5aee89266d66853cc643199cc35454623abb191d1604040b21a19243322b1311b3ba16e6a433518b418a9fb34196ac7256a43b51b59ea095a95bafa6652c866bffa6ab56f561baa952d53868a765b56648fd6be2b6ee4f5aa0dd564d8a8aecc5bc212f931faa90d894064f1870f999811896db55420d46453c8643318c84098f19d7f9e630b361a382aa46067fc0c14668c2718cd6ad8b8680d4b8c1ee49ca72844b63aac41b37103474d488b7605485d792ce6734bb3926c8889928cb6dd342bc93e9633f68c343f7da4ddc091840c89ba1a6925186caf65748991f6abaf36d2aed0e4704386e3af2c7b9eec575fed65e8096a3523121247080e04148208f6e747041df19be2ab5529ea8ad3ce992599d5acaf8a5d1aca215bcd2ac97462c0889940c25aad357f0644aeef9ac0d5cef3aa1a9ef7a8846c992c6ae9d08c00000000a316000020100a884362a1340a23556e1f14000e67944a6856361a4843498ce3288c619031c81862883100116394a11921020080717b97195aacb2c3de1fc97ef550914fd747c43876e954a7afd2356e9cebb0ae781d3a5123be024fb8dc03d671c877041dcf69bae3f13aa69e40165a24f6813b2783b15ac95388e9205013963decefadd3e5a8edc5780c1bf779f56240a786744989cfa9133121375abda31cf4f4d518948c09f3718278f71c3ffc19ab8e0e072e49b12a7703ce461c1fa7720fa7133c17e31e2926ab71dcd06b60a89ef55c05e2cdf3a13055b5a34dc2bf691b609adb498f6870a316582e668cf24429285983f25916fed194cedaea6e29a346f475ee72824ba5692ed5145e76f715305caf63ce3a7817052d85391aa5c21e708cd5117cb876ea99d3a0492a9ae6b2a9c1bf943b69ee2c291ae040e6391a9a1a0fd016a2683c213b653654a0e39b97ed52bcac5595ca3a99098cdc36e78cffc56266e9c65958cf433886e3726c3afed90c4796cbcfcfe8565c9cc0496cbe75ddf57b4f1fdccfc90929e7f9dc2c1e2c9950845c9811dcfd3a7155cbe5293f954fbb7227178e6997801f1b65f27473b372912f8549f962dfaaa664b2a135d901ab8bd71548d412201b20cd940ee283995d95218b0deee5544ab16fdbc8095043d01d97c50273ba85b4399a2f36e98e812940d2664ef8955685f91c92484ed598f31f675ce197e5355c088b3a9e06b2f1ff28e74f66ed5c479b64020cd6b3f750ef1ad6c62004ba02fd7e8cdae61145e41d51c91a1f7341ee9c11f19aab7c69d859c9cf1a4414f279d7017cd7fa4c49751ce83c75d39090baf0511d5c6b54fa3a0d17f0a5d24d69aa3d0cb0aa367d82dcb8e90981a11e07c163a56c13942b40d64dcbbdf226e28c2e937396bad3be5a46f1f27a97dd956f45629d77e4f74cda4a349927dde06f6baa99e7b630d3389b126c3abbea98e33b2ea5595fc71392796d45d2b1e2cc8161a1bc171ddddd7cac94eb5dc9118079bdf7888e0a726d1ebad948ff20eb6aab556a916460d9c2e195e069e3b8f53008254757b149655f7e0dc45e29cc00c455d75b7be6f382b465eeeb67daa9d3f38c8b544978825998a1c354228f3eedc0bffd136ea5b07ca18f5a23630fc0a808f3a8c57b110860db133e8c28b72cc816017676439f45c588c7498810f62cb1d367768c10c9e7928e71aea771d517b72ef416d49838b9744ac847b98f728038a0f9147d88ccd379bd1c5f8a61a459f363b4ed6fef7bd92bf83d54effdb88cc1fed6324744de0a197432736e55d53b47973744ccb30644063cf247c9c7ebd9fe82860194dd347d732845235e08fd117f6359de470f96ad018d3ed04082e48b416f1334b58fe677421e64f2dce79cb9849ec90ff313275853d405af8b0110576aa61578d70666f1965aa80742ab61ce1d062229b16b04ac61572d45ba5b35f5e6a1158ee32c08b1e6d1bae580646715f1d0226106fdb4b426af8d339925643c34e58ff23cc303a3b11189c0560466a66306f955d7c45aba9039d1d39767ac0bd777a5c087f14dda914d401d5a6e5de62b384f077c6942d9da771f8398ef7cd47540cb884c24b493d7f3ccee6afa8aec75372d65374470cc279ad31ce25d191dceaa7c6bf4d43cd7f18f5d1c3f1ad9d56bbfd673d50cff77a684bd980c8640fc1682bd80332411e470e9fd44bca179f91cb0caa4470038fb61d21b8c7ced198c2c23961cbfd8935b67482cb3257c7d7b92de7a0b2c634a3b9df20e50c15800baeb4cca43d1342dad02a3d779675b61f3fd9d22d2893bf13db99c0946aa12830dba57bc1adfd37e0dfa09cd54ae8f04f2439d80f9f37d8a5ca9530b93b1e2e3f0c007f2055cc775300f34554cbe09d4c06b6374971ff7b1f897312fcbca7fe08c0c6123945924218f052b2034456cc8f9cc80d157b888033488e79664d28d66fc02e10f36988ccc3ba908c56f7d8fdff1642c220d15190fb989f35c9fccd93b0843175a337a867a2f78fa2cd6d82472b45b00be00c6a2031c4f41ff119087b19948b5eabf72c3b1a74c1645df3b43ef77d772e754d93247e5c3816f5e0fdaa1ca672b37878348c6cb0841f183ec88d093d5b773f886795b80b0d0950ead91868994ae7c59c3ba423930d3a053c33c7bb49757a1fb655aaac8dc0b3d41911092a07e0619297e2c16e2d714e37588e21b0e309ffa638ad4735e0a5eaff835f51e590f0f3f9209e8a2c161c7072a81c705e1988fec9667342782e0a0170141542f7ef7fe7a338e6ba83c9a44afc04b0b7cfbc653af2185ee76bd67f121f983f36afae4ef79f134bde519995d18f68ccd45ad626dd75f0d8658b91cee3ca8198f5bae383629128d99d908c0591d01dc8f45221f528d03bb5ebec47add9357999411ee81f1724b4bafcc01dc07eb2ceb5a4194aecc013d3f0bb4cb1d32795a3cc1f922ef87325f29603a5c6a2d8945cee45602fb2bbb176c64df0f54a71925c960c561cbadee618518be9166b0e3ae5ec958ec34bd91e5929135e5163ab603fd5dce49bc501e345c6c44991f55d018a4e71d2317c1c44109be2829da813772c31e4cd7ff3c3f8ed75e5fbcd75f600b0cf2c6f793c7f12b52b6e1967591d1e615a026cc338a3b43618481961a144cfb67822f52495d74e34533bbb0237e272ad201dce8f93ca8a700f4360a986ecec93d8da5d5aad71d6cc6e3757795e8dd0dc4ab77f962b9c1f5a8fca2a5b145658003501bf95af36305473aa9b18f0fc67d24b0fb5a06bd7f5117d654530daccae8ddcb954e3adfa9249cad65626d22f5ff72de9ab95732285221af8735bb2f7fd1616161fec92bd5d2aa2b65204ba9f23e37e9d8a918eda752ef3738e92300891cdc6f818ecc108131cc7afecf4746d0397cde1afe8cc392d5810128966180d214d6b06d3dded2081d811136a9c0b254505c607af3ce5f124a7eb0502981840f6060fa7bd5a0f0663e1294070f5904d44d3f31ad2166a99dcc6aa080d6634b54913bec9e0aed278a1be49c54059cfb452d0fdfacf08b08081ec0d8b120d8ea39e572c94b8978d77bf7e76f4caf7f1ddef1bbbeaf19168ffcc6e93ba7f1ba5ebbfbff6ebade74b567d166eee1cd06362d5aa7402640e59b3e4d515039505ad74cbd05ca0c9fc0a79a45c5d6d513be15763a0603a760f4d1b989412a1398257f4db6b26083c21555558ac4cd29d359ded8e99e596e1c824a309ced8e976d3a587e1e7e6d9adb426c48d9ec34679b60cf2600e5dd236d45226408ff036ea79fd60a1dfd058bb4f6a5045f51d3a369617e15e918a78a1a6ea09bd0bb7a6ef9bec8efbc13b999c2a7b66412a646a4d900cb39abd01b98dfdda836e0642f1540c80d02290e9acfaf12ed26bcc4613060c422062a36e0c563a5133023b46e50ae435c17628609802af904d063422bc0e4bb214c9460198cf6ce49111aa96c400659a8d64fa2dd31d945d0e984965c74baeca7f5fc9363c519afe2c60989e1f51ead82447c6bd9652008ed6938b1df82fe4e1f1d120464d321f881c721bb8b510a287f56059e7fcaa24cf3bdcbeb55822d792c0011a42479539e51f8459cc115f0b12a14e76d25b4d0f38b6004534602d60d6eeed6e1a6230bc5aa8f3203de19f1b167f5d3ca9469f8c4414c9cc62eba43889400acdf08d460e245c016cf8f9c04003547f6c3f93cbc5cac4db1331668b79a46813e32b6ef00255f31f8af3e2f07edd53f84e994d501c820f7bbeb98fa77a9084de53f2f54a0620ac87bd6ff7f7550f6b77c404705103baaf949ee9385effa1d2cfddd4eab32a34fc4dbac2bdc13ba67f2ed13217d9b74c6331a923f0d2f9ac80363213f702602006b3380e1fee82cb431ec8e668da263ed7714828cd8d784055169c26f925c5161c07aa92a57c0f426d3110be359065006c2fed9e3080285bdcd3e10edddbe25afdf1a6c3468c9d1c6e06c12efb3552ac75b0b5e18bcb5c3285e03305e07ea74c15be040aabf73eeb46fe3de6db02679bb4e7a1443e9e6089666285a97763463010bab70db873d6f64f977599c7609e4074748d6d3a5d306e7030ceecaa7039896b08c8ade926a0ba45835c161d9ae07fa3785002ff39ea68c9e4e1f84d1962afdc5688088ac4b3d619f9a228d1fe603d1e6f19499c6eaa1d3f53c4d82cacd71925e8927f1cf9f3d854fd0c4018eb6c74d3b33ee84f264d00540a89cb3375ece521b23fb66c9de70ef785b2bbfc5b007d9b628d25301d49fc7d8fd6ed81333d12abd7009433f4f7e17f03d27f19487421f7e21dcfc24d85b4e948d3d2734f2dc7745e31fba59a5066df1eb941fe05212e3fbfbe8bacedd9c82c3775d496d3ebbee7f71dca06a8caaebd387f943c58e466e87a68cea978562c4072ef02cd8fe1769b675ec624fd2d21075a87d3cacef80c01eaad52c89a7b9289c9a94b7ea44047fb101c0ded4a90766fb4c3644a64a42f74aeeb910fb5b4f8a94a9a99403f130f1d75349af2dc5429600f69430fdbb0ee44dbdd64896f3bb1ed886bc7b165e54a51e9de268a7fb1fec8be9f9acbae3a782865ea042c4d6ac812164318cf111f607226aa6f6a2a41bedc3b154338914721dd3fca2afd9577fe2f755f19bd3b0a613795ad50dd351c21ccd93cace58ccd04e47d12635fda55258afdca58662d04e7aa069ee2c2c9eaa150bfc5a89f103530d71884512669a5e9403531dc8e0965fdcae5668d98069b78598c89ea9e04cab820e176aab4e7b19d413de23698a3da973598458531d0d4e1c62ba5831e3404cb5560e96926631c9becc09173d8a1bccead0d547d7214f5dd683a6533185f31dfb0c426a34bb9031a54c5cff9e048ba90e14c9b0981c6f6a34499d638b6e01ee1cc64836579c883502812e63b36b6a5696cd82fdbde35847a85a37429458f7d9262c34674ea663837abfe4079e5167c2795e1df57ecb9338734fd743f21e2b7d4fb4ff6caaa06a01dc86963d427435e25727838e745956b824a556453e51753524242100580c009bc13dacbebf070ca670cfdb9e7866b88c837f89a00ebddc35be692f12594baf0db8e1813f3f1478e88020af6aaa61c3bd487709068c52b1bb1f307d91fa9bc47dff201ba4752459aae5f3fae51c54600b534b841e07166286a9decfe42cf908574e8e4f3e800e2537f91f47f210530fc530203cc5c9867e85c7496e6eb062fc3ae4bfe8d004c2912271c1caaf81f7691ce258273dfe13ee52056556731a5d8e99b7c2236eef698c374b218a2581d303c9517338928d343456e215cd3f9e4988302da6d33416e3f3eddf6526d6e0ce152ff366e0cb5ce3eaf73b8741d6a017a475b7b7a740bc7ec2a274e750e3a44e14c0b7434f572506fe14171180b64c2e73a654ab89663e1f18ea4a4234db27c1aad506b72b988805fa86e2c75a1618709109e871cf250f74f17184b929b9199bb85252f3bda0b47e7ed4e8199490f5589852dcdc21a0e4209b21c9087d10451aeb5a77cb973230fbf728d2d8c0fc113b8e10765fccb09d63a5631e4d825a8ea4399b372da990dfd0b6f326ff3d19254e3cdf13d97b2fcb37540716208f11737e01440df86cf4c7848ca873ba410b10156f9dc80a0303a27a65bba7842927fbbe6791f3e7343facd13e520421f07357d1bd0ef6a5701b2c88ea2eed3076f7c9832b6b1d101d2374a67bd98c3007700a9304ff3534944cf40051a300bfe67175df47b1ae0e30722fcd88299a02eaf81e8107a23aed4097593eb0b65bac3b7d4aa574d4ead5d79d6cb536505957728d1344d59fedca306eb1cd11ff3639682c9483a8be199e49d14e0a03cb385fde73929241cb2ec845db4ca8397a59a7b76ca8b65828ff5486abc99b388eeab3b35273383b71e661d960829fe1004c12b5a7c1d130f5b0470160ea8ed421449400aade885112e42a0c0338faff8ba30e94acb14b7142387ded59371037f75f7b4908324bae83be14ead28cba89d471dc05e8abec89d4ed95f09dfd92bdbf3425317a7a419f5f5f4a424f5e73c2aa4556bc94ac646484d681b8815f7107c0b6a805ed3fc6dfc540e584d1936792f14094846e48ce498d3f9042dd73f015939c26a7ee759ba7ac0ceefd0d013818f8ac718291a1794e1c17897c9245779c31e2b107817747d83c3c44af2fd3c41cc7fb36fd40b37501cd2b9f2698a09b27ee2cdb1e1f2eac0f89357d5f60544228454ea298abba3051e57161cf593e81dd8ced065c42831c9070f313828ab1b9e8172784ae001b13b7a587e2c8a865d650c63f966d9c82c4d8d4132029779c9a2cc520f4d085eb87a605eeaa7dd18bff62132f774023b43898ad1db09143877b06d2d80591037098db6da1b9c7d7efde55b238d8eb0b2a3eb020143a5cb99095b84fdc09b38b80f62bca4f8d718a4c961ace8069df18fb831646b8b88c0a934326d08da81ac93aee0cab248c1c592f88b6b29f137f5f9adea7193ac2fdf13a301dd50893f8aa87491b6c5801ea39d87ff6a3b3885dbfa913b753f0a7f016a38adfa895254575118633d6ce30c30930ac1c24a90994f3cca1b8a51007d523c14dd9a6e255e248921c00f6b288c8c6a75ac38f2d29a4f2e9ed1f24a84997f81bb03f791734006a99fa14eb891293a17daf3ebf37f08821bc5cf311ed5f677dba253d42f1c4eb83917cf70dee8af6455c8adc09961f15bb246927d9365583f896cd09a71f0012150984bf26fecefe2c4e47d90ec599efb429a7369139284d6c0d3fdba8f7ae8363970b2ebd3d1707692ebf62306beeb034a4ada4e7ea0c5a3aadfc517d534182a986ce38ad6233de333c3a2cac3e50618a7165de5ca20c81010f6cff58fd022b5ee518df1e35e323aba462cb7d60bceef299e38294bf54e69fc998fc7145da56a9da04f9b429e793c2f43200e45596228775876bad0b4b186b9210cc76216715b5bdc2a5c38eabc2c181a137f01e1fe2963e380b52855b4216724fef3d505e86a587920e9243242bad728f0135795ee5e9cc02e13c6a8f9e20f1e01bb1d98017eb365e17475a33c6852419e469d7920e75d5a3a745ba600d9c00c78546c74db85a009ba2013b508a8e36040fdc1e2d2565482db535357e451bc14d9d2cbafcb189b54acd480d62e85bc386d252aa526f02cbca1b0c8aee9041505bdff4e59142ebaa9ccb8675151e36964e00704674f18541382435270b4cd9b5c7930147b8ff419acd312baa6e88e3a6bb02b57646d51170bb0862a0711112af84925690b643ae8aae701a6e5af516f1286946c2290d01fa5d26ca64fad3696cf74f8cef7b32150e20350ffaa9b733a7df52236bb69298629f505e8683eadf5df7ebd396aa1eae940fe9ec2cb192fd2b61a3e5ece1e4f40a087a3f683a3d75810fe30941e3050194aa6a07bc2f87bdb5fd08ac5dbd00c26ed8c6a3d6747ab820f43ebcc6039dd3ce4e7eef3d380a0216f935bd0d14ca231cc646c2236ebf19f05dbaa8041df576051c9628759c7717c01ad24df3c0befeeb4adc1abf037c6b23e33628c0c1ae744d4f5fb52761b809d1cebb09a99228653f4968a817d133cf77835a4285e59fa1d1fcda39f5737f7e2625a90795997ec270981e843698971b7dc20a0a5f5f59742061c281cc8e4ad03ba0f9d0067482a9573ade6ac22bb56c521abe69018aafab9ca5a93f37decc1663ee8208b15d8bd3b0843a03fa26b6072dcb22b8342445e4323043c8ce3245384ec81071386890257772bb61a7f46089820c06451ef7e1c4a39f1c5f8449bffda0dbc145fde849c01837bbf9fc2ea2fcc736c310b766025e2ca0964e0acbb7b65a1a499bae891c0a3c564e5bc1e83b6f4d3fc4723198b84c83031aa5719d5a13c6e11fe3c7f01d1e338183ef03451f5bc77beedb9a036a0e4572970bbaac869e15aca1e1ffd12b8587cb084dcadd056ad9031c83024b67cc7873639dabce835aab53e06a3accbd2cd3a397868d5b1cb5cd72e6d30124ae901efa8bf3df62fbaea8607d4aef61965ad8981ef36210fb8b4137f2ae356a60f5809d73c4d619a4ba0e8f04e515cc40505a634b1b449f258de8b3b6fa064b48b92a53a85aa797a9437a66935789c2a3828ae2de72285fb7f7d055ac721c021609cfbde6c798c259b24c790e57c1fc66f0f839d79d6425fe1b3c877a35ec16de05df470f44b8ebd0d870ae9ca76bc3d06c392d1d3a98548f5fa3e5dfe4f9a6402c4a5c8d6adf9cb0ca5f6366ba01abb26a850ac6ecc4401560858360ffc5e90de64ad3662af7fd40f32df456fd67170d829f3b87e508e1c8d7a299d2efb2a0f3ccd0da3383bee70d77e60df6b3c48f95c411a34705ff1ff9ae599bee6c180e9490fefdacbf1b73ee498b0dd5d16a25a92945689ab111c26c2f1dd97a5946a00f615e65db0fbbe8758e006f22af16f54f008f8905c49f8a6ca1c64a61b3bae1c3635847a78c7ab231e6c6ac2bf1f78d0e9e9764e86df5b10c855984f4a58cc902facd35450c4e8b4a310081b66c03dfd6b939ec2905e4cca862869b8ec42f27db478c587421ac87dc524219043e4d40823c90d60305e20a10f86c64176597e44edf3755c58c8d307ad33a59ea998e2bb78ba96907dfba69128722d908db20e6c1882f5cc3be233e709504cdf145349b88b004d05b2475ff0391db0403024eec0caee4ff2373a1ca371eea6a986830373be8976f3e1463105a0c40a3a0c7d002426ce2cfc56ba4e2e51944242f355476da9b6f5c344aae7ceede73e04f4d5f9c33c9afd0d95f0bdc8c1a6e0bfd11e10c975e317423d8b60e190bfd5804420524aa7605fb40094efbfbe7446398405ca918750dbae4cf16e77cd95bdd288d987c9b5256cb88bc49653f2067e0bd937dbd71c7cfe2f1e75dcfbe9fbd5ee0b85541153014b7fe656b492a5bbdecfda96b9787d48eb9fa897ba3a7fe2ac3424a6e27b342c8609ac03fdbc011cdb3311a251610045d54520dd4d80491b83415811e4a253b6eadfcd4826b071fd5e419fa4e47ac83a6e15626321e8976122ad419fbcd6c438033c5fde6128204f25e19d02c80a742941889e27ba0e3212a189842494c70f24eec5a5b218112bcc7aa570bf4bf78e0f4b5b28103073d08cffd63fbd11a504a6e1a3a54c3b4fc98c32362bb8ca4817151528917eec00f526f2ad9365c92113438d16f30298596a8de6fcb85ab8cbed6466846ff77353e9c050fc4e0b316c4de122589fd6c3d17e15fb3e7495855fe40a00e79d5a3b57dc90e3b25d0cc5fa528281508a52d8263598d1b5a01fe4f24754aa17581c83efca53da8186bb402e05eb445fa5c3a22a8774403964d1bdb1f166d6be6db074d9563bceb9a8ff56451338e46abbd3f3a76398e6a8f2eee84cc25adc8d806476b60293e2a075278e37b9a3f21adaae58e250ba5fae9b37c9c4da5ff34d1d5135e52241d2490ccc890114b287a892aac2924567aec231ce5890ddf988ceff4f77a3ddffc7d2f99a81f93d8426ed4060d2ff240addab32d53da34fa5beb91587cc360fcec0796de1b83e2ceaf72c63169427ad5c5451c403cb3e2524aeee2086e9e710404175fc40e6970bbf00f35a08ddc6e047f2fc5086ec972c7470f29e050e52ff59ea94b96abc8ca664216f2dbaa55188048e94a20bc0a6dba10df5c8b218471475d051959c7138fe105edae59cd5e9a328012c514ea9ff95f777f1ade9e0a151a1301d41217c243d20601390b9b1c4052f4c790531976926345b7413564861e1ea7ac9d22916c8f8bc46aca0b71dfe86eb9133c90ea9fc18a48e7022e7bb3436a9672be9531db6597f7cfbeb70ba6444d77453fdb823af13e55d8a12e45baa54dbc73990f9173cfa19ebeb380468bf55f1699ce311343dd2f841975d7b6b697b0ef755ecc2fb996b6de65cff9f6f765276a43a31ca8f7b6f471e94cdf6b705f0b05e00db8b692e05bed6c37957aa3a135218dac937aa9e8ea19012b8c4b093da62585c93737dab271ef63983af533c51f94ad105f8df8df14f0056e68bad1d19f2cd2a454d8ab470a638d2e2aad348f6b42101a3808b8a33fecd014a4e9c4b1485d1e23b20ee20117c855ade034ab1e206f52e687b819b89916dd17d3e30c9e9de890d2ce20389e03b12e73c9e5d98b92e1afd23519b7160d9ecbb51ff0592d65fce88fe2a9aa2d3b46333120a60ab54221e5160f2f32e0925351f021d093a8bd1b9d15487486df17cbd8815fc5d83ccdad1187582cd8d12431befa05d60c9541876a958e7035bd799a73316e2a26242d5cd3bf1bc74032c814fc9900b0dc5164370a0654ca84be47f5d0162ab42533f3459aa29d31a99d41ba40cf2e5e8306da8bb2d65d19a9be6be43870f6681c63f5389bb8191193fe0c944808e80229e29feb7822e81141bf8414729f9a17f201f09e09f811f18680c18429529286f5328c73455ba1510f7e06d09e193c72650ec5311d4f14dba300d521256aa73f36fd0ee0f62f3a9ab8f3b1fa67ef3944a2734d8cf4591fd3c4049980414211d96180bedc7b3fa38096ec065319d22666e8bb1f3244428a4a7faaf4bd52c430754e71281b3d0a488ef11ad13d3e0c3c92fbbfafe95d50c5fa938a17ea7a0c994af4100865a7384c781ca150fcc5453aae859ce00a69ff5500a00fcc1f040b950a09587ae99cbf153a2202d415bc228e7bfe9a8ba5c020381f4adc696115ee5c2e10339dd3fbaf3fcff82e519501953250cb8d480449ea4466c57532b5339e16ea992f67a8feaacb97958c5d6a0084637dabda47665b69f2c46ad07d358321b03fffcf4df275d5dabf8b6768e236ab9e59fc19f8f399d79df8263225cb95d9e7d2c6e9f8caf3c99d43b33530b2b60eacd8470b3321b72c097dc4826fbd0dac98748650c4e023d39f2f0e61dcec67871dded928796dbbb815ac002ce02c0c2ff4f354fbaf0d95d21f95535227e1d0198b1fda42145e98af668b36f2184aace2c624dec22148b78e82202125d9f1c2eaae3335602156ab82a9fbf90d4feb068159eb6fdfc201d0ec72d01d21d5f0790aefd804e2bab9a0078cf2a2b1e40ce172e6435f50e1c70e648ef96613270ac3990d8fc4c03a9e696cdba33d8cfef52abce6daf7352f73cd1152e1e8a2a5e71567fe4d4f1d9e415134f00f8cb4fec1f7491976ad949cc32312de7b367c098a21fb7df2722a9b3217658f2eb3f0fc691517bae7365c6b06d77f64b4fcf8ec89fee328b3db62af7defe966b0e77b2c444b4109e247b0c64c09a071330c2ba7faf4a5acf78f0e9ea871e331e62208db021ca40e50fdb8f7f88408ab818d228ecfb222088282185373550a2511fe8700602919947fdc198c8365b0a192dc435678b036bc03560f21c74f3dd154ddcf50e5ecd90f26c72a91e43812f53bfc3bc04372a58274ea4d0bde4692d8c2882308d461ddb785a451f3f4b806f9ec12541cac2eef9f49d0a2d9478d60128d94a75a31957300ae8afa93989828ceef915892b52098224f822f8cf572303a263ed8904a8f0e37172873a19edd3545725e15cb18a82c836526a9121a2a462956c5a25aa9ef5ec78662008d72261333a3b2a0cd6382456ae103390b23caa9046506e179da548dee6fd1aad4b84141a1c4a4dbec16ede13b89db3b7c8b37daea2e33de1f1cefac4a3639703b4b4be272b96fb24f051263aa1fa6db84b33d9fb33d27ac84d7689fd0f8e6d14dace0bc4cc42e837bf76d26989c9b55e69b8958109542b2671f0819b9ddc94aa5a27c4294a73c3d9a8d315db480daceb752ce0afd1a4f799abe80fe73684211e3bcdc2b027fb53b6c8ce6448a40595e31c7a5e08c8f815a8499704b6330ebcb29ad7a77f5f3eb7588551f8871fc4aa30d91ba6ecf1a1b0ac8b3f64333b7d7a0568e03a18da80f065c2e91cb95270345f245891fc469e2dda235bd3fb5dd54fc6dc0a4e167ca7c20e54cbe5a9de2d7bce53bc126e55f819a539952920f512d35d5dc1a42f5fc256f4220a5325ea3d652d6449497e04ca6dec30f0a81035b39494347b51978e7a3e97c78c242be46773f39e709e3f20d18640df7b91d5ba72f83843dabbb6cd9f33ba275dce18e386d79af58e9f7baa30f9383d406c1993268bf863288860e84fa6888f60db87633885b174ae16c39fd24f9d3eaf213640b2cd75fdc61c3159ada16cb2abfeca4190f1dcc03e6bf1104ed1c66bec4cbc0358b5d056104da22505ad593302ddecbf84e931f5f1c09ad20265bc8502b60cc59055b0ab0ee00327bd0d03b422fe71aa6f2c338ed21e2be5cca4607cad9a76b680b617efacdc5967fa402ae50c63d61c1ea9f27a042cbd134a1a3ac0846d756b82adaa03c0248db6050f49c7b6fa5d1d6e01f98e9951a82bd5a7aac726fd06a82d3fd70856ccda6d1260cd26d38362470afd07aaa5f208e282eb0d5d1dd5e5df8d5357fd4263dfcbb9dc11b591575e007d68d544a3ff7d1fa11f833ee2b8788affc340c13b79f1bad368dd4ec89b124675b8c97aabee2498b723ad7752a55f343a9b4ec326a1eb255e6c7c6d5efe8400d4155e94fc0066da95ae84e2f028a5c74147a9b42eead9f5ce071f7026fee224e82afcd1f1ed2e0509cde738656ef228641bd6be151e261d4a046f465a0da4e5a5f5f13a27437755ea8a9405222407a9082099e246b835aa26cfd79196905c3df6dc60dc4f80c5e05ffb218d3e6ce20171f53ae6d2ffd5e577eb3b66d3acf14029262fca41a159aa6d683461509ca31c7d9ec75bb4a33aea3503329d26b453f86e8d0c4414c8806af4cc7e63f9d103f57346714812a98b134401c7069b245a943123f571682cf660802044014f01e38cfae15f1ea466630ae06e0a9b5b7da249be2a70025adb15f7ed45227000e4753b1f1b6d013acd53810690f52683977544265c3a4bf280a4a2755e74cd850c6c2484964aca72a20f5dfd61bf0dcabed9fa8b651518850be3db4a019b2a0bf55c6aee28efdff756895c5103d73945d273ef1c82c4c08306a762e5a2b528bccc48dd0ff69cb14a2ab748abb7619612e4169f32a56ebca0e516ed0527d14c4676778c81085d9a36868cc4ede0a5556a930eca9b20e859d39e49d2e69df6043e778d7c4122055acf69213cf94e49dab788d8d20da92cfaa38d362f3b7e8bc1fbd2880cf97161a99cffbd8f95700ba9018e02532f4739df9cef3d7b3f6038a3f026d4b902384f009f819eafa37491d129a4b299bef29dd9a66b48060bc5ade88b0009cc63054d7d23d5138eaf22bdf677923dd264e95c683545bbd620956a0f9acb66ab31867f7fc2513a628f34b872d781668709a75075afac2da209fdcdb1814b93143e34960fb5a271e85429c00d71265355b5102b3dfafd1cad803df878989b30ba3adb011e72f70b34825082340fda4ac082ffaedf73c9dcaba5272f7443f80e769f6f0773617790ff3bf61466cc584035732862ee8895c0c9f8150a975a24fe088d7676873347521795934637c8197efa2dfb94d5d917062f3b0feb268a0d3ebaee185cc022917c1b41fd68ed4afe20b4eac55963030a3fd9bcbf0576500885abd2ef8ac2a0cf2e9f10e81215e44ff1e9a73a44da7d138255cbe8d235c03c5ed2726a0a42e7059bd0bbd582f1a4daa261e3294b718b61515c930f3ac6189f0ff54b315039d81d3409f99e689384f6b948b9688418b53953d7a6ca2a4d10097e2375418c79df1414fa660e7529d6db50485f243ab0a036c300f5607e7ad48b84ff3d2f20bd6bca6507857c1d9f40b706603b0765f72cd88a962860236328bbc9ac4232b77e9cf886ad8af6e0343893bea83f6901ec475deecf5f8f90b3ea2efbdea58085cf3baa0182ac7dbf40efb488b233de03270fef0c43192dbb2cdcd679fe19178e1e97f0d6a6892df36b27e2da0202c628225acc6fe16264934cf698919792ed5cbd3628c842eabf93ddb3b8d27c3febe39ae14dfd0657edaf3c4aa7565b5de6ace29b3c4f7ed5216411c09f4563f06254300ddf85c239fc4a10a54a4d08553c8c90083291c8106b9a6348b2e00cd34fd40799f3574e04dc71600c104f285302131500584a95591254376e54941573a3c84650ef0a91580537c7e373e057e285edef8515618648862cde91ad9bba546d4e9c9cb4c9e7c48d3d7abf50ca503683ff550d230850e7fa4c6426406eb9047d10b5c46cb7b8db310b7f6ff688afb98f22eca56ca1d67ea7c2a733ea67efafb0681bf9a9082b4bce56b28eed8cfe500707cfa035c9300630f23e78e5fba2fdd57a09f33f07ec21012282455c120b427516fb311a5677019cc8698d53f9e1bb301c74de2f2801971b0aa8952e9a8f0c60cd1f0de2b949ce85ada7fe478d64f91f228c2f2b2408cf8fd1927f93b77a30364bfd036889ed04581c24b8e7612b43bda0497b06aa230b93a73efd59431940ee29fe1926e6605ffd018f43d3aaf7f2029fa787862b15e795723f4829f17e46d75a0b140c934736f13c2b2ed2dce254f3767a46d1218ebb22f8c908457eeb97bb4bf0005f5b654ce61780b4034589ec019310fa16794ff951f06c80f8a1ea51188f9c8b30c53baaeb4ffb2fca61bcc06899f9e1cd237b52da486c8632d7b44650a59158d5f6a021456838f6951ac98648afa008128209c0f9c79ae942d2c0c706fc5946198d0ac4154fb1204fd08ec8f51cae7272fd5c97609c5c0303e4947535ec56884a2c23f7967d33ed0ce72fe90d5b0f52c725021fdb57c7a97c69756db83c4546bd0ab409b6a04cf3f425f4626b6dd18ea2b2623d02507f510248fcbbd61cccc5027f671f662d33dd8a3633ef4ce17a00b2b36fd3bdd1f55ff8a90f89be16bfdd950bee16fcc4afce5cda23efc18122e595818852c99585786b828d4cf55fe5cc52b699bc40b3b04e6b5ca2ab619c536b3390397e8ba7acdc03c091df928497af34103fd5dde6e00c86f099d5f5f870dd129085f800923f1434d9a24a4ead943a021165ee1067a6b04290252a4762a57bca48f7a8c0d3e5962e7e6c58d8aab25ed4dbb5998e46c95e2fec66a8977754a70e7ba9b33d1555db5c45f9ac85486b979c14b00b3c4f7c525429924213d17df92c3d7504038251abbdb7f6fc9e52fe00625db32ec66182ebc998156c40e3e311911e4fe6ccf2b3d31333849bd7958878117ce0ef1cf61fd89b722491d3438795a856a27b4821acbc6e1c7f89cbdbfe4bdb247b527aef4408fc93f2d89a5cad6729ddc9e443191e9d7a7ca167bf11cea6f16026f86fb76eeab11983a2eac861eb680aa24069f202513c5572591bba5695c09a7f3273841ab5dfd54e4701892253960967fbdb9a28c36df5a2a0c8f1803d8eda12fab87460de797cdceae36e792ee621f83f59e37894ada5b5ca9c9c64b30ea2a99297808f38f79623660660f9a8e73adf6be56efec1cbe182d051a07e9fd8f405dbccf1e0ed15d51be823875afa34317aaa2dc313ee9d78a08c0f597b0605df28a741daf68157f0383ff91dfe824cdc2e3401cc4dc0a649cd287aef07da02571bd8af624ba3aaf4941bbf1a68c6892e19021ae1dacef1138046e5c4bd3e0163af105d70bfc494fe4ced6c2748129f67e28048d9b75659ebcf81e2305f9ea8ea5f503962b073567b3dee07393c41a02c4657b823275a8db901323784a3267f6de3abb89e0679c0dd643e5870353f091be5b53aaa3af9bbb97d812c1838b15a7d67508a5489fea07d469c4a94d0d858a31e539f58531d30ae54ea1c80663e2029679887f393e627c1bf5ae99849105f9dec793068a3261e32cfabcc7edd58202a070369d538793d53692348888ef5a35494ed23b6e28db493085caab3e556e6ed1d6a6497e10ab85a866db1a7113b134980b541ac3881f4391c262e721a8109848899aef500d9e7c1d6bf79e4dae93fd3a86aa36be00116474d0600e2e7fd6f43d47bcfec4b7dbe169e89be55e29b064fe07df7b3257700cd3e3a6c2bb8f2175a35fe2d6a3c39e1bf51d2d85f0be03db6152f7649bbcc477d54fb729853aaabd5ddf55925433dad294e3ed58d0215514d85e7e72cb9a719524f985e54e0f4b1e8d2b26f7c856750b4032648f2724b9a45b585b8f7132b00d25502e0b3422036ab1b5def4be24b6eda87179cd3762cf6083058c5911d0d1854b4b7583e9858d1ad93700c2fcc31ccf145a1c22b5c5c933369cec3f8cdb7031f6549b63806957905a723e2fb1bbb14b8f2213a5f8543b703a245751b7380c7d2336c808c96cd8978afad4add2a0099eca962b92d5f8950e6d6d0411ead61683a82236bd8a16970b20b946ab2a8fd1b0f6cabf0e0236813c9ca4f8b0d54d5cb2c91f1d60b786a7893afd8c7241b26fca1ae8d2fdc621481520ce11a6f7a7d3278717603e4773908321ecfa1bdbd4dc40bd5725573b4c5be84a2f5f5faec5e009223a92bf9b8ce3ed008b287cdbf6452173e3adeb6a549016e45b1e679a48c68a841f999598dbce29d00db2730d4398dbe83572c397e8db60ab67429936132526e77d6f7b992c76425075cc60b602230883116fdd6a68308848c7aaa2969ab3f96cf249ddb2b193a2967ca76e0758bcf5210ccf21a143544460465c6c706c6287a537bc086ee1a6858c23bb6622754cee588a09611bf901733e1acf9bcb638c7f1a26b949249410a5f4d0c6105785285f116f35bf66808b75503100c4d415448d26c5cd0e4466e4749822b98c20b7bf83a2c630630dcb41ffb9c987093e961b62f88eb3b7c8f65b7e64a85b7e5a46e384a9322cf8124873545961f6d45cb5068eb7570188d1cd21bdf296912a834c9c6617d5aca24d6500e1d2e7a34df9d9b8634e48da76e51ff4d07289df500c0db94a5489b4835abaa87d21cd366b3bac2c58510e01ff41653863bea55a68b98d76c24cd9dbe884c83e8f6109141f529f2bf6848983cd586a6ac51679ee2cf325f06745a6df2e2e3d5f9210f201649a8a9649fb338f51acbf0d8af7c61f28a5931cf41c86b02a50b1ff7caaa1e78537547bf65c348f5efb3b715cbfcb7880b655c070c85a021430003a70a3a73d206812ed5a0808e50204c0976f9b8eee2ac9289f007a79283ca780cf872542020657a1ec01c05caae8d5055041bbe78339df8a5c13936220ee18ea30d3166dc076951538d040c84f87b6da4c9abb4b13112b1c8635046b4b444838e1f5bf059ec84b388689500e46269ef6dceeed118c4ca23cdf33934626bd7cb52134692959a27c083e7740c59c3204e1deecea99822f73139b06475da8ca0793b753162f76a8aef6b5494e8ae7b3e68af441a981d3ab46aa1e6cb1a03e0810cfc09aab6736b46cd0225cdd74306bbd2462f7947122e8eddcdbee3ec816747836dcd305ac99085c8d0c492f9da425e4f12b50442082fda3936ae09544e73628973bd797a0e717eca5043f52576efcef68a43bf240e5580a5965e0f6dd8036475b1e0f40ca3e806f849886b946ba106e59c63259f27d9c3b0c61178f3eaaf9c353060c2e8412fe5b5ca45e615419ee3406025ad7fb07e974c3388233e0f7c3f8a442cd6cf54da384ffefcf2512b0c37382309987497b881ee8fb51a6d057191df2d807b740bacfb63b2e709a8b6442806928da3401d352641302a64d9126049c96a287295f80692c328ff5bf5cad63e0368621e6b0af77d389d31427bd60c3bacb6399ddbe3dae85c33ae9b8f97b3cc456535787a46f308a298cc98223b197ebf423f9aa6593d0700803e6fd0216b782241c4c04f67b3738d8020190edd3d4a37f6fa84cbc777731590373a4ba88b1346cc58b059a9103260072e1ee170de31cba5073c260a4526f0460d35edd9f5ad38cdba8a02a455a4b343aeb176f8b26821af904566b4716b8c70339ae2d072a5f647beb668111f5612f3ab7f166aca33ba04038cef7af0541b99d3478b924823273978b6e6a2b675e8e616395fe0b3031e088fc6bd1ed2eaa5b96806e0ae511e856563b0292b216f095deacbee0d836b8a2495ca54db15d97ebb335003456619d1af1d7779ee1e975ce2499ad9084d9497c60833191f600a7e8bbbf9adb4900fcd1db18a0bc19b9235302fc09f36d87a7789d565152096cfad359a758c4342d86dfdbc92cb8740d4e6e230123c6309b228ab78507ca6916c006ca4cb842a0152eac22b556d1e6c30d42abb804025128dc81215cce4fe223abc8ee80e1132b835dea47fb4f7eb9165f8b4a4b8049b6e09726358bc5a47a933881eb9c9bcfc7ca21766266a96aea2a8309544cf35305ec67c8d69e99d57957ec678aa9ba2eb6e7811c636a5d5e1052a1841a00a09a9688b31ce9f749c1c6dc1d4a6e621f9458888456ffa3fd5ccf8770f44ad7756dfd35c6ef0afcdc32d52d13014254fd2b83e237a2125dbcaf20680e5d88f5e85dbb578fbb1b387bec35844cc5efd55f88fe3ad09db5ac8bd91e04de7d3e1f81e0c95ce46ffdb8485e18f36d0594a60ac5e55dd49edfa74458c50b69657a4df61b9ab63b049db87653f46e06f5f1b4e9e5349fa8213392b267e40fabe71d6a81f921d2357efcfb3ac7e6690f91e1fd1223d4f2d048070a3f21d86376508c29322ae1481db74183881a4f234a611b35b6e74f0362d46873473d97410e0543db427811a2f1820e48dd8cee194fe36d1ddb4409f083ba0a5eb686f7d0d456f550c839ab642cd787b4013760a73c910f2fc3aec35d6612f83b21121c6c921fc63aacfda67a0c09078f9c8b88cb7989dcff748881d2ae325f13c7b48a457e6dc3e99c9d11a43f3bac179877b47722d2b653b365d94ac4a218da651d5a9dcbaa57b78e8921c72cfd6891588fb6678c5fae3031e1f65a739d1500d21ef5a0a41df0a4e746cb08954f8d937073a387956b0c5e448a861f66049a6ebcd9f16b8b817e7dad99af5be45e27b1393195ce525229d92fe0d9633cbb2e4cf6e0f82252398f0542fc99b32d28ab436b833718384bda3e68ea20e869e4130f3abd6a549d05fb42fe62bc8d8e13564d6f1e0fc81d060d28a6544d89e4523bb8a644fcce366653229fe41a11fc6454db0b072e618a482c5dce06a0f1df44e272a28607c8936349346341ba4f144ea2fe6bc009d0ef0bcd8491ffd3d85dd4b28e969196add0cbbd92cc7ba7552db6a42fb5eb99b8a18480791f7efa84fdcb6fcdacc4a235996159521c2cd022857c81704aa431c52acccc1e04254bba1b57b309589e85bbb20b9fd63ce45c556378a765d65f8dbec6f219ab7ea599d9290f46acb78bfd9899fa9a346bfdf60488d1eb8d3d398ff0761b375e0e2185226b0df7bb658d6b5e66a04fa928b7ed38581380b3f8ed11f813f4f522bbfd0e2200ff19405b612ecec9070c32e840000320d3c34be1b897ed074c44939568028ceb61e26851f7a538dfcea6f6e97873bb6636678d599d5915794685a96b3cb7970ad9ac5ce1068c2286dbc0b40a3f6a297ed638df622a63b13ddc14aaa054ec38e4d550d56609354dadf313a004f99c297a5126d7742554de0594662facca913511fda5cfab59a4e4772dc788f7a772169dbe6bc5753b803a53b153f1afcb619258aab61dc15a823dd386d8d864a1961b0bffaa665ff12b3aaecec161a1893970d0a002077e2a21ae63fcd5b12f356bbbafa345608b53a981408f069020cd27f9710a22a242a253275656bc4cc772a6713e81bb4ec3f8fe0f8b18ac78984de4247ab942d02b8d3ff11aec1bf355a1923e24cd75016d8e761f9ad02d9ce50041d60c5f7734df3713e21e2590963f8b54f67a9bfd4f6c710d6718684367e1673d0da302ce65abcf0bb61bd0023632c94c14458d621e62679220828df894eb7011ee92c7e16ba922e4f839b76ca47ed7f8e498cd8b2f1d65178ecd31e7e0945fe2edf1b52472f9ddb7b874ee55ee53936ceec34f8a86a07d6639e730e79c63ce7c89dfff466c5bd350ea072f6b20387a8a146c4a07a092fe6c9df5dfb882d7974b762d725c566c7d0b36878d7c64a8d572a8bac29298c9cbcc091d03715cff879b60755350a7bae91933813daea4242a02524a19278622f3dbe4e5fa12c5135f90ab4e051f79c1e191b668a22ff3219a93038c8d9844b1fa302506cfded8f86ff42dc71c68ef679ff604af084cc66d441050f91961711c8087302cbe089012f07e343f9e8c764be14e4c72f4a174dd1a94c8e713262ec09b54abab9c5131de60edfd314b0a5ea482f27d05117469ba6106302108bf55ae63aa268c4d3f71a4397cb1cf2d600fe2bfb290692c0ce9e493007c0d88be6882a3402cd883fe062368fc7fe81e2211f655a5c5d7156c1c0209b393ffc5f56cca0ecc0ad3dc3b4f62f9fcbf030cf44560b4c260deafaa765897883b6a2721911623c0bcd0bd4936a5bb89546877110cf26ce514d2174be90bb45dd79cfcb13139d08f2a96ff6eb9d273c9afde81f2d69ef238d920bb46978360dc090f02677fe1ba0ce6da7e2b4457ede95cd73acf27d0cd10deca770a4508c6716855b8f61506b79744646d8e9f949fb48f5cacdfd8e7a80e57c9d11ca9acaa6f0482189f30b84e794de5be3c14d9d728601621c93ec90aa61546ff6cdd85f772fe81e5996c19087837e2049a311bbd49c3048c50f015d4154b280a8e9abc0e2819ffa3fe3ff6464c68c9f3c003d74d277fe7c5c2881ac1b912ccf09abc93d24473bd59b43ac265cf12324a86adf6079c1e798b29d57063c2b5dff4f2c2b9803a3b587ff922f054ef2bd3b6c289aae3c2b0f378c6547414b228ca585b46bd2d971672e22cec2942ef7e9b531975fef5f114a90c674f79d0f3c4c82baf6096be68376990c5ed649613555ab5d41bec0d3ca28385c30e02b31972f2f647a36ef03762623e6d005971f6a4e6aad1c5948378d52a43cb7dcf1ce31070207364c3ab3981b0fad1cd36f64dbf40b325a21fa7cc04070ef4615c537ffbae8c44972d08bd93e410cb1dcea72d74ba25a77a6839fdfc4584319149cb7558ad648893ce1b1e46d7caacf7084817d5b176b7b599a7e7093df7d6754f8c37753ac50a6f9158e2f832448133197a2bf599adaaded512de4ef03cf34e03b3b40f84b4ebc70abb54e342412d984ae214b144b98b4edb4efb98d1ed26da642205987c9a0c9bf54019e555f4b6dfd3cf02edc371fb411f5a220c8ac9a4a6b35b55d0d314d77e4c304e924e18ede67e14fa3cc1fe01248746dd9e3b67d993c5dca4b39006c43f53048beb1d295332694396f427b8bd4b853a38d6cde71c735ebb822448ba6e90041b496643101e16be3f347222007a0cd87f034174aa237608e58a911970a3402bbbf30cbcf96c516bac6fe6de09338fd30999c03be77e40a946f6621e5df6b5d914f858423dd973dfd87005649fa1368685d0184fbbaf103c50d35ce3442dbfae517722028c8542a7bd327dafd7969f4084f67640dfb399dc17baa586d07c2f41f49e33209b4328df53dd811b42644206c96b03c64f2b1beef8b6b37c973e2c41ff8ba66b022002421a513ba48d01b40750fa7bf94f746c30797a78f63603410859bd07ff71e7f7945377546a608dec9f0f4fb10e19cadfcd0909b7b7137294400cfc09f178b894ab5f5da49607c6c84b2e32085d75c3fd3a0806ef8f0ad1c48540fef50ba708dfb944df15180949e09168262a5c9cc1a417c23cf1639f15b8d0448140b80f2d7ea0d8a55c080bfa355421a162b7d18c24f91827958433cf11f47baae52e706f5c8834afb2ddb581bf2087c9aee6700c3967ef0fd6edfce0e602228ce30597cb458c7c7cca2dfc2306f13b9609524ad53675823e2c545ce79fece80e2fe548618fbaa948d5e6700aefe755393e26f674ef67630083a4a5d2fdf8f5703e3ac92885a82f68c7def1b3e05b28cfe56f5e75e3fa1bc151a9ed855e071caff82afa7184142b3046152d69a93f588392ce29405d6b9c8dbc76ec7511fbd478b537b2f95918d6ce0145ca5711b2be4ee26fe73b27fc0384bfac20457aa4751dbfae0b25544c13e196566889d5b19447aa1684fc4e4db870db1b32e1333f808d2a54253715f3050e5525e84d936b55000244f44638fbceae0cdd47067818dc9d2926044f8301bffd293223e128aae9ff09c9760b266394c034061480a7f771e3beaed9881285beb32dca7ed878eee51911a25193432ea24f6c79b3b8791fecf1de1a1cb63eba51be4254b66979266efb1d0f82b1dd9724aeabf5707af8f08ebbb9eb12d763468ea511135b4eeec6fb9525dc02b1e82d2a29207623d20d49d0ddd7581de43cf093b12d99b8192dca1ac051e01a65d3175be21febd93f9c3e6ac8e818147ab85ea3e72f9e05145c0504a23cce6fce982c415332168065cb13b57ae3afa275904e4eee3a0e34d7e86b746221eeea7e18361e4aa49fbb0c1f26648e152004c5dcf9298d8cf4c55761e4300a2d3ff474f44e0a86767325c8dc13dcc1f5aaeb8beb5d7d002f7da3c2d775db62d908e0c8de02d65199777c51d4f36b09687554906cfcec21c9ff2320f8e288d68b85748aeb0ccf2e415667bf5270b3316b29ab009c771a444c051abe20227312dcf232c0694d4d3740492d8c1997e64cd8e4ac8d67c3d472ce1d26d6e30fca1a5f1e60884a44a843c43ba661bd1d574c22d4115b68c9215da6c3b016f4b41e2f5971b2f1b38725d23fc8dc06844eb5070d067c6702c93fce8d1fcd301d1d360c3ed16d244041d18a5313145f654eaf0da1a8c14babed91fc926ca66990f32516fc14223827754a50476b55438d4ed54de9e44eabdb6a62280e30d3f645b51ea613de4caedee90eb411aa123da9421595bdd0cc0d01581a3338a1e104ab677b17d2b06cbaca5bf9869cb18a36b3a6eab542d2d37c1f2092d1a9fe164ea905a7043eec8b7244594b72bb2239904ca0e4813fe0d00a05a4ed24f2300b282544dc7fa6230125dac4cff12945638c494c1a5c9b23a2a0abf742ba2cfb8611db74ee42fcbbf0b42a998f19f7910c79629efb5547f1c7d3bee0eb139cb1ccd1d9be2ed210ee4c7c70c05843e11458fcccfa07b08fad32ae100c05eb7238c1f8e6bbb8e35913f4c46420db8686937708c11e7e864fa8a846556d6823b1550fec5c7c334913cbe82be646484b3ba84a66e4abaaf51a13b6cf3617a2eeb1405d3aa82bcc2d653970d4961c27a9f56ac479b91e7d63f80f2a704385f97507b4b00f72ba4578c1c3f4c01d822854240a6c06ce18dff88c8ca2acf9d049a7e12deb816345452f51d98306a6e9bb0a912d6ba52b0c485b8243c4dbbf91d110930b403a4e368d36f7e6a009a28b8f779e15cf48637cfd967f5b966201a234ed1719921ae9e2baafe038b6886d2e26da3fe637c93a224e92c303235b3778d06cb56108a749117d80a2dc1e13b67de2a21006bb02229b45d600e8a4acf734e2eee1c6a1d3e277d757c618c52ea51190709fa4b0807ce915e3ed5f9648052431795d0b29351486664fca0df79a5db91feb8b565f50c8c0b46ee7300485e1f7d6dc735a381d3995a317634724fd54e8c0e73177ee1b17a6a0e9a2b46e133d61dda2f7afcf9b30e57bde7c50d02fa55bf6d0bbe3e604c793db7162c3dc4c9a9b2decec4442975212bdfdb54845b0eec88f911f71a54019f17f5f71345bfb453ee1935708dd9c5b1657815b1a88d19a54bbb3f51ac34e12f761a89a91aa227c37ab8bde10a3368bac1b62acaa545982ff8516be615742db8c97ab081d76f6fe6ba5ed123acfe03799681975cc8482927bc54ff47086319533a8dc7856e3c51ffe6e63f53ee8aec6127f09558739e38c057592d16e69f6da259d5919348fb1dbcfc7836b1e25a8f9ef9556ccbb1cf01ea7f3838a844fe680b9dbe8ce936d509a008424734eb698771b5f745d08ab4a0126b8dc346fc0d961b1cfb3e5e48be8d0dcccb2a39b2bff667b342a600f1344688b478eb203ef4c16d75854a0dfbece61240d98b200ac880e1971eae04ef8a49ef6a1c6bdc4dd11dd0c3774693aa1288db249f93263ab1e8f3e14ac013ac399f6bfacf472f86740f13450d09e1b1b1c852f0ddc4e028ae45436c2735104eacc46ce446fd14212a7dd69149753c3fc1babd4bd5427348c6ad3fc42c73e458405335a9f7697c0cac8d08f00810affc5ebd3abb4f891378c623218673712f109cbfb5131d803cc47c31319dd45ab7e682820bc47efd66b06e20bc94eb07830ace9c357b9fc5488cb2c64c4c59935978df9cb7a4d7cd5df5f7b1a06596e06ccd1d2c030f28de343d1bc23ae31bedf924910bc9e5c7598110c5ed2cff439effd1c8b95269ca1284317d4fb11b77d206332d8c23a3c5210475ef5e4ad074a0fbfa7bb0c2c072c941b61333febde745eed467df9dbd7f4a38618c28209ff50747cf6ecb535f17fc17a83c61fbc0259be430df44be5dc2a2015293aa872a4c56b2bd204e11edfbb48dab2a514340ba81b523524ae867ca10045965c8aac866535ffccf47c77cb773939e302e4e4c45c3e29dc804a2b391fd5274d0f31f07300c5a672ae9860fcbd46bee36742735fc599b2da7251050ed3ba8eac7a65f9adb69c095c22a835af00fcb6fc85ab5dc3d94a9e4f3de17b34fe33724c2ef9ba3939b15949fc2115cfb2117ad09257b025725b6f15a2b16503135e296167b5e1bdc2efe7493890bb1c5707207ff87dd1134e047868fe949bbb0adcc8021a402e6f5a505a514d0709e8425663641a5c75b6140cc46267a6d1d08888f511482dac2163f71beb01232a501d94dddea128ec31211f9adeb89fa8d2f82e1ce449660fa17a33e2cf4f07f7d7bd196831b69cf0ba85b4bdf796724b295392321a070107ac063b98b49bdae5a457b558a66d9ecf45e14c0548c80a162d38172f444660d01023a7bff9dd9612c29e4ebbbaadb5b15c477c6d0547c019db8f3671d10a57cb569ea75d9d06d3df7c1d3aede425960eed8051024f9797eca64739fcfc8c44d3a1d3b44e63e9afb4b16ed64a6eaab1e23102cfaf1a0c6e7654a83f2f11fade58ee650f0b46dd7083a643a7913c34ffea3495f96ef25176d3a1f95a29dbe17edc2cc754ebbd1cf7ff651a0c6eae15d522991ee9f073054f4a7d00954f400e3fdf2ae1629f97c8e131ada4c5e04982438642a150376436365d6632994c9fc5c4d49091bccc0506b65aad5623d8ce4e0c0c8542a168c06c6c5c984c260e8b8911c1482f30971576b55aad42ecce4e0b8b42a1502cac8d8d35994ca6b63171c68c01b12412498575a9abd56ab5537722e89a4ca66af2d08ca9312452255d9ecf5397d56ab5535db46b75edd8a7bf82c406920b071ff77303cac60614090a6583e783749ecf4c594c97993e3365a62c06cf5751c3e665a4cca5cb489f913252e682e783606823ab1d18ab91d52a464603ca06066a0485b2c1f33f2e300e3361312e30138799b0183cdf23625f6024ccc50546e2301246c25cf0fc6d450d59edac425674b583e76b2d2e16289b15a810144549daa61893c9043255589275a196242da92dc9bae0f936fab8b2ae7656ab55dd01d97b6fe36aaa31b49ada64c2b3c65cd523299595545dae4aaa24129ed505cfcf3e97a67d3e9ad635be56d7ce75ade4b55a79d70e9ed4521dbab8f250099e389ed84b257a74a1becf72078a074ac660fad2d4a21a9ebe7c0144b96c70f82c6380e94b177872c3d397a41e5d9754b964e9922d98beefb4c886a7ef638028d7e565c0f45d079efcd3779b1e51ea37d473a8ffc0f45dd5a2184fdfa30051ae26307daf8127de8f682a854acda4523c5ad4e100a2d09c1c1d9ce0c9c8d377961ed1d3c95d4e4a9c7c07a6efa516893c7d470144a134349e024cdf57e0090dad1ed15249a7e424ea2a3070200addb1430bf0847bfa4dd3a339bb66b64d0b234014ba82041978f2e2a54733953aa5605228255ae4e2e977121065e6e4f40c30fd36c19390a7df46f4689e4ebd726a96930ba620534094494373861d78c262d5a35962cd526b96746c5ab4c209883277ec98820a9ec8981ec9564ba645433fc2c41d4094b91267f400d38f3ce0898aa71f5b7a2453a9684abd5050ce0a30fd780444913944f448d61d23304d17307d0f4014491367948468917dfa1d80271f96037a244b1e9a4ac0f453208adc1167cc6fed904d46de02c20778d2eff98c03a67fe90a49ed91a73c14218ae7c419f3e9d3b761b341250ad780b2417dbe9f87c2c1bb6cf07c79c3c786eb22b96ab8ae8be43d31e8f53406a5570dded651fa3406a5d41bd144281da12294d28e860c06a5235484eec0f39fc35ab4e84aab45ab4557f0fc182fac8b540e976a914ae5e0f93484549013cde974a2c1f361b0b856ccd28e102f6b270ec4cbdc9da51d78fe88a4b135575aadb982e78ba89820999239da89bb3225731cfbeacaac9524cde974a279f1899e95e8599125b943965c966449eeb82ebabd3da4aeb1b7e4cabc2deac9153cffd335ce3cdc45b71fb4d3fadb7e78ca4715951351aa4d9c311fe55514f41c8d05e523ada5da4414ed0838433b711c0acf9fda0a60c9ac36787e8bed4663cdd34ab064be66852bff83f2d1f641d59aadfad4a2bcb1605a74f1f02e47d6c6da7ef4c84bbee5f428f4b3b483e7679f36dd3f6493024f67c1d0d7f1f26f7a32efb12e872ed67408572f73f33bd4857468be977c34d2f996f0f44d0b106e60b081c2f592c6d2a36ce55aa945b484e767ab8d955d1f6d39db0f3ca9b7390bcf4f8aa4b30f5a22d10527270b24d01597ebf8c875a094240c59acfe1ad382e9e4218cd5a20b092d40d1e502bfbe6070e311999bbd265f2dd389247cd400e3e000b77422890b097921e1041c2f938f7a452fcf7ce8a1158ea90bfbacd787fb98f3a5c59a37d3f395a330d0950286ef2a27a213949a582b6aa3d22e1320e9262ee47a38fa04f6cfee637e4bea3e287d69b1e80df57c253bcf36e333f1fb3de54ec0f3f3d5923161c6672e183881a6e00908f78fc85c97fef96a69c17485dd9988a3abc59f80db6ba4e7295812f1ec72c5d747f635cbc5b757f30a5221637fee5c8b787be43370040937df5fb5d0543d859bafb47fdd97c0de62574bcbccd5e2a61734e039824798b8527a5604d7aebae81751f6e8ba4c234cdc2cb116ed4e06597ff6c911db20f93e58123f366c38c3953d548f0dbd637c42a649e17e76959b4a7dba057bb6f88235d8c259aea149e14695f85235671d9151b59010da37849373e46d48ba0141bb7aa76bd2b684c1d52171379d736236c6f8a138de1b11cb16c9fe6e95c26d10739d2e70e1145ab8bf21a035d10ee107b2ec45e936497f1bc8b24f749812f22fb647c21293d2ed01b627fe0dc7d10b49c98227d40a3d184dd0a30a3b18f6c0b047156070a635713b859d70fbbb74a85feec81d238c58f197e899022657a5b094efaa16894469fa6c5a1469f0aa45d25423532deaff1f5c4eb5c1f2a5c947cdea90fc1696ad734958be8a3435cca97948ec26255a14bda884cbaf5da3547cd4321d922f6fde86abb9342a587e66bbce49c2954ab428fb9be0497df9320978d2437ce24f1cb720e2777f8c8d7d76229e5783a529d532e6b4c84a69fa5e9a50d28432c9878173fdb388ed97a62a4d9f17e2e9ee8b7adfdfed4f4adfb8fe648c1d985e7c1e78c82192e2f0630d1e4c4a379b6093fe90164d92d69f44c6dd12a430fdec45e9e63b7f5e2841dc08fdcc3ae1fad00191bdec7c9822d3bbe1c98879f0d9491f8020e9118432da84da28bbef4fbef5a4cc93c586cd90cad81452eab1567ea63521a594b2ca90feecc31e5658e17c68117d206abd22d70d79612d87a9adb64af9f0616a8aee7ab9aac50965941ef48f3d984209924c4a08ad6f36b20b36f46e8a8c35e2076ae01cf161e683bdfdc9fee053009e5c3f55944018ff3e84f13f1e188abf3dec24be3c0d9668ef1df62aba8c03de1ed4e5e72ede364f47dc6c68de08e77d0ee86f7e73c40c2680271166461e38d3c01c2602d6808988de47cc43471e2dca9c04fa9bf9fbe7381838b77e16b1ef2fe3e307a2441838637ede707c282b8ede48ec1e0ad1fb08134530a13fec220c840e136328e05525e25f371bd15a77c08a0ff98e1fd2c18e3085afbf64f0f57ebd5f2f274bb6d968c2635349e9f59f7cad86dc118ee46560f961eadd889873ad82fc618f2a9470fe70ae70c209e7f4441c428c029168e69c93ce49e994c1a494d229e7a472ceb9843be79c93ce49e994011354d881610f2a346c51aeb80a240c7b504107f716311c55bcc0f161c41686e44bf953bec399aa842b6950c0049ef4f4e6f4a49b583838383838383838383838383838383838383838383838383838383838383838acea31461882b0a475860c4a0c6aaa0b7bd8b430ec61c3c9201981b6c70eac2fd9581329ca00bf287fc833cc152f811836205a264babbf2c51aec920c18e610f41a97186bbec2412a012e017bf4a191c6513a44d18b037218f80e3670cfb6756a3378ee12651f205705423029f250d20f059dae0019fe50e1cd02abedfc7004718c0f1598ea0019fa50982f82c8f00e2b36cc10d1205388a2a3f7c762a30e0b34791f2d9b7e0435f1cc0513cf5f0d97d70e3b313c1c6674fc2021af4fd8e02388a2a057c761624e0b3bf007f761af0d01e1c386a12023eb7170ef0b9c76080cf3e821dfa630438ea1794cfbd8493cf1d85027c6e2ad4e86f994e028e3a35fadc35107d6e1c10e073fbc0e4b729e0a87506f0b9492080cf8d82007c6e16d0e88f3bd10938729387fc677c8e3e00f039e2947c8e5ec032b2ef73ac097d8e4bc8e17334820e9f6310707fdc017492cff164c3e758831b3ec72470f81c91c0fdf10838f256099ed81a3ec715ef732441f7194e01c380fb3d0047b205fac00d9d48812389ea800ff0e47a91cf50e6c56703e0fe4c80121ce202f74308c2fdd9e27e1be04892b06a788d80972b04bc7c1f50a306b6281c5eae0df0f20d02ae80f0720c6c5b7ef0726580976f8a9739db82fd7d5039f9a8b67af072bde1e56be3d4c2fe0b882a1fd5190578b926c0cb177bfe3c783986571302bc5c0fe0e56b8026f9efe0e518f8d241f1723df1f22dc08b0ef6afe1e5187ea5465eae222f5f027829ec6fe2e518feade3a3eb65005eae02f0f20d40ebbc607f1aad83fd22cdf0720580976f89e922617f195e8e81a9ca432d1fe9e0e59a83976fc8cb9c0afb7f3315072fd77b8397af0d1e89b75a7c445524ca477355839763785ee63a2f5faa82fdbf2586972b0d5ebe30246a85fd47240afb4b9859f290bf889763702fbccc5d175ebe30d89f93f5b2f0f25de1e559c2fe2156050ecf5de9225d402cca0aaa949dd1d101e91aabb8b6c5f6c09eec8b35d5d4c092d8b229b02a96a4733a81bac677060b2b55ca577c0596c46a1a02cce985c5fa748d359dda825aaa2a95242393ea026ba56a539b60497cac758d33ec05092898d3ce4e7691ae185ca54b45d538ab2a295aac150f1eb0e4d48419144c2cc192f8b8768d334a55e812d4445b6829a703b06475069d160bcef8004b1a5398265233a84c82496049ec2c0b96f8cf928f2c0e0c39a672036c042f633061b75ec0eeaf61e1fa6c3cb22cadb1d3f0191a28161567f8e78f6b1117bbfb064baf6e3864f77cf95e95b2af70fb3dbe65f908b6bf450500c787298c645bd807d083062c8127bde51518cec0a001432dea8c0954caf8580ac31a44f08068812840f1041451441145145144114514514411451450400105143737373737373737373737375040010514362a53d0c1577af5ff0a4b3c661a0ea8fdc5a7e11c07bd03fd9dc11bb0fcec9824ce90dfddddb173f84d48325fb6804e82e5132cdccfb58873c28d316b120a37cba882e37bcc219252bf1f7a15e7c843467f727a9f568558aaf55e387beb39371bedfebddd78ec5eba3e3a3c8434012da1092c5dfb70eb617a26f567fd597f966eaef88a1efca6339466e766a983af8fd7479d5a2f1d7c7d3649f5f31c01be5eeee8b4a8fa86a3fe943bf087eac9eb3a6d4f649024570dac1efecc05fbf3e7ca30e1e08807ac86c00da2c0aeee735edaca9612c2bd6921f25dbe6736f317b7697ff11dd7f5e37d075fd2087cbd9fdc55ff717f39bf1e56caca3ed7cfbc665ddd1aa4e9912c75e87af9fdf81ceee6dae0ab26fbed87f8d4fbb7d65a5ba7d65adfa447add3a1eb2b8dff6abdb5e2a821af7d48adade309a99712f8fa6d7bcd6bd69612e21511f126e186a33ff3d9324d311dba3ea30103e7cecf22f1e577c96b1efdadf8186e8b23814b9245b6bce71cf6bdd67bb1c7323bb7acb1fdac874cdbecf72a6655c2d57ea6699a7b7d3b3f7356fd2dfb1ed96eb497c3d60cebb2d43c1d722527b74aab348c5643ee08f71f8d0ef5aba86439685a77d15c2e174b87b01f5487eef42705be9e86e1325d249ff9500fc39d5eaa8ced466329668b4f133c710cfbd6da23ca92b325ceb8bed6213e387b88b57a396bed6b9afd6a9e86b392651fda97d123693f6c6db5568bb1e1b09add5efbcd7a56b332e4b7d73edb1c96912d65f38a8838c4b312054baed73a29d3df3513512411e08c15c7a15af82a022cc9a179dddddddddddded62b3d159f762c361bfb9ed06f6d9673a68de75edee97b2af54d7171b8eaedd0d5141b8fd299c53a2b90dff161d65d1567f57fd11697cdf2a1dc4dc9622a3d7a5195e256a8616b71e1a4b41492d6a7c3d35a247b0e94bad4a702e9785aed8c73ed31db4a655b87eec460eecb3b761bd5eb142b8b955aa4815a9f56b497e3fc2855c8d460dd44c2a005a64899207bebe9f477fd7f3d0b21480dac95227658b3c79e8fa4cae00965c2f8f8033a44b87ae973b94505171c142a5be75ac12f2db675dca7d04768fdb7068df2a2ba516db0f9ac6f25aefe538ca0ad93259922591265ba449bec8130c0d5972095515a000544115544115bebedb3e2c2276f578e852499632010820468bc2edae4ffdd2b2eb953e8109e08c6bd42c2dafcb055ffed36b5604d952dceb964a0d89c6678bbbbbfbcfe9251b959a8b44a3e3b445591dbade9f86b7f477892a3c7177f7f7e994d522fbeee56429d6abd55b9108a059ffee1cd77bff3fca8a11a374ce194f73c2c8c013678be79c73ce39dfdaa9b249d5ccd044187cbd498fe2a943d7c7a7d1a286b9f0a47e7c0b5120ce709c2b361c76ce687fda194fd9dbcff3ed6789733c451b155b4ae6e5ed2d8ef9d42b2262ec62893be4f5fdb382d06fbde9c91a77e0eb612d4262fb437c322080a82124150827dcfa50e65bbd8a4bdd5abf468f668bc9cbe048f288561f76ad9582361c956a4f5ff320a6de0e1045e243074a3c5471c724d3ac95538deb34f6f76c36dc76ac0d47f5dc2b22e2cccbfddd22cd96165ddfff55df3f67efb3254f197cfdf5db96b289346d0cd7531d445c0c828227115fdf1ff2b989803d1b8eed1ba645217f7dc7b408054f20debebf8888ddcb39563c1111b3786f95da9fcb6245d7ad900e8274d305b0e47a15dd4402ceb83eeb668b6ec278e8fad94d530729ab454344acfdf57dd7cbff1f2f739c77fbb7cfd96f5eb32e9614edb3cfb4ae5bb6d36cae0a2c992df0a4360a5f3f91802730669af0f5d3a545f27f78adaf1f5da3ab5ff075c2d767130c513626d7981ecd8125d77b47593a97fe8c1f7dc0518471054fea5fff2e5c8040d7fbc392ae3fc4a7b6086e38324f7a45f4d7feae8f5b4afdce51ab9743f39a888833af73ac102e4b87942c222d63f0f5972c75abf8addc7deefbf377957c64d2a1fe1d642e0d576393094093dd6c91dfa293a526c0f5584744c43f7c7745542c736aace0eb32e2dbb9e80f7cbd55c2cdf1e9671df262c1d7db0e62b9a554afb617b1484a87e7757d06a7a79e6e7be0c4851f68410b4c6841fef6737e54d1c1fe50b8998a2d6d36387f98412dd3ba70739b3aa7d522eba356f1906c995661a9a8c894fcf69a476b9fa5cc0c3ce999042cbf5141ccdf1ad55bcafc0d9b5848f775128e1a35732fd739e23f8638c7e6d130b03d40b8599672604f449425c9d2a3fb59caf71d2dcaa19d78c0936d07f0c4f3798fe7b52e7fbc1cb72e626c0b22fec4d896127f62be721b67794e879c95c9d4f4302fe5a3542a954ae5fcd871237ce559b0dcc1cdcec229f8a32b53fdc9bf9dace990093a1e229e012f714d3eba5eadf7420f0f6e0e4ff5e734b224b3cf601d7852f3023fdfd4cd3ee3339ea221e6c21e362d58875c75c8a7f7e2eebdd416f7992bd15f084b9afe644d94df3ffa9332054be4c75c2965c94bf23da74539de59de6ad10f7892bd7c0f033cd17e7b4d7baccbae0a22febb2a25be4d7f59b8d9031b698a8fa6b56bfdc577cf0550b8fd5994db7b0f687c88eb403bc8d0d498b86f298e7308307c913d786206cfc0b0870f1dcccaaa566bc740192423213462cf528b5a247235cce55d0d13bd789aa5596a1879c99b2f6a66b811e7899a28d52ccd1813576f9b704f22916dfbcd83fdcd52aed6ab5e3ce5783af97c8e537c8e3adf5a7df5721ce8b37dd0e7f6b22cc95c9a1a9a7e18d40c2a05555f5f29e5df2b5378fe0c0724265fca2bafbcefd7cb515e59ebbd1c1771c3f848b63c345ba05b2faf7582f11107e47d89751ae84fca4b35969410ce68c3c43ce8e74b16d92291802739883d883a80a2fbf3e329362122210e01cffbf15629eb551ff4b613013da813013d7dbfb17285d5df0481409fc10cb850595a3949235e764cf3e7164f51057afa12a64570082128dd8200fd36b714d06f9e08f5a28a87f5868898d26c7f6b1fc013d04f8b02fd06fa4f10a0a7f4419fb506023ddc52404fbd24021201793f7c18f4206b845b84c443c416b636091105e2fa53e599ed0d47a41e136650c1200c9998e20706bd5461f9db1330f741313e589804c31e3e56705635da713f412d58ac70c1813e2d58ac70017a084f740c2104f67cc63e9ee857add24e848ad0ff3c287b8c66a00fc8d3415802f2b2dc3e160402bdfc6bfb010402591dd259fa9bb42463f006ba9da326ee9f17eef7e99fa64d388aa41518c2b0e18aa729c8e0f99147245d90973927699a192ee4be61e6083cb93f9f83214751154fab1938636dc825990024387378debfdec92a21ea8027f6670f2ea594d2dace63bbcd769aed32db61b6b3b6abf8b21db5ddb4b6736c55e0eda10c182b360c27840dda6e64201a08c866c3579086c90e3234353631323bc8d0d4c4ffb8bf5d270d58e3a26044fc75c8c93263be265c9916654f014e013cf9fcfc896a518cdebc69991ec996f69c4d6bce879e84415d5496b3e443853f0c7bf838e10c3684517ab208f0fd171e269c6c9fdd083c4980e7b7aa617cd42df3a4c3b56eb3bedc30f33f162cb69410c63ebb912bfdcd554ce4114f67b8f7b3098edb2c4d96fe66c394f284e96e143cd97e7e13019ea8a00ab80570e42b80225029a2b8ea06a925f09c89c1bd206d0b02f41744e9bdf6efd3bfef9dc87d2972bdbfb32e1d72fe122ede577ed323173fe50af7591ad16266ffb162baf0e2c93d7fdc7f0b8e63e19d5678f7efe790902ef208c20de96f3e8c2cdc88733c89f8a913f1bffefa7f18e44340bca8ea6ffe87a58ecf0f2104f6bf9d8e6f5ed576b58d08893fefabfbf7bebc41dc2f22e2cbcb393c4f44c4223f7c59759b8812e10cf99393d306cf07cdcfd75fb13d0e5eaa17d352c601f4b2bb0feab2bc216ff0f5320996cf83f3d0177fba0f8ed20596d4ffe04fe7d1f1e04fb7d56cf8d369a7cc5732fce93016863f9d95b1f8d3d53655fce9ae9d0b7f3aaaa2f8d34d1e9f4ec6122ca9f173a34beaf3d03e38fe588f0496d4975d476852bb1db20956a9e049c253074f7ba3cb1652f7e410e099ba3d011c3cc16a12969f7577777b73a04742c88f50070ad7bd2c716c1cdede425c8e12214436f1cf5115550e5bd188f6514c405d892ced22e7fc9cd50e863d563638c71e2b1a1c371bf0f6a7ea4ff627bbbbaf7067d6a0eb41e02f04112d9a2f4580fd3de4ba176d2488ca17427e23fd4d0f5e20aebf27612c3290610a0c84d6683dba3bcc3ee3e1e96e8c8244eb99ad64a8d65a6badcd302cc32848b49e9101ab36b3d65a6badb5d65acf6beff9783c9f0c9b1f6b9d2d34524fc4c68a35d6d5db5a6badf5c62a0c8661986718569f6656c3b02ccb30cf306badc52848b44e066badb5d6626f2b866118866116b398c52c866118ec0fc3300cc3308b61d8570cc3300cc31ec3300fe6c1300cab1584ba4b77e9eeee2ea30df920d467a5d5c629bd0c64b0d65a5b3f635f6b7dac56cc5a6bdf5a6b416875c12233d162e75805c3300cabb60c16c3acc53a08e2b5d6596bd45a24a3565a6bc528c56a02bc738c152131f6d5babbbbbbbbbb3b4481d8a7b5ee9bf6fed9be6b9b97b5900d87fdac4a6b2be6eeeeeeee8e61eeeeeeeeeeeeee1808c37ec3b077cc6badb5d65a6badb5d65a2ba59826d2bc83b861342dcbb40ca46253018252cbeec7b3615a86d569cb80592cc36c05e96c209fd6c030ea1173e00bf1c2d89f7517bb6137942d3f7be161c295ef008f53c21657e3d2cfb23faff5ca86259d4580e7f448d7095f18f62e9dd62aa3e3a0ee3bd0af9e497f3bf417df9d747dab7ad43a1ebafebaaef76ee1ab6bbd77b23c4cb8594a1d1f49157755d308d87ef61629040eecfde5f603f68ebdff80c9eb9b4a958f380043fd2d5524b5a40d771d6fb9e88c1473d522d721e35f5aedef92d7cb9a91a8448c58cb825c2a9a990000b315000030140e874402c16818a68928fb14000d72984e645e3c9646d324c9420c11621400000000000000000440c46a0004b997bac1fef753ebdbf36722fa151acf352ef51b7ef5258cf25dab5bf5b0fa2914f331f08d2a39908156eec2ebe242ff60efffc00cbc3718a3629b025821d50b9bff9c54f2dca423cd92862c36b5b9c3ffb4773965009d4b5c4b5889e2d119222513f955ff74488ec8108cabdaf60d86cafbc69e9648238eedd924d8c4659e96fd051474cf3ae4b6f4c5326495e67830cdbe6f7376de48eff016b417ebc5f3e0c05ff017778d612e3d53df2ac7a52bcdd6c0336fcf53a96ec0739c4ed08e332503ee98b1ccee397356c091cfa6612fcf43b2a1bea1533a2630f7a2742fc8a5e2d58991ee739d59841c92242fc7559fb22b19f006047175601eaf3396762b9d175ea72928601ccfc7bbca183ecd44b56afff29bd943548bff702abf50976692b2783aefe1bc2bcd7466bc7b1a927a24c7491b6a20c7fa728c3c471322699ea3ad391561b4243042d348d9d60bcc383e20e004e65553291a27c65be107ff404945244897cfdda0b26610b9f9a706440eb1e636b8c6f9ad50b94b95b8776103ddba8c11201b47a9ebb2ec5a931e7c92237d2f93140990a63562fbaecb6b622d52b8b7215d9be7b9629a635f9b42b2db46eeb648f8558b11a9f9f4224c4457504108be0183a3e2c01c2258800191f487b315ce49d0b442d199c03fe27b2d6e6b8d1d084102d83f1a5e421a108227f9eb7b92f721fa8c900d8ef9dfe05e7dcc066d411883d462bdbdaac01b3c2cecfd5721ab14714bfee20d0496782115226198d306745422c9a83d0a64c17c25dd1d066e8b012d04072a1b67bcde242a95716e064920c88d50bd28b135dde247020d565b8e49d8084e425eaf92b7a09a6a53edc61454f4c0e11b22cadb8b4a1d7fd47ad43573d34a1b5bc225cfeaa26880c011265f3b6f692ee82b7081d5a49c697a0fedd4e1e2854040108bbb9492648955b66e6e1e13b4f2784831c084123badd640e61ce2c11208b83a8c19f21893011b732394a585c65b193137784d4b2e6d78b08521760a9b70e72179f167a32e0f047b592c74172efcf304a991f5681aa8e5901b290a94c56017d4eb52419b1e8b273cbd077630ab404216df842a6ded48ee0ed59c221b88f394152b280c835b80078edbb3a964ada0bd7cf53f9183e2026d76f8aacf20b638060878ab6baf30af33a850f32432840332fc743b0707a81188d5b320fbe82bcdaf8710656117010a6935728e99480fe9316a2f40fd96dd4923eaa198739420984e48b5da633a4ea403692147418e2de87876771b3d35e396b9e08268f125761ab01b4b0750fe988fdeb434c754a303effbd61223ac05bbc37a80f2b314471f3a3666c5a126219b185c1a042c120ad5a1d02562ae5ba861a7f325724bd4ec3f8f0c8a04b5142c7b06abb10a44ade358cbcd9f913fe30ec3d648d0ea16fcc4c55bcf71872f5b82cec3fa59662f9ea785c7d0d3a373d24abeace885e8c0d0f6b467ec4b2cc8572bc3ab6c2d3264a676ab6f805708d4e39851fb3051309262de2a08460deb4bedc8e1e5f7188b8b78cbcffce1be4b1a29240c156c17201bb5ea22b3e14315e53d18e9924acebeb6d30a21d62d0e3612ac03a176f000ed3a790578d51d9d864b65834cd120f2f98160cacec8e697fef3bff6af1a3e472b12a0a219ab957823a10dfedc40435fea7d19c297a8ae6fcaa47633a421dd14f9ca180b8bc5a5ce7435eb244f3346374e9a192da32bcb7029ab34a8928f781f7bfbf4067d076f31168d9ed325c53b93deb3c66ff40b76bfe0b519083d56c6bc98b21a06e7fd159f6a9627e5f32ad5e6e3f4e4c03e4a81cd4ca706b10525290899d3ac74f89918b6fc112cbafc68a04a698830b1b43e1a329430a1eb2c845a575bfb8ad9138099a4da7223070a05e47be7568bd718c0f79f48b655149b01999216f1421966c16730ad10363996b74cb10f3f9562099672f3309d1f961f254b2999926a1dd7d061dd8699f2f0393f4af82b1b4c50d745063e0860d3fd0d807050c2ec819eb17e25e2608cdac8f3e5e7ee8737dfdfe24477ca76e96e4a4b98ea1cf1b32aac60f374eb96ef492a6aa84c91413df2ab7462a0d21875f69c0964ab0c4a5b4964f3a81e8c91734834960a940249ce585e924f381e1a007f649fe5fb6eaba683a56ab096a4d477ed635497e9a8530c3266441d11f4b18729575dd891b019fc3f16131959c9038bf67234dadfcb3c9a5ed3441a6f6626cdaf602a0def652e2de99e69327d44f005d284564f71553add62013fdde07a4d196a8fb651cb27c66033ba8622143d35a65453fcaaa042e8dc601c4eb6bf97783441b2784a6f58a6944635d27115894bba827cf6f619c85dc13e23edd65ee30b25cc69bdcde323db53cd627c6a1b35f9970a4b394791f6437eb47d7620a3ba9d2f324658b7c3a5331726eec3bd7afb17d4713d1f1a9ac04a7bf2eb1647bbc5e7a4d83769765cd5bdeb85096ae73369fd8f99308b3e092a4f86be2402e8b934ea5f9a291842be41d6b1cdd3e584a0cd4daaef0aaf93573ad8ace391bca6d82adc696d5b2fa72461f667901db37a12b902bf313b9f8f0fd53055c350d1c66de96ed4ece0d8fbdaa1b92e224f599eac484c9e219f08befa9b996f2f7435bf9c8eaa5476d1a3f3ad2e11603dcab378b3da75c3ae0f3a9abc7cd86a9ee66ddd53e12b29f560d18b7e607a6a3b14b8e106497a93afea0fcab435cdcf737ddefda2c483dddf1e99a610d1bfc03a6075be083dac1acd3d24ad27959387c2368093b207ba01760478b307a91eab38b942656d75c8a9b6518d57ebcce6ab0ffd83e1137ee2d865ddc486190826597d58348ea01502eb53d92ad7f5d70a34c293d28b41f15ce9ec243ab7db1f45b7ee9d14e0dd282995b1df3d26f884888f035b6c452aad08392cdc7c64431ea2020c6bcf18230503129545610a9ce0ae89bb16ffe65e05fe3d1f3899e98c551d881b35d1ca28e13755cdb26287981bc86f2cf8b2ac610d76148cf81d12aa299a4e59d6130b5237d231c46d8d0a4f57a50fb5a2426af51c1a08b53af910a94ccd3e7ceede017a85902069db5b2b6b231388ab35ebc361646dc2fe420c0f4da8ed3fe4540d7ccd7afbb30a37f4de64dcd0f0e7fe51f861a9de845a04dde8a5d24fede4f6bce1c30ccc91ccfa117af131f4c0a2bf18d7572356d83f2a9aa69a28cf98c053d3cf5839566ee93ffebb383a67a5ce1228ae1f179c4fea9f18c21a0dc66888124cb96032366ea4682142d4cb4e6a62453680906268a987af931a6931ec5471ea32c5e83534e70b2b16410b223a97f0300cf99a46ca71cedd7946e8eca0fa711d5260743ab888e56826b95553a716bb8b8fa8919be36a8f3308224be8a6809afa3dac47865b533379e4c09330d21cfd4e22887c71c43d5ba2d39416d3ae95175e741ffd31515d4f852c294a7a3059b44804fa34c35111592bdf1a84384d31495395151169e9a914d159082c2563c22409221dae910e53edbe8753ce8b1421149fd4580a26f03c34c09b11414ecc94893ea5f1f2f8171e65a4dcc4a90118a85e0e14ea0d2a1880690e96c72677796b5c745894e6bb4f49302d70a54f4ed046515f6c0f56cced33bdb73f131c97968ae35ef0b1a24c2bb759a08c6d878f2d5c33e91593e3aacaba0017c4439a3ad5cfef60699359fda297dc99363db6da02daab3a3217b8b2f7ce16a7d2e432e9501a3755d747315492d2d3fc59383b0af8b7f198edeb4400e8cdf1cd601c845455e41c66a9dc20f27fc9ce20f0391ce7c81acd0a221e8cb1bc291d0dd3b43796188ab81264e2d990e9ac504bf21cc4e0e4920a6bb311fe32a44df18d0d1362d38b7f9511591191b939ba467c789c6499cdf0e4d5571cc84f1d0d07154200d3af53af464b68bd2af46788d8abe7b2d3b0cfae24081ce23f36608a71bd6239ba70595f59d304697de9cb1b50dd104d9f869850b6b60e547bd7bb2714541a8baec6ed07f859d38d65191d61ea1b6a31d1a94523af1ecdb65e2648426b514a5f772e9d8888eea94a125ef7a8d0f42909544812590de1692f825628314dc332f287fc872e4109aee2ae5002a170b1ee9ad126bf3423ff6fa1e096d6b70a5b5baf8920d412c64371e148f1333035b5ea3a43ec231071fc0d982f1db40290dec2bd35a2a57448aed87b324b2f5296348edc42a16f70d5363ebf0abe8f20f102ead228b53d7bede676157b1a7a58ea1f450a6d2a11aaffefd53e69d733c2536f0178e67fe156b1b79f5ac053206c0461f656081e2e340e07151a3bfe295694e8a1dc91d7b8fab0400875c08b8539191ca48fa1aa5fda839a5a8f8c6cee24bfb136410ee35abe55adaab20a0ee6baab56a5524d2aab6506920082a992aca38faca417e53a3b43af0290603f2a7a0ac16f0506a075ccdfb7162e43180713f79fd0596a3bff36e40b6f73c47f2bc8c2b5df2c1b3493b9ebe66ea1001fb9f6eb9a3b1885a38ec982851932344333d3b533053d049c46dcfe260c29229e6c1912482ad8fbcf0bfee0955472563be52874119df3c2be05f87928eb30bdd717e011ba2d1c387be5d45f2408b5f4285b0f4f8818620ad8b311dd745c12afc929fbbac1bd1b9f99dc1a75062a336ef4a96b9f49c2d6e8f61a31d000a6d9f9f0642566f67ef46ea40c5a216133bd279c63d042e7068c55010e1d472a79e184ac1784ec5de1142520789a1b6bd2f61a7e40bf681a316d0a46c2cd1337739e6a89a5d3a57270e3fac23c85604e063d7f0a4d9d2042ec5df7faeec951f2e957e8d6031bfaf032882a3c8a45d5007fe85f9d13187214011a4f0052524b096fd4599876d01fab00469251c8657832c2decfbc92bea4a5079a71b936ec210571a40f5f0e044e45b21afa6245de0e2c480ee26c7cab91b7f974002ee03cbba21d3eb0e13c63a3e485141bd00a757afb9215e2fddc88de32a9d35b2cf76804aa881094fa5e8ecd2ee6f25ce6fa570272a19edc9c3b254a180ebcf1130a3fdbd412cefaa27fd667cff9543d654d5eb6af11de5fe45c780e534f99c41076df5a9c246fba5180376747e2ad904d67829757ecbe2ad24baac4afb236affc3c1ea8b3e11ba11ccbcc2780eb0a2fc2d77deef6d1845abc503c5bd2cbb65e10c57f8c9471f3cbc37fe8d3cbf4f80c96b7efd883c6e6f15b8573d25d3d4500f58947a1627adbcb024167b58848eade9f46f14f466d63cec0ca3c5358390064f70a95fbb5febeb82ade6c4636ab4c58a6202da56ec0b05f5c6d5f7d7be1aab899898c589893719ffa9a6d5154599e61d17f8c32dab8f776c18a1e3929f1524c2b53517680563945fcd108ef032d7653df8459ba7189ebcc4259868d3d839b9db8b7f6881238c26264f85ce963217dc705595febaf3aec5ce0d228e14291ca42000d1ad0077c461cf899dfa4190890ccc09ff62ed5a6290a43deacd07ef3795f42e8affe5ac9e1528a2243e25b2eb314ef9bf6ab7f60d3566633e363e42f878215b800ccaad6a5cc7ffa25feb5445e687ae430f86f5900df65f9f22f49d63206b2887f7940015dfd852896a636011de41c54b44da57af656ed670ba62c24315fd6ec32cec97679ba333d024cffe6dd24d771cca687f1862a690c0c0852463a2350061b7f124b2a1c5004eeb958d8650af6f36de6a3d26ce6d503c3fa8f28ed88aada5d6435d7f12dd18bf1f2011c2965b71037369f83749a5b276d1c7400e3604edf5008758f6db4b376e377cab90643fda77eebc29d434515ba301fad6abe5b892000363bbaf6973c0de9cebb3c0df8a1acb1955295e6502da5195bc51dd5f256744c67623c318ab0ac5eeedfe537957c109ce15057b5fd86a239c3cc8aebba7db923e394583eadfa360fae2f5be6db1ac58953901b55a1b3e93d53fd510888e75a0f6d9276fdfc4d7039a583ace0e48c59e27b0ee7d5a6538c310680414b62e7964048a2f90f5b5efc579caaa42075b30814aba9da651338eca9a1b7038ece792d340c6a2c9b2008bc7bb2af2c6507938f1a08d2ae8ba722fc94346f69801d663f56dfd5a36a233e993531d7af705ebdc8ffc899042e5dcce35cc101778123eac1d787bfc64d85f4e81aac7bd155b6757874f8cf6014ab27ca6a73a60a21c880d1d16bd7f21215c358afcc174f6800506256d55bfe3abb22410cf88a97c8914af1765b1afd7f07ad6d9e8e668c55dc0665cc910f4a9c22c0ba81a956222aa74ceb6c85fcef557cb30ee3a9e7ff7db9eaba945b389d617a46099d44865c2f2a3ef2d930288caf6921ecd858aa8a8d432e43627a67aa4561b3bda6f0d73f4e072c4f008476847165aa9ccf204d339cf98a33f68341670a7eaba92819fdc1ae62d7b91befe239a4a2727470a5d1f30e06fabfb5eeff3bafd49a3161c9be7e86b95e4ca423c4395681e1142b803168cb514e61a83a1c28d519704ccea07bde193846ed2bef72c1316493e8399363c8bfa04e8d59941b3d2964e1f84b7203cf5cbeab8f7fa94c809589287c6f856c6186f52225187b6649ac9fa68766fb6d35d4cf969510a5919cb5e0143123d13462349331ea38c5069ecf41e6116428b15e3c9606670c86bfc15a5531d5ac4f117e22574fca0047160150dd1696c5310c8b7300b0b03ae44b3e27c728ae15b86079ed8234b694d501062c47fedcaff8fc8413aa5fecee4973743ac728620a2824c6d0a5018603eaa4d910ca4f85c42b82daff0f1dc54940ac281c642252a89c555d69240d1841525b422474467a1e1762d3eae2bef24a5265413f57a27aa65547b8175a517cc1b6e8ca0ae267f4fffaef42f5edbac6d59950cd3c0c151f5b3421d219e2ab8023aaa5b3d12cb90b996849a9c8511e868ec0389a1d8eb6b4b7ccb5fc41c3dc629d20b799173cd5ebe8b1bb8ccce22e45828f990c4dea770c0bf0b418dee56e7f3ac511ff0ef97e23ae710dbe4dac5e5763efcd15badc569f545e31e5e3b655ce9e907b7e1a6bb53a0e26c6b081c66f5b20390cb1ce6d71acec4bba24fcc6c7be5b5e36b892581297e71afe362bef1a693aae8590460ec07d20f5fd1bf65786fd611e8e06f51f79f8db8373f98694a8a4f90413fbfa64a60380360aa9ea20de963836fa110f206f8f52ff22318a2dfe95a45fdab9b3ef7fe703e5779152e88b384d7a8838b1cfe94a58f3cad12be02c3cbec81c9583b7e2126e5f2588836484068d324dbbc713386a8a559823bbe0d41a1701d2a51b157689676970b20de8da70aff11147f990deaed821b50a335bf2948e92f1d034fc9fd5050e1c21d3adca9ddd5d1a397d68744f0cb76219308eac5c445ca5972d851bf59fdf0ad0d80e8dba867fe9e1b387a62cdf4821c356a056eb869c78cb5121d4d2763b6029151c57488abd7818b3ecc7696a8686ff59573dc44468cb0965837f975ce14420601554ecb287f252ded6e3617540e4f72d2562d0c5d0670f4a7253bf20e95655823c84651442397e8e7c8920bdda2f43a18bb0e07d29ad349c00b2cd6529d7ef001d06f6cbf8c26700ac5620cea0071a66f903c0ddfb08e51274303192112735097a7d052ca289208b2ab8dad09904d0e0622ae87b69bbe322aa202b35dfbabdc14855e89ddffacdd3521f858f86f5649a786b1fec5daac74d5e7e3396ecf072eddc0e2aa504faf8099f408fc2d4338723a736a8c5c6eab4685659af813718e7f7ba7ce610eefec7965694218b77d7ed73201c9d674f2f9fe403e823fdc8c755baa2e00f77cbc8eb6f8b7426bb8bd9046ee67180548ca792b4c6a3ddf29e88a4b07c9839693a5e5e25b39b4b3463a78a57d202a9f237589e1cae610bf3759449035cccd45a5f5af01763443b785352bd88f7c5bb5a5e8f2ae24bfeb5a0326da26bdc0aeedd57d046cf0b6c214b0586a0466eb0cca8ddbedb04335186337bac11c84a9da125aeb2a09b1eec8d59e0bf5629e09acc2291f70163d6f3f5aa782316b210553ae7a79176479a66cf6322deb8db5b1efbea8bda5ca5a29b1deffe9840fc96e1b7071d135b6999cbcb5c2a6b76abee756e713faefe6a9b135d3000ece0fe728284404f2b6e58854b16dda691ce24092950fb8c4ab280662074f89e725cf40f82688e3207338d4186f8554e249e7c041c1a1e5a98fd026a1e05ec2eb7b8e96fa455490248d41e27f332adbd4884b8c9b98482c4a21d8363236212b844e7557b4271b30e94d62e13acb46e869e680fa77de201463f5682fb8a2d9c3384866acc466965f60a44f8a32a6258cf34de2efadf3b24a251340843e83555434eca62d04e3b3ca16b3746913da8690324e8b3c518f5dcdf69fbd4d4ef52a73c0afbab62ed6a4b40491d50e0744b3da693051fa6d1a83f30cfb64fd0996b3ceb59817c10919f6f56a8cda9b18423ac9f5907ee630081a013eb18160e504c73e9113e7b57f163d20451f50f59535234a7579584963fcd7a41124762e0994a39e9a32ff1b7bf1a1df84d1453175986579db75e1ea17e3057fe5f89c0f2fcba8f357be28a15e6b90e14fff56af2132a3a2028d062223c7629ed3dc17feb1e00adba6a628282a35c99733ae190539e239a4e13d4720b1173001a153677a05e1cc84c7ae549f2a56e952114b29aa027514c5a6c7a06e7d878fb8b1f0a8b4faa80d2a94d88b6319573994d23d8db024f40ad018c04412c654f92445206ac3594040f6631ab445846a9b251a1e593987f5760344db3753963003bdd89d28dae8d8251dca84e88c950f87724b009849a5f3b616170819978186660fad91242dfef8538bef004cb9a613a3c828450101585f90bfb01e303da271a6d37af91e00706cb33d7153f612cfdf8e132e922c8b163ae6a506f19e288f97491d6d681c6ffd4c06ee1ed5d5990404171bd6520cfc951e107da46fd6dc6d11f4d118074bafa8c4f381b480793c1b2e06a5425fe69086bab4fc18f52b2f14d774fce6a5588ad6d16d65623eedc482db15831c2215a4974690141ba836457366d902ff9c6114840700e429c83c8193b2475da86a7402197af9db205e53c28af2d9ef09958b653879f9ecb2877bbb72c1848590c444d6f4e4e7892d5de825518b3202c5c6c07ae780e080e2f753614cfdcbf5b0f6310a5aa4ea5f2c48066669d6386cbadae9b4a5404b09ebceb580b50ed7652bb35452f0ddc79b1a87de567c31356b7fc22527c77e305ac96ac35d7107c7546629a86f6c99ce4266a97fa4e2738ec8645a527d854f540b9ca4ea7848763327116217585c84361fe5266410c80f4a8ac68d134e1aa272df8fcc9ef2b167e0fcc00068a68af3eb66e7a4a8ac392090ec72138b31d3b826a9cdd1f01c0c3538b45b8e95fd0efd7d0b7ee041ca60e985de957e7ca46d27faab24a17d0798d29cd57f10490930c80c6fe3a550674c8bde4e2dfaff3c9cda5704c0aee043bf49d610f0a4347d07248174d4b21190351180ee1021ea4c7dad4a330654ba1c8dddd9efa9dc8e097136c3d48c4544733c106e96c312740e184f41494c318a8697a40167ad393263fc4eba9b493b29fbfaceb1cf5e8d35ad2f1c1da7e3406a86654131b52fb92af807118684246c1da0ecb026c23f5d49f05be0283ce770a4b8a3c9bf676a6bc5a3c115ecf397c1ff3de0be2f6084e611f402213d87382a45475c241c1a8470c3351b8f55cd2644dccf7e0d082097530971e60b1e9be643de0dd0292194d17eea3089ef9342b3a9b38c3aa64b73149c3dae00453054eeefd222eaf6961c43e45e3b822bb4bbf18b7c50f8d417ac291ed1307d020decdabe8dc6ee255328707b09a791e059c5153320d8f2ba76f0a9040c4211e88ea55f37f652307c5c2229749ad400b7e2411b5fc5f9ef793adf126fd95e2a4a743ec854f25af7336f32ad238671c2560609b338bec1286ef999f09847fbc81c2bcff2203df404e75d37db4418dab25bdae84249e099db665c3ca4cd5d244692ea957b133c1a8a223852bf2bb9fefd4edbd133805a2d989a4031cf2bc217b774ded9b90f87cd314d8e6bf6001dabe99bd11051e2ec06d37a7fee9fc02662662da9fe16bf507278abcb3dc71ae85083d6e96a35a09ad1c9ac30ab152a75479a3423d1c7cfb0102d71e9c56ed0a96b462aca4d4f5cda17484052cccaa413a742fee1fc31cb1ea6d73651bc66159d4184bc8fd12ee8a4211d58241aeed289afc1d23409636228badedf1064dd871071b7859da998650bb5cd6d560db5ea6a874dc1f824968963a391326aaac48690994061f71099c4d03ac20c031835fe722b5a191aa68b501be9b682546a9d77e04170922bc7597f613e91f4784d4231c580d1784cfe8a88638f568774863758a2e6fa692425a80064491d766688d56dcfc295c5b67ffd64f81768ebd4d887ebbf140bebd0bf2206888bc920cc6ab2ce0e034411e72b3d0c18da08c4405665b6fc07a5ba4d71792e81446eead09784116ee4708341a6cddbcdefdd396343e9ff5f6eca514206f81449b32d72ebd12a4076cd27360813728223ccb99541ef83bbd716e1171c51fbe11a69550b3559a44528331ebc2c639f4d1133d3ff1908914e20ef9d9b7e7d9c7ed4734afc8715d038c7c27701a737d72c35319de1185de489ba7bf6245023b6c643b9af737c8fa7e9f271a4e868827cddfb4757a3cdbec219eb0541c92be95de267637819e91f584d1fb0ddd6395096c3165705eca7e583a2c1df08d3845c0a44577fec99e0c03a9fb84bd081928c9d4b2b2b71b9567cac03676d874429b09c53b58b3cb1753814476201963189136e4d4cbad76f399d81c818658060ef69b15c84a9f054c391847bb10557cd83b15a85bb5b8a9ac7105bb8018776b1caae2d56bceb16a6b739a7c88fc1d4f46815aa615a035495e8af294c4df21e834c082715fd8d4398e372a06d73705eeb10a039c01d0b0483c31f86e1b30a209811e612a07a0d77891538a3d1feb214ab5720ca427eb4f16107cafcbc7262495e10ecd43515a09cf7ffa90300cd69357fb85ccd90369c50624cc291d339a8ca4d7ae12f16865c2bd3e7a84091ae690913c47e1440c5667924c60cbba6b1203b3839519177900e3d258358252d0164f2d8464832169cc84e195c183cc85750599824d3633deae4f88eb5d6503ea950e29893de7aac36b28393a4f12a02056c84da9fa1a37f12aa4fc1614e95015ecf58d3348ac7b4eedef56e408f5aca37a1cf0fc6de0a7da7756f0ed188e1866dca46aa8dfd8bc0b19ae2496ab3dd3d59ba48a36e8c94539fecaa7269821121a24989937a870f948513edb033b285762fc2ba5a1f0a4683b94e558a2a6e93eb108a18548f2508394c82a54c9e0c07039a36e5a63d9d4e70f8a7c977821bee635cc5aef02c4eaaa7b8e80445f4bccb71ad6a59d3ced659913cf1fd8f0b0b43337f4ef545489b9429ed5ceb3019be8135a2b1cb2a03e093043537fefd618b072582eb8e232194e0bc60264ca689e3a64d5de3d1d170d69e744b6e7f5365e65383406d72999173c95a602b263924d34217d099a5e28f26c455a8c3e250494d1be34e9b2d1c6e58f0837d0398a5411c3068d1e1bd7a8e3593949cd5535428c9ddea165aa662f8cec4a05559e2e51209c4e26e4e6f372606c204018fb63a17f719b5e6daf99d6cf4593cef91983b30c551550e424c48e8f0f06905169586d9193a7ae8fadf844ad72c6d8a6f89bc251bfe3d71f56cce7eb60bc4b1f7622d6fc51b0b856cce3e1d17d6200394c80d36561656e1d140fed0f7a48ee5320069a861abfca4e6e487d20e5f8a6298f12850872b257eb64d8922254cccc8ea73327991b5a1485031083cf5fc42bf9b1a3a790a71ad0c99fc0741dfa9b76892453f8b53675e846e7cd0f0f5e2d7fd46631d8f58b0a017f37a8f51d2213babd73af40b49f8f6234b81f3277f353aa870e52f3c89a258f99728d6ff8dea9bc8eee20606e5efca1d6611478af66a0bb671d0a2291f2c601864b6b1ed360a5aa1d221d07547dfaec34b91e78374676bc7ea62320ee598093abea6bbf450994fde417668d231c841fcb7c3191d85fe4a07e05b8b44ff80ab254c0a55ddd3f1d185834cec69d000ab4d97fc5c2f2d4527967a4e0a0aae5c30a1a07815ed124841d88a91bf42e4d186f8617667389280bcb37677d43c6d829d3bf586422b83cf2f91da489a3ab6d537fadac52c28fad120d658aab57f6d001f114a2a2e83ad7a3c664bb815c05afc4622cadde0bda575ead623b7f8885301a598ceac0ca7c3cbba9a2e5765f2f11feb4b87806321a7d803e816274e83ffbc1d1d87dd149f856ed5fcbc5c3f1d94294063d78c0afc190fff14e8fbdeb620be74ad82221551608baf92ce31b60e5503dd9b98ff49a8326f1e97bde0eb0edc06af64acb64311099f3af9f68b08475b35878eb2c769d72ed52838cebe4a0c05badeaeeb24ba511e3a3f009903cf112ee3688330008a8411d48872d92e5ee34e28dfa104e81928e2049158c998a20c7dd64fc16757f48c26065565464f3c9331acaa1994ee7a16ced931b279be21f9635ba1b87124b9c27589eaac509c1e99dddbc94bf2084e5c3c8fa9f7bd81aa175c42c8708b8e67dcacb1437ea6f604d4244112bb039b5044ea91281eb5a1e83e892680daf29f4084490916439f9e5b2796691bbd18e40934b768c3688fafd684dcfab3c867e3fb73d8eba011c113c389a8b44d2b215711cca59c5296fb1c816618bdb6894d79ca83aa2546084857cb3618c7b475c34abb631a8c5cb074958e525c1143764d2eab6e199c534a9dfae63b20064bc581ce35c90eea487f01477dbf469f292e51c980d1af5d3243834c9fec22316969cf30ff753fec48e5f985f984134499b147b6e3bdfb915a0264796150340e14d7228e1de5eae2dad109a415984034420b041ec285d52c583ea960ab1aa8292c07e77cd7f24f5d51944ec32bc95613c1d555e54f99262b50656138091c2e4d5e557b267997645edfe5bc7fd77ca85a7b843053e3433e9475c27323a470f2c9b7f69ffad8d62dd8486408133d8f498331d0d2e0c5e7fd7bb4aa6d47db508ee7e4e4939bf421e2ad7429e46bc009fcbe4fd6ece502e4205d6b487e00a8529cbf681f42926d27919cc7b6e22da31d125067964941ad97718bdc7dfba42459420dfe5d275ec5609785cf77fab800df5706cfded04eeabba8054d537ac641c845b380119c8575dfa5894fa34127f18cb33471cfb5144fae9d800c031f4ca9bd7bd8ac3f2fb85701d7a8d56f53f449cdc653ce767a3c9c47fc4f960bdd364e19e24a73f79a95d8737f907ea04288e2047fe2a8d820fdbe2bfd83207c5ef605243b086fc333b3f05c8aa91f2b89db67db5c149541a3481de32f3d96b836adb390b785f431c58b7fee05317c74a2acccc0d8f7e6dcc3bbb82a8a697d4a2fe2f524c5f7a9cff2eaa6698fae19399cf380975a0f2ee85e00284a975eb862b487443728762adbe6de25aaca8062a8f74b62429a620c6fd140b4987f6385a16a9ee395a5a4594dd912289d2628c997876212a53ea0db1e46f7c99083e970d00bda10fb94bbe24cb7732ced1ac16af4518abe8cef4eeb837fbc35821354c118ff9f17a314da3ab6b18f8871c1a0762976805f548fd8c259af0c6428b39df42f5471a0964a2f023ac1405c9124d74f58bd05e26ec40ca391ac60aee7bc7c141faf8166cbfb8ce39a2b7912073a95702da7291877f8cea65db2aa97a7d9ab8d5190e7b7d3270dd0850081d0fdac8dc32c30ccb1f0f38035f81bb5355505662d9ee1430efc5b2a6491ec77d324ae12c771b09397e3952ba9e1f8d00af3f2a99e718fc2f5c4ccaa12aaa444231da0e3f80a50a21b7c684a0c4caeb980988ddb32bb5b03085a264ff506023f49cb27dd1b5fa824f4039b59c445842b2fa4eaff7e4c0ff27ddc4efea0004a1015c45bde16f33e954ea2669109213549bc15b015231590dc7906fe05e40a9f0692f32021c1fa30c263d5824c4e85d8f18797ecb03610673bb0bc35228e615dcabed8a7a3b01949d95d84ccc95b20463cc2a665cee321818b36938b810c6519a81ea232fe7744d90a184147a741a5a5f144311987fef6e247748b9ea97652dcf729b7d9dff9906e4f2af6051a764f51ee1fae89934c873d02fca1eb7e5f5450a277c8e25ee94b577cfe9469b206a4203e847a4dbf96fb6c0555571c17c9add387d87b0975c67bfadf84b2f9fbff8adf4fbbc897d25b7cf4fec56e2fbd8c4bdd2b50f4f8bfd6de52bbd7d7c62b792efeb26f695bc7d3ff15ba9ef6313e795d43ef6632f86db4f774c73a96a7d3c5e60165ae88cb5f1314f3379aa38e49e2633837eedd39a38f70ed449b808e6198d639e738a743bd11a12ebaab539c086b53b968b6d648c45751e23e74aef87e1364c5b80ca67d57a751b4f48bc52a44682f2eb926359af5456f81f5655da724f07ada83d8a1fd5560f47f9be96475291145f1f9d716eeaa9ad58ea53d43eaf3f6aead70b553a0c531679e326b293a3072da9e2a62e21574785979442b01fec186373436d6be8934208bb8a7205ebbb0ad7bdd2605c4d6ae9c1ad47e26a9872ebb928df9711fadd06a58cc93bb645a9b502aa1d4aa8ff449327e424573f21741761caf6796c2decc030b4471f88bf35ddb1b32d1441344b9a3a052423d338dc443c86c8c0309a8de67c5549d4177c71f2ab7540d1d9a81bdd4c9c43d0cb653559534c23a9d8b700e4dc5426b3d336a97452966d825dbb1dbb5bf7f548fd497bdc70c983addf4dc0235972b770ba3d2b64200df3f283a1ea56d9f9dd14edf0efd27e100b6f35d71b3c0de686831f7cd8d5de4e69d13aa074343f20f224cf46918c72c86d6b2599f7a3cfafd34cd0078fc6ef1aeb6b813f9850846bb376b52c355c982ec240f72cbd32ef890718e4be8fd41d79a28c2205efeec6be0bab55a1881f4aef091bc2bbd90d183971062701adcae289848623fd9682bcd61f4cbce624d5e3797fcc1f2d9f859c109ac6a3e24134a09ffd14e9a64c022c8a50efe1dc19996923b370e0f62067f12f9e8456eeded2e8b39a223f90fb1c35aea88f5656db7c2eee035a336163fabb9eb62e985102d6137a6b56a323069ff9ab83de6008cb5266887753230eda9bfa2fa059eb7a3da7170a10e84ca5eb6ee516e4556a6040114086176c17b095d5ccd6ddc0208f891f9fd0f6d04c50df3c776ab7e2e5249fbd931f40ca538e445c8ba5d7f722a991a06d1b69bdbaa6b96f67d0ac4b397ed96504e100b38ddac66ab6d5eaa66fb41632208a320203253e8f9dc5da371e74f44f46fb5eadd5a5722c9ef5991bcbe9f8bdd6a941f261f7b3f655430b9b611d488e7f689fc4de015deb8169b53cc914e9e45badb614ce4cb202ce9810740308af2bd12bebf43272237de4be76e53bb65b629bd830e351c7a2204bcfe459e80ab024987a7c2ad851b49ee8edfbfb9aeb183eee9d8ba5c6dc8fae86ad092a9c133658c6c327f0b5743a5095926efa1f81356382cb45826bdc5163e23ae8922e13ed58cd57f59e1a90d48cf50249888788ef36a5158861c7b4573b7d8348803117ac95c51348656a6500bc6e38fdab1b6151a4ec10638de523b31c04fbdd34f72b11824732903199ce2cfa685b6dc946a1fce287bd82aaa104e21c344635c807944a4aaa4de7b9282362cc9f4bc91d324712a519de90d20c8a58e6499ce8340093026a57114fdbff1b33a1501d5a70deb3bd315a0519f60622f0d594ec23daae95a681d1278cb859ff564ae1646385601ac1b1dd784672a650fc1d14414e73c82772f2dfbed806b439143f6324752396f7110bc1df88616a758d6a91cb3e8dc3906904eb745ef65c91925dad699f3760112c52d05eb9a96f110f3d012b3f25ec63902e4d40411195f1ff362d69f13a38f6b6c368e9892c437775c00b60c288980f6cf2c0b78c9555e0ba0e2bf0bf4db68804a09026cc9d8b2a7bf0fd419f653b576a3aa608449699e07361e557cb0aeb80ca7110da6776585f03e9440f8300f5d2284fecb4ea08ed1b0308a115542fb055799668142c000836c055745168088816ad494afe0c203ae540207c0cff6f9c411d0e3f857d50af1c50df0279214c8db4eeb1dddca5fdf0e804f81f4b47a25dff6a1ca2d52dcb40e182cc2bd10e1104e0668a1a5f5c07d9502331b75d99592e5913346075bbbce26ae0d1f28a167c2a6bd1eb3db6a8f484b1e5fbadd0426e95f4e22bee97a73d01c2e622e6b3ea82eef37971f311bba0f7f9bcb87fc45ed0d57103a9d73b7a6dea054c60bb88fdacbea0ff7cbeb87dc42fe87c3e2e6e1f7117747cdc1cd41b91de91deabaa707b13303914f59551e1681f887de41bd7f70406365abe3561a966dab0e66ad1e3d8b7fa34ddbd5f079576ae6298f9553a487fc7f52904f6e1ab7516d985b5731a15e4fa87269e4e104dcb3c6b718b12e6163f1b35834ff6b8cf6608a2d9a28cf5ee0d8b51c4e38cfa98bf4ed16d0dab3f407a4e5c60fea218edc36a50823194efc04e722f53d070d68be481583b6d7f3cb2b5e368533d21d0005dedf432c9f423aadc0262a98de09a0b88a3b5a419beb99d0b150012ac44479c3b89730c68a0b7abd431a99e4711cd8e936d64c359e737d6eabc567dd3d39ae76b068a65ace5b88911c1ead5233aecc4be2000631de2ad832942960f680a2b6c27e07acc4869523ca2aa3c3d5bd7f20bd3104426149e060d47ea5c1d5ebfc09a8d2a6bd91bc38477cbb671a99ee66b90032d7460f6bcaeb0ae9cfef1c250eaae843e1541bfe0d4dba046d64e85918ca0f705af222ec2c2be7c8ae75d0e9a6a98f4b22298a487367613c4f6443d58a6fbcb1e64ad79fcdbddbdad437c4771819e14b532f98b56bd9d218d0def007c4c2abc028db3282841f0f542a15af29221a2c762e18c103e5f52f5bd5164b40eead7f4b248684493f1052b4c0b40370a4babc49e3a4cf48475d1ccc4796b8894b235e9e228c06a05433a12074d96a7e8483acdaee03e0704580ef91049d412f0ccc173a0b97c22869e530bf9ddb3f14dfb1c94b48642024da8d041031d745041831614e8a063e8a344b05143a442a3c994534a09e594284989b294b0326b85ea25974a6997623174bb59c6d4f42784a526d115985e68c08e895df2254bc717c52a7b4ed2067c733da0afbacd66557a5632339427d5d442db3c81a9da9ac0fa959c04e6c1b518299f24a221bf4ab08cd7aa99af556cffeea2b986e333c25739d7ce69d5997ec372dd9c9656748a65392f6b406e3c123654e237a9a2d95137a44837619a1e57aa15700fca4781c8a9dee89b25acb787b47351cbcba3c6cf6a15af69b60d4e5f66e6c85affd9e49879d68a529237aea2d759b669c87976a242dab265dc92dd067af96eebf359a4b8552592fbdb18d57455dcd1b07202ff673131eef1218783fa73cb0a67afa32a5b726d51f0580f472b4dac5f08c5486b60968439979afcc05467381dcc64d524f8dc5603f4747b1aead8c18a116f18482d8adb77791473755d1f6a657ff5f37ef6c17f7a753cd442fb2d1ef2a190cccf7bee7f2cec577f878742d0bf6258593a3f58e30b81393ce1376c9a7bb4ff5c16f55054f9396ffc473dfb1feaf743f1c45fa1300fb532bffaf33ff3e07f9aba3dd442fdb2f050e18d9ac5b27ef075fe5a17ffeb7a79288effe78857415c04083b5c20b7d902065772502a817b030fb514bfffc77ff8e67f437d3fd44afa2d1ffea190cc4f7bce7f2df4577bc74321283f16c943659e3fd8de6ffdc2cffa4f3d9480cf5d1d72c42eac548133ac12e1e76147a32a8aec844bc65ff4611367ed6627b5d6c15c67edfc43c5695b6ca4157f8565ae7ba8a20344e958d0ba5ee42d41f5e7792c1f2ccefaa1e803173628a6b8e1b4ce0b17323971f4a28f285d21b85b9d224063a77fa8be0f5c760fae0ed86c5a5570ecf613c24cc2f81d9194441015000e8e85e418058a83884dcd80f3802888c2540a0044082fc8146ff052e1cc6980e7dfe1ed16b5c19054d07f4925a4422806f0729c04ec8a61bbabab080aa709c8740b7bc6dde24b38202a7a43ea9692e19095cfe2e775b5e5db56098c4398e0bcf8feb1f7bc4cf7291a98f531bc05551290b787264a8a3d9340a77aa09240298aac36de49632a8d9ba523b4a5342cfb7bcc5f60665622f32932b1dce6f4f1a4b4262b1665687ddb8d414906545fa2fbbebdec21cc89fc1897076ab28590d262327e48bfc59750b02c1baa30e3c4b1834b9346051324230b65e81a3e8408fca9ba9b5c9f7248663c8dc2ad49c8ecb101795dc91a304de9a4e6df597d4a03e31077c41222fe08514cf5c50992b1c63759b90f8e3f3d4719a228ac496a08a332133844df442ec7ba2d51f00615c305d27fbc3f9dd446594a59bc08ec99548634529382178d3c6ff17b232f548cc5b2a9610f0e8f4126fd9c1e55803d4e26ee2d7f04c4db53a64101d9bf7e2523be77fb6c0eee4f99fc0518c6260c867f1c0c735d4ec715f1cc65767e1608f40e640be1a8bf17bed20a1e7e391c30b45c2a96dfe29d73ca380f53b718f67f1e5cf756f0fc14eabd88cf87fcb148ec28a48008d30835270ba1f9390ada9b9f1d1bb67a064fe5b7c76bee65ed1d4883608eab6cd598da5f79f0d6ab7c55953907f70183908cb9b36389bb46f11fcea6d04d87b1492c830f5abd09182cc77e7f92b730acff16ff3ec6e18efa2799289f656acd5aa50182b5014726a7078119d4fc3406c647ae165d996b3b7ed25e97bacbf1779963a88962550139e8267410adb34309c8a84eb9b142c5b8522c0e2fc7ae3214c2cc0b0a1d6cbd03d899e287e4e9ff4a52a8ce9840b870a608d399b869bf7c49aa0585bdef21db19c113fae9824892deb3f28554b4526193e713d6753fb284d3e72ba10ccd0a2d92ab8b898856c3e67271acb11fa1c4edef97a684868216c9d58947c4aa60e5f838f0fa631d706f904cd9bad54d818080c970063c33c2683220404431b15c784688b4bc55fb6509a36e0cf4fd8c3dc5152e5a226200dfa558353d3b76f6d834256ed2ee69f22e0ae514f2ec028be130195fa3f59d1b5eb140f426032b2194deb3b0ebd98ded833660bc648caa3961cc5c0152fb41eb78780a57fbde4606fedd6a0f327f2ca08b4424c184138b5fc9afa04d519d1927281b26d1f3cf156353b53fb9fbf0c5007982db0bb19828cb985aac5dcaee5ea03fc33687bf2473b3bb92863dbe71fd99007ae5a48bafb434d7fb02733a52004186a4b66edff4f88eb782310fc450c1b53e73f3b89999229c76ead41066cb3e311941b6e18219b6bd9ce00ce4a4efe2b111134fa503830047c41ea506aa0b9fbf4d8f0d4a3d8b9ea462f59581cc064dd51c2908788b0b8adf8e11b36bc7cb48fdd22008dab1d8b3c0a78431962f263576521c4be67e16e04df38cccab435089a0e5bdf8459c4609e63c39777d03725012774a6ca19009c32da60fd4c3afe7635aa4b3e9eaf59a1cd1d4a849a4f5c31e3c8903c5f4092f96edf8b8fe204480d0f162a173a65bb999177032625d0c0b10c55fdb3adcd880bb74ac3b3d60ee64889164ee3c2dacd27149518683371ad53aa54bda6a8d14e8996d817c75cbd75993033ab90e68aa77ef4906cddac28262d3808aa86aa4a75e5c946f088cf50dd81d47048013f9031271b11cffb21fc865f52d93036a0bbb5514e30c4c9c4a17e0f00a29c9608ce1489de9f97b31d82be0a6fa042437e9796fff9bd12735c1516946f4dca82188f4d58853a8aa7e84b70176eaf790220bc6f0b90984c0bf34af08dab62eb74072a7ce248cb934ba392e1f707688ffa7a3297cbbc4529a59fe68a82192291414edf29d5ec032424310005705ec56551d34a9ca40bb621c6e24af32e0825757f745e67b34a0bd0ac344732da8eb99f93f257324688915a5679cf0abc422c95318624440731b002f61f2845b1ac9fec3655d1221ee7a942761e3c51f6c534c4dbde0b1095357aaec76efe425c13e53350960bc0249498862400ffe3134afb3c6da5f008c046b2594208ad8e7d920a3ae8a8a23a366a7eeaf9995dad2babf9686869992309dd8754bfd397410f8a3477e7d1d8c4f196cea49d97c3866501c11e935d31ca965a6e7e900294301920eb38aeabcb05e8a3836d18557e136eaddacd2bdfb40ea73e253e88cad36f4ba849fdd52dc8c05b1a2cc038388d8702fdf1f6f2bde0aa8648f86b87c79933ba2ce75760d430288d2fc041ce68facf6b2d816630cbba0d33df1b316cbf80f6a101dad851844caeeca80d05ad4c5123178125a2e77b77d77e965d7d2cba40128012353a5a267a3c21c80485df8d0968e3bc15096a9cc5bae8770f54d8fa66b01ebfd305e5ecc9940b6e9baa8baec9916e7fb5ba97cade739012cc468e0bea43498120584a6e673131f5dedc8692a92b372e2fc64b4a852be59abc1b04cdce3cca9e68ffa58a2f6a6f13bcdf5757856928fba1a2f76e17b09b30a12b7b1a9ad6db4ca066062d7ff61a44d3f149205764a194c3e721cb6e77b6d9602118e640a3c14ac486d71a7d173f781f0fd33cf8dce2e1a8964528f93c685275b2bebdd25b2ce8557101b1d4e45469cc091d32a3a8a570b37880665fd70e0f51b5d3233ab394cacfe98296525ca39fc1133a9d275cbfea37e1ce8da055220fbb70a56e80c1ba611aeec493f0c5cc58ff550635c4c206a93703a19dfd38928d2141482da8d612c9b4a20256377b29997513122c022df34c84aeb41d373d0463b3249f860d6031ec31532beca5e5f2df58d2b7a1a2472af97d36aa83c6d8b01c41f22ef05bb3776da4180880d1edc08ec55bc08fb66c876abd7f3c9c4ce77803e3255a75440971cb6b994912fa97889467b7e08d123c13bc192842ce15d4226d4c2c33d0ff9d217ceb7a5d1ecfb6ffe6f5f5606079607fac6de41ca430f8d397ebaf8702e38d3957876b2ebc303f8452f3150c96e13de33387157dcdf09d7187b34002a28040914e311a61b6c20fff338e695bbe2dca6b45c189aa5a7312f4e2bad828240791ef6e41be1bfb2d535b41ea242da579aee351c47fcd682793d3ba767235d0997963554e8178ee868906bb082e08acd59dab23696c5d5e1657699389b0ab55974a3eab2e0606d2ceae64c0e296c8f1ff421026671b1203ca3ddb4f004d47f0ef9005105f7edd45ac7d07929a1b9ea7ae85afb1345b30deac09c2a982a8755c9090f0d8468af691094cdabed015c3560cb13f98d4fc00403e6df458214c36e44e691aa4b173cb66bc7c20d0a947be8d5e38372616fec31a0631ca39b8c1d4646796bfb1ba4b0322c95d09bd38b2fb671d9fbec7c0d5dac390b79c29d4c254e9b604ec1509cee8ad6063201f3e92e20ec00c0df16c968b40ac547b99ea574f22d91fd871f1db924b88d8d9b6e453d39f32392a85bc955d2225179b86d34b54958da04718c37eb952e21719e4b8e270e2726f5785234567708c9d94c5f58041568e8abbe30d8fc2dfb3c28691e517dd54d58b0b695e86269b1ea649682243bdce76f079d8dc7adf272b01086f2e2652b3aa3460a1847d2dfa3923fe3084a0c9cd27b21c5b6d1d6cc417bcf70438aed99eb0a3af9c4625c5fee3f7a525816b930a7f557e3e49891fbadb124eb393b08d94ecdbe48b30a417c95f62f63f00bd87e6aa84dea836352913ecf7c9ea001d046596514892a92ad658b46ee76bc624d50ce4cfe601b407e2889ea50f7da256d5949e599e8e8759584d78291e682554c97d3082cde19342e488ba0a1fe9e4a38b3724372aa639e2748127947ad9d913f4324636dc0b65dd5528881466f12b2718edb76dccf819f3a635edb3e4940a4418d6634d14af5539bf3fdb1c88f158dd9ea25276546444f83088932b3925a94e6620d8cbc1c36ae2807b424de0996047320fdee2db3dab90417c82caeea368e3d9d3df2de0234e576cbf4ac274dd3db64c40781b959187860cc655658e03e6354a411f4df08091d99b4f07dfb54f05c025df53cc16eb3c0825ea9f904edc1a24a8993d593f916044691d5f9d7d9a40d2ea4ac967843899110df1af5c4ef7b3a9cb0c039244f5a857bea1077276cf154272765385d841c60c97bae242ffc11036a0ff2695d3052b29a0eab70ebb65bd1020dde6c46b7d6542b81725790f193b84687a229866df96a52f512d22ad86869fa1746a2476887754ff3e3fcf58300e4968652d1ad799889d1001eea73aaf55a274f433ab1c30624bad192b6f79632a594024e06ac068a06720a2453c42a95a5ba47546895a6ee111519d4f9b236af22211820a1822074c09483ab4a36f60b3b2ec0967944c587baf9735f4fe56fc7fde5f7f21984708c31c66ef6a03a1ae45f2a351ff0139f2935dc5aa421751a8cfe5a9ad8c2d2441e2098fc407ef890718125218667c48fdf6371b68f31b266fdd7cb801b1365d4f81b0d4d412237f8cf62edea6c9ff2627ed7f46ffedbf30f0590eaf62f6741024b020a3c6363fddcf8c5242c51f9992a73134ad4249dd8759bb4491dd771523a0349a52b8640ba8284b41ff7e3b704eaa206d968af2c95f5418daf13ee12aa4f470a4bb74daefb5faa83c5a2544783b286d220ff2ce74a52bb36e70aadc1ade0b77145c40c3d2996d43572e33ef4596549244ee9a2d2e250591c94f7a8cf0597da7da147438eb25394496e8bb388f475e31a6f581a9634ca22d24c280a0965f12112c42cc49fb2326b495dd3953f556b57acd57cc45983fc3b1c654251d6cffac99acddac51f65953fcadc66bdb614fb0112722b410d06895ca4c125424bc33efc77b8eef3c7a4aed9a6f8748d8ff8fbdc4fd71091342cce89fb42140dd9931e107f4f617c4f523520fe7a7f413816719a4f69700bd2358c4237441cd846c6dcc53f2d883e54fee52883dab1ea826b89d58e4eac8b9fa3a8fcb10a0954590a55fe942a262d0d7fea06a127558e2549248be3b3385d65acb2245239be0b5e3bbe951854f9d7a9d784baa648ff129692f843dca1f27bcd6dba69728d6b5f8764460d25469f754dd8402ea56b22e5ef6f074883bc307e4ef2bed311336ecf49550af2b98e8633d5db81857848e7c358bbdd7bac0d1077f16fd352c78e9e42f4d8fbfedb1d63bb7ceac3ced746b5f21eeb8a4d3bd597fa5c364bb902d2e0a722ab1e10bf37f6fbf7e6ecd2d0599a7deab106f907575ff01a6ea9bd94fa541bd859f5f182d7231d48f5cfdc464585a49e47a52345e3e71e88d57e554ef7cc5db44e6a903d20330d76dfea56ef3478c3e2700e2db4383d0336b81fb68c3517599c7e8e1cd96908a6dac5df75297e19db9e916aaa72c84c2ae750f99797502966d81eeb1a2b6de3b576f1f3cc5259484fcc6808e8c7270610c460437a60a9e7efa4ae09bb295d133acd6b4ca967241d66a90653dff27792c8eb9918840851c55431213e3e5f3091d8365237d84adc5ef588305429e56481f35dc2f87d1a3ba5f7e2b2f2dfcef3b8aebe26ea59d26ab57a7f9777f7f75ea623bd3813378d67de879c835a737a892f542cce0bb35259127cba6b4a315a118db33418eb5695820703b30a61c54dbd38be3463ac9f0782f13551579717baba92ec798cd491562e2b19d2d2c87ff91a073758d2df4437729aeb0b6da15d52b6a27481524b4c9794067aeaea042f3611a566b034f23bc9ce909f8304c5136189fc81b54311594a26f3eae2ab958b2a85d57b8ed56ae55404afa98fe3e62b1b188bb3fa395fa8cb5ce63297ad7e7d737722a297972fd6d8352eb16ba2d26b235124729b1e9a353dc89aaccefb6ccc0741166bb57a2f84148c8f6bee5af2bc5fc5ca516cb033909a95780a4be9bae93e80d400bc949419b273decaa5697e723265ee92df7532996c453bd9d2c4a41ca841f9303eff7197d2d7b5a591ef5f952abfabd81932a85df2a780527a36ad69f286ac2fb4695ae636c5baf22c6dd35d09130a0b297459aa8b19bacc672eb2ae71a1fb2faa145cde5d16176773b8d0e8e2b2fa5a901181ff2c5638c111522dc4e9df6369e47390578febae57e107eaa48ef4b90c04dcd443dcc5dac0589cf9e1cbfb7b0fe3e375adba17c06ad5b45acd7f21d6d5bbe052577497265cfdfe4b0a1e5dd117b8ced5dff0d1ada8a4a72653caba060c7b4a953eeb9a266a50e6363d644b932d4d26631907b54b7e911de60f2c2544bc4485faf992882589ba6e4e16ebe5657e2e53dde0fed464fad8fef41cf5317f876bea73dabbe7384bd7a4682bf5deb7524c41a2234dea3218dfb6b055aa7c6eaba8f289e44b069259aa7c1491d0d02cf200c0ed0b00b9e2d1a0fc545786b238287ae34477988b46aabe6184730b206dcf6dcf7d3bc7313b4a9502e7dc779c7caeb58d032d0971f51fb7d91ffff9a9f29b7faa8c71a5aa4622ac9498c90fb71cb817aab2f31cddbb77298f56dab9de8c96eb24b7f9ca3f2e7dd14aacc510b72229c3dfa365934c770cd50358156bfd452b72f6fc237f921687ab74a2ce56aa14b6e766528a4c5a1c558e8dfb7eaeb9df9edb386ee3867250c5be0803557e546931298d559d7cfbee5f3fb81d1b6df977d559e0d154f967a60904ebd094e5d1821553469559028f167a4c5055c1a5e977c9626e5555fb675e50a8c97a18e574334dd47b221440cb0c75541ff14f5ea7baf77fc9999d2d492c48ae3882523b9241cdf3771a4492b034fc0bd819ec314983c8dfe9ea0252fe425799c3e90216ac92bae055d206d4a629f65e50a8355559c31bd64dd76c65b0328cb78c2a339cd57fbcd8275a1370e6454d8c96a868e2244b15405d274e681515ab5d5b00ebeaefe6ae8ad54a32999ad02037f782428df634e6c98cdf11f9ab5ac165cb962d5b6092c21083fced7956d8befb8ec2c0510a84ffc2e2d753fd5361619276ae635b4be6d790d581b5bb98b358ab4d9b93c5da91325db331280209451444a11881a2509027651e772213c9626d7162b4d740aa21aba1448d596540ac86acc68c8a5caae10fb924658bb3b5af91949c594ed21a25d575f293338b9d93e4728372d32911228a9f2c317a533c9af78447f3682fa52bc9aed5f0134054d9e897b2997c0a97dede4cd25d56c5547d879ad3f26b5a333518bb99a47325f0807ef64a96da3f5831d53fa9542aa59452aaaaa0d4165eef8ea7ba663b252529947628d19868dbdf98e8a5e9d270c75d1e1d7102a2f2a780780c51421502263518e182a0c562b11a62123e0c25d1830ff56bf86aedc71d6ed35ada5241fef62bf8470a437cff8dc200844e7ca7ad85c1e9116e61af96c134c3ae7e2cf9b584bea84bc33903ca609a2158b9bb7be0b1025e3c83eb81edc7b40cb8317a304fdfbd2ac90c5f07cb5d1b94f92fb769f573376ed36ada52c1fbd4af807a8ec2c03dea5314062074b847d1563f13c160e82b4bb3fdd64b0d6e4d0d6edf94991adcde05693a057949870f8a724738b739d18dbe586e8363f39c57c75cc90be2c28e4cf832140368866be4338d1687a6cacff18e250d07ad1f8c67e4c4b0c1dc9fffd2f2e749fdc3a03c39df03ebe7ff525f53cb1fc6f7c0785a4e5b2aa4dee5755c280f104e755c1e06e5e159979f922e3adc254fd81ae46569f8552e37a0f7dca7c3e5db96ce7c979f3a2e9f7a17dab98b3f45570ab7454bec460c1be63a101e1b701b97ef408ebdc03e9202ddc6a361b7fb726997e3ae7dd5b7756524f5f1c2f570cd61f156c68bd56518c31e5c15ab85c5dcdad2e1d33df72f95e792d0b19d9ef7bdbb98985cb77a9f7a6eb5c4dc960eef5b115898471996fad60a0c4b3dc352f4ddc5ba61468afa93f7a7e749e9f0f141f99cdea3339e75170af5a7d741fd6943d10d76026910f36569e27774ddc55a1ab714c25b19efddc661fddb3c63dbce71e7d66f36151cb6c1a0ca8fa59d7e3821e9864e3f9c7eb081c5ac830888a0cda0490c8caac03622ea5683244ce0ac09c372d53daac2485dd53daa02a886f37160310bc942b0d43189708211512861c31425454c749123a9a5b60a240240a4c2d43daa6511c28399303122bb4719638c5fbb6ece8d3c6cdb1642953ea26c772cbc909b8c43441f21c0b06cf753c771cc5764911bea24998c164746981f30dcd2dd53eb6a1b29e9c7547f45f15a624aca6d71a8bcba4368caa2861f54b322caf4ba47352a58384c59f7a826850c6a341e529392aa45791dd566503b42e5d5bd21f1c2c92cb878a9f2592f2b4c3d0415d107868b815087304f6242d418759fc474e0bae7b8ee461326a4d4100213d49208d1c4105c93ad83e86195851339a0a168e390c842a9654d1cd1557ee021355a67f1a476758fb250e251448b257e409a428910254248afee9e1964c822045c18517244ad6720831a0f3517f404c1248a2d4e08b181892b60af5a0e37282a124289104540e18eacd5a650aa4c9ba25643033591aea1813afc216575fb9d58635603a986ac8612356635663e1225345f76b47bd5aeebe9badd6e97b7eee517bffb425652250b212317c96e93728afdb6c27062b7dd76dbb66d0b9b56c31da7e18e3c9d50a7d329fcc0c6f81c5d9e71fa6d49dd366f123770ea71a3dd1a9ffbbaff3648f486560b175dec5052aa1db463a72829b114ac24fddb64f70685120d7ec0ec50b24225c804bcb4f833e941d5a1d49cf6e2beec202f810779908c79108dc90f99964c4959d08af0fa939293b248722b98bd7f927b2eaa5cdb9454ffe664f739cdb9bb735fb86d9b7fdb731ebe2a0dffe6b8222939c94929b9222904cff0df3cee8b9bcf924e71db5dca3faea841e7a414e2ad310df6a296cc9d1818168249585954e3b756b8d20cff89f05436dce0007fe6d53101a82100b5bf1f9c79056071b6764c754978ad1528ea17b6b2298abea776dda9eb5c06afb5020f5b410443fde977b84ea0c58f8025180f029660de9f9eebca84af4e06f3b314a9e1d73ed58d32e48848245950061125e6e01a29132d8d4c8203d5c743eb929ffa90d8209a55672094d010ad3ac7d62537a0a507b8d4f84cdb36da46db68316f16ab87aaff57ee79c859ac397d28a7fa738cc5da8dc76f7d82428b71496a212bf045ec07200c618228246042841107d8419521b088e2c727061b96fca0da5905c7fdca0a1172679bb453b8170e1a105eb852aec5e998df075d8bc3eeda5cecd178bf2a1ba88edbb6e71c0aa81b8d0dee84f15740759a52b90399ef4cdc974a0a9e2c25b182232b3c681ad030c4045eec761a714a2289e73b74507a60856ba18a31782b248618e18c9c803bad800b79aa810b986ca722e4e974b3460be59562c11c62afc08114311e829822a90543af6e18d6b4728a73978927ba236620a2bb6d8a4dd54520e173839803d5aa8bb012a4aae18cac0e5822043c18e20922478898302c2b21c881bb376e06664b9613bde212c251152966128ae338d4738fa23feb503de81aa29c5e06ace2a486ffda74e08ea84105ab8d9bc1aa86335c15a12a40f35b4d6158ad26afd423e11d86fceccb1f85066f70bc6edc867ba725d078e190bd74bc78410ddf9d7624042be8c5d001388bc560e126c575c93f7d3130bb2ec9aa1c9f238d8d1ee15e4a971c05b2d4b16a4c76bca17f6be62651da92a5a29e7b29deeeee2ec7751cb70eca4031c399ca9f7ac58effdffbfde2774b7db9ee519f0b2ebf69e7df76fc6ebbea3eb8df9d319dfbf89ddee33e3f7dd7a7ef4ea7cfe90670bfdf726286af05890aa3850cea5b9503f56defc9efcfe97737b88f8f7af9fd48b572703e367ead06d0a5ad1c1c654e0607f3b714a9e11ac56ca3184505d76d92f5537afc75de290f104b75fc23e5e1e9e1c1360a32c60e260cc7d169947a908417cfe00f51759904ae2b6394f962127278063f830df2546fbf30f5bba9ae91f4e645e3b533231616b55acbb16089c5e7ef6e1f1d9e1e1d9e1ed62ee5211d94c4d032b3702c763f4fd7d830ac9b93c5fa2efa47daad203f4adafce19cacf9ac10acaceece7157eb607e0d671623eaec96b9a50f519276107d7bafdb76aa8ffde519e156bbaf9dd729ee3f7e8ea7605a4ce6c3a87bd4c5941abe7c8baceb60ae3b6d72db39f085828e8671df46c3d0124c4cb51ad2104b15ac8b2484142f4095df7b77cf43f264ee9e8c7bff7ec899277b758dcc6de6ac6b1666ce0753cd1c1352a8a74a59ed6fd904a3a2684620c8d59379626e9f637fb02b5504f0c28d586583005f2c2d0dbfea068cdadd8071028cca9d00a37654de8051392a7f65df3299feaeb687e4cd5a3cccafa1f464db4f8c6c62b15e80ba156c92eb4ea86d7fb92b673f1c0da2a043d6fdbaa24e5def4a1e8c2068693cdc26fea6ba2d66d839315bdc620aba6bbf2534ff280849d4defd9b2d6678b3d4454a94c4f862a7844bc332f9f959e10c41bea9a79f49417dcf7dafefe6cb71170e143e9d0659d6feba9ebe188bc1ba6f9330820228b67d0d93df46c3106c553674b45c30bd40aa5d6cf7818f92902995cae6d7706751466dfaa3d035ceb0f8b196667ff8372f77792b89f9f5c80b2a9559326a537eeb2990cffd6e0facdb86f4c0620f6c5527706da9b03df73adb73309df892f244da6a3a61ac6e5aff204a11414354a99ecbd712baa69583b6fce3cbc032534b62bac052998633303153069619ce21deca10e9baae7b32733c13b87198d0b48b9952112b756b33e00d8d195002601502a9cba99d291b5df4de785b6e15ba90eada3dc628e5b6714852d6a08a89b92467362452ffaabec195512d31532a1b1e50505d4fa8aa542a562d0056614f7da9fd2f35c440eddfa5915275824bdd95d17bdb78dbb66d7be9666c698c1adc1abeaa0ca0b95a8ed4f01b2513c112cc2c6155f7a80b256a3853fbc398a59d9619764d57d03ed5cd54a461c7748d0c92db560229a58cfeacd4fc2153a30c727777e92e77e86ffbfd5c364da65a15dda58cd1df37e9aa1cec519618c82577bfd9e20b20c98ca7d3e9247f94504f1ffde432ddc7e8f1fd1b449a2116856986ee0e108532caec97df0f398b7ed424821abe967e024ad2b6c5c871f1555ffe716b19bb5dc69519762a7862c4f4c487a5eea7d120dfbc1ae49b06b95fdff4831abe1633ec6a2339428999fd9f478ddf9477df298d9bd70d8d9b125eb3eb62281ce2a7baa54162dd8975320fbc447e763712213203111fba7f66c5a61bf35abc32fd4d77edb3962245ac0697b54644baced9c1821513a6230b3bdce6d5aa9a135339756d38b0aefefe6d3232ca591c55bd891b68b08bd4e622ab9f1aae7eaaf3702c699b58314b582fa38b1da7ef3d75f4c8fee3e5c9ef86c694c9e5e0a80836a25183524ad9cbd2e5b6fa4f36391a55b9b9ea4ea4ad1cbc34fc4065e99d8e98477e3ad8690becf4392d4f4fa36b7680ff2c568b8bb4c57da0747a8a3d9161e6e40534e002072a31d87eb724048993a054c7f6024bca737aa3abc7180e1d46955f67070b4f4020abcc562a4b1a0892788b3d8941861ab24fe5f7d8355022551168d8cf3481b53d37ddfa651ae4239b6f3486e5361b7db908758fbed8a2869b04e4c930333333534247f936bdcf54b539381a6c13ac982d03cab0f051043378dce2c6f38a8e236fd17592f26cae416cb973cc5d8fe22e4e77451e2028cf7f0f8c7ffa278ca32d4975f8acbb9cbae2f7c038e6ae08844ef75c4b05ee51afc33dca4887fb53478db8067257e4bee6553510de451f36911ce21149e9940d498f6cadd85a627ef51a0310fddb5f31c3b9ebda06fb842f2e4de7f84bc805eec7fe5d8c31461a3ae5364fe2755dc3fcba7990e368283f7e7137a6db4643493b1abb277ea0bb6b2ca594747feb487764fde96795fc768f29991efb1b701b55777a99e1ca16a6a25233281a8479418e43a5def3e55e7574972645c39e97af73971f599fc931ac62b15413707915fd406b0586a99e612afa9382b9fc4f84f9a85e860ec69b00ee571fc35aa513612e5406212e30d5376cbb26aca8df547437011c45ad938afa7065a7dfb875c2dd291c3737af175851dbbbeba49e7e3342d5002f7a7497e684a29b0cf83c3c5027c563e6ef1e9e02e236a0bb622cd435fccbf5e09ffda31a733aedef890661241d8dabc5fce6d7c520f359184911910a0d43517e1514f170f14146a7374bd37fc371e87b625444a1c108299ec0620724b41cd97210c3f2fe4f12bd06d5bf70521da6babb3b0eb17a43b36b769d6469027585b732e1ceba69c4154b4bb608b98088238cf03cca8d43a173f075e3421e0110d440b4830b92dc000428596230552498746846a66d16f6021f020651c46452a450c1134fa89ca80e339c4926b4ff742f52507d55ddddbda32147dd7b8969496526259597b988f9959b8c0c11d55e7dab4f460f66ea733a85c34c3dea75ccd9e2cce0c5e18f235081839ad060a4313698216853c40c77c64347d7a898059b9b126c16e7047e6e31c8d4041c4ba83e568331de931976e5ed516f0213d105fea2723f426dfe777167ab29f6ef7ebd5852d36c495633a2c58c7e6440955fa81abd0ce49baee3ee9d015bb664a99bf3877af9b930c2962d5b2aab6c747447d654ebf45b73bed6e9b99ea8090db68640196915c1331e3ddc6695a0e9d8616556fb5968014852ede7e19f1913d38eacd46ac8316f76a474831a32919a3e82e99aca345c1a90d54f7d214ab58b917f715183fac33434d10458c398ea5ae4b0032d96b640820b238e802598ca063710339f3cce1839c6189b8cd42802588c31c6c8344bb53b8fb323c71885c0628479ec1a1926248e4134982269c87cddc4180533009634592a010c5c608392761274034a07eea7e3802257b4d136c6086e4be1edc6796fd3d99c62aa0df7a1cc8c47591da5b5b0b42c39eb326e5ec74b12ac3c3b231c09b054ffe8dfa96c380e302c20206180eacf758e644277093dfce83083294f2859026ba3a6241546edeeee1ce6d62880ba54ff0753968096a0a87e04041854b052a0073d2c09a2d2c30d7a3022084646b032b7f24d0d5358aac0c115bf46d591eaee200853d0e0aed8df6131ba8245edf7391988888c2b526a3f69e2c5124d4c00841534d1a4e908aadbca3fc295b9956b38af04517b84daed2df11d773a9dac6c81a3044356b66c404d5786a8ae942000384830bbd2430a0790d1641165f1732a829584120d90948ec82246050f5810adc0058ac9ef1ba8f134d82b03db1f74ad0d0e1a266001b5a1a8fd5566acdb83034bd30176f53709a0988163a81d29901f0d7677b71bb5118eaa532f55a6acfb9e72a14dfe3dc0da9c285b6997bfd32c38d52953f50f1550fd5faa4caf4162b5e1b52e36990c47dc7eebede3a08f851a940121c92acb1889675df34be3be1d846bc8404be3b2d98b94e92be3a276ff6e2569ff29030e7797bdd4906b6b7bf9be7d3ab8e62e7ff9b56e982c44f92588c91d8bf5f2726aa393149ed7c96fd66d8b917dd61823cb5dee6a49712c8d7fa05b4ae149d8bee732712cced679af467d1196fa1480f3cdf51ef534a9ae802a2150dd610d4b7d303c112aadc0447d0cf336ef171818a8fe75a3f32503d57f6eed5d78dddd4b96a64849375183dd4c9666080990908cddb1af69eeee34f4ddddee8af05a0d7026cc849930132256229f9f972c8e3f3f33599cf8cc3fdeb38a9154a63cfabb1a0fb192e68c75335f4360d779d3246241c9a4ca274110119c44a8840e95453dcbd4cc00000000047314000028100a878462a1482820d323bd1f14000a839c426e54190a244110a3300a8220638c218410040c400008223345c4010082b30707006b81a9aa7dfca2d4ff02b8507e89710c0896fd9b484c83d7925c49d8b10dad9f2ee60cde3b90527125299af2ef1bb55439797caedd87eba137ba314ce9825b8b73f14b1f937d23b2ab20879488d42975b7be21c8d1a0bf6a2e5cb7e7066c42efe21b47e160bde9709ce54fb4322717cdf4f0570bc54cb3ad87cf70b61582fc43ebe80f3b1bd28853512c24c9c0f1ce907955d5360d45eb9548b5e021f41b5a5275bb257fe7654a8ce34b70d5e74e9197a9dc25720c00dcf34e4452b0458a982499d025312abc1cb9172ff2b6e342fc40a1567847e73905b81846b78882c685c9cdf935db42c124e1a7db4c6922a58e965a85485241261913499b0a228d7d1067cd224a8cf706285ad779e0d8da594772374d266ac673140223f536a00851606b9e41c3f7cb6002056d1275768c2f1dcbda6868ec403d23ab798e2ffcf49603f1dec0b3c809b8870bcd43dd9e8d3e5cd12d2c045eab2a201739f24e21b440132efd7d255492d31819ad8e478802df7e4cc289156d5ce6c781f9f17bd7cc18e5da28a803a4f83e9c48a4d7f3ac12890464c5d31b93301aa4a2ae9814bcf08f5dc067d6d7db9b35a531f072ab61b45945bd3c6480c460103936eb060294240fc038fbc5e736b9b1e8432d30f9d333419fd08b47f2f8b6758034303b90597472ad4d04cc622797e980486d01838948e2139f15a97c3496113e4478afbce339348e8b0e2d0e5b11c9f36d7045cf8db1e8517688cb9b4573eb4f7023508b1d1790f7df4f64240ee82e6a802d75be0a1a9e6f5f32ebcd8e1baeafe7f4d05c211d1cf072e0f174b043db67e838ae0b92bb0b62cb1701ddafd079e278b1861825d3d7bb802ab42a3162d84bb6352ba25bc06893d5c0ad787b2740b43acaba11ac0b044d05093eb770cd1dc654497e6050166a6bdf923e02d4750c02ff81039afd420e74fef2a99850a5660014e22313094d8faf2a4ed21e4c069e676d78189873fb770ec0ee8d13e7415b29b2d8f563fd65b4d51ae6215f220d8806f269850f67b39d68a1d77c93a05c1defd9e1c2196c362b9ec5e8655e6c286e3e4360b50ed11dfc7a8478b0c5c6b4a7947811f378c68a83255dffe6d74859cd1bce80c27c1ec1f26399f9b4f0eab0bfdb979689bcc385a19310f5480c3855f3fe0976af8873cc0e4eed9005d324b0a640c19cd448ade82e4b8e19e299baf3c33fb3d458ab30f47f1383b26e838fca43c94e989624bc502cf4c40737ed681483c340f56b15d2063f352fc809bd63b78ede01484825d5d5eb851f325875ae69fef212e2dad63f61fe5815099fe11046c770b003aac75c5e4633dd61fd22c799695d8c9a871a3f242724a99c29803d20c9e1089d886ec4d177dbd92553a6f4b893605e48b27064ed869679e6ce06a18752cfec03eed2345587ffa076ba3b7b0fe5f9649642e3dfd56eccefc523c031c9e9f83af2eded0f0c977136789f01b2e38be3bc47d245bd52e9dd0da7772cfc0b9c8f016ef8542ee6e5cbe1655c4e2d0dec4c81defe18fa814719ba84bedce75d8306e0798e6d27b0373807f36768e5741fafb604f548e9eec565b3ec79914dd336f0f75cfae82aae60597b8b599fab5d78a0cdf16eaf3ce531d9593765fb7cd3a740d070f925edca8fade3999dcdada33e8aeba2715c1685fc087e70fa8d0413e01092f1d0513cb3e7cfd101ebc6f99c1dd41b4c5fedf783a92c196dc7471b4c097b42c74ccb427d3228b357804e6f83201027791e2ba3a719229bef5ef0ff2ae167b93703a69db4e9fd9a5a8636b76a96a90e3d1329204411f92ac448d7be422b7c8455a3558dde2cc41ea8917c6f51d940976b3e9e2cbca0c2fa82a4bcc0ca5531f30e8879466aca5c9862e031d071c3327ee9b88e165bc70f53e9f63804a91489b5e5c7621079a2ef4156b9520ec363b91f6573da7b94b8188eb4b9a53e3af0381a51c5a1fc04968a12eaf671c7c163060e15c0e474f22341b05b7c9a2fb5665709a6a438b891bc2af3e3d725c6eca032deda5fcb54715302f1baf9d791c2ab9a029fb0b44afba8cf6f649e10f3a8deb2a9134ffe6e6a592586da8add767c48826e26d1a090a2edf23e9c7b70761a1b6a3e1f4da86b204fa2190a9237ca10af47c6d0d8cfe8d11806a393c1571e64636db755b72cfa40cf12bb9fa8f654991ab5f3e3edb567028bc17efb753d6f5eb7486e6516f2edd4c8eb297c6e8c8904d512aa8b00c169ee870a780c5daac13fba72f6b93bfaeb2da82dd6ddfdd88829ea0b3a4a028d06f9c42e6946437ad8f71047f98e5e592bc087e230242696f451098742dac35c138893e393b2ba6a08d395ec58118e3fce638f3afef48224d3e1af2ddf231be5b1085b4a4d215794ec7c98f3e46ad24ac1b8c5d3303f078d166149ab85edc0249309fa1b6798712b78bc868ab562dfbcd9c5229ed687932c5af79da0c51d020d485bdba3ebaeea59b4f59a1f1b72bf81dfb4c3c23a0311bc1d8f880f8cd922129b759d7bd28c3603068baa358773275215be86cf88abcba9c9a79010b98fd9b2d4c5229c3a297e9d2faf3d3dfba5715ccad3621fd4c1d81b149187e450a3ada3938e2c5e6104c5baa84b2ee1a22f39d179846a323fcd9d22a662bcb2aa5cc98a98828bf65eee2f84bcf02a0b7e2b9ccc44c691e1d8c73753d1c301e55b465845375a27487053771b602bbc0268afd29c71da16ef73be426401f1c355002de1ab50b5e4301b479af9f6da502d47d75d7c21cb8cf1d87e049459ca2f4b7bb3e15895684b486315e1c10d47236c27c286a03f8d375230d20685bae009b5d815667331717ae4ec0ea281bb99cf8fe19378b4495efd1386fbd89d7ebc634a03a3fcdcf57224fd36c089c4f71ee007bc2914d5df6058c271dd04722324ce81a066a75dbd55dd4fb6c7cd1280c52d858a6777bf9d85ef97d30ed02e722415b1e9bf2a1128089368544a0cc507dccf6c3f20ed3c272bd01b221f281cf6108550c65b31ad4d450bcaf8dba26872a67ba458554a93a85bffdd78e21658a7b66fa439aa55245b0bff06dd9fae105cda8a093355c405928133d2864e94629ca60ff6e82bb6da36c4a28914ea125da0648304632134e6ef63422d288e7e322cf80e5ed629837527efc3fa845911c6eedaeea2c3c9f8e9845c063510f5800575c3c444fdff5ad1523db280884f7813c8e53de093cc04d967608a51c164fad311bec95b79f90ed3e61bbe7c53f050c0167b97721e1db2048681f397f20042acecd0f6a4f1e4ee7d2e3e066f3ca8fa48f52521abba299e0a0cccb56e9575031f239d1d8162ce8c895aefd0bd9e26c5c8024cc4489ac627c1128c4777f46b9ca7800818061bae92d824539d1a0d0f1eccb9d3b1ed3946fa51dd9624809bbf05b04ba7b2aa9660d229a815d58291549335e25e4ac2ac00636ef11a2a12e5c11864b860d477231686cbf9eafa2e71e21367528f3477af6faa2e2ccde6b0c241f38c7fcf8bee8510f68b23df93fa690a6c7a5aa9e0d5e911bc650b1c11df4d3da89eb53349df917b3079c07133918184b40a01c8747d5df2349aec315def63973864215fd549341585ce674fcbfd3ecda881b138e8807122315d670b819000045fb4f41bc7571388876343d6a208075210949c2ad0721d267d960d2ae758882d4eb3084b0c94f4dc334fbdb88fe32b159305402bd611973e582296a14f117005faa2c998d85f5456d3382f7a7b7d764532fc632673884ac53a515a9bbd280282d6638e1eb0888340d951843cea8e9e5f1eb93826e008222fcb17b07fdcf3a9b96dcb5ab6d5cd8e08e6bbccf6538794ed7466fae2b65884310d73af24771172ee6d1bf5cd07f5d16255d7653b894206b057e2a81e2783872f7a15d55a94af16abd3735a17dcc7de99ea323b54915ec2bfa5f6401ff5d4956b6b3f2235296fe36fb46c81b2dccb7a02cdfb4ac1bb77a64acab41c8ea731069ac06115387fbfdc05ed301bedba3194db588be5bf61e871dfd23ff9ec34916426e8401e2b58980f2ff88bcaf011e5f74f56ba9fc1f14735b37fe4f32cf19fee80e28cbe5bed425874531875718fb9bf8c061d9d45194e45c41e9366597e4c5cd19510d3331d88c20ef4ab48221acd5633562c831ae22e54aee885b2992897980480f7c63cf558961da157177cb610467a38beab43d42060bef90ce7ac17a83013d62b5a3cfb211d64444ce0b25d20b35a2ed53008587d6c23ed26b8a9a3fc2b26cdaa9c50a95d0369ca5a10dde3f32aaf6140b1df76889db3d42838dcaffa04953e05f3b8759f4ab5026740b079e7a25d8fff812d27016d8b1566fd54c855ae2973d57727883828d3521b20ca9cc732d33df2c12a3c0d7e5340fa267908e2eab2ac1afe7942049f716af279a807e895dde7a89a1783e3a73e3ce8d15d152af174cd82ef8407a5173135344b32d630df2aadde5cbc2411c66edf568104378c0a3a33bbc9cd5428d22ab46cd1f1e41fded4810b77647d3e3c007f089a0e3b758217f1c87ab7f3dbab53c8684a6cf90b88c31c9a6d1c5a917456a8e4fa05aabccac552e10d8a0254075cee83c8066ee74d3c3dabed33dec92b6234abd4a1caba0a6c82fc7ab144a485381b7c5fc65923ad9368239a74ae1ba61ed115a7c945d53213254e61b16b648f97f3e437d8352521b5f68ea55a2914dc3825cdc8b34f4bd5c116232231284adbb85be6a509fff015ed8768945966f0783c066a46f17adce76d9f5de0396dbac530aa4904518be52c5f3b453d6220318ba2a246fdef690f1b9e8a6b3bbb539b9a321fc535e658179d93959737ffe0afde522e99945c6856e65f85b5afe1b6f7c0edd383be34327b4f8317d456c4fa68bf74b928da80dc8384a119ad832c505e028d71de6d47dc265c2132a08c53324daa7be84c5a93b4bc3e9e3d5d9e462c751aa075de015bfefdcb7e8a40a41f4ccd5fac35b5ea79062fb524ac569c5e832d7be39c6ed23b2d22d0f83b013459c5d3a04cc7f0824d9827d841c949bc594073d379af9a39940a0b8301b0eff16b853c7d19da5935920988a9a67d3a0b6ef37c145bb46a255b783640b9ea57b42976c5ad70adce4de8e944193aa14fbba1ec61793ef9d2c5fab62e72cf834790504e8b85e4608a0128725a81fc2c4085a33b09e25954ea8cfaf07ad25eed649b548c555b88edec6a8cd188bf5c6b53631dcd473267cf20ad2ae3229c520c6b65d98b84253e1088bdd5734988bfa88671bcbcde2fe5a9dcba54568d345025bf163c9ae8cc33c7abc34672dc2e9b2bf0196643ab2ead9f212c2f1c5fdab5a33f8c8a5830b76414c52ccd0fe64d211b7560eee0cac2ade9b8890a31d979e59cbd05d481b8a942d013ad5f851c4b422bf551f31528465944ec45036dd079036a656a4a786388e196c4d958d5b8131b6b03af4e6ab42fce47e8fd54f03c4d029dd7355ccf072ff29fb858890f30a5b7e59b89365e11ce6535b28f5b420c4c4e695abaa54d1c4a3e3b6871eb8dbdb64f6face977ff0f1858b608052b9df509656d0ece1e9cc68697ccefb08c9d415c5854e9ff3954e9d7c72feced2528ea95fef29a6aba5427c7c68df1edd6e191fe76cec2b822d4812c3036c2b19ca9d607c1e283e84c01f14c3771ee097a9a49aca5c6e1b5e5fd9a754a2c8cd7a96d3e1dae01adbad583fa2c77fa3c696f4195f37da5a660cb6b1735f0b75606ae7ab54d89896a6d20f24d5c1deb4b5d5a277ca0574409727e12542a704eef9207e5022669c0b49ffbc594210ea6de7ba545ceca099069d0cb542123c6183e7320f1dc9af33be0ddadd6b44a681a283054e5e91d0aff8d03a0d5bf348ec06cc9265b79ea38295f1977a505d8158cc94acad2346fb9808c109f6b5d0a94436ecb7fe12b45d9046edf5a781f171c7c530242a0c8094b8756840a77ec07c86ac7add7d5f3879c61252b9815395eb93a3213f46015d4d82d1b44c03a351f5e6090c25395fc91a82d71da6d57e70edcf56313e7f787edf7169c3c76f58c92ef968c2d8d3abbcca0810a413a723f2e0f6c86435be2d450a2f54c6e48861ef0be1e2904c36ad2e69cc28816bf115e7d7a73684b577089a56eb43ee8de19762ad4bed901ee89ffbec5214a1418bd1d4e682219ddd05b760b0cae4ba02cefe7d8aafa17a1264ba65a8d75df93113333b86330d522e6c95e390e45e09ae2736de04696c6eddc67db3396bd13312c6918b81defb32911c57c3e05d4a41a84c7b33bccc8553fa88e95b19a603526e9ca22b042a06f2adbd698236eacc12acfa20bceca099d7eac029b6c200eca99ec903f60a0aff1e5da4a74d99b35dc0788446812a0f10b80318095e2e42507533ec83962d8268adfad61b82976d8048cb74dc08be830381a390fe276ba8ed38ab21e6244ee38a75371935525d396249b108240fb9d48414162ec901a4b214c2f201b711462e027587545ba594436f52863f5cbb634380dd0e547e6aa5d2dc654e72f734a0a5d5a598f61d8bb4b6f4dd568b841d0885f9fb2055ea5c13e831bba454b4a77c0ad525d5b096cdac8349a4fabd916fb3330f684750bd315abd08145254006bd866075d594e428262f3fc0b8a6effe4af59994e8818ab2e18bc71d5e31f163838b71941b37399a230e3caf1a78f1fbacbbf3018d3bb022f3899537668ca642fc86a7fdd70525451a4c050df72fca9d1643b7421653c0431b899b224bb10eff386f80be534ae045ce52729d000b46a84a5800273de8183df94d6049934365e2031265e2d8e3e85181fd24ae3bc775598b454722c8adcb4060816475697998e1bf9cb426bd0040b2a421a94a0b704debae59c09c0220571e0ac6ef3c2acfd60e8f68ea114979fa1b9374ea981954b587dccc7c931c52ff6a30dc598123cdb2ac8ab67caa20b1774e26aa2d9bc6a1aebe96063967b10203e06387e74b4783f724c58d67049a239b1aeef936f283578f9c94ae77fb378c7ca760874924142cf47def503fed25978d1c20f298f903dd12f0b28ac24639108f95e034026528f4779427f2482fc08d76714cf6b70dc6851bbe5d59b165c6830edd3f6dc7ee742a37a6a81df391d1b6b6a19720c67e10569ec6aee8a6e31d941de003dd0c6851745699a1d49e3f24e8e0f5b8b931c7cb3f4a5eca0b1e1bfd83f25a60c5a38d79c06ca6af5ad39f7abd0d1787c5d210ee16dd8ad5aab173c22ab1904c42e4fb3df1cd8e216f6b208551498acc905205c01592bc04a56bf0e63609c199babc9ea11166af17416eecc1d0ea837a86f80b6d58e059c2a2018732b314803d747a816d72702d63abff1c2333f1a23eb4837aa404b65d853cc1d36593ed62039735307ca08628a50ac4a12eff1284cb1f29b51541399f18e73d5bcf46d2dfaf295c337237d8e7892eeec5a0458112201f2ce947e5f5e1c5afc6d796aeb96265c1dcbfcc774255663253235a8f2e38287f14451f79c4cda37083d3cb4fee3c8454ee47deb2a28638664d614191568e4ca173ca004b6bfdd9ef5b3c10be053986be7e7b9874324005e85009df9e069598d0861ce4c6af74741b64c32d5d24adce6edf7a3a8696561d8bebe911f98ab56e61157a6a8223cf05c70b1bea7216bb018f71ca657d7a52ebf6e6d625e24685c607d3fad53dd1324bc329702273e690e7c8af775743516e61c4fbd5595af8b5d31603f17fd595b7a602e9a2fce654ce1224c17301009bbd0fbf06788c6c8a401f0f710ddb943ce3188bd5cb31d138d0d3a06bc961a8cb677fdb74788cd4cbbce58c81fcac6c2b3867d3ae9c611c136b22e9b1bc6258db0e9abee7df191c17df0db7755606aa3b893eef600ce09af481063f960a131833698e520368fa52ba311921442f20479b47bc377535a656dd601a5e8a4380af0404757d5513caf29b28fcbed0486f0a713c06e8e6d6681554588d4b00f840c94a3651108f096db0dd8981c7acf7a38b9d4bdb3bed9a19e0e06f0fc312aba554b2d1291655cf443b90286a928caa70d70663dc691323f9fa46a2d0e18c15ba70cc14101d1f3a6a4707f8d8b24a077210aa55c0342ad5a13781a994e5d9200297654370f8645586255b081892a5049145095e2f39817ae97f0ed5c921e9a85eeac0220fc5848b619ac783df02f35bdda1eeffacad729db21993f6f8ef60802238b9b1b17146399947c6f5cd16c4531633b088800f234f351cc7438c633619d150ca254124ca72e530308512088682817dadcddd1b74eb590e477ae071e71f984136f3620ee631834f92b438143edecb489d0c84763a9c8576b5120f84231f9f90e6aedc835e954d02c7ed4c87c63f52d1835a28a28e00c40cb19ac430719819067340a1810c324d56f8699b54a09ef10286ce4bb2261dcd1b4379a163514e5058a6a39e108bb8bb6e9304eedad635864b3f51d48d46b62a7fbd96f2f59f106103b30904c60a84265d1c932d89ec0283a206c22d306a68d68195e02d7e0575cd109885950302ee27b1b0efe247f5d2a2f2881eda558b8b3c3fd6fb011ea4b822601b9a137d9337321e21a683dff450ddd38cda50b201b6f3b4fec0cdea441a3ef2d0399d0d9c490e51b13093d826fea816d9bb7a74a0569c92fca04b4e88a452ee275df632055254c7d7136affa2a4b47722c6b3c483c95af9a485d780a456126a16d9ee5b37c78d3dc3b0c02f139088079a30beb9fbb4ebdcb28d15bca60dc3c97fcda46764959dece29798fb345cd2561918ebc5f12abf782b57c7ff6baee4016a066252427a5c1cdcdebe2bbab2984c63784dec8f621ad1fa24413521550438642ed7bd9804b1b98ae8f61232b1231334bd3099a327ace32babdb2a245e051aa2296473e59afcca3bf1524330d840489db3f5c2612481ab268b526920f60f1ab2b994734c83ef270be445a8ddd8ddb2425894e09f687f461be1e9d560d063444b46dbb3e191ab8df6d39f7c36a9a9f9e8758f08bb65c1ff5a2f369a93497efa65c6c5b972e48d21aa9f11c52d6062a2a5de668496232493ca9bfde360be642303f587d611458b1ab744a4521c9f0e219be19a48f934fdf4ce605ebbba3d1b8d6a05159f05458d901398ea5b47f743b8f2504ac36759e69056d8e9df55d403cc0492ee787f76168e9d02bb963214f8e0b468e09c0db57e7ae7e15f2af5acba8fb3676ec55598c85a0339775527322ba36c3dbf14c8fc2a59aacca9b06ba675b52f39d24f0245d4fa0116c001600d1ad0ef030107d5a240efac5959c27fd018abc4ddd524073be2636eadc63bd5e647fcf1a5e66c6e622263d9d161e00d671c0dd6901786a21620ccd8682c36c929081c6913d87233df8d8523763dd25ca6a81dc80bec9bae0811fa36f2b03170b494ae1f30be5ba5eb9e508e2c4ece2c311fa1244faab1c82bc2d0b995f3edd7223f642b462340dea4f461d2793f8a3522405a43b60828064eb67185180b6d5a349f4c95e4442073988c370263349bc55fe80b9d94766ae85fe1e03b9901c3cab60e28b94015c14c3712bf8b928ca9b164e1eb6e2efd60986ea220b643083ce860891389106427ff52e0dd4278bc099fd8daefcc231e81e7374f4ccd8c1b3883cb8a09c3df9598a37cff0cae9b99ef09e79206c6aa2c4cff66c59eebcc42206b1168a375137273bf6f2fa07f89b4951177530e0f436d34264228705a4e511fb28fbfa0d04d4aae6070f0d79118ad987d90ed1e374a4b3c6aac72ac064f1601e8851de12b097ad2840c3de7129868ab279fd6a1558fb182791c60d49b0424cc52295b77ee97f06ef8d04703a3afd527c7b5e24d5d8352d5a5ce2a734f3b976e4262a6ffa6bf4d99f68d9f753911f1cf024ebb3ca67c2521c107543b144f00ad2cf0ba0f1a33c4f159c8d83d474f2277732a1584abca737f0b4c266b7ef804277375f31e75257b6d79755f3bf1a8db9929d18a977850f6fc12def1cb2f58fb6f36b33d0023716405146745399f250521b4d06c3a08e0667bf1bf3f53730fef294aac1607e078f14943a3416ccedabe3538f403d1dc77451302b8959777c56b471abf336774face25c2ea9fa6082b519377279b62a6f7d3e8a445e7ae4c0104da258a8802d31e0a77f6bf3641e2d19c68dfeb193d6c6a63aea2b1f70482e8c299652aeb6162877efcb3fa942d8cdcdf5ec4b8cee48c652c3c1ee48274e9a1e8a8632191d1f0c690b74184ec0b48407a226d90a6374db6a2ce157a535e4bb3ceb157da60a16887a74f98711ce425f72fc393ca0e90636a8f7c4c0a12c349e33b24d1a3fc89bfbd9b448e6edd87e3db5ea8a3c0bd69d4b7f2ab2035121e1a8e0361294b52057dc47cc694545fe792aaf4a6628a3705d7358e38388db08693db11ed3522908b8f6e9ee635d2482fc28064f06ac53b3097fe1654653eafd63cae1ec8101d2658b5b8b0ba7a066ccd078ddd90d26a6d6b2324e1a1a09f6384c0b1ee223cc36e9af027a337c9cdaafb76d97fe3bab5d2a00d8526dc9189f2c5ccc80c438e4f1ffedf1e39aedd95642ec68da9968bdc237b2731cd9b7f90cbb86ac7605747901b60a4405ac21626015be7369914353a322b899d98d489041d32d7ad6933b13cdafd8de352303371b0b5fe66b2e6e751e59cd5f6b125d2a9ee6dc73b9f09a2fcae89d5889abe6b2c5c44afb5aaa563fe6b8ce629b83fcc8282ac56ecccd71b6339ac5982116ca05d95d31aa6180550c6f24e4cdc67d9667909c22f5f4969f8982054706dcd8969a390aa82c2ca4c1b7aa6cee9c7d1f878750de039e54541375daef7ca65fbf53865b6e4681b2e54579a5558f14c094358c89898ba4a573289f940d3fdd295cb48af30616cc094d05dc6037de09853638cb09a2a7fcc071ebd490d0f363df458acf7ad9a7d4da3063d161362f6f20bdbae1aa87cf9526b39e6b299395e7b5fe2a695c1b612ea02279e0959196124c00af909f903965c768572143bed0505b81553e658247755206d655cd38bdad2b6f0c58a8d65447cc067689016bc85d0175e94a723dffa2c57967a2d845c5df8b0107548a6412496404bcb98667bb134dd91351a2e72c4695e764b0c3259e306842fbadc3f67abf68eb12e34821eefd4ca5a44d52f389e88d2ea88da746ac9e1aff0877f5da855ab230577d24acab71ff21887181f5a0b51f5eeecbf67b24d110bd1108f88ff271347baee922579a71d5f657e31587f818c8d04dc076f06acb1fb80212917e4dc3a1264ae1dc9c9fc89750ed2059cae514b451e73bd3a8978fcad32a4940562034eb3ee8684b9c193216be223bbb2d144ebe51967f45af9ca5035b9b292be9610da59b2fa8f1dce7056d1a4712cfafb07e763cc5d63f8b0c15c27f1e8b5c156ceee68c6b14a7e49110937d03ce6082c06bf44eaf2fa312f5af11f66e5fb8174a0b9fc5ab16ce2fe3d8d51d0c53ce8412189ed8c16d520a7aa2e66091d388187405291379f8c54ad1b49a0a87595492372edb9747291a8bf198c82f52c3f266ab76fe6e97a652cf7effdf23ef7e717714619e376d84d17224a11884cbb634515f4688c23d6b50c85ec8169040ef9e0778d015600168a573f1c951974f9bb789655804ce3772c9cf6230c321b8167f35f862137da9067f45f24007094d1527e0ddd0814aa6030a797122c3091729cadc320370258f0e937b225c378fb3f2d8c6d2a61e65fdebf11905a348464a443434293a6e55ac4b7cc3acecda094413eb4b12bec0588a1b0dc5652d21046b95770d8f251d2c5f6e6642a8900162000f8b0ebec0725e88c8439926360f1563a255c9c0d925603106570e0efc2f818664e88bb4b96504dca4844a141043921ae93f283870b11856221446336004b8278f366e64eb938db95b78c6173950e7052b06da6618c7bca31b7652df38527072917afb2767bcbc9b0c83edf24618519957460679d2da41008617f3133329a8cba1a188fe172dcc47bbe304f1a0ff169fe52fedf9b0171adde24a9c9d132d43e013d360bd96e0c49228582c180e905538326dafd5abf4e32befbeb07132c568b2f9be7ade42e115c2238309b96eeaed675998f2655f7c067c21a43fc7756ed7332ae5401b0bed6d6421670c2045fcd714282359855bcee053b91bdef42ef0eb3403c019892e8820bcdf8d7dabe3391292dca86b4d9c6b23401d0f80c914877b5f5f5091ecc7aa85befbf01c9e2c3c4d9179792326b3aea647e292f99b2afb3b97c8217c909f4f3bd580d465e25b95750ad46e58fe95c8cd4ff3f99812ddc7ebc524221cb9b08b28903a56195c2bf977b6d5c5a8aa3a4ce6f86c0fdea1ad234d4a4ec4165a4c97abcd75dcb71250a224c1c7c82fcb3e8fcf48f6d3b5cb33d7710aeb0d747341fa58e9514407960d850dedf913f2adccb54a0e0f8de7e9e9920800aeee8840fc4cd2387321cfc034c7e64ac3cee02e7e7db27b760b31ed1a573b5209df894a1b00c6b225397b6d26b5ecd3fc7346e7a79d76061bb4331fdcf0bb63e1f7fc6571c5d491bb766ef027843243deffbed40debce624422debc42f2698cf703794ebb408bdce8f14bbe691c9561ac8450c658400b8a8172a68fb60da427cac4c040b60c8b4e9421aeb54a5af913c53fbfada48ff2982d312052f2fa189c00c00a2352b880e4fb8eda61da2ef1b1f28abd14894baced52fc72534e1524a57545c9d844c89d6cd9ffb6238508517a9cc588889de1b63a0b07fc8c25279b672ab41ecca5971dfd09284efb54c99a49c7e81dfbbe22e495d7c5a0a08771105475f7cae46a6cce30e8f6361b148e5ae3c55c6c233209b30cbd08edf2d15e28d0e7e50b7a9efc81093bfe4853e7d813fd208c7591749324cddc1f9e944e470dc6591f1958123bbdc89adb0e0c8089f561a78301fe435e1d5ddbd48f3ddb04f83563847bbdcda33fe47b83bf0d44b790bd61f3a61314eaf9f4742ab714218219b3c7d84dd96c71099f67f593c696ae306e6568c39737463d5bc23f07912207551dc0757a340b048f9617794b43557bdcf78237d0041aabad3f6aa746838a34a65f91d6d4834d24acdc76e01625aa7ab5fadd7fc24e06a39537e9076b8cd6f9eceb439023a53f8ba3f239214df79023d3ffe2420d115d2eed8dd084f81cac79b48e826bea66f5f2090318407b9cb9e5df1cd47ffb69c328c75122571fe540e68efceb719e068be3624674a7e77d2a6ea73279d2b564004c3f349d9d2476613abc8368f97c3ff96ac2b71463147ac881084c252cbdab5563138b39d573b691e7f7839a90343cddf93a8c663f0c3ea181588e4d307d0ff2a5825b643f0cd1eed5c856e7222b0edc247b69f9000042fa463079dcde725ec0f0d816bf1df654345e31ef91fe8ee6af7afa0fb1d67d0ab268bef4ae5fc099b885931b1c4c0ae7fa054c5f9323ee1bd5cd15b94d84ae9fab3c8437b5f84883b4875e729c61816fcb65737985d67641217d7c09dbada3db390e94d61cded0ccc8e4d2561f27cced6c5fb399f328436e6052d392d7a553134a294d0dbee123fe0fb0504bf79ad6a24d285e518957640a03ea78f682b5716d85864cb065dfff2c45d53d5783ee8249cb69a1899ee788164d49591c53eac2ae20c292b33c5ea9afbd44f468ed6ea871df6c3dc5c891e2c130165f26d54f7ae9345d738b159abe086e07a7f6c598d4317e245a5152723a8e134eff1cdea64ef9be0e9cf62e6ef2e4b0c1942998e82f68dd9f7daedca5337df055cd397e3fe8913afdf780f51425121d0bbf8e88a6f3d4ba87688cda748843dd82ea9ee2ce83a0cc96b856070437a011d2501152abfca55ef2bea4a479797542459533cc41af490e64b5835fcfbec896143ef76adaa16a6a90fb78d13d95fa5ae654734076afda91822d7e7ec0f80d8bebdd100b42b98fa2550ff5c65f81f171320772a4fee8d5bf4905282564d0c146a4604a5314e22e801e81edc03fcfebf4e5d5ea1d326fb1372a17fae39b6ee15880d4bce92a75f91115d8c61a17d8d135e1a4697e9f5745968738813eeb55b8edbd04ac09212e6bf5b10561b348b7bbdbd0cbdaae7d0ada9c70c14ba559fe3b265708c380496459e9a82d594ec3d3920ac1e89a296761046b545ee65dc7b05aeb1ccba044182146d22081a26598eec9d2e4a9eb7b48f172cc176904af5133ec4a1bbd55ed35f2fbe1686fba75f3349ab7724fb144a792f5757a2ef3b7e7cc3911819c2fa5d22d2567b0bbb3392f631a4770056e50e2226a1f1e2119a1c855aad77b883dda1915fd5fb67a803f72107032dfa7a338ae585af85f1fef7cb94cee0552b3b99e307ec87fca3a0273c868c3863e9fe9c8cc5ef1017cb2a1cea813157bb92343344af5e0944adcfd61c7fddb2ce812f18571840351ab5269993eb760ad569f09202d5becb8de31c5bcc10a2547f4c239c8219a29497ddf170053021062ed4f933b3fa8569bf2dd766fe1b26c66d10ad7d65926d9031e5a6f397c5cf6b51b5143d53574a2884950bb66168b2d4f3a1c61683998abfc4b10d9d461da8f20d2eaf3fc58c63068dd3897019339dce75a9f53832e0c8cf416c475002f9d4c7912e42fe61b2b91626bce16c0e9b68b00830d4fc26229fe0f55f95880555a244704dc9606130108e2eac313c36f02ebd296b9433b6fcf46590040b10eb3e43249e9ce721d792776806e9d630894b5f750e97d56e7c983efced6d3dede0ad352b77e4c3097b18a45f278c4c2f209011a25fff8cf0cc657413f860ae38fd1196dfc2a0aef5a6b09f67f63557faa50ac500379ab2c94772d17b0e6e7ca421e170c087ee04bb97058285be617a7d261e8a4ccc7f62bd363b41ae1f465dbcbc389c001f12ad5e48daf682ecf5df43e2c5f6fcd506d0ce2d5e01b9d527ba53165abfb9e852219002e3efab8549f2d786054b4c8ec335eea3a7b57c507d5207e995c782994bf8117ab57794eea2d441fd6986d68028964f6276e10418d5c0069ed411abc1659e54a2ab7b22b7afea1caa8dea9e7dfdb78fc55da0414ddcec3fb32975f88c1bf71d1be33e72c31fcd2e1a3d515796e154563c890c08f468e5f7467ece7203fc316580887135dc89dfe0b1f9db6f7866728339e51b9cb2c90de38a1fb4c80882e650be515db39a98fdb37e98e3b84681c59b98e20e1a2a6d3701f1d8f03e323c836e8bd8985c73a15f05d8baa6a171b7abb519ba7447ca402d92759bd30a405361d5dca057e4d4f4692a1bd2fe524b79e2053480b1fef06685d36d83a9ea96e7659c362c4613d3f71d2ccb9fed30ef9842d1104494b349119709cb6f9fb242c2dea34402e67fb2528e2758158220d881fb4f3b2350403c868e62a58438b0d8473aa18bf5c680d8ec7b21044cdede5b6c3d6f5f4e8b92f0e93d789ffc245b8cbb4f32c8c26c26daf0ce4a23cf7f809df3896780245c078a42233ef1369a216e81a5556ab1710bf0094aea1b8a44413210328dcd46f1db6dc48fcf45bbaf15f0b4a333774097223c474cb0f33bdd89b2372790c0c5ffb08f16b7fe659e48ba1a7fdf19b2eec06dc1a29fdbc4d9b37d7cd9cd103ac2dd4591c60e16e8470f57ac43205d8e0d7ce5959b4da3c216767196aba5defa68638897d99168de5fe04c5427a70d0a38a9bb3270651536c811cdcc65b60c1676e1e69e9c2c2de2b42e66698bbbac778792230e8fa973ac8f342118cee1230c838cafdaa8ddab03c30e64fc841f988970aded7346b49ab4bd44ca039930e70182464c2fd108c9003b0258d54f70c31ba69e373c09ea29aa0882e218d68e1d2d671210d810f536881cd14f43edf587a31bc2510eb20e0b4a861648abb6966d527ef5a0ad2405634b74472567f0e3eefaa473980e714e5d3f995e744f6211669e28ac6ecdec6eb90038e88a80e70edd47861b461cf8961f44cd20c8441d8cef245a8182f967a28c2d46b8a402cfdb857c1c2e17815c82e36e2c800851da982390761ea4ca403ef68e6d1646feeb48daa3d8aff2c947b3340c7ed34158767603318b2888ede09c7848d69ca02028ce6285e13f5161cc6043170c0cf7b6c52af8494bb75561225f90edaf9799a7ac51ecdb12d2de109c57d9715e2bf1ff08974cbf1c7dc513b6affd08e1d45a5e6c7a99520d3d469c7391850453ae966ad2f1e9851fe61b7a063d5756236a462231d0092b79012554fe809c1f5655699819c0ecadf4d3331e536499fdb9b1aa6a7ff2bdd3eb489f1ab019d58104f28f6224408a682c78b96d94d8d563e39b001a933653e8ef64ca216ae014191fb861bc7d9a3be809ec48720ad226c5066698953552f95e5f7c6249b219e648aea6cc419765c0953d8a25d65a60381cc8a9613ecb2fb38d804429bae1b44c04584f58d3acb9e05bb908b6da4e37e502e8e8084dfce304bafa5c225efbcf6d0c46b062cdec484f94d82ac18ebfc07e3e5a29698a40297eb70a8583ca3382664713f29cb94e09586d3913b884f8970c6bbda3a47660ac7e495faacf93294376c9b06e411100bceb561c30b664659e1e86ed102e85066c86968e0dd6376b6bbf5d01874967b732e4b98241917f1841f605239f6571919d37a95eca1ae2e0152c2d3004ee5ffa1c4d831fc24a888e4b5f0857246faf0313906ad04c3e68a23684c1c88a28221eca8151e26e65feda64548266705b4e107add6775a778f84fe4361d8c93e2ef5ed7a01347627370d5c1f8401296ed63b607411bf641706773998e68605361e8a0e992d791c3a948e62eddfd3703cb278055847d75abefe950938ef26416a689c591299140f7df63f547ac118eae140f6d8b7c5b36b5b82d5fa7cfd5a466c96bb784931b26d86cff9f31053e72b52da7026554f878c48c730091f442c2e454edc4631ae7b1b2c9165cb73c1389604965b7f336de82572fe5013a4ed1ce93556760e20b8c93009e5f8654a6417d48bb4ef1223259205491452f89d8117ee8cf43b36b671bde92090a2c948eb55e91c36abad96ab4400dd96e8aafd901fcc24c242e74493748b5890b2fb4589ebbcb38b6793ed304fe0ec0f725348eab87bb206259221002265f8adfa10b6eb205c0df7d6185190fb282fbcd698c5f0d4d00b8bd0c3c18db41a6906c9d781f6208ddd1839acf6718dd5f430d09da8e419f6a81ba1334175139e848942f7fb0cb67f2117a9911b204ba8cdb540324369f747bfe6c570d706a5b6a603461804e0a34e5b3aacc8eb7d9fc5e8c666cc57cc5f4aa41c252619284abc1c3c7c6530ddca85751c60671ccb341e316d0656882e79194e0f9be160f535b68de85ec1679eaa4cf6d903621220e4c5cc09607245596e5b4b428ee18a036fcf2136113412817d726ed01e8e963c64a2906ccb3a89555c0d5a535ba3296ba84981211e98da6f2c59b0238af00c3d229822c2f5ad0ca91acae3c0a5769892d194d6124c3adb38608435f1b386acb6371fbf4e4cbc13b94da62fea3be17302c8a15317bc6b7e65b7b37d08f536810fc5b1810a33fabb21c073d50def49c7a1f5e55371278de8b238f47194879c1d86e4993c00808da52d48d882b443321937632e53f0527949babe6c486f6cda63df054d8cbda7051b6348e04075e0d0ecaf848ba3575c6cf0d2cb79130d1a06198ea49fa08900437ed7f94d18e65c194a537b99a45d6d2818188d10b902ae6e798344ac7c2aca0610808698336d3e89b37de864529311ea16dbdca60fc709bf70c73d2cfffd5e98f295d84c32cff83466c7cb2fa606b6341a72c8fead0cc6a94eb03a9bfb7a70dc51d817f9c15a620a3e5948f1eeda0648055ace85b02eea1cc9ad302253980d95f0be5ee4820e51fd0f33baf0f8a2cceaa2909a0559893ec93a54834d37838083013b24c9cff057f8ce38440eb13d824c0a1dc6fb5af5c2ba3b33230a4efc925dadf56d845bbbab21479669a0a07016a426bcc306e90e898a67478b0efbefeeb4914c69403e81265d17f2c1e547e444305a7c1589f9d35bce02c112e41921d603a53f0ca3a6bcff66981bce7e07fa28e8e24476312f9323b19faed5658aaaa992d2336bc92cb17165a286974d7c309cc804e2a8343b00965794fb5c5a1a9998a5a9815667e4667ede0fd23d84df44da73f3da7019ec37a1c9e133754b298f93b2cfc67e0e0b684a6ecea4b05fe805f43858040d8a7ab1bd103da0f6278bef8b2f81de67e2a7d8129eb12408e58cd7aa58c61bc57b2cc8a90899b450bcc308934255fdf2c71ba50b0b978da393b61ea1e90edda9ac202e04c9a0da7249f9ecce2a024105116caf4bccfbdc8bfdab0b997c0ec6e127fc40ac87e4c94d129742a09d85c18586e64c3c8169443082b6c5c926f838f2120f3f2238704adff3ff6bebb8a7ffb9bad3272247d29d18f03ce078f71ff062f75cda0e56080dd3ddc6d10ad0634620775c5fa21451dd9fafba74a89b4a2c8551715294656c9da79fc97370c103df1a1c4c0d51b7fabf46872f980f87a5069b6ceb9b13768567855010b861c93626f9972dfd7d4acba703a805d97aa022eda092e891c6c1aaaaa80266afa451431558b3b0f409718fc6f31d93e0992ef5e30cc0f6ef2c7893df164ad161f7af95f4cc20d6d5b4bb60842eeeeeb951c452c2aa6721b418023138a2dc3235161b86dd5e39cb226adc320600307448fd528039993505488ff891160755b5ebda969110f8f006f45b49cc3f159699e51307cd8813e9e86bbfbfd07c03f7fed12f503bc85112b1cb042eecd44c30feeccaa67f6ed1c89c028380ed798e08b5590cc3b516a44b652dc451fab7927a9499ac331ac25fb5887df60769a35b25239ea436c4100e6407273fe0b7dacc7cdb800615f8fb5e923307b6a432572ff72719fb3c64c477eea9e5256a14d54661664b73a70a145b8902b8685e45283e3d81b9dbce88a612767c02798df2500708a8593931329a183eb59751fe1211bc35b46e78ea32f01c0ea945c219da7a37aef6d70d6da9e89e9239c01619b9c940f7f9c1379314153234e99a74dbe66434b9a3149a30417b0ff7bf4b36b0a2280dbf4b70dd938600b93b055d71200ee67dddd5d9cc685576f0fab3c3d7dc531269c91867e63d5785ada2e8ed95fdbd7cb3da9a36a2a871b6d1e97fe25d84d48624f66cdec62dd69304fbed3031a7f4daee7c25a7a7c455137fde522421a28f5229fd977c5ecc663394e0831f146ce897283c8f094186e742c5bc5a92c479fce768978b289dbea5aa6c26abee047b140b5789bce5e9590bbc78f48e4800ac91af0ea7dabae7a09166bd0ca459c10e2538ce5964e15809581d1e8ad4bda843006f4179fa269c8a65c2e43b561255b50db0cfda6d5357f08b59b2a9bcf65de4c93989711d926f3a9d3db9c672a0160e296fda2953454673ab284c7d1ce2e3d7ab929aed2ef63d593462265fa4dec93887ee0e1a1d447631101fbc32d09be11eb5842409c01fc223ae288a8ee11c5745bc50c881c8deaf93b65f93c09911c725b57ce422166801e1c4c02676322374d05d06e677c386e969bfbd06ecc30a47e0f2c2d77e8a5194540190299d28c6b1545147aef39996ced0e36a0a3e9c019462be8f3323a10125e2c90935b8bb7108ab64708f0ae16b64b3a7a8e02720e87abde8b36669ff8841de6a360c38793b39833e9ad23b19e215e84d4828c4f9c841f266de4b60dbe1500cf6228555a367e67e112bfde8f0ad2d4ed5c80d56637fb55e2c9eb7d2465578a1609263087ff2fe88173bd82cd2426b9a4342793e8dcead1719bbaca45f9af46f33f85139b642280fec76d1b482bd73841f9cc9481b00b10b94f818174128dfd5639dfb72e15ca3bf8dda57ce524bdc29e964907601ba08424dd5eba4925e9eedff332dd1f83e19242fb2a6d2b770f6f5099c3645b13efc39907a5eaf3b66eff6c10f408d05b59560f66477d09446e52749850c3264d1b5338c8f7baa4a989b1098a9ad0984441f693f7f5e6f2744d20323cc90933ca5e47b890a593b209caa422882fc997b370014e8974fa92ace8b6e6ded43a910255f0ef2d38957771039b82d2abf943ad683e727919552f75839c0e1b8effccabd3573939d95f14f0a0239ce79ecdf8f598fa004244716f4e39b4a310609155d2806148649379b18c9ec2b2097ca46825618acfd55279cb4215cf858f60c5d64cd9971e5adef7064cf268fb7be49281ae9adee17ba1080dd0b1d8b78dbe50160b66a20b741d64d17fe6e013164925d0b2df6ed78dfc89ae19603446a03f3c327c9a33326c89b37d62c2bba51f978b0c850df30e014fd822d34dcc2c380c48c7bdcef17e777a1fe07e4b6f088cf7b7d28101f2db1c892fd613fc8b32e0b1430b65ac9ef7ada5df9b915e325a3f5ae1406d95f1ecec8d53ccffbe02a76deceff702df541514bda9074084d403a6150ef01b33614dddc3f83f15d6fd0e492739a112597452cd144eec804bcc5338785b30fe078856b3b04835ee194103c816fe171c9ce7d2ba7b918984b7e90838170f522306874500c8df18f5807d24886388017ba44e2bfd16b25a5c8f05f694cf76128734a6b424f91294c7147e0957df9188cb85e8c336150881cd66bc9fa90a1d1cdac717136f70e0ca149aa4bc1f51a5146bb25d1604cc9778ad2aac93a4eaa7c939eb784f52722b0ad7f4eca88455546394be376cdd826dd49cfdd25e29b0c659d5664193bbc557e8f7e3c18cdce36e91a7f294a1b042bd420d9868b1bbb3ac3ccce3492d986df6882a77c7969a97a160864d566bae99207c38f28fcbe8ba3b2b5a6930677d3b4fb9b0d0b2dc6c701b5eaee44953e5102b6301531ef84c99a13ab31d4942d56a0c96e00869aabbab1f8d7fada62cc2bc7047f0a2ec05a1b3f88031f8255823f4602807dccf6f85587fa2b334ddd0430c4094213cca00c6525ee45db91a884e4c16c3672be20a8b8755980a342e07b095d9d9e2a143b6f14e55294a50979aa52fba022a203cacc228c99792eabb7692f5fa8cc619ce10bf8bb471d49534d58a058f58dee2e45abf5469118bfedd0bcb2148f7a604e6f12b5bb3b0bdb0ec356592639f33acfa8965be02816c04d7b7d0bcddb0a16a9990c3ff549819deffe7c916b49ef847551e9c5311e4fe57c1023f59a812f3ae58ce96e862f6b502d8c32aa64b4e609da69f774740735fb83c91221906657cc23e8cd1843403c494999d2d1d8e4511390314ccbb9b9a5da3717d6fd06947cf4c0e6ff59109703bd25427126cea2e2e0772e1908976ef11e9a83df8535c00624f1c075215581cf7b70c3f934340825bb5824a1e2b96a4694b3aa93b69adb1ebdbb4cb1aa097d21ed1af2a76e048ff27bc055ce4ee3c26624d7ba37a36049a753cc7feac1ff43a48e226b92cf1debebb8d875cb83b2fd0c7550e4ae5d14833d459a15d21763cef89a67b452790d463d1cbbce4b6a40668390b0249153748a2df59baf565d703a38b9356c31dabf849d36e9d5b3b0e19f2aa2c4ca53e14a2c7b418b774bc420cb40a085ec4c82cbe37ba10df86410e7c6978938464e08c47cf4c7516052058d4c4b3cf0db02b65333bf82700ff3b8ac4cf4e452b73a4c953818c66f52774f7b6b4d7e8225112242e635857d70eef13273dfb5f91dc683531549df5ea566d49748065f1d454c5cc11d98b8aba9c4bbd3ecaf4b34ad33aa166ee9846f891fca5d1ca7368f8f8f64046ab30a07a22943f90d29f341452da1f9c08afb19059fa8d34137326ac201904744db2e059c17a1608d0a8d5bb46ed2a5ecb6db94231c5b7e1ca72766d43c1f93de681734d04b30a8142c6b88d035b99fb4b71d931b3b8cef6adca552262b9796932f69580c0b9351941bca29a0c40b21cc27187bd39bc6f7b91efc4074327d602fe793cb9abcd17e95cb4028fb2d9ed98d727106588a4c38927bf503ff0ed02235c455a731045f94ebdfaaddcaaf8bfe176bac76c0cc9b072ab56082271e84b973dcaad6e7332e814a175965b59967eca1a8ad908d9fa8e187c56bbd581aba6e3e4bd8a2c4083fd558e888099f5141bbaaf0fae6b0e9c020d3020759d0e01ed774c210336a47055729597f20155f13feedefd59218fc56232cc7140cd2cfa883e06fe87f934595d260ec39f16a740e24a91de33c4dd0f2704c74fb5cced5d52a79cf201d353e56aa60b3168f7e71eaaaa5c98e3192bc2bbfc2f61d0145f9a0e61372b97e7941771881c4cf2b49ea47a7022618c363fb7b6ee059813980c4b8dbb751e7391eef974934b383bd85647e3558b4a303fab0a67001834b9812bb669d3b0df03526b4600aa8e47b70a5f05a935c2a63bdb0089df561b96fa05c33ace80dd5655a44b2a44ffbc86ac7a33c106ca93cc8790d80c51ad897c6d35a8d365bf1e104c1c8fbc82041643bbf33f5e66d5f2324b6815dd9ab3cb733e4c2f45e48ba7d1d9de9d2d9a328439e8b4dfeec44ec46111a36cc7be824cfa6784fcc01fcd47b0bd0871f2e13222c48c4805a2049229fae9483649963f0999d0d64d15063166be5fbbf5659ece561e0c71ac2305d0c9476c7c37deda03ad7e06f3dcb0562fffc59ef12d74cf5e1407b14ad32b31ddcf312aca32bdb0f743d1fdac88fc793758ba37acd0aa756ff736393615cdc4860b9428c989003d4c98b86b197e00af6322e8fc1debdd73950d40a1cb6805a5f3f3513f37824ff66a7670cdee8de0148c47c2ccda790d2b4dcf8c50e438ef113ff9003bb0290d26d711a8251298325c425a79e576e521ad9e9cdf825b8ba7c15c060918acfb8dfc15bdf549c7dda7b709c89f655b7c2ed9bf59b0b9e8236f6611bb02b97f7cc25e28c7c4047766d62ce231e9406925bcf427880c933032caa0af5945a19d2e0785f2d4852dfca595b3327e62431f826fb9c9e5baca95b3e018489f79e75bfeb62b8460c9c76811aeb63c94162b8b1ac28e7244752f06d2a705327d7d16c7ed8e7116daf2b7e31b3236bcc41bb6b597ce33a0c7fa564e9d38442f4c52c0fc0c46a823b68ee9576364af699c438a52b9291a99af5ce004b1844ee26c6e84fea9882020e7b548f169c5f310085cb22898df3749a85ef20c20df590767ec0ea12e44b98f2ce242675e9836746d76ada201ddf4f411c3293de460a58b40d8c7816b04bb7cf222364680d1e94cdcb4f3ca831bf1afd1394210951972ab293b2ccf0419b3c3aee7aa9da990a3eb9c5fccb6f88303d05a57ea9f5a0364db237af35e755a171e632262d048195b6d8d20c3aafa390ae846d050661842cb4eb4d07cea92ab96a63a5d84d9fca4e1f9ae1efba59e0b90a906094db8846cd44326bc1a1e3d36b905cd668bc2ae69dcb9e6430ee74ce1ab31a06b54e10610279fbf1aadfe7f7e530074d4984d2ede2321c4da08d2d2b92a9592b62b5ec8fb2a4c91b0618c44480d0928c4785fb45efab464a9136c45a5f32019ddb2546ea2445b9fb00c0030433804e5f2b195fbaa3d6d1c31077d36e2eca35aee3803aa71e93f8cff094619d93f954394573b29671ac1f82605e23cabca91d05415e65863f9d4076b8bc5dd386c21f3cca5cf87eb8d376ea17365c47d4852f97de2c6581951a1be1293fc0d9f046777c0ad92ca94fe8c76494bb2b6cf785fd34a4dd91c867ebe2012c1599a38fc261a8ca1ac007564d78f1d89797ede49a3a2875ccb9add44950d6a2df022f7bbdacd1970e46d3475c9b83ae7487a0a4c8ffbd862984c556454ba3a37f2f204f07875c9e66c908e109134602480cec6466242deb2f614c44f1aa47f35641ba308853e6dcef32f286b897016404ba9776c8cf2aaa790f57631503ed4fb1867eb384c74afd199cda730a884cb7484f9ef169f50440ec2797451dfef20e317021e2dd3343a9e21617f7a96906d19de5a95703c00b3d56929a9bde80b60d09d613065717b54c598d99ab504bd2e68f89afa3785b6c91684c72d19765f58e4a7ed0b5221205a8896bb76d2e1d7af35cfea83626197fda0fa01054d0b8a935f56024fb8cb2f85c4cc71424c0afe740e33eb2c454e039e25352c7c30243b72a97803920cf9cc3ae3379039c21a1f7a0fc9d140a44c221cce4f50ed598469fab3b96e29ba9707968101a9962f57dfd66a1406457052de4061b0cdb94893a729cf31a6a64729fbd489ac0e98fa7eb90badeea05bb3c7076861790e4e942101a859b00aabe676a531a33ecdac57f65e48549e8d325e399a55e7fc74f4f2403f327520daca8e22af2dae163b398e364d39f9ca3e25209ba064efc54d0d33297fe2078407592097a835b64c735e5003ce0ff2828516aa82b57eff398381dcf46c6c1d0f2c14b8fbb4d434082a5e5b193a89981ba4cfa042ad8db051b37e75faaceed26a51a7ad3254b6ef4eab0a09bb7400608404e60d80a590ec5abe29bcac97a0be3f8a8057c74da6d7e9c4aeea8da36cd318951b1d082cd211bb8015ffd7cfae1eee6d98ce8ca7992389e052554680cacb04ce0354a6c5d836e2ce205bb9e9f814a7605712a0295c763fb921a91b959c34ea5d13308197d154548223636d77a9cc54bda99b58eee19fec916be940fefd00e6ba67c29f87cbf7e46b838758d2f0a68f1874db4e4a4e4de1a649a53e7d71a8cbe34edc0bbf1be0884db67741e55aa982d076a1f2a8c52cc621728dfac6e08ddffd7a7a8ac3c5cf0c295529f47fd47e627a95fdbd6ccdd769e21ebb74ed40ff2c2616387441a0b78d8494c4abb266879b799a587ad3f2a097fc75ee7b590a33a5b928dba25e832bc986a001dcbc1bbbe37e1ffbcd425ff2ae5716b3a41324eff058c393b4b32d325c93835fc1bf5e3c6a98fa4839a7ec7263854ceaf5b4a335809be6362a685e09d2be31894bad997a1d6469ccebc78e5bad0c5d228f0bba02f873c0c55503e5fd6e90a35d179361bfa42661058ddc14f18deecaeb3e2f618252923830656504c218b1308c420e10ddbe3aaded7ce78d0a4545a6f7e74d37f8e092dc0d81fcc7345332f4e79efbefe24e121b13e1d88bd8d6e5bc4268e612304f3c39333433360c58745fe535c5b16639e08e3518606d7ff8a52a8f6b4ad22229b083c601b9413781d3719ece5fff095215856597fc875b6c514a8ecb0bc44e12e95c64b60b107305899fe358b63f70c700b4c2cc10ba5b4c17c13965122c0035f802f4bd0496290f5cb9376ac48a421ce5400935ed24e3be7a2efd52d9019327d85366bf0ffe7fd543854d20ba69c63ce907fa6b9bcba0f73384fe66fb8b3e1815cc9c83b5f02591af5a2b8a74e35a7b16a54a14fb7d6751e3bd530926ee510667153c8af626ea50068d06f0716532b223c25338d8962d5370efa43bed1f0b47c1e4caf4c8ef2429cbb48c8d5418882204744a9e8eb498df4a0b83c05c06cb6348afe2d46114047df3383595c915175e603f4a86da7c795bf04f3731d8c5369c4961f32b85ed0240a30bea40d07120c59fc3238b421e2fa9c74e164670caa5d7e00b8f886a5f21fc9db65403b79efb9075c5451225ae320cbe49471fc435e73eedbafa287168f7f139314c2e327735ff156af53d6d3beec68fcf6a208f5f6a7e84ef73dcf8f4d0540c094c452098afc764157a328ead97e15bae29c9cbbfc355a5f5775f87935bc9c71bdbd6aa8d5b3bec43c230fc492b441308cff8c4279b3ffee086a90db2ad795de0876d00c884835bf397aa214c9a3dc52565175b756a59ce38cbb4ab44c2fe986e04c8e62c524d5b2a84190a10711e361239498103e274343ae462a449d5bacb9ded9b6312e53959eab164626961392a244ba6d318509d1fa8cf48fdec073782e777fd3b4c34b9285a30d5243cd25e22c7ef53ed9ccc17b1bb349cce9145378772d6a91a004c9abe394c1f3c05f91d35408918622380b2366c60cacd45ddfd5232931a25ea85c382e272beeaa28fdcce1124e991a8a9ad2b856087c06a4a2b6170d556eb0787f23ba4fec74501c4b502ba13266e5d30141aa903c9ee4cf221c721674e917a2e85271f2d6eff360dd3c6f226505e67ae763332061b149872ec1cdcccabf515e0f40620df36d1fb7aad0ec4a4c4a57a40430c3dd9478cb788197d2fee9524c3c856b164ef265b4a29a59449064606bc062507b10feb4c3a71f40a4d1c151e4c2a1f0f3a0837b649ef8dede6e6041531d915427158cd1f64534c98c7eef570a32d6c6a36b57bb1b5d65a1bdb059a0204a2ca6695555b796e16a420b93363a0dfa754e2bf210eab4df13784c3bde10ad55b8e0cf4be8703c6c66cc958b1dca733d87dc57b45f185c30ae34fe8cee9f930739bf24c21d2135796251431f9b93b48437d52f79c374f6e9ef8cb7c4face1abdaa13d2275afc12789a078f3c45f2c7863bb79025ed8ddb93127e8e82569525e9276c3b979629b314e6e6c374f6e6e50c4484ee89b9b83f67665760bdb3d11b2fd5b4631c4909d03b67bc05e2ae59cdfda343185200ddeceb654ba9936359b2604b069d23de96bf8852f5bdb9fd5ae4f5ffa4c654c5f125fb9f45ee9f35f7086d12e89364dfca5f4f83d9bda8c69e235725b10c72d4eb442a4d0e6eed30f9cff852fff79f9cfce4e5e36369b265da8337d9b9a0d137f9b9a4d131b5bce39601b87f92329d136361b9b8d8d34ef143457a726d02a69e6aef65a3b441554249941fe5a7fe4d7d28cf2e86be84ddbd2c1becff7bc08ed9ff3662106a421dcf975ce1dc6188b5dee3c5fbcb1287ddb19ca9c5ff373ce13c86e33c616e79c47a351deeef7bf9bbb2ee77cffbbb9eb72c6e2ed9af0a4c7fce7cf9edfd1f66f2a5dd274efddcfd07319dbb3806f8cdfcb395f1a425bb2a52ffd0b857d61ec5bfa57695ffd58dea732465f29e9fd668bee2bfafb7ec1d8ef232001492369e8bf50d875b5ca597ca12c4a9ca1b5518f1259982df04ff64fe04b1003d9dbf4a79b244d07bb5f92965f7583a2ac79629fee13f097fff41f82fc09cc191356ec9292ee9c52ce164e77df8959681f42df481abadd6707666a082dbdc6b79d547cd58d01df2f1f435f154066ad55adb5d65a6b95326ad08f159d09fa71db27e8c049cb87701c562be7f13ccf1d89bd56d35aedc5386724ecb51d12a3d1c8f3be0f0449204eb5d7babba6afa990d8ea5e16249afe0da5f65a4a29a594526aada534a552ad4c8fb62730451fd7412edca9eebea8406bdb5bf1a407f8f9768233c878b18cec6ad51265cc1f43bb560f7841409c3663f0929a3371db9421bd1455f0423b4d6fcff667050f2063b6b2f8cd5686911b06870c3937e0a0811d199171ee492729dc41e23d907721995e37f2d14ff125eefbe00164bc529ed8d248d0afd73ae8903bbf0edf55ca97d7f57e8233f4d8dee8bd1946a28c0768190404b6a7f497385dab05e942b198039d7452b7a4fa37b40123847e39e4cdee080f951d6e19058fcd367172e64aa328f08f7e7ec95acff33cafd6b79fbfabd95e91ce282314d01a54ca3890e8170e9cc6691c9d4ebfd0f38d7fe6d1cfd7ae250e1ba3e9bfb37de9c34a980f64387706451c29d02f1c9b7603a5f9c5b97309045f4a508720057140daeb2c5ba6962df3ede398b1018a36ff07869ee7dffd1e457c6c307cfd069f14bec07f0ff4b1bdd002768fc4ee7ebe7f436f28da25148846db74280905a2d1aad7ea44a5d49f662b7df992224b3efd2ab2c4e945fe97b5d6e69d65c62f294548480b9c0d5a08d9233c295ba40ba594b664d4fa96da2afe0da50cc6d407d5dd494574fdaf3a867d15ead4dd9dba5377a7eed49dbabb537777777777a7eeee6edddda9bb537747622db5d4dda9bb5b5bab5377eaeeb53a75a7eed47d8647f3685e109ea695bacff95127db9fd6a755b47d5fb5d65ee9d7da7b419cfa69a11c4e8a10c5d11b4eb1acb474c9204ed6ab294339a129395c073ed1d50995b2fdeb834fb4973ff0031d631ce2807f720eff039f944a208e0c2f813a58b436994e1fc57f73469d50268d42a15028928bf2f14f4805a9882e41de76ad3565ed2af52c2b9655ca06ccf7b1ac52ab942e955629ad4fa7130a854aa5542ad5caca0a8c6f4e4aabb529cdb24ab1ac5238effb58562996554aeb554a5b6befbd18ebaeeb46a39197493eabd49e13937c74d7a5483efaf56d161f4d53249fdc693dfa3e2426cb11fa5549a5956dae52ba5a8b7f94c03fd57e4ff4fc39a7e7691004492452e94be9191b9e945ffe3e1008295fa93d2d9d734e6b75a57482a439e79c93da6aad48add053d4da643a7da739a718830f3c39ffc6962d6c39f29c5a4a9fb6509732f9a594b34565b66ca19f4d5594b41b87c57c67b628fd97c328b5b3a2808bd237a45ff26673ce39fb9c73d6987c991887231b687f8fcd568dcdd68c09b181caccccccdc666555ccaa305e6766acb5d606f73d712736e31a892e5b7680bc6b07882ebdf002617875bbbe74bd3401ea7b68b7579fd196eb4994d8ea4b30157a68c6b6ac5e8337a52d2dd392866efca9159638bf698966d9ff56ac1a46de27a0fb4fecd1892cdb83fe4864596b57b4053f0db5dcbeeb53564e0e7d569e8174c1ef3299ab1e3d0d59f95956cca93f62d1ef7ef40aa83f1259f91540bf135959d4b3857fe5c281965beba139ed105749907defed8095a9d47372dbf795604bb4af1da22d17265dee9b5aa05f9eed65879ccc98d7df66cccb4ef1a4cc989767c5beef59216986a80ccb9f5a222ac372915503fefa39f8ebfb57910712a7228f0ebf0fd7b5efd9cfefe7f8f3306f2f7f1ee61eb6d77d3eca242399b6b87491a0681d725bb1b26aa88f9fe5f8e9e7fcf473be30ef163ae8d966eb3e2907dacbd97ee324d2e562cfe6dd3c27b3e579b6ede5f6b55366ccfc37fdf8bc38efbecd3498e457f135b714f7047976788db60cd9f65945b06aa0df7d02e68ffecee7417fe674cf638774b17f43bb574ec21fb47fcdf144bf21ddd95a29a48b7d6badb55f679ece3b56eec4c31ff4fcdaf8b344169302cb300f1e325bf74b4ddc5bc3b32430be9f4f46b3f17fb0aff6d13ea06ffc66d80a1d7cdf1f4933770f367ecf041de09e49817e491b96326cc02eab7d8c6d088128fa7ef7b4db5efdbc3bb14b7b2fe9065aee8c3794fb0343049e94112369eeafdc2dedee2ad52eb8d744b12dcdb65784912ef64d34edd65a1f7bf4d4e7cf7d435c2bd5319f479db45adb39e946d6d6f744fa9ee83f797b10a48bbdd6ae589ecad0b796c79332bcbaf574fc2f704b988d43d982ff6569752a323c5b6aa24d9b8aa42982a4b18fbd19e62d694b856dfca526dab70e757ba32f875f3e067580dbc5972ef84d28d0fe721c9519835f2cbbca8c794d2036108ea332ac89595364d550a79843e9f3187df5bc30dfb761b6dbbbdff4b2c439938c48da826788b7174a17e90aad43ee2b5691f55464e56775624e7d8f95435f82ac2cd650df7b05d4f74405d01f892d120ef47c29d205bfe340bae08c331eda3b9565c6cc29bb2af0a48c97dbf665bd3f8b5543f7f473baa7620f242ee6d0f7be47fd2ce6d0f744ec62d9bf2e77bd7c6c1910605d9ab65c973bf150889e3fbadfddc7f5a9757912e982c3aaa17efe9cfb392d77ba5c8974b99856f3252526362a93c5c8d819a2645aff481a998da50c74e3b7314365ea6389bb6d4cc2425398d97aa2effbf64661dd1efd466137d2e163774f77065147e8b9e98855837def6da8fffd0507e09b5583f7dfe778ff893c9074cfc3be2776628ef73c2afe51e81a902ef886b6e091935128445317c2efe19fa20701b764cc568989f67f7d0efe85ee618f7ad833b63dfbe5306fef7e39cc3d6c0f67529e2dfc78db37c96e919d10dc99038a000689726d3018ec032c2c42cc60348f52c56dae20045acc280e4060f3209e1800f9122a43a8dd8f86730a14148dae19fa8f0eb83cec022e7e321023b600f21f468040e4f246322638cf0645f0e1e76ba2848d07d70e2c31009d10f1a146fa68c8433a01810f7ce0da6185082e396178ada690692b88c801cc8404218ca72331d41022a0499b103a50e9303962a5033a42e0a68e0c6a9073cd50e6845cde0a0810a8a8b98e1647e4a87079267c34944d9e20c1906b8633432e8ff5d1907ed182235272ae19fa4c0a99cb63f9688869100229bae071cd7026b922e6f24e88a2a6829f181f0d251448980cb966c812c5e5a110e30416149ef4f2b179a0f281996b863342ae1da6db225e10fc3fc41d7ef890b2851df76f26ae18638c31c618e32b4ad902c6b5d65a6badb7d65a6badf5628c31c6185b8c31c618e38a31c61863fc3b608a31c618635c6b4865a6cf8fc7bb2f0a69f92a78a1a5cdf4f2e5ed00e2aed39f7076a8441dd5f3f0e152bd57bf820468f1ed39a82307ff62e348b04a17d2b3eeb3c0a7af59257186d62e7dff8932cc96df1c3040b7910da87e874ad4e1b1c3174fe4608b2e8abcf8c2a5fa1f3e5c2af147ea4fa29843f7f773f02fe4cfc66268c4f4a9700777e9c82f5cb1125c263188ea7fecf0c3c74ac7bf7021b1a2ea67a00a7fece0f2c40e4e10c5163438e2e63abd0f2358f08004258c48a2033570a9542a242a31c60bf47c2abe7e0781e1fac123080cd78f1d54a7d3e9743a9d4ea9d3e9743a9d4e0f64c675127f7040064e10d184164118c18f2bf5a8ffb1c38f0ce4a038c2c508a63081115ca7df4125fe30fdcece4e6cc6a57a1d9a04982bd6c2556030576ce552bd4e89080683b962d9a5fa9d1d1d1486b24bf53a39702b97ea7774e41734180c48cba57a20332e95f8e3f428215a488133020a1e2c71cd4f85a850cb1627c2091bfca0c50ba00471cd3f853e8aec34d9398249151dd85ca559b2424a0f387723ef0349256d3aa152aa1518ab124c8801c5ed93c8a0b841714b22dbf353315078149ac8a1d0825913b926722d987dc804518c9f1f268898f8f909c5cfda4e4822477512b2c97611c49ed040297511a42083e5436843682c2c2c270060860930d80fb59ad39c46fba1a6820a204e7d154a9079105886d9332d1f7d92f82c31ca3c086ccf5f01c00c8fdd56ae23f3d8cd63b79a8e6ccf5f99316346ab05861735ffd1a38f927a51dbf3572e2eb7e53319b25212e23b5b3e0f0b6860c67c4b88b0e5e7206b7a2cf197f93a376d98342bb6fc03c89a1e340ae4ec3080a4e97acc9224e9012342a40ba0109cc1b7fb87208ef6525061060bb2820498bb244ae9a25f96be7ee94fa1f30000c4d1250080465cf6794b3e6fc99e4f49e088f679ddb5b324497ac08810e90268cf5f695941b5b918c2828ba115523306dbd1141a204e7dcfe5030930b767976ed4658ba78c345840f100c7035c4b0b88736bb8c7546a5fdedc65c1d3ddd96cf98cee53e48a2c7868d0a0c1c28b84f199f910d9388eb35994a1ec444b916f8029e33db601d87dadacc73646b1d5785980be620151a2448972056ecf37e105a664e3b1f1c0b4e058482a6c2db85080115c822bc048c5585b82932fb8cf13bf2a41ca96f9f2dd0709292300a0a03ddda7091f16f828b1a7cd900080a88cf7d868cbfcacf56a0990f7d80cb900e2d87701d43137fe17becb73675b32914c340e1f32d296178d2dbf45d6f498f9cbfc21b21eb06df1df189decf9a7d06144463470ed381bc71916dd67069ac067f42932444665dce7480f98d7b84f09e4cbfc9b44bbcf127bfe10990f12d265bef402e633bb31dab061038698d10bb808e06ec0bd80830106186264ba1a3c3b64a8c1b3e7b3c4c4c4c8cce4966dc60403536bc9c8c8ccdcc029c44c2bf7195388edf92c3337ee09b4136837682c0c2218443435f5f473faa9c1f15981c597dbf36512311f1c9f9434d363b2c75cc7b3f9261b1b1027bf0d9dd6da9aa7fc6c219f11cf62f86288c12687e797a797ffb244ee23dfca1a1f32fa8bfd17def2760a4d5dd7bdefaee7e63d4e7c6654c67ba4cc96397a8df7d040becc9fd223fa481aefb181f89afbe543c6ed3d36137a6c5f24c6087a6cbe0f9f24a54bad5194e60d5de4acb5d679556ddb2be1ad237bba4387d040880b018397c75c886c88120411c4103f89e00288399b2390d3e612db93ccda64c203dba5af336666979e04e294625cd8a407411c526d832f4f30f2aed81e1141828658224614332742cc20ff509105cc95331602051be360e3bf52468522444c81b96e9d31364382d832934a50141bac89934d7bc86d1a04139b6621650cceed6adb5e12204f8f0a90b84684a08a953d9f8a46504a53031fecf9757e7e624f101c614fd1c8cf0d226e846acf13649d572b1d8bd7488e9279a6ca3f3c39c1523fe4a0643a020cf583103eed45ec347f4042374b35cbdc45ae8b2193d6e9483a8c40d34008016b2521c0ef0837d211d9a3420426311ae9d50090e0bebc84d71581e6d127503417d5668bb4cd165c345ad58084bdc075b8d1d54154cb3fb056e6238c98cae82dae8e221401ebf0e22708882adf0094188a753aaac482642018e948c2cf4a043ab5b25b061531db2ffd31a2ac14ada6803165058313ac1499c108a26a3265250815922a2b242a2a60f430e181d4154ec86045840ae849aac98a0fab2a602871e2c80a891e60789825589282980c4aa0bd2d836257f8c4702bb3d816aa206db70c8a15e1033124a470a0553e44a14bb3952cb44e010c1d3c91844e6d191484062b5104d9c1ca1441ba5879407f3f564ce80c025509f4a90895123d5262e502fd31b132a22b142924e82b058c1e685215328d82b292029da700d1954a2a06ba6e19e401a20f68d296411ea8a222823679c089d65b06f960040c241adc32c807255696e86fcb201f806efa6e19e4c3920014c1236b1ec413038709121282209ea02cc49c92b65826734ea7cd989350480cb6c46c54722f6dd22de6e74f40f754cc3dba9fd27be49fdf85f37b744fdf5d72c6d42532bf0f17cbdf3d8b3ca6bfc3baa722cb95c4dcfa92d9b2df637efe2eec81a4fb09fae7d0ef5cdcb1f3837ef7637e16336d7191b0d0f52da5756845266968b67d0f672d91153af8be3249e33b06dbbe490afd9234d6fd910c7faf98d3fd48e4419de6454d07cc12dabf2ce962bf04f9629f820300371657d2c5c2c0d4991bd2dded8debb42e69bd241ce2bd63a752195acd0bdab5b6d48496ad0c5d9bcd29efb2b5f6756a65adad7f2dfeef7db82c161fbf27f24082c5ee3d918579e41f892cecc3651fe32e5cd1167b458d85cec9df893c90e0e7513f273f0ffa59645dfbeebad65a3d544de02951a224773054a61df56badb5161b653f4ba4b0b5d65a6b78668415dac61963ead15438ef87cad4f76e9ecdabbd3c208b933474e33749a171cef91d9695c4b2943125306bb337ebc4e26c5bcb9c032d2d6e85767f54af89aea5267a7b965503fefc2cfbdddf67e5745764d1bfcff2ff3e077fbeff893df2df2bee48e9527b0496c97ef94c46ebc536dffc04d4479c713e39f711cae2e6e202e2dc77c9355a70a1528bc3d96cb95c0e87bb384b91cd64f8da6cb3235264302d2c403f5940942851a23c6169c18592ed66bbb9f04215325985ec051b05c88152cc0a90a32227a598d980e10b1c0e87d3819411009109746a11258205428e1f222ae3b34c892a11d14c265de6e7f8b16103c4b96f23c3203363d25ea53697cbe170369b8d46bb38ff8ddc903ddfa4270ff8e670b95cf6c9f908d9a6d0b2c0556c978a4d07341f9c74997f23171313233333b23173189106886ccc646464666e74356e11c0dd50e3363303e2d09fa9376872a5d6a433c6e56e8bcca479c82db23d3fc68d1b200efd1b95a606576a4d3a635ceea65033691e52a8edf931686a62c062c06a70ac8056403862380de1866a46e062b099c9c07146c458330267e33e94c3e5723893be59ad302e0788433f47bdd9955a93ce1897f39739794c7ac6335bb3213e382ae3332239aff11908e4cbfc23ee83db3e2bb2a74f6e16c4f5994c2c426484ec86d68022de4702c7a2b1682c92928ab5c86b917b912aa54ae98c63d138168d45aa942aa53316a1542990e47da793468952ba9c4e265195d262d521812470f43e12388e63917bd197475d877158711d9411945ce45e54a140291ac7b1682c722f722f1a8bc622554a95d219c722554a95d2198b5429554a672c52a554299db1489552a574c622f722f722f722f7a2aeaa52d9aa5236d3d0b9579bc75aa44aa952e3cd9fa60c3d345b6dadd4567c33f879b63e911558797864a0304456653254885185d4d8a432a63c94933447733b1506009fa1a3865408879bd1625129f4f6ffe10a344c98d1808080f20cdf99135b155b4bcb0a344ac8f9cfcf0a3456e34883060d165c6050598cc672456655e88c057a3259989090f7e572391c8e46a3815266b32452ea2c890ade8cafa5ea5338e70aa091393f4fe8f384f67c5a2281b8cf9b239a1429b324b35912962a2e355c085705576384ef0b8843bb513869206d98e5f602836252abb98038f86b585a2fceff02e93227108f9db1dcc0f2ccaa6889dc60098df6f2f2f2820dd3c5345a2e97cb76f663812ccf123f3f2ebca08fccf1051ba522b339b301c324c253850786982132598c4cb533cb63792c4f2c1603e2dc8fc9323768e8c9048359d8c5b45c8e5673b3a15c0e5f5a2e628bb04b687b24379bb09999991b34235bad5613c2e16edcb84153d3fd8ce3b88446a3a1a1a9c1915d668ac52cacc8cc673535353862c01430e522b0033a0c470c170237e062b0b10cb0e1662373d41c376e80d992a176336530a6960bc4b1efaaad3b3281f6bf994becf956dc1bc4b1bfdf1500879991b1a5b72dccd69cbfcc3f994646eccc12b1302a638be4bcc616618fd8d89e46581e2bba5210693a8ac018635befc8033f8cf1d883f1d883f1d89342a5503d3d3db6de140a577b53281d5b6dbd23b675ecc1f8da3ae26bebd883f1d883710a756d4da174f0b575ec49a14ef803475eb5d7642a9db47f0a5512ab0ef881df38f2c06fb4d5d63b625bc79e1e0f7736636befbd9efb8c3643c11d949e1e8babbdf75a28b842e9b1f5e26aef68abad77c4b68e3d188f3d3d3d188f3d188f3db6de140a577b53281d5b6dbd23b675ecc13885bab6a6503af8da3af6609c425d5b53281d7c6d1d7b304ea1aead29940ebeb68e3d18a750d7d6144a075f5bc79e9e9e9e9e8c6b0a954255ac932b1e7b304ea1aead2914beb68e3da67903139e94f1b25dee729749de08fc4a5dfe97426f37110527369b98420c26b456d3525360898c194a9b26830524c0f4242210055201002cba04fda13f3162c440a1d56202e5a1322a3b328e33668038f967b45a4ac836b6c6b1e44685c462ad9615dda1300a6b69695981068c999bb8899bb82a4444da65f21d36652aadc8967f0159538bf8cbe4610149537a06cc6cb0e5d7206b2a0c06a32d36cc981d9266440487ab4284e268cbac92827f0a208e4985192d355a5a9433c6f4fa51e1acb5401c9358674b8b19343269e05704fc8aecf9b444f2ea0c14c7d99e231146040623427155288ee2aa48b9d1604195048885d4ec308be7e2e24e47a3d1285b3c97f8f971a9813a328ebb6321576a5966cce865c579bdb15c516f3829b3c86cce5c5c5c6ab4e0826906f6926f43443be624c203d3829e268053d6820b2521b1d8ce84bd6003ac4244f4e1b4bed55babdea80c0d5d8b6566bc1df246ab34fb7a192b0cff4871b793cd762dec6915b6e73b1ebda642a954ea3865a4b829b95c4c4c8ccccc08e8e7e78749ad2623233373a3cb9df39c6432d991719c9999b9419373a781210a78ecc68d1b3435387736ecd41a0e11c8d1c89a5b83c30a60b66e70c45071ccccc4206de6c3d8e4dab83ae5b8b90171babfd11d155badb1d0caf1e50071f2e7b8d9a8972c95465b66ee644d1dfd653e2a3c8d46a38aab526854a60ad5d16b2a947a9b52697b46a963bdd5bfd153862ca29072524a3f8c039ba1a8948b9c0a6a07d1cc000000010583170000200c0a8704922cc9a12c0fa30e1400135f844052542e98484491408e242908a228886290310618608821ca28437507003ac018148cc994684d29eb437d9ae3556be208591e0901bab2466e8e318b69b5ecab0c9be83ad298f53c6b50268e67b1a2caaf540198aacea97a07e4c03e4a79d682e1bbb804107ec627bedf324a4df881b50cd73dcb814b8a8a145399266169ee183c3a1425b402844b30a61f20d10b8d0c903f8c0a8300592a13dc22bc8d11d74e50a31849702a653cc5c1a99b16e1a33ab596c323d7c011b70270f60a7781283851b6fd507ee34bc0daa8d6f6894abd165a66810a358b8d932607bd43dc2715334d0e4865023782a67fc0a9d4b50eec6aedd35d4cc503e42f8f510652c63c1f1f4135ae5c10beee376992f21ae2011fc9581a71c931276ec6750d6c71d22af953e554a61e161d1a3ae0ce5026fe83e04d93d701f02224e4529953be733f44f6d7d14efe10305936e843235b297938ef2ba070df20c2f396a1756138433f10cc04f623495e613a946725b3c24f2050be844f0b0390ac10400049440b96b88066ff4372d378a6f6d8fb1ccef240a081e4edb7ea17a15286c37144ed90eb19c2f6b9e6efe11a262c4d7809285dbbb475eef37a2aec7dc4a56c7c0009b20240cf3c48d0d74415a5cd3559cc72430dedfb42fbd3626209480383a22dd0a210a7ab92cda27d5a22f27c91a97e7f755015195adf71df4e4f104b73ff4be562ee9147881dab8b7d0f2c0987ec38b8c2cdc4c510342c5c61a90c893c79b6529dd496a958e80888b452e6c763916e28ad51422b399b352f108e871ee115a9cbdc70989ad50b2af3f07008263b63c09f04c2169c18b152845e80fcb548dc448b5bafb179b9f61fa5a2183881d408c6696c768dbcaeebf9441a66029fb945d12d74b895c55efe7c84a0ac9ad526e524e144a1e837aadae065f1fa5864c18c72dd16cc1064e470664a460239eb54c0ca5ffcb449920891c5eae77440921149e1376985f7782e419aa903feb1bc1b435ed813c7fd7b45b8231da6af30fb7b81e77e2a97c1f2c9bf113e515301ca174d1a72880891f6b7aa94777e82b58fccfe2daaf94ef29dd5c35e41ae40edc01980c5056f90f1e98531bd0fdc21b77b61995a0217bc413eaec5685830652bd308004f4e27f299750af0322ef101ff1a09d1dc2d851857e1065c81cbc3e1f50b0e0df6f85a9a230b7d0c7b86500e7e99928c3b06e52f819fcd04c46495d691687621a508c0dcc6be4ea608d2ea1b0757617ab2d31474a2189caf14129c5ee92c2fe1ca75ef05f54365417da70fbb25b300b25b187e39bc4d3ea0130aa78a541efc212c26703b99ed05656819b8e02d32530bead032b0e04d72530aea9d005a41cf9076f055183c4da087f4f85ec8b09e4830bd91ca9e22f688caab1fbf69b6e4c0a6e00e8d83b9f481e94e3b208378019ce8ab39d20b8e5e3084e473b797c3ab7882d7050702b0ee3378000fcddcf91bf6f426a901f4eec173a05ca704fc4abfeba18adf009daaa6e475b25b3d95f6fc75d5221f1e08a8337c670646d2a8dd88d0d14a29c001a2b79d6523c1bab16db09df4192e4ea9280a6aa24e50641ad469322c185713ce1d651338f174dbdaff1c3ecdbabacdfd4cd74c5f3cb95c6f7a23b4703fddddc033a150fc42ced62abdc4a2153826d4b99e7343f8e03673a853a44fb182820369f8680213e2e68e74ee8a53d023962633026d74412fd6e312ac23111c15b552630883ee9da15b8b619db118ad3d9cd83501c592c9af46c524943e897f0313820c466b94b4274ac12df5f7535830256151935242cde93f1aa9437443f3fcd0353596117a899f82a8bef772afe449345c883d5f9f153b9e497081ff347ff6d71e98870b89552e29e353ee7a19d5a7d7481278d17d1ee9931666282a28e4a78cf1a5c430c58112736c80e7a47b858a7d8fc0de05b8833350a2777cb569fd33b50585e63304e78c3c97b7d0f922e5aa663f6cbd649c45bf84e15b18707a4929934b8eb497504f3f14252e44e076454b1ce8bce6e0dd0a29b4173154f3e88e0bf8a7e81db1c4c0488b40d7db8d89a1e2d1bacc20486887bd90f5062c988b5b4b5b840340af78546c4b55f2a4e5fc765240950b75c7baf07f0bbc4a90368a568e5fc7b192366a46952b7ac8d1cd2b0a176a9640d781fa1a7fa5d2b53beafbacffe91062f3524a0966766d695ace2a1076db5364577f08008a6fde244e2b65696780a746b7715475943cc5c0762b43fb14007be29c53e48e0a8ee27766b56e08e5ad55c9209a6233fa77149201ff68169453f59b712df580f12320f4afdfc6aa50ecfc96f581c2ed40af14d179e89cde7d5484dcc947b4da133185f08afebfd169d618a79dad157347ad77ae8de008236aad350dbfad9574e714e3a41ab86f6e903a774d96391f4f3d822b67d943373c2025bca32e8cf2c1f91e8ca45c7947fdc6a5508cd5e2909e8eda7a530b3453e36614ba6382251fcd0a8c3f60de461fae8b28c241a9cb2ab9090d7d908554d1048cfadc1d15a1045e25eea239b4691982214492d2306432bbbf3aec6c50ca5c9b04aa8a552b39b6cd37ffb42c6ea4f57c7a72962aac5ec9909af36771bd9903c324fcad764645a93f4db108ebeb9df76ce20565f3522680f9286fc470a1304c9850a681226de18ba28a9203e4fcc598b89bcb402f0c946c90a9d32aeb8061d663ef7f43113c2e0b9287902b49a22947099c7fb25bb6dd8ac2fca3d80b221157af49db80010b61b81c3a71a07c7b89e70b178fcfbed46b9e7fcdc60142e93044633aeab20eafff3cd5cdcc99185dd863fefef2de1117938c30e3f1ba31b46a8a7634c7913ebe8902a5a3993f3ecfdc1a4bf7100669683c4961b0cdf3b6bdbbbc25da28e18b3e2dd0ebe8c8ea677bced697410f8859b795dc4502250188547b5f80a7520f52a21a8d4a89aeff5d9ec3001a43112e8b1122bfbf0381e77e0c335b9799c958b6b291ee54b9a9b0c3888286ff0e62557375b3397e565595e6344b0f9d142a5b144e1efa9467e36ed1ba2b7d0a0ee8dd17963ca58cdcc686254fb963d598b0f429cbf83beab8b0e9a422642e69e0a7e7f535439412330d814449f3fc3ce428e019caba11bbf6c424b83210505a4e171000e88f302069ce76421722451fbe252059095f5b3023d598b0e42bc9383b4f7f94b76f53c2f903fbd17b8f6bff499ae961af95938b47684c6bf82aea53aa1d1cc2814b566a15d1962a1eba0a848344d4cd26642d97a6a5383fbbe782d07afba207201f15435d7137b888e204df8d291195758a15e07a22c5ea7773a982a73e7c9bfb06203278008bde3ae11512175794d08e7c1050eb24c35b06c4ba94c2bbf0aedda18ef801ebc77f70b1a7cee125db39eecb94f8ee368a205868dfb8f5f03104149589f35f7441737e7c105a268b0900944738b069293780fb2beff8403f4ba97ae0c76b10766abd9ff91dc2e49ca09433967d23f65a213228886b406b992c2000903fc600a43563560d99c4312561ec5bbf7af358fb6fa43d6b5681488c730acaf8ad5fbdfb5cfb2fd29a71f60295c43105ddb84b2d9291e0350b628cbd530ffdd3d71494d1ae6a91acc4177f91befc96dc12ad28a17d52c7adeaa9fcecde094d5ddad4530510dfcb75d02ac6b277eaa13f7d9b826a63565341abf8dde36b16a4c6549b5aeaae6496827a7c5374dab67d92044933bcaf10c4bb4f34f98ccc26d516e4aace228467e19acd222d7003ec23745c52df9062174e8bb7e487c2a47356890fe5d1746cd89adab16911250235e6ac047bfd4ec490a041a6c48ee2f2cfefd81c701a83f53db9cf6632d92899cf847827f403ce785a260b358c2adf553d3b0dfa7b48ee1bf2778be27a9d6c0c0f7c9f5300d6ca743281d943b8c1ac9fec3bdb76d60ba36d5379533d47a3b13b61736446896ed5b7fc14f4fe199e1a275ec6d1ddccbe5b831968861cc44dc089cb54a83b63518025496a0f81ef5c56e93bdd9c19ae3fec9e19c508ce1b14f7cd681c2b74e89c65df082947e0d07680dd053d9999b2c7f5b76be64304dabfa66f4fc448d6c6ac101c6d275cebcdd67d69ceef9f208cc7e4f43e4b9d9b7e7c3929b9631ae42879bfb142b5c2b0289a794b33fb951a47f881c0a3d7f5fe3e5f0b520da982cbed5e8452d04954edd6622c24961b245e1707686a70c4d064739ee882eb590046509a32330e5aab0726fad37854c26536fc51537629422c9f0a4853ce0bc934a49160504dac9a93eab6c4413b31cb79594e238ba9053b57cec845b4c208708af5f1632d3311dcabd25af040e278218f50e6f7ff3f92f9b31e44c5ac384e5a67faa8540ff0661246c5486a996ed15374010552759c402dedd348a3c343a21b20957718ea1500dec7db06e02c934ab7aa9c0256907f115178d109a8e7ce9d24ce8a45cebca858fc59b55d8ff2aee9248c126b8852174a61d04e2e564e15479e7cef3cfdb818a962a416c506c8af80e5078ee2b73cf1cb642212ad60b5604d3955323aa860e82ec396e8b6115dc1e1099e037550220719a7a7039795562e66034734e1783140b23a87d4ac02f8be2b0fa31b7d7e3325a488a4eb4bd911001de8afc78a9fc16769d0c1ff0b6b6b0cb0e34fb999d47f10d119f80845a7258e3dfe844b9669d01fcefe983cdabf2165f0de68c9e09babefa62c1b3983e8ca3190657872aff3819a8de2c9b0a497b81015939003479da3b0cb28253fd52a5e5745f622cad0dc8444fdd05dc8d5edf4c497dc8c33cfed3a7286c1584daf2f817fe709ac41e1bbd5c14c3a1d83ce264c320b65e9843de0afc6eef45990be415387f34a596115f1cfc1b560efda10fb75dfb55c70a28d35839da907cf869113bf4e6352c42282fe444bc7c274423e19fbb0b1c9223f6018f4ee873509c44bb67bb4575f20027036a8357e5b225183ca1b39de67eeb80da8c6d308624eb86bcfc30df139065ba841a40d22164d0f7c76de1053165f66568e0b097a78d08c73944b84fa025a5b03a0beb3868b7ab886cf9692396fc9306ca4e726403c0ff3075be889c4fbea4da0e0d8cfb6d2e4af6adc2085f41f9aaebccdd87fb1f2d0ec7e852d503719bf320838156e108c589dd81f5cd0ee88a2704c9b54dc00d9bdb926dd777b66e2689d36726b05321e81e2f5a2494793f32eb7d71b5aa8a18cdd52d52e42881c658664ee201b5a41c3d2b2ebcffee6956df47ce461dc3270a209a66aa22b28fa05f23e0a1c4e8742082f90904028f7c181241ff151ffd523192b0bbbb2d2453fe47bceee28986fd473e7e5cad994e10d8ee7746ebc3ab1a8b03a5afbcb1f184cd7dfc6536b722f7e48c673ffe5bb8e71704bb980418ba2c3fc7445d502871d831a232338d6088f3c31b736154bedadc6eb767db4b1ca0ce68092d91d3b54f9f3647ac365d41da90c2eddfc118e647f90d37afb15c681f37a7c10585f120746ec4e936ace62e9bcec7239b836a926e9d72dc7ecc0b0364143dc8c472998e50aa728f131f29caa8a6831a629de1ba72c24598694631e39f3eaad1f3e438ee83c486cb5b6341ed7983f04a0edc8067d62d58a9b39a82d59caed80d5bc72223c9d23d332496c536084229f0b027dec90ddd2db2ea4e69441f0d721b97ca62cf32c22c1095af91709ad769489f891ae020c498bd62f16fd7d239fa168be6e88977accd87c0d784454323fcd45a64f9e3cab01bd2af35ba9d744fda622111a05882d02b219181bf9098b4064d9e3ae5f4dfa44917839be83b7f01241b1b6155632eac7b9c3870fdb65f5e2f114a4465793aafcf995a88037335b3923f03869cc4b31714c72f38ed82be2a9fe00112f6425494e6762e69b24f76d1e81637ff1e7249a53aa1058be547a5e4d813cb4b42d9665c6c30dae84456e5652162e0415779ac8b79d596729f31d67a8a8cbae02908a1e4faedadeb9949b53859d076d2ebaefd0f1f6c9f9b7392fab0a6eb9c1dcf919f6023a057235d4333b40bbdc75ce6d054aad054f1d80da551e045ff7f6562b8e465e2bc3de98c8fe2012684761fb109dbb562019cfd39ce8c5507532ed1b239bb0668e28488cf5cea23bd45fe56963b7997cee61580b7b1b6110d38aaeafb34897bc08871798cda3b199320fbcfbc5b9169e011fa04538285d2b7216de2017de6559948608374d7ef766ab0fd8767f00a19973c9d81d7e8035784b3c189feb746dcc130efb486e05bc66524e5d9ec06d990b1414f13ea7c86eaa5889a405c017ecb0a0acba812fb65c6a9a09e884abc2ad55308afb25fea78a5bcf0530f0b1d48b2ea48c19d868c15000b9be0ca0b70d91d1403491e3abc99b70e68a80fc9b7483e170d3ede546512ac0f52380587e7fa6eca880f9c8ce17da36e4b0c4aa942bd61e1934fca7490d88c76302461bcd27b40437c2fb9737fc543e7550dfcd4cbdf0e352dde0c5bde5935082afbbe9d7f08ead1a94c3415ba3c5b5b0d188c6347112ee132e8ac43029c75cf74888e571378e811a044dbaefc1111308cd602c59fbf2ed8df0dd4cd994a7a974ba5208c8938cb04cdda80333430cd57aebdc6851efd0a330f05bdd2c1ef398501bf1a5a56395d117f874a129b0cadaa503fab12fa85c9c452e54aa0bb9b37ed9899058634792dae2b6ac97a2964c268d25901ec946758ec9c191dd76a84d32d503a610a9da0838a51ca0dfad4da1ef6aca750772614ba80cb63b3e227592a04cb4958f13e9eecb98308afd205b5fd43ed75d608a892c9e3e9857515d721d4a40a3b563613c606dc8a46237e451004ce9b5031124b5145788c17d34d0ed4448cab0f9ece8766a5e3c1dee841b448411a19fed30ade24ed56ea7b9067acdbb00a5e42a06f90fb5b6230a756f595ada6ab849fecc305d237aa05b484fead1e5e778571460c0317cbadf9298245f8de7dec6dd126803509e05a3dce406b0b30f2135af9c084fe79c3419c6af63230b6c65927190730ba1c0fe5fc48d6c03e018e2e80bef9b716ea3522f37ad169af4c5c32abb75fa84279e9d38c4eedd6ac21a55fb82ddcf0c7dfd8abbc6ea3722b26a1d35a2a4835d7a3da57c6f55540c7ee3044211b3b2cf934957bb57c962b1c2b2658ed5f82ca4a99d2f3b35590120e0b70c218b8be4d4627402b632de0edaabe25bd17329dd21ec1800672a350884055d1fa9d173909d40b4602193c04ec9ee2f9400beb95ee732d1898685a6848909416f2af0532fb913d64dbc8809f013dda95d3d10076405561a2586b985805d709317b747e8115dea94e8eda6af13c230ba602b65f979c7dd76c8ce7993f1c60f9f3ffc6efc00d32f74ddb8f90048aca9c1afa84f07790789657a3fd1b0532f02f8d213466df9db959cfd4df04455e6ac27e49d84f3c56340cbdcdefca53e9c5c409e00d2a13becf73d085d05fd79e249d8aa431a45f282133038024450548b9499bf39d88530a83661bb4847f2aa249988a98728e1d334cad17f8542b9e982b222b9fc78410215598849b6078cb4f5248646cd7d73d0a5ccdc052567930b04861941f9f44ab8de5674851267b68fdbe1f0bfb7c12584316ab9416f978a56088a7691df93acfd84503f84babb0847454152ffa1adbb777635338e3128c9e4a511b9a0081111cb13c316a1fda57ca536fae0d2cdd4567d9cb4aed685595bcd9bc9163ceb60fa79a8700dc406cee61940183c9de61b7cd3a3a0d263afaaf928d911be258ff7b445fb04327f1a03c407d9db114c85209ffadd5941a38bd9ba1d40fac5d870a28449b47a4e3c568f31b59b95905c14617943af6f70d137c339e63ab71268d1127a3a63cda8e413e47387190139733ae2b5525b53b82ce35b35cadcaa428f13810d65560732f682b2b67ac410dd67e269c93d7ab363a6c209e0dfb6a8d00ef20fc07ffd7b8d3ce95b5d6973e1be2226da4d015da7611d8e2404f06a745283445cc0248e1d58f89ef413b0ba4ecc27bd582a4eb638a3d9b2acdff5614ffa99c799caa07511b90d2de75abc5bd4e82328303cac5ace9dd3939e374b23f815ec2feccfdaee49dfee72808c4f7aa5b0277aae8cae1e71db3b8a94dfea904774490d7daa89a7c55f20453459d399dca12447a9ef83132463219ea71615d287b9218ee848b414d2b32bfb733a27b4186a1e831ee93782437fe147fa8904e7821fe959488fdb5d19e0933775ac8c8f326f92faefd1574d574a32e4760f1672d95348cf5e9fe80c9ff58af1030c2fa0d1633b99f4e91045bbaca1e0e24a190237ae2f1be448d14ce2141585a02844d639e33a95f72fddab36bde8dc027da7800e384bad2616570fd5c1f95ccf802f761034fd7d363afb82ba2fcf8ce0096f6da1f69999eef700e15eeac5e70fab60437d9858653d10a3107bc718d007b9de1e7cae911273cad38155d25bb42ba2bf795c11a2085962a5b8ebd2b96d42c473d6157b2d6f4b4ae81a071d922a40a8d488f8b94e4403680a53eeada57268286a7b5bd072a1edf55140503240d1a79695b48758cb6ab3c8a8847433065d28b2cf84d897629eec0193e21bef9fd845acac962ae888b5a13609b5d4d0fa72a4da7816b60ad00a699e546927513930046d5bb2bdd9e9a912c0a74e6e18f4b277cc0c6d123d5051f9e509bd842c10aab432d0df2cb5b456f5aa21c566f3610da5a9006d9af2faca92954f15dcf265bfb72b0ddba1830b59550b7702057041551632059ac5c1eb0233e34e542e600e700083218f73d4e88b23549d05f93ca6000d9889e7772b11b3eac8ddf2a2d90ff0c59561dc8ce1013fcb38e166168f38e1659c2d5eb6acf18b935d7c718f2b9cccd9c5e32c5203b1ff4e0134dcd8db729c6cfc8761c81ca483c5a3d82716ab0df9a7c41999fdd4a4dcc011276c208269650d2d72c4f4b7e6463213b00c29bd89e9e093fcfe0fb0f4a71f5a6a5e386b95fd168f1bdc393330b946eca6cff0a6540465074bf83fa37385321881f5512752c7eba10426ff3f69acf4743181332be8ecce97949156c91553221c06f9ffb218bb321cc670e3f01bbe580073317c2d864f99358eefc6cd6201a6e80b83718d8a60a32203184b7dc38c2c2262422d352e5ab8ac82747c65bbc73d26b6007fd28362960112f861d5b4d77fb2a8c40c369eeb3af0d0a235d94b7804ed184eeaf5baac47ffb27240e3b1eb53a73f3a3e69309653519192d508e70b9c315fb057d590a9e844c1f71028bca2ad648ef3e99368a3402bb7eda4dc8e0eba8504a0b749b3a8fea60ce5f0fce2a6351e886e1003e28ca02932e600faf8b823cd31ea161e088083edeb869cf8c2292d5ca038ba473971a5b474093a059084f84fa4ee8e3b397d177abd2427c5f88f59e24b590fa85b634c16c2a104ba580374d909d0be16e28f01fd21eb269b21e38c3d0f8cfeeb3a55921adc97ea26541e8c6634e202542850574953c816bd507083f25ac5a97ca3e3af451646f5211cb49cdce6fafa3d99ad2f31421c4ef806fad127d693945bacf9c44163414414803ba5f007f83b902baf4f8519385b38041adda17c8758946a5fa1db3a766d740309ba51df1dc49f8558f557b9b65d218ce6dd932a65a24f16535e7b818c5d30d1773ab712e1cc445c226f0fdd59e190c721e37cc0005b8ec502d50a6017d6a95eeaaf8bfe474a778f0dc4ccd81fdff11d753e89d7762cf31c1ebc031923c7a9d0b2bbc4ea5a892f5f310c15795769cab158978080e8df63568e2eef1e12a110a082c2f70de58045ef7a865d111c73fd9ce60230af845ab648ae8c51a45d0121ec61a6bcccb78b98f7fb92472f7af5e74551cd16e88b2ab3cce8c3cf33eaac41419011d0fc103b859d93f44640f2702801b0b68e1c61cba48535eafbac42be6aca8a2146269b3f8629bcd25554037d9c6e562afffcd1588a88141b34cba49b01aecc1932abbdd6be123746e7a3a61eadeadecb19a1bdff59aee6c5b1b1b971ea9795d7906f29fb57ccb35929a2aa03ecbf4f5c2588dbe392041c6c7d39110c80f5f0d3824a0689e3a6a885cb12a136f0fd5dfb04c052d1ab710af31cc907380f47fd0e579e96f1d969724a1fa487671421490e15fb220ac11615695858121236259d028b8f51116b3750ffe23856ca71340e2e0d4ce8d5870041b934a0b397d2804b27d2005d8a8790b4e996daf86911c512dc9f789db740248586ff305e6c4198298799a9bd980c451d0b5e971de4cb5f08263834c05be4fb005f7b379c1ec2910968934200f52a0a77c8c5721158a4ae1d8599a5bcfa04bd3daf63f1884504a364bf6b6fd4799ec102730d0ed6898c0737203fc9201e75a02075e4ac45192397dab38bf30ec9c5502d04a9dbd5db646782386960291a0736e0d86e15cb13c447c52054707f672ea7fe4a58067052fa335f12f410a1b9d137effa865f02a405c378fd94869b23cf14eefb4ca205fe99199fe325c1e7540c08d8fc98954e26c476b21cc9df6758bfefc7d730f0aa71227cb0bb4dea5f99e851ffaf26c0c2f76ddf84a807ea499d7f4f981f7f3e9cc8d8f7b1c67463976d8fc1f075f7dde7df14dd0ca63ec79f799d1bf2790d0a0f42738d8531287b80b0959c80265553444e17b2604fedd406e1aa03f402fa9673622d7cf7f47e9c9399b67acae053c0523c83e3475ec7fe6eee9f80a05757b28d83b091914b71ab5450de74f64e79bd6506f21804e2726031d37b4c3be703573c7a0a1fafbb8d9583189c6ce8b7a97031a06fefabde1677b07fcaa4db2714cafacde50976a3a15382e091588b668c974cbcfd242d1db2e671dc0b25eccd9b139a51c280864e492a0d03341199eea23ea7b68c103da08a4c98880a8e9c255393fd34668397fb5e4a4c1ca1a8355002df8132bfe86fc0cd1bb302fcae39493c90b6bc1f629ac59501e53de784abb73f8f7e906b7b4e18e634f4ac174264f56a99698ed9acfc53f0dd37aca5ce7af7ada519fc60a692e6a29d3734667dd620f47a009cc51ecc1bfd5289740647dc503130750a3bd1e4c8a67b6eb0bff15ae8f342864ae1067cede44e462e6eed2a8aaf59d71efd192ac5e39a2d8609c142b4f0f9a92db56e7833f86b33febc8e31f08fa2e7c9d755958c4c33bce6abb4d42dda8f5dff5790a0580d805263f0967e700a8088107b8654969326307e1ae2950e26caea756009892b4bb7821ab5d399a0ce78e60bf9e055e2891afdea2287091d68fcd75da55a3a8e4a7b2bd5a807b02475506ac4fa26c4b8fb8b359bf86efe519fbfa8f1f41c0025632744bf0b39d4e89ee900b3465950f3c33aa047bba5b85b0ff80c53f8486f7b92880e84d141a6bbf7a10e502ef85cfc00b8d078f3ce0334835b456d6036c8f98d7fa1a66736fa50f80d37ddb3be2e2ff1d4e005b0354e66ebdd034260259a0277080cb99f8fd711c6ba2f05f1689992d56d0e241795292582210367a88f46da2392b002b95f782d94afcda52e1ac18b4e69b26961f0f0b1cff9f81ef452490c896782b767f47d000d1c81f868c2f3a45f520656fdf2462842904781aa3109bcf366412846c6dc050c3d05b953fb89a68da16c1e9537eaf78dc1525a5b70db3089ead4c85eb2f73d5db54295e3edc21ab2f7c8d30c66012d017b1492c8faff9371e87cea7452e666a50d260d79be2bd0b68a57d13106f912231c4d633c0027110b866b49ba4c209fe3efb2fe8c43940a02d29985a9880f48c9bad480a9ed3867dbc8ed2d536f65ef0c4aedb1efc0759e2a40890a0d2a8cb8089fdd20105e7abdbc6c86bd8ebfd27b08deae3d4de3c7182c82501219b1e0b115a9b896f05269b60e8d667eaf7aa7e452c41501ec97a32a772e8a8dc16b1c65bd1a958d84036c4ed9f7c10e469580de477af1253a1abeec545b61b49ec910b568cedd1c703907768d5f97c2ea4ca117a91e0e1865671ff5dbac8de4360ea9c9adedf8c9ae10fd21106d0eb2fd7b0911897643b04f36d2aebf90a481a473b5b07444af7b14d23a4e7310fc77ae7dbd5ea0b05629f10d50d00c6147039bd7330598d136410d678cd342fdc4f9f38137a73e43e9f6a16fff63262391735e4145f9290e4ec0c74a83439f66693b2ddea406642c96688b0d1ed569096f0307f57d6c86fa03167c4945efbf64d1a162f3c7cc24e4fe7d3331bc46c3cc43dc7b851e49bd103b0b021a0efe1246e5967e0d26096352d45b68eb189eee4be83f23ae5b360bc2dc96eeb7301ec1ac7f77a6a8971b27dba0f37880507f8bafc4221b2755c72a3df04ca75b84c226f2432cf1b2ceb7f4d0a5b23704e3f2d3df63827855a600b892035576b89dd016c759e52a661d77755eb3d8d2cdaa9f73e24f01846a3e1bb6dfa3691d074ec3f419abf740c6bcfe17c1da10ebf6acc9d0bf8fa4e189b16360ac90e7b58162a46eea78d40a3c7725bf91f908a2afbae649454ca588de1264f2bffde2ef7051686fa2e2d121d76036de81c70fa53119cd7619c517e176c286c7e35e4c9c05d75f43cfe596c29e94a2c582875edea1beef7bd8592e6771678512b080b4d93ccda763358958fda7edb248f95cb78be5fb20446c8392454bcc02ae7d59a696d27983198ec1763acf0b065f0399ae7388b4cecb0fd47155e907a5c2a32c50c446885f514ba2645f2637ac2227ccbdb3f24967af5ac97722c0601414164bedd381c3fd6351d13b3dcee6d65da72fb1ea3095f60dd9f0849f420ecd5858e6a37627d3f363a88f818eab5ad547a7c450953b0b6a7590280492bc61f62eaf4d70d3f0b6162493b0016833b19dda063d04dcf8793c1366fa9fd44e5e4a34331781490cc00a966f5063096bc3415fe4c5bc25a0aff1a1508fc9cad4385e8d17f5140990a9f75181eaac2e5a3c8d6ca2f77099980cc3cfc55099919675a7f24d65bf093e3ca2196188f2ab23e03128100b7d43da7613df5a129fa27f15b0cb89c2416097f705ce76bec7a8cc30678c4a3b69dc1016dc725a5118f0f03e6f83874e5cb5d69267c6f0c210c0d9ff8504c59aec40268a8be9abac1887cd97bd0b64a432f7480717a3cb315e64127e281ec565a5d1dc1062f36c6cee6b2cc436e9fb7e026e38d803fe6f54b64f8eb535ca34f5104d2edf17f99136486e0afa5648eb8bf15aacac978aa5a22e5e512274dd4751033a4707c4de83768b19baa093a84ae935e39c9e7006b2e233d1cb1545d307b8f87287141a0b715884f32cca7d1280be05e389d27ea4193efecd4e1601f8ae1fd4b31bc32cf4c9ab3165fc220b6abd65f48905da8c2a20e94e517e588860c5d270324959ad7f9dc8ccb895b4068ea9240c9f046b7dc99345fd08583307aac0a400a8e33f0fe0a3f840ae2edf7fc64cbe58492db30f5f9db12ee029a25c26d3d758431f7f80f48f5dbf8f454b11e27f43f7b74f6342767705fc5834e3071987b9fe45febbbb18f33a0dbe5377bb069b15f483f9834ef6cf1f2423bce5bedb81ff77751120edfecd42aa94afde7ab2df52f7839428707aac1aae0a65296670d68cce45202177db55766aee80b5f97e1684098d26ce424138a905918246bdc126c365a30c6adc6125d7eb220e296155d02062f97580c702758d149b2d4788bb3524e8c77c97700b54ca83d5a97a91d2c859a0927bd81e87cb205b41f0bf11b88a49106aca12c249aebacf68b6111fc845c4a9f340359afa07b4b34f998e2ffba4d69cd9946784f01e70789cf9b77495dbd24247d407875a0841ece7415476fcea8acbc9129c374889c1bc4311db5435240a4c036883cc12ba811d0d02323dbcbe9de96f464558b73cfe0f22585a1b6c62e942a39fa043b2affd20b337d561888cf20e4ada7bcc4884cb27ae6d21a00b266f2987d2ae44651af403e7635263711aa3278493dcbdce1a39cbeb3ceabf7d3e3c1119670486f704ac7749301a91d14423dc8c0b2b2b011f120a07f2b39684cc5b946f6b98e89f5d2da8e6f64cad249102bc7622875758127f5b88a49afd2cffb0667b14c83f3d565c3c5e7c529ccbd21c62ef7f96c3432bc5dc6a4bfc102a660975a6841dbc73eca1ffea1e6fe6f1d78c5c44e4f5f2f17957e43eaf135d7eb07d8d4544146589448da8f60890d999142257127e56f7e470e6a043883e5e1558b8cca7fbab97ef50fdf5f306f63f43c1d04eaf4fcca1e06a64d962a3d67050380cea83d03c0b6d0742089b77a6a03ec566ceb4aee8c931368ca9aa6a08761c6a42e9249bc599956521ed25e468ccf9c1617a8b65426cc06cd44ae944a829564a75f3f6b97520cde9216801ba353cb6abe5f62b9366861782837043d29c6f3765a116242bf605eb41e74582a7461eb5a834168b069a23401bc9dfb3cc5b067f470a8c0081f80ce573a80c987b059c3d1a59b4a7d93d62b857253a2cc15eca65f80b1daaa7f82339ac6c28d77a76cc9ff584f0c14bbe91b8b95b3ec91e68a76d8c7c7e316ee5c6ac2c72be80c9a0a316b7297697229447ca87361f36428cc26c329a22b531af2eb9ea52515d03bc1c25c24dff348af8d62049a52896be84a6332d332328b53a9aece97454172582392e0cb357af33acc9cdb1d4a7212cb2e0d16bdb17e655e97a89bcb6ee651d6641bd19c40c795578bf3293f8f2c60f5a52f45b73e7e01aaa36207aec33cc15168eb9d9add60fb256057ce01aa09f24636d87ea0ac7901564e1caa2cb9255655a65fff88a168b76c09a2c7f4d132f0aed874d0200ca2ac9553e7f2e2b5b5a9f2243dc419ea02c3b4e0e9576c145d9b045199b578cefda1d2d7bec821cbee6612ad66cf08e7c82f12663c04faa74cd218e9fc439f229a08565793d38f2e9c59428990ac548f688bb2555b356af0edc4e73ff8d59de48d5fbd48577b041086f07f7d99ae9ad70cf1e894e55f40d60ef9e78d5a542488edd4d57f6d33b244580acc01d413f31dc3084042e55e902d0c52158ae95ab7bf39df37fde18d52a1aee900e775271a14d73219e214d2ec6c815d7568ca00df1ddd38126baea95641afa7216f2df759a30a943aa7bcda81fa52819a75fbc095b7c2487acd23e478496d326fb6e6e0c26199e3c063aac34044da2b0da874c3d19c8b576bdd631886f7ec910fb0303fa47390e609b7fecebeaa5b39c6876e66701cae27a8fb25328b1de3de8df74ef6ba228a18ece447194401ba56f67f795e7dde334b66eaab4615e83778fe8a87f398b3d012e0b7aebfecb06424f9fc10e1aa39c1e74d264d0e26d28d97f19984b0b5f328eaaec92b3672644056106898c6956c9ac0f92daaba50396a4cad9e672d010a4c9e4ddb6efd6d6ab6b2e8f06417f95850001e9fa84773092e8ee48c605eff9171ef751e68803f3f77f90f9c741b94be195a0c22f201ecafce120fe3ad0b0b740664681f198aab0e23b2441ba2ee9326d51540eb0a558e01f0775890d2d76d536859afadc80b8b601ceed5d5a37cc7ee644249ce63a0f75e79620a3c09a6289e258bfc498c30c48dc5e5a7a87204c7aed24fb31258ab1627f911a59b04f6a3d5bf0b42d2ddc45ffc8d87a3ab5606e471f173e87cd141f4a85fe660721f619ecbbcb13cac3c58f6408da630b0f576991be047a3361e07541a2121bd09225c9d371e114b57f558778127912dcc28e33b8fdca6342d992df23c4c18451b94ed2a3de6a0198c34c7ec8468c49f1a1b4b19da256df78d7016fc2c95598796bfdf03b5931e2937570a0644467af2e69a3f90dc42c9c6d66817d2adc5c34bd32d64e14f98e32f6099a4e05d95c1d149be09039478af2a756b1700203f17fa31c791114a04b6386bba0d684146d27cb4963106f5e6a6a3c4f19b004fa2f57dd50a0c7071c5fdc9648ab43b81d64f035532c336ce06278581eda510cfdfcdb54f4a7ceac545e9e4480555b0a5f2c0d6e2f9c7ed3883229587175294be39210c6626f534bea25843c3c8891b47215ab3377909e3257b9bf9d1c2ee735f1cd5189b0ebdd16f409328493c3c871e9ef3a04b13eb4bb177ca5b8407b43d77c160e65a25f6f7047737a72576447d3533356217a544678bfd4e6af3926efcf1f78d63a7615c4448887274d053e68dc26470f40e90e42f906ac1a560a945d73233137ef854c06927ea26c1ee824246f21fd17f539e30c49f40f50ab948dde9ffe9cda94b6922ca6a25241c881909ece13ce6f6782bcb5f372e07766dc4cbdf9edbddd19467139e6e4e9988dbfc1040dea516c0ca55b221ef0b83fe9b1968d382de02a21cd532cebb20ae97f998ae595a6e93d3df3d220ec6da63e0bb826009e13729fac6c9b493c27fda4b039d89c8a33c10ebcabba0bbfc58472ec52a6fcb86936f782dca77a5ea49a9a759dd4ef23950b809c817c5305446c05a78f0e5c3c91596657b25d8a66ebc93a52eeaa3c02644acbfc1fba9aa380787455ba11a21622117e00a92dd2815fc8c26e158a51c4c810f0f2553f1acd70c1bce97368f38ab8635278e2ebf936f22da51b0c431b75f311daf48b6cbeac77fedc0b3f8aec430df48fac3b02d16ee594a6824cfce0a7ed811c91089e2fdcef3e0df46f98a88d731b0421d2a1211df06298834587b3fc7b7812b1871286d61b3b7b63f58528356e75588a3c160ed4ea2133dcb1342338a1f6663329f120ee76722ebe9843adf59dd7d2e25a39d58dc94027b8aa59a468756ac2ef6383f7fbd32037456690a6a55c9789a946b853386ba39286e819352516233cdcc733d872a3c818f0e94b0b2b67579d65d487845822eaf496e6dc0cf5702a6780f594b35529eb9c340134726a30d5123719411730d652c36e5ad57352a7137685e6b534fb179a9526e295a2677892958cd9076ba0fafd935221438413bdd8cce66535c66e3b177c64e7b99d7b686b9a9bc56c9021496c284aa08f393fb9459a7352102b847c3b380c685d102dab92ea9abee58a3d4913318bb39f69c6cdaf6f9a480e520f03e9232b6a69b0f52e9a079400b85f8e8ee919168f65137730a890fcda150ce252ff7631335ebe91d6338c24fc22f7def3019700c86c6b06c01dcb899d4fef327761b99fd442b6569fdb346cc7563b700b9fbe1c5f5053a0f5275720a8cda92bf323080110e8db3f7e172d78a4bcf1ec06c41264c9a13ca0f160971791bfb01be84dfa2253c01a6e6ba6f0cac7b053383e483927fd728c55f8fa9f8584deaf72f5ef5535b44620be35b727b62b1a812f3509395c9508818b02db1fc79c297cda9ac9e4c427717496d757f35d1f590e455be4f0fc670f4807dc826ebfb298691539ce503fe3e46fedd97d3021ba38d4bbbc79f87700a8797ac440fba371d75a8a0c120f939c9072045787952f3101ffd6b41d2aa287c3d7672e638ca207abcfa64d963a1cd4ab810bce4be19fdf8272ac9614fe1900412a169f2ea3d00bb54f84401e7f99e8aed6f9e3c25e717e37b355f5b185ee6686f93e9838bc864641693e8fc955d72fd12faa497409e4a1f0d7e51de6de2a865807002bb2cc4b9d42a9f2dc8433ec8c05f8bdfde4e845ff1a9825b0ca1be366ca46a21afdcfd47e074c2e3ba7944f0b61a4002cc70cf229f0fa96d5760a01b377638f19ec04a2c3b70d50376c92480b631cdecb524d64f441db93e65b9bce77087a5535d482f8de744014e39c28775fb412097bd925a3b5547ad39fc1a5960fcb12ed40a1e232f46d2390c5126bf4c0d7f2b36a188138dea7bc1387618f5457c5d77a7a56320f3def65300b0976839c923bf209d67ef857e12051256665b90f083414155f7eb3cf4338f8c25d485dca5e9589c92041b8bef589da84efe8000b31bc215cdfe9b250f95b2a4a10ab2f88a1a46f656355a22543033d8312daa4957437a36de92104fad1fab165fc5c2017ea4dc311c85c69d8df8a0652c6640aeb97835d24350fa2a4a1dea226598f96f5923a4e64ebf2f0a888256d5bcf481443285793f4607f2034130b6bcb1480b9f51b0bf353c8cce6f84f6edb8a481f5adfdcc84b52169941f046b4fd1736ad5f1763273bc6ebdf1c749bafcd0f4d93493291056dc29dba60689efef4c194f9094479ff8ba05603be173a481fdacf0ad60b01ac7c27e53bed26df934bc679e3b0bd15ef41ce9e37e3560876ede656dd8ac14ada019cd878f488adb54b0b9d3c0d17d94ba547f465ab3f509ea014718c59d69a05aa7f29efd07ae45000d7785cafe22f8f5f7e5ce359e0a20b72a2d4a6b53b34ec3323f481163ef6a83f4954a9642556e2d586d68f981981d2f4be2939bf7821b5f3aba2281f39c9f4695b8bfc7262b95831d3bfc2752d93b9d2274bf329c03909de1c3bd13dbb51ad16266b8213cab77ecd71400ecb39f3174a9f7cfe60b627218ca35579c8de2066dde1d04e14dd2a045b5f1ede86f5c6cf8c2e07e94b79d53467d8e97f461a45483a646c30aa3088700e9fbe7dd888e8408eba1c6a45622dbe2de6ebc0cb870f125189211cbe47aa0cebdc0760ca29f6d669dfe8829d4eb19a4944e508554915132dfbbb9ce16fd2273696bde98598304a9d37629c7b6a0a82cdebc0f840de784c8764d1802cd39629b74f0c37152b0fe96e91762b514191b6f0a3724a7ca86860bf2244f48af0e5f4ac0ac031202da36c2b46830379759cd365d8926829bd72526a10fd36d123c8977b14e7bfcb9990b0c4c6f17223b9b8cd28d1d316ffe60eb6a88f5c6d9e93072d7f5e75f8edb97889911821646d786991e626b37fcab52a40e5858d44c153482ea2183e908b7cf7424913556dad396d18aadd3bc8f01ce391e1ec288c9b9b296b393829be0de5af8d71eb33a0a0416910c641b72d08e53954e4b864ca927951081a965033ba35bada7be5e31f6eb9fbd00379e187fa2c0cd9280730c7ea198a3272e177d668826aa198f7a3f905d0b6ba53653b1f701040ffaec4826aa3159afc028dccfc8659924238191e704e609488ab4cd2686e129607cef54f711e71da4431c1b1ac983900150334ab133142c0e5b4ac6dc4000e1eeb7912f49894f6d973776de2bd7aea0683e66cfbde9ad528edf3533563f526004ce08554fbce8aba6f5baae15221c37349a57d264766b0b516424d96687f81f91b51b50a31cfece063f7171ec569b1c601a57d46cbae217c350ec1d4b9673866ae68d50fa378ffc4bbcdd91be345348a36727aeb295c5d0710a91f33d5b022b334c3f3401f50906e88fb055b704d2d4e6603b285de7c5a21d46c0cb7283270256652eff3cf209637519c666bdc459c420a286f93f251e10860dfdd6a8995d5890f2b87c7fc8e21c015cc47c545d315148d65ecf1efe26a64035b144baad1128f84a4e8e3a27f4b4617a7f03cdcc8c4ca798dc6fbdbb247beaf94b61d60b7f135c82f86bc77cfa98f9c084ed406512876bba636c86091f34e5fa1a2249110196a90c4a607f1f598598cae069827865d9eee087c6e90f20fb6580cc423ff33cb5e300ee96afc049c4fbcd6573310ddf0a55f9d815ac145c0087ec46f859d0be2473472c724f0c6a149dc3a0ee2c821a9065cdd772154dc3450510a3526dd39bc3d57c392970ac405cb9abeed925a73dc385bfee958166522e29e6ecc17334168845b63bbef19af0353a0bb6ab07f813980336d699f49bc713360ff2c6d60f6b432a5df0da8ecfec711d0208252dc8758507cea82a3e666721caa92b5163e5c96824372ff6658d0579f359b6137f0ea93f08aa6c226ca7204fe26fedca5088e79579f3d8a3a4ff23b7e8200e9b67cacb09613ac49d891e69d684345b3698f91c7aa4f12ba4041abea732b2e6592d634dd754c38fcba4cb24ac4b5004f22ed381e2c5075730a5225ad47c09c1924446e0de460b1a985ab2c78d0437104c391a9cfbd1864f5f94243aedba7ca507c42636e497d4a01fb1a21f6a7d1ae4790688a75ba6098c605acba068c3e77671149bf0a549f3ac8934ffe8cd480f6a238164687faac13779e4189e4faf126d8f6dba624e8a5f8ef7aa30fe82ffad7ca456bfc816f7ea0ae8ca7e4f4627528573b3f32e817939d4e3020a74fb5ecd113b17f3a9dc61ca37f1c0dc69309a352aad0e7ae09145ea8e9b34a935214b974c9cc17193a1058ff2231c67ff18a3dbc4a317d1a36b264459f858294104c9f75284896a55434bff469da77a6c1292c6bfa15b9e1ef5bfac4b1c68fdd2461f3cee668a80504d366451c55aa43f279af45c3774e28b7c750faf4908c7fea72c2a6a9377dda3e000abf2631eab48c9f7a0fc6277d72c44708da92fade8e2fa139e1281a7e352b4f213027d30507d900c71fd733c77f25fd4ba393a8b2ac86f3f59b88ad7bbc98ca0565214d69a525a6b1618d6ece6fc8ea15cc12374c56b4325ff36890992961ec772169f6a3e28306914ef450e333a191f8780ebc1b1a549596357ac72f750addd3aaa749e474b6076ebd3427c11d96e64de36449e7e94846dc10d999e6a437d331a1ce1d8d817f06592c3e53f10276d6133d670b635cf1195e92e2faeb3429b0e2b3dc631a999482824c2e8d75b0d92bdf4a8b1e5af1d919753350ffea898ce1d33ca84817c5eea6543370475ca1104e93e9c738ebb8cdb8e6c4a7bdaaf9704d7c7a205561daa7262ea497ad760239c4f7347652b24160207bd9ac62b8c90b50c7a38cf1ad7b2285ec7dcda3c3361d7787f0b455d57ba2ea1d65611f36c38b4342411fbdca3d9501ccd45815003688c9afded304bac11ae2e780be939bbea4ba38ea3d9946c8d9bd14f5959f72e2192c5f7137032190791fcd95599638616ca94e531c9740651187f9d73bba5fbeb855a82eae0979d92f51ac25e6f446dac2b334d777523e86a5e8c88857fe85f8eb18b6d62566fa17ffd1bb794f0603af7199be2c2ebd45fe45f53911b4c6048930685144ef6eedcb082685559ae67f5291f7164d05be08d04d30412a4f071c011477d4564f25dada4da3f0c87618a1823aa636c60c55fc0633439e53fa7705c7ab833f11e3d9cd200e3b08fd101344c86056dd716ba6b841fcc837841a62a0c59205a21fc24843cb2e27cb41c0a720fca669131211033e02ec57149fa89d82b4216e5601e3a97a5d0d131070abde61ee1b6ec36828b7459695adb8196303cf08be48798920902a4f34f23e6c91a8a2d6c245d812926336fef60b4b84f1132fd121cf49b58ea252c3cbd9f49c82d62e750727b66273c4dcbb472f8a9be7f9569bcf5159ab743c925e7b9cb3202a058cafcdd28383893d2016760f5a3b112554cb4dff1f9c53b4ad67c9792e57fe92214cb985f514998975ff3f8c0b4f9813217eca999ce4daffcfbda690944fb52bc6a0f9dab2092e32b97456421738aa3c24141c005034528b725743b00080e063a76d9070a79d33581fe7e819e102daa96482f5c7a1d28f20024fb965f9a9971613882a579fde184e3c65965ef02549da38b26fdcb3054604a56ffc5803ba809a7f593016daa8030a29365c963bd8f5c96ba3a687a2a45cefe50a176f2a35c4fd5b37635022cdbb1ef227e60ef47e9ccad5af7464ac47cdb3e8e2f896e51f6068dd87ea1f1cd38947b857c064684644201342f66eb2e59652ca9452c5071408e7073a648dc36c22ca7f3e467995a76ff3feb0ef7f0df4d88a45834443ee58a9b12b3f2a36ac30334212d7228f71cad1945404c586463e36f4d78cd6c1a279212cd8d629c99fd162eb6f4f03473de941c0874b89a26f240b1a870ea784c0297f7277e81b7a7ffe621af92e57c034f28fb0555632e2c86faf0212a7ea308d7cd943d824ffc52f57ebd8d4d4949c74a54e05db6b75b07e3bd65fab2be5c3d418a5820c0c0a14578c8c53a94b29a553461f8b97686cfe6099b1f9e3a7d8b4e2259f70b676a0a87ac0a3b3625dfa3e723cdb5c4ab9ca2e7d2a9381cd1da8a8d84afd2fa56f773885ba94fe0d4baaf499d287f9fa9e156c68ad4a7e1526b3e89d672602ed1e1f111551db6f60b37ad53a338efa7cc82fae56fada0d2fa924189c3fd72d66b05b3e63c484e5e7191bc06ed24b70205058fb244ea515a79b34f9192325d8fefeed4a0f02a31b9f036d5fe98e5234d1df58cba53a9de3a5922866fbc35243611ae7889a38fc95fb1cc73d18b210a3151931281e1d09cb888432fd37aaba9c2604a93d950c55fd0e1651f2abc7bad55bd5d9adb25b9f63f5b9f577e8aba9e2c79ff452ad3fbf6ee23ad82c1ed8b05bb77e0d65cf93b0befc1492a608016b297e6a6d6b2172ec68314d7d5d46351336d56fb9b6964b6bb95aae96abe56ab95cac9d23df8eadac5bbf54c3f130bffbfa5f7d36a92eaaafaa64089cef81331aa8c3cc0df3bbd7c0fcee5160e5c71c2e5536a23ab0d6af150c8b827a75e8abbd7b16a6a9ef51c8a1c34bf5eb6baa5bab4a468c4b824bebd67ee2d676824dd56f2865bfd3ae09a6a91f92402318a750b7beebc038e597d688d2b8fafdddf86ca2a9d68fddc334f547506cd8b086f9297c2bf56bad6148a37e0dbb0959accf34fe6dc2c78f889a0f639afa413cc0a8aec2a6fa955f2f76b1cbc6a6a6a65482dd307126d83dedea272c1bbe8e885242fbfa3b228a07f911257a1ee67bf6ebe788a8d0d7e7341c64fd89839fda8a8bea4eff2ae8a954b1377343e847dfc3be0b0e9c027dfd1070cabf72b2fefceae7aabb8ddee5798cde25f42330823c462fc4fe086cd8572ff56af0fa33164df5452f3ff4cd68effdb4f211b1f1638e976afdd1ecd6d73e1d666e98efbd06e48b1e05ee3511e58133fea1ef01a77ec9c1991bbc0f3d0fef43f6bd779087e8431f0285846a34d5b79ff4b67707790e671cd76e687b0f9ce17e7ee845e00ce8673890c7fcd00c0ff9decbf77e06f41a981f7a0dc8bf617e08f4bef689a6fad16b9897ea87b2b01ba8c377fd08839a038883976a7dd1947a83864b6bba68088e6ff40de5e529ff86fc6ede3927c8233e081402faf842b697df034e68af9ddf5fbdf35679394d469f4b705abba006bb521068e3f96e4355b14a0c1660aac070d10512182cc460116304892523046925630732398089c9c842cc1164ae90f1830c11648821d323238bc9915105172d1950200209205899cb518898018c1744f8c073440a24238e6451c4112bb238020499268e3041e4c8921820ec7639ca91228638424446cbba5c4106133be282f402cb294146091631462cb5c20a20a6401a82ed9e88918113194868e560082530540c61c48fd52e47198235c4ca063bb40a31461965cc9e934e99ea557bc5d41823479829e79c73ce4977a49452be4c980b1d69d3ce3ae79c53ca0fe79c53ca98e8b757a882bc18995f5ded38e6d39412068a91e336b95a6d146394914e971861314619e77c9971c619235d5d9e3298849933c6cc396794315295945eb567b06ab1ae7e62669eb21a444cd89c74322915c6dfee965256192de59432524a1913639411464e29a59c73ca28a5942f9399594a295da894538e3419638cb2db6990aa8d5146d126a58c524e1902795cd7715e57e50914921bf3497e8c3256ab8da88b9472feed6e29a59452f26bce59aa1226c61863e418638c31c639e7b499734a29a5cb489652caf8d9cb3cc1186324c51863cfbff56fe4c85c9a53cad8ed25c9a4106a081bb25d70ae116173d2987366cea8b48518e5a4cc2b52a9cad86462ec18a394730381a84fccc65364310ea69372ced8cb7b2eccddcdb23bca397ad9b2329e6245721472f1ea4b27277332a69353a32b59a22c994d462569ef9332533a67cc194923d61a9d647ec5aa71dbe8b8ee9bb268e2e73eae4184ed9f74fe84a518e3e606e06c74c8cc5cbd06739c91e370ce39e79c33ce19b4058d144365e6bac980b8211e12759b73ce0e650c21b33898ce637c992eb3fa8939c618a70ce79c5364ad681472f15eba19b91a23298d8161329b8c4ad2b8219479c69c9134e2746e886da661cb819e412be2401e1276c7f6cb39e7b4e1420d1aff0d9971c619bd6ee340a1193710d77921911db9bcc0c4c8c820b5306386868d8782f54f6050b0a0603d815d7f99ffc0d8131e1b4e624f789ef03889812f38e971a12573d2e3a4a725bbfe325eb831b23ae011c2757cb5c455d1b444073c376078d5a063afeb2fe305030068a06a1aa85c70c1051b25d30c970b081dd9cfcf4f9dd1e50242270001f068881f001967d57e60d4158bc562fda3648b122d3f30d7f527954c3d8425b110bf985dc086b07a08cb5bf2fac1643209600032c6f081c1c6d0b9fe24018cac8cf3e7e707c4e5a420070683a540d5c562b12d5b5c2ed7952b61cc5e8041e45ad23d1c2805391c688c9ceb1f435e970215071a4375fd7dd3686ccb16d71597eb4a18b3eb4f1a400c31546851a11565a5f16888386ae6f635936e31cd1054cbf24343809727ec3c6167c650530003d873a552a980a8d9622c45537c1726ac266f6053fc70d6e05d0777304dfc971deb4cd82a9f5325a6896f0a4b9175304dfc19589c5d71438e892e57b102271789bfe4d3f7bb7b34707ba2252cbbf3f51c7ee23e71dd0676c9186337bb6eadd69248a5524d8d8d8db3eb3a03ee08dcdddd9be3644109670f40b8138a2b5ba3dddddd4d3d1ba20d3d04c1db2fe527ba135080f97d7b981da1e03876cccd3988596ed3dd9db26bee946a93b94a6650e72519ffba83bc999d79fb2146055368f0a4082b1e2811048f11b0203951022ba070c4cf12862829a20830519a7002b3e507531811061527e410010b992329319038a1c50bb670c117567e1a0658c820a185254584f004154b34f1c1420aefd3022a9848c2172ca8e28a982d5e85b4c47f5f2143c4911c2862d0320205892e604ef4c00329a8305285922549c862082df881a2c5163f68d540063aa04205bee2054a920461c6e3841d5821e30a226ae0050f9060c48715ac7c8a4c174de472a2ce4f289077042d6fe0284ba85c3d52b6a881163811ac89a6f6c1cde15dc50d4b36b72f0f12281081053710c206385cd4e0082ab0a0c19422b6c0e9e79bc31d890c2d5548a5187be01932450a43cca0ca1841a8a97942ead6b6d7b44d0a92269030216190f0e092a22049020b2aba1c252e5c498c88a245119618e20651901cb91c6527895bc5e5284630e15ae7894285d93d01e98641542ad50542baadd9cbf1db93cfb526b52f9460181f0043ec85804ba7c2044710bf9da384c1734d60af0e366e7fd8373cea40564eb5c78426a2aaa040810205ca14d6359f88b29a2ba2bc97302f5febd15e11e5a2cd349994526bd18facae8ba0249caa4a383571d0375bff7c5a70dc376303c779a377f15e5e422f7afb30f45950715e22ebc80e0e2a1893d8f48f12a6917f44cb8669e4bf7c096093b4fcc4952f62215cf92196c27b1b51b31f9207ea8826f953eb696273a5acaacd35d795a2cb2e3214ba56ac4714a97bf9f2d2e5e5e825a9a7a6153acf7f3071c74f333670dfdf607841494425e2127d533f6497ec5382f587017d33368040cf77c68809ebef4bb0fe1cc7d0926e468bb53f7affee3d1aa20be8bdb844d017937849fef6459eb844e3901a1893601ab983bb43dfa8446cc59ed804e64ac934f25dbef6824df2479ffd441f3302c2b6c29507085b8c02849d851d3f4c9cee4911e5ad1a1eb9d381aeca21a5749d3abaaa532d2e322a54a8b4af3c881e67b9f8ca1de6afd95aadb020d37c9e38aa61be7216cc4a6c8a8f949e96e7b8ca7572be5da5eacf4bf6faca57abf615cc57305fc1345f398b8ad62e196c0a95be8954ac159ceac93433c27cac786976b0f655cfeffcd42eb26de62818921451810e0e7f1d7952d3aab0694ecf1551ecedc880c519679cb375e79c53e6ce39a726050b36d45c9aab490f0fa78838aa63384960457442d2c5998dc54b310d768a6952bcf4f2d2649ca8cd49d2603089233fe6926453a4f10373e3ec68ecf85e3f0142df6c31681cf3e7cf6f281a0cb6c18069e66b31cd56a1fd83c54b5383915aa022a3d2438069282381c4575cfadbd5b64f723248cc070e39d01a4d34ea70c2d610f15328c3a54f431b1c722c6bc9e2d5d0c3e53eb6fbd5ba1ef1bd8e2f9f47ac2f447e04399aaa0fc9c334f407c80a88fbdcfbbcfbe84b0ff442e477208fee85c4efc08e4513d52ae844300d7d1d3b24e0254a5f3532d84454942e3a73d1209886be8d282a8336d1a7f4084b6da07dde7d59d9ce2513eb28ed609776af4b6989c638d517c7a5cf51ef6a205d310dfd511336a43cf429cc4f61c98a9fc2b9c5c74f21555d1e3fd1a74f93f0292429f4096d72e9a5b3ab9ac7e6a9551abb740b55519d920c57102398884adf90376060d40d7c84f06f95e1869f7870b3fa3cfc0464f43c38a57dfd0b706adba28cc13c1d2bfbc1a978eb0309b2d3ac1c3b7aec0f2041fc14ce20c44f214fa199bb7e09b6ba6d1bf7d968aa0ffae86fdb37eb9597db6474adb96e35b464fbf86adaf633da56834453fd41df1f6433839ffc3d09dfd183076c04db745cb772b5d6caa350c61de4adcfd19f8cd2a6d0aad410e66ed17379cc6bd55a2b29c6a968e36aadaf75ebf7066aaf2776cafc146aada965cea614b45216dd9142eb8932ad155173164d75cabc543f04869c49304dfda804d3d4d75e1a4c9b3265da8b7124c986be5698a63e11647336b5902ac9a55da1c1a040f9a2b597f603abd2605a88a850698d0b3669fcf36955064dd3692c4ca33d6fc1342debb074b50070ca2f29a2e81532319751712a75232572e35315cda13a5186eb52d8f0af4c0c09f10bc0edd5700a74e3cb60f331befc68904651d8f0ef471a344871e541708951a1e22a1795f7382b76444d87f9cb797ce7cecf4653c75a4bcb7ad65a5ad6b2fef152cc55aefab161cb665a7efa81a0721514a689d12536bb20b5469b65029ba207f35a3260de0ea5b14b7d2ef55e977aae4bbd1e8f4783716a74e9132efde85f375d6b692f2f953497e6f24f6ba2f578e93bf63dc1869aeb52eda5b9fc14b68f26c5637947fcd45798867e2ba16f62118d833e8d7d0c8c4444cda5bd22a84989693017ecd2af9d9f4811456a398142c5b276a9959763a17de5f5caa2a567554a7dd5d70a2aacfa542b5ea21f752ea53a50a0d4185dd11751b1a26e34049be3a750e43a3b00a917f474870752eff63f92d20fb55cbae5d2b0e36dafa7300d7dd137ba9653a2a70da3cfa2184560144da1efbd045d7e6e76a1cffbcdf37ef37efb5aa5f71ced42f3411f0aea3d40d74dbeec4d11a766dcd1c2f18dafca5f2a54a850a14285ca11ec8c2b36a471fdfb2385b060f94a1adad77f982c872954fa46c6c00a4e75af6030601af97225a55dc286a42b0321cf420ae314776c85c2aaf04dc522a45c502cd7e91552cc0d4911d5a3c6c4787f7db76ddb6aad9bb7d157abbef68171f8735fffc034cd51b85efb1024a23c4ebdfc54a7f8496b231d440f6924d73f08b7711ce76db5d65abd59103ff9735fb8dd067d6c857a69fe449316f262c78779fb1f3d7ce490b30ab2a5c8e6d383e7dcb66ddba657837737afdbbcce0611accdeceeb181eb289b68f29101f7628236d5bd29dbe27eece6761a9aff80e6388e638ff36ddbde3dae7b6ef3e9c1711de8ab3e57b030cd4f162f351746739c69820d39cb25819ffa87edc134f1454cacdf907d38101d04a2c2d28353de0d92801bff07103fd58f91e4efa509b2a53f8200f152fccfc7be977ecc7c61fbb26c66eeb0d1143f87236125582d0d6cbf11949b9972161a39a2a4943578976e95c4342f4ae4866bfa860d10ce2c6ebfff01f8244f9132187294fb4e2239c99d537d3b720eada9b446d14ac20a2d2bcc0e608555153c51a65801104eac6211063664d80c5264b95ec4a40c66ec6c2ed06f3e9b6b7b6d5336d896659b6db10dcbb605646363c373c44b4e59dc73ca7f7359f18eb5e0e2940da760aeff26c54fddf706a39b6b7b6d52bcf45f134baf7ca2282c7b89aebce49f18f6e5ed3718d224b488979cda1f3d0f2feff23cd81ffd7f8cda9ed85cef6a5715ac7d1e466fdf9bb9e1e55d1e03f6472efff21818bd7d6d2603f331cf03e663be3f26e6654021f6635ec80b2844091e2f6f412130ecece0a5c279016d74018a1d1cc65122a43b2f1fe36833d7613e0d31df4cf732cf3830df4c07cedcf0f2318f0198970179183dccc7803c28c163f430a0368b267f1e5cfe05ac2aab09c1f69d1a080402818460fd867f5d3ec6197d8c53577e9ae9be87db8161dfba535b35c95705bbb9a650d6f50a4c9cd173d2d3a12fc8372b302f3908aca08f092bbdc43da86eb0cdca7665c7561e2f2591627768915869c037a05a8453acbe0181f548e3b001a740ef60114284758782a8cf95237d03ca8271f883967087707d5b0e2c8d81f48a9744516c072eb1cf845134e6c40f7d82caa6f089a85aabb5ee73fd2783ba4120b07aa0d18b56db6b27a2b62936265cf7235cf72a3cd7bf0293dba6d85cdb4bb46daecdf5e1e8c38d8beb9b2b9a9cceaefb532bd019a7b82705715d1b72dd270e7b391c0402814023ba8a28fbfe548712f1d3f6fed49f1ea13b7e4ac2439358721b0c4dd7e672dd87df0d6d6e080ceded6cbad10d6d36d7e6eaba11079a3e6eeaafda8f97b2c8b4cc08f90c252b661f3a76f0e8c1593a6736273d1ae49c73cef8bdf5dc7a8a0d6d5e33f86901f5fd75d48892ad699ad6daf62f1d9ae67365c6e2660496ccdd524ad92f9f69bc0616567ac9464a29bba3d72fbfd239e7ac4cd3527a73ce3961aaea835933a78e595343aa11bafe312ac42df009062ef973490602dc1862f0138f11852b85a0a50beb37466666e6ef9b8d246ba4d4115125994d12d77f8622927020872a9abc152a5a57ce76f726a0fd5015cda9a9a919adaad8888958229548474444b41215112521ea11b5aebf68479444f484ee6c3bddb613511c616011a5c5a20d15a7523e1a4cec3acccfb62352718a6e4b34adbb9b511b9206b76f65432da66971ba4d60ed097cea82515aac6f64681cfe7e43d24cfbe2fa737352a61d43b3af66136f5f619a4b7738556374878664a02c9866dbd97642330a86b46c3ba290ecba48751d5ca2f3380fe85aa5a4b47af5f973f8cc8271f4fb2fe1f68727914aa4aab54ed8b4e2a62b4c13ebb7110b2d59624a18af190b013c51a4f022063a3058d5841db9d6139be86bfcd7c7d53ecaf082d70bc2f0c10b329ee1179e9715ebe5c8cb0e91244f5e5e2faeebffd27a69f232e5faf3ec668e533ee76c8d2b488aa64a83a7f29277dbbb132475244e79f605f75c2f2b4e5929ae6b9f037aedeb97537f7b2e89977cc74bc26b792b2e80bff66d9f67c47522ae7b46746c7034d00b824dee04d3ccb46c043d1d2f67f5c4865a8feafa9c71aa079c72d237f23dd47aaebfb70a89111aa371b8e7eaf15c4ce33f7384f5585e112fb9bfe807367c59c51a321bbeacc297d5f597d5cb8a52d8b4125f90712ae022c7d8b19999e9a8e63a4fe14235e39c75d6d81edfe3941f9fdb9f773fa26aad3b226a53a9542a1fb6dcf697000e3f6039ae2d72ae2bc1f164ae2d90f0afb19dd33a8d99c18dd64abdfafd6d97a34b0f35d391047e7cbe8f69400308e0fadbc0c29a981924f9ad52d6ae871bfbd60af352cf2b33464712e082e525be367e0abbc7b8118c2eb02f975d58885c9112395881f113868e00c0f0b926a8570711122c30c070f10c3a2c88383f68384a9840de7e57b9542a55119d1f70985029b558b69957a0524a49699451d22865bc934a19e38c71521a29fd30c618698c94c638592ea1e5bd71254cac9144e39f46f773eae0db95524b698837fa4b308eea64cd8d92fc9760f83ebb70bebcf46b6cfc446d688d04f9cecb7caa399cfa3811b52006467c806bd007b65dc7cbaa3b8a12793b8a114d6e3f0e3ff88a9429c17243fec9993b4714b9a7bef1db2fe329b41d9e6e7f3f8d7e19fac6ff460cb7dfbf9922ac5b272295f4a824c994ff59f3c5eb462db31ba70c7272f40be77c192f5f1ac3f91284991f497e1c417c794710df7ac9234802c286a2eb224ef5bb1238bea1c34ff1a5b861f7b4ec1fa06674cf0d885c8ee2729423a2b83a627d825139c49e88ea1cb4113396172b2e8ed0557ea6b8cd839e1cb40e181551aed2f93a8788ea277a6684172c2e564cd039429529aedbb882650f49d0dd1e2996a31c01045f4d160bb2a8ae76be6e7f4863a30f9b15e59454ab1fda40dc0799cdbc502814722fe4127a89bdc45c463760c6a0aeaeb5bb6bf45a6badb526c0c64654411b17d890b26e48a246581409a64cae7fc7cd788134638334003979b6c718412010756d33595bd6aeeb2ce92b991497acbd609407c1a6f851d6322c18c5ecb2722d7e027912bc09d1e33b0f82e0147705898af8d5073732890fe2275c8f0206ee1b0cbfdbff1dc1d207b5f0a40fab8ca72d80a45a65802109bedbafbdbc14dbe5a5f8da3785058135a8b061bba2e86964b1a1af3c0b46f91023099cbac2a9d047afc28d1e05dfe2c6f71fb03be13ee094f6f1dd08c7af89f5a36371a3571113bf8b0412f7a488896d26d6bd0614b6af47036d99ac469817d0157d905cc09034b23b37d66a4333761e11308dbfac86a4e82b29ae7f1cc2f5e78c70fd41cfb16b601a7f5f4553fc6a49110004382d600361601abf33fd7c435f45d4f5f758313bf125d378833b4813b41dc945f6e2d6555db3ee7dd533558e0e91242d1ed691265dc3c4744f44c130f7b0c9bf59dd93c3b7aeebba8e7a357837d43ddd85a2156d61c39ef9ac8bebfe85cf9a70fd09b548e370d7e2fafb1238c57d655171fd0a23681f3a14977ee851f04db382c07ae2fa3b10349823719d8913ee1b4d4be3f00fbd88ebef4470aa1ff4a123396d1fba920b031aac6c90ef2d9c12dd64e105e6fdb5990ba869b35a8423551968c7b7b29ad537dd137a606535abb3e91b1e1cad74cfa45aed593469e99bca0235d80ca6e5fa73b308a47178cf2a8b3710f7399c83c418db537b583d11d53eb5c7a77bec8bbaaeebba2ea6d3ece7a1cfbdcf7f601af72814b94ea463a298f781db55537c01306e843d88d1873ec491c47c3dabac6ae487490c89eb9a0f9218afebbaaeeb621e1643300b86f6863ee4b44595308e7e570f69c9eda7449c47df501f7a00a1476c8afcc056ee6c160ac5781d0c07daa88a69680ed5a149280f4dc24bfdd53513c5cbb2975ce52c57e938115f1549c27bbce549fc894d735125188aaebbc4bce42e3f2e58e24f57c5e611a6f1ef9d1bf24b2504fbe32a4ed5ebef392e2a4e6dd17e6772602398a3411289e4ee2a57b9c8402de8b8da713eb0eea906d8cc1b06d880e15d179c5879e4e4dfee44e89dd78895d1a511277dc446537fb5965422f221cf660d72e8b072fb25b0a39f870f1c5cb9fd24086243c6d25d5cce15050761dc90b7dcfee9610f5736111dedb3fd96649fd4fd2ad2976a6ee788a6c69931621dace0a807967f673a55a20a56c7ee8eb3657774f588b546299cb85ec45acc9cc38e1e1cf02144e7076b21d27287949b8d26f9b2b24195a289533228ed5d4b0d16ae078f29d24b91c7d74309edd120a594d26d7857823b6261a9db7b78c9fd454ab061a9246549966489520adb3127680a33d72bb5ff5bca5abb99bbbbe75f3a5b74c5ce175db1b4676af06e651afff7b11f51b359d57edb645ed2e2a5d9166d4bcfd95bedb9d58ec27234c9eeee39e79c5bedad2ba5946a945e818236094267d1e41c1bdc28a594520ada28682b82652f45aacd5a679d73ce49e9dc66a5daa66d954e4dd378b66955d3b42d876f5a104d7b81eaf96dbe14438d0c12e821e50af77f77c7d9910057fb7e8f5ba8b80dba67a5bde6bfb2a9e57fa49a4ea77095f9ab8521d190a11483ac5576f490f4bc988dcf9452bfdd4d2ba8d15ae7f4a14e79286d1ddd61c7b4bf1d80d01dbc0ba4764f29a594ba578387827a1b9c11c2da085840471636502009a0afaf0d2cdf0a02e9cb4c6329b5d452fbd2512b28a7d65a6badb5e36ac75de1a30110708fbe61c6d160389970bb037c8a1f927866b9fd217711e757b6a9bd814020770ee4d30304e2603e5762587e405a8abcf7007978a97bb83bbffbefe8d1de8387979a7f070f3ff5f4973b7a70c7e6d1dd50a8d8d9a2da52c405d58cd1fadcacbc6ddbd4b41a9fced7269d9103b5478e88ea8f2285159c81a09406f141821f4088b056aa9c66661935f4a2779cd0d738f4e3db22d14c7fe87bc0992f2b1b728c47f721508802725c38dd0789a88e0011e57de8fd6d8c53351c4db23d071cbc54a3c9c7b24783c88a2c4834a9be1f73deb055a5ebe30b5be3ef5041bfbd0338d5318dbf3f0dfedc87dd4754fbc026c631e4fa8bb0b0f1864d9b73c1c823ec0eb273140e5d777dfcf4d8f1c341f0798071f8d7cfc787430e5e9a49626dc757588e26496bc82c0e6d35f1d38cf9d0acf85c7179b176354fab49cf132bb1574f692c5bc8db3646e5503770f4e21f7ec92d54359e880a81b5d6706243fef95053cd97b1561e99e3a81ca2c9553ef0cdf6feda0f9cea42cdc8f522ae11b939fcb40eff700c0281e2b6814033c7e1df7dcd844e447593298890d22baf99b43805f2d3163fd59ea29f6827aa348fc6731d098d8753a0279c6002dab66ddb661637f56bd942736e5322b7799aa7e33c19619cdb22703dd35d43fa1ba51d3633482691981b4708c3888cf1839302de92852463e0f00cdadb58804ec5d114a053c2d14021ece2c14941e9090e05693802fcc31aceec1ef3b3cc0b1fdfc0340e7f98be69a1c434fea1ccf5f71cabfddcaea1fd924893890178c480d9893ed1155f714a84c52c71e61f63114bdc32ab56534325b62415292f2e4ea1ae47ea2d6b31da0aed851b99c24ab04556d8f9616c45410a7d135b56d392627c728cc6115d2d73318d3f189b78c99f636b5b6d45a22bba6076404bb4d92a48eb349124ad66f591267516a436a36cd8d46e23baa18d487eadb5d6da7995ce6a44b101a00aa8440c192d7ad6211a110000000003160000200c0c86c42291689c66792af70614000c7594406054349c4843590ee32888629431c600020c00862800243434eb2815d138ec49eae8dd54bef8b10ca3d7aefc26a67eb4d1854716656089071e7966aa816806080c00557f770e34248c854f2c37a24b8d1ed684ac0f63434174ef5319e48f1f6abb877db4351fac0c44930bddb6321f42825c7094065afce2c5c078bfb844fcc2c7d9fd4cbe66747d5cc4420c1681ae5e91e54600c53a94ae0e69632c2acee67d63763a9bc2bbbddcaf4a478e16b7ef0fd24553457c329484cee6484749bcf10ba08dfb66aeb1127705f7c0e604f48d27fb3d48d75546cad361ade7c0c10f2e99b490687ce77194266faca1ed4988a8faa246d3b933d444f6b5f017bcfc997cc3e2af8cdf677c59d284264a4165f5f2c8c645cc9500e9d108583c9a2252f6bb19693b96102716dbd144a0a3594b1337ebe0aaed49904195c7b8f3eaac12d570b0f14ee7a27e9465cb73e326b234adea868013e092ee21bf72a2a14198e7ee1b438646277e0f93048eb1050b1c03c498b0f9443adcbd99790a5434f437799777e08bd1cb61dfb357d46df865e12acf178164b4ed4ae1faedcd8dbaa6052046b987bf0070525e6f32bf2b64cadd69342104ebecec375a75bc439869e54164f2f99692c1858f0083e8e2a6ac98ec4a18ba40280df4e5eda41366410ea813b35f261b221a9df21ef2e13237adace1981f2e65c72f0a71c7cff3fd15eb733fd0d5ba278fc37ae5af920f94624fadd1ed7f1030f19fa42d58fb443bef08ada0cd9a30d4111d7a7da110ebbbe2ff564b80b4a7eefee49bf15f9a78d95f2c91b14b308bc51e0d1398a7779325d3bb454140ef0351468a799b0fc3e0f43198b600ffd7778f272d22211a47a041f0fdc26d067d89ffc0a99aa1d247573e313407407a08b6000f7ce63ffece980e581f545e76e589ec69c22f354d27bb64ad82a621988a631224f99deaf4131ba9859ff1d9b33bfdfcd19926d1974dad7d6867b2aca413c83b2d6ecee902d64004f478c2a3d919e522d137755b9e454010519f31fdf8672aaf96739cc0f5c14dfacb525250c7080a839f11c37065546f3adec4b3c0d1b8469661d02808db21abb053ab046c2319b1556f03aeaf429d03a3440187609c02e67de599211aa4ca64abd6325e19ffbb9228533d20ded3847c574b0d570ecd74be9913cf434f59420d621e9c75cfe7aadc5361a704a24911911957aa508449d348e39ac8814a29458c174aa95ed9c19a0ca5047d5005313f3d2530decad0b293e5b7ef89b90d8396b3ead3ac668c7d90c9a7087759b42fefdb48405fcc789ec1f4a4ee29266fa0bc3c990d87112665be6b8fbcc24094e94972fc7b00011a3b9a77a692765267b390479440132aa7d81c26ffee765a5933acdf92a5400d099b9a51506645f0f734050fb9c6cdf09a0c3c3281400e95a6a897412d5092a02331878bc492a1f53b06d8dbb57d2445fca733ee9eed297214324ca480a28c380a3184f1090571ab87b0aafab4ea869cd4d1a5625d8875656e1627cc340c95a08f22597a5df64e2cd34f3565ff78a4a02b2f7b7b6cd30092713662210a01a0491676917d2b3ab46e53a87978d89edbb78fad12945b177204d4b8c71eb70680f4a2c770f6fc18b0a9f7a974eb59e1478027c1b285b1645312654d7751217e1f34110d5f396485c95049fe76e62ed26440bfb4493e5ace9d6bff281141cd641b6e1700008728e81b89598037545fda5875ab6b1868187866dc0b421a5e00b79ade00397c9341a1afd36e4720e1478d1566f3c8034b0f7623e34957a29d7e244cf70db32424ce77eaeebc6c96f944f63129e7069b8fbf083051f454ef6f774df9ee712fa854b710244ec426dec292c02393c28bd53b7185f10087de34e689ed86778310f5d2ef904ef275113ff5e56260beb84d0d927168e05f20654b25c998d6cce1c84ad020a9b7aa7580f40ec6cb1956353ead970927e81da619775f4a3b1d6602d1b37121c8c6a0d34b40486cdda4a2791a99691072e0d073170822a175adcb6e852484f49f9fc7ab84d19f0e4b66b2275f2d888a5a2326f2fd373168dd8f75622b4bdbff35f4f54f8910b3dd484122316818229966303ff3c88077655e160514d07699a041b6be477a6082c270a0f5ab875f5ccef56d3aabecff0abef50263d09a1cc719b724c477898dd44f84b28dc8368132e4915ac3b7fd0a193958b71584b414956c8942c2e2e296351a73b01e2c5265ed31aeb6c15ad27250d0a46774abe2551aafa0ba77a905de67ee3fc9d71bbcdb7ff2447f8317f134fd5dd6bbed34a7ddaad4e9347cbe752aad6b5a4b6e0767076725d86068b66e4643f625c2d1e37d49d6457881d9ef77c073afb25e1d69588d7489f339b560049a7550a79452bd4135a641855a913e70387f456436dda34b3cc0bc9b5e76704591e923f876c55710750cfb5d1a03b1eb73a10239c009156adef817d06f76e4243e64d16a6c04eb3d43a0fa40bec356eb38968561b058e5610a4b21834985599a875dc1433ac8eb48253e76c081d241731e98b6aea2b7197bf6d61268d7a31bbf948e5a4444f07828978a444391715b60a72ea70ae17024b0aa902b58191413c5f0023f2f8a4ac56d6318e210bfc7288af110c9d303f8fd8e14547cbf98621e1c3a34fef009210bc67e7cf84ac377f5a2546e3611db1ff87a65a4cf151d76bb523403217eb735d9aee7646e49b652dfd113e720172785f7de7a052d1180e2d6893e134d5e24c932fa8aa0b522595b20b29252d11c58e2b2d7d12378c5514356931a6ca7450f3897b8feb9132f12f7e2b7a739281d12060ca9baef32b5b384d61d163d1cee95a812960bab60544e865c745f5630eb87f742bd96a92cea146c12c93ee88ffc4a24e09a353466c0376af2db0f14b2b66eafa20286f8e4762e5351b402564e856cfbd9dec6ca6441c75b89bd8131007fb41504814ec118b52cad4d336006ffef8446e4ffe0fc0b5437704dd46af75a15d231da4c19b92ea1f63e3e33a461b8b01b2b05fa595c3441e7253199eac8481c50038a23379514565a660b3bb1ea2fb2e00cc52a98e32dd3e7252ac7d115d5951bd3cee8f39e5dea250ba3ad34a18c2cca8b56df5378394c349a497b25f281f24eed922d7c72d692ccf4b2eb6534c75f81349e523e0bff6a80d4cdb3b0d0af80fa9b4003b48b45a39518c6a7b7d02141bbd2b1a23f31cda1ea4e3c9d2cbaff1fd49ab240a26ac6e37d75c5c9ac2b99deff52c7940bf902e8d5021e905d10a6cb86aadcfe1f71b94891764103506520935b102cb62ed35aeccf7d32747dc33962befd0d60b66bf9ecc3e2a8a33f0405036c15227cbca5cdd3f5f2c02addb75a8ed211bed4366042e67b377e94597ae51a3d5e2fc8a800a9f95c346803ba08fc28cd43eaf214485da597b0f39cf9939664cb0d56e255473a55275814ef3497c5951076840c6c5c98a699530b1f21930a34cd0796709023c3c37202ae3f31a08e0fd6cdb2d05389887960258f893672d12ee1096c3ed7f80afd90a502b60e1d20e87bd5a723a5e765230b2b45f344f7fc3276be48d62db093b6d57372f1fbd8eade3ae8214e435f28362c8493f539ad83a436d6692ea359abeba8a4552c4181d976f5097a0ada0fabb09fb1836531c5a365811f1722826ea790af113e1972dead9496559b2584022c7dab730baf68f98163a86a89e1395655393a49bba81177600c5287ceaf1883d401823b9bfd89ea1d10a1f503c033c637994fd6d62e49b57a910c241bc9c6345bb0584708bd3151cd47ef2f259345b033e0d3b280211f607d002d88d840423768dae9f9838d3bc343de1636dad1d028202df8b19f9f84cd0d968f2f4c90aeacd63a067729726195fe6d7652bf82f528d6bce45f7b0a7217588b5277840e5898039c373617e6205f14aa71885788a1f2adc12746013259ec3543cc03354fab09aa90e878ed8deb38e237e6911e31618d66fe6ec370d6791b31d69c6483c6994cd6c62aa706c9e16922fec1eb453a844479a8d500026444615315026c352c745e2e5092d9d73b9705673800e2769ef07af6b5d5e97aaf5d8cc72c250671321a860e86cf408030c3aa0395834b621b2edd886423953ec8e801db416295757084ab9fc75f12b06b9b1812a291f0d40c95fa5bf2451032c598fd42771bdde781e5cc698483da1293da1e1351094343125e469597d15df8b8c1de83f973fbda1caca0803a9d080a02778d04435618815a363efa609f816d5e6d0e0131f6c604f04ca07cec2911a7a0621c47abbc1daf19be7a40a6881921074aef60fdfa541ac3e034848bdac96d2c965190063cce6f22f99a3d3a5f55c7c8de4747aeae3bcef675c1c85f00842312c995773daa21033d7efd8bdde5927c0c0c48ec2105a9d7fec4cb0ad7ab9ae8e0b4a1d02aed1d5bf5d6f9dde5fa31e453f4c2e589954c5e44b969d81d470f0a1fe87014b2c5db390034397302d68e6199d1e4e8da9417d6b2d5959cbb6b97e6bc9ee6323000773f05d8426396c04fa9044efc1542b29dc7da9b7d65109f486df6993eefff1fe0c72695104010af752080b98b0a841dd8caaba960b900027560e1023900ecdb781b8f1dd64d38fcffd6821d7900dd5b3dff3434023569d224a0f4e909b316bdd0fb6ad13f551605394e69288dc1ba3370f5ea18eb5739bb4e69e629937c4543a554efa9f11b26ccbc1b9f5c10aadea4f437acaa47725d6f39f6edb45049604550ab1504edfd5b07299bbd2f8b67c3e12dc8c70e2c6342b3320640436f642f58f5e633f5270c2bd95c6147a335031a1e80f817683d507d8e163520058a6401cb43a2f5330c804626d08329f00bda915b9151c8f7ed349b7c3b49c867c7fe2a7212d94361ab5fc02e62987def9a46f652931bea20f23edef7fd737377e9351f0a23ac9f794142a83273769f4e041df705d0d3ebfda7ebffc5211c91ac3517a68f8e653eba7433fb45fe721bc784dc0791f073cd4f67dc22ad11f44767db56b22225b66ba56df999bbf92378a31f4165d5510b72793d7db94d24239b1d2bdcf4779bcc5b104da9559fcba2e42c20969ffa61379f49247ca11bb0f159c1759e973af1b1acd4671c6b8c995d82b061c51c9125875da9f42b9e1ec1241f85a994ab7584eda60cb6f09c51f673b5d329eaccf52038676099197d72ae3e933797d59bab79239822264a4915df7f784fc640a3a74b0610502bb64a9ab27be90a84424696f624df07a208c6c54d29b94c9a3c69b1f6e41c641d6761899ac1cc8ac7b3d802d4766b9c113a21b606ff9aa46812a8d8b8d0306091663b00027b8e67c36b7a9e3f9c6556f5f29c692cd60b1bd4888fa8f59ad2190005fe5d47e16c3adb43160c470b51b4f0b0980226980fb2effe854f4cc99febff913c1077a92efdc8237e2306fa85d00b8980e8c9ea4fa3c495b70bf783ee66197fda090d96a4f263302d53b9556cfa89ad39f01d28ec1a189d208d0a64e9c7d220789b82f22ee5e63d719c2f8c1103af6fb843fd28cf58826a432817470c88801c99ef975442368856247bff9301b10a943790caf1246bebb00684b67832741f5f5d459d20fd502442b5075bdd2f08caeb28c905fa8a7d4db65fe93384862f9fff16763bb53f772d1d122c03fca0d51bfd7577a237f024ea4c090436e9d00c37649b0796fd06e97096d414f630a54651b0c7ff95d54053b29d5af330f8302a0c4edd96940acecb17046e533f1ebc13cda7d47d2b2737f66cc7c107017817b1016b08d5f213dec84df4e5ceaf4e2006637f32b336f76504d11174074a7754751740b8bbaa3c55e714c6cb9d73d605451603a3d1a04fb586bac75971860d27510ba25c79ae47eba2a0818af76cd7291a2b821abc99988f19d8f49fe6213c1e27f700e3b2e79b5d8d28a1d4eabea2e18bdfc077b246c582c7cd62f7577d5e269c3dfe9fe86eb1b2c9ad34f0c78a95a64791bba9d4cf2e4f5691445936a467a565e770c7a215e8e4c0241cec0cbf574380b9bdcbaaba03f18a876dd3ef303f0c7306f15afcd9de4652fa802629aec1d650e756e6324f6ee5650e90e4afaf235672d16fbc0fde1e730620134caaf664742e9ebc55d17df7f84f0c93c2c3779d2e2a93b37ac0d8de1662f1a1a8dbc66e90835e66eb030f3d84757ae45f19441cec69afed1dc30cd93f2b67801475ee92c7a9ee99d05d6860256f555c86db077316f0179e07f723f203c0eb26a8ba3b7bdafbf1ce171deac109f0d4fa5dd9a5953a359aac35a4b46ca645fbd6cee146b079b7eae47219d43d8da7ff6855237ecb053def414444939ecde5aeb310c22e905a43e9b68ca5a67a03ec3ff2dca49b688c0fca16237ecc412f4f2fc2f18e6fc8eac90130d11b0ecea09e6ef8b39ffc7cffa32ee17a86e30db4332bbc92147eb02bb54985b47fb4351807b02e5931ee3f555456fc39e457e5f5cbe5c345fb28f0227beb3508162e2e40e63be288b204a133be6369ef404556dd405fdcfb87bbcb9f20a177773a18607fa5285c0ad8c82d4a6565b68db3a2d8cc2f78adc1df9772238ac190a3fc229bf60c6abfd1445c238298e085f2b0e1081b988877c0eb40f81c2e901037d020b0ae173ab86c2c9eb57a6ac5bc76ff18732d27466a9158e91d946cb57141872859da2f1aacaf98e435aea2d4759b7ac1f32314b02f2ae1473e43615fbf66796b864cf50caadabf5d1af137042e90b8eaad549cd81058feb6021ca7b1c90104b0c00b54f4371becb0e5f2d6c824eda56842b3f3fdc6c755a3a7a19d877495e32cc1670ffc2c59d916e8f0172bb1c4d1dbf72023d06ba6f6c26f32bc812fa4c27751b5d6feb5815df1f98ae0a95b853283649f7d9bb6378f14708d98d8afee808d08c7f3a400f4ad187430bc1a5140021370d4d1a1193c0e179dec0ab5600a6ee4545693a339393e80e6dba9cfeab07256436d0b970a2288065c138470e09c5ff111ccc310f350320f5a50605a06a0da087e495203c5931adec3536bc8709b232ff94a0575d60dd0621d8d2e8f7a7ce715de7e3c96c17a3f658a72634d9affd193880fb0fc1c3e6c0a76b7f12e61a093cd1450c5ee83b1deb1dbc6aa828499f2641c59006fefff82485e2618d8400eb9b36190358ac9cb1f57907e04324b5195b21d5ae277a65ade2ffb9b0378c8f49e31092815209fa1a8e7458916b4add35b29b26bd64a15ba8c0e53f109a26feb90d06d84c5b73e26941340cda9fc342ad427d6914aaf3d72fe50b855586855c5a2dfcf44f6e343413fb444f8a7897a89904ca60e25772b854f643226161dcbb0949779c54dad05ebc9eb055662432a65a566c534a8c24bd97eaf5f8da312f021f6fd8490305bdf77d88ca20111f2f9ffc133f9a20851a3a2e202000a2a6089824d461f21adbf20c922df82ca384ceade405cf625319492ce5e3635b6a321537c6349cbdb6cbeef47223d6b58d470b9e90905842ce7e2cb424330f126e26ce4003071ab75a16a81e86a24e919bf7f4ff7c5a68c660ff520e34b88a6685100dbd007e371269783d545fecf82468398edddc2891211264c9664ff63790675ff7ff08bdfeae871222402b6dd0ae2be310c318635c5842bdd758cd76beeaa2e770de619e37eb2538d575478cee61cd8355ba9670696cf59ce87fd13fb7e03fe5eaa0f07ff9e22c92d0bbaa6d7bb9c5acd0f82042c0d326706889877dc7148abae6ff5de483afa759c379925cb0f1d52955ae81e50a6544858c3657af47cd6cbb0e446faeade33dfd4d79409a5970af3fefddf28e6250d38f5bf8fdd20b021d228014962be44b8deac09c97e6ea0f0628b2b2819c7a3ce25de8f501081358f78ccbbb4034d297ad9f5fd19ceaa977a3053e5d4e163c73430c1e4c59c45a5e13a64155434ab9481c5d9fb36579bced7bca7f87ec35cf46d34a6d59e09633eab7f280718f3b5cf0bfd3e031c628383b6ee7c30a167028ec07c4cb8f3687c239025d9a5603166a47d9733a06f952509cf62414e377a984e4f942bde6722cbe98f2541b5a609ac71b443c46a2ea8891ecb95c4dc5aae82e58de14630ebd348a06633baf73fb3654347b2b1cfd876c02f01d760ae74ffd37b7381bc27527c4db25c8e4f3b7b6a0987201cf047c2d17b60cfd6a8144e034b2a0ec720cd64177a8e87a43de726298db34e9ec276defcd80dbb87eedbc0bde7dbf41d3ec1286b0734e4aa0dafc58df10710edc5da4f7fd8a2529d833c2155179a8aad7ee6d86f7d9a53bcf189429841badefd897252df075dfd91a239ec313f1a7b5f067494c48a158a974c24d0b5ed12a38c8d89a307578e7f7e87a01ca1d8fa518cced27ef5ac46c51236da1f1c45c7c79aabaa1d2a8516c761495a83bbc83114b9c98d97373439b65e3505c66aef1a79b8c055439310a377eaf94a6e88fe087e3d3cdd1462a3361c16fe7e3442ea6db9ac5ddd60fa5d8d8c99123ca89445cd9414604b813236958337e98028dd389461a2049c83f421a5994a8575c07e77bbf1778a11b2ee29760c2aa1a43be6eafdabc6e8ea37c36fcdc553e3e0c2347d5fa5f952ab2e28f98a89ea9360b81888e496a3df78b2cf8c0e065e9d9ff7d0eddf7202ce65dffaf7a322f517b9847911084c49db90666a11d97d803932d5ff604ddc5c9ec305a6626250a2bcc211440dcff76d09f0f3949a3fad59f75abef85c35d8e4f34920ebaf001d916b6f1bcf77e15bc2f0cc2fcd9483d114b14c8e1b3d2821a984da86a4282ec0e21f8d48a78199a44c5cced20a70d7a36c96cb573512772f6d123d697f5434c11818ff2a69938ec377ac5e46750c21e944c53d95a93f2a8350b31b3fc18e3953a3b0ade812f9e08768a2f74e4d5c5d35ff0b87dd1f9e8b2a17f38f0040e6e234ebda1bb6e6376abbe5cf3afbd8fc27c7477001c4d5e797dcb3b3a18b6e302934f9d1ebbaf42387035505798a40728aedcfdab72385e31d2113bfa2db3d7c8c1d17ed153bc44a012d8ca9870e80c81ff2bbe6f2e12a45b2cee053841345e1ba9ce41cc3fd4d18bf68645c5f1511637eb7139ed5b0e2b1cf8acd8bc835a663dd3042275122bf5423766e378aeeabbc1f4ed50b30a412c5a3efdcf5bedf341cc11302cdc95dca3c1310c8e6c9d74bf73428d75b5d223fc111f8714a7d9dccf0a7578da7f590409302aa451360badc2d272717e239a733ac3b66238983734cb23017ac6e727a110a6d1c8ea8ee8c585bb98bcb0f5d19a88a544a301381e2cce0830a203fdb1bc7cdbb980403af9f91ffbc5b04c318296ffb84a705dd25896c009cc44666c06afa3fae3516b8314bf3a64ae9a5f229fab53216ddaacd0c28d8d15aa5f3a2f4fe503521dacb03b7fcda049d2070dcf082655a1e0c83f98a439779347884c2e374fb94907f0e7e3139cd12f0d56b8e022b47771ad05a9f058c4d7774a1b10bdb7945ad28dcb62881656669671a6264a8489a213b42a2d470bc91494d50924a519fa96a2c93cc5e3c493e8ba4c1eb7f0f2572e218eadbd8a39025f7252ac9aba00454c5856b040a34691440ea4d255d13ab6843863c91f57397811ee3ec0c8e54cf7ca606ce78296f44ab2e2492ffc54b39dba2b0dac24f4467433f858f55c26e157e6df274baec016c64679983ca7c39339783d1cf06f60bbe15242c7ca71a947aa0c4786817cb1f826862f926bb236059c47fc263e8ffc11817679b2f8f34a7cedec3419fcba99207e6ba98b769e1106303865e56447accf6ee8400d4e972c26a24da35e8713924bf0283682d619004aad3daf5e40cab1b0cbee8bc9431bf87f32c8f004b21d32778334ec739bc16cf46f72f99fb3a0d3689e997b2b3d325a3213f763461a1e8f30ec8e9a791ac474de09242fb70abaf0754d1e24e7a465d30bff42f4daa7e6d97458f5e77655981976e5487cadbfd23fcc48863d33807bbe0e6b873fbc9d71e61a757b82db7385b387efdc78eb719ada3de6332817414abb266aed844af87ccbf561aaad8c13396134bc20f591ffa018dc2290eb209da7192ee94a522da27b868432215c368c10395f5fbb287142c368a0719a82c0c2e7b09420bce040adc605b0561e728d07c4fe4b73a9b3eb23a0c10c2688facf0590b00e5e3406a64c04edddfd4b6bacec8877241ff8ad0b566bccebade206d56079167dad86a8baa8d796d7db9364eb7b08b1ab358b34a8a386b1b41579cd836a70e2afb4202615f2892187cd69e99d166c082363d089c82b572b7fb545d99ae9a80a99dcfc43173bdf8708cb25abd4920ef3907eeca0bf1f60130d8fcd05096ff104cd08b571bcc860fb558b167cb55d6833b524ae0eaaa3690a924a2035d2e9d9ae9b1c3fb46beee612c0477361549cb57e49c9636ca489f94c45acf33f2c548437393627a2b373c3602c034fbf431caecd5f72229787bb2c07311c329c134435d1b2e82d75128e16c4eced3453946348566b6952a44844ac1d18272a51234fa8d8075eebfe0db57deec3f7026433c1ecb48e1bcbf22a819df64f7c0fb21e87e8d9680b473d49b448fcc2e6a01094a8b05ee54dde806d899cb90efb67e1359aa8373ca352cd80d9725343c954756b1540b1d691454c1c52211521d82f39828049b99b094729ddb24222fcadbf87fa63a4bfa62103343089ce303433e2dc85241865b4b5a7fca2176bec4a65a0136082bce09accfbae05a119f08b3fb4392a9b07fc2871214fc5abacf0b39818bf4c70599417ba261beb64a55626e4ba988042be7c355a5a1954982524814d58ac89056437a168588f8f01ed8877a549f3a430055baa782262c81924c01e156aeaa10b0647e29298fa630df690e45c13fd8ce5864126b9d0d9dbb1db9da1b6abf1c12f25e5b7e2e8800485fb7b215b27223d67699f053930251d1dcb8a313e0d0dcf6b9159a9ff71a8af91898b76ccdee1eeb838a2380184c60b53c0c4a178b9d794e97d1dd3a048aa19d446e2306d39e834248935f05784e6b35329692204631f1863f21be9897e6dc46abe23f4a9dffdb10a51776be6e742ab5beca9f45f7445c50a6ee75e9cb7e0f7565ac3a3ccf52ccae38b005084bdcf308322a61fa778f4ed87bb1f89b45da688d98aa970c1992bfabdecb61a2a0549db427febaac315298e4286d626c73437b0732bff953d003d3938ac2ca52d84fcc3045fab7ff6bc09048adc8a18c3984caec8f36dd56d4ed0bda0891ab085133d177d70f6aab5d2a6f9fa5b031d2372486e2e65926b57d16419888de89cf92c342e7f5a110ac92f939a0ac002396870e47213ad655f56f3c1d561d6784ab6b2b075b65a63a6dde3a4f4986fbf304dfa543f42e447b6f58dbba9fd892c6445127b136b17b2b66970fda0559de38c07b1c397830d01ef08071e68e0a06669fbc654c5ac83d50e1c9e9aa24351a8b938a38ac350fcebfa87d39df7bbdbef7c76d116dac8a2cf4a37584a8755d396a6c53b919d593098816de21089abbef39644aa63b5ca2e4804d67516f5a23152bb46c759526ec43a3fa8737020d01436d989146f86d938a23a26ed0efa007ce8a821bc33094d9d47d9790c1c3e0a2f58ec76ba939460c00281faf74f219584b9623e621e3ddceafeadb4935bfbda3d152a46310ea07c4fff3d129071d1133c0045d198f2a2cc693126a3a05fd8ba7268f403573f163aac40d5edc6db09d1e61af9c08aca15697aaed9623545b87498351841ead20e42dce7c31251318492e2166a854319aab4a81b764df1180985cedb2f03f4b026a0858b87147e1c07c748340c51516167434b42d582bdc61409004a665bbe708e56e444533ab4b92044c1a2a5c46df413f8964a189496ba7f7d7ecb5210173026500c6592474919e210bc8aa7329758132486825cd0df29b2c3fd6e329b279cb2da904ac58549e24015006bffdab80a667de8dffa8b6e8289d0f9380c761cb2121d084cd6ae0471308b7405493528e71ca5b267a58a7f99ed4b75f6b511a72bbdbba70c3ad36a8c6bc4c624a87862783909b8bae52f6b60e0553b4d9835782a85990df19ffdd6b63924903ab837e83eda237e777272bb860973529839451e37fccb79d6964511d93d997acc486cae0b945c30d2b41e32eb9af2058ef30b6a0871bd17c15a7a416d427bc5a32bc4ee86427559d22888c60decf844f65626284f08e82b2a54bb4fe321051f65e0914c8494e4cabcd3644fbdeaf739cb3120232e5e7a6f8774073838f36d299ec04b1333fb4e8150e1438db44b988a7ec0f4032b951c027835496599984e4b310eaec03d7605f0176150730b9d579d09fb9e965e8fb9fdc92b3604617ac10a90690091bd99acacebc6bf67264614f43a0ea7b9139381365a6103074610965f4a44ea6f49a79f946077b3c1802792213f860099d3668526494cd8f1714bb9f5cc67e06123946411fffb6b3e860bf71548c370eacdd3804ba2013d397fe86c372cda25307c80d8d207d9904793bd4fcaa938ace8ae31ad04f62047429858019ef3c273408192155be3738608867294f1244a5621c09a651bd17456a39f0e8c769de461610ecfce5541b7e4edbe841c73ca83be6c5b9fada08dd454354c03d4478b93c838149bc190591a93985258d396538d4af0ae6d2f91c474c6f9b3ca814e2d870abd83b5eb9d561662ce58d9a3e95ed2fc1533d763a5a130364c10c3e959fbea2c7359b2754c5838d3acba93fd6c899527988c0a134907a60b29c2986a4cb8d666206f418e81349adcd2047b7827250b0fdb528feae50cbe54a0994a99423e9655a928f99169f368ee23ec4414b1b3d6121144dc194068026db19d40098fcfe18132641b669a06adfcf41b88c2f5c4ac0323f116a99117feed472e4d46d127cd24b150ffd4e8057a355f4195c0e725a5bd08780e8ff322f021f9e2fadf47a30e34bcae5303e0f24fce017e94ec1a8568f3a40d47e8705197d867564442012dddca43eccd15c59ede951c29a872cbe75c9219dde2d314bb110f8198bf60904c8b9e3c5a0e60e7a951a8af3e0acb3792e4f8794be6a406fe558f7ff2d293fb56f6c2c602c14c1454ba1157807f4ce0611064d70d55b7922fb91a00d2bf12ee215d87c60cb8d7d1a5dc9421e2b7a518e9341fa1386cbae7cca253e84c01efa321d41d410411f53f5e17e411619de021f73e28142b7bf8fb553631cface137fca20e1a3134434c4bd4ee1905394429a7e07558a2c63e148efd0e85bc18d2f145d96fefaf8f58e7af3377f79c8f75ff6813b3df21a42a7de2612f63ae187423d018539f9915038b4d47f08c31d302f74539b0cec3a23d21217afdd70a17c36b0469f0044f696baea9b52baeccba31469bb212de22f2a47bab8f9fa30780866bb63e21a59791984a4237fc182e098c4f5bd4277e8abb3fdc4bdab05c5c5ae4dd82d2226a3d7074a56f921fd82f63d0e8b1b1e0c620ada202b9d11761713b29b3a2b4bf3265b442cc711fb285db55e9ed761e7073b70b743e803ff232176a7dfcbdef789998fc6b4fce1d8233bfd963e318f8ef2c40c752254039a87aa1ab2f0fda9d9f44b8de254ba516373861cdcb43a6450450e1fb99ed7e69a8c73038433be57a5daef2ba6a07bd76bbb80e7e8a652aa0f9fd0869efbcb6d30ae66c55a406969a87050ca9b5fa3a3817f3de598c3fb01998e60ab79bad2c1bd40f2722248c72e8ebbf5131fff497a932cf6ac44fc3cfa3fb47f9e89781585e6f95b0a954a8b4013f977c11f826e43710dbf626b49ce5e611d1f5a527944d45ef89cf6c4175609861b1c0d51e01fe85d18fc3441191e114ec7485700d7b03896ac26d1022528512ed18bbbea60d2453647328b78153570ad001704afa10496df0004cc32be017981277fedc0f72de23cd7f7141732503a3616851c9f36ebc0c637c43572f1899274abc1eaf9897092d53aec3508a185dd95ee06882250227a59a5e89d2ae2804c9ef37b0c4295e4f9d8cc2296a1283c2f4664e0534404ebe80d952dbf228ed5784d3dd557a2cfccea50f441ee6c949f517b5bd162d0b99e977820836837202e6c11872a24a543cb4d44df0a4ce10fe6d12af1eaea9ffe4459fc8c723c587945a99cf0bf79237136088e26bd206fc81f13f67eb8ac58a1c0488ccfb217104ddb67262bb9391ccade11e65d36180af1b32f7f2db846bf8ca98008f3b587b0bec02eb8d0da540ffd16d719a5cec0315f1861bc60948fb8b620c000a51e3903f5bc91c2fb9eb5147252e01a525824d441f4d56d3085be0707e96b5478e7f8de30c3e9e44498f8174c59b86f26e8e989750d5a74cc65c9e94b7a2a0b9c37f288a858b928076df97f7e693671f5de8d244995e5e215af5b2b9ef944c8b0f6124cc173a7bf5ecf984869cc5845afc1cfba70c22a2caf6a9bc4355017f14df57f6ebac64f5792e565b3659bcef306d9f2e917358a87669cce88ad436e52c756aee5eab7486697c1e91464aa7a73e2fe00f22d18492598a62b771dc5d1bec521c62bf93dae3b5c1aa4fc7ebc6cce9c1ecfd1722f5e7452aff2bbbe26fc2e87697f6ec5cc8690e888ba6680d90312e2025497dfcd5b39fe96ba62e42bd0741c6d53244d188c9a27d8d00e98200a5b8246d297d9928422ed3978ef851287899f9828bfa9554ba897e2dc42485f7abf69052ce4ee6ab3647e35c48d3c2ec5722098e75f85f2884364f499294c2d7db7179c92fccd6f250655b988f2aa272f38c39547a3d9ad8d6ff812e171a22123c37474962b457d0cc4061cefcfbb79bd326c2d7784f80c8d648affba8a2183c0df676116ecc4b25029c197e4098f506af0b5e3a9e7a4fdefed51f6a4516cf38c170fee0b03c6b6aaf2aa7a80aec5f7e8dd1a7ae6767ff406f4db529ccdd5b0d799ec8f8b29dee0fa935c6e10879dc778317272395d4ebe63085325bb3adf7e881ca397fd862fcdb1e4e50bc65baf4c28414d94ed9b76bb50c4e0561e862e8879f91842906867203b6ee43c03131c56945f2e164097de9062314443148c8a24d0a60c631e77e5e6dee8675b20c809752712b28b14364f6a78ffb14dfddef0c931d15a9e6034df291782e2555f1dcfffc4edbc3e5e665518b1cc6b2068979a16ae1c37b623849c443206b30d7619f8860e359b022c2d68c2a81f189590378f4fa5559cf2e825f67870039746743e7e55951587b86f8426965d3fcde1a040455f4a1d2bc19c75637d7c644e09b051e8a48defda0dc50b56207476ed25a61b9f01653fd94b122e7878a6761f021ca5db134a6dc42bbafd966f9cecb647bcbee89c226d631339700082fa7df229961346e1fe883837fea6af81b12e848004c717a4ee98dfd58b8a8500038139517db96827a0b1536a5d534949959f06f002e854257de646a4bc090c5df112e2d460ca7488694597def916393d47e972f4f87524df9188ee4152f3bd45812527ee161ab9a28c41af4f3ab81d789a9ce1f42afd4a954b90b8135f035c22b11b68e4144506ed0d01366e6dee4c7d014a4ee426187147c9b8e739e20412463d9f742a8ffbd76ac7f5a521a6bb2542708b05f168ab9c9ab389063e4d478032055182252072e791bca364e8791ee50e9c71ef9ac40b9451cff3a87b6030f49049fc1832b02f09b1db676f8dedc3396b83311192b04748fa251792e27a1f00fb5469cd8918288edeea9637fe3c1d8446d300f77a5c7891756cb493e857b726fea8e0d34a2fb29f94f7ba45666f1b492386fb8792df02037c2161c4b244874c81694b8db6bde0e52284fc913e42bce8d8854f4280e24d496aecd3b5c8bb4639ef06f5ecda1f9fd9a3d722b3f2877d965deb6e56427b6be75bb5cf705f85b087fa287045bda4a6f1716edd40bb13c66b25c4897e4605cf1b6dc773dfbbfb533030a2b11998c5f01b96a077b6f0ee7781610c99385693108769dde942e006dfc8bf148cba264a74295ec076acfbc401de9d76fb1fd723073e5b1227dff80d72b79b7357724829566f22c6e6f75b5494db57ecbb75d59f48c3622b2346a3fff92b740cae3cc7c17afac7b34cfea27270d8a7de42ba5ecd1a6041dc333bc237b1d9cd8b33b398d15f08590f890b7eaffe7f062a9eb7fc3441277706e136dbb4fa3ee5768698d520aa0b71c63472f4463b70df732796dd6a30c3626890d5b96668a13b64a2b62d10df36dd5c5cd33ee938013f8d8a634841eb4b24583376716421f319e9e48f692dcefd8f832b6595c075f5242e7fd7a972993550241122dcc8dd72b8f92c3115d8da634f9224bf384bd1cbf52e166d66a9ded8158c31395a7d93b1eb0135802e93947173e74849f02f114db1b76b9dc97b69604c23eed7c6117eed2d0119340c0c92006e5c7750e631cbbe977485fc8967ad337f5d6fafa76f391ad7d880712b8607b65fc7ac2d192e39d17cbfeaa8defe52d89d8a97ef0a3f35227f65d90a3485fd5adb348382fd4d61176de1aae4745032055f15a043f01aad36ac660f615e486f86227f7b6f5333ec4f2cd9d8766380599c39f7c6101679e937a06a39fa10a01726305b6755d04cd4583f0476110cf9da882daf4459b58e52e3b7c6d7b7f0390e08dbc76800da338cdec6b1b54ccedbc5d04354f8426af4d8f8738fd124ad3494e135965e309e476cace4a626b76589f49ce735a239aa1022ffecfd654b58b28853bf7841faf89b56646a60ae475993c82f98de45b7a6f3e227d9eee5157029492251c19bf4e0e3a2279914deb6a67a875cc1d23d2a91b5907ff2817e7f3af21afd0ae6e65d12084dc46d02ec4ec10c0c41ae1ffc0624cda817426c79adf4b2c7490b4d19069cc4e87cefcb163c1361bc0b0644c08deec9e918be155e869a421a605b8f4cdc5b9bcc5558875abb4fb684274dbab27064dec43590761b65899dca61b8556b6a17d9d0c61092a30f9ad62cd37d8486f6d33924e6ba765e6f26cac62f3ec7b553b3f6229d909d535da055ac65d82d15b3ab158925a1a4e15a1a6634e7920e01bc493e38e5329f204c847147edfd3de7794f84888cb3e0450be679506c66e4668e3acf14ae97dda8b84503c38099b343324a4d832848fff8155e76309fb8567bd814432b27b7fdb31f05b1ebd04d2632dcba1c03c464b4aec03ba56ee6a778f9d1a43a1b677304a9d797465849f8122f4dc4f061341bb0082779994cf1ad90833a6023658d77069687043983fcad4df9ea70430f182f13837b6de38091e5be33de1b1b81d8786de9c0478b6b3a92d110e9db0298f75f7acdfd2793b0a5130b4601a273d484723c4ee75ab100d3cd1a28254ce3f1cdfe1100979ad0dd00afeabf8507ef0b5b2aaad8e36b6e188f695af49311ca61ee9731858ed18b990eca999655ed02793a8e6c151cbca78c0e0bcfc74177451405c73132a38655f328bf6c4fa4631bf271214d2c350c6c9d1010e719171596516df663a03f5a21b1564b4452c16b0eca282f1d5bd7128b54af0a430115a368c6726b32b68f2afb939fe99a832723e90c648d10b4d1ced857d078395380b086f403af3169f2c8dbc5c1aa73bf2266f760db414bfa75941f2c90a778719dca098c41ff1e030a25e2ca7f9b1079120768d48f65c5fcd5751911e1561faaf36da23f5b1fff0d7e981c0e1147c9ca460a3463ed5df914b80c434f281d9f966c2d76aa276bcd4dfb6001ed56fa59468e783e90f899e02c218f4562f2641755ea216678e70b984df786a5d75864d5f674063dcc25de0880ec3465e8c17e6f33632c46fda679696bcb463cab58d0170f9fb92e8a783127047f7004e69d071bff2ff22ba652858c1b42497adcc9490597aeb556f608537006e66b113d940510eb6a101b2ce96a314be70533f714dc2db2b6f527469662a1a4cc9d5258b0870b407b5d5fb729a7094add23345a1027df63a9d59df8a111fdeed1ec981659a68e423dca3ad400495d0a2e66954b1cd649ea26791818c417d75688b0db85ef14d4261f3e057ac7a3c50fe66cd82e3c60fd1b772ef1f28bc1df8b119e9578de511e090974888706f9320fcd9910f2e81172f459278c7b383ed39149eabc512ff6e8a1313d3f7840b5e006248d38a26511931bd11ebf43f36921cd4aa9a4a1a602897f14595eab90d039e2cad8efff99efed0805aa6fc12afe524381057798063a931dff46cbba0101ff963f9f802475439b03b00bc886317ee804625e4c06a1f66ae60cf55dbc15eef196575d9aa9b003770b9608378daadbf1794ee90ae1225e0cb132acf797d8f9723bb5a184e35ad14f9df930b258e3d4c6b6f8495e76abbdc8a69410c68dfcdc392cecadb107175cdebdc6d42fb50528571877eb2c40ca02400ac0cc1f5d8ec638fe096ad8e4ebf6a482e0cb01a811860742a2fbee868abf8ff1d16f2e6af7442f670609c9a300633c200b19422f92ada1e3d35e1b35e314631810a5a2af0c5b384ae565a0082dc8c475b6bf4d295d3a273d09f0337dc01578e2af539a9c74c9355d56fbc625386298a5a01e7b8c0403828166409c02413598845aa5733211a32f4869d704b04e80a0675fdcbca1f06d7d0ff5eb2aad78b48b16abafaf78c3aaff3588928b086fdb7aa78b08ebadf1dd7a7f5c6d66eab96228c10de5452e92220eddce8b3ae84ba334d5687c61bd83020038d7db83d08a34c7f0d374bd9f7fa09b85e67ab3c9ef9fa9129ae55f647db0b5a0d8268a8f184d281bef83fb5ba2ea8bfa6f0e69e273e017a76bf68da6f871c726ee52f148c6d77eff8702b1d48cec5ad2a5c395c108d04461b73e097952526d2c46bf2c1ff3137e6af4cb87fee1e2260108c2ac900f9611d86c700bc5e05acaf5c0e1e2b4929a6662c5373cd68a5bab1ef8466acc3ba8c89047df58c18abcc4c558525a2b90bb980ee45ac5b4094ee155b93d48ac23ec76443f0bfb101d521df3dcb6c94e78697bb87983b45bcbab8e95a6a3eace5997ba6073a7ef3bbf1e115cfa1aaebcfdf1c92fc651a062f2220b1a04f3128d6ff4d8c0ad1a6c4301666d0e279cbae47fd33bfd0aa2bcdbc0087b75be207dcc4b5070dcf15dc28635ce2fd4e32c62563e2bb8ac0c082da2cd8f24d143161e2dc61990799c454a3cc35cd293675846ac8506a1b48e4636a3ebfed8d5992ae05dab3a7dab33508497d53c9a15498f7a91ee3cee9fe9931395e28cfe3f566bc93cb9265846901d6b99f0487ffb3514436effab9c5faf6e37e1959ff7c66c22c0dc850b8810f8de3a946cb0fb3cb59e34aa06503343ee7d7ef448e91e85b64b160381ecec669f817a01b14cb91e75230a41fe58ab166bbe66675ae0131cfd5f2565bc2dd6cac2558c2d54cd5f8a5e7c3573f625265adae23dbf079d1df2356e50419fd9faf72f4efdb51a44b8ef1143611a0ed92ac9d25e2dd1ef838f8d8c2fa5a9c950ac6a92a777f6a9760d9968d47cb4996499758f7227d2e2f82ebd00c0462e2b5ef40248ed2e48b44126c42249721643e6225c866654c00b88f96e3bce72e17aa485602b7e566e39ead55084aff15b960f5e2fa2885fe762dbe9089fd59201132f73547bfead25193f50fe654e3aaf52722d3e4e66f980e481cb56e223300ada2f692f3581a26276746fd1f864352031ff090378aba2a87e57ab2b3a69ab65f74f207fb57042fa3be1920c380781a4b3433cb9e216eb82a307243a822ed8018bbc054cb7070cefa04d1de7102d280932c584ddf21ca263f0dd446d8a90db063a0208e0d41ef643ea6f000dcbc073903fac49c8b2830238857401013e4a7974c42ad1d651b739fa5e59bef34ad39d3bad94bb012a04e3e229cbb629c5310cd6a390eb71d75b7788e99612c616108584b6695820873e99769791e2470fba1410d597f7c19e6af7a0ea292869e0c6bf371fa9086224bd7450e29099eaef0bced913c677589bca3b00b1aea2b2692629ac54d9b1de7a4584e278b663bd811debbe45f56e65f5b716480b86046bb36718f9af3e062affc0c266d7990657cc0dece995b1d6fa9f346089323d005eee32050df46836be27ae52101e3948e39b4616f94192536304cfbd87d49f86bfe11c632f7c9d0d74c6472ab42dda1107aca22e10968cde61396f95952b4630829504b35fe6e22c5840657eba7952fe3c3dc9245075f678c868d6af8a5608a278654855ce9f62f2f79911289bb9a07b859864c2738e7bc000a3055d40cdaa6e95870e0688eb2c9f02a6215d7aa4095723825d11121feab6ba2319c30a9010af22884476c9f51dfb7aabeb08824b1389e6d9da5ffcbf877697d6a682104758c632b44a1c8e9ea53a91812594678897dc5da93ff17571a8480ef5460408253bf13428d722b7583637e1e9a21abb95f06f2387a7d065d66fbe52344ba757decd735c4f689c81109269afa778a4e38bfbe9d24382c726483979123868dc1af6377c565672301be7cface535352a3c07c1eb531f31100f423703011f9f042aa5cea760f35e68f1300103ee32501f20136abe8d608ce9ceabd81449c52d8d1134fc72534982b3d2a798bc4f81cc9262ed7d90e41184c705526c1f942d6b8946457fcdac418d30913aba5eb82724c7f42fcff923b60544bd873cc4762c20f7748670fb41ccf8be35848a3ae9c8c6318d1b63413a697cedf1d2a936804fe548333efabc21b95d8ea1227c54ba966d63c6151ecde7f274e0479be8556a500be5a55f8d4e5dc914e7da708af21768486b0701c6d37a468e6b4680c89def51c7a00d5311b5963eb53660786fc74911a630a0263714017d070d355daa8e328b112196b0e1fcf552a6ba0a65056b8bc57d8815cb84266f9af6174cdf16e5bb87548b0fe01e21c57160ad6b084908f9ac71655c5d1c3c7cb72f22180bd60aaa5b1fccf8362ac1d5de057151b85a02f86f7c2e9c7b2312ae73d4cf3929d2c0880b813cd15bd36c2d53b6500d1877dc1b83759f8144cd8a6d5cdfd1ad670fd82ce2987662c30789939b027e541291d9dbf22d991499859472afa1ab12d82ad8701a5dc09c73cc375b756c8cfdef9ffacbb7e17f00027393a574f1fe0b8b97e2c38ba0f297bb61a908cf706094f40d50d2edd949d7d6109783c8b2a2f150a215e740914ae6dd85173b55a3c1dfd6c2481eda702ddeba1e0d2f0967268dfbd481ca323eeb0b7a23252a7967c1e00322308332e75e8805a653906a870a0fd50a06bdeed452b3494f58b31a29ee82be3900c3e00232b6907862e90df4b3b44a815795638cbad12e6bbf355641045fd5b80e0a3a9265cd4da7c9e9bc5d56964d193fb02a022fac2137adf399014adbe08539d3d6d08c5afc52b7fbe488fe88dd71a9c0114816139cd66b568d418842d068c9e0b6677ee7485b358ed057cce75f26e9f95567d3c64e59e5ea5669681943a4c8a4b409975c2cef2b2f6b51047faacaca5d815395a6377512c1f604b45e9b46f11be814acf2a5c168a0bc4e375a19e973a276d3e1ae4448f734e33f6ec2d9241bc2ec2991d47f4cc0dd2635119ca9d5b0251ad0012456662bee21fa9ffe580e53e5574dd678dc6966f61f16a6cb907d651a8f597f66d36f39df907d7f8e95fc4a266ba8e80f535b0c44ff5b08f9537d7aa0a4a472e815bba44fc20d2b4930201e7458bff3f93a5c9b98f5e282caa2147cc3f68e3856ef6e90cc8b725f11450b7057a644433fb4aba427f087b282a1d3bd06bae19537409b1005da80a7aff989601901ae424d65f53721c857d2044ade59ba8a0ec02089fc41b9c6268d07e5abce147eb05047d51b1dfa57f6204047508537f5d9398bf8bb259e7e3d7920909bb08aeb2096d5abf17bac54941b2fd2b818846535963cab7889a7f62499a0120038ee88cf2dc632184ed8190649a58766620504bd3e3113b0e340acd1edad6db329b447726dadc926f45ff71caa378d779534dcf8642042b1bf4096bcbe3d7faa4b8017b9d909623b405bcf4b36ad4867f0d32ad69e249bba7b91d532d7a8d0fcd8e7ce89df2c8459396013be9bc122ae6c0e13af8642ffbcc4f1c06b5daa21c0a37e69ddd3d643defdefea3b73199ff8f6b19091a2405707616eb09ca51059249f0c36a10154a348a542a6415b735d0503414fe108afa1eb4dc7f53698d979cdf1f086f15eac26a0003065f204fe4a5e4aee46e6ec445f4a03cb29292da53d8276c30feda5b35ac5d741de38a899fe08765558f48bdbe2009bb8163b6a69cea3356b2a99893db18432e486e8877b66bb4500053d9d211b8a6c46c7eadccd47232d9d49ca8ae1bb1884e357376263d566fd6c4af2d16225ab2e67fb1ec3305f2cad587963979c4322a20319fca4cfdf7e99b8098f0f900770ea37b7424c00665a9a6e96e5173664e10fe1d6121a505b77c24f6b840c94cda52e075b412625608d5f057780ff458148e7ef89c4ad74e9559548be0dcd71887af15c5fa850cb75665f44ad9541a0b251a17206627309ed14684fe74e20a519e82f516062ceb473bf4a31cc8cc7512a028651d59eb6a829a90ee4b60c68af45bb985f07c0ee1fac514f09f94fd4aaf69008df5d5c7a0525f795666fc7e8510f786ceb9126b1fa2605feb3707ce000d51d302ea70efd378663e51825f55dab579501a89dde521d83db57e68b8e8fb37f28d6e9d28857247c9c27a75f95b1960895678d5257c09dd0396d44d9ffabb99b4a5e81370a2a816cd128a3752a871c8c0644afa23e93ee2625c48cca701e0a0097890fbdfa1865380210d5e6dc1d54af1ec2aea6264ab0a72dd781c9c34f001cc29440f183aac38962b5cf8de82b7300fd01f64bcbc37a297c589d57450eb296382a8daee386715ffea05ef1a4ce296d88bcb044cc7b1ee31d5fd55495246be07ed4a2561d2fbbaa98958cfdcb20d40dab2a4b8a6dc5f404f095c385c69eae9094d37692afb0160362619ce4f3e71e52beccc873129bd08252f865c3e989c6a19d3b344027fb188c2ad0ada9f5b4ad61cda1bceeadd3636b0899438a867aeaa27e4bf85ab31d94b03e480a7fa1e2b231d629347153459eb3f2e509eb469eae6b8101a08ce9c9c37548127b7049f4cafec7e63852b839606c01c6d83854601c70381c7fed209820c94588e4f8394b2a0269886fc0ef0e5f94f7541ff717964d61cb5fb436b1ffff501576936f6a5425441b15c9319b1f554db81a67ff9ea4e282a939ba6f904cc055c3f1d7afee2bae23ce27ce3b1f421aa29a4c62786264007d8993a0b509bbda074f4f6bc9e299577d92253dd388c24894bb42b398144f85b1cfb1f701f5f5ba3fc788fe6148d38c4e22005390d49d8ff0969fe030914dde8d6e85342d0db176622af01ec91a165a54b9312dfc2187134281ecac520a9a7fe0435fa919e2578f35003f1bb534aa76be895fcb9a0f19532a4b69b4a8e8396f42e1d4299d848b5b93cb7f10be490fcea1c75d139b9458501f33daca440fa4cd6538b9e4827616c4e4ccb48ae08e1d9625d56e0baa491dd9939a85232a51e443bbe6a783e05acc005d2646030cb5e31dbb0119ef8d9706f4b0837ec8501e6bc0873fd02386ebc1437e9c811e69b80fee41a4de70fb05f9eb8c200df287ef5d5ff8edc3231aa0100177ad6824bbf5e7fa331700da0f04057b4ef1851b8b00c2a36d3e5a34c2113c69413da67bee107f1845a77bdf0b4a02715c168acf7341da37eb046e5fcffebfb3aebb0ac93bc228752f335f676f171d763339b62392c6aeaddf0250eb34661b9806602f7fd7bf475011c7f6411e51d10f1b55fa3a7af43b9c1377d95bc5f53bd522624572fa5787cf82e37a53a144e575e4d06540c163d1f0ba5d016d289a94ee1137618d4a9aaaae0b1c5d8342962c943effe1d55ba15c4901a46121c36e0613de3787600bf66e098c98609155f9b7dfd754ca2dea36a9bc4efacabab7c8e2f7efd09e1dbf1390dc640266fbe2e4ea808bb059a6a0fbb717cee91b730db871b582f02dbbfd1121439700743c2ae3146f4455707bcfbb274e3d957b436e4b2bf3fa57d84e3f704230f67dcd24fde5dfff5163b57b78650c046a38c810a93bb4a2f5d5edf9cecb561b7e18a0c9579d7301262fe28f7308b67384c29ef40aa36d135833d88fe042e554556225f4fa076205aaa46354490d3f1bc66da1c76fb4e19a3a8aa902ed136b25bdef756891ad4f313e5d8b94dfc056283772ef462ce339d9fe1a9a4e5bdef57434ecb084a19c1e50d827b435c03f317032a040441fc4adc4665969370ebcb2f855c2a447be2414e94bd1d19fd99096209edda74e9507948d47d1f9c05959ed06086678df738dd7608951e9005d64c550e7d0e1e681bb63c7f07220924639a1ed330242b745056c6dcd59fc747ead179e723230a1a7d70f45d0cdefe726175a9611c91927f67d36e69e7fe38d7634716245c965b7552a99e3831018071046944b617c19cbe8046629ee276745e96236b9584d440986c06b2c115e34b0d61da8fd2b712aa2d1dbdb65713f335bf4c9cbe636e50e6c2d3f4444299d1f171b0476aa9110b83916deb7bfa97acf20ca4d9b19a9badd9ea7631fac3c01d845d0c5d39be40b92e9d0bc288d791a6aadb192c52327921d60b218ae4b8a2b31b351dfbe370b559a41fa9d4234871effbf333776a2be838ba2b52871a27c817589abb98b1225e423efffb8530367c609e0133051d41b6ce54cab187f8945056c2da2673a5c242f5645f97fa903fbf239ba114447ee7d447b3f9f09c5d51603f6963c7bd0ed3e684580b9010deaedd1858cc7c432a0ef64f24d977e5c526f59f5cc7f8298d0f691bd78ab119771e2c6c9ad106a55f7c7e15b3094c48c73e881394ae3cb21308fdf7734597d3d34e945091f4596f81a2c42281427b348bea7d21aa3f0acb7055ffca08b620e8c263f302115a72e2b83f24411cdc3e05352a691ab70869c3bc3e737eb76ae7198e2d787ab64e7b9e671828a0b98c1fabcfb35f618fcd8b5262b70bb8c3b12651e1c399c7cc89cc53387618a6a77fcb50577c4fb858303a0fd5a64c8b343b0dea70ad0b05d25bb3b9a4b25bc6882765fa3e8fc9a8987dfe2949b711da9d58f52dda255791f78e57523b71b010b75807ffe770d4933e172aa70e054741df1ff33115a13238106f6890bfeebc43fa4066adacc05a6b5f3482686e85da43fa4a3fad24667656ef2a769d6a15251454521a7258bf365828ffe05925527de959af5fc5b0317cbc776afbb37fe4912f01ec7162aa40dce8883e3f21bd9fa64e89ca1f06a368b463fcc1741ced423b18c6b363178b3f171d6bb23065876162ed4d539bed8e70b960f9f0a0aadaaaf337de1c02067394ed5d6f983e29f389ccc8e9bd9b5353afac67496de5390b26cd8cc695329588c358a64d51643815f8d22ac1c1d412ae0d0305ec512dcd136786d3bc92ac6259c9597b51fa8521152c0c31bf5a2a1f409ebe4654f46496504851a432e3cb8b298d08dc4f24c0dc4b3f05c6680d18b089945c6f74a361280201aa5f807fcd29545d87615a7ace1a682548e984f9cf699b5cc4b89536d58783a1922f467c218fd9f183dfade91b9097d539556718fcf84960ffff98f47a56bb926d899fd730940cfc480c64915ca2bfff391f03a4df542fc72a5e80bf19f3c233eb89219eacde2a32cb19652fe5451462880fc9876e095cb629decfbae8ff9e858799b6efc91606649ee76da3eb41232e462a3a8ca23a333b782154ebc48085c0ca79f0ff5cad072238f76a8c48b19ff27acdc5cc0eb088983dca8b0e46f5e8d4d5f52b2b460794aaf1f173a0918a1c1ecc37d32253de02237a37306d07a092454a2e13a756e5ec20f810881bbb7eb33f8a3a5f2bd72a4baf08d2849cb42bd13542b629ab08e7499f3016b02d244a2edccf9d2c2300d2c0bbde98b09d7ed43a70f142bd8c45373730268ac14f7bb54b975bc3a41b5a3537b37c76576af7424082c20cf4ae6dbddc5475756d4548022bb2a3fbbacd30a09b7d519db332e61e85eeadcbd96d69be98d9ce601926145e2dd1bf33f8890aa96d0105e750f14cd5aa3722bc70bc09edd15c411f084ca1a80aef075fea31ccb9e23d7cfb5756c096ce2c93e54be588eebc52d307ada914c86aff4d42500dd108f7befea70937958359ebcd98c088d0194f20eeedf7bde41e26aaefdbe55a35f0dd779d398740b18f6c1968609ba943fa107cc8a3ad8ad683f20fbf92d9204c2ca508b6af097aa4c06bb334b3f29e3b7a3f0230a8d5e066874c48070c09ffa665b653c81b852edce2e6ca1c6759f62f4e74eeea379068215b1e4f3683ed2ef814e7d5bd1d1dd8932cc60cb6776ff6db3bbdc60f21f7e21f4329847340024319085199704a8e585b846929daaa9debfbee01b6d4eb9875653e756216b62ae497b7b9bf129abf532ff97754492423913ad8d0a3d4f7a77c2a482e4c9b9ac9d64f9652c4f27418b06dc3399dd4e754f5be1c27ad28dda1215734d52b666cfc8b73728bb2dd028f2b1eaf7f85264cdc29285554d46b6be5fcddba3941903598efc7f3b4029a4fa4bf59da69a12ff9bf6ae33a51f3ed42204a8aea2fd22c5e34b753bd79922dca704cd74968b29fc687c49d7d7bae717648580dafd5234a54e2267d510d63038ad0779433abc82b60de6dd1602022e0c047f9c738bade890df2a48eca0541f0e263aed64e38a68c50f4e0aecc4f46d2eb1359831f43996871005ab61b0ddf01d9bcefaf136e3a4d091e80a82890a439fd51ec847f3046cfc59194936981a2f903e53878cfcb61b4c5472025caaf35135c4d17b946af6d764e5c802cf030a21d518270fa3fd3e8ce4e16235638319ffdd266280414d3e6608bea495512f115a8ed5025b32091bcca81aa78566c3bfa60d914bd69cf94fc30744e1ca2ca0d2178214397a7392e683227adf8aac0a7d9e1a49d54fecc142d09d65da1164423cd743dae2fc7e3c4bfa9e7b46a456c3884b536b13df60862055959b438854e9b3cb28b3892aa67ce546f56f6cdb203d6c30f35f32a1083042cc9a5d01d3b9a26ed8734707c63e78113256d4af1e6aaef8bd340fd6792b3a44cc79d8fc11bbe3d0bc9024a5df9cbdf8e2a2f8722ef2f0d87d02c918e32fd04d7cddd015d0f541f912b2c723c7987d628e3131b9fdae83e3ade9d6001cecaa360137ae5418534adf965126106aefadca79493bc6d4238f3e0afc21048e65cbfb63cc91492b63c13760031da158a602b739cc426922a07e2cfb49db9d76f70b5ec64ba535319a28bf634ff281f2abb63b33e40b039a2aaa6f5b409811710ed13a2a4cca92e97c2e36f3c8b1d40641dda3768759bd166baa695fa5d45353906261921b6a4e9b8ece17d48b48acfb7568d5e1c0e7f189e4e505d0a9c2d7da89aa56d82d11ea0c45eb57d390efa54da414d95b2629030d07cd06e506270fe38730e1af0737993c084469017d8c8f11e288d19d809c12e4a86a8a83282d287ff22821a744b97ff2281f03446991e163bc0c21274688721f25e4b85e3d5013ca0d25e026933709392721a7050022529dffbc3eae0f11cb99d5d149c999714e4aceeca4ac21ad4f4aceccc40400a41e3f272dd676ceece39aed19a8a03f27cff1fc0dbc3fefcf99d591c9fb736e5c54a2c38bd00474d349ab8e3e2e7cd2fa9c94805e9e32ec7f5cd5fbb86ae767fdb83c21fe80739b809aa904f4b07b2626ff0183c0dbd3dade8f27adedef293131e966e8428f77556efd97eeec224081c06982014e13093876db7e08c4218406513069f0e31af51e39b3911313016a673b036287b9eff7e026d06fa1471186f620709ae8579f03c71c72c0cef6aeaf813edc5e2050a31e08f4d1c33481be730f04bac93e8a0fd4047abac711ec1ea0f0a475d23240f971501594d249a786b1562bb5b69ba1e1b8f1011b5bcbc00f2e630d1cf034b086169c13bf0527ce165b615ff4549e503b523c645a2c55cc0ca641e08d3f88d0c61bc61863fa8dba9f1dfd6cd8b458bc2e7c030480009f8089f1e9c471f69583921a5cac2585c1428f73f6015a560dd600baccd25752175555256609a6c3c145170acc115582961346cba9523dda8382bfa5e449bea5e449de87929024c4316b8a0f9e90464da94b1cb10169220943970b28a815bf00e60a9dc194511fd31aecfa2301367e1a3c600948026af0c5bd01faf6f18fa20ddadacb59a16925e15263c136006cfbb4aca30ffc9765971f831ba0dfd56f07da05e727b883e700ef71f82e767e7e384471c0f7f877f81e87344c95ce0fb1e7177240dc9c70a41f041d62dba7f63f10e5222f9c2e76b3c485e27fa04e1cc01efa491563ad4975345b2c2ea2f1a2302ed65782dec2192e561a88e4e7df1868eb5e2ac4d4b690b6d97befbd97eb6ec7d27877bc30639c47368ffa0addf81e7898c01469f775abd036fbf7de9cb3df8b3b1b2172c697b335adeee6099a1bd64decc58daa84a4b4a494b24a55cc6a52588bcef8d33ae71431d63a14faef3aef68a15dd3afb758491085690ce1985f00821380601a8165e26422a14a4c5ace9216724233b1c4867105d839278d3a9a26ff1ab33477a5301fd0c05ca174758379450d6999045d520d0208d0bdc3dc35fce8f0db61dbb64dd3ca52fb8e706314d672097919b199d11a7ac3b55aaccbe270e6b81825631cc7711ca73b1c0bd71497010aba35e3b3e4fd0a1266a9a34484c4200719d4e006f3a6e80a0e5486ffddc176561de14a592f38213963255992a54729a5b4721cd7e1502eba401a64ca8062aab8102a8040b322a2f23a413a17e5226e878b32a5824e810e2942af13f3b28604192f900ec7711cd779bab25931656461d31f73a0f4f33a1d47e4c6488e94ad99c908f03085a851e36f678be55d04040846406efb35701c08826004e4c621037e8ad5a7e8efaf5bb36400eec5199f167274cf799fdf5a6bada6e18f093dce560e1f39b8ef3e477eeffd6b4bf7dcb77828340c7df0421ae18c8f05250c333a120fdccfddd285e134f99768140f0c3100164f6c2f6731f60f732c18c76acaebde129e58256b8a3ce9a8a8baa3c654562d2b4cfda1f278a88aaabc88cb5ce6377ea31181aa2ae932cac3651eb23ea14eb1a198358a2972fd589fd8fef5077344aa43a8ad5d7b30abaa2631eb11b3ae60d653256749dbfef5c545b8eaa092550615055fa95d069e44c75140b5f8a81838be023f62fb531975512dc4e814f4097a05e5027d02858246e1356e052d69385d2cf154b2929e124fec038486bac99fc26862354de8260d253fd0dacf5980ed1c0fbd6dffe7b1374e09fd6150ed2d1c6768df437701648ffae6049b8b8d22a46a5b0c52d8ae85391359c41936907068a9414348cda4a248e9a66d1a4829f52d05aca3140434a083851e4331dcdf53ea2dec71bfeba828528a6f112314c1d8e5f3f49c2a3d4e6ffa5eebf1fd65a160f72e3018ecf3eccf92530ee6b2cd6a0a7d2fe3aee48c68c56aadb56a9aa669da562bca29aa8e264aa3df49828ecdd2ddfeb5667b1228446f4f5dc645352eca5cac317f7ffb2e535fdacd5489b94cacc22a118d344bb344ab06e6484362de3047958a99e2ff7a050932814c2037dc208a206862a2a1a68a3f8a129aa628f843d7a794526b63316b6134ad0f0a1a3c318f8d27e691798c78669e231f1d9fd407b5fd3fe4e7e5c3837a1c8f7240df9ee6a0360eca537d5b7c369fcdb73b94221ae5a2eec50c3c96c70b3f1b2268fae367b3bd1817573601889988619acc4ca698266098265a4c91f627a71f4f8af8d1a40b3f9a54b1dd44b6fd4d5e439823979a62b23279c214ddf73791c234f9c13469628af28f264ed8262e1324fc68e2030e2a364b1ae705dbff4b85e3b7da41e806fb0b6340fa8ae084233e627bf8d5e093c1f6f76ce84a0b95e1c1c2b0fd9b85de1534f4a410c3f6f7ba70f1aad8de149e1550bcf91d6980efcf0b53c5516ca0bbd05b79311ecbc6b80803a0301ed01c5018a581fe0174c4037c33447bc4094939ecd14bceb3380956544da9f54522cd98218affa190465942d7d38b8beeaf8dd038a494d2cd5acb41a138dfecc5cc9d4d3aa79d36142a5d740b861626b42a17fd291d3910daf5b56aadb5d6e745e2a2b2956108da43f7a29ca29ca29ca29ca29ca29ca2eeac8e2cc633222efa7d5d140eb445b97884deb1fdefebc25cf4aaa38b7247bd2eca526aadb512b8a83acaef9fc13d537cb6f655ed217687a7d0d176c453e8565f462626060686078f3d86eccb1e43beddec31644f568745dd59278a236821768422ae978831c6335c6c31c0453422907215b167fa2505c9cc92d6d21479d281da11c35a51183b31d63a149a739248a44d491289a46fc39107ee0ad23e7e521d4dd3dcbe6fb84862f1a05e361252c35b8723fded35d8013920d5a15eeee98dcd729fc11b34b8af1f04382e14e283428f7346c3f175a024311749486a5cd426d0f3f33aeee72ce5d7e094614d3820adff8123de37b463ccb06f0804ada5204030576ce8ef14bc7b7e0e98566c1af6e0c8307b592224792f11ceccf38e12482afee0a24b95e971ca6c8880f0ed3d1fddf094ca01700353a5be05353065d4efeae763fbeaf9ab8b8b3fb88dfc39ee739f037f7e77fa6d6f41971f5cacf97e8efcf825c0fd7d1ff2e3f7e13e17e2e8546147cb344d3eb6dffeb7b0468c1568ed871a1698a595a952574853a5b2d0a82995075aa358905b137425a3a48d1b42eca0253d511d305666c36a18d8daad29cb8b1e27acc7e94d21b0b1010fc8c4f0f8d65a653656b90a196b2384c67adb2c673963d036960a2412c9dd85e888f8bcea4c3bd519d55e341e9a4a43693bb418ab72d59cee2a954ad3b490ad5e68364ba312ed43abd0c62852e8b1ce3cc6c51f5d556366c9c72cf180aedcf79f59cc6609ffac2927afb39ae20fdee8d155db572f57b94823d9a3ab3655cce603dc78406578137556675596b56a6dc8adb5b6d63a9b611cabf9bca6b0aa4feb3b4da0c163b5c6134339a25168882201eef1b7708f431c3ee8e3c8cf85610b57c99a426715d4c87ae3e291598dd5942adb95ee69d28c4009a26d58616eadfdaf2f17615f31b63536532a0cac445cacf5e522fbdadbfaaa235deb5719176b7ded5ac7fae335d61fbbd6af3f5c74c349c319b8d0630888aa7251ac35a664d359b55fe95720e80567d02d5019f52916b3e4148ba9521fc5067a0b451a3a42fed3c243a6362725120a5dabd1ab493d7e3ed76a96d26dbb45c89255aa6256324260afd60c4d59627c5557255e554d71ea9554aafdad57207284094749e804c645f64f5427650e9fe760ac7528f4e21f862872bacda393c5c9cd53611d7a513543af759ea743fddfe17e0ecb93b29c251b7a70d3e74b40949692171e10a5c5e3f9214cdd0b9093e29435e5c58b07859c140afe170f0a3d8c0f859c124688821f14725a2efcf94fc859bd08392c8eac64b9087f5a9c998b80b881b77e7f8eac8e8e789f0e2424f803bae9d3aaa3138c750bf438f44fc82ea475774272218b7f40cdd4013dfaf9bc0683c09beb7efc60faf9a8521fbe61e712709a3cdda640ec3037f71f0804e5c83c25e034cdaf8eb5e3388e9694d092b7cf95d09292125a5242cbd25a6a0052fb891362bb4e1c284949178d249f7392a4aa3e252911b3f4bd6eb12a2d434dca96461d81be82def3393e5ff23940ef791a5cac94083247c97f3e87e7415ff29f6ff13ce867c97fde8792ffe0f0f120d38b4f8847ee4330c4d63f8a9be44958daf7fcaed3d93409874342a239ee39a007f6f8007143bb607065aad4a7e00d933455ea7f40969a52df03d2c0c145a418f6c2bdf572dde53a2ef438613e03032e15a85b8bb6d9f75a2bcca5e47ed00a7e36100454d398400010080402814020100804e270374d36832ff9b90767c74d18a4260ba2b4e0efbe0b716c342e8899f00f61f2a0f4005ab354b235b947dbb2287cf516a2ff62caa2e8acd4fab62b509bb5722cca454f15347ed488b2c4f64751cd927d502b6fb199e2bfc56c6b6e31dbaa5b2cd472d1a975816220e805825c458c8034e1cf6011b40b130e41b09a42c504c16669ce19a8c86681e6f6088a6db7331606a05605b5ba28b4e6df2db1fd4f5a36ec54327441fbc740798c1d2eda40aded20508b00a5758e0b9898a1fe56d4fce957bd86a3871b70ab8df5b7b00bfe943ea594fa8d01ca93d609eca475e23a1172f23a217262e4c4e664b6fd4f62273527371663ad43a17f71de15c6ffd486a39d7160f468bf76c0ce36ce117a1ca0b00443984e1ec69fbcff8ceaa007e7883364a638698f77d5027a180f23c401e341f587308db7b5fdc7bbbaaecba2e1f89403a32df716ec809d7573fb3611a070208e76b6f3490b7f08c491030738d8812974d27251ddfe0568cd920ddb1f0688d202e34dde24c41175e4bdbf8d950c11738b99450d56cc09961d628358209e17ab21cbe491add59a5dc8e5db363486eebddad7000f97a0b5b70d085d4b49f9317ef17222c0df04f8e54432e1d0959829fede833d7a49f7e63fb4b0c60f2e5609780be6caf6d65374650b4354460c6669fb2d7cc12e5d68a68a46052b6cffb155e38756fd190800323b973af272545ebb6d1bc6dfb66df8dbb68c72d155d6510d3253fca9604da10ec14166e97ea5a24e21a525022128c5ad96b5242ab096eb38db699bb5b6eba2d038aacd17e34befbdd6dadb5d6c7397bb6b73e072ce5dbe39e7cdc4e294528a3b8e3344062680c1aee69c18b758b0d7614c29b6d6628c31f6c21ac4ce69b427234e16091828c17970d7c15e354de3b8d5115c0f8cd0145da54a1855cccccb457f5022dec49ee7799e0c327a9c36b0cd9144304736d3a6f376d33e634a53b354999834b505f71cd0036ff87e7e1f0a55ef47187b0c8558950971091e68aaa6746827c4e1a26e819e2e76dca6ef9c4ea7d3e9d45abf8eeed0144d753a60f660b11a9991ed88cd548971dc3b42c9134a9ed0a1e9d09496d04d4f692dcc5a3bb3301bb33556668dbcb868ded3f6b736f6c6d239e79c53026ef2be9fdfbda9c3e134c3d4fdc76c162ccc53fc7512babeb5d6daef28d8b1a0a5a60eb446b044d8fed689a9007f3bc5cc76ebb240a6d8a3f31d78c2a68e9b3ed0fb214c9d0776408baa29282df684f21d10a5a56351210afd2f5c6d7f1a76a12dbdd0aa3e17ace4815d75d4bdfff7faea0c5de8eaa95c740bb3315be3e2e7da9feb0b32fdf110d937051915db1f0056e84db6fdad14b31484de194657f20d5db1c21c7df30833534c0b06cb85ed6fb3406568bfb9eae80b3f8c6ff86daecf356df75b8b6e1abac95a6bcb8ab1c6e50d41cd74c11de6be5d87bbee86d8c22cec1281d194d9f50dba58b589c98ccc8ee848a1c8171ed945784e3a479e9a19e62958f66ddbb44e123469e45ffe108ce6b123a4bde81bcff6e6a7c9349515847d45155998a25a65a80cff6945ac6ab1bd134fec59a2ef049722c8a682e541a68abb2b2643572a8b2513aba35065d1151bbecaba5eb56df3d4a4ac5ac51c91ea1566897b26d868b1fd8aed2977cd12fd589039d260e0294fdd70832882602c154bc552acdaaa3fec0be13b6689bea726cd2cdab572587fdc1f2b6b7b8a9461a93aaaacca625596a7be5aa45ec1aaa36ac45334229870b3fdc8ae456aac8ebe59e2b28c4644dda266a1195163d3668b600a4191a0465022be2f6443a88aaf46d6b9c90e72ce39e79c73ceb9c3c91d0e116d6fe771f9c339ff8733cd39674ec61ed771385ce6703854bc84aa68b5ba1711184dac4646f2859cf3e7e5cffba1f1672b481a509e95c7b562fd68cdd0bc3c443c46b6ffb4165b4bb7f9c5eaa80bbfd8f605c9f8db353117fd936d5fec93c5e8078b930d3fd754f95cb3f4d1eafa73755d67adb5f613c2fa5c2ef2c8b8e89ed6e7f2989075d4bd02b09aa592c9699ba0f66892fa62ab59da8e64c16de8e1aba6b867e5a2738fc3fb727b177ea92ed7eb72d1c16ff61df96e3824f8c56a8abff7d15d4d1add1cc788a9e2ffbd3e2131cfcae5d1ba5745b1dfcc67933055dc23bb9bd3c9bf3be2a26ee6a2f1bb293a2774afee099d14ba5757853d8d018374ff3fce3977d8e3c1b8e3623534a04a8a785467d571ad583f5a3334b2d88b8891bc5abdf22bbfea48ebfcd2f9a5a7ca1c6526668a7f5ee97b6fce39e75a3f4abb17651de5150056b3540ab1f46aabf049a3f62af59aa5922c854ed95175605cec94dbdfca5c8492af6ce7d9ce2f700bfdb171d19857fbc645b2db85192dc020863912ef8cae30409cdd99f6ec831fe426661ec2cc2bba92012ac3437bccabebe91dc66c6777d659cd3aab9996f3f6e5fc82043bac591d759c9829fe433a4130d90162d371a2b3eab066e905e92f5e5cd44377867482748074a6d059e521f9354b5c2e92a9c853e072ce39e76b0424d976354b1f16b3d46d81ae70665d5eb9e85d0a3a155019affcca2c2fcc32afd58f9d5517f638bd498b0ca09101984d4571c6e5daac50d35975565da73baf588d25ed8b3d3d2a44840e3d4ed85fa10b3a5cd3c52b0f2e7a7dafd5a5c54577408dfaf675b0d5c591e0d2c248134fcc8e3c010523b81e18f1cc2aaaaa50291e25cc8f5595a94250a8b22ccbb20cf9d754a53456e2fb36b4bd8dd1dc77341673f1c71b342e7aec097d292ce6a2fbad59d028b0eea82917472f77ac8e38cff9ef3b9f83f31f0a878f9669caa1830fdf737e7e2837e4a1f33d4c5327f481f35fe8b1eeeb3dc205557a741e3cb49af2d167377cd79f283c14a14d9c9f26ed866f07d41b4eb1f775e4aab2a67887090deed14b0ad34caec41c5524664a394b153555fcbd4491d4dad228d02385a1fc134017d40534c59ffe88f38fcffd2876de8f2417d011e5bc178e397cdf85a3a8f35c381e608f14f5391c03efaf91da6996b08beee7d0699a8684b68446fac988e927878123e1dd7befd54892cb5ef7896587d18567e5b2978ba6d378cc6bfce634f32355874da5521c4ed5510eb14653758453f4e4cf6261418f2e841a992afe2f674d1567cdd2373acb59d65a6bb1d74982eeb6ff806d7ff7d88cb34a5eb3047bcd12b7fd9d888bb6f799bf62db063a41d31717412c687fb9e8a3bfb62d44d964a8fd919e68cbfa0bd41c365b2608b02c6087d9f28b317ebd30f6ac4862dbb6d18d6ab89b9aa6699a0681ea2389215decdde3fefc2aa04992f4417f043e346d0bc71a7b4341e182a6af3d8afd21b60d59aa76639b9bd26db322064aeb569ad6b956ac1fad19174d5917eb3a6da39beb24b4f6dbb66ddbe6416994550784395a7532b3b47d37b3fdeb56dd0a772b4b1a81b51d0044cc10053d6eb12db6a23ad8d5ca5a9a18a8bc02bc03c35c30c618638c2b6639428f134601972259604c804f40ad95e3ca2c8218994f9ca0b5e7132708b2e7cfbcef062b0c448c319e51430b2539752a844dc418e3cd6e35882417dca2ef1a9e3aacb596ce3929e6ac89b3cfd16dc6c6cfe16799314bdbf6272560fb6b96644310f4cfb082eedc7b6590060f200b89c62ccd1d6369df9f6e4293789811ca62868bfe336ca0335a74d17510b2255d64b74ca3517a9c321b433a285c62162eb10ac7e0159671d1c430fcdafeb88567304df7ff2f3a608c4b9d84ce94d2cdc6c5bb476cd3ed40939b8d8b98ac238bb19591e4be57c3db4fd85491d1232ef72d5d345a19cc4ae5a2f11259b968b4ae1de322177992d0fe32d0408f9bcd8db93ee60a4e0227c4654de9c2f1431d02ecf19e34d6256c3b0df18c4adb99a9e23f5a97cc6a410b9955e20a2ec86264d696304f70b1a198a228a6a8db6c685db3443b7a3355dc92a07df1baaeeb3a4de3d818ffdfb1632b31572c11530617d396b3b4fded369bed6696f0db22f6f8e38fbf59618fff9b93c559f27efc4d9f0497b8fc3e5807f529020c1af4b09662ecb6ce74e865adb516e739aa43eaeb083d3a49023f627a117e822df42468496f4836cb2c5d8bb7144c958b7358354dc3f8d3018ce6b41dc1c5ea257bc168623537da49236747341ddbebd56ab14c962674db0bcf6b964675942b0e2e3a79c2613a7ea7b390ca666da1b56e9659e2828ec1c9d9aaad2d059b0aa88c1f2ff2e5aa332f1ceceb65ed67d55571c75ca6c0c630d6011427972b8b0f59b24a55cc4a4608ec55672a8d8d4daf118b71b4b29c251830b6b4d4c27c10b0e19c7576576bccd27d77edc754294b5acbaa82d981d6c8cd5a6badb5365b3c73513555bc2e0124e5849ff21219cff0910aa43a5199286b79811a738b99c52c52cb5ade4bf3213bb053f7b43ca176a478c8b458aa9899ee74f2aee374baae035a6bbbaeeb3acbe174b8cd126448273f27775d38da127416b5e977135fdb75daf77ddffcbeaea3b4ebbe7edff77d78aabcb88cedcb54b127abe3e5a500634cb5d751ec75b4087da9c018638cdd9de36e5c1341eb59154327fd82dc1f9b2a7e642549f6b8b2d935e22207d3348e86a3025705ba8273394b9595cb2df4c7e5a25114e2a271baae8c091847c35181ab0295d1844cab9ab5a215add891d128d55c34aaa500b6b9d090a4e18bafb53023acb573523ba9b7f3143b6b39b035b7d5d5de5a2bc61c122633226cd6040504cda859fa5c99ccab1c73c10c93cb8ccaa99b9d982ad586f9945f32eade4c66329f322aa3322aa3f229bf643293f9259f32994f778626f49875e432a3c0a0b30e17f90fc16496224175ae2ed6b9ba17aca371d1f4485967c47ba1dab51a468f9624afbdb67375ae6debc1a473e28929e99f863742d33df10da40964069943e6c4f57a388ed382e36436f42cb9dbf836e79dc3f9cff43d474f34a5d240fa1c45511de2e6388ee3b4ddf7a78ce207d028b2e90cc7edc8141bc7715ce5388ee3344fd33ecff3bcef05a389d5c8a68a11af6577c05cbad023c9dd5d302eafa095d2496725d55a6bad788233c553603c177a2491485e6bd5347aa5482121080f6e60d86badbdf7de7befd5ac75cf669143b5bad661e96dbb9dcd92688fec6896b298a52ed015faa1180dc323a443df97ed37db5f3b32a316a6473bb335b1d91117d14bd651b54c5411539a464bda19a5ff34cc928f2cbde06673c44592b4ad1ab648dba276666dec111ae8d196dd12da86dc2ca5965a6ab75a6badb56eb34db61971511d437b36db88c07871b4d0045bd26d236df86ef8ae40cfa962a725a725a925f1c5d76a9aa6699a664b286472d239b18e148a7ce18129c518630cfa20241a2d6bfb0d17e110f5f858db85c25cc11d11185dc1e1a432a408a5b06fccb2586ea68a73d1c58cd5a0c1002468fb2193e94a41004b1ddd58ebc6348c31c6d8c3d646ac8e343053fc29115bccb2b832eabd066a049d2ca8940c9807397568060000101004b3150000200c0a0583e170583c264e6bac0714000d65864c6e4e3e19c9234910e3300aa330830c220418020c000088ccd8540500313378c8557e4ac7b2e195b2a07e0a739899dbfa5c3b2a80e72a407861e144ae0b154690e10c37bd5ec6e77fe1854703fa1ff0af9f09019212e70a24c2f96dd2c08b92e951f2e4e31659b78fd61c2ac8ed407b3a91560f188d6ec32d0d6d98695d3fbce7122f959848021539090de22905d44f6c1a72102d50f1280d3a2241eec250df115bbac3cd817aa121038f94afe39d5c5032db5020c59700f51af31c3cae9338a62e31f9d4c0042b501e8aa8442bac2545d9af3c1d53a16a34042bfdc341c667f1d69800f8b5cbf659e30120948224ed57c552c3edb896196f9da69ca379062025f427633445a7fc97901ba8be84675e8f394d131d88440d1008600fca2b64512337062a20db0490720c362ab8d828e9af1fa981775e705b96aea0e3c63c636b87071adc3f53e21146299142cf996802bc009340273d64db98950cc0b8acc0051776adc5593b221d7278af1e2ae1ed07265995aaa797e550620f93988d7932b2595b6431272083760a118ea8328da69b1ecca8590794e42c9d9eeca416031594712fc2c5d9895811d6d6ca738053a89a51ddd1d8cc8b845aa069332f78b59f456664ad253f10bc1c9c07339e0926f6e24d9eb783b572d3cc0e104439dbf0d345f13667e6e4097d1d353437c4cd29d00f8ec3869ec42402032e68896130b8edc20682091930c8edca5b3507e2c6a50b3d77a7ea7a9dc18126f5284d6655dc55b426a331a04d5ab89a64110cb787960cc8452568898d3cf73a616fbf31e12a80821e97a122242cc27c74862a7e1b4b58ae336ba7e1fa12eeb94cf6cf7f2bedb0134445f72d2208b752bb21e8ac05883897cf97133ff62de02a1a330e7e9f64229bb5614ed7baa74133ea660a62990b72846b2c59ffd1aff937fd8e6f7316321a5824f37535c7aa391a719f6d4ca91b2c703109b8bf7fdb36dd28b87ef4fdffcb6f5a7d3e854306f0ebbe4abc4f647921b43bc31f711846b4da15a6f9c3d6169c65e49c848f220bc196bd48eb10eb6910b3447290ae60b8594cf436f244622f72047d971294ee6e0269e253a345b16c774692a1ef8dd54c879210bd5ded1320eb409cbd214e0bcd32c358510901e31bcc2cb3a33551658a854f6df3ae5cbc99bc9bdc63f718e12588974109f41f17c426e18a757c14ff63116d798500ef621dd44a516b4a3d35072173b821d1a22da31ea36c16c6b3bb16655853bd69a37fcf6f5bbf8d1bd181eb7aa2f35b3bbbe5a54dde9f96f456db582f54572cbf1648870ba66ae4a5a23599b6bb6642ad866e0fb206cc4c444be32ab854a944b2cec668eec1f2dd1266001f03bf956c6c9815201fbe581b1c65b477c1e85ca6b31776479a86b2cffd5617df941d331b516d03f64973fec96e8e418215f6b4bdc1e1295a1331af6dee1052616f10c16b27c3bb6d940c6ae03bd8fde62541d790de3d2e7c63c289da59bbd744eb000b2766a11350a4c484985b0536bf4b52179c59232e2cf00c50426c5ce3c6986541712bb6be3bd7fcd1699e681747473db1432019e08aaed442f4852b9cda5cad554061819c39050591989664100aef07ae4c0af8dc740a131d958960addf6e7cc5bb619e8ab26a69080d8b9b2beb66df6cb110039692ff5183c9def032d1404d32cbba60c628b420502ba8818afe45723d951bf31bdb3ff210d6b2a873b138a98c797f1e92a99ca945face0964dc08a529bfe31e987051ad0904f4307b0a723813f6853666691d700b1174cc72457c5996785629eecd6740998af524836e97afa82f6f5db76282f15cd3b5f74f1599e535d274242dafd52b1a219757e4f70214e17549ddab2ed3ad9885c0ff506e0606ad6cd55a1d5dc85ebd2516b9953b84554e149c821c761668dac0e537b5bd5e16d2dfed89d14721759f52de8dea32290f88d6ce059ab834af838d18aa60e6d552293757b2f0a8d26ad68a4fab03ab3f12452007df4548466165e6ba4b9438d6d592f8e014c5b8da657e7971197d3494293458d94407e910cef0c1428de51456c0f382e60c36ecf547178b8b519611e5a7a55d2e00675eabb2c9e4e322843475a24e3f64466a6723090ec44cf89630f33b5460570aa8b7da76673d8c89f0e9481b7a7f97ab8512beab78930b642df50de33cd871975b3abc4ee4469864feb848bb8f4f5f1f0cef628f37d62062917368a110cfe6aafae423c50f1a666464967d0f5f2cf809fe8848e4b9c2592be03af3737ded6934d8e064b29d02a5d23cf8346f73c5529163424310636d3156eb36ae024bb62ec52c9683ee6948b1023e291e74fbee028e13e5aa0f7c5957b40e496e9f7e0d5fa048f32df24e4f3596e65e3373727bf7f53a8f167c1b8df7df1b0196cf367f7aff9346395594ee89469bd26ff842cf049b0bb10b5e76b213d08d217ff92128897f12244b88c5871a8728b55ddcf509d047d32c858018cac511f748e2fdba46e55516ac85d947b6808b71eb3a115ffd07249f195ed53c958b3cfe2e31e1feddedb892243940c5e8b0da6a986c03e307184933508ae06e559cf533037ea6aedc9f6e557df98ac32697944a02cb8d62c740d3f2c4eec8bb82bcaf818b7830da8535d14cd7f5960cb16d6c1ebbc142ec5b94f498c24b9c78310b1223bacdb3d3cc827825e77c8afd7d8382b06b019f2335fc37d189bc6b60ace38159d51f5a65070a6007a9d143393a8d3efaf0e11fc5ec670623d0eec39f844e1c7e2e6eb5959842cd2ce86d58a39c5806176736afcd23f81cd87d8e29727c7027e260b7b0c5d55d1e925520471468740d0b413d3a1e4f715414e7286ba11fe3b7d80499f6d0de0c65d4e2fd11370e4d5e4e13f9778f01a3df2463cb26daf3ab6f4ddeba22e1bdb9a2abcc249c7b5a78cc004064d1cedb08a9ca61271d1a16fc92b76f5b6f28e6e349a5130639681b3db4bd90052834b4408ec73e59592cd9dca8e71808642de66c6d4afe1ce4b9636ec3c67377ee3353ff2b8922330baf297d44351d79001492c1dcf7376327caac3c5ef3baf520a8399a1265ec538a28ff48022583c86aca0bf826a924bba41efd8bf964361a405d86530f7524de10d08f32acf507a7aed9f2f5c237a500213f22456dcaec975559cf67886342f8290e13bcc1be5eaa15da81346111208f9cc15004af2ff713da29901dfd41f9332a0e9efaf77995239d63ff47f2f0dd0e44367eb112ce9eaa994b074806bb15a60a80f5e94d5dccec84425a354755cde75eac32f103e52832878d35a7be5f41af7a154ea9521c42ccfa27513499aa21ddd2e0933499361c6f9ca63e4688de83874518e10c3d828bb0132ddd0873abd559e5a3852389e968caf1dabfe6f91a599948e87cd3c96cbf5e7dbb01204a257de0066ee8c7812ae32e1ca1607092553248ccb18fe64dd73a30962926a8061a99e133b06cf1d9fec009726a172df446e9ce13b87655519089448a29a6bebc556ce65886bce5dacd1ce82c226136ed9b0919c6d26af6f4cfc8b5feb1c3bad2638a080a51ff1d1494219ab66962b9587e48f1d9106dc96e74d98d8054f9e9f9d5690850dd09a2df46d3c0ca5117d9b9ef1f8cf893bdf9bb4ba5818ee336e0b6472bf312d4092f362581429c41ef8ce3777ade5905ed1f37b19dc09dc451a1af6c97c67720f86c3a9a8a320405c1c4f5436e7bbb9eab3111a711b8dd746d8c531595535d605f99db7c786f6185d756e093386a5b9173b38985b2466ddf6c96ab31a3ce658f4566f3c5931419d4c57c752f941ab1bce9deb98e9c4a12cb5c0914f52e4bb960597dd6820c5eb208b9155e71286322baaf55961914df6bebbde5e84f83c4ac8ee1ac9ebb3e11bdbb713b16e9c657d7251886ae6d603e46a1f70923c2dbc8c7874a9fb355db7b0c14991f42d25802ee272d3ea58ca3d5c4d0cc038729c95409bd144f792fb789aa797bea620987eb5f0d4ad3c9dbab99c83572a5b95670389593db181f72edfcd8e6e1710815121d46202480123af75c1c054f9f7bb6e3de1f01155fb98a1d4fb02312c09baaf1b01f71c772f67bbe7733c723245452bf278baf23f32875711822cd0ce0b08c266fb1005322786d8ff8bf470f6950b2fc6699f06d80b248e38c20f1882c1b9ebbe2894206495c877a0fc5b313c6c6c6be1a981520694c2cc54a81b0706d0cef189c5a0029b836209bb5920a773446aa4dd31e20073e923a1979911e3851e2dda7b801b2015a69cba27f85a4404180d0e50555c05ec27985905d1419d06ba9a2925f05bd4c571271c03080acc305f54924f2546904be8d185a38e837282fbbe3658b14013dd0e26f2c915f7a688333de133d34054e6c21948dba68f114b4039cebd63e335bd23f759c05a64043cc489f49571533eee24a0d310fc131de57c057d3411b4306935dc85c4e37634192007e5e4eae900664d7025855e4b3052297adb16085d611f93531182fcc078e05b950ca0a18be38cfe06d7e63da934af2167e8e46d651969b2ffd722498ef80ce4949803823cf63c5d2192dfca703130edd9ae1fbd66e1171bdb5fc849a8b0d330a81c54d9a83d895de036f314c3441e92311c354605e18a6fa0c54fc29cb63498830b1ee3aa10561746210e6c835d380488bee79cdabfb2db6e872800c461dc5fbd71a83190003135aeb2ea7bd75a211d7496a7e0613b6371613e377f2e8af49833ea3bcdcf765a0781273030d9be0708ba98d8caa73a5d177af6e935fde890ee63fe8dce142c60523cd4a5ed7557c0cd4639ce385a1efc3a859b6a695bc53f0abb08393183ef7a8808589bbc64561ed99bf1e382921dc393b95dafc5d6861ca700287f8707332990a9a48dcc2c5c16d195d9f291a8d24ccf09022ab10f53819088f8f744e60abd53fd33b15cfd773f3d86b366f2445b60a4b60e221958c6b5b13c57cea08b0c7ca5ae67e9098259b82515f505e8d1e3ae5314b01ae6681b1d74478d20bc62bded251e5baa6309689f5c37fcf621fac68c5ceccd09d00a5c25ab964810faaacaed7a871d230550424c4193c1d533fc349610ff5afc997682678ff2ef7536b9a50a9ee9ffaf9aa4b1e1595d79eb7611874878a8d274221a132c49f4a63bfc6c627346508799115c12353387df39d7d87935236b36a658cef1baa06052754e614b34223376fbaef792bb899ad9a2ca94fd6c3a78d717a3a94bb35ba6425c28c06f2feb949a1c963baee406f81b0033ddd78f009c47d9f69375c3d4b2699c8ed2d415a7b8148396ccb7d36e833516da3102ab02f50237c01f5d45242eb69ad6f27509d4300ad9dc8d819809c8510814bd4e32662e8172b2ac5061240af5d2f48b129823285c0b361f803419da2a8b7c0acdd814194cceb1b827cb7fc78526e4e5bc2d3778163f0fa8abf35880569ccc4d2463196ed1675f930b46876f0a13dc0da08192a2528ff391ad33ac2a843629d91aa7aaf4a9c904343d8b6fa2253a551f4662071ffbd292fb9f064b52dc37a4c9532838730a7b92e4c1c6ff056e26d1ba1f3d8bb5e0565ce98bf9951a81663201c8b18b94ca6404276ca8029047ec8e844a03ccf00a78007c426fdf0d6c3038721548fcb7b949d7ae590b10a86834ef51a391b8e96c58807348381ff24cd66cc3b6fa3f5767c1c128cf5d9ad8a23630bddc7384a6ac003eb6b145027f5fd537ea3a023b6fd590df16f8206d7a1921455f5face16e9a2ea619dd443b93c3124e08e1ed7bc979d2838c2bf38201ac990be58c7fc21ba774c3c84c66500c8edff9f6422017311cc63075333a9741f257a2f3daa6b889f967c7aa9118a3cc28e1402d4c6b3a43cbc57310859fb17aa0f98a4ef4b519c9d9c509205f3b502268581fd77ac2cac099f87f3db2963cb918cfa9e9d1108ba30ed325dd88e99e418c7321da8fb8fcd0493c4ed82175a63d592b5e1d3c93212915b722782217bd0e76f032b26664270c78a6731845b5568edcd4280a00769b0ec0ff4f3934ce4d91cae5c02d68be1d5c701ad06d66dda06f7ef97e4a110c6f90f63b6d724ecc85134f39a72c44ce0de1164d965884d9589db41dd28896bcee9d41cc9014bf255ba7d5c161c78d8009a708282606540e239399020fa6745bfe17e7f9a2406cb134089676f5a224fe891e2b609eeb135dc400e04fbd6dd045d9c08b6ba9b646bbe006158ac9676ed08ae6411414b7058ae9bc831021307a726215e093d24abac9bfb74ec06e5ec05d3748b1ae57be3af96ac8a1763f779e2d57d44a7a70492114a17ca819126caf99b284e1b54311727295f4049515c4aca7b3c9fa2779537315125050e5dd06d3771af082122527b03379814a96b69a4a5fc3766122e07171dc045d6e6333080f68a617d6d689d91328748228313319855d2142198d19c3ab237ca966ac0de3f13b6e4eecbbb63a55bba39230202e27753ac94f8fc24f54be9a61b562054942f641ebaa49ed509eab899e54586b277ffbfe295c0f892e6e203fed7f3a7d4012e623a653c3c56e535f8a5a181577c4bcca30ca2f9add3e2bcfff1d061e7d5cdd82452d216749830dc3b4fbdf14f1ce969b07002414bdbc1a6af74694304e02dcb774b84fc2517763a4e190ba75748dff0bfe871ae762fd5f58384eecea29e0c17c015205fd5e7d71834f41fea7556a776b2cce88f6da85b7905eb589ac0d98e97813d837c219c78fd8ee58c2a299bd0aceadde2e467c5527d285e6a0017412db53988a6879cd441a6303323f51ee27dc29048f958678ea42c66575514ecb32a5b62f62885df4be11ab8a82eec8dfde0479bcee862e26146921eb5e8b581117830e18afde04ca4b98449643f90300f894236ec2af34a201aa6f0e5c8b10da7832e55fb88fef906150da2e777105b8045dcaa1cd4b113eef35f82a66d0c7ee241e6828b063797b017f335b0034310ce8000268b945c6a0fa9904d65191761292d6e52faa8df8ab2a8f33b6959ac1fde54617cd68f68b5fe5091060f7a8a82acda14a14db0b940b45aab54c6109bfdbd61b0588f41fed0c63ab55995c925506e76048ee421b79c29632e9de471ff63518c546cb58479e478275f5e292cd0d16e1054cb680e71179a787bc208f922492e3eb07b13d4005f44eb9e251b0ec68c0b6b50a95af076b3118bc5d192065b9877c07d5fe13600504cb8059774319f1b25f4334a11d0dd6ef59752ac9545104216e54cd645a2b718b73de8062e9d36d656574c237e7769d1546a155b9d48a15316c22d958d6284a51f723dbabb13873f886c290a9fa220b5d5c3cccb12dfa479fbf3f244b9221fb8a9b425b4de194b59568a5e3b3407c88f534173d986c84477d903e362b5ac1d5992d06b39ed52f924cf6e03ba174527bd8e3535649fab41d5884ec91083ee759cd91d1c6695600cb6ddfa9f05890633af8aca6f08d05f360ef49c615469fb5c60fe45e4177a91c978f1b891e12bd0f3b093573f2c434fe9c1bbf9374ef96631fffe93f39c82f300b6eb7bbc9beef38aa809c92ee88a60019591e7e7859ed2e55c9851d1ccc834a5651d43957e3af3b327fc854d59bde7aa38d4ac32a1a227f3a7bf4ece05cf1e64358b6ca6103d8d8e7bd44926d65c0264f64fb9a00d9b8eb8208f5d042f85113b158b42dbb6cb6bcd12b69bbf1032783bebada4b84f96ced473cbdaae17785bd78bd138e2bc643b3195eb68101ea21d6f92abe0fa02407decfe7fcf0138fed001effff39e2ed9a8f2b3da43e6cdf6cfde16ff52d887fecb62701d615112556056999e5b82ecc08d6158bb8dee95b6b68181f36be316fc790082e8e50daa7ec2b33a8a43906cac0600b9e00eaa38154cef9d78189e389a0ea626bf9c4a897c3f854ea118f77415104504452b4df001f3cf0001c0f28ca5c781589442fd56d99600e462b853be11b8249afa04c0cf04cde767d09034d0fa6a490bbd51233e023e1af3c9f1741cc75a84bd46a3221ed8a3ab9f4a9767015d8db5b313930c0fc9d3810f5b94c1a2d6d80295c1ecc2c2864b44a8a7a5807c17fc0e855d6268c82e36aa8c4ab950febdd08a739cb3f0ecd8746cd788892fbba872917e225da167b7f3a7b317697cc1450a6a50ac7c76d82efb19f40058ea249c518fcd56c36e2125f31ac28c0d90def0c7daea80580adcf71fc426ebd00412d06ba9000da92e40d62617757fe395f4c360eaa99f3927f51a05fc66f379c6644c0ac0318846fcad240b8380d58472d3c032c0b6e4a91e541025d2059105515cf00081b265ea35e0e9f49a53ced250d388647cd83632b8aa617c0a31d412b414bb3605bbcfcb5316d386090cc56d354094e910b82fe7c772cbec6df04d56ff1dbb65c51d48d0f333cbbdce5e06f51bfdcd7089cded0aaaff6e12bf6089943069dc380f95b185ad10c055db4cce1f68c6c4af3273e293dbab7108b8dcc7aed5f6b806c2b36e979a8f1ff62f65a0e406f7d08ebd362d4430309c42dcf6dd181707c06493a517b01e4e3a996a5866d287332f066eb3f22db78c647fcde65f435d3821094a86d291d0cfaba2fbd64e82a97344de0e3710fc7f5e1ed8e12c6412b4b66cf28e51f04f266a1c0f172a0a6dba971e8bc3b5f2e5d34ab2a427d3bab9d580fadd020049c1bcccdefab976813a4981f872c1bc5994177a0cf832febe1f25bbd9daa50348dacf91dcb1b51cd6d60211b149a0ebdc054373a81888128a0feed6849bf0321c83ed58073c169c024d5b66d344260eed622c90e15da18445906e3e2cad2f10d5a7df9093d6014fe1071917582d30eaa8c94883da862aba2124c693379816f43cccaa02402026b6fc76dca628fb9a758a234bbb71b8f7787292d1712fa6dcaef2237ac71c0404b1afb8ed02af62da32d313a27ee85e0e0a3153e413fc36591c761280e3d6665a9d92f81051a753792c56ba1af319508c8383957e893281aba42b6acea3b24b868fb5046c36e81c1f30b2eef348cb7f1544921460c1148fd343d8298cde0bbe8554524373e6322ddb8444058ac3985135b6a5fa6a1b60f62285872c73edc17168cb3d63b23701e3b11e05702b150710df94c4e14b5f31192f98761b5d1d8f3899575ef5d5d1d85937a934ff56e9c65d1887ca27d4c452d5ea13d5be79514c7f9f9f8e71751aba21dc3e6ec2d7eb619aef8c238e7cde5fc5a1fd02fcf5fac7e0de2eeef52f3995d3aafbd6014ea162d336f5470b6b76b92d6295801ec5dc05490cb014dbe8ff6b91ad12710968fb8cdd0da0cf824edf72325a3d42312939f944033c723cd129940d126c3ddd7bab542ccfe4f5027ae06b433f7fbc4af547a742b866a6f1e3c7b6ecb5e14a1ea361f09e50a951e29a0fc7833b6a4801e3acd56630f39d6fc7381687a85515612f34d9e881418b13918e7b333d4320abff71881a47e0054038e538e7f287f5754972c0234343cf835a710a5cd37fa51e77da30a9ed0ecd4e89d7ba22b731e714dc39903ed6b7af98a54b627b7415145f6646e27c3ac0cbf80419112ee859f6fa3bf71f32be228607dadbe5a31a068ce4fb052403fe498f5b0698eb2cb1940495fb65962ecb573fab1367148d188e0ad4553b682f259601214bf8ebb55caad173055ba1059b9b9eab903e522bfcc4826ba2272d5fd5aeaa9f5b376f95ef95474b84435988f89914eedc130c04338274e72da1ba9e9350958628295fc691ab411e4ed74fdab6627b9445701beeea15bce8e9863c2f7346f31a2c3139982de09fe4129c1787b7eb2da8ea348aa37f4aa64fb8274e7e426048d278c037ab53da6bd8626eb457ab8c31c82f182fc8171cf35a2300e6fe55acf36e7270e5875290c0aff5ff18ce9b0c6ad404f67aab15bf00b5af18e6a10ef647f4f2e120291c6375af20728767ec67de57881ba5ea298288b9565239f869ef85d481e2f78582a9833309d7604be1974f004895bdde8b6c5da8daf81a5ff15b1b63262636742fd0513e6bc3db8ff69431b9752d9b134da4639d9e37a12abb74f64114b7f59e5887f886d3b978cddbd7db9608173efc361145e7e2ee33f6a1027961d8681ffde58f79b5c04d680cfacf7ce9f8bc0fdbddca89f7b0ef1fe1e100d7e5a5a9159dfb261436bdc2d3796493b70d01e67360e1b8c59d3adb0e5cf107500085525d59a769cb66ea3546bf98ccecb68c37f02e1544aef4e43b70e4281d7ee22354ccb5dbb90d368090cdbb98370c137cc90dde411480045d762c64557c58b87fec37d1ec55b37145d3aa9e3eb5ec634f37b734ab3f505e91978b984ad4436322fb753f410dea5827279ae654b31f9dd4a8070928299840edf5406e06193539d7465b96dc3c6fe3a57b7c61641f95135c0e2f516c32a54cc1a9fe5bb80f61dce2e74d4aa1c1700a9c664e41dedab23ba5e18f635af7e346a41f07d6ae702d9fde6eb5429ce4db8f63c33b1fc77e8e39f4546aeae3969f3ca53513121e33ac3debcb1791f8fd222e15107f7f01d7e4bc450e0013ccd28e6d8f10b65169be582c964f4dedb12d06b874ef968afe01f42717cd618ae67d26f79cf1851ecac6db6280fd4283be25d2c6110ff1eaa541718f5868a6e263392095a315850663d56cf979a2c9184cf01cab92350f06856ad9da0e807547631458f09e62f7b8aac084d4b276c1a2d2ed694c3409ee21e5d6a04603ebc7d0686294691a39750d7617fcedc7ee258d7d804ebb8a6ee564938f86dbc337a64fc44ecec484f08bdb9b50158f1951f1a023577173332a0a63a314565ae6aec494692c39fa05dd7aa7ada947a04a916f7de06b406dce89ae5a4f043bfc4a968352c73b7332c3056402ad44b2c5ecdfc6d7b9a873b81419d7e9edeb72e5b808baf9a8484bda7095eb6518cab1176bb989e9bee6ab1c18c28c46ae29666aa33a595094618fe69a9ba884316a9bfa4cdea045f5581b5f1616669dfe7c1fc328d85f15dab758fff34c4cf045f191a509dadfa070a25df3fc867af0ad136c901dc053a07f21e0df323332c9617290a7092d7aaddf179e9df3c2862e91b0c004cc1a1f9108fc46faf58a87cf08944e6aa8bf78d0e23e3a1a67a6513ba1b82e7b87bf22b6684b12f9bd3fa9a00ac7a1a4a15d0068314098bc2e259589d845e08a0b19c9e10f2e0fbd9542bead0681651b29758011fa125b94c2c7048da10a39242fa5173e857ccb585920df134a215ff0c2eb0ea6236e36df4eaa32caf6f08ea58bcb0bd6fb2443ab44a1f8666e34cec37e74fbb8b5739720bd4033b25cefcb8dad8636ce03df12844251ace43881ea5f8268faa84ef00cdf76933cb74bbfdfc23efa63564f53912647b5fea6ff4d1c13217d03573af9ad3d2b19d1465a1c1ff38971371c7455e3e5b71206eefd854a086e4eba345d6b640fc0275be31557f108a99bbcbf6eaca9bc8435d47d8de8759bdddec9a75b9fa29c4445f5200ed8553cbcbda15db0d799e76edaf012f6aa54bf2d363e5fd9b64956b8ecc2708316c1875ff1511a2ad34825e32a822997000deddc4c86123c5ee0552f70c0140ce880104a7248da29b091414e10f2dd42c90472ee836ad5108c38875db00eb7cfe96ef23d1e2cff205a11330e7608ca819c606e12345cd9bd9b62419bd27e0ba5be3c713225117cdc10e6b78208425584e4bc0545a2632c82c3a3184c2a69909ccde285a1cad4f89401a24f280d2723778a0492c1c01f9281ac357c748f6b6fba0ab433b3bdfa180eb5bb033933ce9b70bfd93304a57bdf01f877c0df9ab8113931419bf3fc69e14629173c1cd2e8ed6f8f458c1f48612a4892166524bda06297ad243c08394fe18673d9e34d368c4e5232bdd0e9ef33ba3f3d86a26982b93836a85c5881c6750a2cf4b12ab5f5a29247c8a11c1870951ded325f5d741d0ec2408ca5f067832f5c7d7bdcc6c0836bc7d52ed75736dfd5f4d07223a3d7b095495a15a6da89b5d4a91150e199529967dabc13af7f4be1e004b55b3d2294b62a634973a539a93e50609526ae74ca995338a9354f8a56b4ec48012a0d80739df499f2fda34ed4a8e785d70c8453ea2b10440b145e46ba5cada8cf154eeb39e8d332abf9a0f6cf52f80e3c2054b69eb6682d1e54cf4b402e634465f9d80a0527114a81481340423db4330401362e79aa1244dd9d914e1c28df9f3fea724dfe5ffd5870630eb8dcafc4755cd9240a110d8ed5d9775077f11d2b8bdc4d105323961658dff17b4ab42f45df9ba0ba657018f4cad2de3283e77127d6edd18861a7bec3b513bed517679f1717d029878fe585c51bd1c8cd0f9ad099f0087d10f3b43ff0fc97ee069eb9221d5d76f72927297cba1d7b098d3476ea21e96549e04243f864cad725d6c422fa40b10b161d3c4a5a0d7c12a19c270866e42de8c3af81156e0a74e9947bf1e97f6175845928b3ca11da8e92501101a23b9c8a6a67111d5434f270928ac8730f8fd6777e46e129f068f9592c152084c2511c9483f00ac2f3dbccbeaa40bc41e5103bc7388fe4e13032aafaf8be75f202e30f3fe25cd64db407b429dcedc85e0003f4f3908928554eef1a13ee8237d4cc49ed946a56006d4367f37057cd9926a2955457a0f8b48318405181d4bdb9fa40cebb01063827ea6517a122e238b15b501baca67b42d7fc4dc67b47ca98222353df895fe5ac3aad61500df4e54c4e0063ffd366f2cea35ed4bb276fad1bf32d137c0d95a9eb538a97cda29d9155c8ae48f2ddfac911a34ecf6310c08a657479b539e9a38afdb42bf4265c2eeee00c9ab452eb522f48d97debb59540bd66b7ce07aa52c7b5facb3c80a32f2bd6088212f85a0fe4b74d9feb46c562c52a64e28771dd92a95a1b4ca00b5684e632a71fca4717416bc3b5f4d1f940d70872b2663b496e226b26407b7f14063f4bdf893283733d198646b4029732ba3317ebfa8c04e98226c2cad4a0877e113e4d150338ba479ec623303ee273a86694c18bf4689bb8f3aee85202f66004b304ebc22815686c1f240ca702d05282e1683f7d0aaf0ac63e4abb1ac09f16b8cb6a084bf940af38708de9d677028f818cc339a8c933f7b47909916e251efa39516c13468e8ce85c2f3672f5068258075d48b16767d889e8fa44c053c0c521a791a0c9ed70da71b3e69b1b74da66574d6fc450f814e0b67584706ed721f62f0f697199484d43631896a2e038ec98c8504b6fb85c7cbc9666eacb72e147bb414cd532354604f2261114f72a0da177f644b36f8adb10220f76c8e7766ee3f48f397ecb4611c6a33d9b59f00e6157b9a2b904e513b23b26f5f2642ab40cb1415341773ada70add74569301ef73b3bd4c81b14b78b3d52e9f9ee22c47c73c16161fee07bd5302c457309613c35dd315b851acb0c9359dd901db0a43038744667b85e4b0b71429a2c61a4f5d1cf1c100e154f197f3b1abcca786b6996c0d442a946af16ccc3590f734144bc7593a5a61cf7e9a113e26b489382317b0de20aa60b6a2a4f125333dd6dbc51dc9a3ee67cb6a3c1436f104d00353ac0042679b136f5eaf50c6a545ab8050f4def2953e784b1cf856fc662bd600f0fa2568683754be01b40695e081cad11ed8b9e1782aac349b8f569304571d106235785621f6842b2b81b8df55109886466e785d6082b1009da82158b590bf4328c207d380e30583ed6c475c19258acb38e00bbb1fb53b0939f704583a73c85425729714933651aca4e70483a02b88b705dbdec1f3234cb2ec346107885bd5d4a10f48ee6982966bfac49320dd47fa7ce8b49b2cedbd20fa8061f62d7107a7aa41e8e7031bcb8d4615129dcc5b87ddab753dde4ff5f245cbaf46030d6388948e3e12ea3b0416a81fdd33911df81671f1e08863d5d1ec737a0efe6af2597bff9fa71875703394cb3e508e5674144cc72d900a196cef1d2abad28a1f32588f317e6cc5931574e8813104081bd7830a1cd43227bd90ab9860f962208561dfcb7073bf4b7515fc703578bb82e9f43050cb3b50b966f29b2b471f1e916ab612416f40309da7caa15305d771a6669b5041df87a95dd3007e4980f433ea02268352c59ef0f1705cdc3accb687c10964ce1712789170e3b1714ec1f6b4df85334980a29b3d6079ab698c93b2ebf4b0e090d201163378d3d945ec42c623445d127f2e993c0bd96a4b99b385a4add9e642399c40d084aa3dd7b1b3e4d5bb9d9cabc221117ff16e97f61667fb33d503a6fa39fbbcbae37c31c8003051b231d2ad8876fb53b24aa04ac57553e550f38a29d7cb4b3d291f997e8015198dbd488c22a872784f55a772231362cad596d450391b504887d5d2d4170b22e1d470f70b166eef2d664853850f4ebdbb7ea6d6c3c37e7e9add7219260beb52eed7df320351c230de868507be8060bec1c64d08c8454c5a1198708ab4ce84c9312553ba0401cad865b347e09897328c1f842c6ff86971c4e4882c8d547bc17a5d2ce4dab5b04e779e3e20d31fd2a82aa4407d64ceffd7d1b63519c231b9c9f0ac42db7f12313cbde6a0c34ab51afdd4dde7c0536b30f1296f213f69569a0f66021656ab3c380429e094cfc2c9d317b65f720ff96502d6d173c160545294d283c1882cab2d45c97b69abce9db6ccf49a91dfd54ac9c232f7d3b9b9663bd0cd6d42e6523ab551e1a1ba8684a31b105c2b5f4eaf176e2ee6a91ae5040beb2855d5fb7c5df93bf922118028919ee6f22a26b58203b10c7a38e6f36f59240deb7475f9f61a90e7bcccdb3e70759db7574141a18e8510acd6a73cbb2ff7ffc680a8e8d074e8ceea7d478352f13d9bee81b3c9bfd2493af2fd2acd00e77f330228be4df89fed94b21c0bc55ac5f12dd1cc273a41893dd69affd238b18e1c91c3fd42aa1fb914327e49d1a32b033b734e859acdd346a1ac4aae380abda77e12db67de4d4940e7d8dbf9a9f8fdd611d1ecec11e33e109dc3e0852c055a0132c727dd7dc5cda8f01e362e252ade995cded01af5e876a1fd11224a3783c232bac18db1411c696e7da9ab9645b0bbfd88c08f01659607416fdadebf313bfc60c052e9ecfa2c9b3ec72fb78d48c5f722356bac5cb795d5444b555f4a96c2c4d11143da58108a44fddab5023bba9cfe8251f8e9ef54b2a02f3a15ccb2a9837c54717ec3551553e143782b27213c265d8de5e345892044e9e37918cba71308fc7f3a234dc79c9006589658953ad367ecc224b54d5b71ad4fc4e08e726a3daf16f4ff5f127040a2db97f53adee7a96c2424c01062b6fa2da1116126b9b32b899de98cb144f9afd45552d3737dba702ac96675378e070ce79b8b6e23645daef0f70423f5112f06f7ca87e57c989cce5138162f9080b6a5703b681699874a8f49e1e513db75a4b6cc59a6256ce6f937fa1f3cd169bd0cc65e2f95042361466a4ccdc788abca5ab4ff8d37113850895bc8b71081ebf0612a715807b88fa97c077117d1c0a38e46fed5ba1e66c788e09f16334407df641f4ca16392c11526c3331b7c27e5e3d449579013eb1353935ba244b800d955f7b73f69eafdac8a2d65e4d0092e1d7ea68444ad97cb3f19b47d54882e742efaea72e046e72973f7d279cdb9ff1bb32ee39cd25600c61d618cab40c3519624c233e2db4eb144056ccc1796916213cf44f4c7924073a9a250528e4a671506edafb6c5cacb85ff83fab2ddd90cb16af8422c0c08a7ed0304d86d6b0b8aa463449bdd85c26462df741a2c96fba313e753bb94388d38ad249e173f8fc21e8907ebdd00fcd975c1331fb0929c2d6c3a952637e84374afe5f4e4d7b6574393cc8fbeccf3b128e78c0f527f2f040bcca6234e9280e019e13a52b2a84f07c04fc71a2b6989ca02767d979e2eb631554d2334d819b30e2e849510708c2f1b1beb2993f6e4b4d8c27265aec510a53c26a0b7e73915d249c0e7d9abe1ca6e3c90c0eda741da3037fe2f0057a72aa55007dd8188e6d1171fcae7e4d5cc7993d7dc470f4b6432109fe8093d0b39ba0e70559c7939bcc67b8005b9a760dbe5e433935920469c0189b2d87f22cc133dd49348c17495c9bf036d3bb7aaad992ccb9c5dfdb1c5d96a6fa06169332c43d870a929334ae6a023e9bf29b8d53717993a717e29cf9e1fc5bdd5cce8623f3dc166605de32e0a558fd43134ce778117380e28209cc1c34332763980805099b177c581a03e611af035f648c9d1342d97407f7e8a888825c72f2de0103d5b9516ec387939a02e29e0f9377225102f8aea591ebc42bbe8d847943a8ac48af30c2a4b8fa0f496707ccd37554f0c149feca1cb7ce54765de118d28cb450e255192971c7b83302eff395f39e565ef2705a0d1861ab3b816710e8263da8219c07500c1679d6a235ead3d482fe865aec9843b4f123600432ea250fe21e56d629642f855609db5e1ccf4c932685c45cc4ef450f3d603fa5bdf9e81a4724d147aa8ccb443e94c9df75d0051dc0c80fe4fa2c11e087477eb4927f506306b8db3b0c91ca1cf1aeba928363b59fa759806b56f867a0975b72ab278421563ba0d671cd8945c6c92d6623431ea3ab3701476bb5b07af577f7b4115465db82d8bf8fe28645e03f62da23b14bcd2dab43d5d03e27185f97f11c5755853a1f1c5b3285b99bab9d15fe9832324374c351290c727c3ed430aaf5bd734a97403a0528f848ea3945a4a9b770417606533d009222b653d13054a805f4e622f163cda8f46b47c45ce1e824a04e351e5d98c21d188f9662fc103af8f4845a76fcc80b76a7c91f2680949982ebd1fa8aafa131e2cbaa61ccb32d59f6af7a7519d6663f71c238a9dbee2f42bb048fc90906e512f6eeea1ddad83536c7274dd788fe95741e18fdf9dd041f0ad2759636699afd3bc5542191e76e522ce80c6ffe80ba66c6793c09777d0cfa2f02e0e0275abf3f8211212f75dc37d8462f5928a8d77996eebb8be07586db91e9a6b4f6c660dd8012229000162fd0b3332186b703052d3b70823d643d3585a5cd25be4d6f868eca34514a0b659a28a5c37834bb6e55c7ef4ee39f398d07da7d2516a837bcd4d9bc88fec0ef5c71ed5a220e5c5eb7e350872a24dee3f7ffcd82cb100317ed00ca7dc7c337937bce4c06e553e2091d592f6e13622e497f466202fac07df366ba35962d263800c16825fe2061b8e801603065c51588c9d3d8ee70e461571243820db7e078500af61b71eef091c1d1795d27f4b77641aac3b8a6181baf5719926d3f08e0d8d0f48d63d19db90e874ff466468766dfbdfe1d61bfd721f6a6826376475f9457eab011c02591fb53f102782380490606048f7e0ccd219ce7313e70c19be01cf2840465bbe8d770402b7b252b57e1fdcc15255bb2773da830c8394ba6930774d20471c5e8e0aca04b482066985c4e1c0261e7f0df0d3ab81db15ec643103caccadde78141347796a6605ff5d5908db263274574221ea382eb5f4a1206c13337b6ac09886c13c5ef01c7c32fd9249f79caa5642c43eed4ecbdbf9f3bc5a36622b0f1a24ab570f56652c6870119de4948427d7a7be2acdcaf1b855779b41c631c9952f15e79683bf3db4ee761f71454596b70a7051fde8bbb8ff14fd509cc1f21d589f671f1b58f3b7d02a1ad3c00177ad98bfe347591e5231e8458bf0b8e43b8a4883dc3c07627cf96733047df37d65046dc073cd5694f567964fe71ee4f93d59df0a99c34c7fa5e815350da39586da90033cd0c10e6ffa2853d39985446ac79cb6e600dee2847678181d669c0f180b17f605d532fcf1ea1ac615cc7705990d0cc33820ae3b2d18a2a378379029eb2012cdf468df5dd987417903c0b37a82c3ff101b844ba65fe30472a8d6491afe909dbe646d8b2bf48ad3209527a149312cb4c81a37dc863e33f28bc5b0fd61163a89753d8d1f8e90e014f16fac52689aefcea26c3c5a2e1ae6240d45cc33b1e62ef20804ea95b5ca678a7fcae2692671316d82b06af3fae22134f88929bd4b088e3cbcd82b32804be0a014ce9964c41f8eeef08c1f0165feb092902e7f9fb348c8f35434c104cce0422c55396e977af25e30ce18417b10aac7696bc4533368e0289216520d104526848c2afb2d7763042df6b4a1b9ab0b29876cd035d61af1c866935a3d936f662f726d1b2bd9cba611600e6273e220ec70f64f66458b0eb95b26b8430e16c712c437af3a9cbd2db0649cff0026f67d1030bc79988bebab1c6a05eab86e89e428b03deb2f24fc277f076f5a27ab8fb913a2c9910fc7f56896506ad5d6d6994fecd03f5d84b69d0014a99a64062769cd18dab55c660c342515426649608211cb96e389e77b8476888a54a92cdca399bd6ada168771166a000b3d75978d56fbe0811e686a4e1bafb2e45e05dca45b5deb8564e055a6a20416cc97d56dabce819f228f0554a5fb102e0d481e0fc2356544bc2586e309c6a290a55b8eae4e9c3d434a1b675f1a26e07dbd69f02044f0df81d67e2f65d7a22bc3d950ccff9a705549ad871b5526b2395b1a433cc8d6e844af5b5cd705acc160f18d4aa5dc86faadea48c238c1e1086d87c1c5be7dd7d564c987fd74106a00bc8554a2af63f6f1e9194e2860674d42e977914042bf30014e8a7da8ea6ce1af13a58999c737b0a052e2d4529a4bc6f9f06ce876d16d5121e22f8b90be667867c9057075043a819319064e7df40baf11db02bd337527ce08cebe8e5f0a08c63a7a4b0fec9a59cf63cc186f490674a6a800374cfc4ffcdc70289d38ad39bd23fc3e1662c5a73bac2ba86db91e567592d0f29bdac7f09c943ded0c09c8ada1c84f88214d556a64918ed8d61d3780007467968f18d88cb65b107225e6484f9eeb51a3b625d034fcbf6a8e6c7e3abd2f265f38a53792f863f2c0c6b2203f5e7ae5adfd312f6dd8b3ff4430d4e0c072bbce3cdc27800b1378f5b25afe8306efb6d6c4cc325d03342f0a2a7b650618c13702effee40d535ea9a35da8969ae6834ea39a29e0dad383cfd6bbef25dd00009647b965031944bda3ea29ac4f351afb6d3cb32110c4c6140d87e89fd2b5b1e57e48e84f8a1855c2a6a4e575d1246c4e63c2b80d49d4d986a5f333c724b1b713ac6ce3d6533626e59bb520fe86d297c02b345ffd8ee327622536dc1698b18ee89b988b90f61af16b5f5b68bda16f85e1f9b18fd5e629a771c3a82a33c32ec9e92c1077f5c7d11441103095564820f31ac70d5e4892d71caeaf2f5962747fcd1a68d97461bdb0724db606229381c1014e0e26084d745011e3ba91cbb6eb0bc057b8250fa2826d0452edda39f51c873aa0b17ff14792c462b6590adbcda0f8ffd10f28fb0f02b45aeff8758513c6291d95ff891951547d1a1ba88568b62764307f8ff419c02b83f69b44e6719cdd9910af3d57a66039e1b76ba04c120dca44f6d96fd6c4741abd95130c3a001ff1727dc9491d1a0ac21656e5508b9041cbdf23792db29ebfb1ba28404e9fb3ed8f2b84c4e227d14bcf40a33b3e6c54d46e7666ac2cec5dd07568d1ca46a6c9ed93f31dc99f194bfd4d71a4c31fc29fc90fb2bc59e823c8c1c1e17b9589dc31d1cd307d4228dc3c54adc69d0fe96ce3ee6b203d28d32df2b4bd2966fee4ed334192ea648ff64aa37f497774f51cad83b8a5bcbd1839daacc8035124eca85bbaed4c11bd7f964973cb9f22b42d0cefb31d6fbdf5a801ad80eb9bf7073f742cb72d87b9ad88dd74bdfcef9f1876ecbd656a8fb05c5671ba96e50a67e5fb0afbf510fb7fabba1cdcd31e5f88d30dd5e262560f612d7ba8dbb1326a54ea21fa1c2e5b6ed87d00cd6da25caffe0198221b6dc71e05e7e3f1c3ef702495e81c1bc4da9a9a124c5471715975fee1818812b8839ddc3e48c6f89a29a999c1faf087ebcf2039e68714f1362e19b4d8ecb12bf231df76cc3248390b65d09c3246c739ac2e7f214fcc29940a8384dd1cccc958602c64d29ff119312add85133230b6d36319dbcf5659f3d3b4aff788202ba38d5d30d247aee0b6c8268e772f0e5c400837ecfe9300adcf5d3a950b27e4be1c1b8b8878fc0aaa7df7bf8ee47e1980e97ac97e5f105d431d2fc04452d809288a1336e37d0d4c1f4c7a475de8a61aa42d3485085ebbdebeeca05435ab3fbfe012d716d0023d80d104c844b662dbfe1824e8d19e0d8c11d13630c4f6837ae01e2b4709336265c4ef6dd821f228afa6fbf25809eaba1b62a6210d99bf941ed856493b23beafe6150ba7b49e11fbed68f1f0e687ba1274ebb9e0ca2cd878b5f4938801b5d814d1381d5733d94489f1d1281f26c161de4e66d682611664c1f3d0eb8f57dd5a5d32bcbb95f2620fa195730cc11a50ff72f1ed28b6da567222d0e631b43e0c70fda77dc57ea58bc1085a51f7c3967e700618bb2b31b2e5752c368d0e040ff5659ac1b9c21a3440bb8ce2baa634578d467924ec3eb35fddbfc10910faf9995f005a8357177e884d40082b086c67ea965afa08bac83c21376955fc0e5ddebf46ab2774d71ec645ed747db62ca52bd23741441680f218443bc7f8ca7937018f646beb07c091baa5423ec40d340092e0b16d0104a5221f799ac52002a4ae94c73c4c355f800de5aaf18d87fe8d3f5e712c55c0df03d063cb72b61a92ca84fe99cf3a3018687018b4af6b9a69ad70717180a008b32fb482bb7e7b3cc8bd7820c8a8b3ee31c2ba4066cd475cfc31ab8bf1ac86c016de2775ba744d4caa527b31f4c6d8f02c1e4c92b8f799e33c7e80bf813f25a851141f54e2f68fa8aedf60d9515d7f6a6a651bfebd7febe80a61c2b5d4a874a9826cb825c15c225c043d9a9acd9ecd129106dc045928c062143dabde108afeee2d7ce787989cb4869399b5fe56c84e42e41f7af12f736a3b83b431cc5371baf39ce2b38eb39bd12d50213b586e215912e34cd8652f9d94a0bf5ff44d9c05064039256e0476b55d3ea70957655311f85360a532eabf1af0f3faea275b37cfbbde0e6ae3edaa47761a1b084554aba0e5e2adbcdcd4ab19302b620f138e1117709bcd893d7a40881443a4e29a062a7a7de62e335d37418df17a5ec5ff2097a300fe0b3b6a8d420fce088d5487446df61f3c9bbeb48716df4b05f2e9d2c4d34b39462fb1b57b4a7314a0d975c1f80dd777484d2679391d7dd5f27ec884a450bc2bd11d1d2209f2aaf5b7c42acf22a0ef2f1b601e173246a8711895543983e81d4252aae14e1fd34630d57fe40bb71a5b8c82eb3b6c5c0e3ce8c7d0ee0ea2ba6d59a3639776e1c81a3f626022e3023f704218d2f87e8ee336e955617c4e1e051921f1899fbdfa66279d1e7a8c824f80071caf5818054945b1c3ef6178a63a9fd4b3a47674904fd6dd2f63f376e42e408b0c1cdd3d4657545bbffde2e1778c6fba8bfc7201a817390c0b509b213be38a450d59c9a2e7bfc26214da2064dc7b162c8655048216ca3cee1049ef52cf6dc49b60cd570e67a489a94e6a30444d1360e24d199d29fead9f8f5eab089599e0e862a62a61a16bc99d0e1b0a67fe3c626bfadf374a537d8ac0f2929483f7cd17b01a35c7fe008cf2fdc51c5c28a59114de94a687ff7ef1d2561c2a5f525081e3b6584159d5568c769f2e7643340f8f839871e61a2d7f8852feb1a6406692cc36bc0a3459ee858bfa5a5715e8ff56809a94823b8c6d2c5bc90a6203df725d799b2d85a70be932ab2d73269ae6082709496e86448e82c990ffe6062f65c71428d228d9728759f1845754624e78c6c7f02aa7b450e85eaec686bd0e2a89611138f0e3ecd5f0a854bb1ddf66aa31f6756d03782091c9570ba06fd1d5d58a817ea8c9654ddfffe722aa26bb6ad7215ec87deee5a0a765e338479bc83c34a270f2401cbe51894136a4cdcd4ea2d73efe0f9aec739c26f5fb4e731b2579a8ee07cb80001cf6ac93bf433dc4f0ea8dd6ecb7b4bb000402a2575519937d9730dffc4fa4b4ef92459c286360aa55de11d8d4b58ab15ec9182a4fd8b14b29bd013bba0d12cbd7a3ebd7e82f0d2231f3513782b3d6bdb71cd81ea98abf4020fdacb70a45266037aa07477198728287a58b88e131d1825b885eb7eef7a23224fcc0a15cc282c5438a9df3a80788cb3efe654247a6410c2a3f14768beacbf2346a0ee85fb06b105f170902dc831d23135fbc125a2e1b38dba79c31a9da6b33a55a1d91aec57958e6ad3725875152b6efaef524bbde5e183b5b381c57b23c6271a850ff73b12b3ffef95ce0b1344b9a6c0040809a0a6e9b6e5fc1c8930bbf4b74cbe547e7a7130eaabcf06c998c15c7ded00791c6a85e72f7b3ca75110d3af7ccb0988869d42dd386f0674ea01339b38e1da7ea10f7450157647b8588b70aeec8dea505f47e42b0bcf82afb0a6f352f4f8f2871c1d24b6956bf334130e2dccb80862b34df9bd556897666e6280a59c2a81711fbe7185053bbb0af483064fd15a2ded5d3edadcc1c7bb133cbb01588f96b19b3f07e4584b899b14562da9833b0c69b343d772b3b0b9d0a4f4691b8ed9cabd9d181a32bb3a512357df2868443c61e792606d029d749e943432398fa19dc8dbe5cb3756e137231852ac92e01a31e4fe003339df10efd78f130f09c1cebe74c73454d6f969a239a09d3ee81a4920dc013a3bc1c90562a657844e71f9f19f3b4d153a3f855c5ed34b036b50900436d7238f76eaccdc35fb88982a5d08627110c3aea78ece31727a183156b031d02a1a0929565270955d4a00bc65d49fb9f2cc0ba2080e86e0697a869fa70db5c423cb32a812a15cb06dbf8d0e7a63a802862637e8abb186885e9e20a07393321994e763b0f9ced8c05b930f8921f685bd96748bcae29b6c617f41b453b467040c4f731e577e71cd1e963f14b1762b4b5501438f82e3deb9afc523e3d921f8dceacca49bd61d172ae1624aee099b263117112208cfa3bdc52337f3ef20a72d59754c74d6f1298c0e93eec9499e797aa2e2954559ac596da10adcfa7e4573d1ebe4246534cd745116d559a77ad2f0f6dd23b5c033627296293eaf18c5558a33a7a282a7389f19a3315e752a2af33fdc1268824493e8a299d82934e748249abc43d1f8ca1e872ec510fdf90728c8fc344e236bf97a2e360c7d8a319494e900115675c24a15b61f32d064fd3871879a3ba8e34fa9019d4eb9deba13d2de4fa92f6b02ce15b14dd2d893f68be4f75ff4786016b65e341ac0272f15e2a8819229edef44d0a71448bea86d874420a715856b886817138035ffd9724f619f8004b3398fb1d70c22c1513772d8d124c9cc2021828f42813aeccea0a5a48beb80c1befdf9acdb4986e4537806f5c8f3bb5bf83474c05654dcfd041ded78a61294500b78608fff737b81d98cbb93483cc4cdc4dfe62819416bb308911293f7ec4b358142bc7504b1cca29549751004dab22d8e42d2548b061b99b24695c0db3152904432693c56310a77b0d165a0628837064caaa7166d37adf6431ccaab5167143a687033c253f51ea6b598e45dea35903ac8132e86301c42ba39349b15ff3399bc37d66f70754162e6daad25ab36825126a8d9f444cfe2feb98ea0d0ab742ea4cc3790bb960f299ef07b2281c3ae3e06246775f61a4343327eb0e3c3cc0ac4fef05c6806ec60162f4752fb02c61eb739b27801be37d6f28693499289adc1286b684e72e65b8e9510e5bf591a526c264c40f843405cf16192f609ed1228fc2a18037d07f6583de191c26196e33ba1aba63f02d9c502493203555028d049986927c3da25b7269a106401aaec013a393851a7874941fd234d251098568d355333db6350abb4f7905c31710ee1cc7d44cc494912a92cd50db80d8344f045b11ca6c593066383ef2c96d5494edafbae2a9b0f2f8c20669e1051d1744b398e0b73719e051320437fae4c6a82565016e1479012f95185d919573176bbdf4111c7792a63f1dea3b4dffe10a68243ef01a62e2309377d1b3c94ebef59f13e64bb5ce20be4e4feb937f6e18301002e4e0f10fe97555a02e4d68c2d1aee710db62050d740544592dcfd678a0f4e8118f056b55749cd98fcdf422f6c3ff95e83e360f93ab65589cf7d5fac18fc943b43afacb85c042cc077cb83eed2a5bff74fed78c6df8cba6c4dc901e8cf84d4542da9d8a00eed3fe2e378e3389c13a8b00882f689ced9336cebb2644d70ba996f0f157c7ad9bd8c60b3770b6cef162671a6acb944457cca6d1e41f8fadf6cd1ac112a1512e4a8feb9226cfad41260d9175193aa59db695f086998e084302c664577c5a3d2ecf74e667d0e100556a7e230aaa821c2708cc6a0bd9fc17bce004c2a69530ceed49224485f067230e23aeba55e89ba94e10de8a94d9a122c434a02f75ed6fa9683b4d7fc4bcc7e1acb017d65a3f5b2259d3cd46b7d833f8e71037e2e7e7e32fe673f83a78520e3c19e14d03201936719525eb428fc6cfd333b612e74d66cb4780e3eab06f32a20059ddad4a9131a9468227d47be8e427a984746449a62a07b84b56cf0d9f923a894507aaf6f88d80bc42cb4f20b9f9e41a70f8dba7a8aa50f8f6d6d93c7c73187bd1c6c819afebd4cf1a26b5329c80e1b6e023724b40206c1e2096f35f4a7010144eb7ee3c8191fe417d061bbbcab826223f211bf0456e18b1480f77978148e55428e93a16f0cf83dd0dece9b79fb0965887fd4ddad6144989ecb72f3a6d0db8166f48666f5bb37ee3012b216558f7cb29cc4f963f5fee691f9afc79d01091935d4221581913e84bfc77cb4811a4e726b49565330d5b26931d8ae6928a20714896685dea1646f885d189a68a6034623f6d2a8dc571444f11b0a0c76d311d1067838ae1257c95f03adb5f458eacf1bbda0a62c7640b4fdf8b5014559bab6cc1f0b60995aca46b4a4045e4720eefee5df9a6bb6208f919716120c79c7c7488982356e2fd44d04bc184dba31be98cc5124be450717e17aa5e82ae832152a23ea90d468a7076109e12f5e03ec17c7e6b58cb79884eb3e587eb6d8bc584c9a029fee84d405fef20e5afc16e2516b7062f820b5fa089db9b0206e5e9c6b4647fffa067c381a622a9a6f28690223d4cae9f7792a8f76fd4349983627ad66cb1e9f60b9a7cb36ebfba551a1ec725a72a9809bada6d5fa2583e83242d7b7c8f029ed6262595f69221d4db32c51dd35c667cef2e2bcd296b518097987acdb5443574b459818ff5c991d2f80e76c64787071185ac89a540534d21722c10a218338b2226ad306723dc608419c1103fa11ce6b5d409615710e31f07647113c4d51c85bbb00f8176cf1945cd576e07dc0343ecd229a045cbbc27ad8db8ab67f46924de0fa61521ec45c4e4575d5fb8575320412da8018a4512b6089a216757809b360188da07eab682fd7ae5cb9a54c49069a06b306ae06a455d7bde669b0e5492bd28aeb3a730694c75d0c39533650303e58abded1335b4b4b8bd7006b6d08b633cb670321bb072dfd404b1503bde254cca141e679f5e9d973901a5da65dba5d1a1c1bf66337616c2488404a553e0e1e111d24223ad84434f144114372f0c4061b88d83d5a87e83e880e8e9e431c8dcecc2b07e630b201c67af8b3ef8a3548e0a1f6233d31b0f5579d319322e776fd6407a02afe46c296b3f4d266e325acd133070f1218c8f4dcc4075cb5ee2d0f43af0aea253d71627db7f196976e42a56b1e535d14a02f12a01ee22b522f6d60caf6291037706c99aab2c238e7ba1ab197318ec8a3a35018e72f0ab973c08e8292ea8f4e3e8b56afa6dd76a7a5c5072823a492b225f6828213245e2e28e3c50801077079d1d2ca3064b064990ee9f1b450ecf13a8fd77daea024edc7abfa1c3dae4a072df9705927906f119de33c38e4c6c389aa5952a994abbca6929d0a8963a7dc539f1339fb5b5d62ad24594da351ceaa512b97f51d898802b6708de5a1904cabcae8a94703102828a32f05486e16d841be2572f6f741541e7bc5f25206d65b33b2fab76010fd49a60112c082050b163f8e3c192dbe5682ba70414ec05f6a782925fb14b42781cf679da2a71bdb2af583ba940003c94c285f513e5a3b037dc82eb6f8d1b3f8fc2c3eb7f8d1b747817e41c938ca655da7aaaa9ada749908c95eef765f684d4886e107adb5ec25a84aed253a180f51aa2a226b1137750a8693af5feb586d7cc9977d5f166a595b2f6c90dbbcf152a6aa26172ec0931ff109a8b5b760441eeb6a453f28d4bc6a17a1275a69bca6b4166af25861f490475fb2f2d812d0d7cf3c1abe2495c64ba3ef88f5abc7bc14fafad6a341f4bee325cf0525d51125d54ab62ae9a5fc2642b28ccebbae9f2d7b0e02d9779ff2a28c22643d79165f4a7b349c80611559f485bedc388de46b9dde6973fbfa55bb454f958c8dc00aab42a84afd4a86db0602b970d1ad09f80b0ad82b1a55a57e8b0cf4068b4ae423179830d395f342e2259a3a7b87620d5453ad156713932a8e18831af0d041f0a2a636a26bba7a9e68760eb6b1de7196873541c8639bf4e59a219b25ba8ee8df3bf4d49baf805e41c30aa22331bf1ce4ecf137ab673ea0810d70a003ddab9b42fc801e38e66af2e55055dc7cb5495feea836ff1ff4269d697616674a728548216c78c33068e96ed9f08635cdb6b4b872b22cab5935c9c935db300805427d3847e644931c39a5019a2736084229218f2f052d7145b4e790a0a1734744ff2c454fd9773410ddbe67248b7988042d655664e1c8758206a3768511f17e16f352668486566436627db749c46a44ac3f5e27b47b43f42a85132f4a12415ff1998c1de41184420284aa4f49b5becdecbda5470a2e98def94979a717f3967c501f988f8f0feba3faecf8cc60ea37b8bebdb3aab8876fd691351aae79670b9e15e80e3df5e31f3f309ee7aa260ef4aa777a2eecf24aefac26cf886766417425bc555571eff4565acb0526e43eb5d6a9849a5aaa103f3bdcf8a83e3bbcb3716631a636c318639b6599c73bbdd3e3d94c17cc8f10d65a6b9f9a1edafffa2f96e411648288e411f42017c8057279681f84e3a1a5e208324166356590ab57cdb24fe2ece09847a3b7d495033da5d8b065d3aebf8caab5f9c13114535ab9413f00bd70506bbda95b0355f1ef14cd2236b9f7deebd9722a6f60ce657e1c3cdc2829042de224b29744d0620ae39c47a35a3b7591b8a90629a84b13e849fb5bc58ee3abe3a14b8543c78e20ae0812f371e268153de19bd341f48fe6d1a9544abba1fdd07868385c395aae81d6f7b60dd9647461d594c111c8b510843c52d3778e4061a25ec60b0353708d1b5af563bba07910477a056a05cd82e8d9bd44f880b6be27de0b85e87f59d5c482330b06bb30253af7de0bab263c8a218ea3106426cfdd6ab3aba40a6d07d1614af248cd194c9e9a9d873f88273a9a894676272f2b63516d77d8999a950d628b6861d5f1447008f9a2b8d0a250f4d4424f26a2bfddc1d6191d7a2ae23da7699f655dc99325060f3f9a3c5ad4ce915c9332e9fef315e8fef3fd9fcf9b8036bcffd8e0be4b8116e2d0a3e1bea6699a963539f385a1f7ddebc0fde775f0be7331a4a7b63b8417b428ef75e8de7b8dfbcf5bc0fbeef3dc5be0e3fd7ba00d18905009b17ae12a8d1bc08350d24b4d55530af7f92d60f2205087ee4d3e8326604d7d9e03e94b836e5695fad2551d1a94e460d54959e50b7a6cae17a9e45869447f58f57c05d4c503434fdd2e1e30ac55a0278f2700de83a728ac9a998fb05db21a643f84a245a13c2010b5baaa09c33c1e4ed3b44e336d8a92fc0655f1ff21f2b038ec8d955be5ac5ded6a57bbaa0eea4cdd41555599ba72282a2be6a9b2090ea32f6fc29ff022dc46bbb8d92bee44a84abb38d92bfe48107d096d79479c88f6e13eb88e3e5b494fd154d0d0bd075529d22eaeea156f2b1c48af9cd52bcfd145faecd279e832948102520ffbd44acdc64b2cce3efb0cbd4642e0fec5920c2292476c7f1cd93cb2d80899be7f9b1bee0d6ff86ed8bb0d8fb08978001b516496db9d6a180f47baa441af12dbd8b19249c504c11170b55c36d440456e2a765c00c29e21809510bb585a6d8631f65aab078683c743feb06d1bdd280c8710a000256ea51934f4d0dffbccc0190ddd67b0d06ddbb6ed738f032be0a10ddd8f1446c511e0d02e5daf388cc6884d6162a651ad37edc6d02bfe2c2c2d7a38f7388eeb1c438f62f0bace084ae955ed98a9215741ba488c867663200d975f3e876d32365d5755f16f1ebd236607eac2033d69d485afcb035e57f9e56be055beca57c999a91b2ed0403575cc0f2bf83877c0b81b5ccac4f9c103070ac78d1d380e8572e57c76a2702f8d5c4de1bdf7de8e0b29a903c31682906b646ab89842211a2911285de2ba8ed4fc8008123535406468e0e9f183269389367cc330c4212d77f80cdcd5b49c17128dabb97d46f1c24b24128944229148241289442211e7f1f0643c99a6216b683c246bc893233897c893f1645e40d1d389dc8a10fdbd1df444bd1d22b1939181ad206772371e5e544b247ae1a543a27c08163243ae504ee81542125a924289fea133b4238a41e990898282828282f22814e5fb84f29405d925a91d2c772d50605029180fddab227b2914d7a367f1d473c0c90be8a99ab8b2aa34414b2fde3ff4042db978ffd014b4d4c2fb87a8a02517de5fa4839662bcbfc8075afaf7170da125f0fd4545d0d2f7fe2730689717deff24869e4ede3fc40475497918ef2f3aab0986948c1f45b16ae24c22d4c49d55856c972ea6577a50533782712f91ab751e116388500151b1daa59be9152ba8a9236be86c97ee260405357533a21ca42845f25005b109621742013b182579e7e5373922b7f8b18389fed5073ffbffdd8ff169efc2b77d0b9fe75d7cddbff8bc87f1997430d0a4a793173ae941f4e7ce8ec92aeebece0c8e370931aa5b442564e6e483e19321fa8eac28a40cf902b151cc8a526d66af866b07f3cecb27dfe2039d9cc45092ff0bdf090c7ac51f864f24e38b55d309caac26915955ce6a1211a12afe27a9d265be5ea28b6047b8b325fa29fa839f68080dfdbf4f540415f910e313e988691711d92baa6a12f598691791aa57fc8988a12c0f884a946346f477e10b514143ff16bed01434f487f185cc2ff4e20b3541437f175fe8091afa77643585aca02a37ed123a4350f0804529325485ee46f490e9859ad099a27fca0df208e458805c28839119ece443e668e52594f71f05f1128b90297ac86cf1593164b66881f265e697e19c8842a06cf2654928a9fff36550a02bfdde97e99c378692aeeaeeb824255d9a5b737128e9ba6ece0b24e82e72d4ee7a43adb5d6ea2da1943e7d150764b5d65aff434a72dd60edb9c234b6acd65a6badb5b5883cb288dfed54643712e3f3fac43eb08fcee7fcecc4c8a850303e32146a43a13614ea88cfeb84ac26cf9fd4a03e2f7a6241b71e44df521b0c13796cd950286da5911a4b5b5dd44569476840d06c2eea637a8ef000a15750361bcaf5c1f1d03f2ff3f3fabc3eaf1865e7836a393b5a8bd6715ac71d9171f6699aa6691dd7711eadbbb71155e9f7b4b4644a82a130162aad2e0f43af0aebd54eacdf382093ded093d74fc97ecc66c3b2fa37c8e48d5bd20cc992ccdd379c8f906f11910151d02414f152f6e04895b83219cb747777d7d09d3da0867b29a594860c0f992be4de1f34aa297bf1a7712f8eabf301a8bb69d37f17ed47a98aab8ceabdff1865761d4b67f2e95a282293db4d8d325bbb59622cd6c3edb9eea62ac639d70e1c2f0fb16faaebbaae33f98c3a8e85c5f33a9c0d891950241289442291482412898449240e06c35986376a22fd20ad70ee700256f47412fd67f4a0269498aa5d405401ce6abaa9193ae80c1f68a9ce60019991a300b119a819290e464f94a382e80f8012002d00d874b02ee6a5aac9f36cd09d1e4afcd16bb54bf6fe1dab5d321001bde263f7121d8aae89b12382e89c13111d1c92819d10455882074388fe5d8a83adba1eafa37341178316802910f43ad22ae5fd61d13ddc1c0c73a78e8765055128310480286b31c6187777739755636f563fd906aaa92afe3785354de3609c4e4d3571a7c6c14035a27330501912b399830c8092b42205f1d2f63096e4112586120b416701658997b6875196a23f880251a253cf02f5a57bca815cf7f90b69554d241269b52291504e4291b4178fa7eb02389bce656161a9a691665de521de6adac64d1f929e5888f773956bb79a8de6ef7f343766ca43139806aba694fb208cf2d298025103f1e7e379205e69b0cf05f10e2bf24616f152834fb71b4af2c63bc8fb9aca43dfcacdc6c31b19bc63c42a20520f5b0175b93038a605f4742f0c7a50c16eeb26a65d3c4658224674acda48f3de6c240681f0fa47ab9ab04ad5aa58d5aa58d5f7de7b4d501ed6a8aa6933820996122b23324660b29a3e35f4a4a1bc7461bc9471a487dc48256c24ec26b9da0eb78aab444bd9da1d809e340eb37830cb86593672233f2e5779a1f2388a4deb486b6655babcf496df98383ab1d391f8921b8bc52815e2252a40a447845889bd02e2de7b2fcd3454d61d5c0a6ac42b0f5a71d07ac3ab509264a404ca8412362d053d8168b781e5acbc65f9a224240dba8da953bd09d0176f55154e88dcb4aab4398281238f1dabc138e7d1e83f0c5962b1b29abc2cbbecb25b9edb78e89f815d4323853c8e443a728ddaa95a1b605293abe8a1972f8e323e10846266635ea634e8362d21644775ecb614a15a6bcb12e3ed85a4850f6a8b10aa529dc6d036de37ee1e22e9e900d7693e1ce4fad4085da352a568558c1a1dd82bc7c3cabd6734aa294a82e1400a7aacaad48aaa379cf4b0860af9fe75d09570f71a0feb4dbe3f3ab9c4c35a47578d3eafa92a553bab2b21566fd1e4b1cf139be73744be60e7a1036c8bb60dee9b2e4977419264abea673cca3f0a9f25a42d1695e73d3410d06a5179436e79e9bd7d9b3eceda654457ea13649224107941c10c422c51e4c5e4f3f980369a062c70e205052d5af062025237c07883887e53ed723be5631584e5218f1e2e13853c7e0acb48855c16ab2aeec1f48aff28c3f626d15df08f314add8f2f88e3bbdc1f0920fe3843f4c7290bd32ea35ef1ea5d1811bc2918bde20f63fb5adc4e176aa233e80a0d550440490aa030e8100620fa8f2e6c2a37a3460e40807f0d6b9af6f9fcbb724c62508854ab9a156b86a4b97199a54dce6a655bfc86a7522218114a9412c18850350adaf2556a256ea1153d991c26e52bb859adb2cce599d95a1e3feeb88e7322932a1a2dd4d532d2248e0c41f4bf97c84612a10d29c24362b2c7a6ca2105387ae2a6ca1b6e69f178361c20c4c8a850303eb66a33ffd265aa8966ea65276eddc80671ebc3cbb2744be28828223d421611fda424dfba776d9d97e2494f6d8ac140dd45865e7125a297ae929eea8b92941829cbb22ccbb2ac81b2e13da02e1b0d3d75bb6ce00dbdf203d05544b9d5584249f60a6ab3e0121b05dad0cab22c5994d554a32c639aa6d5e817f068211d44fff0bc910ca3d1a8c48d70507396a7ed1d6ded9120477c2421c495c40d8a2421c4b435f32880a251bc5ef0a7600fb100634485dc56060d0d3eb6600c9e30f42a606300e7cbf618a419c8f448124570a0478ce0888e8402f465035baa8a13919f45644047a3a42e0a8b402c5fd70d59d645c145a0d56ad9500315b0f03d8083922a7a074f00c21902580943cc61ede24c077b3ca858386b331a04195aea9396e684a4a716cf4364fffafeaa2a95f4b086a689aca4e5a1596f3caca8d1dada32bd642d692d793f7bee7ed5ac37ad5ab56ada144c0b655794b44af238223f21398887b5822ac876d562d9d820d5c64b961c71115b31eadb4176b0d370acda1d3f2d69c9eaa2e62b6d8b92aea02e1d055da9f4b4a47f10417b39f4e4620e25d54f11226756082c2c54dd1097dedbb7298dfaf4f1d3c7998f2f88f7e97bb6a7dbd6f2b2793ecc6a970bc2e81510c60b1a647fd6bdf7deefce5cd243f776b9410ce182e897d584dce3bd91d1fe26a152d0e6febf6ad6da7bb50ddf4cd3b48d15048b9ef035728db84234578e1904638cb1a7e3b80e7b384e87eeb9cf7b18638cb10edc77de7fbae7be029ff7b88f6745c7755aee14518b107b3c1e8fe78504b679a703700572814a50ab4138201d500c748afea01708096809072acff33cb9b372af4f1019f4f94750e9a5912b41253d9d6e4039a23fa80419012501b269816ebc845f03b9bc943db7f3797feee525245eca92d013f7a177d1fb73dcbbbc841fc743772ee7e35e5c597249441f7d097df485fbbaff7c222001163f9f426610c8d56490a3e1ca0c4682ce1c03b91d0f331ab4a7e1f888ecd13a932ffb4ffe60b822677f69e88f8a1fae04580233449c8540650b558b1d3e430854761f154165173a7762e02afb270a1a3abd296be53b7ca6cf6f25b4cb15c209992157082764561dd053c6d2533415aec37df0215e8437e14fd41dd49956d2270c5f5955c6a1a0ed6283083535cacdfa822d4102d92eeda3577a5053af54edd2457ac50a6a6a1f44b03c07e5e16cd711dad5ae3ee9e904a4011b3ef20ba3503e7a152316c76689dd4ae4f8abeb9295bf5add8fdeea163d792f7a6a1942a41c82072c2174f458b92006298f022d51819660f4c465f8c7da02ce5f6e854321c32c56e7361d07f6ca07cb5ffe2a6b58a056b666c59ab1a4a5b137d6654dd1df96d6c6e66cb09aa4c22aec0847b0adee47afec32f30857985d551d0f7facb072b5b228bbaa2a4bf258612ec410b46479404bd559ceb22ab03d5817a02caac2aa38569845a156166557dbb6e56d3bddb2282908aae26f44c60821a96c59e78e97800019b14658214ae4cfae581e841b71235c48ce1e04077afae0809eb81cb48b090be659541645b604bbfaeccaa6400be3a145d91528b340b5f8d134b11a4fe491c6b41fbf6590021d680d1bf4843d30f7bb21866b601c8b65525c8fa733374fdfcdd37d5dae2930c65e87bdaecc9bf5d0daec6a18638cbdae3deec3793c1976c69678d88dda54e7ad91efbdf77ebc118d99e3681bd243dc406c990672016a03053a138b19debcea95458a24231b28c93f269a3378ad580d4dcb7921b9275783258fad804266b0cccc5c594ef6ca90644b6eeacc766eccedee7befbd9c8c97eef7b330e92986d837336fc4ce6eee97e1bc11727f15b39597827829cb4cd1333333352d07858c69d163654dba9b3685e18a6caf47434603064500667a4845af9fe36aa6e12c2352cd231ce1f542e2417ce5334ebaca7ff01a380fc7e146289133f0fe584dd3636cf017380c5c45cd51965557d587e1e1054744d07ca0fae2dcd12865278f5e8c583c311380e8f7de7befe7625b8c319681edb6d65a77f75636b3f61b8d46f90957a65a1b0fbdf46e4c1289238c10e24a2073067aac20645f96f6341c907865e8294b023d5927daa5ba644c6a606203efe5126b3c1a11e99118b3d4d1bd5d90aac5b41ded3ca2c5597db544acaf9ddaceadda895bd8d44eed94421eb55303299191797900adace566504e381aae861352724338900af2085281545e1ab3d70e2f8ddc0c48c64b23c712fdb919969734d8287b6559e6da887835edae5d29a5b4a44103e39c47a37f169c2020f1175a86a35cdae8216c94cce00505978479c18f0202bc601aaba0cba03cd5270330a6fd638d6aa22f54d4de45a42bfeacd30228c95d13426e1204dbb7175c55bc9a3098d2207d4991a9e068243a8a9268d4c0001fdb6c929298a0a61e8d401cf9156c1a29f75bbc29b774958c4fc962f0d05b4c2992b1200547c9e0281617a270248f2d9524354db356d4ea2ccbb26cd432cab22ccbb20dd30d34b36b35cd6ad65a6bb32cb3d86ad9c5ad9be6d138fa460cdf7befbd382e0f0b98265643053177f7783c486039eebdf7de7b37cfdd3c3179b55aadb2ac08efba01e55db3ea39d4204115d965c8f02854c1874edf8bc3cdb38161278ee11572e894064da53eb3adea985c451e6909e318fb2bcbaacddc1da75218772656514a2bad36d093bb58eb7dedf4d0b5203416369247ed3ccf7a6aac540aa6a5764ed687292cc85acc4b54d4581a4b63d55a6bd558d5c4bde3a02aa9aad2aa20463f583c4cb788abb4cb46ec1faf8d387e4c1c69f911d0325b6bfd8fe16391a10501b706ab4c6ca0a16acc9ad2a67583e484754eef705996711cc771dc9b7cdea4874cab4c8d4ccbd836418d3e410a68e9c6388ee3dcbd650574a57f25b68cd8b707ea44d7748de769284f084c6c3425127d44229148247ad147240a31619aa2cf13db4f1d161f324355879e32d3a7a082965e9452debffa404b30debf0ea9455429d0538cf7af536897af4428d92e20e8414d39b0d4ea326beb88a843043d2b90b35d3aa657aca0a656551567829a9a1593434ca92a0f5809590aca1abb2502dbccaeb679b8ce6bb385afbe0b9f7d18dffd944ffb17dff62e3ecf9f7cdcb7f9e9960f0919f4a393a0b701043988623e668ec984d835e7071caf0e62627cadaa0dab2a2e52ed508dffa3118b655e0df740df9f82e3bd213625c12a112291f72dc4b16146dc0822a2ee452270ccc0d15b66e77d7050d30bef27ef9f0afc71f317c066e10c1bfdf862d5f96a8caf4a8192aa0b76a465d621b588ea030dfd53beaa8386fe2068974af68ad79876a944b0e809248753e1279f9b94e46e85d7948a8711382611fd5b80e3c8c42cc010e4265e7949143a39c94c1c97c907a6938ab92a0fefcc252f8d89e312c5701757586a6d5b1a869452fad4be6dc109790cc59a65377b01a514538cf186378c31c618771ceeb8af0679ec546b3c105db5c3762a956aae068d07f4b45dbcdd2aaee9d1344dd3348c354ef3e08ddb380fd6b66de336ed87c663d470709c67db36932bd4ee5a43adfef7358dbedc1a5a6f2360dbddadf51103e7856a23b7165953a4b4c981995b9d9ea8946cb8e7b8644800000000000aa3150000200c0a0583e170483ca06aeb560f14800c648c4e6a523c1a8a634910c338088214420601028c01040021033335641b677b402cfcd3c5c11d43c61ab966027404c0a758a20cb68d367e4ebe4faf9b30b77ef9cafbf4d1e68d9e7a70d2ec3f9115781a52c2d83eb1e57dba01330a93dac977a658e54df787c709dc22b6c00d6f78ae9a0230d43f6b8d9defd301b69d3f29ae843efd66f828e6fb7567a87cfacb5ccef6f9ae0df5d33214a13a3417044c9aa3f604cc4e7e941afe74fff5ffed5eeac9be51d0fadfeecd6693aa5e480255b940b009cb09c5354afd402fc72fa500a738fa51f1823c6065fe4ca515f09f484d7afa810af44011074248993e8bb8c68848eddcfd187684a7f720693ecbf474662fd2868df0560b3b3084b676c02db74ece599b1a9dd92551a5c326fadc13523aa95ac09e5e96c726ff97b207306e0ae8f71295cfa5d8a817195a69f592c2a998c71b1f9d2b1c68912836b6b8b26f4b90a3d1e30968daf94a78f0c606727e72742fe8b51bc0423cec94a65c3f196e4700cb9b3a9e1672119c0a69c391eaa045fbd8081ec0dee707f64cfde10c3e71e118dd29479b9dd6083d0924ec390abf5f8fbfad7449f0b111d63c35c17cf59cc957c6fe5114aaeb2103403abe56ba1c9dd2cb19e11796153e217a59c2e7261d86d5cf5861027a10bbdcf8a02722be0fede1f9f1e3815f3fd8b8f0ba4ebada2c5358d3203d8904baf99f64fc63a3d95850f2236c343e401e908d26fa474303e2eb6473be26b6d1000089cc55311fb69232e8998ddd6f5572055ca0ed15bffd6c0cbc16e0135ce9b3e3ab84726781fcfdf82471a1df12baff090cccb8c2442be11c3107af11636febb3f439633591b27e50f4f9390c5afb7e4bb2f74d9b2b778edee819f0a7dac28cd0fa02853cff276b1b40e482224ed54c462c6bda1f7651981b2372810f4c3db3d973caa4594895b23f00967ffc127d24fc3e2649ced0d6bc6051f994df0979226e1f3af7f2f4200a762c6eee4e453bd3cccbab342ea0f1b87762ae0ec255352d6ef65d27ebaa1ae4962188bd22c78b37aa62c98dc72ca21864ff0a54203037bb9dfbb0e366ee897407467d8125fa3f2dcedafb7e97734db6183ea4a5e47559039dee991537accf44e9e487a91323614a45d0acaa4d73396ebac0a8ccecffdec87371fbe2677a19dd279735697efb5530d50efedd54e33fc892d7aafb194338fce92698df5e1aa85acce3b2e642c96ec02fa5b41a42fb1e8e41995f97a5f09e6066e63d760fafee91a842a6df29aec76f47b68ae0a6892a77cae02f47bb652094df2525e56807acd8ecd871d2ef343b6fe876f8bb6f3cdb14d6dab13ecc572661e3814899c29c410785f2e741a9df816c43c8673557e3d0a5bfd51b4f9c2080ac33f5917dad740270fea61c8ebe5a30521e1aa63bcfe6b2e45542077ee9a34c2924566fa1c2384ae8b82c5240539983aa3db554e8114117722376a58c8bccd4cd54175cd38a7b50180ad78ff18d96806b2a932e7c8e427e2cf5691b8274100420cdf3d3847b038aa797e0b07301a13b5fade90afe198148c9832952af6bb3e725718f232295c3aced619b02a49620431d42bafb8d915cc937116826374c5daf72e45507c8cb46c58e942cfc0d7a340750464a3bfaca75112e0f60bb1cc92ba567a36ce955d8c3351e3668d5a77d33e7e1284225f6461f4b1ec6d11c05bdf40522aaab0096480dab1a4a0c2f1e68c144b2c6a07e334151131cfe25b79bb6c38a4f7bf6f9abda0a6d52ee73cf7e179bcc126c842709c25df15889b4710577727c1ec0b281ee3cc40265c4b2f78cf7d08b2d0e0e501f0d3b7da049983e034f8045fa742d1992d9ac1182f6722ed0ef3f6421fa7816da702e549ce8844bebc415e231ec5ea5c6fa3b3ca4aa9eccd2a412cb21afe9e3bb61ed85b44e6bb296a12fc548526b9a78915bb16bc0f7393dddba0240b6319f94e485a22a7cca7498fd231f1d107ad1028754892ebaa38250abe936fd56b9432d78a524ae84ec124437b0b71925c11dca557e1534242c8dab4bc43c0437ea89db610db001f1cb01f5f8b3a007409317e016f87a17fcab202d08fca2d7983224de71cb46aeb90ca8cd222325ce0ca348fa3188d564bd6ce512962e3fa74be7f1ebb1c3658672ea8aeb44a958e831237d286ec4b9b6d7fbaf99b42b94cf6d3b00b5e7d61f7796b491441203bffa1e0e2319ea4441e88593fca68c828a0eba1913711e6afcdedd84e7d30a04480230f3915452fe7fc3db4eb69a0c9f85fe887bab71f1cba9e18fab31d936b010f5dbed4291b270962c0f7edb90a9d6e470edadaf44bf45f04fc7bee387f1d4dd7f290e36877033cb21d434e30d1f8f81ce387a4f83bb9eafa38d1ca5e2bcb50a417da01a93f5b811cb1b37681f424c129180471ca95762a6f1de91cf9ec0dd673cb88d985b99a182bfb511a865358a7ca09914606893ef285a1b1940cfb40cf33cdb489e3e49d1c56dcb874985880507a4b0b700f13aec1872e1401dd22f2980a1c5f6deb938bd476507d11e5fcabe146e28c28d59e15cec46c5ce9c3166c5462623bf529d9ebda0d40d27aa00b2d39225e22a8f674e791f004d6cef6dadb4e132129a653d1b52925a366d91d36ab4f5164cf71bb7fb1c59af11de096c43a1d1f53e4406b0f2a319aa53a4fd57958faad761ac1077866b11231fd761fa1abb304e3d3601885594374c63c2132d3a1d16e2e8d16b746481506c923ae78c6716405275504a159f383623689a4a9b38ac7dba5865d7d57e6a7bdf4f1dd086b14e52d1a46ee4885bd1c548bc25b3b2a56b25a861561f2bf9e5340c0bd3301ac0c6c05fc2dcbc6aedae5c63c8af35060ef62370558a17ad5b03037b45a8fbc6fb7eb13b8702cd130f15d0591285179740121526b60b8d80e1c2bedad62eb7d3203c35548e4540915eb0961c85be1d8bbd84e7d3cbcff8b55af43695886df278e1796d2b98e5875d836a506626721e8e738b10af55b27b7810d5b8f2d613cccc2e33b6d7f35aceca5f55f79d0e6b4394829019018d009c9b01ba5135292e4d0fdf9147070b69880d5fdab2e496643141c58d5648f950d8756c89f642bc4317cd7daa36ba294454d5781738e9ea0b961b22f488a89fdf9074b3cc50a8208332d481cb18ab067d9a449fd8939036239b4f34dbc6115453bab74fdbad7bf53400c1857558d529ac41b7f748d0de0a725a5ddb7f4af2b3688f1598fb8e2cd5a8be3b29d27b47ae279b14ca4eddb0b0291251ff38265bd5057d40e81ada04e1ad5e5c1e9f104a0f37d9621c0b3845c9e8c82e2665e137fd248a1750bbce355e9e8f3bd14cb0533373434a9f498d2bb9868828c541ecc43912e79b6c39ea29ecb21fca7bd9055de88feb08f51a19c8566e43351b8d7c06dd88e54e7b4fdf8b4a2daf039a109f6ffcc24f9a196bfb922a5d546c5ded6a79b140b95038442749ed7aa1cfa1b10234e516a6fa6a7719572c65f7ec20dbd241bbe723ce8ebaaac71efa79594b56bd3293b9f8f612df70182b945877150bec2ee11317c601ccf2a7a70e41cd63f24050c494d462b069bce4ef4c697d4ab20c5ab7c17cdeda141fa8cc084811bdfbbaca49c61352b7cf009ef86d71f894e4ad7340b0c8b07968534cf71af9ce6424e6ca42fd956b823e2ba2f3e9acf71b205356d9ee7570779648af12214cd80ef5fa0c420c5a93f74cce00f874ddeb19644c8ae7c79f5a10171df515b328a181e2d5f7e03ae9f7f6be133c351dbd840b41bfdaf07c0ebc32618549cc845e887888de4b7b32ba1dd11ff4dbf04dd3643aa063553ac6ce8782c45bab14a1fd1dd56fcdd75869e629bee48fafb5ce467d07a93dd1fc2dd14859e9d33a425f100a20ae74bc2550747da583e09b14475719c1be3ea41eebce77e4ab34d976ddc6d685fb30376f3acd97af92f783a4fbac26371e3aa76c4d1c945054c76ca10d47fa6550b48573392ebe811455f1de043bf2ffd9241bf8bd0ad42ffe26947ab1cc6d218f2255aef0e8f07d7cde1a4fb56779de11fb1678fbc1807ab2eb01f7f2ee4b77cbd714209665d7bff55b6f8b5672fd2b233f910983a055911f261377ca41c94ed504993e7f7096123079e76e2bdac2e431404466624bfb8ca0b01bb6bcf07d9fcf0eac958e89097ede6d476ce967e5eefe247556d0ddc1e6e1bf73aa54290af089b6c112591cbcd7639195c9d4d0c06c6119fcfae42c355d623e5def3688fc478f723802bb2e74141e260e8a991607d0fd1e47f01ba9c011e892143638c25b0b2008a7b6aee58e13c640b33bdc0fe8b303913472a38a55a73cd1eeb9ce3d0ea8fa4b23d4e90ade2934421108e6be7b46b0dbda19219f3125402f674aa89e3cd8092419590796b6c29a7449c8e20d5f5fdbae3b18b0e153a7a54692160254ae045c23641b3b52b54b9ca2fd31c2d9acffe72012d78c52d390a9628d09e353387f3518fc328b55a5c536a750eed6be3f59a2ab740cc6b4b89f73038a4155552c1cc447a6afddc4861f0c287859c871f97db42b039ac64734d3aff39511e620af63a614148819985358ee372dd2558dd65f80bb01994c6f0f177148cfa477fff0a41f80a63da62a466c43f30a64201538e50879ef868ef508bfe442046a48ccd5c12df40eafd2e6123211c9a0660a953db56ea53d320e06a622fedab56f10b5014969ea8bd3a93b555835131da354f61fe1876b2a7a0e10ebb73ad9597562fbf36c0457d1b7dc46bd0912479c6f090660af2cfe508894b60ebf38b9e6b2235193bd349cce640a3d674b451b938ea856313cc68090f20f6ce2581c822f006b00c8988b5173ad83797d5a3d9d93c6da9ef45021db5d4d07005336c12124daa43076c9570c6d5d8b094a2e71f4555c33fa0591b5b28ddccf9b2b5069d1458e9a59101d2150bebb4c7bbbe6d5c7b55b6f0a52838cf25cd85ad8d877b1d8140b51356e85b9c7c2b2cec6a7e6e5a66d72c5dfb8fa1a3716ce6e64e5cc20602572e3761c27b07a05b08bf52adaf0af04ceefdc3264edb02ffa5e38c2939a7c47de9cc0fe73e60917bf7b2abd60f0693dd3d7b0548aae38643ec18827a90098864d119b6700faf4f9b625896da381dbc2e8c13761b5ca2e8b996a85606e123aea7a7fcf01b79af274e12a7003d42232b509afb9b3d7225a13d6b2281d64c0af7040e171124f62b8689634d9754c45e2f79f4056467cd80e2aa0a07cebff650ec1709f98949d676fcc7adfe4b5837dc9c366e101ab2cfbed31f31ac0de87989ec21f0885205af50c9b7d1631603ec91645809f77ce254b58cef219d7e48539de99f3d73fe9790f5474720cca500c99cdc01beb59f96a89c1a83d9d712d522b1e4bbfa6271aed0197ad11956324473f41f4e8a50acc7d4d4cdc9245864f91efa6f8efc01f2c91eef7c0d6fd3b24f38d9e61ca28642a1c80af41422c8f26215392597fea0b54287ec174e8ae7b1a898f9f0627bc72566b7ce3f8bb6232f80826e279de310c24b99b9b1d75758fedf2920a5431144e7a952091e07cf7d1377fd8e28582df94f73b5eb9b84afcac7ef65ac59a9cf877c0ae5a68dc9c290e46bf09a1787a0ca1e3a50ee8fb53868c8a5a319dded65194af808d5c13623c3230cbb9bcff8881310a6b3f2cdce3f781c75299d94b9c43f272bcea4968a0906ebdd62b80e74f9102b219c61148e4a3cd8cbcc51c8e0c0fcdd5c82af3c624eea112fbb349c8ee084c4738ae47fab4a3ab989ccc92d0c73cefa35ada65090180c9344715d540eb958c8e354123404c5fc09bc5fd5685c92a7df5eb63f03b9dde9b08847ce2aa82a73e2d35d0d35a021a73ad88d9e493062456becd6ee8966e35b7e09c0fc4428d9e59b92f3a1ffe5920bfcb05ff0fee54f56806bace87f59c23ce6b0dc97246295330a60103d3bdc8243882fb90b16e5623e4715c877830b8afa050c90015d3401fd7898924d8b27dcd9aea4dc17b5b3a9b1ac8657f0919154c649afe6e7d32ae2a052fd5eb45ba8e51e94334988a01727692cf9597dc133cfca636e3b3fc8eba4aa1506c15de632d7473ce6e1cf7c3186871f212670e4cab50c4ada3b971144777f945d8ba22e1751e82df9b356984a762ff418aec7e7ced3a98c5ff64f70b90a672152d8e2019f212d663f36eae024e9990b892112691bd478fc2d1b572b03e381ab1b87e6099779cbb69ac39729df07b8acee538ecff3e39ceb075c35e6c6fbd7dbe953ad8e55b1880c54a8907544701b5a9b6884d8dacc12240ba198aa925396254e1988c968e84d2d1515f49b15c77b3f39ba6a6ab603970b03bc13ac06b6855e95e7c0b100d5237ed37a2868fbd58877aedd347af2aec18e8d40b6f77abe2d54816f25146128adad042da2ff0f310e18c1ceb67a91d0d37eb85bc4f1b9288856216ee5c54b9459574eb5212a98ed233b7257710c598a097dbada523a16ccf91aaeccc5c680e84cf10776500bf86d4a88d2078091d835248e5f23a26b2ce2ceb6f486b7e5857742f13db4921c50e1cc2833b86fb4f2ffd6f495be480885fd40342673ba30f8c6132398c3d187024d1e90ab6e8f17495dd48d6e9e3973843888ca489e4b9fd71d3b67caaaa769fc96eed16e24dc77bb7bedf7922da000f4a479f50a4d2699140af9de60ed21b833166035d9e575af93728ca84e55ceeb01600d8904804baa92f4b0f30fd36839182a265535f3214a920e4f89e81400430d84bbc64248ff1e29e69b74033db82d23ea9e042876e7e20f9af2bc3b514a6c6596eac2f2c64ac68055aaf4c33c2bff33a54513830a587fe6b9ff3c2bfb3e081dc7be9ccf37a434082d1e6098934f73044b361a2835e991deb4bf101d7052d4dcb72f4dedce29247219e9c818daacbc4c4eeaf946cd597b0415fff97095fe5fad02e21c979e6650af49b0d123e80744cddfe1c03a0093815807032e25fc10abc89d1f4c2d940c16ab1848724703141c1cc0e252b2981253887d6f4b1753c7dd96067dcd63b1871823572682b913f016fd38f5d7365b567d086a8d9bde01bf32d42da2c1dc7fce2abe65d9576a6d1f53fb5d9825ef10b138dc5eb7e09bf4a9cb5bc41b71d104b3fbd29bb007d5e8c79cc2608bda9b153bd85640ca9d2bef8c48a01af53518bf5f4c361743f2eb244949571f61a492b11855e8740042b9e76bda88d2ef4d0e377baac2e74ead8c5aba7ca8bdc51afb13c0dba4c0be76853ed636b449fcae425ee67b03cb36768d27df771a82d03e92d2acdff29ec66aa79da2f90b92ba52fd3b87e2d60fabe9e635f13d1582476aed15ed1c2b14aafc2b8dbef20831696e8836528f5b34ef95f2f585317826f9a5c937da65efc3b64073154ac6d2a31b893ee466793d48916e4af70a05e3ba0d71b61eac35761acbcc098b168f81c8dc3e82b04859415b55be7efbed09369f61fd17e8ada4fc5642eb0759a62ec3fb0e1faf0abae4fc30ecf01201a22dc787cea9b3190ea8d5d9b044eb5273cb101875101c324ae337126cb35d26434238b0e1ce9d13044ec640790b5713cf80557657fcc9efa6fb0e7c9a6a12d911cb30769cc7320e5c4a1345b9bd612fa8fd9cbb116c0df611f85116c519728f7ae540abdef3b8c207604d72280caa1aa03f209d1c95f4dd872d438d20fff1de127f62f5640c7eca35c7ae95fb006e73659765c96b32e750478c0cd9fc4b2a52924a748611a54d815e2f3ae90e471da062dd6e916e2b3695747d947e50ff8cc65dca1a4612ebfc913995444d8177d2a1d4a40994d168f9f87d1c126863f5c048237bdb999f20ea52dc4c32a5f3d0911805f142a438d4ccd2c80c5827b0900d3e13392c4e23f668f6c7d7d9b0ea13cf2caa441b693c0d8414d6547e21380a9bad7c09d109bd81bf1f7a338f73c3eabf34df86ff6ef3271fb2e7eaad3773379225c868690766e95e49b3d8217562e1543069030c3d6ac4bdf55365a583eb53ca1dd9db80a9434460fb5fc8dc32bd0c66078aa7a6f6960301660b7e1cafd086ab52d578c090260bfac4472bd86c27f4000c17984a383b4eb873d2baba124f371385be34b782d5eeb8340ffc3d4f789a2661e82550a6e506efcd8353a0631c8243beee03187eddb66c4414d97207fbebd57c4da23b240c4550276a71d7773a541555720317776e239dd444dea7c30be3a42c33220e8a53d1613f54b12bc2e8ee1bd3c6181ddd0d5d39fd429ea09fe29c815dab5a397b40121c18984ebdba9a95f66088c58a6debae47b69fdcc03c1abad0f3a4b934e4cbf580b5a2b991ec1f23c0b77699ceb12727157c31270fedec912c978882140a045760abfeed8a0bb3b7be5007cfe7f1c3c0e898c1435c8ab42f4b5e3e71803ea21f78f96a0cba17e94c300b10f9b944512ff32b8b927abbf70e9a0c73a2360eb105675ff6a11f52ba8a523b758fd6021ccf0a2bdbc16ad454f281647930fe811705c83534fad2086c012741ae3d3a242a9d1d56875941add21ebdcd10cd1ce98375515a22df0187337c1299d3940bc5d3e6d72d7f03afe59731ac2ef1184963a4c738eb66feee5edf3c8344572472f0f9816534c2a70c4ef2c303299996127686a09d106ae52426ddd759252e959647f0669ea029e497a65cc65f958732cd21946bf1285f52ad3f355085f8534e289b816797de442b5d96b96d30989e235b91d026c49d473e2246eb65c114954cc644bcf351695f128ffe69ccfeaa9532a4e2d64e99974b349dc4bcc14a2f3b4c5953e55c435be492460874c5f6e0779d680b3e30aa515ee6b64d81b01531a37e5e3c004560463d7b0b2d5332c4d2972295370f7ff468036cabb2001d5b0d22c9bffdacac80d617326de7cfbf5c3340ddde9203d00a74156f0b03e5c2de68e7bac1467a5c414c75d12815dac45b4a353c1194a20683237a9f2d71ce35588fd01579bd23a6c276fd5854ba3d93d14e55a512ed97125e8bab330b2bf4be32d65b2496f08eb48bb1bd6a1462abb1cbfb1b385a06d2902a46d0daf933b67c71b2cac4a02f70a3036b75b7e1c7d98b01f188538ff3f88f950ad1c1536b2f8063cfad95c27b213bccc614b84e059eab4f7e6f3ab7d1fe965d739329bf81238a972613971e93eb4174ebc8d52a2ca6ea4eaf6d0581c8d8a544022511905320195e9d4001f9a6ee66f7745f38ba5f1af9472731817e8be6ba112942ec849db614ac17f4d549e77efec20a6b210af261595a5fd8df9db7102a572415e7b178c98646c698d27773d022e9cb445f6f93c7890e4db12d4af61b654421c45828f727fe08dcfe03115acc7251fe12a9dd19b20384a1158e9217d1e8682c8ce9495d6c1b984f0b95229b60c7f16537a5b11828147a9a24da002e6bd7a583c161e2ac7ad1b3c5cd830c4c5aba7f97b16a4d81f99503cd96cbf6e3ea55b662261393233c967fa27f14403865442566c3e3135b78305beca98610571f802ab30bf0aae1955cff6724fb4329a51b7fbbc0e240a6594ddf0334cfd1e7ea1f61cc3c185324e8102ea7bf6ddafd041b5dadf4140eff19f547c2c2ebd32c56249ce4774378de469d6f188fe45a21ea09e70ccf12e8ea703308972c050424dd32dbdf2d2e7a9a5ae10856bffe5b352376a0159555719013fb3ce948a53ad7dc9766660587daf1ef7bb44037c50501e35b52d2619c0ae1fda4d666cd7db3477562a1f8aab2c7f607bcaf6688132cdeeae589b53fafadf54b9963cb5c830c7f0cff96e04286a52f5529ed5d59e67f1c0c3f8754c43d7fae30d9d6ca522a5e233f31de100bb8327a492058e44c80b21765eabb8c412a02c92a4dbc770a3f3f848142d0a4e4f04ec09059b6f48952e0fc2561398fb40ba411842e22418740379ba0e223e81fe8f3c959be70fbca9781b8a8222267c1279936fb6349e58c4c42cf6d66eb259a92eb50767b97baffed578bc5e0fbd6f019e1f12afc47c1fcaf01315715e1fe8263f32c7d00b5cf1492350d7fb21e8ce2a002cf57d499db26321e10408ba83f02f9d529a602103e125c4c58c11ab67960ce2384eace5a4c601c2194193340d4b2814dd576a416310f35edf33f648549ab8c75055287051ce8f8ff50b82c2867f7ffc85d82bc84bf0153605e163073103d397fec33a329350c0885fa5bcd534759c32ee93692ef8701b9d52d22fe9799b3f8701b402f1f3c25f98ace3e9f1d0b442e13d408697f1458e6da453960cc617a2c32d8d076923b31995fb81002b7ce7e2c7eca999b8a4d0c070137c2ac3fa65d15e0b0c55b812e85681362d9676ef089c4eca64e23a414fe9d221d618c7e5859823aa20cef9e3fc52b4ed2b340d7fbf1c5e3642e2e90cc3017a985ed13e05eb7bbe1ea3292d4ce9aa1d30bf406f2058c31bb9e187ec90a668577edd81149623031ce8905271736f65914391ce82b5d70206996b88ff2fbd02715396214adc3b6ca91f40def67907b41f809ceacedb03467eb2650272967f5a24e7229dbdc33e1080ee136aa60fb1459acd129c3af2ff126ef8f3087cd776be553b9f6118687fda17313f01541c05301285a93639f48dd61f0a8046d00673613ef5012df9038a4836c0d4897ff8a1b41815279b05989ee73b6f7227a1eac36a36f79c9f3f03ab8d79ae808eb4ef086a9918ca240b403a985e199627e2f5a5669b35b359ce3a16c09e4012b1129264010deb6d7afbd2a3e521ecd62c805f806669281d033519697d56032f2c16f380f9c50b259355ed8389a8c71f7c75c5dacec18cfb172e936d299a2dec866b759bc06acb3366eaf0f24e400697983ff6bc78f1fa696a4990691ce722a071f60e86587f5b3509b03581a18dc41603f6d6885a2701ca048c687512c09f77e7c5af6b95aa41fc231313da498081b6ac04a2ef0f723e2b1a4f021418fb4827a25f138662573a5640f4edb106045179884158a72ef7dc395a70f299a5e3579024e037260699ce44d1e9fdc941a412f67f13362b2046237bab0e5a7cf4924d95a55d3924faeea77c5741bf5b8c5786f1e182afa094456bfe1b4c3abbec6ade70a27062c8f118dc275c5c43cfe85b67e304bfeee16ecc201c482f43337f85d277c03bb49141a9c2fe1715eb6dd8966947968169d063dea00003eb45b3a2818aa503f16f86eac86655985249b254efc8078886465562c86122ebd596e8f7d5c2a6520b882e854d2ad2bb928f2ad4ab4f785f1c72863723ed4e1b720e8b058fc5c3dab431ef94eab8c2870934f09de430375c04002a5af1ab2d9bc1593a2678c0bb9ab0252e4ee8696a84f5116805df0ede2ccdc084e9fb126637b8f531af7d75a480cc2535d5f35615f69a04f5cf92be9301dacb7ca00e6388da45805cc64b6e7a79122c200d41450a2a1de56f6e27e46f9d7137490c4e7e0a25bda5216782da58a1a1954d9c66774e178f5e8aaaf30bda7f9ba0a29081e685c751ad98cd3d77487aa81dc4dc0b02d795d72592e895a2a89381e75323ad2f216b9ef82ce0170e813d9c1a0f95b05d3992885b3199408b3dc46176714e15ebd17cc52889f5520513f1c9f2ee508bffcdc9b096a0b72c9922349b675976cc6f963b8bd80a350661216e421fe27d8470e6e19c27cb09e473223e4901dffa6d67b71ccb1955e4f95911044573b16bd5f2b322b402ff166e9d7544d251cd96e853689158177614172bc49a18f370177821b2fc1f152f457e56a0eda66bd5cdcb045867ad2a11d23675fd2aa2188bedd64598331a3f2b558cb0c82e850874c959f189741ac675c53296866f566e770e7e565005a6cd04b03c9a6c3bc9473f2ed3d9f494fe2c10c1466db60c6e38416ce7b1f4442bcc46d4d49bff8b4aff46dc0ebdce240de60c4c28bc1d6b2dea820906fd1428e05e84b1f6bfc894c1942d8ddd3863f3ca426c2ef4d63c2373ef12e0231af4d636add102d370bef7341785be94619cf21a9f6aefda5423b807d2b356c78cd905540c2452a48e0b5860e821b20df1815e5c1d6669451ed54452f8c82b9ba16aa29875003920510fe793bcb290a5ca6824dfe1d2723a42a1feee48e3dd97ac4a33c34a33ccceffc60f7d8154369a9c3eb6bdccb0ee0e3ee9dbf90e4e2b8854f6179a6037a55e4f1b469e2c643fbf674d5f5e1484b170d08409ec3a99576bef57765ac556a8a7db6f9de6401e5399b86aef001e3827d8b2acafe4a437d8ac6b68c750d340045d056cabf4ac76fedd6ab5787ddc42e06ac1265f9b7923f30ae8b51f81860446ebe82f19e4ce22eede5d03e65c20312ae1e8880b9a651b3316b4b9d9a9e11f2ff4ddeceffa8e97dcff3de822ad1f094a5e33c619e8be3c0a366e385f9c4cc26b409c8692cf8f72011853f199300e3022316cb8fdcc5f1aadbf5015d4b0228520550dfc30f24da01c9238e89668e0eb23f40618df589140eff10e24a45a0bc128b399b9eda526850af43ece6ea42638eea07bd448cdcaa3f6411f9f3fd94b792ae7f3b89563ece7f17d438a66e766840ebffbe079d0adc40600c14074ce12314237f2b2d9cc6ecefb6f36ae5a0a7520baa5cc05dde555e168abb6f60f5c0d98a4c76831d80c714c2c20039c7f194870cbd6c56fc7d6e28c30e1c7bd6ea26915998b012210b19ded24df12fc113abca8cc62c151aad5d145c5a9d777a4707b6054f9e60aec4de7d8ea06cbbd80d0383a01a3aacc833630bda4a072f62c5054f125667f8b331138302c0e3ddc331562421831945112282b90db5bfdcfca86032486922d8b04031e82cefa527528a884873d9e9dc6d2095c546301b4e1eea000dcb8effabca04146b54e5c65325c4cbb65f26366088685b282abab6899dc6ad5069bbdb2c7428a9d465b34a18f66b3c0dc33b40243b03ce022ba6232f93bc5c62b26daeb1d27c369eeb92306a15642aa25600f9aa225b2bfbd06d0b8aad6707cd099174e5fdfe8fd719d002219355efd78e849661582269ae0eb078e842cc06ba118b5e6f72584ffc43993cc2546a618b13c2cd568ffae3d83f0aa3982861ed0a3dba4d36a0f6c9f93c2c870dd1e318aea4c06b225968daac132e156a13b004db5f7384324c0fa574cdcd4911ed813236e223b08d46f3c69a4e5e3c260831edc60ca8f5518265fbbbc6b6e3a8be8f58edf329cfc85c7f26832cc09765e9ffccd22e7ec35935fdb5fbef95ace46aee7397e52d346b3d7ffdb607fc4c6218599aa003fe445b130c875a80e0de54930286a1d7a2f31f80e501f210d63d145decb198e7e17d2140b427677d6e3c29a5215dfd1b4868c616c8dec89704be374b4cbcb2ea8f6fee4282f7e0c21f14d5e4fc551ce3e9a0e44db4ef101e458410e54ba1f6587b8a9c3ab774766fd65cebcd8d9c3dc9b8eeac28ca5254b7a52d57308d0d01e99b013787a5c6f703bf9a7232f6a342a169cafb4b022b805bd15c6441b6c0a2770244dcf826adf2e0a1f97acd13e9b4690c2ab1e7f7a42eeaf609cc17a1ce831d2b0838cc32fe08783afd8e391c2191452ae22fdbd18bec7599592986521240805851698ce0725e5e318ea1c550de2ae00bf3e3e804d6624eca5036a35371440a3b40ef6fba44a3b72e1f00fc46dbad8e54f32a16d4b5cbe6556b6ac89af2939687375abd54f0f06b234e898b6044cb41faaed95347c56fdbe6286ccadeb53cab1cc80097b1d2ef0c53555ee0924870623b5c0181976726df8dc23c7d4c1c1dac2abb9510e70549b194a7e0a7f40aeb81c4bd21c42aa751458a3fc3c342b08e5d717dbf6402a02b9761059d672e06a7c3c3a514eea1d8de9b8f07157c50b8bd544399ec9646f7ce7c71b344fbde359944b641208444bbc022d74b1283fbe7847b538b6ea6d8bbac0cb03dfabc3eb2c75c483c0325812e5bee343a210fbf6cb89c320df39356a74295f6c3f35a63477f3ba209c96400e6f8ac9e216dd0781c279724e3446d81f4610860aed37530ef571e4f5fb20d295c6df59af0bd0bae67ed6ed021d31f0328088329361e0f1d0e69b533e40df71221f5c9063771241c0f0206156eb92e2062d7d9ef2c8a81c0d45951389c73a5a2ab931a7be8c055a85da914517d2985732af82109a016445a9dcf6cc9680ff4c24786382633cf9d13594fedbbd07fa075a7eec449b2ef6f5513a941cc19fe0b9a74cdb67367dfc02fce4dc9cd264da541669edf18b007e7cc88e429f1857315717f6d3bb083a2bad74fa07e0fbb4177bfde4dcf58c3fb1ddc61ad2a6394c8c018f95044639d696edee1e48363ead1d046b0c4d285ec0f5119f7730259286e256bfb96824d133652150e13f3f04f82704dbb565fb07b811bae7667c157f6ec070abb6308134210c42cf480d76318109b0ed542d66dae61a2148859bb309a0185b864c498e67deeb9531d98ab45a1f45524ea630c60e5509433574a1ffd920a15b40225e8d3bf3d332a40dd3ad584654e8ea9574c542096cbe4c9d53886cca9f717b1261568c8c0aeeb5959fb88888b9906f23999651726b90175491c4d508ec774c1f9eb9f587a1338c1c102a751b96ee9528f9d9aa7b012298ff1cf424b5049e4bb58ab1732b49c4232f8658416a3d8cd0f30fc49ed20aad8174a99dceed941fd06614f2af759bbd26ec9a7b7ce2fc08a8b1307d51fadffa846acb58752e79649d8cc58f7f4eb9f31a04325d7127167165295bd5f2921553ad19508f7dfeaa0792a79aa71837b8215d2e2af9b41130d01293f73df84395ef03df67e24b38e38b00ce1f8de91e65ede738b70955cafad6e71902aec88addf029c9797e15d8c830d9e0ce73e70b10fba79122f9f1ea1c2b71f0bd1b9a07acb9c500850a9512706bcbfa818f7bdd51afed69feb5714c9778f74dbc55749e58275e0f488c42cd4ea0a1199a0f5428af3c0baef2067d29b792074185251344f4a2c980c615616db90e689a9dbdc3e0193c9cc4ca19a3aa42e70416028afd78b8a80721143d9768b13c9251536b2f87bfc6c68307a1b070c6ac89b462dbdf03e01468b50a70f3f779cf30e104bc4c2d4e8f994e833b80c26245bb64aa412356545fce1d8826132c3b26a093a52bb48c2d89e8630a2dc7662cc184c028c5abe0a5be472a584511ed101ace4ed0d5badcf62465b64f5bc53d40b6f86f13caf9e0a3006eb4f4111eee9a86e352817543177aacaa2685dc74afe5c08a4bd3320f4803a2816a95a6e0c2002673140ec406a7017fb620cf7e5889bf63d96b49b0a34e4b178052a450133f596a7e779415c6a222174f2ca5f30df45a7009a538c7046f317d36dad7c1ca19422c53c09e9760929552f631ae6671397a37e5f7b2efc7c31fd9032606d99efacf9a0438993bcfe569eb6fd5cd7367c31273758e1e90bab6d65d70d0505eb95650c89ff7182b7f329c7a9c5fb85c6a30866ce6feb9e0f600e7f126b5f81cf4197479df50b5ac0aa941b4ac66f378aae4756b5c2a8aa8200d09c4584b1a29f6a0f9fba8accd6bff857bbd4c6c9ba623fc2550bd60737cb6fe24ff1c2698f784b27cb346ada13235e61b29ea9749a63f00537658a754df5265a9da19a69da31ae5584413ae0e383812d4641256678659a1946261011a551ac18c8e70801a7580587fb4c292593c855de50e2ea5bef746ad8088bb7822882292407a7a6548f5b4f1a0a5bd03dc24d08b1321d573bbf4b21d067aa1ed89eca95df88ede32dc55b9395c4865ef7a722bf5ecd23bd071eb016583e133654fb1b3342d935bc395c2f37fbf243f0c0535b2f52a4a056f61c93fa45ff285b88385b88571cab68095004af82f3775a06ae2a190645d251dcc54c5913b0f2ad7bdc096a42af66585dbe22621c51947100ae9e77ce297f4be65be405a773a8aa1734141cb124a846d597ed5d55bc02049c7a6cc03b67885399d85e84c0c43fa09923147b8720eead5ad0173b17ff70db4d7f2f1c57e15a360cc22701d5db161cc0123ff2ff8531838e9e00b0d53712c46283a3890c8fd7a877f38dde1431a81ede9591435741a8b218066a0dd54af35df7cf5f38c37f98dd8a4d3f0c68108d8cfc6be458563b48b2f6107bf5614efb20342597003f66e2a36ccf89488ff12c49a7839e187baccc1872175ea02d1efe82f57b6fec8af005377fb8d87e490a39b4195fca96712ca19c22746621b9608bd97dc8d90c3df80ae77a4731100e0d0b7d57019d750f8b0910b276ade4520e16f64195b75c8282ad6df30c6dbd28ee82a4f57a06a96d335e98c1b92237ce8ae4d073ca8da5b32088672fcf587e3138c5647e7d3a26e20c27d651d2fe8801d962f8ea1d22319559bf7a9491729d6fc866711d26a961c4026344e5b249e4cbbc9885da4e63a92f5ac78979b186eb9255a2560016dcbb1fb054aa615f93b84f62578be4f3d83620111250981621aaebd96af98472526426aa02be3eac5258f262d766c00de4724e9fbc0ca86605f4a74f04f91e9858491cd100720ca7b7e8de6bf7ea7531dc307a294bc105074539427edde1b84a9ef57251b2d74f27ff3b51285bda7d76f3e823362b49fec3fcdd82dfb64190c0b72cae173f55542e9fbc1ed430e9f40562c0f44e05a03d4e0d3c0929266a571c0ce2c98e61944d081c67ad40f56cefcb44f71311ae9bd20d187c1a9989a189a8aef07051d25b27a7ca4d57e97300216c7c604ccc5711ece61b82c210eec47fb8aba7d0d77933a7e982eb17d6d51bc191baf0f2ffd64ac15973560341e7b4459b08f1b27ad43817010e4378e06450112aca9a8d80fb215ca11821e30cff4a1bbc8d06f91e3e279f832d97f3a0677fa2d622aafcc342d0b042edf3e7a45e6b1496816cc2be5f2f5bc093a9cd809bdd901db750c05aa5ad20b555d48fd43fa9674351c4ceceaf2e0dbca7a5b110bd75a18ff66ac189b8d65525e5f97648a4319d9b6bc6dd0d6d63fd49fe260ad7fb0668b3e24028251bebc45b7c320fda5f84470a12371024b3c23b926fb21536d12cd31c69d0ef11bd28c6a4469b8988e14310e85dbda28983a1c50486d162ecd7d6834bbbe30b5790aafa09ec1528731484399bbca0d5e9ee0e16493de16fbf72d861301d2b29a1837b73e11b8a1799304ba36c42474100be6424e415c03c49a77eb0328231295ef33f20f17cad060719688c5e9d8cf0b939db500037449723ca036ce328c7700808a3a49d25b62d2ecdb0133883cca9dde528eafeb6210a37391b4ea89621db924b848ba5a9fabf2bc9ef3a9a6ab3cc39a9f50c75f4abb52a2264b5edb17510210ebb058c545e4f742065dd51ad6b6c7e883564ae2246489b96402bf2a481b7d6e4b7972678ad0cc1d813240f0088658b590eebf314147a4b38ab016ca44e5fcc7cf44d7417ea0dacc75f8b3fbd75ccda5594cf9aefe5505c384de89210ba440ef762a4ac73bf6a11891b1c19cb16223a033d64f3c66f408fe119d3fe6426c5e53d1a0ebbd10fc00ea287ea8ab61888628f6fd316243268c18eb521a98fe5bd982fdc67249b5d19caa1e464e942721b64deedd329ef6e4f9318e3dbf9a4089884281d409332b876c6181d18516dae0171d7a7192523b2085db0fda7740dc76f4cb150fb2e2afaf644af2468851653ca97cc2f4fb4582ebc86a2c7788318fdc34f224ea755d8804f84e1625375a98c8f8c79e0ffa596454097258f8f055ff6081a141fe56271e7c4081b71d95ee7d3bcf56f55886b3893fa75d6988d0ad8bdff9a8b20c68ed46870aed2d4900ee4f523b4f8048f8ec5adb1d98cbb81f662319337df881e8cf418713f70ed342d1b50999a3f10e201a15a1929a8d381d83f2438d7f076c4ad34cf4a771fec8cc81ec24d6cda716246bd7d5ca608f6dd3513a17005e6cd605af962ac9174bdcb22735c127b9900f9200765d7ec49e0ce86a8b037ec6adb5fc4562bba89e2cf14608aa64e3a679403322d8a5817e181a37c3cdec5396864c4c88d2e0cd8d604098c1963cdba7c1ded1a12a0136f54818bc925462d1e99614a762d365ee4aa614e1c27130b98683be23acaa233ebbf216cc0547a695c39a885339077d068f00a4409966dc3b9556cd2e7f910f636ccb48a6af11b6b9a2a7a416f001ea5a2a514e70b4d411dc8ae92e79b35fb05ae8a2c1028c9435d48c37ee23b3e2c34a93d7e490c878efc1b9be6a700c17ffcfa94df4fd8965d505c1f742e217447e5cf936c0f58d5038b1183cc3a92d3ec3af2e8f4f01f264f879f50031444ec2eca4a34902497ac19f6bfff1de33f869e31220b1a6e445def2dca660c0f87662418982d03097d4c73fd463f900d21733d7df049b67f93c839c7e789a7d8be7b6bad98c0f66f14119dabfc21acea5dd113c0b24fd07b5b6828c0e02fe65d85405c26602f2f38f88e3881193829a4fc361520c8ac61bdfb75f4d7755dd0877012f976d1e070cb805280c14f886bc1e63893a61a4a77fec56682f28b8582d6bad71e0ede8b2860ebfc0f128c0c6fe0b2e39cf0106c2b9a7b08032d7248ecd9ab103820f98210473f0c0f37c32fd81663e16e8eb0e5da7cbe5b4c5d571a3e120ba7e9d70261e13a196a003d2ab02545fabf651d90989b458042fbb258c27de09deb3209db1807ad9e80db2206bab9ad9abaed3367f51da96f275e5218007dafa18b1c8330df132924a7c36f6c752cf3c1c0fd8a68bb12766f53766335fb3f3d86f7a7e755410823d2ec4844dff35e2200e42e9ed975aa3e1a73adc107478ddc68c153adb664c0bbb57029ad0c2dddaf2e3400016f239f10ad45f999cc3ed30b4a9697efa77b08e237118067122a8faebee1af6d1b3b5c6de22d1392ed726a12a6ea44c883b5ab342734c8cf49050e6a2e89c6c4211045ebb464f1e7a5b247afff527bbb16a073186a5704e360101a79f2ed606c34e1c3c6da354848129c5e38c8952845f7c59a5fb009d5919f0dbb15d35831d38f2a923fcc708024091f5f20fed3d9c818647edf734ec80af7a8f60ef6520fea2b4799c6eca7af3f05a2c956941c853605122849ec6dcbba2aa978a0318daf0998b2a38bd5a691c6b96a2e83b1d4f12db562b85b714825d02cf2acbae6bb5e57426b1e3570d410f56647982900aa57cc9f4d8969ce574cad9869b901374b186bc0d55a328ae6e4a0241b4481acdd1209257cbcb0cb9cce68a5a82671ad18d20d5a98b50b9fb773f96d7fac5e636a94c4b812224a559c8b35fe9527fe1e0ee60014dcacaabc4c026641989d9033182368a8af6bcacc78eba3c20478715c80afbc8ad365c9d3be032572e9b92f5740e807a949672e92d48cb8fca5506942ed5f94b6189cc256972b70dc9efc173c27f408489934faaa3422e8464cc38b222f267f7180ab5197cdf2bc3d58662f86794737c12526d8419945feed37f26eb7182374fadbdfbe1cfbd9363deaab6c1d7c9858af49432f25b38fff3cb81660d2baa26584680e62322240904775913992235692cbb4d07cfbf737f8541efc4ef561a19e391242d890bb85d3097ea1c6462b74439a97a53df6e5f33b958145312c0cf60a9bc68e6c0493382af667c7f382b32775a8e7a1ea50ef9ea07cea5038a90a854f2ad3804077e88d4ac37f94a8f2f5f5e254333f6a2b8246cdbeb36756e0246f15e0d9b782109a0b611e3f8f1b56227545a10b39d32e41e5c897c77ee94514a932e828e3932c791e82f8baa03b48c4550c1cd2ee1403b64b059bf1d34ff17866b542f72737700febe07be4fe105fc9412990c72c2f79e309974c67fd0b8e9eb175d317d066b9bd6d75240244dc80b433b5fea5e72d3ed93ad362e55d2ae21b01cd40e321a59c3277f97db11d4933096d97d68168d26e83d9b4f9eeea2ee5eeab8c8ece4b70e34ccc144341164665356d80c7d1e1ff4d80b1420fa22173dde061735a30342281267006149bc5ac0d830fbe2c82bd865531883593ed48e3f37e5e0b11e848252014a1c8b5224e5d97321df37ff92a08bba3982c06cc86ad69b6905dbabd4cc051b0967c71c1c94358e75752821026b758369a93ed4c6dcdb5fa80ad5e14a50c0a69d5392c265f74a27fb8cdd2e185d507372f8cade14224d9f2b05dedff087bfa8b8d3d56b9c0256a852d79fd08cc01403b4cdd755e23c581e7730e127023e87f2233b41a8212e6b57d3c33e5d9e274bf8557d9fb63980d82febed76bad4cdfe2a6fc8417b3696afc513a0967b8eff8a956f85508c469cc150e33a3bfdb9a9bc3e5e5c17607c3301b3902fe0646c7d72efacf2918cac046950575ea7cf84eaf22ef58f711b359d1ef59a5697de826ca20ee339a4995c1eec11962dbb228b80ecf38b49f86d1902defee652dab19c180dbc90ee243ac069f6326503cbac75b7caeb568ed6a29fa5fe37851715ae8b3df853b86501fb6e24af68287fb3d2023a6d824fe4fa049b546ebc48a2582e6ba1014535c5fb5497fa201aaefc38397695b90ef762d36f73f327ac34405b7b045db803b4bc084620cf5dba03e86fc80e5e3a9c90fe6880b674a547ad090bf9775bb3ebda64ede46c617aae7dce713ea038cc505c019b0a1eb70ff0f4b358fd4bde40282dd64d0d54f8b6b1925e1c4c1a5278f60bd8ee9703cb270c16f71af66e6c9e7a83cbe9880998014601980dd1067271647d92cbacee98bcdc40e764bcdcc5aa2f6f549cd8ed0a4289a4faf3c66a2e0eec3dbcac500cc49958d971e35a8295f25c6071f0eec93a7a4f6b3295098260826580418ad078516aefb361aa59750a76440f18da7768a848b972bdfc574de7797f93e1c633ade65b746e5246731b64f49e51b2e35a0f2fed05629f694b727e9cc6dd78492eff5e6e29f91d90455d32aaf092d476ccee76f98de63a8e974d30e115078d82d46443ca80319f4c09d1b5d1606147564e8314f5c440d5510bc00893ae465de316ba91648b9ea6e91a405cdddd5e7e4ab64371895cc370fa48421c7725e3eee2838b03d202ae1e610fce9168723d3deb0066bea112f268613bef41be6ca6775ffd6e17c17631b9e95aa10430bcd185873186b0459b9a7f92d73f10b416497dbd4f22c7bd9f5dcac2db65f19faec48ab177d70ffec95906c8a6ea858a3b2ab9e85598306e3a43fffd4b71bba577bc776d2824fb7efa9578a5742dca076012aa40f34ca5ee467ef2d833735cb21a79988f4b245940179c38586113f4426bd3ead15350c4aabb5a2370e416318d4305122462809e4678392870a9b6f8095da7359904054aaa594b06cdf6e0a6099401f405f7e5b9dcf1ee03d8505909c6e4defd45a61c419740984e8bb11abfb0e37369fb5217cb0d7177ea9edc0c563341d7d957637ab1e7764d237f4325dd4051b50c974fa42cbb8525752e69d43ed1956a9c25de1e2383aa582a3e6e8d82a69fcbea29aad3c307e2b92a8fdba67243461ed3ff2eba015a776f1eb6cb3eac3cb411fd67c80ab0b5706c785564a08f494193dd9b00b43e6b449574b2a701c3e139617554f5ecbbfb260e76b3dca9cec6fdd847afcd39d7f9f8239d1b8848bd1961815c62701f34962bfacecd188986802737b8ed628a3a3b9857eca22cea9364e8f3c6458dc2f593d609e2839a96db12f687737ceecdc554151d47d78eadad2b47bb6a988f3d47a83cbfcb7f1523996e002a74a639d7d589a922af7f2286458c11c7691a8ac3edc790488a9c243b096505ec826dfedcc033c4f79786721ffe058fe18dd0190cb995558234691db3f489d482704111f809dac41104cac7510d21a3f7bfa62d8eb21a46c6fd355520065c4ca97d07b1f60c09fc89e7c4e491bd8f94d5259e4e98cef4474ea0ec6d1250d1efcfd8d6657e66433a8a6ff9c6c8cafbee8a13d9767e002b10f3fc49af4d1ae258645c4a0e6c631cc0c7a436322fadf6603c105bc08475c3de464c20c5cb19e66593d6540c173f4cf7b06eafa2d329e9b9bee49bc6055d7a2eb105eca459b66df194efe27b1a987775b81ac4950c12c6e561d505b07fb6be90bccccc2bcba697ed37ea8842e5b886cc49fba4cd7ef7a1251f8e680360c9d878bae43e872e4deb504a28cbeaec8e01aaa549f9b8b43d837a11266d4b595c4f1dea910cc1994d46da0ad0a8da20d24ee02615967d4db73c8881831822cae65ee14f3795c55418f3048a9d2732a61196eb32875463882f141f92b4efd743400798c061eaaa97f0830269be9e34ca0904bb9403edf72f58b6ed38c90426a03fb2307c32c3315388089740a953216364e8a346283aca0a74acc0f0441ad060dc81c37b3452f4ade4cf223deaad789c3c1a59504515bbf71b86b08b602a7cc18b458fb7550a30a14ba5b93669b591a56b3bf40ad9ddfdf5d4a7799c6be9909ce20e35d64152dfb6c69e64df6e38f6044fec5af9be6f1c44b095b9e39bf942408ba04ee4e0e9feb08a5733dab36cd7571ddfc05793e03db8c50931fcc148e983c16f65817c42613dd9727c06d0cd7b0a679cfa292f9b8e208d81b4b4ec59a689ac387f663c03a1808c494b8e59c11154887fc3c5d2caa398362e756e791e8343e7689849bca192eb1d11bd55b48597c8d5f88652f774a36d98ced57f27ead6e0dac1e614b2db8f7de06634f79990e2693265c11b215cdbc6a09b9356c1143700a57485b3f8be0c0abbf8fd23ee143c4e8e5a45245d7131a3bd93bb9e6bfc50c643f7244713c74f31735dc162d69a431d9080ef807ae2e7767353b0ce499e10d31035d1bffa4c9a694e3f398acedb56c31b9024abb474a0f0892837ce62e275f4c73d7335b0f2cf9a877564cccb0276442e00dbee30b1ca2a76d13f9af66202d623d3a282f0c723d6c4f0c5b1a600114ef45d6807e1677ac4374047b07a9e31794b6c1bc4eb12e0669a0eb728ecda4ed9cc3b2f3e5691576a9c3c5f1b86f0627dfaee3e503bee18af6906f0f91dc4e2dbe00e2b068759bc322d2818e7820fea74d31825f04ecff4a280bded3b21c0bd944089b00ec12568ae033c5575ab8ce583da57eb9fd39d9e637bf1cb02624759af8dd3de1d4189e200a3cc146f03d6fd4b96840357757db93f06d1c0b26a38663f7f395cdf479e4ca4737b98159ccac468c8970c9b5ea01dcd5a99ae73d3e45edf2d4c4a16d7f50687ad241f14a47071aef65115302605ce4cbcce69de9ea3da8494aa58e02af6a195fe0cb0f3a820f9daeeee30fcd9240e9b4e823a7de09f6e99144c6b06d6694716250a1ae6ea6f708a035b617a359e64892856d7e00ace57e0bdc6546c467b2e89702a9634ef920080cc43e092acd6ba266dbea0b9499d57aad6e764bf5ad20f92d0a76876973b16103ecac6b91e0eac07993a4f5c092c65896cca692837ee2d8dc1fdb6a05ea3155079a481d34c5f5f7713f53ee28c7af5da2f99e145a60c335088e4d52f98a2b4717fa028fa27c44f41c3745ca89a12ca4e35e174e45ff0bc80499aa2ad060c463ece69c435ef448b596f477a64a2c4980f6e7d5d0655aa6c9d970e8ba0fc9cabf0acb73bebcee6983fb0337b2fd1a56a671d8d77e8045cde516f80687025f8d75183d12ca1a744b1f2a7a9146aeaed13c6133727187d6ed3551a9d829d90bf11f9a99754b3238f3af61b1649dfb6223a58e69c97aa4d503d7e133a89e51e258bc7798b60035c1f2cc3899e2f37f24bd705ff2ce0f47547f4f6b4393b71ceef0d8720a3be7365d2c8b5a7b64b14dfd47b5be388c3343edded23ce6d0434bd9857f3823c62ff23fe7176f5f1b99df3f8a8e91878cb6865b7ebf3ab98b6e30b187a38f2b13fe077ba261f3ffd9f9dc25bddfa79d3d55ab904e3acb5f1ec37b657a071f55adf0c049661516bafc6bf7377cfcf5efd7a2544608546288d0af07b56db253b4d823bed5d9cb51ad7ef16e2d80d5be41d8d578d5c862691602efeed78dda057a6efaefc1ddb9c37c449391ab2c01543af308a40e20eec746e15a53de6f78b0ad5c89cc025f529c34939900e0ecd8372fb0b96342886206229681cbecbdfe4b4d371813545004fcb0109d29dbb933be751d1f81b3ad6d4179b354a53557b0a283e4fc69e51e72c8ea79f881b49a806cf04e8e48818e92008686767a273c0da20c1f0e1844622dd75eedea30cdadc2b161d03c9aa37127c9602fc7344ed46cd23f0dfa1b179d48007c2ea00207cd7cc4cbe451a1b7383cce202fa15f7762c80b73e645fa11f064da454c9bb6ceba59fa806b77ece787e7b94c3db5308aa6b03ea4ea41d5304500b366841ef8d0b9a61dedbc35c68f2754abd896ed5c94cce5c46ce0bd4d3f6fbf59a3d639bb02b8d325c94446dc9b40530b78c874667ac923cf4f750e4463d4a87d5b3d63337c011ec5c716960eb53b1241741f01dd480bfa25f48360167e44bb29de31ae9192aeb4a28ec4a79a3072abe3738502f85499cf026362a7a8343bca0bdf4d18b6a32d4cb0c5ad0897bfda414ea2da51c827e4a180f59ed31613b8486dd183f5ee9003ef0c73a4eaf94f2a48ef7b399ee4e0e5abf2784aefdf7f4f80d496b80d71569f313c32863e3164f0188ac2b004e5cf8d2597288d2d3f4988ac9a98f557ec2faf9e9db358d363eddb4ae1cf66ad8c30c200e55165f4d7d6591ae3c74125bfc89fcfae28c170d7a3aa0c1a4505825b12a52d4a97b8a8f80347eddd2ff2dcceed64006d34bdc6b2559e1a6ad2adb2185d2247a7099a49356b1940d177f626617161adfe670d262dc1c149ae2a0ae8517dbdb06e44d48ad5e54fdac20e228828afedf804e3a307c16ba032353953f1b3eae8d0b107a40e56fc6a4e39c10a0fe421a0b1720d35150a075277a407f04e8638e21dc9d50dcb17aff38e31c9938392efc4a1fce05411e65b6a7c479ad3b27cd3e2ebf0afd78e03b74e492547f8f2de46b43f628caac4636cfa51a07787db5d15cdfa136112c58274436437857e5623476351850d788eb06c54998ad1c7d864cbea8db8f1b724885ff589532c0a992ab3cab04099479a415045cc22095b0138ae50f400ddc56a14d00f58e0aed505c20211124202c72aa2b5ef935e8c6436ed32f8939b06ac07044da0c24f23a125b66f17c41508c7be96ce6e259f52023a6e6df0f76db0740c3a5e63d28fa0b7524c54720a00fad3165f4c9d74131786ecf3cb83a3dbbf276da9db35c37af80ee21ec2aa05e87395f64c5ce5f7f1942f01ab5e82aee8620c400c8640e531af860926e31d96e9af826e6ddb116312d6ef0a6e28f3717f91505328a446d61895df082549448bbd7162c45010a866ade1e1c48856d114114cde9620375fe2fbc44df7e16138342deb021d25bba4fc413a88253a3cdbfb37675a66c129238698d3c780b3182b1f2b43bb5d21c7ce9c8becb569665faf8e935a7b37a841a5dd36f68cc60ee68846168074f3fe97559eeeb759d7a699c50e870a4dfc5c18bac0bb97f3e9057dfa4042eaab10467cf45d76a0cd5217d8211f9735d5a4dc7db249de992d36e357d0f781969bd0a861ed2595b3f7bd93f58c2e6f1c9f8c77174aea1bd0517b789be9e4d5714ab11acfecf18bb40c15f0cdbb1ef161cf648c4afd8acabf49312940a73ef904e910e8218cfc7ef6509144e77075daf6a554f2110386a83052753eca14ab1d27d570dd565fcba140582fa07d649f3a413098a8ae0a47116fd9c035b259a5132b09985326409c1b4cb19ad04b61c028a66c359a113c5f58b77fa91ef06729a8b23f08181aa450d019fd2f8238d767b7f905afa83a0dcfc0f49270a92694b08f241a3e50a32686733eba30d38608f88d9f65b27ee91bf75f747807a4edcba1726f0fa1d915eb01df84e766c30bb4b678f317c04eca5e71b5b216b7adfe84745e7f9f0b4776d74ef6159e7409fedf282063b42f9560fdf46391609cdaec89999eb40c3803296048203aa61627b5a3016daa803d93e1f7d4495a0448bb777a951535c49b36eb2c9182a5829cc968d5b95d461283281cd9c58ccb79fedd44578fc1a00e9d6b49c5dcc1ef8e5fbc4d743a8dbf3e18056406f2fedeaf356bca534ee6ebba5dc5ba62453b4076a07a707ee3e9bcd2b69be71970b7dc53d2d09a6e91dd873601a098359b1c2491ce000a2088231628844cdc14279a5c51af2d5828cffcb104ba91c66ceb34bc6bc9504d30c712baa277cf3be700245bc0f05d0c554396c398db77cfb1a730fd8f222986346782266e5ba57df611928960ba54c66d1c16596f9b8a0d851be24db28c134d27ffcee0c8bb74b06ff98656aad21cf4e5c63c558467acb5b5d7dc75d2ad470749d76dd51c67a78cb4f5e7db2e692169d863362cccc4f4f05d35989561d61e54ba902ab32718e45348da0b02d5cb158cc775c0acd753c8a8b26634ed83904a69159d0681fb8c496629f161bf4bf525d5e844db294538b28c254107f446ca3bfbe80ac092c9d89a21873c95953e994954e4ee5e46c354b5c48b83a84811431775ea4bce8bcecbc4879d19938b8a43bd13b34de07438b5ce809c39b86f54ecb5a4b6f695ac33ad65cb04cb7aba7b4c8428b5c2bb5941622480cb58d85db0bc09121c7715cedbace83c16af53cb5454e66c899dec347b9ed378e752c2963e9b0729eb4a05481bd76a66061b14e303e8b251ba2ca3ec232a2950f2b960e2be7490b8a5781edf814c7c2bd5e3b76388db5d6b2547e7296fcd5e6394d5afcee29a576f395af3c8ec457ae5279405fd22265799ed61a35c2a89ac0a81a65a2ef7d28e485453cefed1bc129f27d2834d1d04481d0dbf7017ad1531afa08bd0d897c3e14caee8de0304ef7ad729acfe73d96f79f22de7fbcff7c1f01c6f9c28e519854f8c28535fa92f1a5d6812b14d72836390752daede04b43217dddadca91206187d402a37215cc0d7e834c829cbd5e6a5258660a4184eab29c05937ae1e22c29a5a4b56e1c2725c7b158b57eb2c8b4ac46522a4d725c0f588f1ee08e1d29ec8e0c1bb9d6a559c01604a8128c83a36c23e915da03eac412b019b9855e9138e4122416f40a85f5609910152b45312e0c069b919c0b2cf3c2cc0ad91431da556ea394763eb492280288aeeb41b3541ae889749fa394b497344f8e1a08dbf4fb6b8069e40c3c030282d2a62c76db5aadb373bd63b2152bc562ad562ba7c5aa92f2d4568a6529cf11203a4faaaa52a998ea48c73ad6b18eb55a62c9b24dc758c63f460c9108028d8533d1377269f8002945ea8c3e304d0f814ffea2186b01711ad90ae2345267c755a98ea5de4ffe256b83fd0001f3786cf5cca46a66a50d2a958846bed8c6a5e7654b62486ec3fa081050c53234b0a3a48d3ce3b2e5f2603d720b1024133e60944b1c39a4ec01dbf0e08a5236e3e90fefc08ef2f5a2c926b08df75278dbc7ca976aa69aa99c46b65aaa96aae5f1783c1e0f75c91a175eb00c973ef5e770c44e1f017101aab9aa740e6d43cfd04c24cd934f8040620e51641ed0681f10ece505b2ee2c6119917b0bc6c33c4c4804e6618474e75b307ea42e1dd9214fbd9044234b29155177f7b8bb3f972a0c6812c1004dd4efbcfddc2050ad76542a75a2e80306e629487a18efa54062a5f0a5184ffa18a412e949298c403a227d3a5e5e48a291a523902f099c38223aa254442271f5c661808c0333dfca30ef46613ce0e8793e3040c681e90a0303436160606098e3466d7a32e79c5f04b1c384397ddb98f84451c3e5eea69a49669a99b6986848583256b75f464ac611194d6ebf4f391ac5e46714939f0e212ecf15c573f99c737eeaf550163b398ee3b82c76be378ab14ddb2c36d66247016d162b0badc09a5ae0ae0c4759ee68145b81e63436e62b58410019dbbcdcfe1566b2da5d61cbed5761851c6eff0aab182b6e7fcc4c051f6e3f9803b6aa7cca8d622c23851d453ae00e8c22356990e4047c429a8115ff629bee495ddc7e1a396ce3619bcd5b81fc971d6dccc6d8a6b24c9646d146d9166d146ce3b1cc101847c7369a869f5a592619a949a32c8ce48451160836469a814887d4c52856dac103d6b7b0f9fd160833367f14e978a115d8ee8ea358fdb712bb3476d0a191c3322659eccab82efcd7e876e1dce65370c3e2ad60e46c60c56dd905e7136e2f3fcd2cb0dc245c1b9864a00e28a5519b4976db6492d5f0f4e8896f4a2de4ac619a9a0eec6719700615dc5d53f3a5a628a5e8139a435bf449ab517c444ece1334875ba4393487ea18514a3991c96d83f5e8ea266a6247d18e68a751a347bb47b0680adbc87f89a0605113a21d86712d5a6b666608a5ea42ce0b3d5cc87992851553c07a78b42c3cdac786cf0c1e173d7a4021a564c92d3f21f4cff75ca6e243eb7aee7ee5376ff79cdbb63911b93cafd65a6bf5b80ff7e90839e1793e9e4fc78f5ef423cb7e5c00039eae51f519d0c35f80f91c8dfa3c10037a80332fc0808cf3020a813208a937f4262b76148a31334d887e8880c0d0a2b4e0f62191f7f5d3e16dde575fde3f00d03c04bcfc78a16951fb7e40b3a26461e6c6156b372cee86051728b8e36f2bf96d9edb2bd362ada54d2137aeb201c1cabafdac95e3b0c43626701ce7e9384fc784ed388ee3384fe7e9ca711cc7558ee328a559642d0ac1e318b9b32371b4ead3730bcbf8ef6c3fca1db9c336df8a084b9c35c5b2cd0edebb3f3836edba1ad5e1a7c5294d730e25cbbdd0c43de398e8a7a3f37c17cad677bded23778a609acea2693b2e5bdfd6b40df478ad6bd82125ac747e1a4ad9a60c51a99e536ba855ce506bad75abee7d3e0fc97d42a4c57e090e697189e5fa69d4c85cc01e6cada1b7bba7521e8f96d9ce121e6f73dbb66d6b6f04d07f51ca954b5693cdb6344ab6164858abd49126dec70b0373faf4e91c28ef3639e7e6146bb86f33eeee30da65e68a3125fb993c6762b6adc6e57e7e2c8cadb3468d166760c41a313131352e074bf9f7de077aefe0e7bdedf39590f0c07e64caa22c612db99d828c4a9419121e709a75bccde7c734851dc5907dd1692ae9419e1e12879f6fd41021207146df40be43fc94033b8a1dbbfd39ee4ca3b6faa37f79b146a35e5ec74bbf8432350277b0b519165910861f600fd0bf1666ae0788dccfb30b0bf1f371a10a06f4fb68145f18fa3fde36ff7a20c8e528281ceb27f4c21ea29f9cb0dcfb4d7f930b6740b1c51bff80fe1ef8d70376e2918f6683daad608d1669a2fb14f9d43f6fda4351dc32ea6f88d98c6d462eb6e9afcdc8b163f50edc7e3b455b92f1312cec98b93165c3ba41cba2f40091db73735d7f9812ebfacbb871d33aaf2a5ed79f5b09ce58e3baac8a282eb7cdfec86f01a11cf48459fe1df6ca1d467c405eb9b10eeebd4d06fe6de3233ceebdfa8f7ce5d6dbc3856a6d8961f4c9cbca42a0870236b03d5fe69c2b9f94dc36ac54c22975bb7af8a91fc77e4442e8bca789ed98164526e3bfd82839c7ede77c19c294e65b0e8c9162ccf8f2df3a4d95623d9cd1912f977cffc7855db5aa51a616fa6e6f5a00f33312b6c1816d92481cdc97a4279136e46fa0e3c0321274558b72e7861d56cfc981e785b2d8b1b6f0726977dc6032e6046310aff4508c8c6d93dbcb00635ae4977125ff4d4b907b9353051ec788a2b072ceff2b53045372838f0bfb32c99395132c1b13663c3842c5ca1225c06854d1036d8a4b58b597998da46e9c867166aa54c9233f5aca9454354a5659a5e440c935b7a5540dc47148358ac6c1805dd706d88cc841fe52a956969b95292ec50b1405136c70bd8a888290ee1f17c0000c9fdb5ed3627b3c277e785fa4f3bcead5f73cb5b3126a82da90428ebffb0b2ce30ff2c1e5915ceecfc1382a4cc305c19c8b6d42d79f4b826d76c0367c3f3b9c943aa5c605b6b9c180a611fbd43432fa54f3c2cfc4d4b8edb58c66cec525c1b9be48a9452f526ab18974a1ad2d3627fd8656f005b66996ec20f937fdc2944f2187328104ca1689e3bbfceededede3e2bf043e2681c2e7f831594617bfa1ec771f308ec0a37254fe96f9ab35140737777777777ffd972f3960de26615e81fde7d075a2652f8fc9b1c5100b132187eb08d0d7db458dfbfe626c78e46c9a79441d314d6051f2dd21f4ee34e411f9c8fee3dde77a1e8a32352bfdbd1a24c7601fec132fea62778b548fdf7bccee3a9cf6efde4dc061839478b158793ff4e63e2bc2fe2793f3538237172348a73d8c06ea128b2f2aeadeb0e50a82c11bb5b68ce9d527ee6dfb08de8cecfd1a8cdc51d792ec0fc4a43904ff36ddc79bad3065686a510a6c5f9222b56e6f40a72ae73a9a5241786e205bb7ea545a7ae235ad4256b35456535555335f57a5119955db9426554e634b25693d5a8ac455d5eabb5aeeb0f46b1a3acdd1a16236a0e2f59b196ac588d625036c132ae16bb1fc0512e87bb4e974b0551105e451ac18e9eeaf652b58663b5a1a7c2760db1f2c1728bb02b93fbc2082c312cb0d8fcee677965baacc2af88bc393b4737a794f3cde58d1b37a858a1c11d4bb013d4ad0b817feaacf4fa6f6ca36e5b7d21f56e2cc38d60db6ad7851ef05bc3b991e0cedf5a985b7ff169146fdcf65d13ecfcb136c0bb74e958fc3e70e76ff53dfff0e0315329ff8e6d7c84dcf95b9dcf7d3c3a3543193a96f1f78c20b8b38220e8db793c616599b1bb21a0e064d15ba8775e0f10f91da6a4c568b1184dc6e6bc423a8c168bc5b0c060b1fa7448755bfacea3600f3f4dfe74f4e8c21aba276204a7a3cf34aca188c97b74ad21acff92e5641758bf5e3baab85a9cdcf7abc0c51a6145af97e855c575e78b5caf4671d79fff03480c8d7256ce54ed6099f90212438bf377fc601b15ee7c188034ca7f1ee086929533965232ae6522d896b3e33a4e38c1b2dc8f92e6e3fbb8c0e385302df6e719a7f37c42fff9421fd0e7433f81ef416111d0f7a1d552aaaebb2992ca6e976db44e5aab64d5f7e4b42aa7ab5048ae8904e7edd93f5bbcdd520aa10948a6942f650bac9c592ab6d8ab15cbd26aa92b4dcc1b24c36030ea3e78ab25a1b474a0b4663aada6819d35cc8a196c0683cdbc453fdeaa14f4bce584b7569e671cdabd272ce2f96ee527a7a127673166b553ca72755a0e872965b7cfd4105b6c1a8de57923382e251361afa1111cbaf169c5ca991c936e16d86d0ba7bc620a7d820be99f34726738ca0bf32f9fd78d5ef4a48761530849e04b384a2457148e12e45a28e41641e1285736b46c99e62e49ed5a3cf91c338e56b7e59d3184f5d7b13f600067726525102cd2619bd39dd70acb24b6f7fcf31ea5d45340f361459d5deac8244ca525b3dce696586ebb64ff746c2d1b34b9702cfd68b19a9858ef1ead7e904925609f177694140b8d515853e71ccc51b54a067dfa0a19c152192d8a82b057b64d726f20cb3a4c10f0217724e88476161ac2741ecffb849fb00828b4216fe147b29cc6b415f9fef3a0b048141f5f11768f71ba6ada9a3ba63aad0a708097b26e962abd4afff295b37c09cd578e44dce19efbd7cabbb048f79eff849e50ea784b4ee1ac27c21daf2f2fa484bcd204d3b8159e857c79ed06222885e5547ea2ffa1a22d6ca534720a4cd34829fdc236320896a14f5fc43824106c833e7daf9bbe680bb103bbdbe452c96af1d207c2364f3afa1f7d0dd0a735350528c0e9048231628844578727ec2b2dd2078bb0a3d4913ad469342bcb926e227148292dd2efa65caac332f49f6029fd5e31a5ac45f45a3d7777ff568d1a3f9fe734dcc088669da876082e1f0f272c226c08d58860bd106103108f17c088288247939724a24a955d674b323f3111572a0f1d5444607999119104183c583022b88c98a876928876769c8878c020b5a313e21103d68e0e2c0f263a5d4282ca1134184b28cd204932b02122aed08107251d5b2f53d121082938a143111270c4ace4a40747b460096f326861ac8c2f19e171ccb802ed56de98653cfb0df2e5bee3cf4f47e83dbe82c508b223a6921c40b30e766bf7ea5cf6f36c5bb5910d419f4a5a915ea4fa242b8c836fe59174e55612ec561213b7bea866c77a6b188e36ac5bb897383040930fee613c8c9088016080c385267fd2db949fea1bc1a1a0c9070c2226faa430042324226f70c10b70806543c10c274af746703ce741142a548043a4045d0d70a288be043e841d9c289d0160c0a1bbd3c15efcea58eff9d9251ddbaaf54198ad598f571d25967a9f3bfe6525f6bbe3838c333f1d3d4c2f219a9feabfac2c174c03a3b6c28a6d0c70eb93688dfa7c7d9216a41adb6c5f5350dd9a028c9ad390561546edd6b73956665d4e53636bb7bea5cd6e8dc1c40db42a96b136f0a97e6539c9e1d6973034db6cce0c2aec189addfaa119db58513f8b5bc331c445cd0cb77ead99b4b015a623f3903e89f54d807bd2ca4fb5d66a5ef79191c4befc5867b7fe485ac18463bd7514ce5ad861b54aec485a793fce1f3de98669868717ce7f2456fe0c695bb6dc3a6bb1be680beb81422d0af88453067ad79b1f12935b736e25e5c0a7fadc7ddcb373211101081125490a8786f229ec95c2619c1abc4b179c28fda36d417aaefcb8c5faa4c5fab5266c970aeb8339a87161048eb536846d3c5f7340b5d6f74436c769249fb6ef5a229d5b63ec3cdb7ccf5f8369389acaa54fb53bd2a7facc36d50b96a95cb08dba81f2ce9ef3f6cc8c283a69d5f1156d61e59de1f8def6f180a415cbd4b7394e536b7eaa5f2a59cb39b7be47a4c52a0a65aac5fa445a9cb1841d59a686d4f9e9b0618dd390567caa95c6116c3744965bbfd61af57dfdcaa551a0afdf1d9138faeb87c0caa5c5faa0235f586b4fb0b53ee9138e95dd435addfa436aed01ace5765dc30dac40037930c4162f565ebc4ea311271ec778ccfd7cbee15982688b166af81eff38405e0174b10c30723be901b7e7a67cb7435ce9c2d5c5cc33a16c5426716c2188266d50c1361e829ae0f528755989f58ab0fe2369a7447ab1ccfc126d394df77392547e9a3f695d484af969fe8cd79d4fa291c5f67b3b2dce6ddbbc5a6bdd48533692ebc5891d493b1744bbf3493b4ec32e920b446b94e9437add397ffb91b473e79c011b420633f653b767666666deb6e9bef9e69b6f1b6f0c6ee167dafe8d25330b19e2c2902142684186d4d4d4d40c11420b2264480719f2a34559011cac9019112d411a25595af855c3a5227c873298b0236b9129271ebf080b6be239453e7ffdc2835565b9f2ad9492ca1550ae9412988194172d96dffbb4d8b15da577908f604d13f09f8c639a405764fbfa3334f99844b89f1ebab0d1f7d0e4c37ffb2d24b2bd77a1e8a728ec5802df4f7eaab9d9e12707b9f483b161d6b60c07e933ae601b0fd17065188269a73389c343504ddab8826d6408ea8265fa4136e5241fdc26bd6e935c7e9234da0c5ae8be3c99019b0173cd20cd90b10c37ab4d1a1582d2a20d4c33833663266d749f66665cc132fda3877505cd69424cf84413aa814f33892394923646506d4697db3e83761b549bdd115493a09a9ffa3d22acff38034667c06e935c4d2ce945aad2628b8460b9a733524e4eaba5a3b3b3e3a2ab3b826a8ec38524959f684861471a2a505d95b25849633585c7339606579ee478bbd7de4ec191291d6168c8c2d63748bc4062b27c577d5aece8aa1ddd775d0dbdcf556cd33d76f849fe8c28ecc82feb27f98204fba345fe172796ff042b242daa70902a668206145c59ad3c717b8b00f389fe026840019fea357169d1ca13d7532d4a2dba262fad45f983fd4275478ebdec60c7186effcd8d64d4b685630c30777b166e92d8edc79a5b13de08e00b4bffd3b16dde600c30b7df6b3c3c781c56b14c2588d51d3b966a09ab518c83d6934649289d4e0c418464695176d85d7777ddb5fbead55a798469b08652e601efd28552cb4a25e58a942add2c09b416d925ebc2f1ff8e353544aa644a4a4d223922a2d9b1535df99d0a89954188bd1d0c98b00df9d20895206077ec585dabcb69145309e2d541e99e307b527a3ada0b15c02cb2e8f3e33db728fafc14b0813c0e99653a256c0dc7fe7e0fdfede977aa4635cfee9246d1b1f660b1b0f45f78b05e089ed83ac3ae8231d455f7dc37c881baba1be89e7baf0b01ed76a0dbb15846be880936e78e4c9332a7f96610709c2f04e2a7f6efc018ea6a64da1db9e64f41204c0383b428c495cb92aa46fd67081521aa74ac46d197eff558579ef740964feee72d7073ee078807cabbbd07ecfec61be41b6af163ed584e230229dfa59443ba9cae498b52be888a1d8db07eb4285a82e54e67053f4ca9cd6a5c4833352a4f1adc37aea2809b334600e145942d564c10628165dac25416a260022f6210eb810559ba64a104278c74d0e10b22184184255471e40a26cc392793391b0a04d0ba9881164238510327ac7859f1430188784501fac1f6f052c2e4079c01297870042968c085065a669e1b2dde68146fecc51b01220b2d39c8a0092c76c841125cd586aee2b9d8093860c205351e8e48714115af22445178c98a9e8b3633801ba0f0c414246e8084162001ca095c5b3716225e4e3e193f6ccf83133ba8d244174698e0358496d74a053a4c23308842931d80b0a40a2c5d1ce195820d16022484784521e545141aa8e82828d149b2051327582c99e255c38c0c4810010a4e628083284a645cdce092488cb4a00a142708c20958c0650a236451676a805c701b67c635839da18b163de842072b4a8ab06553a1892b4aa0418b2d377849115bf202598e11488670c4921c62599ea07346123b5acbb1cb2f190dd3937013594c51d2858a2fa8b0a2b872e76d9d24368a50d23090cf2eafb17100263550410dac48a2053710e10794e7cabb8573b60fe6ec91a38d2041051323ac584084105870e6971ad543b8f37fbe38ff860a87b975086d9bddb6d0e4a1bca2223ccbcc9f1647777766f6bcc0b28bf74329652752ef87fb1de46d89fff900196e60f660d9bd9bc0711cc7fdf62e9b72bc85dc564e394bd0dd3c846a33b3e84a07ebfc9b9e73ce500d4dd6a94800e86001a083edffeead7677d3e0710c9112e06802072e9c28fef367c612587ba833518882173770c1622a210b22a470bc8e754e79012b85e35b124b20b9439638c245c2d0a29cdfe0ecd9dddb7cdfc231887f0f40302f7dff2289bb7d77e9a6db03416ef795d2700c72bb700c7237d04feb8f412ebd6905b40ebe42e6adefe19016a5874282d0ac8947ff840210ae7f5b6c102158fa33c204b1f2457166a646361142877479075f407185f469645a655a7f874c83795564302a4ac8a85091f29e2c3402b963c731f6f993a1fe4847178e55dee989bc1779db8fa217d25ee88d42ae178ea28fa1743d9388fb7478a149247ad1ada11a8e5de56a0d3f339018ab08348dde7bd1e8b976e7ed3ea0fdd187dee33e1da16943360cfd4d874661281c81dc31f43194aefd9ba6ad2e53c18275672e536142122e2a64bf7e1d81942ed7d034328dc2056cfd8dbe8221d0545f9aeaf3e5de82268efb9b8ea183e516e5fd46211b9a4223d746e007e405fd10fa196e345ac076eb8b1c14cb4998731d2745eed89b46719db9dc77e12855b7cefaa12ae3722fb98f7dee657ca3bce73e466c94e7b98f75d17bfcb520af2834ede02b7a05f01585a6d16f40f4f547a100ba58130fd1d71789a3f7dec8ed42e028e486de8e401138d6dbd9d05bd0c80d794590363eff699105f045f71eb0abf5abec935fad947229215de1aae1880e9c90031dcc74204382e679a825dddda7cf39b71feeeeeeeef359cb8feeeeeeeeee905dddddfdddfebea30cfc222c1ec73c8d89f239ff6908d9b6991a375c3233f3d3f0af72c342cd8d1dee9eca53c968917dbac7b8bbbbbbbfec76f7e71ad286bbbbcf58c2caffb83b5ff7794723d743165ae41709619bdb2c59cfa7e8c23687783e5c28a354f23c9f8e9bdbe7a47eea77f2f3f2f35e77bbcfe7b393e3388ff7991b75f24aac7ccfb3b1d8751c8b1d7724901ede31e7f6f904a5c04e4f122fb8511f0e58b6cf8da3a552add4b9fbd9d5eeb976d503eea0e194512a75b5f37c21109d9d3fd78d0b8176f8c2b1e4d5f3b5ab74d659c3ee6947bd0de6b3767a3c1eef030a813cb49bbe712170879f603b7c122bdfa32c7a9e3da2e7e5bc9e9027914b7fd290eba73a8fbe9f4f07f771becd497fbe079a3742e8add613a484c731ee937a3e0be305e6636e6c77e32e1c4b396e6230b12319a3af54b221510aa4144a9f1759b8c9b143e298ff33376afea70ef2138ed50b47dbd5331259186b3fde57fac450813d12c77cbebe81c01db01e00c8791558f91e97a36f98586ec7e7d351c35172372d6ef2ab5c28573d153e2a7c3ab667a15126bf4048161ac55120b8f066036f76e4f0d460a7ab254f504e8807b74aecd65f0452f25340775b519a912b7717a440964aa556dc14170e33ceb3235477645a92294de0b8f4392d97b302c5ddbe1a0185bb3d1b2183bbfd7ca5c00be357ee1873fdc71ad7fd6b0faebf2d15a184ebff36b84e250750a0b2a3c58c105192acee56c5a773a749ca9e73ce39e79c73ce9efc791e0370bfef471a97eb5398b4319f633002be502f912b8944aae491168104f1536aa3308963de09e3babb9b66a1318a85ca5aa44f5020b00ce83b04360ed843a83f6fca9f7dc7d9810ce8e1c3850a8844a06f642b778212c782261fdf8b5e1412b1ff8544361a162bc02981778113857b23381eb781dc85bfb8f345c006983f250e3903db983f650d77fec8ddf9a22dec9c20e3f4a7a387e843d182357e9a1f026ffc341f04360e8d949d418515ff347fce3981b438653fee641990f93f609038e49d4f418aa5459ae54e0a6399f9a227d81fb367cfa63fbfc64ca31ec05a665ff9b5d05dbb0f04ba39bbdadcf6e9b868b7be274d527aadb5d65a6badb5be74e6afcc20b042c1529a4cce506db875ab5badf57fc000a451d5abc7dddd6bb5b654fa5aaba7d6eaeeeedf97aa1fe8e3793aeac16244bbb445997e37a1df394f5eb47528a54e9d52fadcf68ef676bf7d3a3ceeee2eda827bd222659befd27f59d9a669d135b6115dfa232a75683a12e6343bfc449fa668a0aba8942db75539cad40c040000200013150020200c0a86442291482c9847bab07c14800b759e486e541c8b835912e4308c420819620c00800000118199191a720016161540b5382fc3cdc4e332cac3af1690ae77ebd4cdf42bd5b90e82f5e9ba09d4eab0d795ebbc67891cddc18772743f7ae8e55452a2d7857b92922fa5dc46983c6e5e0cd977635e4e8fd75d3e5f59724603bfe2ab8f936e3c27893795730de440c19840b6c706e60bc0de09a3ec9a755dfb4dc68562490a0929d1b63e403490a666f09a0a974f62d1e886c7c97df24b33548e50374989e66619eb4c792771d3967b639752a0efa376b7c4e75829b32dd42e272c9d39d642f250cacceeda6253a389fcb28a7d9fd58508a8325e411af39522c4e3dee38bf681d799f20b5ecfaab01db4319b928ef2606dd4aabf1b38360165c425dfd54da1221639422bdf5865618f506aa6829cf5e502a0164f8e3eed0df3cdbe5f251cb629cb62a9d915aed4102b97572f1ad95537c8d57a31eb79c9c8447a633ce3e63340fdadcc4c89fd82121845ee76009d8b36fc7770e808016169eb1f976db0b62dea62f705ca4a077d7201d2b4c91de1e89aa6014b061e54381540d4754942db27d2d4f494ef809e08a33d9ba835f8f4c4a27b42e341ff3a8352cbdd8cc5f94224b85641cc4c2645c7f38b832a01b186d60623525d38bb26baae6b6b52656b5ff20b309cb09dae5dae93bcd400ed00ef2f1f85a14090252f3381e8efd65b7932cb43121f52441b00440b7eb26be441ddca88f5e3bc5f6b72c4390d6504a4690c299935a51d26af9c1c6bced04cb9c25af3f563c1ece717cbd928826cb26bbf8d664dde0db49d7b676afe395d40b38d94339e1d2a27a74e60557817104174c66e3ef1766e6cb5187c752a9064d0b07871703018ba46c62d3c2b612774b9e83a09b9b50bd63c1649ac297d83ecf8e761378333913a715c0594608ec23f867b915b73fca3920e570411486cb6c3b529e614360e0d8f9358a9123972f155ea2b5e19225b1acd63fa33b1da947647057949262fe06bce9e4277d7443484ba73e870affad08cef8494f08a107b6bad3ab10e842310b741787b7a48be83abfe939814c7e7b25c96e5f120f50d9cbddb811b007416b09f378b01e0e4197741ab1278d7750c272593aabcbe56e2152040e241cb3cf1b5f8adebaf321beb26b577e09fc4111ff4a4656f109be4853135e281d47198729234f4fe94473e30a49e2ad8ef43d6e0dac8be53c0e75464e254e5c9f71a92766c5825bfa82dde9098dc7de278dcfd92561ef9917f6c0d128ea3ad1393fa298066eebee9e11d0f3acc701a7a6748bc1fd8d34b50a7518afadb88ec9bf4d3c67e23bca9793b4029e8d3b6199d8e135f8e74984af870dcaf086cbbd59a3232f7a2fb41d4fb0796420b62e5cfa1c241712c8566a8b4dc26044be1d9684c030211ba1f34ca6625ebb6b7b2d3d39aa7ef7dc4528374aa7a97405d4175ab50314809c9e241aa6aee2b8054c889e8ba8e6acf31b232d3698366548230a8f44c5dbdc70f38b928aed2eb0d8f31d884a76fb2754c60958538ae72129177ccfa7ec592a087c0cadb6d0c5d8fb49132b77a1f6a00fadd03a2dffc065dc5b4eca218652e803c6945eb9e7ee49689d633b579349fb8479f10cae417c0d77f26f260a98b1f98d3e4147c12410a276416339571cc1c981918f8d9a28f4a03652e6009b100bc1e9619b8095fef89ba465ae8b0b82cc7351ea86a62f05b72b8de7cdc3ef28994724b8e3884d69722f22e0ae3903200786fd0e3b382cebb00b3f1b700f6e1a3e5c4bb042f0d561de39137a47f338d46cfe8aea9a626ba3ca98eb358de26f5d70e5e2521163d36143e5469761ee48e20c7a3ac2faff669392f8f66a2ef27072b4bdf787aeff4542d0fd95537e5adeec120992ff133151f2621ab351a7adf9ce6f2b3e1917f62eb0f7280e464e2875f8c5f6fd57cba4142a5df452882859bb2777fabd45df39ff83800cfa45e3c164cf19b097abb7371f42dc726f879185e0702a0944bbabbbb242e2047877f2b26ee5e9f94e4ec408081bb20c36e818c1c0f7f1695da682cea2ae0846d8bf6d1d86e25272bdb68decb5328133e8ff29432c9f238da78f920873d76d0e577ded4a8545e6d3c79841e5186506fc1dcf8d7d8cf96ba8a963693e0db472465c1c6936b9e7ddd1d3126645f663f4da55355d4a31887798e412862a7596c91a0b297474f37f2927e6b74b438c4041cab6b2770832d11ad19b8f8f6c8637ed229338becd1fadc568b2a4f2079de858f5fbadacb13de59eb948c7b054588307054603d5d7513015906aa3c369d32c0ac349f1b529d708be6282aaf118a17c6e643e82d2eb917b6e93fb2f62cee82b85168413835a20a41089c16d2e66ed6631cce2acd7472711ddd78d83f10c458c22c14cbcbeb1106270aaf24936346224a5c0201d6237fd3d98c6a9bc9668b55eb69594ae77ea5b97aa2942a777b245ec85e9b6ac9f3231fd7175b146329a9d7e3ad00faf24ee92d4a1f58f1675a86aecb8f21db391fed481026811fec7c2cd6f6f2464e1acc26f4541464b32f5657b793f3a85a6608cc0cfe87d0ef5b73956f168908a2d33327ed27084e6c4565d15d55fb53a55b512b42c47402387e65b5505dc614c42527a1f204904198a61688f57a741cad190a3a3aec9794fb0b0566f2d391e1bcdcba57aef4c8c51d0f1ace93d7302a3b4951d0e923afcd3134f134595bb40dc0a3efc54414a72d7d4daeab7f02c96bc9e3c882a0ae3bdf79c26dcde62e6c124951d776fce5f174cb70f7936bcd840518f7db95b76232a6135f16fbc704e9d11b75b940e3d9220b9129dfee217071a5c5c36851d7d00b3ddc62bd5c2c3cd7c9eb005669e0e94efe37282031c6748933f16d2c1dcc394c56055dc4d3658e3188027e4b55472526300ba6e5b894bc078e5314f723d1c736764991dae0d3895c6872e5cdbf830aa26f50f9fb2104ac00c8414fcbb4b79fcdf3b22017d3175f3d4ef91dc88488c0f3c226859831d7b244a8dd74145d5cbdd5b4a5693b2c22838beda90b067016b08ec776edd3a8ff18a531e20a39d659fdc39be343acd39cdb7bb13d2582e37380386a35788e3fc3f8b08652b7b384488eb14aaac426d2c0cdc78cbfc54b7bfb3bc768ce0d648b61eb7440c07c835e8fd6f1df9b78007ef0e1ae96f9cebe81b2315bba065e65b2980314df1af4c5b5f771ab0342565f6de6c0b5489e8dd11c93593609dc0e334e3e304c04c020c5e12250a5dffaa0a607c215a3d822d61a850b5728b2852afcaf18e514f6618e9fa2c1716196b954cd0a67bb0ff191fc76e5b13ce1c66fbe29459b59af6a4b8257c0d8152c825360cdf24853ff4adacd2b5eb2bbfd8a20732e11c9b23f3600982d90f805ef378c6d41fb8a3751bf85e8329e8f8bfac03074d9c1262f6973091b148195dea0b72275f984899eb7878ecb7ca12e3f211f267121c7e06002a33c021ee7f65c9a94481a33b264553443aeab8a106323fd320610496aa94ddb534c7cce9097eb6bcb4b6c7f7b2e7a76d299bdbe49b55fdc32c671cc65b216286c886d2a14701f422ae639da14e12d3c40e4402324b5f7ea53e5b4b008f43c17f1702f48d0d5fb54222129c069c526eaa98f71299bf4fc8f49df76e43b43932e7d47d4265d1fa7ffaaf83421df1893442c1fea5171b2a65dac6e3404487bab60a819b41e74d26b6c933802becd6f852b660bb7d43e6d0f80a44624a6a5d06db198da01c67b5d8d6e51e73f5b902566ea6594de30b07c383ff6257dc4f05fa3ae680b3f7b49d59c3695a124e8086dc528022bb0f201ac9602d942eac3b0f24ed664115dfc33b1fd4c8c4faa0ea5f74ad64e9b344f2a80eb041e7335aeb6e708af333d4f994e926050af2e93b80b5f9994102f01a950f6820eeaeba6461483cda72d16283242c78e2515445269f45ff50f8192fdcf384128594924ec941719a2482a867ba3aecfbcb1b25627adb468ed2730e8924d1c46aef9550135e417aadae7b895941be4ba9a6b9b360df6dec95ed9320f1307c11a0c6662604daa4b7830b8de451f2c109220fad25dca74bbcba2884965f8ac8069d601f20a7e7d8a6be93aee87d76c5ac07edd1ace5753dcc7aedccd3ad93317080563a2ee139e5716fbf4a3ac71b294ebd0ed44495db1a81126c02aab6460a6a54c94daf0032fc31822969adaa6307df7306221cb12736875032afc75fa3cf21774f8b25e1f5252a37f7aa783712a691909a1b5120d2e7f52a879b5cb6470eab172153e2040839281484e3eabe6e7ca661a0934003daae189447db2242950763005d57316f58170ac5cf901b5c26101f400f0bc526616192e79c5b97fbd11bafd6f7d93e98867d474c4b198bd128fc8d6e15b9a0ae5b8e469a771be9705e356509f48bc82b23631ddce6ade81994a5c86d5fc4cba5c2b6ed00a9b017bc8057ef0449d00f768c9329673e93644b7febebc03a454e2b036a363a85d4a3660cd0d4ba72179f36c6b8bd72043b39f5da573459e2ccd8b027db2b550aa1fcf3c6bdde57be355f426a894b23dfce9db3f2f5149990238374c480278fbee296e9d3e4f6c4151b50d24de1cbdcc10b5a66379651efef037005887ba9ad3657927e59a58a7ae6bf5b6c9bcc4d01d3c95c4104bd32ff67af10fddc5bc54c830ffc648f881066ac75762e1e678fa7f474bec4f894bab59d53fada234a2bb2fcbc2e4e4e69c18f4fef50a45422151d5a5f41b1d7bbed367ca6f80fa1d700c57aa5bd7cb9378a33c19087fd5dac637d0c4fbfc610523cb220958ecf935df577362b3a60263d113ede4a908f5adb36247035ca089aac760c639ee26d46954b0fc5ce2ae055cdbfc21873f2d6f52faad463838e8fbfdcd431e82ebdea0faddf621b9b217ed5dc77e332e50fd8bcfefe1dc5442d8a8917a386b40a1852a21c8a01691e21c56ec7afda1daef5f0c935de63c4f556a5d92904e291b5112eedad9660793799ac90c335e88eead89d038a6f3fbaf0761dc7d303650f3153a4ace076e33102c2ad58c2c643bd4b5deecaed44619ddba8ec5704ebabb2a13711070586705bf774c13e009b463b1a37c27382811ea58d32cb1c7549e34b3287a88036f73d06400070c78f6f76575a8d88357df9cd693216c634fabb63fe0f5c4ca5ac835d787c259a56ea0969ef5435621daa28e0158a3198035cb0b0f60f190e61a510f79df10000b0cc35fe188832fff6a1b33881d05abd301f1d8eab01bd9e34f2fb851b7ac01c170bc8a6c6cbeeb3cc4b95957caf6e1b109570463855ae2a563ae81d85ea794cf922ac0c19b1559d78f1b4da801b048e77d046f8e7e46033bfa3b329c8ba598bf328055e11db1255c5fd05cbbac0354c508448f3a5733bdba9a733eed7d0e3d2111580d2b22e92914671f5695349909fbeaa5c4239a371fda4299c112c38a0bc460a01fc577f991c7311df80ee86d3ded147721f410d8e33164e89e3173b7f3023bd513579f9128c3827caa7548f2802467198a19f0d933740e38abd6de880e1287dcf6c66ed15103684d7fff4e8ecf8aff45f3be420f8bc31a2e5fc6d0c206bc4a6fffd0bebb472674c3cc2dbf4024041b0a1a224576adaa76b8fdbebe154bb3830aff07460d4cac0797909d79df6b1d7edf347229c1f90c44a1b899a407c86ea3c8bf6121f28e1e282ed7c95413e0806b2e29917f55e1552c7b71ff19ea77362c1ff5599afef9f7079d8dec6aa9383ab3dbb69b993398862e9e8ccdb3271121e95f9b4937252ed89df99f23d521e9171e692b8e01ed6f3137c2a3717f6a70a88f6e5d883d05744426d73ad496df7093c0f78ef6149330c31e70f5990303d5bcd1fde50273cfd88cdd50254c48bdb39d8906149a684a226a754e46ba642c2da318fa4e8b8bda99bbae1aef5dc2dca168b73a27c9d12d211915c8a573fcd643d7e4ea24f9e784f776e209d31ff1758f277bdab239e4d3408182b62f7d3de52b223c6c1f7661ac3f2882446bccfd2160cc7d08829e45767d022830c71f07660d9eb8d49860b0e38f2817ba0d7c14cab035e91a9db3a6d871a4ce0d88562e6722c0378e6524350691c4d55ab5f67ef808e9476afe03bf0edbe488a520fd847182c405442aa8a306314b5edee751cd3cf50b8f78feb84185fda19a4a81f2b772e0c75ea8d78d0fe5ed45400d5f93db69b5f233e9cc403c07aacd330c9137682c61f1c3ad484c6042bf09ef9730c8a0011cc3681c2478160fd41db25108644e2aef2a9701db44f77391b9a459d0aefd6ac77e359c9ef557bf3881f0eecb954a113c77eed31d71e7f016892e4d200a4b759dc9d24f5bc55cc8f02733cb2a76b3e0d5ea0a851fa1420770e7b544fc0be70e9e21bc1aa255b249bdc3b6775cd3f5b232912c7e6a226eaf7ccf83e4f6612e72d163bd58d8278b835a8fd981ce4304fb17cde4b0a9b4ced818f8be8b21b11511bd619a82b17c696ade78d374f112b0162b097863855d3616a558ad023a54b52305ade3e4a080142aaec57dc59c6b72563a904d946b3b58d5ad0805a34902a8451306ff8e458824c84a3861053d14c793dda5cab0ba49c7aa7a9d802dc104900740b4f04dbbd3bfda7442a91c1794f016b4aee6523d14e9c98358dc3a9d217259942e25c87cb463547aa626b36d6dcdc109481998085ba00b1cb197619d9ceb5db7869b2bf86c4eff173cbadbc34982ae868032cdc4ec476ea35cd40a1ae2fe9403adc035d079e18561165714e4ff0d8f50a0544aac9d76facaeb1db946e47b2b494a50b1fea29069e686b0e2aed5c9d95aedd8b5222ce8a08ac2b9dec1b5d0025bc246c7d2baba8f0b80463364d00aa5d108bea0aacdce05df14ed0501d205b2bede514ac0aec8433e7ee02abba7bedee51f50844cf115fcf2e513931b71ea727ac8b221d1f5bb64602d20bfabe9be3d9e2d9e3aec7b848085a97d979202f89e75b5c4a0a121de74b871e0bb12cea003fee31adc7ffb1be5496fba468f7ea9b5f04d1a0ebe7e151ec39f862e1b0b769459ce0c35ad24d6ece2880dc06ea44d9f8f5c002bdb60da0b02d29f04392dbd2fa225ca2b97520221e4a9a12460e2301d0754b85cd49b4d2a0cabd2340c8183dc8603924e0dc8f0b87eb6906225d279324442f87baa55922b12c7be1dafb11cd1060256769f4d177aaa1e368bf9c8cfec6cebfd168437c24fa075c3ddf79d200d6c9c825c0ec3900851531ba9536d09d3c2d1b5d1b7b94632d40b4522eb621c1ce9754711a4fd80c5f402d0be585205a837a0ab4f7bc3e4acf6004daf9d8bd747444afc3d6a0ef7d92ba4391f278efda49a69da6503f12590d7cf4804ff876cabca792c4d58e983b70bea759a65c8c857967f511ea0b16690c800faa4e0f5dc086cf4c1a50a5ea16c8d1e03cef251fd83c353d58e65c22bc81b95e89e2f5531e5f0e677769fe7caf74eae0db47fd4f8fb8e7d3a7262acea7082209b87d32e17db8d3b08426b6bc80973da92752746182329122778042459031afe810b83caa0e36526781d88bf80fa20c72a5aceaf5ae0a08e827cc50ae86686f101125197f60a5ba168115a1ee473a58beb53834136206679d1e4d8f925aae3b172c4381c980607c6645ee2387a558bb06213893166e4da455ee4ee1bbb2f15b00b1dd8a9749dd1af2e9312aa8834cad7c64ac1765cb885db04bb05d5768ed8b25b3d1e125ad8322c28180ce357b201a868c51b69f9cc16455928c9525941cbc12235da287dfb2cbd73e405a0fc2360d332b0c147e13277a1a8fcf7175990b195a677914c6b2d2fb02348e6454adfac5f00c6d386cd65f340f08b6c9dc455d31916d2a6d0daa0ac48d0aa36fe439ed2941a8c6037403c32dd6ed8b3113b5c1639ebd00ddd70f69848b16b670be8b55a3c72bf941372ad1c03496c83aea1549b7867f313ff273210818e446e1be45658709559ed545f58b7dc3d8cabffb5da247958b1fc7b9e156fe98e99f7b736c07f8d43fa826362cf76758a5cfd5528c9de4415d2794aad76da2ffd5ba18673e87f85ace0ae5ff669a0accfb3bf9cdfac1cee95fdea59f959be08b28ab8ff65a6390e97af84d680a4f2878f9e8ec3d4a2bee05fd3cba6db9e39001ff3166e9ec67e6c0243b7e73fa337afbefbb2f597ab1d94e8d056a0030a413b621397a3564b79aa4e327440cb519573e1a1e3dc1775f3e445c8379b01e24d02446a3bf3b290842b7b2a3cd00e5d907582680b0edfe692f7fbea25ed0616d3c2cdecbf81c409023e20a5ac4d87ea4e704ad9dbc8a234a832b2294d0642c5397ad0075a0cec28b4a04086e757666f65188056c9332c2c1084030646421b6cd2cd8863a52b21df13544717e7ad236923c31fc2bce4c05cec986e9525dc5a3bee2542e1eba6f1bc3a89f58da8f8bf5278d4eac20feebe30405d56fe6f8005b692d2947750a76fd4707b8375ffbe5d5be4c34cb90c1a4f576403095c6d9829f158bb3764dc80da2fe530c4d827bede10cecd3e8df129222eb53e76fa3f00c7000483c8d0569d49f412b87c20711de6a268a78e006d8cb98bb3871dacef6e230c3c611fc0ff58790c20acb4910b60a942c3857d7065ab65727eefed7a88362ad69c72c3e51bd2292e4e99ec4b325d8b6a281b8b811be543e187591a9c72072fea8af4701e386b6334170368ce7a7b3065e9e4ad45969cb1b8ba96696b67d5944f59b2f75c7f17c0aaa21f042ab1db654b18b416da611e44c9348ecac64eeabb240093de90ab49dc9b9a307421ca3c7bae476371e057f92c14c67d614349b824e634cfdfd06b4dcf145941dfe032d7488159d26e1f76bc74103031f78196abb7d53703cb1c5da3c6b4bf50e23274db7d577a2559343a4f4c002e4f338bf593155ed1f10f949981220f6218ed76446e4849338ff1c264bb5b547518bbf4f800a73e09af8731abfcbcdce2034785311fa3239649343f8dadf321974e97175fbd7e674640742e570406b3211687add775489e59717fca9f5698aa8e642e09a0bb26a53e662e9971570beab59d7ee33e8bf34c6628aef8b73a1add88602595bc2713ff1537b5ccd8203ce6a999b7fb4c862cd9ed379c7f4636b7e2a5017b73d3cc5b9698249a50921912c5c098ca2dbb745bda1d888033ce11262604db08fd6b814f5d24c9a1a1c6ec6cd9ffe657d361d88783a1793ee8ca51143c8822a6639918a9148742a701ce144ea9541136df68470cc1cc830e5775f652446b7b2ef4ee112991841cedb0fc20663d4176988a2d79e1c311b344457a2118244dd17487b9a85915dc88657e1584a519a5ea2f9f766a8d108ee830db4ab8c82ff33b6454443943fdc8e98b8d7cddba1f4b3f87f734aca52f4a8993494faf8e79e15b2f1cfb843aec0fa7d6c2067fbf63110bfc43019af7a9bbf6c7ca6b1463962e933e3b1803257fb22ef3b0f5e478386fba7220040529f64c1e8594e600303821227dc5aa14523b0a339be3e3d50ff564f2d27db04a63012fd2a4aab4bf650a407f74d0b3fb834bd01c342376e33a3ccc4aef64bcc1add0886c7d63d2595e936ff10c9effa4898691869f6d1a8cc8f37f554ac520afdaf0618ee36e7db32fd05b96326c48a3cf9b15ee26318cdf25d4cb8d8264a90ca5e8b53c4b60fb6af76ee738af309b069758dc82c2f8e0f45a4cf0ccb599a720a2b748aa458832891ba34abda16858252c5ba19bb2754519fe0c84f8f054e6790bf6e65f59b83cd8919333551638442e003f283065b8155a886f28ba6f4a23e403c9389e4093d92b743303600f0c67b775015e4f5d18127ab862e087c139810a14dccc5b816f3d9a0b70ec422e88b446cbc9cdb0575e1fe13f0af933a6a1a8369c357015f4143362cc56cb9179aa2bc7fa63cc23631e33990a0a01feacd9825ef0e92cf3e579c0d2841bfe5f28a1cb9034434812f30f970ae9fac43259fc97bf5838340ad0e20a5d1659a21e9722c140ca450b7b33ffe4c4ba8e9cc39d8e27529af9f0dd6c0b820c0846a5c8cda9692e80775c9b64d148a5bdb03c6c98fe7d02349da5c105fc3ff48cf2682f0ced8bdd693a3e1e1443f4fb5f422eb2f628e7b1f91000591c8310478b85d2fbc757348fa6bd52fc3c1ee12663dbd4eef9c2691f9b7992913228f4d5488328db867ee1b6f761d6bf468027ad1f7ae5a9e5a25a33ca32b60214147f9ecd0b1e8c00bb560c8e80c5c11ee179d7a3680d0d3c57637a2e8226164d939e8b1ae49f41c9fb2074d20440e1c0138046378ef631440f8a71ff67e1913f14e7d22193f3ac2197eb2c6af0fcf8f32e8da6ffeddc52da50d077d8dc12bd87777ab31b3e45aa926047ed61ab16f0e721f565791e09b27ca23458a6d2a621545d88d3a3028cb9a154a4887b290546ca2417d40f114d2ac479a52b19932154a9a2b4346dfa77428846566d7d3ac42de1abd02d1ca7f0381526c4e8a7ec35dac30257c75b81eba31526e1e52150839fdea262de4d80ccae4f877cb19870819c85defe3ee20a3f01564faaabf5914c45452480cd2fa7d8415888cd8d4071f51f2bf6e805744be1aa154574269daea24f8dda5205018ae19e14a67c7163d659036e0524eb8d2559ae30d82109da6765c2b52ef5fdf209b6b479d4c1a28e03185fa5e72c25fd728b9980a7ece2b4468fd411d48c041e00581a42e8186c76c89c5de2c8d44398c8532a637773d54aec092b388a3a45f62db10f1b8ef479a609d909d9f14923a656047b4471f9a33041edfca480768755b23b83702ab6caf5b8ad811c81cd05f492cf11e1dddc800bf01b35cc1ca507ac42aecfd151a82c5e1e0cbad099560ac166d34e85384a2d2824d3c4876b2cedbf124194f1267884adffc80224afc169c9dd1a9f68526ca1f3c108227fdfb1d03959f12658b497ab4ea8241c1315600c7363b5ad1850fe7598144a96b8ddfa1f509c479bfaa9c9c68f5ca6f536e391582fe3611138febfa15225733a0bb8d17fcfd5fff3e1aaf0fc7147fa6557813ec852f5a97cf17712eeee5ab668280cba4feba69c03ebd091ef53dfb33017b1fbfb58399292d5958c628ff2a8ab2a359732bf7a923fb6057e71bb7a2ff641b84f13ed9134da77e016393c08381d8281523d3a046365da887020858d4235e908e8b45f0efbc07c9d9b76a3ee181eb430dcb80306e20fab14cc156b2e2ea31a94435b596912ef185ba3ec09824b5f0a149782bac4312b4956ef6d545c93849035b9cdfe480509efdbbc6b0e6a2e6ad413be88932c87f1be8c4af07d03764732e074c12e16f4ecf5a77d0a449e23fc5130bf85da3fecb079ab3144d8f979ce48b5a117c24fb1d3e82af48d5c575bf9fb61841e0ad899108ec67a279fee2037ec0a25158940c6b4ad81e9ec1963df6de0c8647c4920f140c17044968b3b3dd73ea7110691fde0fae02cf0f83fc89e1520b6186a5382aaec462926c7513b79f62b0cebd0bd7da1d74253615ed52ceaef4cae3b0a343fddaeab634d5465c865a5a10037068a5ab1c985a499f132d183c0bb8693d81b1cf307170277280f880603d5c6028f0f30fba1e47be1a5f12246946f7d1f63b89a3f720cc0c06f4b04e6b63a8acd06efde462fe71653e34d9017b8d8336b0080d6bfb49ac6ee56dd2824026b56818070bafa0da0da3e10f9397b0d772202be3fa86d098791bc23892368a99438be1a6a5630a2d25a416f126e822a717a2239374a707b304d9a3b79e3cedd6b48b7c73d06c8158547788ec7d3a0abd105c291130aea2d7408df367cba4c16b89c78dcdcbec420bdead4e52a164e7f09bc3e6553b52dd557767151ee668fee330da9d85f7413395575441684d12649581bdf5990f1ecfb50be81d9cac864f5025ad88595d531191ec12c624913059f1647afec3bc4913e65641232f0b3cad57d99e764960dadf316515944fb4c2b964fc9eb9416797ef0a0afa1d238e6a84e6e7335682b2d95e6c09722aca6413bda66dd1612f0cf3b500a4f8901e29d6ebcefb1bb14c3a77ec8d2256072acb6703adc905844b15e52eca6186fcf1ff6832ca1ec8ea22e385085aaa90848fe1af0b6697efcd47b7e2a400daeb33a18ac7caa7c0e7d7d276358022a2153a38fbe9475964243c331627463b00fbc7748f12d5c5d0f9af4d8a025ed3706dad504ff3f69340196ed0d8abf1484564745fac34c8fdd92119900805ced462c4ee0fc361e7958a9c2b3c5cbb62109d804d86a262044ab41c80c9abd4da4f1de79fbd8328e28c955cc0ac04ba675ee3413767389cdd53d17e64ebaee4c8b743112b55f5af3677819e9a3b9e728658857a998b9ba317e83ec3aec78c65a2683f4874c880083842ce11cee803d173134c76443677f9737d7dbc16b496935857e2a845d94e195f265ae3125d62505520d1dbb75e03c8176ac2e544bf12f8a382058935c8c9ddc673551a3c9c753cb82f663317f979de16b7eb739048bca5f1c1ef98f300f291ae6399c84f7a10ab5b7c073d210f23197df3386a2e85894a10a64f1b3369547f555b51009224d43f828bf41d2a3e36a2d335c84ee8683a0445849f223174e4208d9727bc510535c8a1ddb16568cdb87cc6e5495bedd98a266828b6a0f752cafa20bc3c5e2b16e7225dd717bf8880e664350231e37a6b85281caa22cd49cffa5f59af522b728b7e43c2fa7e1f5b798fb7ec39652f7780f33583c81f4d0c6f4e48643bbd7494eca3362c092ed694de56fa3d1a35e18ab64fad7fdae9fcc4b7456e3fa94f6fc396502aa1109ffda38011ed8120bad5dea42023d347e1e78092a7d46b21a29abd195fc6af02b8633dd9a6098d186bb30bbafa7570703e25c6b0084b570a58cfaba49377a6981d033389bfc6ee3ebec1607b324ef4162857cc286f0b18148761f8418df3002d57d35eeb1ede55b98d062a90620d3b948a511fb84c070e58b44a9e2840383d81eb22a06c22eb96da3af01062623bda789f0a79b708481195cc119146a6fee74e84cba2e291b1e0cf59f3b38a4be2a74e1fa38d398a19ee0b5377ac275e6350e87f2a68e45800462f91a48b89eff8b39af09bb020cd41d5c32c1a345290e6b5d13201007e91a84c97192a6e7778a9fdad01da7cfe61696a4d47018addd4dd26cbc4e8d10bff2b77cdfc356dc41e5caa81d5fdd1b1a8cda26a29b1c9acc19e270d000ca9bbbe1449208de8551ef09522eeedb70081667175c7aca56db08fc546afa8699bb3f81ea1abdf625351ed7ef94f54d433eae0077a683c3e88b02194cc9d1cc4b53189213e14ccf2e6f686e1d29204e79c3918f38dcefc9143d40b5af377484532828e9fdad3d86d18ca1409cf560b89aeec5ac30b1acba218b3c34e98c447db98c4f374d87d89322d50ca764998e5f8f4c7a3f83dcc4dfcba3813257d43252ada052fa2cbbe77c2514fda4c60cbc861a38c668ab776f678342ca50ad8d420bb411611785c3b08d0844a0ddffa29b5cae86e7cd6c4d25f77b4c1bea9ab691e9589dc20cdb5c04e86effff2d222dee8b5060cf192ea093361f9e4a01669c27d3935d2af1296795292426425aed763806ef938160e5282c8abca0975244e28b76822f2b021081339a6e7acdda72fe4c118c2a74ddf2557d98ffbc744f564ba587935c0a7e176f513f84ac4583850e1109b5530ced14d6ca85040f341154e7d9fdba6732868077cd8492913aa51be99e9c5c343e853d85166aab8196a8fca6a7521545b3886137f6a180a177011c0af7b679740cb7e1d272d0d77d1953c59e660c9cdea7307ba4a8c25e9641b5b3102232e09491356f76b3df65a7e2faadbad453f89205d30ecfec6b05d7c065ec477622d7bc1a9cb4efbee752a8a5d82320d39f37cd01d9e030e1f53193fdb55f70f979c6302c3b4cf63b4bd61e091e761d1f3cc1598fcab59930f5a4384908a30a72d8008a0c8e63ac7d39712241810bb3b271c41c38974a6e37643399b06c69e4b8c53ab0f7c48ece0fef159063e3ce211548c1c0b739f4d22a0217cbebf4d8e8833937966b7c5e2f8da55cf852c3cb9967737c1a7ebbdd419e696b80063f5765ad8e6840a9292b8ea37bc90cafa8bf02203245f513eba1cbc483baaab6757b6a06ff787b9ee4841360fa02dd6526d754b40c5c7307bd539b6c19046ff6109383a93bd9e94d90b94092c0faf15b186397905e1ae5def0d8821b8841b8065c7712ba67bca8a5a7be6cf3b2f12ddda8cc02a9f4c84c36724472c8a2edc4b924b7abcf4649e2fb178232e1f072869426de014a4941384da3b86719ab884adc067d19e7870cf4ab89120fbae865f65332ac3146dc862f3c66b2364334a996e130c8c6b4272f66ff60404763b4c8d57f80a55b653d50e5b0a651dd86b5516727886f47932224e8d17404fdd84d9f7f2182ea9bc1a76527a83931bd04615eb785abef1b22fd2f919fa6e0855540b5ba66782e0d8d7460ccb8a50b06e963099708cc7597b251843572be1aa472f14ba8c92596077413b01fc05d74e90f8b63bb14b2aae90d8614b69e9d43ad8241dfeba1f728eb46e3fb7b50f10c0a7bb714450960e627ea1459c99aed8ee7a21dde529951a57eee15b134384e4ad9436803664ac92284e690c16881ac928da967a86dd992a0465478b656e8c47181187042b0b976106704ebffb0f6a2c2ceeb392af4ba53a2ce2b05aec60fc102da4110a38fbf5220e7a1d05f1961a9297a24637d5d0615e0a146e9a664017dd4b73280ead551ea5cfa75f3916e21bd89e31831398fb508367f38b42d868b84034140e65395cac81c6a0613bfd41dc2c14b06a08f8bb519dbcaf24386320951e763ff87207fe75ae87e8de7cc87870af7f37040c55cf02dd0ad5e7f72eabcf30ee06967ba41e82b6d981e20a9be563522f957c01db8dedbe3e14dd1950e31a9ed3fac863e49d07f5e9f4452a8ae4eedfd33f98cb9bd0411c0932aa3b993f58d4a7a98333e01583a9f16ff96259b7582723657be6da805e5d4b2e7c24a696fe6f059a5a524014667ab7fca20816672ca03cf74fc4d9144d581964e72450e9bacae7b56242eb2d3f820bd8c3ef49df90c704ef017cfce40fa72af58d72db4e67e918d44fce85c2f33b927941ef13af396af1536846db3c40a3c2c0958c62c80202807e685c4ca5c1db182a8c2d712feda533027a546d648fff149fd63d3a4cdc1c4e66bce806a9e8b9348b7c88944f864987e5a51fe5c089ebc20ac0cd7e6c9867a161aca731310b25d5cc2f26d5a8ded0f69596f83c526a0f6985b58db86bc9977404b512d674a5497dfb84a5b27c2ca9a4372ff2fb20a7394853a90bec88ddbe4f6e4a85278ca3b9f1463c926049861d780098d2e65526dfbef22efa1a37f335ebef40a9e22b68f5c668ba67ac3f795465945c01a5d7aac5f83fda1e4d9db8ea9cf31c11981d2c3ae8ab8f4bb4078b9d39f5ec719748a05a91861d4cd20288c4ad706cce1a33d824956c081dd5e52d569c46d31533b9d2eafd5452e3df0b79e2a6c316d377d3bf46cac1092076373a09387f4719d6eff6366988c0c9e75ebf4ea1c0f36f0c105d42271f15f8235f99a58f98156ece4477ee9864c7e420bc71bd455ebb4690ed3415ec883eaee597bb64ec36221cb6fcbd4633215597086e0f9416bcf404fce4dbf51f4de7944ff64ba7348979ca3595ba7c6976c3561079ca0b8c0389b63e2c025e2e8e236b61c12b559e0623d04f08abd4eca19d5ec9862fb6a5bec82145b96ad171d5a0db17354e25a9eeaa401fbf37b68e0f992293c25ed96b682de4ba43bb091981db761f1b7497fdfe8c33e8474282033d405662b128ba2517e36182820b4d8f5c684c44ea3af30c614f45abe7fbd9643e6f19c231210e3c7c964c9278bc5b6f017494292d555755a34a519aea804132f752de0810d9f937b241013dc1d2d3cca5556a9dd728cd17a6bfa71c8326b9dc742b075aa77a2661d34edeff4a11afe1035057e2f9bd51590607cd0ea352f410686ca0efdfd12f3ab2c2b6391f736c3bc78e75b287e5e9ae754db7255082f0731e1393de8e7f63a8ee2e6e8433f3766743099c68e90cbf5cef6eaef8777647668a13b04e3eb9d0e2b209a06c0d522cbe7630daeaa13314f83d2ce321cdd0e93aaaae79b6a12528ab45cf48fb24c6832eb296f32346276f44899ae5ed387c90dc4353e0d7a1398c540157671598111dd94e633cc2363d71ca7f99968e3f20d539d1227098c15c6cc091fc6b667a3fd134a0c9852ac8d94d0abf145bcd936792c528ec3ec8d78a221dd61bc1bca68f3757519c48804e8a20cb02b3a4fee431b21dedcea5dd352eb82110f5587b9b7805c72bc8418ece754ef8a339ba374bb9edb25e46245cb4dc57b03310c340abf9a230281dfb64b5410bf5c8e429357d6fa751670a348b202ee8e7414f5ab171fdbca91b99dbd26df4f86c559902a12c1d894026e1ca0325537e312653783c643b21d3c6913f2c50f5d42839985890402990ef08ecd1a291a54d75a0234584373c11e45495a8e8e9e1e4d09980b7f230e513cb46d9e1ecb247054e2c3d3c84e6ff0c537f41c153409a013482ebec910f37940a11e58abc861a1048f5140ffea042dabeea60f4eca774b6142ea2d4b9e78637ee89b409929b82fc6cf6b23a68151f7581513e4fc2e004ed31c522e739bb93cd12ee341c9f0d6ddfeef372a85a3497634b76732dbcd4359dfcb1cd108707b935bc39a9be3a8380da8e64769fcc2e7950f9e69832e6489c50f60a5cc28d07356d24a907bd01058a5a12bc192346eb42aae2f55326323ddbdadb1c575978731a176aab0d1de5b2d7d05fec8cae450eeac14c5d47e9c5afd225e01e116fc387866160332b5d00b541bd93d687e0bd92857525690b63a1f854841d6b5540ade0dd6dd64babf7710538c1b9d35064d39dae8ea2feca7dd87526786a34e4b586e03ed252388512358fa5160ad2386f340ac1f7469a0827a7c4e73f0dd16af46c9dd4fca62187d6352f5e04b0800cd9b5118e63ced7f7fb267fb6bbf1a3363098dde6dffb2aa6a0fd3bfd0cafe13d59ade125bc0ef6f54cd90e80297b75d7be50d9a52708600aff56c1d82d287586ec6aeacf30677c0019ad4a8ee1c348237e5078ddadbeb6adf2ea4e36130d10a7014c1913ea6030988e934f29eee222366fb2a23a61b24477a546dd129472f4898aeef30dd59dfa8495660622579a363d7455f958b81c538e77a51df09ecd1eda195c0bd6fb90a2c6295c50620a842560299f4cb4510bdef84dea5f1e028494cf15816428edc7f61ca9fce12bd0bd1f76465c819e1e22c3fa8b961e58e952a74d435bd570748a3b4545c477e584e3b9911ef7697bb25865899a12736667701bb360ab9af768cf36fa5a22f5a86381f8738a07a92f017e2545b469c86d41eafbd900208be94ff6da5d005f01b533a2b4d90b127e4d546763e7b1e68bb13794e32c7f4698534d4d05c1ca1124138a2b2b2db6814d61e510c242503156c2d3ed84cee2bc9182bcf00c1ac6ea9da43a0d7866da8a297c84d2fafe17ec78c41552b900ce49b8d50e5bcaaf32017b86923edfff8d3e5959ccecd034e9ec9af242467b2f515cc0e91293c6ed02dac908d6d2b438daed6138c080dc34fc19f45a1dac0323d15b0451819640a6f8455f03ec757720ddae7d3832017fba99157c81919f9d3b27c5539f8905241a63db173a94a4dff5ba3c9c3b511a08bdba3725e3c8e14a2894113d1c7f379b434e8488114fc4138e1a4226364847477c7bbd38bfce684021b07c9f6df60490eddab103cfcef1a50c7d938700aff42881437fd4ccbcb74b9993536e5e36142a24470da471af30f6aa47d46371bce933d461d4f90915e38c63853566b3c4afd59173f964f2cf61414bcdcebce7b7b08ef7c9e7e734a6303bf685504c684edda1163921bf74c838e5b44dafef13a3c7eb04131f4107aaccffb5168d46e25877fcdb517757d94f563395b2edf697016f115d0cf703475a4e11201b7315c20626649cf575f4f253ab052e3c2d96fa2e382a9a112e65d546e6edc49f35b2418c74fbc414405ab087ad682badf224a20db7f9b91a316704f2d40359075d6c2574d12290bb1c2eb401bf7ecc3ed5038a9051d3b4fe44e02d0cf43e65aa8fcd78fbeb416d038ee1d6d79e2c57d15cc802bb5b91b8178ed625bd0785f2406e8d5765be52f7c214f50b365be8913254c7cee690a339e23759e0deec5a4998502b8ae8eff8b9c5778ef1733fa7ab221bfdb9952a9aee012d5729c7cf6fc3a224eb1f254505ed31612f776928eecf4dabd684561e7791a85d536357cb3070f65b820f2e462da4219a8be9ed8f949b546f0dd54e40d6399b8fd96c37f59784f55d00375a6b128cf02a1e0e50a6591e4f3190465c2ba950063018929bf2b3f255014cc1b10cd4242a60677c1b5781001a28a81cd9e1c3d053207642f29b9c23cb8c4f05ab7d4e6d3b382bd0ba18129bd57dfa10c7cc51ec6d2822e0f493d79acf934eb49ebd7d121c37676ac55e029bffe987a4687425fe404c5051494399f22313c530a2ec589bc31c8514ccbb01b528067145a60348de36bb4107ce9bd602c524c7520fcb085b529aaa0e1161293e86877420cf025ad155629280d5f4c55d311ecca01cd76708dafdd7083d9b3f7a2c1000b601d19215d2bd6ab10bcf1ee27e869af50b04c0e1311e538f60a3cbe464b4c81e1ebdcf8657295c716692e9e07065fd1483dbafb143735c122227c85227ecd33f7d24c2c57175f40f03f8adc4d4df9bec70c1dec02d12f4869c474d9434da079625b1cb7f0528a9b065ccad7654604d895c0d37e88e8f23883f4d96e76b3e4297e62a5c2d5f493d67519b01a9bf22301f55703aa1e93b67d59f74039a3f831211fd9959e1c35348862fee714d4f08a7921e372c62276b17ce6948083410e037190671b9445064a2841b5cb13161fffd70f6e293784faa6055d0f3c751d925c74bdba2013f16443246545253db9cbc7b5a0922ad27e37536e7490a96aae8fb80d40d82249d66f8987c15b8e52944457efa442d73cb5a825ed9d3541bbe1911336a038620370d402c05d0eb2d1683bd140314b37de2e033a8145d5d0403683b900efe780315c80b110022642419718add4ef58c84eb0e868421fca8e0b5cd7f5af003fde845bea1833a152dea1eb7b9552288b27b250081a675cf8aeddcbc48df3a07d2d210b8382ec17d013f2f5aa65d99ad08c5ebd58b1f1b50fd55f7bc813e1dffaa23c7c6421bd387c84fefb65084301e46ccfc776acfcc2ec93b3b22f1033cae974c3b23be262729915012ab0d09179932909fcf678eee23072a80367e5dea3b41904e50d539309ea3d350150e0f86dee233e904b8551aeda9505768ec7f80cf33ff4e1b7fd78717c9028dbc58f5307f4148b28fc2ea48a4808e35f4470f34a7caa8ae26b180cc1c38e95b40c5646dcab28022912896f912b26a80ea5cdc78a6725e67c5699aeab4f2af56479e6348ceee1f3e1325234fb88600b29c4b12fff21921505a0536098aa72095f2b49f5a0f6f8da82b4a2401bd50e7f76b4648c34eb6eae3d299846b10f3f118be8e809289be4c9250aa9a1ee5aac69a423d49e906b9d344f822daa2fff024bd7d37558aa0d0209ef7dc8a588d42521c3d8f9d204715bccf2ec85462c1ca1044823e91d314587b729a27d8f6b6f2c0033ca2356f0c3c46eaa612ac59961822c7eca1c4a7374e94aa5fd848aec46166eb5a0380ac4a77a3e4f45ee8aee503fb2118fbce78f0cbb7cfe835282bc62fd699f140416d31523bb1ce9edd6969398077d505a7f683229a0ecb2e018ef579f27391dfc1eb4e0b40a17c68fcfa96a92e51d2ddd5c3b1d8a77d430a791b5b362d86127e229981140919a1a891c9d4961ae6398f3c5f47d32ba163582288f3e61497fbcdd200b68df096f53da0554f17827e96433474d8d1c7592cec39f93b2b0bb57e79beb61408d3264b950ccd5af9de1c772a6de041e91b8c0a06a09c90366e12576c5185baba8adc94a3fd9aa47d1294858c90d3468c908bd03676d66a46f674dd1732136a9c66083028c01f5b467666ebca1092cbbfa6b9abd8c081697aabb522b4a1d153c495e58d00b4e550c75b48b9af7611ce3c7341486a1e69d2b9d788ea9772c04ad74b459415a1bbd36e632ab243518e4ea13a3d1cda8c648e9337914a5ec0acdd9f97b4d37be64968b0697b91428b14b4fcd1fe8988fcfd0fed1fd9920fae1bb3b68254c259deaff1a5dd369dc82a8b6e0229eccdaef315f3557a00dc544fc29582615794dd6a08eabf72905176053f4a15225884e47982be47ba1532f173c9f591efddd4ace6e0804fba133b10ee90c6b5223b3e94d2172df823f03be01bc9230fa1a0cca150be8f640200fa8914e5947b4e5b08f59e98caa17e66aa563a8da9439219f32694040c68eb8ee0e54531a64771222153a181221c0ac833628dc67bb3497242666ed3347b7e68edec32f8ebc662a50ddfc15b70e2c9c00adebbc67a43080bf4f0a3d2a3636a0c0c1504a892149f48bbe08765f12d6de269106e16c94e4ad44ad3a8e9f28fda0f610066562b70d0ce3af657c8bfaa72d9d09db6f8ad9cb399d0de0758ed8184dd7dc03345cae398c6ae9bcec7309e83a1c48f330220e998afd58bd7ba25ffd9ce191ab622f03b7db01a82f665709ec4c52ab6745e3811d04916ef3ac4c78eefefa9a85cc17285679c908d002c125e8262b70f21cbffb206272d8b595d7d9f2cb158812e86ad58a772dcef5687931aacdc5fa3a7aae732f884245f4cbb0367a725c16f9e94f515aab7ec8e63a72241c5fc7b9ee3c50530e08f722f9fc0e596c7a469ca928b56c90b85e70a7f8005b97df104631defb228564c2724063f30fe6c59221265cf968170c8361567aff43df4cbe67021484131d9af3676fb8d760b7288aa364e129be4753e1907308e57060c6e753203206f5e1f852cf2869b941a6b3fdeccd714d04d20584714d3f348d12fcad49ef80a16506a4ab595ee52a26406f479875db55ca108c5dfb15fb1bdcb0b7d5315204a9917fbf699b68ac8e12628d344a8a147c372bb5427c1cba07d9caa1a884d8eb4917d183a2b88a1cb07e675b21ad9b29b68ee95fac29f68acb16aabc909e61e7fd6c8d6279c4171ddedbc7f823d82f62723acafa41213ac2c9388242f4a3a94ad84083247fbf6dad449c27453b28f31ec7f0bbcdee47158c6cadfba1a8ad561c9fbccb1abc19d85f0f3eb47c81f548988038eeb044c3f4010e4413ec63325287d44ce881db5343710b6556aba041acadde85eb46665996186fafb64c95ce890d0ccc16f4bb20ac7e6a824e0128d9054e8247878b77e2cfe960e7e826d6d66597f06e94b085a517e57d8e8b55b091996a45a88dddae17d6d7a121f150a127162ede198f1715881e4439b79967e982061da513dbd17f45278a7d3640b668e34a18e68a20a76233f8455e08fec6ae60d529daa94768bdaaa9744c66ff8bf43bd1a63755290f36678f444758003661739b2f183773e7327f0081ca35db688b2c34722857364a0997f69c4d295be8efe4f256c24ff620514882b295a2d3932fce3157ebc8c54b7b330bc7c1f7e7adf25c88697b83b7e9c7ffe8836325054026cd9880e805e7493e7145cbff218e2913f586b01ae8991a69bf81ea37b62fb17611a6860af5fbde4c7cc621bce8223bf40f0e27a42429ecf4c0ccea282b53225ae6cebde546b440b940bf5051eddf98d75894e26ddc8aebc774f816f11ed41107cba403113c9a50766c3fd839e800c97e5d6c56d1fdde30cf36f24b77f7957c63a2d737866b819bdee6e1a084bad3f6b468fae668e104923f2dc13c94cd27717094eb86d411add83b7dc8dafcee71cb1830922f706e4f5f77c926e221f72be94282b87d9ef0ac98dfce2d3328ecc5f087c863e9d92b525f47921aefcaa51c5500b41c1f8efd44e054dc26d2a1d86feac41a5d60823dd1809232f32fe4043bde8827bffd6e451632d35b44594db0781eaae92d6f3448ec1c778b087bdf807828a243aa32bcbfe0a7f92117693d29a685be3891dbbc9ad90878a33ce305c94a67e91783873656b6f60332455366baab7fe6ff3ebf519cb561481bdee8f04ac4c956beddde369bb20f70ba5f3ca7ade7e30dc49bc6c77360e30460b85ea7555b12a6faf62e0d3b3e09eb8c616e26a298d320677760c979afe9421d4c21b9a1f4f6d799f4a764ec5b77f8eeab135e63599c2ac90a22de3ab1ad0c3f45665498cf6273f3508ecf008b4b8b2e8b6fb2d5628f0020e7e4c3c5ca120ca94ed588f55c12a3629551c435d61056af6ec4cfd573678bedbe9bb28275134f95245bb21a60d0cea08187d37c906f215a819e455a65d5c631392200555be8736707998d6887be677cb2110b6d451a73b3ebe7592d166b7a24c260179ae05d4f0c5d2d517c8b683562c4141f824ed081a886260b246479970df31b57c357ad2661e4de2ec37bfa3c7d77d0da3a017341715a016d427fb5f65e2237a01d407be56be7d203f6d6ab4bc77053a09d48348d8720da1a68ab54c1dcc53e009873a79e1c4e0a96af40ccd88b697785cf6ae306da506574daf79925868d28d74c72ea1899f036d036ff6852e26660303a31747fc6df2ec25babfe5265b547a7a6b9b64348a7e3080781a4533ecd599561957705e4d613f2026965c357b71efa94fa89d3707869add0d60ebe4ea9af42bbca70a154018520e7e7978afeb936a1bd4493e470822b53a1edd7a92904b00169d102394d0348ffdd4443dd68ebf7562dde39a9f6a12dd288c1975b07b7633c2dd844819198a888053dc47d035caf52b3430c85e36e210ea4c4688f56f41ccbed5c42686b3985206295d64c3e9b8f5ec1b2386158b2241876a42526efa90fd9191c1b9d6d98a97ee2b1f6c00139a4a643fe61f4a2c8ad99acba646dde51b28e45cc369bb0c59b2f08fe94ef7a21fa5898c280622c3dc037f08f6d1aa233b86927340c3de9b381a49c390056ba0228bedf8f267880c5c9869efecac6f4fc813aed8fa7180eb0eb2afc391cd1f2b7e0f3c7d0220e7e1b3cd52536bcb98e103aab2e07cfc5de06f3d0eab5a2f36ea8dece14d80c759a365a169729d76b0c93d7476a47749a0dc2324c25b3d69e0bff83f8f9cebbe52b44d844045b6b450199e2a20c88e01466665f67f2154a08c45a65d053a0aaddc661dd4a8871111cf2c6b491bcf409d3952a11d828dab52aa32e7b1052667edfffff9d94b10b7fdd0ff4a684755465d4a0b41989720b26ac491ba42edbcbec12416284137c18f148d555cb7a84e6f89664ed373276ddc2403db67890d7ad7bae5b3bf53c4bec0696f8e0be3e40e5290f2aafbb6dac83f8c4965164bfe5bed67bcc0a2c738694fb2442dab7e2bb1c0a541ed54af9c4d7d90f5f41660c81950501d5d3baa0ee204128440147379e1ca81b2a2d602f377b0470cb56ae4ff7eb05d10604b0ef5233d99f6ebdbc95b96c372729e94d01cb6f449068cc6d577e2e33b7b3eeb789e09eee78baed604e77d2a2ee7a14c11937b43e741f65448e949dab547534a8faec3331c51f7280f87851b7a1fe07af17e0a48d0fe88fd3c1b4b7ca7377d63de306254316cc528f4e589cb547ef5979ef14de35fef2a79b84195b9475b0ef0f5153860af6e117bec732b08f812e7d75a5a656815d84f362879e6ae50890490316a9f764422449f549a9f3a7a0c3aaed2d3a6fce14809e2e493d32957520a573f367b8ecf22316ba52bc8bb1cf966fd0b8d2909fbc8df4ca7bae68e12a7f47cbd521ff154ae2d97b065de310464ffbb6d84401069556bc1b32c81af95507d3b0ac4dd2a4a7a63c5ceefe3047253cefc7d3f3f3686122f0e03f99bbd3769b1fec9074b46a48e7837d5ecc68ccf782f557c41a82faee0aea881ecfa8d4955d27d7cc375062a7ae1d101e0f40a157f11c4b525011c83324625deac9fb20f28285c927d8d9aeb466141202ddaf9094fe5b3da9a556fa416be8163ea73ac01d4c3827545253e5a4f4a9add76f019fb18661fa259dad13af06a40a2b818e560600a68f50bf84946db57d893085c9d8eccb8f340864593108e04c023af233c9d1a236b0263417f69c77b22d1d93b40926bebb2d4c9a26779912c1a9ff9bd7740621dd3188600ce48979b507fd11c2cf0a845f052c2d0bbef031edbdb45a82a22fec790afd97a52bfe4fd24b9b57c99a10831ab151f609a880516d92617d05141a263cd1faa47287d57220670103c30b17dbf603e73938cd452e5f068cf46f0092a0316c26183b44c22094f290b367d781c71ffbc971a2c32d828f1fb94c8bd0dfd50e305a7a8ef85892e94e5f96107ed1a8914e46d86780e3645ef18b17778735db3dd29ba294e47ba3229505823ae5aa8f8e38ded42e7a12f0686ae4e0d37910144ce2a7aa292a7ed1252e4bd8f8c5385b40c706fc105fafe2b1621ed51724f1571362aadc3eb2d053d78d5f0ced436ccfada7d7eadddc252609628764f20b2fc5174a3ba54b7825b3791be658ebd05439edd1606b8ef7212b6ba4b0c5bf1fe7ff2bb7f8a98642727f46747d12e31107563ffac9f33df8ba0f66d605cc8ed19cec9faae5110403306e72baaa28dfe6e3b9b20b248fbf2e7453f65ebf5a76b1d489b59fc4f9fdfbe22c98cd454f1b6acdeecb79301cc8f5181ed0b596081e570f46276785ea94a9a749fb6f9ba970f25dc3e8ed358adafcf9cd67c260945b55574f14f491dc7da2e0a5f82d20bec3dcd353dd0303da98d3764fbbd5a6de5e00ae1a5df35aee8463eaa5b750052bf7e6f85b5c18916ad42ecea0d52436dca010a6493377731fc602d0b8919ab01908e0babcbdec5173d4dbd2498c511a71e8d0bcc003a04d56bd58b98317ba1840dbb1eb42545dfe6d1db259d8c20559c9bfd6300a48be50f45c4ee069e8c6f843b4c12577a1d4ae88afbfb915cedcbf0442537b745704b17f80de198a05dd91c60ce75458c3642bb6b7af8d3139e71e3e58876594cd1bdd6f17e543708ced71e7c35195cca649b694f62be8ea29ea93c2ea583e4f9389c114547d85588a10296a8ee925f14afe0dea600c92dc751e1092e949342711ef2edae6161929f13d9c76e5ee1bbd24b710aeb1adfca5f3e6818e3616ae957c5c651f6846b09ba6a6dd67a691f1c05364c84ec689874aa087f70a89ac43ee254052d323a8ecb1fa64775ee5b2e8e3d07b265846c7f849a5034a0d5d8aaf30e6bc2000531035a469ecc772ef80ce5ef3d508fad6e62274429d2fa040beae325f216b5d8b5f384056c6d1f0248f7a7bf215a59c6b05b2cc445746b742dd6d55f4dca6846974aef90ae34f52a87c85d7210eac4aad77f01c5f8a40a8fd209697295dc320337a8a8517bd611abe0be5ea5941599889b23fb65ab4a62df51acaf53e514dc78e9b31cfedf19e61b4101a5f3d67de4a9db94498e1e5f7cae788cf7451eae82f87fdeb41801ba7f19158a3de78688cd4407810e21e16a909145c9e3e9016c6c1f8375322bb33a1b22723b27ee440aa3a326af996f8999155ea3fcb720a176778c568fe029688be49c69d2f1d897c65b52c1b418f5cca800c15804f42736577155042d8728ab30f9e80700c09e9c058787873689fd366f5233d6aff61d53ea66de39dbeaee62679c31effe11e57c329e2d021374ea44b8fcb37c4251595f431d5bf52b2c7db586ddb74f421fb68f7b87b8e023b6edcc6c753c189634a0c3b65ea83095b90e2b439cc27f423c51c0906ec0fa824c1f05d7b0ef37dd404ad92212c34c6d8cda5f724bdf947142f5ec0cf62e635f9d507e030974b1813951dc9f7285d2dcab7a7e22d676be98ea862d83db5615ea1bd3dae44c3b1a792af301775eacc01847947c529d4511bf739bc58fd5cc01cdc3834faa0a53974a5cff7ef47a706c1dc74ca12f74029ef031e00c373f127c0fd4ac9dc01102898b7b4a2e3652330e42ddb4a4f103f75ccdfc14b58a1378b1ffc6fa9d81a7bd25215c9a727bc0f46111e2f95e055a4ac5463fa3d11a342bfa71fb2ad5a5f8e3279319156bbdd84f88349ae8f06a281bff9b77303425cd71de216674d153dbc68bbc2bac1127cc779afa6c20f70dcfd10abeafcc6bdd53c82ff8d29b14bfdc848f2feeaaf37835e39201e56d824acc38c48c50fa487d3ced119ca760c5ce38ecf15048ead2c6dcb692c09f0c9708a29902a1003a74491447b71db18025a1d1f15de51546708713c1aa6c23d42eeceef85c2b50efa9c36db3b24195d2dfea33a92108171b378415371904fb12660808b941f50868120f0878bf2539767056e40e9f8f9c1fe3b3011c1cd7743a18fb109cdc29eb28390f75c195431c0cfd89180b3b1c868a7eadfe4b1637bf07c53d6148fbd1a1c5fb4f0528b5e2a230d8c6684d5b9ebe3b217ca3bc4c7fd88b7bb1eb710a0051a7d924e9eff153b3a0c909c0e1787cee9e658a768226ed20d28a40d5113d0df7a6d6940c51b11ecad73fdfae680de72c2466ca3b44cc9e61336e8d4b6075be6c9444e949ca2bca516498fa9544c5e367b538d518256d3ae5a518538aaea99cce49d0d7d6f41d115d81131c717bfeb5f7b07f41b7f86118c40d759f35bd7c6ccdcee5a6074b876c4678af98633315d11548fbc0baf65b94112c9bb701e71fb35ae150953d8c9bc81c966a1493225420f9e19cedeb242fc0559aee6061a596d69173c8c7dc1d93b6381c478ebdcaf8e2e531f14bbaf954275ae89b90bbacc4e8f2e24469fc959662ddf24dd9dedd3be0c2bd06d49828e09e740c8958de2c094ee99ae96d9984ead03bbf3c41af4f991f68274c7f29ee3e676a9b42f3a11d30626351990d8b3d64f7c65b76061def11084b87e6807ea1de430e8ba44abe08a53576a7b66202604f5d821ac6e3dbb61193d0479a4965b67bf153cf071a70c683d1449b80e686cd55b6c33bb816390db06dfe973d343ee3020af254324f8fcdb7e295c94488dd378853ae2e8baee301a128c9d63718acb47c3915c7e01c398248d6823dc08183eeaed6ae1c5457a1dcc6600af9bf2efe93f7226cc2bcf7f1835eaedb839f74ed5a435f8a2a6d6428cce11594c0ebaee74ce19550b31348812f45a401f33f5ba84a00e301c494cdd3dfdb2e7ff8118069b957715bff5b7536bb7317451d06532a2c03578c72e88d944bde09782288d4f4e4b8ba8a8fd898f35122d87d1ee1b8250bff3cb46cb7641454cf27970e4bde57f2fa1b391e1697b6ca19613e6610bf22033f9a560992cc484d9dd5cdfacc4f7e3827207b5afd598b2c69df47d18d43798a6ecd52cf7d031b18b4928d885dd44235a1e21b97b56b982ec2bf46d06c585c41f26068c3f8bcd8c8f803d54a04dd001d1633f75c8a59fb3a92bb178b18798f10678e9753514471879a6815d27f41b8cb55657e390f52b051d6d85b9929b958fa6f2fc23221be0fb374d2f18dd473c8d70242b3719494f939d5d7e3b68b5f7f46bfe47e3275ddaa71b9b9a9950938e92ec4fca70b5f24511bd0f60b3a14603adcb1f39588663113ec4cfbf029811945a4f29328baa118305b7db758a18aa14dfb9f443ba365e77ca375a04a2ac0c12dc3b501058f46a549078c4aca6229c546cb5d5298c3435ab3204de21f97e6146b989c420256128a05078fd09936dd48b751f2de4ae04b45711b60fa61a30d5b242b376f77ed2f54944558dd6f88f7afe60f2b4e0a14371d4e529eabac4f6c5a3d754a6129705c85566128be5bcfaf879a8aff34b3cb23c6932c39402f2e2b3906640cc499d81c3201243e699233a6a62b36cf8f310798ec09fdc269f1f2a67a416a3886448c6603fc2afe3d05a9bebdfa398f5c80a74cbe22db4279d042b98f45e4a7c2dfae9682488732fa5bfe100589ec44b75bf2dec7a06a2190c3db7cc08f240046645820ef39a6963fa88b384e6b8d8c2e497e2877e02fd52071d8f23a450288a9e95f34a2024280904eb5e0b21f489605df0709fda331cc19f0f09371a80fbaac40b3119eb49d31e874efff50841d8c829ca18d78272d64c343cbddb43a202d47e90e03aeeec1b64c8c9a2aa33ebb4830b6cab8f032a201b429ed5eb6746f41ca2f0e62f5f77104e957467f30ef89c4b6f4f0f5bc783ca45d0a122f9f6d82d20b9c7ea0bdb2dc71f0c33e1aec2b8c90432a93186f9db7009059d4e02d80c6d78ecb18fd5f834290531437e58378684e121411168195e9eca5c40506900455e120adcab032dfab1dc60748844881570dbd60b5f9fae56e616cf64ac761cd6d51d93b906dfc4a0ea744de5e2b3ad896b570413cd756143e802d30b4cf7cd3732ab93e7a5734dca1789bfedd7bd9c1d1731d7afc7270ca16bc612c31d777c656b9a83fa9c3da65090627d58389d9680968b53e71cf2a987c5ec2fe7a220ae5b133233969bbdf7973d2615929e4f7b4288e5c5d123fbe64c9ca77f75ed6bdd39b7d27a8095fcf72bd4d9a4b9dc4b7e0730ad8b3a22e44bc2f74ce128812b825ca95bb200206348707b96ecf7eb97c3bb3844ab3d03c7332e39ce4a8683a960d4a37dc8546c127c7f8a81d16e1cdfee992501c966adf9f99c307f65a38db895078651c46bab10c3dade17041a72d4453c9683df2da7e61e2c2787b381bbb3d5a2077eda5a6733ed0fc9134b287e0c3ed0d15d4a419ff5bb47b4bc8d371819e05e103cd58ce39283021dd7875cfa379245d6837c8a4a447c5d8b8daffd5c7f14ab92fa7a58e52aa9eb5630c2e32dff463974092674c991d1ab9bb40984686f3212731a167037784f1e7707d064caea28a49165499ed8cb9dfe8a3840e09e93d00ac2500d9a82030e7066f72a46a671e0c0a953b3793fd746580e6725bfa0549a9a6454e5323e0e01326982f7c9237ebcfd3b02a183a893b1c335bd1ad489bce9111736693286da5ef52977303940ad32f0b5257762564c25cec1034958daa2a45e6eb952d42306df2842830b455087ee3cfa355e8709a1badf53b998450c8dbd3909f581a1a1ad2096de04ee3c2721ebd54f09d2f02169a745633955cc42bdbbbeeb0ec36ee8249b5aea08c0b14202be432dbbf2001bcba23ddc61bc3dccab0691d5f92a23ced0f9129691571fb270612b7dfed18aa7ca4db8c0c36be5031c5b7da61cdb2bdfb98c424a06ad023301304607a08201801636161f05e0886063312d19410b097b2537022578f2cc17894ae20056a9ba080acaea0d57eb100010e33c5b147c3e645cae62af925ee2033f1e40f443e79eb369d253616b774a5886113437d0b55fff566ee0a5f716e76a13a27ee56d74b2e6458f3536a6b92ad5d8414a2cf0e6452f9f768dbe7d96eb52e9c936028d9df1a60e77421fa653665b9b797a8d073395b2d19f5d863569a04601506c4f6023eca95dae14a73bafd72732dc4c3ac71d888851223287dea7eb8d8f0ffed0e6a8c168a63c934ec0b4ba6803c6352d5e2cb204fa9e27bb5c301616a9034afbf663108a206410b9d87b8134d3445861b28f70288665a0381f21008038300112265c0e334fdec8dd1ae706c2bcfebc7538cc47af70a8746d2c140b3e2e09cd180f99bda8216ee3166f24154bf62e217b6f29539232b30b4e0bb20bd62908222264496501a40468090ab3c44508504db7ba4142bad5ef22834fc7d1d46a6dd8f304b41fb5d886db637fb44881fb6f4fa6a2f7640c8c00a48d8788614303d230da31db2aa4d63f6ccf6ccb589f2897e742b13927177799d81c9c7b73915822ddf2f72ac45ab36d230d8d8d8606084d57829bb51508eda1512ccf8e8e13c72db9e1f4cd915cd3ad4a24e3a899bed72a102abe7033359b16d2535393b47f565ab7bcf6c5127496ec6007cc74c90e74fa2b94255fb78e4802e5870649949041a6dd2a51b391005bb0682102ae4fc3b0f340213004826e3e121f97419d2ac2a30fbc75eb06029176287d6fa3432030647edec94491823b882af5b3e2b0ef591087deccb538c44a5b64414eb0213004439922203a1a7d9f47f3afb401dcf4e57a20bbd7b475dd0b857800999daf1782211ebaae76b5aba505aaa52182c77bd5f5a29af46fce63a0cd575614d0977e9129e2f5049400fdf54735407fa5f50aaf970dd884be7416b5fe39326f14a462033e575bddda2f575a378f8c021af3be53f0dfed446eb67823460664ca5ca4ddaaafeb456e5a29576babadb65adbc40bbcd5cec435e0ad453390cd5e5a31a5945225992aa12ff43fcfaf12daa26fbdc11f3a91ff0f94d28bcbd4cd9b035a6b6dbd371e90104090044e02da6f6dbd6eadb5174789deeab5d6d00cecedbc0be3de765bbb30ae59fdde7bad1f41dd9a94c2d8ead5ab5707e98431049c791f28457db017291caed80b12aedd86f7897f5ebd89fb8476a3f938863400e4fbf52d055e89266e683449dcb4e7db7966a530f76ceb352f43da8f5eb95941b272631292c149993d59d1b8ed3980173ca420483d2dddfa7fa9ad162908524fbb25407df54fd8f93840ce0681ce2688c2f47f9f121f614dae94c31a8fade8d4843576456745c74d1d1d128adc7f824af2256f2a58d1a9b44514065bd135afbd6e6d6853131259d159d15971b2926b18a98714c509e9b6d7ae5bd180b000690bc54f3f6c1829fee313e86d0ec1c0efded2a733327d192923b89e8e8cf874c48221d00fbe136e82bffd0bc512dc14d0dad56b29a5f4da3153fcb9bbbb3badb5fa354db76e9b525adafa358c52ea894239786badb595c2d0efbeaefb5a6b2d756aadb5a20ddb4c5cca095cdf5a6b6d27847bdd9f84e1d55a6bfd5ef7276148f1f1bceffbbaf8f940a1ef7094a799edfbbd9ee77d21f0fbbaf801c39fd0ebb40069cbd96d71b890de846e2595b74d49616c9a76dda505eeea53a12f1df3a87903acd2e440b1c8a7ca916e41a106ed8b8545e1b6c8e723e1e9c112789cb0018f105c797e206b4bebf3b71a50049c0a153895699223f4fc1174c696dfc9f9d613b9978db78fdfd292b180181a2dd0da5d8a013ebf2ff4eeeeee5abc41bfee71acf5f3ba9c0f485bbabb5f3368e4988101b769cb411aedf46d2983c2c0d017ff1656f07394fdadd952023ec771d51abd00b7bceb9400a9a9034c93f0fce41375a3c9a89c865124cce4974c9370d1d3eac9fee048b3b59923371f6f7cc4c9feb6bd559f229f5655d9b22e6b2e694c6c22bb4d76b2e441beea53258fd4af80e453ed04a55b2f40b2d3744b65a65b7ebe7ac06d2d8d0dafdc2a42723b99c94947c5468548566922fbafd408112559a1b1e59519942d0341fde896d3503adfcadd4d4da693e9eb0975fbd283ef220ed37c229729e048fbbeefbffff2f73cd4cd1005f844ed64a7d5d157cff3bc7bbdaf7ea81dc7a5a83c6d682193fe5ba94aec0526fba9bf88669b31a81e7f71d3abfcf98d4c9517bfa1c0238d02d36ead7c2dc31360ef4fd40d4533d24a52f4e0e30f4b52f4e1d70cd612f5e0f8c3368e3fc01a86ea87a888a301bd937397a7ada3efaa0b515508bd0db7d1b7448d355bc3bc908912d22d7f5189c38df05f001f8b6fc3c5d23496272ecf535e29cf910817b94c99649a848b1f19ec303cbd4a79da70caa7373d4a08eae631142a4a4e761c2aa278c8224cc06ffad6a944e54ad46a0bac93698dcd919b1b4e4ec3700d03bb4d1109fc427f7d5059433fe84320937c01f4f65d0079a0da5a921e5492ec8d1691c0f7c79d19691ad681a5f244ab65d48fff9b8b2e5368683905c834c91180e491c66f6db4798cbcc93eda64a71f46f1f88bd75439bebff88f5d6012f4a9a7b8d94c4682cc18d593ef5430a90267263b47da891a69b64720aa12752b5129d42dfb9349dcaa69db53281e8f8d3f0491fd4725788e20b342a649b65892c7159e2d90989926d9c286e241559e914661fc4918a42057f3fb68322af5821b9223d96dc41fe0cf278a07c583e2c9fe638dc7527fcec8e7ca8c150f30e8439f2a71154f3f38d1f824519d003f8a07c5d3b0d3c4b392f3d848fb44e68947dab75aafd7b992cbe311288c3ffac51fc83783858c44b2ff395629585c82c3d88972b2d9c1c9271b8f8d349b2d685bcc405f4fb46e79fd9b69129e2f32b8925be1e9d6ca0e7904571364aad15a345a4300e7683382904c93ececc4c834891164f248f3d84a6e65a4d9ae2eaadf2b4f284c98577214c6a380cf934d6e997c8e34d5ac8281cf955ce893ec1c49b2436465a79330bb4d51cbe748a3b71c26d97fbc49e1649cc93e02214b2a539554d66567d38cc989a9666262c2650f4514e0d136d634ac7e777692fd47daf863ac35ac3f6432ce8c34ad935d0452bfbbbbdb8240ddc21524ce64b7657f51093ec39cece28fec224df404ae7f3a5772d9ff24a45ba71a9bef33d564b769c9e89643018b0c9a31d25852f0c926fbff09f40092466e6148ea4924b87ea5c989696627fb7742b2ff89c663a69a89c763a61a7ff1b79d686cb29b786ef06de6269eecb85a36e964fff364cbfe5d354f3cd25ce89af2eeee94d9f5f88b773e14c6cdf0d62d7fa765a735ccdba613d2b1d001fdd27f22c2c436d2fc4d2bb7eb64cff156cf4adb7081bd4f52fa2d19af190ed0616d58fbb4de5b526bed4bb60ffa007fb6f6dfdf7e189b5f7b5ee779df75a499123e8cf137da71fc57a5412357dadddd1d4618b9c7dcdddd9407f93c71f1e122c820fb97e38c90306060850f5ff00ca19be0534510f407a0c50db27f8c4fca1282b082f0c408d99f05bff70072b711b4c6aa0ec0ff7b42835c2efb93f8fb0b2b9450b5901392c514d93f85eb941915224c19dd2d0085219e0822c910532041d64cc81d45eeeeee860175262e5410de447041ee97dc7da3a594000895161178c8fe27f1868f2fffe9a2bbbb6126cb4201b20f61084564ff9278c345277082530549bcd1a21f2dadf0b4c2a20804e4fe0f636145ee1f9d10c9fdf873dd9314f0a066084c8e880114edc5142882187d78fc961096e47ed1abc78b1189a2082a6e437cf1829e2800a1082a96f269d52263c61542b041eec7e3b75e573cc9fd218d5c478b9024fbcf707707b1b0c2954f1904d0a1e914e18a19346992fb432fdae3ed1e1d820962b045125ad8a88042e65d64b7c9dcdd413aa4132cc67cce1802108800821282400bc24e932798dce4a65155888211b983747747e14576dbeddd4dad00630648a561450dfceb57f712724b6912c3ddddf90cb3cfc8fe5f154fb23fb682481545b28ff752527677f71210b8a5619475453e57b95b704c72bf6d2a5c90bba95092bb8a15749d3f7657c9eeeede93cf5312f2b96a81e28adc59e4f6c14dee243fa8c9fd8f1b42060f7068c992d0cbd1404b7448dd13dab7848bf03ad1f1963881c566d1f5400895e7746ef78048d78d4722d0f40bcd5e4dbfd07c7eb80731380de1a6073c3011c147bc62f45189985179628923373c669a64891d98009b324db2c410029069122676962089022d0a3595d6a06f53801ac3b47d9042073568786504f79f5865e6bb5d59b03e1e40bbe52ffc7acb0468ed0c1ea35ad0cc66331f3140727f0e1dd47b922c9959329384e70fa8d936ac06efffa34fc507d89a3df37a6fbdb69eb5d6fe5dc00c9d2280b4c541ffce7b10a4650dfeb64ef94cd285fad78616f2679e7eef0c1a2dc870a229ae7b00d2166b565a2badb456a79e7beeb9e75efd6947e9af98c05eadb59a5ef7b4b4e1ab658f061a68c840fd3a2f04fafab59a98fa94950714046cbbd54fbbee80bea8bda4e6c65cae7132140a24d9fe0c98edb050e28467f33202109aec80054210020c52a004252ff068afa75030016a1008649e9fccfbdc9fb0b602c916667b868640053a26e0212d67fd5e34ca2bad5c3a9dc6193c26bed764afd9280fedc1aa9a025f3aac8ef519bae53af6b344bea7620ce96d74117baf2d3b507307d670e54fe5ca8be5e9ab788314fd8ac8e4d143761a9564ff886c9384611c513f3275603d8af5a8d761f5e353993549f075ac7e7c1dac473de82b6e51285b523f09a201043269742c3193fd332084087590ed1806f83bc0ebab529049862240432981fae23fda428bcfd48175f00d0f93213366e5f436da9638c4406532a009c3eac79761658e2f03cb44fde7b1d5833ed6af3e07eb57ac909963f52c268f95f9e5902c4baf2a49af52869f2a4d3f96f851e5e8574ad19f4ab1f4f865483f7ab0438f66b2a432554965376254cad293fe545219fe91e85f33f80b8fcf8c16eaa03183eabcaa0da8f14d2b443dfda7e1b1951b62300681a492688441ed0e821c7aacd861d176b8c3a2ed4ca5f8a4327cd3f8aa1f3f54aa5e54e2d286bfb8eac11fc39081268f1e3295c9dc61cd18999249323f663471c021079cfb73f8c25309430c397a02fde20f06a6b8efef2b416156505510d22f44fa85764f3b0f7c1ec043540fa69ef63215751253a1fea43b39bc8c83cdeeee7ecd18ea2a139732ef2faaba82334a46a6f4a79721bde9414b55b6de70b6b445434610c85065a3c9a3c74a69b2614b4445768143f0548a6f2ac32f95a32795a2bf25190382a11ff17b2c7cf7b721e694b00d8fe1777777598c4c0f3d668f472f123f94013ff4380486a268845d268e27dc35c989c5281a39457ab16680ef57fb3c64f65b1e0bdfe21572f527559554363a617bcdcf56fba5b7d15dc976f7a918a352ca98bef4b894c14ffa5459a403a994a6f2fb5229e30396a9bcf7a4d2072c4b3d2e692f99ca832ceb45a81f511fa29c65cab08450601d47542d4b0a63bfcb5769d617fba07fc88c415dd400b21540b64fcda730f64739dcfe21ff10a8bf6350a8bbc357cff232e4af7c077a642a6329f1af94a35f95a52755a5e93d9265555219f99543c6f7ca21b44f367eaaa46119ea554a191f5e32d4d3b06c7c2a96b45c31554a2abbad51593285f6fdc5de3a33f5f83a2c42597fd2c834c2d57fff97eb99a1ea549b5c6fce7a43a99c66e09981a6d5f204d72864913b03e9d1f0d8eafd692720c69f3487e54f0ad630468b315059f9a292e5c572f555ec817cd1f7a07ab134c9afa432b08622f3c11a5509b3c6ea0aea8bbf9fa5523e3f14cae77bb85b3bfc4b1aab4ad03c8db8c37bfbd87b6b76a0b674ec1b968118488a822f290b6c3b0af60ed46ff5311e7b93ba0e8fb14ca77165ffb2b31e576f4e132a777826ea532fb3f2e377a047bedefb6b80c642e0c39f7a2608a2e0769667bd8ed5c7782ac6fc7b65a5610101bd0cf92adb2df23363583e062a63317528f2bd0ee4af4c1d542fbee8739026f517d17f660e95992af18fe5e85165e9574ad29fcaf0554ad37b2569c5ef4ad29a3122915852d958f39b5e46a6f432f8452f437a99d18b0f8a65a9a4fe62f24a434306bae52b3d408dbf00e90530c51962cb740899e230e1a4d248fdd9a23faebef7d8f8f46780eb4c761bb5d2ca9f54e7a4621a5de66ec35fdcbe2a0316e004be402e18a32a72df53959eeac11a76eac74f3d588e3d64e1c7d043169a3c7ac87814c9317ef8a9cfc1630920507cc8c231062a539932a85f79d24b8d2f5304f4323e7c321eea145911fb3e6059f894f69285ef03968db6cc658cacbef8831da8d93e690adcdfe60ed2eb4a191f5e46ab014eea83ec4feb0a279522fb53906923873fb73fca027f38d807991da8f9236581410f32bf0ed4dca5c6b79177d8f71e4c95e18f25f8a428d82bf9aa9781345526f9bdeaa94c06db2d1286f0552f43f82a53872239c6075f879419be0ce0a73e65ea309ad45f641de8110395c9c8a83cea654ebff220aa14fd4a29be4a89ff548ebe5486de5492be2bc998f063a0b2f06570274918628a0c7cb0ae945466ca3132323df420bd8c0c7e19d1872f337a19f1c131144b1f953eb8a4b251a8b4e12ffeab520faf459dfc0a6bf9514c33e0cd78a019c2de789081a8c64313fbe16f19e82ec0383239f902b1d1d87414878759f63a9d309f1ecda3f511b8bbe99617abb29a3b242fb81e68bcc12ae6f066b91c9d8fbf787f55c894c2b889d32def039bd6f601539c9a9edcef1d9312d7c3e4e67353c507d88ec9ad613830d8f2ca1e097c76379fec85669ecc83670d651b6ea3fb046f39d47db31909720ebe430121b201d9cb0e49b7bc0f95dd8d4703d53c1ace12324066bc1aef8877f38878371e4ef6be6b3f92bd9b8ea7eb69d8d931c95eb7d341e9a234e07ea8fc1cc0058e576f394c3a209dad619fd9cd34ac9a35f8d378ecbe07bef79d4dc340e602ba55d2f017cffbceec6aa68c6e791ecd5f92c067a793bd6f270df36e87e6945db64a19ddf2aebdeb5cc5a606d7b5b7b45e14a7e5692c808a37de0a4ce228659ffc3dcd1d4db7bc4c29ccf73edd4fb752a8314144f689cc2ee72fde8be697a3bb75cbf39e36e90b813c5af6de2343268ecea1ef67f058c7801824d9fb1c13c0f1e1247b9f439d79b89342c91e8e8b7c521f9910c74718de87409f47a3a9f9d8746e3f1ad63f53f2e90121413ebd99ec85914f4f48f61e2cad4ff7d3e974b9ee8647fbde870df39512b0da88112511d9ec5ff650f4b622b1c1df9fb527d7ef0b8d381008e77f5b413d2d6b2b65acde6cad61455416aa2dd75bc5113fac481ae6bdca8fbe46430cf5e79743fdad98b45f544a3bb236fba3c7e5d923e3d3e9f42ed07c327510924fdf23dff2fc7c6d792acffba4d29bde963d4c650e37974c5b445561f4a4f26c277984cba3514cb774cce0e30b2c6bcdebc427babbbbbb6b4bd74e0d62416d092c32c127a9e693cd1fb9bef9a36154e606c9b4043ec7fa3a080307b9b14fc3481c9eeb9366f88744cb35177e32c8416eb3926a249a297ab0c927a966360169d95faf8c7cff63a1bf1fe331d0777efdd23663e8e0f0c5f7fe23fdc1ef210b99a49b3c8adc1ce08360192a7548faa2904952fa20f82233e73e8f0bbe3e24523247f8393e4c23d31ba8fccace2f7fb9660da459f71d0292284c0575a4720515c89c075da2822926908dc0bac00719ed15ced7773b2832c5106423702f5620bb53144106526423b0476a64d7a4bd420f19d861e7bdd743464d5286fbdedf8e94a1bbef99dd87bfcf5763f9993c28143950a20ad9081c0ab2fb3d64ad5ea19f1505ae66688b4871f2d36297d46fbdce47a4bb6bdbddbdebdbb6db6b3ba15ea62f684a63d7babb5fdbdeddb50bd076efffd1d87d37f1a8fa00a69ae82b6a0f48fbde9af6e85aa900cba630ddb7ebd9dd7d02907eae95f6c7a0b429a5fde3fc2895a289dbaebb8d8406e0e7b18e7a26d9ce1549cdf3fc7949eea89e554bc095dace5da5558aeeca645e594b6abd9b88aeab35fff301b05218db4f2da5a6ad3cba6e88fdee5edbe5faf3b6b2c0dfdda76881f9551ff7291d290c53106c7477ef6cf779dd67bdee5918bfef3277dffdf99df56e7743b97b1bdd5d13cc3ec0925623d6e9a8ebad4d02907aecf364de55f695f4a59ade2f7020dbf7f22465afc1d24e66bb2e01bc97b6ae597958eb65fa5fa6d6ebe8cb20ad5ec697dec0b9c7add50a6b595071e02cf7c936bda28ddcd4c080a6095bad67a12632c8c78cc2e450987eea63c48b1a2e689ed8aea83915b4504d674bb9671efb94d0977e2a6857d49ed8b8f0c2c7c8bde52fe7deda75a86d3a9d6638a81ffd52df832835c59a6abb4c5b7a0a0a733b07d296f3561e2e683fe529f5ed9eab4cc3d410dcea17fab87bef75f7cf5f6aadf5bf574bff7dfab7efd7dd6d7eddea2e0b97d6fa39d4dc4fe692e61a76cee1f373fdee65d01adf8e11e4fe2e77dfd27aa13542df3d0c8d8675ee1e04eda8598445b95f94fb6364b0dac8357cdf6396bf1e4fe425d4e4eef1ab53c20d8c25cfe4eebbae7391d7753b4021c2491381c406524a290501112833d995699224d432e8a1edaef7e0bdf71c41be6d4beb65bbbfc5a1df4517dedff6f0aacba0bbbb6b3735c31e4a6bb8fb2096073c59c09a7e2094e413acc9fe40647f5baa6e28cc8e0a0e8a0a00aac0bd2a6517005081c920ae6b9e968b20d94fd227bbabd6c2442efbe778ca9b2f91ecb8e4a8e0b27f0a3529dca45093bd945d3d4b40dad222c513f039d6affe2dd4a6f1361610d3b009b868f17386d90389425b8a4a9727f8bcb9dced6958bb34c1e7cddd9dcbd3b0f33ab9b9fba17962d03c4ff643e6797fb23fc8cc7de6d9c964b75edff2b3610665ffa7352c25bf9d3c7e27a6a2355952f011dc7af7de7b3b6b52bfb556a7f4fc6c08b3dfeb4e0349025cdfc7a76eaddffeb27feeb7d2b347c6a00a6a173fd59d32c0d2baf73ee4756963c6cb6d107eec0d09f173bb2e7e3aaf8b9f8681f40375f103fa6a4c90e2d4dd992025272554841426486182146a9920a583028ed25b0b346834b5968777ad9dd12f7bbb265846ab75bb7b056ef9ef3cbb1ac7cf4fedde802f571b4f3e30147e20afbbd6bb7e5e10fe7d5f4825077f99e21c01060e1633c53902ca8a15389429ce115ae01cd104ecef5b5aff99ad263e2b5ae051a63847e48c4b8eb8c92d9b6f691d41cbe7a380525b66fad65a6bad75f76a3f86bfb47e5473436bad4d65fa4f9f764f7ae8adde549b6aa339aa530a0397c2a84b68d50aa5faa8e0279f3d6b99dcfd6387b67d5b07c9cd630fd8a2b68223a060c913414c90cf9a6304119509aed674d7ee99f676d868d0841734f1822ca428413eab2d08949ccf2aa46ac1051854d4f4200a1f4e64fd433e69d890e4937eee1e4680d1e4e7074dfce00533b2feaf614eb3f990bbdfdad52a9f2dd91a3193adfdcf3e368207d9fe08245b1c15e4b26541005f1e3dd6b92865066f95d61a2ee8a030660e17e0b373310ee8567d1ef04ffbbdc3006bf64eb7eaf767e72060cd1a3eb379724ebca0a72523d7e7691ef0cfef433cddda799acfcef9941da5e6ef0518079bcf31d73c740a1e88649cc7465c901664d083be92e66f980a4d7430939df657e5db5c83fdf11bf6a0137a6eb2f3bb556d5164cf7bfbb3dcf5ed66134200be027c76ae27d69acd663bdc6cf979ac7bfa4b4115d544e4d5ea9e7a8e3eddc3c28226fbcc44e4c395e2687c5ffd7ab0a91f95820a2682d8e147162970c24920250727a029330536d97ffcbec30042ee0fa34a382940a28828b2ff8a78c3f1175188a8a152ad2022ca57c73abee0844e4e0c8cf011e4091f68b9bbbbbbd2eaa256e904442491dddf24def02f94bd3de4eeeefea30b39a2078a12352034b842e63d248962c6ddb197c41b4e72395754e4b3e500f99431a3859a129404318320b010c2c810b3dcffa231440d72bf0d203f723f03806491fb31c5658cbbc246b6f644f6178984f0bc5c3f8a19740a05502172f77ff80436c8fda38fdcffb38c830227706ab3dc2f628c0124487a583044139e68217399dc59da41c82d647fb006840d56b93f84a530e143fdb0fde18344135d72c10f1e52c8fd1d1839a87df8529c096490fdad08052608c2cc8d7dfc3c69d50f7711b23b1158c60ba7044dd4c84dde60c4f85bdd7054615b32c211041b16261c40b8390e6d092c8e472b68fa85d6e0b0c2c68a373cec316a025557fb817a7cfa85e6f3c38df30313a3aee16062ebf18819ce4c0e64a2f64f147270ae50a9e130d32451d851f9210a3d49a2204545096108e1230c2396c05da649c2a859c08c0ece4c0970663be0ccf860538043b3c9148746934ff026d79791290e09a2cc908186916dbda7f11a478cbf9c512716c2dd9b0d1afb6a4238ff300ee19ae4002e6839b39d74ab5a604ab63b64fb5bf09cad536b6acdca0998bc4fcffa854872dd6a27ad13859be77793ab05efcd635f13f4a53e0c72fd1ae4fa168a5cbf5a9d5c4361606082e0ac61d55a90e6b13a24d413f26918c521c18f5c3ff4d33031d707651a569ba9a9b7169026d7504fae5ef3c3a67603e41624c7089c0a7466ba098fcc7de2f4a0f4ac61e737e41b5289d45a8304a7259fb586899316f2596ba0d4283ce4b3fee4ba433e5b268ff55b632b14fa6a70f5c683d230afc7f3f17abc1ecfe7a3793d9fadfe7d82068d9c2ed7f1dcdbedd26e8d7669977669b7766997e6d2ae4d9311cc64215387bf5412602a0b3d0dfe92020d5319dd915119f832841e34c918908a312193a4a96fa35b27d70fcbcfa6c65fea5fdc8c4dd78cf8c7560da6e99c9a7b5301add1ed6e92f6a92d41c083fa58ff0dd840775eb83b59d5957536b3fd74270f098da64c0963260b85cc5991fe11d01d59117f2abed0ef26e96ffa12b86fb3596e23e08ccc72751a901a900a460e72d76c33f5fb47c3c84a23a4cd137f350ff0dc52cd1a40560ea42dac1fd83fb2ffe7497613a458942f8c327d77eade2b4cc1fa216386865593a4dd6afb72017f1fb96f60602f7273a1801a7bfa429ffe200a03e4938572024dbaa16134894d0f3db9df86ce8de3c32c776e5cf5010538b322f2596772bf75da6a988f593e67e4a6b909a840630ab84f6bd8eab14e5673749ff790d5b639ab0ea630679d815e47e6303f7ff13a7be6e0d99ffe356f9f0d407024a34563868c56f591273a77641746995218b7f6763ca11188c3f5a9f8678b2907983e38b2a16bfd5a4133d07e4fd90052ae4fef0ce43563c820f8eb1102a415e3da23d79c248a994aab496f34c05e6ba5b486d82dde107d8b37401c423f7a1b3da201ee17a8122a72f81dc3fd1e95348b42b5a439d4fda80c3db521f3e411326574cb41201563e8a1021b1f4d288207458090852c546273549ed686f3c824f822730609bed882f98a016e85de730841f000cfe0cb6818e8df6ab58c57b73cf4b3f50231019f5a5270b77c3c828fa8763770dc5d9e683926c127b6f6ffb3d6fe4f93183bf8ff738b593ff0bb354f7c4f30c3c13e0e397c39e07cdf87ff97ad508f8d3446a94f3dbd2d777793975cf49b29746bbd224f743dd117e9bcbf743cd5f04325f8a9542894fad08fa194059514c6ff966655e1d5724166cc987a4fc6fffdc631952ac792fa8e6b766f61a032991e50dfc38ad765dc12457846eae1353865a52253dda112095459319d4aa4aebb1b6441ad03b20f6a12b51a1fac216af5e4af3e549220483ef82b90fcd19459799527bd92ca4260871aa4356ed7d7bcc18c59917f690d2fb683f68c3d37dcb0d1dd7b9ff79ef99d2d562463562bf25b54a57e55b29e2c595e558e9f2a511f6ab4e0b33e07f8acf1c1ffcc1ca1673179a03e043e5986de7e27637a95a7e20e32664499f44353297a5209fa5289695e8991919111bd4cf8a10f81a1281a61900e09d7f5280990b6b875eff7ba118f41fce51fa5fb6bbd2d142c0ba9a252a5ee6875cc60adb5250b5ad18aecc8d6d20ae8f6adb5d65a6bedb5b7bb44baeea91863fa22f73d53a9c35fac0782355cc12a960f4b95f831f49089a2a82a95e42dc9fbe08f4a1d8ae448bde975205fe545a50ea9373de8494ff5896278715bebb776add75a5bfb5a6badb5d65aefaaf5aa68ff9634d80fadfdfeebc490050790497e2fc3f82a2f03ea4ddf953b4ad25ff53d642c25e9e6bb7ff8abef210369939c0c815eee7f83c7c61e1d335810d0646bad1d629378a103fb21907b616b1364fb9484417c96cf213e8bc9a308e857260fd224fd41668ef055268f94497e260ce3ab3ce9668ed15479d49b9ecc817ad3a34cfae1772b26799f445e999349dec74f652693fcd2cb889ecaeccfe02f96c7875496c0524965d65aaf49422a0b4b6ab5d2d131c3abe645536b361e9f3aab340f755117f536ba2c72bf43d574305019ea45af62f915cb83e52a0c571f3e4bb8faf650a60cf92adb21586b087a5ef53caf8268d710b41fc8467be4cb9c5ef5a072479704a0306159003001355e2f1206964fbd8ed5b3be8a31a31d4b568ad6ad9855a9c3f8e0b394a90f51af2a579ea50c7f857af273a09e4c3dea3d33c7caab4c1ee3afa43efc1c2933fccfcce17d190c45d108936ab6904a0faf7113462f6f651af9357a51959ddc46ec5ac7cd2da7237783ad3d3974ab07e7fb7bc335d128fdf7a7d27b92cae8741afde949a7d19b4a5a32f1f8992212f098a23576a86a0c07da33d29e1b1a36816e79ec7be7d1216348a3b72269bdbf4f228d9e8aa43563becf7b1bcd8018da43c33d37d01a5ef60781c74434b65a65a169d961f89ef439befb3cf0f7239387a87b317cf029e8bff740000ad9686cba1b700f6f510890063742d65a184deefe0c6133231088040b425f3a982db4e1179386f977ffeafed5fd6ba763e177ff82d2b017946e75df7d58be76ca97ceaba77c45e956b37298c52ccab4612f9d76dbd552eb89ec28b7f9ca75ab7be15e4cbad5bd8bbb4b8e0b4eb7bab7d161107cbab8dc5c705c7242376b2db5e1dbb76f7dce53c60d0bbdb5621982e5e72f3674cb5dee3a0b060602741187297fddab6a60c150c1d9faa5fb0ee3266db9bbdfa9aa41eede096903fd19230a02a8a1309dcbcd49ee3e54badcbad53d00aac0de0a3714a67b0050815d3fb030e3c9dd87ccd372f143ee4e174f9b2ccc284ce7fdf9c2e5ee5f385a0394bbdc3d016a0840a461e4f77467851b5a23cc420035aad3e596bbffba0e5693bbd09f305beebe6e751f8e52b62ce444d5099bec0fb6df165df0f2cda130b7ef8dd3836e5d406e9cbe2b8531c21a121e894c2f9abaeec3ce49782412c1b026fbdbd39b951e347d7d02776f9f1504764902ffedc28eb427530eb07d29f0e9c251ca36a7febafb98fdf274ff67c3ffae7bd0de7befbdd6fb7fafb3648b15f8e956057cbae5a2f8b5ecb00601dd63f180212a970347a2e845b112c1c5161f74b99bc93c4550cba61749d1e0edeeba0b4f1f841686dd0fdb4f0d2213e447adda7c9460d0415369c3eda9c08f0566f83492b60259cc4c3a00938092044fb75c07f844e542131498072903f9aa57993a90fde4b33cf95496529132a47420db1cc2fad4af3ef554a65a952e9ad26573d57a853f4520a2aa64e1c806d419f9429de59a3b8f6af0c9c2b170f4fbb0ab669edf6abdb0c0a78b964bf4495ec75ac19fa53432cbfe578ca165d5080fcb17f864e15c6e549f325d569421f5ac29f844e5c8ef7be3a2e3c2c46583e1ca4893050af8966c2c1c7ca272dde132948eb170cdcafd743bf974cdd89e50982b08da720a7301a1ade701976cde2cd75a41354a19a436b7327d9611b8f658971b17242c9c355d6eba3f5139ef7a6df6844d377cb270d5084fadd6ac48dc70804dac282c1c0bc7c2d953795e5c4502c78a921d0928ce8a844ef6ef2c131d566e87c5d3b01e7f5614960feb27864cc34e3168316200691869bb36dd182a77c672198bb068342241814f5d6732ad4e496621168d5e24aa496618fc1387a2187ae3d8240f547273d7da6b0e21adedba6b116093fdb168946486009b9e8e824d5f8ddc688d1c8fa9aacd37af54126fb0647a529515899cb07bffeebd4d1107f8048a78804f540e9573c194c51ce08bd3e96cb789da844ff6b6b5abaddde9f6e936d9a68bc625a45b40ba65eb960b55480d280aa9c925930f6c2e37a22198e6ce6d006fa0a9ac467aba65040a17d87edf86d2b0fea66076d5eecf68a7af81330d136984106998181e69d845d230f29a618982026ae1b271d5c2771c8a60e9c33f9528282457ad3bbb6c661a667a3c32cf533689ccd34524bb689ea079ba8e64377d689e2e24a81cae61f779baf52d2fa3e5ae5a4e8ed7591fd87272b27f0f29248937544a5f4b91123cbe6807f874b909594cbacd18b76a84e7062937f83800359341b19ab0b2601d21d3b02b51391c1c56246eb40612391e53bd531a63e19240f3c908cfd5c9c9aa540b242a87036c84e70a1b51f68f04a2f2fea5ef4b3237fc34acad699b92fd466b8472db7ce88b4ba1b10a10c18787ecdfa166d924034c049fec2e2e37cdc225a11ae1f15eaf4e24fa3a1a367aa4bd3f784517bc6c6f0e85c119e1f19c140a5b927ddc46145884916ce8749f3d465ab7c61f5ea98bc82c6414d22dc7e17994c935ccdb64801139797429cf5126b7b292f3e65039548fe8fd51511a16328790ef8ff219670d4399a4e8691ea27aff51a661a06f18fe1168847ff4fea39086ad98e4c81c927a9725d95d705c90b8dc606a454ea5b41fbdc20667452efb974a254a6935d285912fa6647fd0140699892ed85c6d2cdc4dcee5d63001647f179c8edde073badcb8564fc0f648c3c21b24380d0b735c4b5c4c1a46da2b7a7f53c97272ea5028d488eabe3b9946dfbd2d594eba3c65140a35a272289e868959d050d843f587e689fa3999e7cb647f6a9ee30f971b157c688d30b7abe6b2b9785c501a46c6e4c0b96a13d0e1e283bb3a59d04253e812e515f2c1aa4f7dfdd355c3a54c528615f9aba732b2d3f1189923c543659232a89ee5594c1d5229b08a3a52af7a2aca409a1dce5feaafcceee62f35559a4e399ea6d17ec8e4784ee14eb750b5a2765029a8743d1e537d4dfd12b84d404e532dd747e5beaec7ec6e95fcd5af4c1e3eb93e69d236cfae74d95c357f5102f74ee97243ea6b59b8beb7413d363a70e2c60449c25a10ae6d16ae61a7cb0d8b49c3ba88eca85c3e5139544e656584126fe06a7a957863f4574cc20d389e4c63a7a734667a1b0e96678c3ce653797eb94b022dfbdb53a4b1e1f6b86cae5ac9c4362afc3c46bf49ca5ab2fc52f6fe4d752a20e0d39be5ef3fc51b4a61ce924faedf4b8cc093e9929d9b01325d62841864522445d7eeeeeeefcfb88474cb6d3e9dcdc7ba6a3617109a974ecd272a87dae9182a4751a40b8878c355ebd64a760276d9562c4f54246e288c7f6e47ad47b0b0c21ab6f26d0fedfd8e487e9b6cc518ac1843eac18e76b5ebbaae57a2f3037948ca145df8bad5f59a5d0d8b15238678a3da7e6a355bd42bb59638d8ec3205bb606b8f3c24f5d6146bd5e6e3b1945983cc62c4006f19aca1290798345d3553bc7183b3744e971b1cd8b2d378d878ccf0a019b18f119eec7f5e1e8f55233c57c763e4b7ea9b3e810a3072bff5e9066f997e61a302184fa0c294ada9828fe9083ea917467a1a0693fdab91281d0b2263e427b7ea534f55650d42f3979499593ab94d13127cba6a2fd0d5e9aa512f682c9d9285f397ee59b85abfbbbb2b4ba75b2ffe1d0d8b49b7dc9aaeda8a06b4c727fbdb87f59f368cf97e7fb7b2edc7e64396f8061f8f599f7d6a1c532ee20dd3832feca853b20770263ddee153a664d28f440f8a5e7c4ffc6ed2e969c350b9cd73ac8fbbb735c8ac5bfe7303bcfaca50a4281fb3a5c372d231578db593fdc1ae15079b4f57ad4397198a1548ae2d2e610a5574c164037cd61d684198348c9c2103dda23540335ac3e2680d1a6657cd4593dd863f5d351aa8ce0eb4ec0fb6b8a400040f103c2e29a410527747e53c469aaaefcb05a0554d4ee0b30e61f3d2f1c98ef3979e1b50272e3757bc722f1ed0e506fdfe2cdc4d9b2718243768932f6e08d096fb55204dee4f8135a6195aa35298fe5608f282148221fac8948b9600de65aa013e229703ca90d10761409810061c40a6b5964a804f57edac3be45cb58691384a39a45198554e995b86e0b3da7c4e54a52971c411395ac3f4f5875910db4fc72a1035c80fd60e7005022724578f48050297dd3b92eb9fccb30291cbfee10f4a585c3ebd9b1f6699c664965dee4adbad5b77c80d4085b0cf53b5f974cbdf34037c5224f4f478acda7efad4d3e619668a049fecdded02d231d2ac41663195495d33396506a965ffd32524bbcf1370ea555f5fc763d5e673e63c5683ccfcc59f2241277bc34c993e45e2e6f2044cf3e9aa9582804fbac3a32afda57778b237ac455d9ea031d244f198399728502e3fc89e33dd60542efbbb6a2c9cc73a1a7fa95ff3d96016aee60af07a8937be27404800578dbf942e1a97cd5fc01961155db0e6795da2e0d0531102a278448aeeeebaeb77d1d834cce4a2319534bb6a27577601b1b984b86a1ad6b980b85cb66eb94b88abc655d301063f7c4a63aa2f3da53172f5f8474f290bdd8bc4efae1272ffbd5ce4fe55d94f96fdaab23f552ac9fddd8d01441c5e364f4b7a5c8ecaf3cf969c420ecdf32ac9e5e9cd441a1b5ed6c451a2e5faa747535a7a3bef03852c18ba281a6152c9745259418d2915b96261758c2f4d00b454971402d07a11804b06cc0054a02f9fec4f764f6f5d04a135ec74d5720d52f3580d9ccda76174b623f3a361d5d6308a43034548101b3f6b9099ec3be4b306a9c9de93c409f259831cc9feddb5ae9a0b48c3fc4776eb44d763a5ab91234452f4bdddb53f9bbf865555a6d4a509f8fc683a879a4db36b5af3abc9f58f78174aea5341c515573c7972796eceedb93e5d4eb724a473cd9b4361ea8300a802e7bac7b5ce3c71105e836b14c6ff82f72209e5d01a2108de80353d2e1b9e3c13ace9177ff3033b64c9fe01a9faa8e44c5676412c199a110000001000b314002028140c87c4a2e1703c4c44b9e61e14000c7a9c4c7a541b4bb328c77118861032c818628800008c00cccca609eb8b95e4ee4a412e999408893b981b238079464d116072c09a7dbaa03f02971bf139cdd3c0f460696d0f1b98751aec8ce8cb527fb525524fa9d0ba5ad8169c979fb17cdac8d850df0102b28118bc68f041390ef7bc4a3466286c287288e001a58add5532a3d1accd5228f60ca140903a571900b3c897cf9c4abc981dab948051905eb18307907689312dba79957202da498907be14e68254d8a851619662fd973f864d3e102da282e049c5bde603dfa938193d1a77aac82691aae2aadf46d2571f13dd8c8f143781c13f2a45b3d66c9b7809082a961038cfa08a1b061e89090b51e7dc13c052de7341f9ac61bc206cbd31b95978921264d13db650e8d4e4c325ca6fadc8f80a52c4bd4058c9da13403bee390b3b6e6445ba092e746a3a57e3e6bb684b98d73258631051bce797b00ff30b8c16ae4b02c5632d3711afcacb3ae4f02c304fce4ef9a212f841a93eb3812836a569838ffdec983035aab780241ef1555e2cedcce35dac1d45cc589590ef746b9c394a3a76558edcf417ea24e9d78dc62f41ebc1b82833ede6618013a23475512c7aa588b00b418cb6a8dbef524c165fb743557233f9fb53912aeef6be1c15f5eee29158f1e93a6c21be910a5126966d6649d3b0042e18870ce0eb4db29d580bb4a99eaa7474fa2a7df447a3422b7f8efdd8a92b38a214c7e86ccccd2878a8b1ab844111a580235912c885f01602286b60dd7644402aab24c0d7913455de4bf2f0b3d9c882436b81290465d3a7696a6f35aa8038084c2f167e9caffe31280a1bbe16ea56bb5402878aa23c45069333573321384c17408fbaf6db2c5708b1f7ce2d8089c1025d31e3e3f1eb1fe528b5dc52db64dfc47432644406cc23f095966dcd618e301307acfc161b2929ec47efcdea7153b83205b292b8fcef30f3515c109ec3daebe1da4222c0f04721fd53209c5d84878a842f69024f1462c933c8143b1f3a8cc380e2bd1b612d98543f261d90be431f3384d178142ce7ea9e620301919113eaf019df56b0a71ccbd498fc1d506ce3c962fdfb0914bb7b5ab3ad459639d277821cf25ff6f28060753ddbc991d57e1588009300e8b0e769372791e8a91ba514e0c320590dca0161173f3c67e36cb0a163ac4168bd0147c378a5735b415232a52cf7864d09ed7889888f3f4d0c04d2720a30a94011f1984fda8a8f2ea2aad5d1392d6fe6f704b4ae715aef51fc33c077a107a3c274f6187c914f260ca2140f8b77c2d627e971568c3f5aff43361914126a54b87b3c718e076fe0c292bbe6b644de964dde41f5e883effa399b3aa4c909a953f4ea47f711a833641d9b6ce141062608d72860bf408cd8f0fde6dfec3c4899d6624629ab5f7001fb47dd42e6cb4b9d7cfb557d521950f129a003ddfc4e9e2d92296a10b6ff2c5930f2ba877d30096ced26ee52c725dfeb1b1e172e8ddb7aa31460aced6f082275963d30872c506e1324415e99060787232beebafdf3983a9594ace9f5fe0b5761bb7e41204a8b0121a039c013c5ccecbeea7d6fa9739ce44ba06bb4adfe0d5a1ca9806c5debaf5583abf891bbd1453c9b2fa6e22ba67ba50f544c59a0aa23dac8e2db720c2e47b9e9fe169edeae504dd5a50a2d5912cae88accf725ad8e9333bb0b55ebe318828bea518a782cb56322209be1507e22238615a7640c5b675bc07d7ce07e20d4a9288a9e5558ad0bd4063c5fed2aed9a7895011f757001491c2ac86af82722fb2cef5e7c734f5eddabe68258363d8382a57da47e272e58d681147a7380e0fb6fbb1a010b5e9a393a2b40322b782bccd12c8855b84f81008ba66f82cd582e3668ec83993970ba0cbf53a5e6a15e8e687d2ad8b2d343ec013bdcf66a4480e74212133eea01187c8fa89568a2da92b485923369ee315b61d65aaa2230cf825f492a1e612514241e2c800ae9fe336255d5721645ca29555d816f62517210d8081741535e4749e8ca4be16916745401e89a494d65c61e329ef65eae144ae7c4e919af3ac8953d4ff7e4413753c59344992072c28e21e429b61397d34c61037eab7f121b82401de601cb06d7ca8c5a9f0ebcba912704a449b044e501a94b5dbe09360c0198f8313383a7d668c0f6fa3d62a04c13378018e23b5401876ee20c50f34e4d21a4ff86032744004fa525007a72cdd2ca0454978e72cb474e9993939ac7b42c7be713c324b3f2cd38cae0a93cadd914511cbcaee05c053da759be9cd1180203ded0795e30e9f9474da746aa4b2bede7ad15997ab693354758b8efe7e307889ba918580508d8599aea9555411f59b1dfb28841f54e496240079b98284be30911790ffd983e3e27ac1b012d414f27fc1eb8a07bb8d62f151bfd63020353b292ac8427709fdad2fd5cd8e2f11e8437b5bb2b03e8baf23ae0190464b33fb7784a8b99a0d91f8b0e1d6ff1ee0a19bb64277340f04a231aa2de73c8ef0be19eef8b3ff42267cf7f9ed63c751af060607c2d922ccea3452413d3f03e2d9655ed979ccf479debcfa98bbda2758759dbfcd41a1c0b9891cd3ac04648d22ce4b9b8e00421919427890461bbedcaa63f5c296da82be103a8a37d546496e79b800ec145d35ab5c29457a9298aede308336e7d8f34bc86267d2cd9dc31ea72e717c99e3246d73414183eebd33d24ed2ba0cf0d367edc4ddd9c14d4c9c6e8cfbf160b5d3dd35e4a59798ade9137ff0e6f783393b84955e404d903f06d68b666446f58fe96c23eb56550f51d61537797ee7f93a98d0ef464bf33e33b59b6961f7e687d1f36a5d14d06bab6df09cd57805ed44b2202075ae78f2a0c80bc074d109ed25e9b444ccc1456c24f452ca6fd24e25f08532dc3018bd682a1672374ea0c62d87327231edbdb3d572f3febd09688dfe4f2b6dc1fed3e8a472ada7f78866863e2cc05170ed9bd0d92a45351c8079c24b000de5ff1879ac7bea18ab43ae71bf4b68316bad7a9c8c8b1a8008d6810419acdf69a7c402a37819a91b176f3c14d887c69acdb0896bc991f2c95e9b6f525056d17ca3bcb6be88bf29eef733e30dea49ced935c9e9d0ce37ab8370a0e751b508b6ec185566a76cb92d1b4e1f072bec414b67dec8fc244dee41b5fe5d9491909f0e73eb7f7fca282e24d4772c60fa6e26ef48c8e35b439ba49d14d3aaea18d3a79dd6cb7ee1e382332b631ffe4b2049f22adc7166d1b5d8e522334af3cde48e73a35158e0a835363f8cfcd74060bcefbcf1df8669a313a1d0e051407933c3554ea02de995a155bbba0caa8b50e2981ad284a0e1108bcae8ee4db985ef7819489217134a79bab62cbd9ccef5accaafeafb18657634e7b2004a9ee8b843d25d04902689500b8b004e77cabde861304f5d2ea0accaf8c0681ecd60ec411cc753a676527cef8d447ff26c3b8771e2170aee50e42111bcbe3dfe8e041e9123fe368f0fc5a806cbbafc9d63f8cbdac19e169bb0adf19ce584ea5290a86d02ff23acb15666a5506d8c045c3201b281a0b74ab5d53727f96103160e6be3058c04a5c16b3e4eb97ff0ea3d19b557925e1fb073606fd561c665db40ce539b9572d7808baafbfbcbad3877fef466b1f13d2835534348e0d28f9eb2db86d8b291a5daf9091966bcd0aef2150b1254673c28ddd70f87388d637a2998d05bc3ee889fe4c6cd2cf8775f801c7be8b24b031c9726266c4b88987de6ed68944d72c69c44c392a4bfc73d91590f1fe536a8216dc467bd2a1fa7daf5d099fde83f2fa4eb1421ea9d26e11bc7a483429be76c9d9bfcbc14cd9b53578def31bdfdf4adf6d92926127362dc71c3082125942a6686dfffd90f6f213600f58d6605fd0fce5275fe875cd7395a76ae7674b260bfdd81923302a98931f699986c9524110768a7d654e0144bdebfd30de2c24986fe1dc9fab26760b9c72f5593083e2d31f99cfc9da052e160468f65bafd1d83107011083a786112cb06a1563d0413e27eb80fd42c0a6851f458e9b0a0a7a9641755aa1c01e795e632f1b74526bde32c40bf903b5287d09855fc80326390e41fb8764f203364dce6cd9faf3fbecfd2fa8943fd73dedc7320b2eadc7be325077f947705f5a3af4c0e587bbebf8d1b88a2825588880d818953f1eca2c0ddc7f3f6111679ec4e76e10026a982ed01de8a1c322a09ea035ffefedecfc5a93dd263b427ee71b2e1507256b6542247e0c812e014f51e42729b2c7c0a5b89a1333fb49b0535208cbafd0b903cd91ad28528f7471736b890f9bcdb8336c4d1c4d2606156967e11302fba146d2b50f149614f7f71693ed596f22001c1c27f64da23a9c363908406c0c259c0f0943e3edd4884572f7b425c9ce2c55e267968dcf97a61ccf104544f47368af0cbc47316cae9386f69aa2e8adc8d0fc6088b25c97b6e3a4d86827ca26210f3a9a16d764cc4f3f2b7f0eb573be75f8a3f1b0a7c25714137f0ef6c6c0e02399d7c39ec04309fe8569218a17230558412db6652edb463d6017126979b3bcc56cdc55122bb007a106a0a538f5157f4d0b608ede530c081236d3a481c24d2a2bbaf8d85ccaaef5d5409b5da03bcecf2bd467f19fa3129fb4da05b0d4119d5e106f7f5a42d840c4c6fa77329db225a0e3fd75da5c14067bbfb5c13622c754be8edacd926392a1e9c7a88ec298f10216ef7db7e7a4e0facbb0a2eaeb41f752f46d855f59fa1a4908d3f50ae6108351a9b52bb16f185be26ba527384356a106a0f84563e018cfd4c7a8f1c1d81455d10a7be14763bb89f75759e15d87e88a6173df8d1d664db47a38ed1a0465883200673af76dae104b874fe2aeb99b8b336004827028c547fab01f891265cbab6ac2be358dfc62972ba617f6fe52ab512851516004bf795db4cae6c1aebf168c7c46a672ccc6b04393ee657d472c74c4c906f9da6d64649701bb4154bcf037c97f212a9b489c6e03ffe46ae074cabf00270157833d75ccdd3d008c686ebb90aeb6716697c7b01b725a8ff502a10aae5d52e0c74e9f6c393594d6f1fa4d68ee8a9deac4f3d27a70618f75e1138424962fe49b10059aec314b1b8af3118a1c18a9386c065911dc9f76c479af41533ad06e386fb0b667fe46feff0dff28ae4f8daf037a516a05426592fb9b2ff793186f0016ff53f29274818a397290da11c249997ad460a4a296a045bf6cdeb3530589c9fba0bdb701650ddb5fbf51caf7dfd2a5c514cfd20686d43861158d5fbb12b26ee2a94d6bf32704cbb65f78526c7a943b02359cc30dc4efc100355386b840d52a8015c5dfe0dcf5239334b54161be36b80c474ee49954bfafb0f4896f6dc4100176c20667f5a8fc9b0a192704c8ecf25e5c66672b39db788ed1476964892930a97c0584e27e2a7004835cc5f0c818f0c977702a89277900118177a0b0830f24339ef5ccb847dfd1e143de00cf24e81f8c1c83800f90bb6e8036ff830d62c302da3bd64db983e4a1984997a2c672ef2edf38c315ec8a483d44e8c0fbe4b165597cfa8f5da40d88f68c23cca35a0775677dc14556c1cd06729036f612d427efd240a3bc4f742eac100c3ff87619666a28e4a85cf428274baf0191c74a4376a410520e3df54b79123f5c27cb2a0a1ea230d27c98e0a60712c06134bd0e15a2ffff13e88da38c197f1c7384ea8a7d01f68cf86e04ccdcc873a704b8a067c158d96085aec17fda4bbae68bf2d3d4ba405d4375061af3849a7db1384811c112ba54554a3c39ec2389068fb2b1984974282dda428c6b4906a654ccb10c94e4a6f00aaaa9e5f0e7e58ae1432eb216862e6d97fe3ea9be22933e2c2baa6958c6ba1c5ba699e12427caeccaace29c687a9aec0bc2fb805b81dc2e05a1913b7f1cc192d27d581f5e211c9fd1b53e5eada4a1ed79e327fd3ae8a468acb62d4257f2f8e6cf55b9bfa5517592f3762d0135d95158019691a9d699a2418a52d2e77a0cf5f67eafd2ae62624c969ce75a51bb0a586e8826602955ea51b4732e22a4f6f5dfede8f20f096bb6b81f9cf9b76334c67440aaca622879b91324324ff59bcd6391c9c4ff9b9679ff8906f9aeb19f75e03b543675884d648f3fedd8c6a82b2f6d8d82ac461fc85e605ba39d7b505b4401781c3c4f932b26685a7e7cdc3e481c1d188baaa2b953ce6ad16ca46e45387fd1b19a311c9ed9a0472d0f90dd8dcf766b1e863f31a05170c6bc71bda304514947a420b2fe0c848c0d8916341d47320765f063b19dfebdb8351061feb7d5642eebfaf686b8e0b0a75c7ddf7607000cb206ef7c99b630ffd8ef4d5f87b194913b057e00dd3532a32bf036adbe45830bdba50862d56d094f2c9346136b34c3e5a5a1ab998770e5963c8bf636f67556dd1608b2554a42596e2bb48e38d22e00af06f335fbe2d3ab2a084b0e582f0b3762d261c9c17bdcc396ba92895c33e68acbbb2504c2b1c961812a76a50f072bb059c90be72e58e1bf529940a0509bcbc783f7a0c013db53fa9b519ddb3d6e45abf3c5bbd6a3de61f6394230466c331af098d7b97d693cb0e6017e758ba5ec3a5c86d00771ad1bbb23a36df73ceecbd613d21269112a8d3f27e3c861ea5afe728b75e8924d869a8fb96b8619ab1dfe849f53a7ecb057d08dc74b945204e5c8d584b1b1d547cabda3a2e276b4ccc36a64cd3c38458918ed9a4902dccbf950b66c6d48bbecc7c84aa6d9057aa7a69c903b27ac31589a254cee1230da05882d26283bb4e9bdd5099fe160102b51b77ce1c0340fefb9e8da8bc081e34da819eac92b3e2fd9a3fb9bbf301683f7bb6b9583a2d0ae9168d028ceacbf6068cb89483f00e89df12c5d331969724d0d7544b7837726eb125b565595eb83eb8477196e4f5443db0e2e650266cda62a7a00ae770475b97ad0fc450f81917edd06ac17478ed45d207052a5e42a21d9a11a81cae0b09a81a26d874aab06cb78bd3f6d7a6ae942d0a5ac221ef36d8d0186e8001de00cb0743906d93c678ac52d1c844671181682faa1d1aad32295df47646f7e028c837a521850fede9dfbd76f3e3840f55a3d148f71da91c86712490c26d2d76d1350c7063332330c441ad44337acdba01978121b2412d05c17c15664218f52df6086fee56575bc7c551eabb592c8f6930707afd18f8609c4cd4f93b9b6fb6f6ca02814121e112104a4a77faf4931c814e3b8437fefd2c44c9b3562f92ed0bbd4f7226ed3921f4616e5c1513abd17c215e05d7512fe10b6fbdb2f29aa584fdee59dc1160b4f6ce7355489b8b0cabec09934d6e8b6bda033bb0e042535bb55d6a629344c4931bd89b549455215d00b391a4e0af613a376b83ab791235e282909b52b2656ada9922d472e415964aeff00caf9a37509d5a33d808c21ad90c477a85a87a98bd58537b974bce167398608e8eb63cc823a051a877705e67fafe67aa4d4050a38c7ef6df9082032a1c393610fc2632d470393a829e23bb958cce08a4f9de2c79d2a7af328856a904055c912c7d15650cffa845951dd9db635db931052058fbb6d30a2e8a76b6cb63636d4772e694f4fb6c03176b8a43784dda76119818df88720123fec6025ba4358502081a8a517dccd101299d36382da728433239b85aeb6064792a8be5e622917f14f88b372862057e2a3e4c833a1149362715455d80bb87e6434a89826f932bf1ad02657010916a88bd5b155c6228ae607dae636d3719e85948accdbab01c62c506a8805d84c7b7fc01416b28b2e8440b8b4f91c66498a4e4fd1e1781d6316ade872a8f1f042e1e7f799f19580ecb5b0f5b88e2504a18d0d3d3865895fd03a8b8a15e80d7ae0233d74ee91f445a39f0f29cba3c6de3251d6636cfbbcf0861e7adacbd1c3470a849f177611ae03454f51e6393a06bb1c6113e00684f0d6d796cd9a18dcf979fa312cd763b65c4280187228d86de5a579125a25b3ee621988df032c75dec949557ee2d8ec912092deb71ba4f82053f0c4630aa8a04e6e644ac06a11c40589a55317ea9a1f26591cea4e60668c040a08dba470db0cd04e713668be4098dfdb2906275163ef9a5a80e90a12b4e11111dbdfe206be82e1bc940287edfd957ec91f252a4d06e198321e6289be4c77e4dce700d8217a60ed1f26e83421e128ddb7c82c729518a82b5b60b8c9213155aab9c0412007823515da5a63fc61ee08c6c8af7b38873e5e72e813399ede0bf805bed4ea8b278cab050d947cfe5f9175f0e8addcdad227d43ec669759d6f57e321fdfdaf5b0ca620968622614189052b3154f7da4bd39e301e107955f122cfb3070868dd770971261a7d9b6902bc9c879ccc6fa14794dd7696aed9a24e936e08a58c9e76bdf1e35ea62ae31afbda103f98a8f00142ab5fcbecdce6108b5ca2c2d5699f9f2ce01974320f935fcb149f342f714f5423b60640b76ab1a5c50c1dfe3966a48202d99c5b7cabd7750931a2e9f516766775881f8c95b42374901793116742a80996dd32d4e737636f83dc7c138386155161a4c2ffba5ea0895ff33ca6e8a81b0fb605857d4192919ee725272f76f4b6f2f92c2d22f09b03ab2c88209c1e84d38ceaf4d96fac75bcdd38c8c2cdd535c4ec452a47d29612d7ecb71bb52af0e1efbc3ad04dd7f736c5a86ea0cc46880332fc7c3cf8390635587bf7a9641ca705d9d083993995a060c97483f0d632aaff36918c8a161d965a532f08f4aa4bab48daea3c416e8bf9d9012ce91c87565d6f138668033041a5e8c1bac6851576f0a1ab02c99843476fbab45ffa87d020913c1a59f373b183c31c6ff859ad2a91660f2190ea352493f88947a407f25bfb1b23e586ef179b457ded3c1cfee16512db3ec7ef4c528ecac346070cb665882af5bb0758dcca311fbbffc3bc5bba8a0584ce26edfbccdc83231633ae41d5fba78bb5a8b0a283cdfa0032f08022e90a75518f86f7a6142e4dd7c564d83087193c986a2d3e710a8fdc7fa3abc82d40afddae7788f53473567c0f338d8fb86763c67572d3547a77563e41768807a55ff1f034ca7938636681bae6968e1dd4aeba61ce83df7b00af76e35b9022a5d9b7a63b4057d176aca58d640ed72699ca033d9010be565a8a5bf5613f02a6ce21d7f173c935093945c05e41f709a2521df15641c8e523e5cc2044dd505c2140d1c3f8557870c9d492ad7cb8f622674459bf4203da484b76bcd9268cb52c093fcc864b53179c00126d5a34c26d9b7450118b09a7081f72b9e000a040e919d3f9917bba73839f2bd909a169387523fa2823b04f62119cda9b35e31714b9f6e591918b7391123bee056f95a9bded2086cbe1345bfe0a26c3abd0bfa8a6838c8212dcfd02ea89ae920eb56e9973610ccf9c2ba0007149d791d6d67663af62eff317f480c0bd2273ac804590393c80e5ca0c23c24292931c7c1350b10e07449ca3337fa242d780106efdfb52ff6bec9b624dedc958214193ca586c78fcf7a051dd36b5c2343c16b0de1b1637014d3077535af3ef6f936a2caae19e023e4d6a7f256002502c0f32a8822eac212b60dd5684555d1158094b60774298dc4829d44d1c4280aeb072f27d88dfda917d4b356c1329998dc059aed97502a6b7bea70df7d97ab06001876e49b3685034c825e5f21a8448392d100aff6b30d5fa34e8737acbd5f7d4730ec5f02624fc27e8268da5f18ee8c2e6b4ad881d96485b15c98f3b2c4996aa92418759756c63df6cacda86767264cbf4c1160e07d9cda467af814a66a07d17e391ca079b4e5b8c539b4f12cdd9012ced93f135458430462d8c32259ba5744ba53a425455d5715ac33988f81c68c120920b5f92f0b3d7978e7c5dcef6bf6cbfc8189e14b03d07a81e5f5027fc3027e2065aa6fe66b96caf321c74230a033a9c6639a50e39e4c9cbb7d765a012eaf088ad20511994dd74902d3a1d07b80a720481ceeb42cfad92d80cd58071175ace46a6d790d9feaa5b0d9cd13ba90f6d742cac3e8509b41dfb541138b78d69d10411fe321660b48efa904120212de2dbc7d7428bee9ed17fc468562e16e706b4264881df1b8b32f340494dd4c76e74579776f0b379ac9ae2f7c4900b2398a72a1006849ede1cf1aef9461945630e0670b179d1de2b494cbe2de00736697220470cc3e115693c71660533c6eeef13c725dafbb8240ec76f45cc05f71e02dcb405c0182d3665c8abf4aaa52968c0d85c90f06dec56f3f7db52ea16fbff7d85a156e67cb64e2a364f2f2192c7f9e8d4fa60fbf21f1511522d4dc6aa69adfd7d59ea200637bbb8107b4072ebc0f6157aa6d921174cacfcf4af295c1b038323c5e71298a410bd9175170da1db1453689e1e0cd72861de7f3fb3e5c8751ae7658f7d230e27891cefe2c0eea58e936f2f38e2dcec6ccc4aad521fde2ca808d19b299b7c314ac60b1b4bb3e0cc14383492a9a7c211cf27becb5f46df2e08ccb37c5c2de534f42b5b71a02d86a9c8b6a9b27f15a7f4126dc7cd67d28a128eb027209cdfffdf15ae578620a1bea8fcf4201caa71d214c7bf0c8b6d7a6e16a565120044e83d1c9dd55b7a346963e7993c00f40fcbe8515085e66a7f3f88c5e30c002689f3b49bc027a0a0589e751c8f0682e24e7fe99bd3e228f7d5e14c57aee62d91671343ad6b84a2c6a969f7b8ba2a2118b944fa69d9387230fa81d660c052fbd84f8558c37b1ebbb64899807eaf470f19f769512559563eb122b42eb4c5858ed5a3187f34d091668729d4355762f2090a853a95e0dd162bcb48ddf214177a788a0a68163615284a66271e2b0b3e425f2614205019146eb1b0bf28dcfa6799c54d66f97cc99adffcbc30723e3cb44a077d951cbce091845fa8de2b7ecbd55dc39409adbcaee90437d832eed88c352cbed65d4a58be56e894f24d7037b43649c257ccfc5a8b431a23df16402a54c07516bde9f20915f4424577304c1012056f38c2061044377e38a6bf022898c6699754de8cd3847e7e146fa2aaa30f0827f4caeaddde44da15a3111377d2d009f69b10c50b978410395f58e0e975dc2cddcb08b7f9f88b7bb617de834345665f031c209cc500660156c33be2b2177ef5c29ff03349f6cee30d0b345718c342efa6c7586c846516b304a42131f78e2729fbd058db328131e90fa895e4db2182734593712c10d9ed22c2b8420861354680ccd8bca40efd7e3db666e0aa7286313b951de4d6184fe9349f2c9ed87639e4fc140151261f90ba2d3a865e130993cda26b50517af67c98e2efb85936d10055560b49b25c195cbdc28c65bf79296c2ac48e037df275c0db7d8c2f05f0fffe177485a89c283eaac744c6d0fbb51df31caa2d22a78d280911edb8b0d56502b4b488ac9c44906c6e0cfd4c8597f53230f022c6c4393b7b220f5fa82fa781f8668453ff34962692f02680de04625c51afdd43b063acddc0506171ad916242f2849c6766df61018f1a7a7355fe64b3b7dbcdc8623a283b8e997c361a469e6d68f8862a63e8f9152e5e91a36e7161c74053d7db29fc907fb1cb528c53c3cd19a4d8fcc4691ca850b1e2ec0124e4dbdb18282e386d2c47b4f79b7ae355ff47e430f084de6a30bd9e760efcd3125be0d022d2b380939c07216e784532ebba412ff698c0c4afa344e1593cd36d1886ae1b52a31787e6a7d1ee44616091c80bca6341064896406f8e4ee23ba2040bc5d08e958a7ff5027fce46a3c5305a4edf0bac1fe63d03272f30ab3bf01e53cc05a9afa0af560f35243dd92396f00658eb4302ed3561d7a392a5056ab1899e89918f0ed64545a163da53b73597558e36d0eb2d573889ea5b64353a71b474a5700e477ea49e8eedeaab9b6f2ecd01b1cbcd6f6dc205b98f102d4f098eb30a6d4c4b12ee855877bef7185f93444fdaa9e9a34bd779c0750e1b91240ec76c23cb3df268e40ed01ec8d76ab14de464059ec6bdc3784e5729c654c509dc61d2738159d1bde34ab5f88db84ca17ca382f6cd1d9c798a6a07cdd7b577114de9d3eb812bf128030b7ec6163a9a10f5449ab1c28bddcace241f7a3631d4e2044156ae9869d84606f7f481c7272352328e5ed4884b36ecb609e40d58eeb7dce498d87b824184fc7b0380cfc04d52772e750ad3aae10cd7d883a3aa9f88d3881f46070d80e23e864441c169322149028afe983778b969e2209955efe8e95bb903d2953cb0b7a942fe20be818c074aff0f00980426288b0966844cf59b4a6713c7908176007d7595e15e53dfb09369ed7f2a54bf8ccc48b663eb150e8cdd3bcd66db6e74ca4bcf4040c68d479487acdd96a1522350de15ad67b9f8829a05e506a4af8513fe0f167d1661956644473879463b22055fa8e3b97274c0a401500a230521917f1f825b7040f76f8b0466bc6d5cb531854d1501b40e78c7d14985e1ef50fd873f30f35bc60380dce82f438768f3c09599341ee19f7442088ca346a6dd399b8c16fe428aaa80d57f3919401381709e8f456fa689251442c97cef04229f9d807b44dd14c77b4db1359c4c86aa02503c285e1dc55cf69650d80c9b5e417b47c2ce0fc6481f39997584afe4cd4876afe019c639022a9c72b0ec30ae7e85ac6be609c96efd8aece083b9093d10be3b6a9ee57276e8a88ec377ad0834a908bff53d0aa380d49b3c83260d4359005e171550f4c8590629e175ad673dca9f378f4804d5132c4f994d203ba9ad3545804bfbcd099ec17bad99a02ec4f0c85a52343e354e4787f013c399a02aa36bb601ecf83a1f4a03fb4dd8b4639eddce6067ad44de9f8d191d1bdee34fd0b9a6e71c861727911792b345daac1ef5895635ff3b3fdf46515604f955609512de1b1c21d9b2249040b0a88e28ee93ac1e28441faef2ecfb39a1d813cba6b8d627a00fbbaf2c4242581087cbf9e1e09c0f82ed3db59d86f53ff7ed76c0d4745f5316d0bdd585f60279420c76ecebbb40f68900b19cc3706a9b33f6afb069169cadbfea04022717b4eaaa16a851aae67f53cd71aef9f096dfb8848e0df5ae2c855e92b3e05e80653854061a6b1a013cffc70431f79290059e354afb7e9d9e94e2283a162046fb90f7268c6434e264289dc13991e7b0fb78b4e71e892ada7eeb176c60b997822da24102894af897b9f41f928f8d28f7560fd4f91f61d52eca513f2e315633a251c102982044410fa0edd1605bdd1da6514dbe8cdbfa71b305f7de192aec714fc835182f0e0c31e1c92073f42f7fa1a653403e4fa6156418ca6b98e614c5c2653ebd0f8051ee3a0509e9259f2c07becb195cd3c09484d707cf5fccffd0d68642fc03b109f416d52300348c1c558b4627f42d02f38573b4599cf3ce514c27c055bf141cd9941398229f45f2d98b491dc3d95ef8c46221081cef529234aa67de50b176ecb9c4a84d22bcfeb04d12df0b00e7ff8bade5c7f2943b91318e01ccec44e81013ce34036507539fecec6f6fc12a807fad98785644fda15b004d1469f974f58957cc5c833d29309692c20b11017a89e0b146f7f217d56c18d097639ce6effc6a4759a581d04da9df1e93bcdb67157f3782951640e951fc66b05a5a88bb257cfef5a81233d8798ab3f55e213081700bcbfea442328fc5acd7dde8f67f750562790d97a2b7cb4eaddf6cc2c29b0d936b256c430858753d38725b57fab3b905093f65e881fdeede56ca4b3ba383708eb101fb8bd48372a3187de1f5c4a62f1f2574e7454eefe3cb315f2b5793701e0791fee43a1dc2e0692a68b47c02a1b3ac624c194aac991264d747dd14a9b0b86f545e065c2df94a44f7a4b358e500f827c865ec5e15d5b47e9cfb661d694f245328dd9f5c25082f14348e650ba403b530444427236a46241b732ca1a88b0e4984a009fb7712faea28ad1369264a26bd29abb6a157d43b78741b671aae441ee9c342cda33c1e8026ebca0732f803adb44ddb43a7ce97a8242c140034eefc045b2e6c0d39539173b5d6b3129715f7207013cb9a55d53564d2c4c5ff05ba4aaaf25bfd0ca1f71ee7b91ee61ab2b3586f15965bfd1acb104e4c6b48c02070da791c1ed0bd3e071472908658adaf34c60386b2449ea4f29ad1d6ad5b88e1d9ee9a90070c62c21e230f681925e7111bb02cd20e73897adee2883f29b86cf7218dcb5024150dbb2e7b1f70fb6c820e344e68c0300f181e2f2c9971b38a7a40cbe72fb7682e908a06840409a48d03d772c5f0746558bb580f687dae760def4081786798a18f5228d60316a1beddaf80fa5f8b6f3d6091eadbdeb203c61b6eac14a75c0522feedb4455d4ee9bb117890b741b0a57947333a0444f742b442cbdd03c74518382f5e622d72dcfab9d4b1883a057aa96317fa2a65d54fae47e6402f82166b9c7af97e037b7c3da31da114337642170d503415cf2f4d13d53a31c480a1acb7fe6234a871530c84e9608262c0695f27f5aa1b2094dcbee8b9586bae484f8ca7e3c05d28462d3d7811d1f59afaa272f46e6e32106c776bf37dfdc4039b2f6bed8e3efd8d6a4202710221621182951f6ed8bc6d1ceba0631b6563b9a25dff2b11d0f2b9560b1ff795ea3b91540cbb68ed08d446075a4c875f5ab7118a54d111ee06c1d085e0b1f93562131c587c2157cce51ad698f02f96f0fa5b442a033091f21af2db9b62be6e20f978379171b0859cd00d0bc9f5b4a32636615b0d80ec697a0297214dc7440aa899f487a88d896399c249d46434f8054241b47c0edd68d9130cb8b7a2da2a143df1b287b3c31200d27b8b422fc9080187e12fc8eda24a52bcd6d43c20d50e502a7202ef08f93fc48716c50ccad9e6d30f837297f8fa98ea4b4c95dceeaae45161b3f75f5d441e02f566ff1071a03e9a3d44069d50044340de01d28943595c27c04a2da8d5e5c5012f3417470a5f4c73b54497ecd5804269da3c06c1afe3b6668db2f14efa2be6c4d4c717483ee251e8d9b0e2066d69dcc624b6a2befbe36b2d0acd98586d693a65d6670e467914a94a438d9b53495e3b9e1620109d1c0f681fb4831231e8236a221ffeac230fd0bc5f23ac482711c152f591cc48a0b9f1892503e1260da10ed78f46126e434fb8687dedab2182058fa0ab6a405c49b14fb583ace0cd9d38985f3bbfdb673fdc9300b619cbe1afe9f6d491e98ed0a9508e951532199a51468bb805316ea2f0e2fe0cae49b755a6308b39df4092eb01a284a214a107017b960623b22aca3c16e75eea9110b3844f00d1268405ab6ae6b111d328e590e7552c55a0e244fc1b8c95c9e0f6a108824d23436c90cd24b04779a6ee63bd41fe9ed844177dd5c6745eb02e7f44db7d1984a71602afc92fd3d7a5a3f896c6e4a7aaaa43d5e20233fb97dd0ebc0421ab8440adb641b5fa6a08d73d694033e436e91f4535e4fbaf042dbc5f9cfdd74f06f134075231c11038632216b990ceaad0379411573f9eb6b4c81e50cd2c4144477616e10e828f6226ecbdbd0814d9c407b4615c596297939d6d2c4eb67afee219687e517d3736fe3dbe55440eba9db552a7804dc9aa1b17efea8b1ab9c0b659fe4c0dacbd92dd65d722996e76cb8389c74e47a1d5a03ac87f6904ad14aaa80d78288e2fcb49d7f263becb868e278c0a0c8d6d2e310b98efb9710bf6bc4d7543b9c83b06a588077750d404f505a69eb8bc127bdbd0bc31a025ed76d25673d583a7a739383adf98e25ae444e9a7d968f18830c12f319bc57c4d2c8a2126f0b5573f7a7088aafa79bc9c9900d917813b7d0970aa2eb13810409336847deb8db83f74d1cd281d667eb5ec835be8c0b9cabe822804028c8b5b222192e0f921b3a035b41bacee6806d40f5e8f66611545d6700c5e55d1536f375815a16625f2450335606e682cdb0c6b7a91913874ba76d10976d9bb19d516e1a298fe9ee09eaf8b8b85ea7b58193f98d25c8e1c7ce74c064810e48ad3a98b079b881982a898d5c1c6d2a5887ee4750ce252dfae6b3b1a72a827e1529d933fb5b3cb9644ef41eec47ee865fb527ce6dfe7c204e610d5d506d509cf4951fd523b97b5d65a8342cc54ca1bb51b9ae530687b99b69151bc260bbf1cfbd52a4790f26fcfcc03d094f46cf51667af26bb9706f6609a2a392cde03b5ec41bf52f6d347f254d8c60a7e1cc81276be641449e3cb5d2bc78c1aaccd6494c4182d62948d54744c9853457e87122e7bb0f2979efb0430610532eda30a0bcc18987a34f3bc82961f93ae3632f74393629c525de40b330a50240dac3ea8f871697692c9f75ef05d8374a05a07cd7d3c3183d1748b34555119016558a7d277a6f81f0750452db5df0e95b18bfd8de2529c9323bc090d9d7c90812488fc2c0ead3ad1e5f97a4d048ae9da167334a885c183333b4767fb33503c0434b460ee8ba192ec11fee58dc0985f5bad1020104d87777cbdcc9427616291854557a9445c92076de3225d527bc74df588d71d6a3e3c80ee006ece30764788a66a8f05be476fc28d4a24d81291fef29be9e7186f146fb2495e9e6b0888319568184938761475104ca6d6ccd4a396890654d02f99247586183194568d9c578f4782c894f57dac9af98a540785846f27fea23711de6f34711ebe2a9764958a485bfe8eaab328a249c9ebd2dde2ce81d8596864a5605809a0f4b520636bbcfb2bd8758896d8ba31c9e89f0c92ad1de50f968cacdde85a2e32ec4cb45eeb935219e199904c19c5d2e82c32dcdc94504baf163be1e97916db8b55ba621f42fbaebbecd7fc6e05027c97071607d4b9ed3b679f56cb8ff6da212d3250373c3161b83a40dcbf89f805d3cf5baa661f1918994a74e5751d45d139be2cd85da3e9d7249ae9eaaea43cb42e50d700e63b9290a0ad72403941be91f00eb4ac730073d35e6764c79616b07f17cf19711692931530bc09110290e695c52b7197f54a47a054236b3d73b3163b1483339901553ad4123aef75679c52859f2f6a10c371386179bc97247ca14e57c65e2eff1e01ca6504b00afc6d88eeb0e95ac68fc224a14c5816fd285a841deda8054dddcca485cf686468059720223c758190a591e84f60f0f887a5e58af5494c5a35991464888395d69734a6ad964c30fa08a42e8dacd13617557382f5bf64842b1bd4b551172e10ed89f5093551234b2e656eaf84061809eb7c8f9665489a25b33676929329ed8097d7a7a663c870d5a4f8bfd685ce066cb1e883c01f2a73b8421d813ae789bf8afedf10ef5437a8c0d14c1b4d957829f4c0273984f88ce640559ad1f0ba76c30a6748c318ccf811d16e10ca280799ee68c21c263f2b5e50de21ac38efc0e21cf65063ef44b12006da9f2b3f7ef6bf3ddb78a8b37031737f1e64e7097db5640fd178c2b26d07fb85ff3a6302661c452ec574f64e2b1b678179276c79a6928a95283ea377cddae4f68a8e2cfdd291b704de0ea3a73a64f92912af696f7a51066feb799feb6a3d7a599d049d76e5108288dac465d569a9a00a715fb397a3fae21eb17969ee34db63b72e28d0f0e8d530134e08224d115133ae215b93240dc9097e95f31998c9326686d251f381f1089804c31d9e7934c71433073771a6529014c30c0f99a05f5c78c9d12a5946cc7e59128e02046f2ac239dbdd7d813e1b94bf6d2212c9083bcaa25cbcbe962a64955a6aabce364af5e98754f4110c603de5fff4ca17a79b8efb7effa690857ebf2088faf4e12f0197b8487bf5d7ee4950595011ddc883ebf2c190cf4199d8ea333a10de1ef498be50664c89ff4f2b23d5e9084d4c06b64ec16ca3cdcb62f9efe6dc31d484c3a96f16907faa0f9514ae9fe21828d1fe18eba526b0ec309657f6fe7f8dc2915dd6820ca3711c3279a3d3757a713c0ad001e59368f0b77a433846b2f4ee54ed4e6a5bfbcabab991b975a8806a8a1656f57c53d49b372c589ed0c0ca8e631c392ef76fbbdfd1011c593b5e2ea591c3d7ba6870bff14cb22b1f58142d8505f9bf238db1e2d240cb82b91fa84787d891a29e8204ab414a6c1ceead05164784d5e0b5eb9024d884356a9009f71b0c3abfa5c3987e2781fe7dc248791b2a3059390b26efb56da7a2d3bc15994f2f331433c32780cf6456554f4888b9402bb6b32a362ef728495075e40b8ff00bc24985f0db3a5848ea55c2574d8420efd3ab868782ac1326b9c6476f69e222d75b13c112ad43a8dbe94b9045791c719138909b4c6beacdf0b507f9ac1afe481745a2daa6025176f3c19bf45d2dd0cda129ede785f5c7d20ce5bfced8f8ad5c70994be35d759c1394799a8b4f2ede583298e1a4d74f61d351ecec74af73b5be69fd0a7b275b357d7b8e8c22da5d6cc2b56216ef49243f544e8b1af95298847cde5c6b4e6c7ce99bf9396e7026318a1b704dc0467ba1becfa186bcc0b903f5c76fde4d083751a4e3ed678315cbebb03264e7a9144fd73b1a8087ab7cea8e61429ba167657849e6b9654034fa2878c451f72287770a5d121ff8df18f1be9c8b4af07098a605e282f27f61fff976b5ea8f8e414b434598ef7582cd05c364b745aeb7371bba52837291cd044bc8c39ba3e7a5978e85542fe5198af3986104a8fd58ab0dc14279cae1ce9b58eef91d94a40838abfc79bc5ae1e1dddc1b8296d8f253eebce52d8b78f18b6aa0411d8091b097cb74acd050648238ec81c8cbe70bb939ad278b4e6e9ab8afe20ad3809e6cb952a4a42f94e3ddc2cc9e3febfa13f0972b23a4717e6d3918bed89010411ba9602430d606a87fac54bd415e4e7b62628cdfca1d439ba06ca39b5fe3152e24e0e1a39b12fc9d672e152d8430d19ceaa77fd3c54f3e5ef12925fb970c7d569458dfbec00baef5a4dccb72b2dabc1b357f593b8157370c3e05f1a6424ca1252ff0d1a46c7bc2bee72206d13ab5f0a269366119050451c6e82e0243d81dbce7ed4bd686384d8f1e6c1764b8c2704ed4159ee358b9c58264ab12085facaa487b036b2fce841d03e66f109204de2c42571088b128e672e106079715119a2656f851272e2fc90dd0d6cc281cf170abd0dc103c51caa16bb3625175c22785e5b0e55a7f1a9a685bbdb7de32aba570b25020c29b176fd30922db320cdf209f6af0a387a9db1d5e37ca6cd1004b24670e6e8e63164884878f9a4b0ba3c0d7945e0f384530b4efb868fa5c9073b34c2f12839d4bbc5ad79faf7b70b4214f38846f60d5c8f3de70b449897e9671915a38851d85b5424600ba2ee5567e3708ce5dbf58888402a0ea8ad510437dfc8848ab86e38aece1bcaaa97462cc3984fe07ae64e1ec5992997d3330c4778200a66a7666db22aa42f0906b1e17d6cfbd63c43a55cd39b34813f7c113fe9147980d89836c87f0d14be4718f363250da4640104b09a87bb7664c8ec839c78b950a1a9c7571388eed3a1dc3e229111465ae41c7869bc120161e2ddbc13aea6dbe1615370807edc1dbb2f205cccad703b2818132d2c23443c7bcdcbd1d546fae3fc89a8abd8a8fd320eba71619ac58daf27849c383cfd99137fb19a1d5ee73f8ac79d20a91125e2b505e0e4de16a713b8ce97c89b525163ff5bb187e5d463a02febe5181cc5baa7a859e97d5671d6e9e6c0cef010c4902a5645b541986820d02180d7d813efe1c08c0109e0ed045840382cddf4b032e10ec9e5a9348da51252cf99dcc48724cb25b628a2563f303cd12eeaf658be2cc0454f802aff47c1491e1b3cd079ae58579a9c44916f8038bfad8f1ff2950cd7850285fa4adef0a631b925ba22bb46ad0433b5759d6e9f2d4cc33ea8d08b53c904505749c6bd40a44764863c0e97c630603b20fccfb5266c4e5016d600562da06f3b5130c804e39f66df350ef5785a15d81bbb0da969ceef684e0ab3e18c6ca4c41c958f88b6f4bd54a918b7e7ba78ea23e42e82b136736e4db119784399be79b2cac42356b7b6f3f31d392c007ef210c914f530412679046755e28da3340065c10279b309e200e4a04f430198fe7a7e8dcbd70c252b48ffbee951ce2439d34071df191ae3eb981aa743a2fdc0f004d362acd8dd0a04b828e795d2841aaca6c89cff22b3471b15a86d61399adba42c0d8279a050d28084ad68688300483cd3680421e4ff296c2ff1f6e3f8f496d57429c78d5c0dfd0d0b2c39b0be62d4829d318afec81e7b989a7ef66abab061f50fd5b41ec0e5903718923a7884d2f034682c7209b3d34735714c14dbaa755b2cda26c41c6c65cc2b23606a792490756c7174a48c3e03e4cfe972dcec3d3764259900b263c0c2da887e7d3e5b4f8178680d7c02a7db2882e88bc469ff694194839da2491b29e80f52ed81fbb3f55906d1d28753f29c217ea1e3af2a08caa8d82084b4de61d3297cbff038d71d3c958ba4c843a4cd6f8f447cf83b20e33289aed9a057b5b65bb6eb1af563040b5c964f6d4e4f6c2da6e563ad26c598a89deda887571f056420153d59a8b9acd69d8fadb682bad12f3c18397fe007430080a1415eae15d7b21f64794bd9e4105732fa61b4d296e53e7c56b0b664bd8374665a03a0eb1902ea1346c275a9e1f1751d0e2c9a7f33daff96f6175f8c6c31bf77ac421b24eda80149cc18ab612dd5e85479677cca0aec62611db25b74fcbcca993ea302dac8318f751ff3009ae0310a40b48b4c51a0613434f4e3d7cd03e7fd198dc910da7c1506086ecfb3a7c887bf5e680e62db8ab6390088a03abcaa160a80b17f40840fb5ab9feb79f499b726a0361d7eb82b659ec136f880cc1ad7b220e649192185750ef0b380a837ade753e15bcd3853836f5201a800043691c7fa6bab97000795131bcd1ee3584e9ae1e47352e8359bfab6a0168831ad41e3fe3c288bcb5df6e2c9973c5aa05bbf8541bb26c127d1c0286d38512b0918306e449e5261ab18aebeea8d52f7d4c18116171464afa3fd00ebe247141937bc02467d7c9a08ade88eb6cd192a66ac7ea49036630dd70503d93e568a7fc76fa7a56df20d8c09985a404a2992b8d85289a5750145af747c14b8c79208b3ab2c24378fce35e2dce42027adfbf9d05aa82353bea01dda95679ab722db1d11f076a769a454eb23ca728a32966dac00bf073dc3e2eb8a29c70da75e50629c1c2a7acf248ab9acf35dd8ac3a900c3ddf7e9ed6c45604dc20abd24a9c522023feed508f2ae432612e82f46b8a4a82a67ac93e9a7e0952561877f87c845c692729989a270005e9dee9eae87a2b94e979d3b048801f3bb8640ef727a91c55b65daa2a272e83af5468b746e74883e5841f35a7912c22efd937bf2f07d762b5ac80514b0ca5f6b6a1d282769ac8441a9ce819cd28fcf1e3d2869c50029c57acfbca727b2cc2f6cf85825b37879c7e342872ea336e1400b736350b23ac14d6853db8445257709adc75fce6d2b7116a4a0b24a9cb21ed659c3e18e8ee0b6243009815738675a9c5af70ab2417d2d47429790552cd02fe9df01fab9d0032c1b86c5c97845b49ebc979e93b344bfeed12f132d45902833d376877f5a577464a42cd627446c648a49c42166110d5db7a30e17867f5f46d82e70d8607504648c2425d4b8b2caa917fe71bf324a4b9753879b1972087615d4d030b25a4740159509b8477c82c5a8694bc4b58ae7e225405bbd61e981491c5cf1f340926c712e7109c18f01b4bf6ff23da33ffc75a0188f67a8f16a654b29590c57240ec44bd530e783ba16b18210cc6a55a2229a7d9c475141772421dc3e748c9467122d44e91ae142c0fff5ac59ae7ca243bd14298ba44327a71c81a056a24b857e1b9e080b1389b58dcaa0c4ac1ad9fe49d9ee22cf9cce8700e16b242221ffb93d3330c5b7d40bd61ec5be7c5e0648541d2b7cfde72b5a80461189993885266a55d69a89495745106c7041ec3804afb5cb1abf7544806eec41b95d50fa0a29a1e4a4d378afd23967ebebcddad879bf631346a0106e4d3f8fb9c3bb786c9de09450c20e56383c9eae674143298bb098627b6226e4c4a67099e1f14414ee71f620b5896e91da353c947c8698f250f4b0b90a75f1db6736e2d1aab5caa3583aa0aab9d9fd867e0c20f69f8ddd7f1eeb8733257e6321758c32bc3db3922709a519e8375f4bac52b66f2951bebd073c8d77f4ff4eff13331287fe4640990048e0fe8c1f5366d6c956a85b061a34777770faf946b0e2140e828b9c0581690b5e82f2fa89de51765bf7d61346d0a83f81b56ed5877110cbdefe587ff99fea9624b99c99de5f1a5ce0c0288e65698c297062cd473b6bfdbc24dee21e5315858c0702c313baf1cc198dca450ca0a7f97823de190f22b8297de80b78377af3354370116597e375c79c0891c4a78553cf2d15993db7072fef8fd5e1b1985514cae415da4a15b06344288cd9540a609abd681316f7cc73e718b62dd8166fa05a3078df548612a72506973c1afe5d717a223ab0403c5254114ab9d2fa9551ac2161c90e2337da3068f7604bc08e1f54bab2de224a2efcc0ee288322fb6ba3cda7d52c3150db437a60d7eb9fef282d4201d1a1f738024864896c2d97e3a96b739356f0de9c049d5427cdeb5ef9bde58a2ebfac92c47694462417a516a0bd83d60d2b9548da8d92eae28126f675284984ec90c9ba74f90178275b91c6cbe2fc043208341571dd3248418a6bb05f35519e309d387e024925d1aca9102185183783392551f12d3a51f1cb9f6e845c5c5277bff1f296245215c3a20cbca985423b9c18839be32a94e8e1d2c676e234f342496fc4ffceefd930199d0f7cf8a625bcdca803674e7259934769a0d8f77bdceddb17b9b12b0082f090163553b246e4d63962b78c445cf45627ecd4a7294700b83bc2cad907576697b36841ec47c5274377727d4e03fa42a2cae6c2a231a234b15daa6a91d64955fead45aba05a38deb5283e3a3a372a5ffa60486a962d2c04ada2332ec34c74f614f3e1877018b2d8eb4aa05b2727d08a42f104965f7fa5e3ae2826cd33382590e149e7ca9022bd34f9ab33195ca7e6fc85fdbf4e661dcae94cb4e053078992c0ee5d7b419be6d1d57f2cb58db79d69b0d77617337d9c70a15dfaf10601a432a171f14fa751d0481da2007357d489529d703f01abbcec4d7c4aa4b4dcf5ef025a18cc292aad96d15ee1bae0247d842d2123c31f2012b4041e0910bbfbd9d74f5147e2335c6322fe590388f3ced452eb3a062209fa80bb1e4514b3326181848b5ff23d81bd8bf8a8e2a0e27986833b226ab5f777243c89b4d921ad9c638bf4e3a54eaae9bb75a70d40c4f1b6b815685fbcbe7930152882221d16144dd8e051dee4933cac647ca7b021d4864acdfa821a4efc482863665eb7091e0c7ab2e608fc4f161f5e5ab1a05206c2f7f41347c5c2e1f42a90603da68f6373e18e6f86c46c3604adb5db4aa64fdb00317008ad7b1e9afa712ddc53156e828e589854ac318d29379bf3283387fbce43e9c9f419f4af751e132b84fac66bd0a21d22d157dd0f0e475b2aa45c8cb7349b0ac96b6039facdd080f2a9e9864a5739a40642706083eeb833fbc9b0cefc9bd883147be11dd758b20acf88e7aaef200ef5510fa7cb951045a5beae2f93ec891c2874a3d3c03b25ee95afeab6a803a8a74727b059fecdcbb33c59a4c7e40045e1d5bea29519efe769ee113f38ca723efafdc78724f7fa268fc86858b090760f07f49db079c138138a6391d67191661831dfbc1e2c93a4754f491af607662108667b0ec76779f70fa20d727e25ec83cba0e7ceb499da388b5349cacc22a24011c425f697f4b5299a7bfc2b80ffab13e8ba79b2018c1fe05167ff3bc1eaeafbfae0e5107145ff8b9358914f3ea3cae8974faa47cf2530104501e6f67e9d21f70e75c7bc27a09ee990e0d6d9091e4641ea0a197d2de8e819f716e5a2a6baf86b8e58d5c44c238a6d01e0c0ee1224a04e5249b4045163f4368857d642f396743dd5941a0651a9225552a662662ec030bf0b14df18c9931d0e7580500b0a8055b745a265e4038289dd8cd434feb5b25b113852838cb25fd10b8436a16b80ae8616a844f2922b589e96d37c07669363c702c58a0c3489f8cb7ee5a8139c3680fb26f4cd212cde19794ea3edd419a4381dda56b8d3e3fe298206a1397c40894dadb8a89b22aff5c2b5654567774be1ab8cb9f0c9c7e2b746cc29840d1f5464ea33a5b90a89c88fdb7c6994840ca855b4c0f10dd22080e70b2340d140cce8dd5664fe82ca0add1d9b223d5074bde093322cb4cba9dfb3e6620641810c7a1578ae934ec3f630d2e87ceec93cc6186c673b7a0a5596fe79a9f83509296f8a68bd82e774c06605c797073d27fc1cd0b24c4d8bffab4bc4fb56408c17bf83abbaff0459ed5cde001571ce87d4c122baa3453fc29f8fe24220309c67b2a3609751bbd824bed2671c7d31d0a0003c216848ac4e8ea15f50b8c601f8b96a014dd53d47d04eba6e1ad4e7e10ffb70f6c4fc837e0de4ad64c8a71267c54183d3a1b742a56c1f4af52051983f3bdb1607038199c52811042d3585581cee2f80d95ec11b01ade65aef06deed750af44490ba5696319c22c84f75d07bacd25f74e3036ac4ff6e77fad41f8b9f84de0e0e5f0df96ec120204cf7988ded4c66d7feec2758e399afca48038a30838d24cddb9341efc9b7031313809c7e72a6a95b19433a4b316dcbf175cfac20d02c07693ed2c24f2106f88d228648b810b92c9b63bdc12cc19ee7815a3402865a548ded4a06ccf6e7104e0690b12135228c1de42e07a8b0bfb217e0513ad26b9428a29baa26cddff84473e08d3afd017049b1ee7fabc754c6996541805ddc880f350200c27f70dad9e382d88c60ecef0f7dce81111dfee182bcc8a83fefd078a3ef2c9b131875d71c254fb9a87bcba0134d5b7062534b3953f4b1cb4a07d0bd09651249885078a9ee31cb6b3db88c5eb0cd1787ae02a1595edd6c3e33d171a24c67b3eb055034674d72fe9dabcd36a9d4a9281d670a3d970ab07e50622f866ffde898cdb0078ad5daf37dd7e42d34c8547336b95653ca1f735e50a4e33f7226e4a9f7299eae154eb5e616690ba4c704a1a0a259c20d26cde97dabbad4c6261d09b3049529af5c940a9487cd32e39a5226f035dc201a5de778ca04cbb3afd78df03558fb05bcd9ecd80eb8489fccef5499200ea999740d79b38cea075eeb83accf7da691b3f13a7198de2e645720225da88d722c03bc25edb2d9ab6d0d601fe9f2b2fa7a011e1c0c6cc952786738d6a075c775f5ee8c6bc08e62d629cbfe51d390aecbcb6dcc774d2af2a5422e5789e74ec1b205b0438a2fbd25d3b89604620e39451e61e2e0d508224385a1643a465d34bad467647ada991b4bc6e2ceea0a5ad6e241660c9683b8123f31d2ead3247586c7e5f6407e379f385f4e184528f78e1ccb4d16629b03790175affe929708a9c02c223698d8999121cacfc8872d85b7012dea2d262e49537ff8d2483f8c1faaa4c61b64ca0052777312b641346d3d012355e528162df78a38cb36c86a9eef5eb8f46360a56069424997ef2fe3f6b6b89f352737930207f5eebbd298c9d00704ddc8e13db22593123687bc61891faefa86ec3d266fb899ad852b682e4cd571ea37f0dba892566d7b3bb3b8b6ad9722c1d0784304c5b1631dd6c4a3ca35f0f80335e1f0848be584af77030be12691c2a7349d87453268c9a433a06113f95e693e777b342c0e89d674623b641d0a54c1ed1905829a55bffae7153e885bbec5a43eddda8631b1ca636b6fcef9604a610f50c11357dfd0eb56ccc5873bf5d41b485b4736dfdd3e4080b3b35001cf26cfef7ba32262225f85eb4332b782ae0f43bb30ecc8ebf6a05e07c818f0c2e40bbd4f51286ded7327cb32846ff868ec28522def853f0113e49435097df5a5214804d4ac63998c7f4885cd2b5a91eecfcc21afaad3632bd6ee67f60cce8351ea4e74a273097717d6c141f8c3873bb9d1c72e8bb3c49dfe4c2e08d049ee88372da2d2d4123f95d6880b234c84fdcda481933c2a95e7b5035faa9a0f686a72f3dbda8acd472409aa60d2e47e880bac02d396d18304e1ae9314ce757eb9e314fc581bf3fe1679a1aed6a6c76e406b20ca1f4459c590c51beae453bb32b1160ef19f2a7c5a016b564e29c53d436c4a606bcb97fc95ab82d2331b2c87e1673fc13fad78f5517786315657fa90d6e818c8d3e759c5ea1ef1baca3440071c65c3829631002c32e88262a9079e871ab9634f17a4884ecbc1c896fe6a1b29c000453f689c9d78baa251d5e2234208c83ae688744954462098984da54ba034e47413d71c65fca1c7ba4d787cb18e81d7c90b15654faf35420ca2c8dd2186543d290257ccd5e55e797a1f525f6e092b7886f8c1a66c5e68da47408b233863b9c01e08334511daec272120da8b3f2999cf4f2e4a4da275992350128978b8fe19d027490c373af70b7ede26fd0f404f9e653cfb9cef3679e6c99825940cb866ed93db28f314f6dcec5467bc2d12be7281e4f4d6bace08e6959db897eb164dc8579d7ba120d04528b7d13fa672d575e00fe39398e269a87eea20028ac6865b70f91e85b49a4f4fa6be31414acf317216fba6d38796d012ef3c2ed1dcdc5ebbc6f4827e63bd3a1f242d04ff7cd1467a05239bdb2cb9569899d5259fd8952e11cf2a5e5300b6b834b11e56c41f3691a2770e4964b9e921324bda1daa2056dc685f5596bdabf223fa69fd30f38d034d59f4931efb33ffb868977e83e67640f3a2fc2166c8f99675e2204a4c1e5a2b26399d62c45dbb1eb87797c6327c09f9aa20ad83c8e80181b0d420348b051956ab1f7909e17a47710288df5b8a317a1146c0c54f2277a4fa723d3c7e31e3d53f347daec5e4fc6b39be69b4dbc967594f455b8709119cabdb3af7db78aad905c2825137bd6c93a1ba44bbf1c0a6272d94b2e48d80b131f71e652a627e9ded15397557f05453e406f38f6a79900ee9663fba712dedb818728f4f34e9ee8c4dc79c9d5521d9b1abb721d23dcc864fa7443a73d9310a3515eaa605cbe190b84ac80ef8d0b2adce1182c9272b8e84a6109decb6ce12a7843c928cb8cc4d0fb6e1b7fab7d867d7e9091152de74b33ff67c6c37c2ec21fdfe77351f5ea1fa46e81b93e99c36a604b7901e4b172e011567d7b67abd27123a24909152912630f84fdd073597077f67d25daa5bcdd62e200d73d8de0373f5286402cb7d2dc8cfbff72043de02e4e4ed68f82b5149f202dcb53726849532a6900ffd47b00a4fc02afba86a665963dcb4de302c0031311442d3b1d3409279ae4beb8c155fab4751803dd2f89e933a96973f458496e7eb2145c041164b683e1113393e7838c3542c123a494821bd376b39cdc06dc860ac61164b033c76986c3c490ab10e7642530e27a0911d9e4a3b4483c13fd40d4ca97a053f7488865e8546a330a6d33fa50b8ecb7d66fbe08f266efdef159adb9e5304af9cb0bb059a0bdfa2718ac81878db0531e20ba960874b484ba5484de83e7f8977d91e32e5db5ee60fcee537733700958cee114f56ec8f88193506cba54c670ca879028c0d75a98cdd0c461297441b40b1eacf26b238122160447b3c994a5976fb14fd5e0b74f3ce817166e812277bbb89e0b2d8ce5d3f34ea9d4aed3ffe8f13ac0d7eab98d465f6952cba60c686f82b4c3e3c55732cfec30275b79010eca57f36079db46246a06f3937b5d7150196803a00dc086947a65eea06641f9d985377200173223b892fc5c79e4a372f2bdd1d7ae3835a4e37fb448405ee4bc424cf5760d602bce31968902322a78ad74264cc28f5b693073671013e9ce762b61895ad079208d5f0c16c65bbec6d92518cc2d1971b643acc6a72eead6778014c61eb40151a403317d01a0aabd4c26819c61af51797e55935707e1fd156f3523c796569302ed76f60eac56ebe654361eeaffcef7030903f236ebd702e3cbde4bce67c78b75dfe736c75e97cc2a3626751e064ceae1b2449beb810eee4485512c5e15caea44726472bb5872ccd1bdf42bd46d211b2ef510b3a030f3a51c52667f84068429de2e124f5ef52f74c341990cfeaa04977cf4497b4af818a3c0535601b6b6235be1900888af04da695f818a4066e42b0fd03799b86e4a91eb4fcc692e21076f771cc1ea63817597d20c274d6a1ceaabf7938a9c7c9d2ed0a10d9bcc29674ad5be068199c6f4bcd71d1f68b5cb4c7a9d160e7b2dd4b871b192bde67d26310eefb06aa785a8963fd031d4e313a48cec7aaab435ba5913a0495453976b28acee43b1974b6aae0552395adc15ac15186a99b0f25998113a807fe0044f85c6b9c953a0156d6c7caf46eb2a4e9aa25d6b893dcd6d65dc48e389a768b8189141bf1c8ba9b1e53da3fc052a490f111f0e7bbbf00d213cafb24c0c0109a967394cf3c32de5e3cdebf7b4d0ccac92d2db9755e510d168844bced520d8a422134a2d52d96a1c24f2cf46135a5131bcec43cbe9678ddab7c47b6fde2f635d10532cf9305f3bb824d6b00894335eaa4530e15eae16512fb482ab1bd3b4755c094e8ad56b64d012068e035636510be6e8f5b7f2569e158007ed7bcc805fef6ab035a63669a1e3f2e0a23bb18b5f9928fbe2335db307abe0eccacd56b8109d311919affd4ce73fd9d8c10eb0200ee08a6d132f4ec75f541e813145ab00a8ad1bab51eed740e61d98973828af972abb718883a6f4af01901438ac288fb1b346e3aee4f6697ce999d11efb6a0c71883f3fe21c2593b82a101325f0c2bb185880cbfa918dfab0bc8fa9ab6f677b38b0557e51bd1560e7f5101f1868ec416882f00d84368b34f4bfc6b15e4700bdbb5a0e23a0b1a2377ea8c2015536bd4d618749ba00f1bc8927751907d7fcc4d7ba794d86401ff161ab1b02b3be30cea6d0311d83465a17132391422a0bcbe94dbb8cd3cbfff191aba844fa5b4d466200dc79eedc6b1686690996ce8fd4d05e6975ea0f9643b76a1773b57352d98fbee800541ec4edc6a179df92a41945ee2144783a1a84e4363c93c66a7e5903e226f608aced534051e906fc575271c36be4004b75c3e083c180420c34a14945719c0aeec29dbe3ebdd7a4399f276ee96218a064f1c0860e26fc08264de436c2471bb018281d143ccf1f76e4c9fa3012429a000f5263eb1783100362553e40134dbf045c1ad2bea765140bcf080920c6613a01c749036a4f240029dffffe4e5f7b4480de05621408b03fbbd407e316c76489207e2d343375b595b0738d82660ceb1a70b72ac6c3db221b7dbf0c15dcbcf8db73fc16abe58093750c525cc4a9c90f394b1cdeaecf840898dac93bca2007da9d789092bc53ea47820714d2431e63726f54487fe75342311d5cbb06c39edd3efa1988f67cf2176581ef8057fd320ee8fc0bb63772fa240da76a526e1adf55766e71af854e05fed98a0265a2a6af62fde3a4d37c8db08578873da34dc4199ae53bf19427939d003a4091cb33c611b0a073f59dd55b5aa4155e69a5b82e182a6d3bfa15a1b42a0e7126358abfbd6918ab8c70a7d65611b496e264aa33d7ed0db94ca319717de9d028b37f310a6980aa1e24e8533c18edc52708a77e6fe5f99eca972a72b27b3fde2e1876e30f8949cd88eaf59380c95ae9fc40c10e3d04359566ec04dc75f79a85cdee0485dd8fb8b600f02baec434bac13dda48d2e2d262a8367ffd045066650d1e1f92373b7ee420021a132bb8d92418094810f9c252d132bfa209381b94fb93289dc8c8573f39f9a2a2bcbd68c5107812d4046e9405d73293481eb50f10b84ee8ef53a1fe0fa6dab7801ee3699d10802c0096c42b965de92a0c88c157efa02fccda7d9ae00468bd21e7be7c682a599cfc834ae69c61356cb251e769256b06f0bafd10acebe81f8306128c75c619621ea4df7162b061f9c24894cd21431315164053bcd04db0c4f0c0c259a445f46941a52d198055d3c3c14ec5588e056e32e21f20981b09db6a5d75866c8b334a09a5c73baf7edd4c19096c86c60542ebc20719eb4bb0687bc27c72b3ac199bcf387b59bbacf96c0f60cb2d2db0c9ce484cedc31243532ce161ee0e4231adec2ab4cb35500b76bfe84adea01987033c6b6a7ebd85ad4e844a9225e903380bf361e621c0240285a7438347399c25ea2542b6e01bc9cab0351144f23b638a9c4734dc0eb6a11f5e5f0b4964d233994e9db72c14d0265d319dfc0894d6def024521c17bb67505fb2062c97beb6da0d26240e840015a8ecf224db0c6fd38c96100f13401c0b1b216ac7470600b18f1a1fc040c4f93451f7a30337c722c81a117394cf107225f1e1dcd0e2f59e8f7f1897039522d9fca3745d1f3973f7722f7839192e90d1c091da0801b7be0a01e8dab844a76ca5e24bd8f23ad05ece872bd5b4b16aa4cde99ea45aa12b7366bcd1e560ae39baba96c8f8bc0c4dc3558f88af1d88a0dab8cdbea380d04346403269f95126bd3da1194806e4c2342b989233a42334fcd1033edea57c6c629e130358866f5a068f39d3ae9ac95f77ab4992ff01d0a61d552fe5c80f595e93720575367ce3e9df596d2eeff5e4598e64c380e8bc6108d662adac77ffee6548744917e2b7cf0bb37d5f74200d96422391bef917fc853dbcfd0b83caaa0f8ac82a45f5b6b617e0bf0a04a40101683ddcef75da44a58f10f5b49a5b10542b776f3c2460654927f084c69df82ee34da02c58e60a9a3bf0344fd1d842b6c31e976f1e7bb5d16cab6603315785a5e013a326dad76eaac2281ee5341d2a6c6a74d19cfaf392f4c7eaafe101bd73f91714257ea8f40683740c9018e06caba845c96bc1fb291e9697cfc49e286ee7cd8539963c6b91523190be132424c14635eee5b09a122280c052eebefe2953123bcecf19ad9ead8fc07abc9f4305df38a29effb705e37e9525a417903e1763837362e584a21aa03a4031f40ad5f47e349d18091072ce21ad8e95f58c5a42d728c55fed3ccab4e3991d977cd7a76635b2db5f3177b1f403054941005db14ad00ca520c1e84616fc644f1de42d63190ec2128eb2c24afd5cc60eefc52d7c2c504995ae82b38312032eb2ad6e6702a39f315a02cc560386a1e8bf169626aa5aa4db6412471169f25c41f5c5f0eb8720d0e874680c100c2a2c07b00263002ab2682696aa99863b0b21f418c31d031c229d2a6d5d6b083446a8ae994bbdfef3972520bd00b9adee58821439cd98895113488341299d3b9f4d2d419db790f0122df8aac2dea3bbce883954dfdd9a31e859116617d3cd47d601236450240f072cf5bd5b8e4189196dd50d38ff21c93cd9002e05a687b946d26872ae646a6bb99abbb543853921d11e6ebf8bd618720d6f06ae86969fc6a3a7f807e393b491a4230121ccffb92f1dfd771375db2bb171c14fab751c7eeab3ca42d5135989f97ebe3c188e361101161246e7e0817c2814f112e676613d054cd1dc2e5e672ef41c0b5f8079154042dac05fdb916b34d0e4d770c5862ec324868da1e305a82ad0542e2240a826fa0970f23afcd188de159bcbdd6ada7159164448ad24f436474811257758e32df8c5abd1b5f881fd69697018ee7aa949c27976c2d2d97dffb49684e4693768c069ffafdead02b8ab1018cedcb71246a9d646f83d0d662075449a44301d139f2ede6c80884b0ad4d334174ad2e4f63c4e5604657bed96c31bc15161e5fc64406cc9536abdfb4003fc2631a096ec26eb1accefe644a40e75d244f17356ebe01ab1d0e2d2d47d4ba067357b9b12ce0ae794e55ec231c3abf4b6a3a57da3e51aef94e41d379fd48b83a43889a65fb924488e2eebb4cf72c6151d2d397764fb5ded606d1289c2e7eaaabdbfe4895100e819623e57e5b6f99f5f15699d8cb6382d251e8569f8dd183fc596e3684d5af9f1988087c9b4fa911ba99545172cc2b6729acb58bc1c6351437e14df393f746c81906fd3ea97d66b9dbea0bbdd3e3b8e84fdc82f6032add83f6d72508e8d37bf02c8fc13c1c2d21b8e9c14792db61c31214cfd35e5aaaff1b26b32f84c794049d083a413ede1fbd3b513e64adf043d27f3c08f5ec2025f49f61c7dd3f5d7b5a46c14e3b9ed4a4a779e298b841b5746dca21496554a332b7ec94662a09b5993452b7f6003132cee1b98ba9c766239e909f516de651948ea9f58269fe53482cdf7fc6216ed8dd012226df407ebfc5ec24ca2634de935b7701f961cdaf4c77eb901ab1a498a5098d89a65386ccb79d05dcf136faddb11600427b011500e8c224673534eef604865705c08eecfc34547c9299b8799f5685e0cdd994451c835983816a1f7f61440a78990d928040e274c9a606fb8ea47638ad9ad337f06da4ca6665400929460cb1086998856085488ce09cd8664cca2b4b534cfcf0f8f2f84b250eae2cc0a82af140f15b03538f4a9d2ec635994732238cd307c0ca5ab4f26e7b0c6747f9c872240dcdfcd9052a9029125b4164db6bcde5a2c17e536001ba362f7ec3c557370797d7bcbe67a985e8c76e7f155405399cbd2987736809648315af2289a4294b8365ce60d57418e41f8ab7dcc19c92c0c6080c5b03f82e8b23ad8942fc4eccb48d99e9f7c21fc7cfcae853bd69b6269cfc7be7034a73680f8b08b5dcdc7b8a01657c4b890d4fcced67231eb7ea0e463a0549d120c6cf9645f829760f5e065c168b3baa62d4823e20188205c5c6501e908d0bcc403ae532bb1524163dfc6a274f865059baf44ea9da97400c000b7d92cc956db80684c754b924ad49c8023a23103457c5cf9e36e44518e1faadba882d42104248701bb899f2bc82917246cfd4d9b1d1311b0c3978088409ad35841a030a2b84cf6332170e1ab30e377743376fbce1a221ac3f43ecc14954ba7bc932dc49fec58495c3832d0e2823bcae5a51ecc845530e08182d401b70ecc6e2617c0a7267a113a189e69345b2e0f3f21f593dbd47f584217a01c6a065e5212e8caf7dc8f3e08ff453fa6fce12486a2a70e950ca4d9ebe10d5324b179ae8eb3353f4035e6b9a2ccbea81cd4471ac83883dfbae1910cc1875e5471f58843dbbe011ab810c5874245e52862c58b01757c4c599486912997d1ed24dc9ecf74721898fee210f86942768e21101296893d32dfb38f38b9b550ba5532c0e6b9893b3152403e1ff1af96b433718ed67a6f0efb59b3ea94f95a05028c9558e13805f094ea3703bbdddc9ec9cd42b7d014b12e419076a6b4281390144703ea75418b34d06d17e9ab34bf80e73d6046dd83d8533b2bdb9b09e1dab06b057b223250461d998126e6bb28aadcd4245edf42a3a306f1485fe81de1ad24ebf2491348a49b2dba00559a32c661583df466c2c1212fc74dd94835ae742e5104833954ee5c0a564c765648b74d0336659504d2fb6da9df97fccf56d0ae1b362feedd7e174e3a9a4c2b1f11c4291b80a382de64f27a90c441950233347318a253c699d29888a1405feb1cb0506d13602edc0fe91cd3695394b03419a4e8765ee0b41e23cc7515a4dd28d8f69398d8f83967b0d53a6b3bb7fcc8b5444f4ecd3b6d1027909d3a44af28f6a8c16d1f831bf912075a332aa8cbfb483af44f53376dc430adadb888822c941b3d7e8947964002a7fd0ab716759580ede2235092b0e335c252df03c1af90ef9af42c52a2060fea3b03a2a0ec74a01fa91dd7b565a220747273f03d24390d2cec3a50903b73201de988e3760ab6272fa1e797029a28c29c0c9beb815f5a923e322d51db6b2f31d3b3c463b07f0ff78e1b0ba61b2e153c718c88979f493d5b85695af9f34603540b3d52ef0bde135a68f12a1967c64ff74191d411ff94e3ab1f0384cecb13a06fb3423cc3fa39c0764028d83cfe48be650c78f7b27b9cdb77e56ce8fead9fcd9140590e4e0fc5b832c0835420583fc3bde644ff69ff1b026d5d18ce4d185534d72532bb86fb431d140052559ae621c9d38bc1ebb108ef8cda4040f57f949096fec593439882a33ef8efc293e80f8aa9bfb22662dad361f7557a5090f79d61bff32a3f9b9ccce96b37a3bf279be392da8b72557e48d2aba572a4bfb5abf92a9e1a3dffee79ba66abee10f22746ecc25af482eaf16827cfbb0555c898acceb162f96211be56e8b986241e817bd3ca0699a0bce84a8051455c6d946b33af4842d430c397246dd3f6da80f1cba415eac48e3aeaa92974631ffa26444dbc43c32f06150892c6451dd10f92e2f8dfba4b91151496d409ead4d2ed2802583bd69e1bf990928a315f5fcd235bb96d5697aa2f2e071ca6bf6450dc0d57439f43d6a41095bcda50d83a83f15a7b1e022f4f59fd8cb9c22d29cc961ce215e7b2e68c22f6e063a20b07d50fc26dcf417883dd638a444b6a10e0197e6520021487583944cd3f962950c00f4344e4aa08d7864c9015f48787863bf73a1b665402cc7d663cee2dc6aa5a844a3e2439dd7d5bd9db63b8279963933bd5bc2e1c85b6afb49a04d1499376c174b8e13869992489b3451caca9c43e7784b892a6b542cdcf7510cc021b0fc217d4613fb040090347726af775ff6420303702c9e53484f9eac5f1a60cc2cb3c51b9d1ad9e6054082fc1cdb2915a6b0f9a8b30f5df94f587345b27e3adf0a8a69c2f2ca50fd08ca40ce254fb6a8c7efa0549de3bafe4be38ec950e2525e52f6f657bb23b1bbdc5ef20819f764e4b8b05cf721b6751fbdfbc208eb748e005bc421fd84fc6c3b769fda7a2abc45c73e0d0d4292481e59128b3d0897c5c99cb1aef4c3b7a2d3945e6a337a51403413396a7efb02e3e71fd9ee30f259b5d899a9165566a94a256ba07338cd30f20abec4852d62ad5b6f246140dc84d500392be2a7fb9f0a1b79eef42e53e6935197a05bd0ceca99e23250064e56d88808f36fcd27c6e72215ab22e5f0d5dfd77760c3d31b282020697db35f2089670f32b84a35f5946682f3247bb1e81176b3e2350c63b7605117fd307a93e180e5a90061bcb3bc63e523b1060202cd33ca82f29127d04cb999a7f49972b28e1177833bbe2ecc036547aa3531ae80c1eebf0bd10b26d0945778fe7beb2603ef9e1a0bf8bea5bd829a551067f83b7156366934eaa0ff91a299bf88b046f1a3c2a8b86264ca1a4de261050a0312ac42ba634892fedd57b7558216a82a94b0514ccf268fc22d80e134102f2035235de2821fa77d68fdf8b2ce22174f4893b1dc25645cd5abe094e4025f979122832e1d1c647d7faa28c1cd3767ed4f63f91a8b15478fd078830f7bfc10ea8d79331ae17dfe096122061b316f5c3cd7d09ac04a57ed302c6ef3b41a6dcd5a9a49821e5ea55335a7f2cda3957bbd94fd4c13890bf7bcff93365c915dca8ca8df8338a43fdb0bd8f4a886eebe7ad64c1cea933a82c67fe2d52f1cb9b3d05e0e34e67c0f72864b883e67a3bb64b411e0c65e4cf819b49297b5529e0806012b81f2039974fe640df5399f3a243351d78f1af15414a836c55faece38c996b92844116ae4984e3e7fce534570aca0bb40b2fca9b3e49d3e59210a201bc46955811e62dfc15a56f58f3eab7358cfe78c0db61d219440b8cdb3165f3efa936d542e44662c06bc968591261c5f16d01db5e34a81d8f37514748501ba05ad05688b90b76aca41c2cca32820509653060ace6f6c4189404c963bde82c85af7c215e813c93621657d372d661d8e116beaa1ad114008234703f13e4910b884bf979a4f94390ec9efa51964bf2b3c5d0904cb180dd4f00737eee3a339916aa654ba404d8bb978d8d7c77f9433054c02291a809777a9fedbdb2e6781c382bb846773c83216dba7402bfe4611ce01b25f716b5c90caed994c64bc5391180d62a4b42ad062074273ba74013977ab0a89f77b0b091e2d760397f71a3302a9531420c157ea35c9bed61579a4e12d60c631e990b9c96845a3c63f885d017b9bf9796f3fc59010de4c31be8b8028ea5a6512ebb5fe8b51e81b535c96285699140c082cf86cd2183178bd554b21d8cede27ccb01fb6f9fc64c5120aedeee0d33d31e6bffa98ad1b0a063d50e8d6270907e68d5887d7b867901982b8060e48ba227c90c1bc3c202c0fcd32f87952066ee5d996a97dcf454c9b2b521588c5cf9143a179d9da84405efa0ca980f3d62d09b68fa46b295ab39c32d01cbb0080ba9ff73f5ee9843437224fa20acfafdf50caeb3086fd7de0397f02c5944d5a16951a55428f9202d975d6492d693ffab41a6349df754082276c2124930ba2f6aacaada170544121a9fea6dde9fe8711b3444b298ba1d8af49033e84b917e629223873f01da04134af549a0d6020356b392c070810cd89ec9d012e44338cc09272cb10162068d92038dc6dfdf259fa714dcb23a300caca0a041c2b4b81828fbbedf5a565ee9907df119feb88528e0e530c224a9e2434fd58e63450c672a75ee9a52b9816f101b158e6e14efae55de34e346209a34cdd5e220aacd48ff4e3ee56ad34ba9c8f3bace8d86c150642939b2ebfaa74fc8aa991aa14c8168e0c77ddf3f59f5765b61224a964902dd07a45dc0d9de5fb1b8e43b2b79cb92d3b2281a96b800d0370eb74d1052db1a84f6cb949b658f21dc9bcb3c41b9d582f1e51c8956b0ee063b5bbf217db643ecc1115a9ecc03f6178be331caeecfc8435a4a9dd2b340b864b631f259c5ffa76304caf9d51c720537a18002aa6e61da240b85b8836c06e090dbb4ec5119f97532c1e5f80e587be6d86b815f075fb4411b26ed81ebbb810f0604640c0c427c24d42c5d7b4adef7c212381fb3ab2a57d46040852ae8f392279fb9a9827c9ba33fd981ab5ec67fa72c343a45870235f68169a182da3e2b454c06cd6637b4a46076383c8df2272dfbb001846f139640f4cf58aea62b20c516ce0ecdb6329398df60a35a205910096659f3fec62f6b1c1d8a79a05058f63055f0d85c85b09d63361950698d397867b5b8bdb8d29a9e831176b3edac007ddf324b0d049f022df0f35bd63cfa03ca7f940f30dfe0b1154c83773e4f0ace7143e87c352ffbc28e5acaf91405aeafab651d8687468f6e8a765ceb957e32636773b1c26597f28d2a12064135030615ff7c56cd18d64637e15819df64f38592f4021c44ad445e93d2f4ad649dc1f747f50a738967ee1c4e518595dfe2910b93b0a465b0e568c020cdfc89dee7b7031cd6bc687b66320d7279415328ed7e8c6b8d2c355f97acd9039d4cd68f57cbbaae96b3db22f9123a66d198621a18984d9a42c0a6bb8e75224c00d433d1c56c8593654df0d2d244d0fd3bbe83f428d15cc4aa17ac15435e8067ab4338f2667031cd5612fc799643cb87996c03fdca43001fe4ff3233239af6e3ef6ea256a95fbf946c7fc01bbacb54aa1a4a4fc05bf31d71592fc2fe2000e2cb85b70a3d1baf2a377324c4f7ff6a034022d388bfd5764fd8443770bfbaaf9a2ed1b9a4ef721ebd4be12669cfe273f491842fad8ec0b0d1c4901e0817609f7e1306bb10e49847844cb76a9be0f5e886ad82391ae9ce53abdaa0a3c35cccaa5416bbff638e34fc546521f3d73b8a299499fcf6ab2368a6e015d9aa5373f5c95dbd6017088ec799f0ca7ebe04e779336a4c2f047af6f5f34da03ea748bad5a405c153563b95afd7a55e7190941a945e2f5be2bb7eb86a5935591dea718455321c6e5a268ee1a15f9be77eb1e9fc09d70fb80d619522e9b0e94bd0a7c88659cc1bcde9779ab9219c737d4fedf5a37125ab2b257f696524a2903fe0a3b0b160adb022a62619d287668f5d8d6ef25b16aa2c2bc752c1a836d1de75018ec1007d9161eb22ddb136d088a36e06d497b0beffc3c714591fd1d9f28430d36b6daf366ee00b7bda5d65a6aada497d5b2a4252d69c9707453013a673532c15979263829bdb5f6562330b645526f78d02735b5ab4f4db26b12a2cbfcb43e947e715b2513254fedce149b5a1e25a7c76dbfd31215951e26131320a592144f525276a0a03019422249e164340a2212fd088598f0f0bc1f2539efe8ba263f38cec9b6f168da4f128c9bf0c8322418b6e4de9f29aeab498f26389bc6cf934de32768c795954829a594524a29a594fe8894464a29a594524a2fcee541e9bdd9f402d11d1787470eaf8f1075fbdc1d2317c9c5b93eacd5fa74a272e527160f357962a1f7dda54f6a2b6756400699e03cbdb5569ede7046323d0cb5d6ea6b471317928be732b2a3b0543e57c95d593e26f863e518afdc5a43ec3728b21db2a7d4960e57847c7f82ae8f15b4e7af9289f2b92b0b688a0b6439c13713c4763c912fa09b9ad7ca62a96bc944c91b7439b982f67cb2c960373b7278914881645f231315de1e9e7002dbf64c5478ad7692d5448576c94485f467a2c2eb734d11a30d31da1057f6c702d9a09868844d7578ddec79ccc7b480446c05f3471443903d6930f199280a64c77dfa6830f151a264355174891db7ca4783c96ac9129c89a22fd8719b3e1a4c707af460a29a28ea821d77e9a3c144c50408132052044d1455edb8533e1a520449f1448a27a989a22dd871a37c3498a476ec60a2335194053b6ed24783890e93214c864801345174053beed147430a20299c48e12467a2e88f1db7e8a3c1242748101f134555b0e30e7d3498f8f8f183c9cd44d114ecb8bd8f06931b263c98f0f8f199288a821d77fe68fcf8fc28f951929a28ea63c7dd7d349aa476ec68e263a2e80976dcdc47a3898f263f9afc009a286a821df7f6d1f80172e26467a2680976dcda47e3678787e7a767a2688f1d37fe68fcf4fc24f949d2e466a2280976dcd947a3c94d131e4d781899283a821d37f6d1f8318204c96aa2a80876dcf7a3f1b35ab2e4e767a228ce8efbfa68fcfcfc4cf13345139c89a221d8715b1f8d26384d7a441b361a4d7ad0f8099a280a82fd13f4f364cf608a192cf979125de28699d467a29250573486f5a9417b56a05d7f545695b3adce9e96c89ed688ed3962c48e1dda1455855405ee90aae88e0f0b07c60ead4fa6dab27ea78f89337ba8680cab64b64ceb73a750a9a68ac7d611edc823e4c716c263479e1bd5ca8e3c4182ec1af4d821a594d68007a54bf6f43942ae3a3c4e0ea98ff5b977fa4c1e214a76687da2159264cf29ec121e1b38d92e3bf2d880676320072981193a886d1f306d7242b9b36b2ece9c420ea50aa71483763855a51e7386d6479656d7e7faecea459f3d43ebe3dd09d225d42764831c521f1f18b345529f097eb6c8538dad1ac044901f3c34f8d9a61d7982a48ef848b257763c72822e6cdc9d7cc0224b867dde06761d42aed37590b6583ae8a9d651b26c622ee3d2423c20c664ad23d361a93e946163628c3d5dbf415bacdbc460dfe9d2423c20c6f8a075e8405bacdbc43cc69ce2339f6dcd633a858f7d6a2114901098cf6e835e7bbc2663e6a911b94f33d7e52f1ff0098b9731d2013166eea4521bcb1825e5aece17f60d415bacf84371ea113969c556cc300d6b18c43e191fc07c2fdd9888a0d572d82136ad3aea8e9d14074dc8e087953351b10926a6d8f4966aa2621339415690d00272720077eda77afc6d7bd53fd8d898d75eb50df94debd06183c38720607656dabbf6aa6de8107dbb4d4c18814ef5db37affd07f94d13b15d5e9f2a11a7aab363bcc3e8b467ad43888d4c5b68d63adec188df4e355ab7ed4478d7fec3f6fcac89107dfb0681eef954bdef54f5f691ae7da390d4b95257eb902a1c1d554aa39046292a7db2da079b18d363cce8a1d49140b4857ebba77dd38ea275c8950def22ad43f600e958e9903b3d3b5a8fe8a104b272beef64ff9d1cc07dfe64f50f9c3ed979fa015f1edf67b789c1be8a42102760508d2ec446e8f8189fbb0d6caf2365037fa4754c3d553833851f4e1c154dad80686a0564f9b072b4d504da429a42f9bc0e2136f043d721c406e9f83a6e181033b2c109b1113aa775a0e819d23aa60e6da127e92364cc48fbf01e63bc8753c713f9b072664f0cbee8a10bb1e19d3b3980bbe836b88bb40e2195d3b1c13da475501cda42cf69aae369155dadb487144745c4a64f36ba5fd3a71fba67fd831564022dac60054652a918aae366c6742f827bd6271bf8daf135ed3126a447e418438b28a183231c6149a7892882d329fc900a212f60c7ce8e51ec90ae2cedb3544ac8b77a4ef479c71e0a4481684bead2548e31594c8c89312105b246367592c87098e872838c03d45a6bbca5b1f533e35a2d2bda7a7510669b82196594514619659431c628699431c2c8731e81524a29a594524a29a5945e7c93e9a311acb882835c6f95555bb5567d1febbdacdbea9ad196af7385a335ab2c00f61b5de294b3478a92e4faa8d55da36d599b80bf25f8d29c325093fe4618138c2c33356da594d239e79c73524a270e1366827508ceec95442698ada6986096514a354d67142808cd60e6fce2c6a6cedc514d233446acc2ced499446691c9337ba692f93393ccd59c4205a406f645edb8da10501ed9a2270d67a2324d5b65374078643c3208b2b3cb20a104b2339d2c25832164d7249d413b7b463fb033cb0299f718bedafcaecf1d18aed807723875543bbb8c3bfb544d2013459f7de64c14f64cfbfc7a4c303b0ea644c6b26bdfdc31c1ec56b564342d35c1eca525e4ecb28a6883f6ec120c5201d8b3cb50a624186401b22ab6a66515d1253b901c4e9dec53c73efbcc3e914805c4223b3b9e3b13a53dbb919d1d7f2facc8daf1b78d6a9bf64d9e9d659a86a63827f4c8881b9c170d208c4f7067aed0ab594a2dad457064c14290c37b4a6bad95d65a69ad94de0b8630ef8feaa4363256ec03392c95aea4e1095046e95af9d5a781a5db55374106233b6e33ddde9957889064fa181268e36de547ceb0d4347d7200f6fc1fbaeeb9d327ad3ba64f9a8697ac36ee74a457dfcd0798c9592167274208119c3e69df8e1d3b1115fb49d336b663b7bf3fd9d0271fb69f1e1063b6d30ff6dcadb6c7b72e9e9cfdf860fd0ff695887bec5563fa142f55b405bbd5a7a8f1f163c4187748a49c290e05187ffb603cba6014e4ecc79a46a6a49c73ce39a9dc51eeca045be7a5e9dc36969c25b0f5b19461e88053f9bb415be6e3c6840fb465ca189cca2b39651015d7755d242f646b471e205b086f938b250a180880acb01d7980386187f941e001b6034903bc0379edd829c28e1d5c5726000f909d9d77e401a2236a226a22fa11fd609113395a4e76043200c6dc911cb26c16cc1121c7163c8fe727f24196323232f5e646a6ab3f221f6c4a841259ade6cd963a61f4895b3e46c94d3da515c49e524a29a5377432b64d406cb132a28bbd5a2ead4c968962c9be95d992c18f06cbb22ccb52cbd2259a9aa0a5945a37d68e095a4b06b516ce13788252c68e53028b255ce8704bc04a7436c029287d8105901b71118415600f6c38c04bdc0acc820f09959f1c6469cde339ad9ad2a6fae620cb5f4b879914737260cf98f9d194a42a30ad13943226c5b05a2ae56c73c6b47a9efd4b25ab54ca39bac8d10ee5960fe53623dbd2b4e543d3dc40493707fcb4d65a29ad5397a24b29bad01cea36a3fab0c30823ee36c3ee39434617f96d86b5e77127ba228775df6d86f4229d7be66b9b7137368fbbd203b06d86dc1b770e9871618c3196f37237ba4497195894d83ccd21b6008b8490e5edce4e7489df2a40f5d439e0c8122a60b5add3185f6badb5d6582ba594524a65a6ed51d5b4b52c1a0127bd6e3a48ef23fdc28aec9d7bcdf89ddc7a203d7ff4ee3b911e427998b77722e9d1430f85beb066771f7d618d37d2c2ee58bfb022773a6b1f691aed244da3f22e7fa1a76b465b339dd361cd68ab3cfeb4759fdb8c8aae1334e9d2b6e52c1289b8634ed49df3f2bb73a2fccee39eb99494148ee3388edb4277b9bbe77cf99cbb900e6d76e8a32f1cedd1bd2facd9def3e34465d21727c871cf1905458777733a14dd3b89a4c3bbb30eb58f0e43475fbcf7591775d71eeaba8ebbdc76b0e9bae3aed3a1cc9ff7d1ce87a1dd3777ee7488fbc515f9eeede168d78c463535a37d0179d3dc86fc6c02ecc8a303229f7dfdf0bd8e43b9b3ad068e7d7fd56739486a59f7b3469ce0d5218e6dfdd2392aad5f886387f139ea9e9f8799a14d9e3640d289b4106591a96d33f2570739fb7e88923d801d7986e06c1ea4dc2a2fca9073d01d5ac02645e52827dd44c33d63eeab97cf43523bbf04940390f2ec08da945a5adc664eefb677b7b61af95c8dd0bd73d7f069d33321ed69ee1df7793f7d618886caf16bfe42ee9e7e6145e6ae5dfb3a1e4ecfe7be10c7e62eb71eb477fa84dfdd7bfe4ef8a1e76dd327ac67e204bd900e71ec7c4f73a15bdee3a683dc2a61fe49bfb022679da36eef5d0d957b9a46e558d394425f8e1a723a47dd2a3a4e30a5eed2e34d5f5837e974e341bea4eb0453f451749c20e97582a4ee167bdc7a188dbeb088dd5d3bd6be197a6d3c84f95a109b3bbe71ab5137d64348f74887445c0dab4137d63d6b4e87f639e8c6c23a41ed9d8e1314e9d066635a243af689b4d789c2d730eddaf7c28a9c836acfbe1c52cb517776ec222de4130f72e7cb9ddfe9ed729bc1bfdde53b1d661b5f073e360d3bf20c9962d3e06d26db19cb1b293d90234f0e388d63778f33068bafbc0b69ef2cbe385bbceeda57be385bb4ad72df05e4ce9fdbccdcf972e341e2e71a1166e6103f1fe78fcb3a943b9f7bf779d7be30cf74f7e6d63e39b7994ee7096e9ca6c19787999ace5137d6f73187c7615315b8aeb8e20aa09d836e3943daf23076044011d3c634b529889c983fab0d03073e3858d5328a16f4c4d8dbc464b78989d9a771724c3008d1271ac1bed29dc832096c440e23d04c6c999729191d340a72188dc8082521059e9cd566841ce65394a7f89726ea14350b18847c2bb4505b7421cb13d951877107460b2af2a624734ae4f8d04dec4981f6d356b5ef64a346cd8b13bc3ffd40b5f9ed9b2602cbb0e6ecb8cd883c3808734c1e52b6bdbfa799946d9fb22dcca4c96eb130c7dc993ecdd863b7a7b91e7970903ae510797090dad7afc7cd469e1b00d9d6978271a46cf9d904479670ea6c8cddeaeb56bba54f362c4bdfb28989570b430eef0e8cd023e74b831f51d000c82a0a1af8a0278a18f48862065f28ad78218725934a09274a3ce9e131821d247e943872162b72784db593a384ce0b8a80343cc92198532d08f2440b86003dd18223ab275800e489167000c601be904318586a358354cf0c4c20e3863164ed83c1a52ce430eed423f8387204551468cf91235471c4085cf0c1461672187b6e04415be44f841c469f09e0a02df22e6e90c3f86381821c5183c060081d5bc8610cba807458c0c40d523b64ca6587490c6e7800e550dee0f4d0420e254eedc1d9d1525164e1093e7e509143e9836322c5c40d50113a951341a47022064b5001699243a962d189220b44a268f2a3aa901c91850d0709123ca2f849e5049139a69e232f787264891d40479648c111d592232f4882ce102739943a1368ae9814b174982881481149a44bdd38c10487a69cf83102c8892b2871424e2d77b6e46911246f7538c8a134227faa41ab595a012d71f2c415534cd5e431c3e96302d9f295caa9c4963ed2d2712143133e82ec791cea57a2311ea3a6e60fa08d2413383eb28439033922a902ceb6764452859b1d91ac82f68e485653ec8864a564db1d91ac98b0b71d91ac8c6c19014b2fad9d76da69ad3da6f6d55ad6b2d65a6badb5d65ad6dedae31c6b6db6ed238e3da97d8fe30b2dcef0b3ef0c4bf60cab092e592df9a1c18786243e4af69100ec98e0fd931d80d4be2faa979d973bc38beabe0099282c47cf00f43264df388393e87243553cdbeeb086151015901c979b89720144af0e5d04ed7b19ecb87ef63bd9b0f950e4530ea19dfd52ed8dc89d347be1b3ef6f0b80b6ad01e8651e11be67df8701b8d935fc18d9f7e1a886150d76a8edf04535ea219c01685f2dc40f2e5cbcc55b3cc6e09512c4234470b88936a8e21339d90b917d5f43156dc048d8e18bea050931c68c1862cb0e4476b8af11e48424a8b0ef1111ecfbf085c99ed12890b14363ccac688b0c1944d40cd09491136dd0916164dfc7889aa12282f7a18c20fbca204263bca8680bd044d5b09ae00c151155c315a07851d1183400d11697d404af10512e1d88a085fdc8f52f2adaf2841d2faa971c1ac3ba30125deeadcfc5f5f9ec7b1a80680c172bdad20117485c5213e5028a08debbf8d99706a07d4d40fb3e74b9d9a69f7d8f5f389901a8460eb969e899e07d18b46f0076ec7b1939d1e53e0b0a4d3d57597dfac12222e67a0e8d71b26175547dfaa1febf26c2da577d953e0e6db9d58a543b34f5a8f4e4f0b075c7c80cf67d0cad223c48f6c5b289b5a07a9b984b6b40d426a67e9b0fda72edb7e198866018f6d5e01d1391095e53116adaa1d967da31f11899200acae9b30f9b86cc1832954a998a4c54340df94c448af4a4a43c4e54580a3dce1821ed224d24fafc5c74806e80b42017abb5a01c5676984892820c9b2e3754065c0e72188fa8f6bd8b8ecb4da49ae672b30311da72e7144277e826f35224561bc45ad675dd8b6159b684a623f31204bfe468dab671dc0b9197212f3ad1a5cbe9369917203941704835e0490c8a2eb70141320dc8db8cba1b00e4692227f2e8de26f3424426884c4e28b4c9bce84cf0e608578464ec3034fb6af0cecb9009de4cbf1089a29116cab263188661d8370e3e6a401c7e4cf05eb4e1d043e578a4a16c3238f8301d6593c1a1c7046f4a8a4cc1308d03ce043189848292f29233c1fb944de605c804ef7189542a9546a250a9e4e552a9d495b8ad542a05807b5145977b171c171c971beb72435bee6d62aad6517fe9d396ebd22a3d4afb7a27162b37ad4e2c589c04a00a92f20529162ba77884c8cc9289a239fb9a7c4c2bda724d3d2ec8dbbf971cda725fbf171dda728fa994d1e3420dec6c2668a26cea0e44688c998bc52344f6bddd7200f78ce9888ca4896a87118aa07dff02e70514ab7d7776f8a29a594d544803100d402e3e26a80ae5c676d099e085b1331da30bf61d80e09d8de91d72824cd4fcfd0ef73be8b8f8982870dfe3383d7ca2f874c22f562ed8a9d505ab7d6f72c22326c5e3fe44710dab1091e8724f7adc94899c892a05d199286fc80b91894af9fd4b915b6168f6dd98e0fd54f9421b64fac2c8f3635fa07d9ff2851bcebe47b9277de1d673b3eff60951475f58127d42d424fb7e532189366c381a9069c844c59cc9b3f9d837263411d937b4743c1d6e29cbc816f9d8f7a1e6f3736feaa13140d18e4967a2e2cebe31e6661ddedce93053b064834a3aa61e51927d45ab7d2f0f75d368c24363dcfba2da1c60636a588d20b7b88b6335ac6a58d5f04363987c6a58ed6bb54995c317d5be16661579514dd4d536ed1876172d58f63df605316f5acd2c9ba6140f134328e561c14f0a326c5a3a3f447c76865cabc8be3cfbde98a8edf7404c94f6fb20260afbbd0e3486f57b1f688cebf7f7fe507fb06fe122fbf6c52b316c89ec18132d66909ba63f8bc804efb3cf2a32c1fb16dfcebed9d7e2bb13015387b6981e5a3a7b490dab09dec7e9610f4d3dfbfe844dab890a5f5418c6d84d3f34060664ea99a8e8a3a3648909e8def444e5e69a9cecab92da5785c7bec719f6a29aa8b086d50b905bc32af6a45026038680ad8ba69063fd8cc7aa4badef4e4ffe742facda96ce616e0b4463e81d6bd56309ddd6ad4e901d164e689f043e761897ecccc9ce1e351dc6273bcb6876b34cea7cb5a9339a2b4b1c71c44d12f3e216a3538d5570f275d2af2f44d23107c9420ea161a288d813232494dbdeba8baf032e7e4a5dfc9f0e9b181718c7cd0717dac5e987b9f86ac55c5877f1c5d104ef6882375b9bcc3dcc0d77e50839fb66440eefbe2d0e3379ea9cdcf5bae540f775892391c8a41fc330ec31d67d69b9135dee370fdc4dd213bcf0ed278414a262dfbdf50921eb17ca21fb1e63a9b5f8646a8239aae84996bb08f6bc5b9487521e6271d34b583dc6be4d3b86fd04d4634426d917c9bed4aa5181bc2f70da16b0d9d6b16a2d7d92a7b1b72e2b0d2d1a2c6e5f315dbfd58fb6f825afef246ffd229da41e9d8674f911e9f2a44b6d1dbbbd8e59ef30168799d7175e6fa173d4ed71abc1e298a66171ab694ecf513776cae2745cd28e9d3ba64313d5a1c7ad879487beb0881da20f61af5728448fbda684ce8542f4d8a543ee71cb417b5851421ae55cac3aac2151a88a9e60d7a34b7d9ad14eaf9d5a1a7b0f8bf6de61a676efde2dfd421c9bdec5e95d7c3034cc512f8c076d06bba7437bccfb422b8fe5ad8f74fa5dd6ad4b5b27698a85d7f3e58661c7eae94796ce131c691a1b5aed1de71dfae2f642968db94dd3b4bc912b63c8a1dcd9a1bf37e752e93bfbbe24039c28eb9765df780f03889b9f4062b5ef6fa8620b374a61dfdfd8c1cf0e0f701fb31b6f64b929f39424073973b833ea046b68b3e97f24916129a536564ae9eb0763824864ebc6d030b660c6d6de7b4cd7cbdeb241d860731ae9313251d82d9d65bf7ebfd0f6642af7d867914cf05abf6c4f8e95ed992c20038665194a683ae9a3ee9a9692ef48cb77a4a93c6f5a0d7cefb6076b4fe773de55be109bbebd761ec7a9bcfbb6e17f27bc7d27acb57bf7ba6b3ad480cece0fb1e930b353f14cf7348de99ba6497957934d9c4e797c699482421a8db2eca22cd2de89342e77a150f6853a7c6546060cebb08c611886755aa79dd6d81e0cc36ccff6cef66cacb1bcaf2fcc3f1ad0b13dfb02b224b526beef23d6618e30392c0dc4a01dc3b0e82a5b77ef5d77ea745881ef0ba8bc0b6214ba776fcea4a84f280f220c22873de5b039ec2ee9acc31c76533961934845c56432994c9aa61dfb34313c8868838bac43172d5ab060b1b2723aa9a8984ca5524a0a0a0a89341a8944a190e7e5dc751cb76d9a867196619bc3764e798a26911e5e9b3b3d461b480fedce8fd18694974e3ab43677d3554c3ab4763ecab573498735baa06892dddc638c600efb7fdb20563b4735c9510de4f6ebbaf3f60b736c6b7f5d66d8176a9bfeda6fd835fde28a8c69fb2bacd1ea04341b2c670f8c308c48a5974e4f398ae92a1a0e85423781a78145f4dc3d6b5a176a1eeef0bb735dcea18bbeb03b1dbf86b4e7ad5c3bbe3c71cf0f75d7b03e717a3b8de8dc37d139aeeb425de87498b9a25f5891357d7a48d39c8e358d29f47cd27182256d9b1eaf8212329548b70fe93005058544229142f7b4d03d5248cba3d1e8d8370a43e872877228c7cea2bb61a8f76ddabeb7ae8542a150e8e31e0a1d874221d1f3a7ddfb3aed9d76919edb0be59cf5a66bb41c1a0c1e1caf5c4dbbf71e088d3e5b296d406ceb2c40b0502354310a9d0b8d52eea1dcbbb5d5e01eaa41eabe90657739e7f0599f50f40c498b42f7de1d8686211197bbeef3b8e72f0462e7ee9e77d1392e140a854e2822920e8100627bcfa1cca5e8ac694a17699a91f7859cae61e9468f3f69a30dab681ac6d8f3bc779ee7799ee755d16591e8a12f64d9a1735f08c4e64827755de7795de7755de779a373da4387a1a12f9efb6a584421cf0bddfb4296ed853c8fa586251a018285c3022ac78858d47428b30cc3306bc6b601b1b7df9abd6d3accb263daba7e311dd6d48cf0a43fa8c0b06bd8352c4379a67215d3b9d357443f167df578f4e567d1e956a3bbe89ee779a42f5f24b7fcb8cd205d24caa2c74be979de4f40abee237df4e5632c371eb693de7da4306fd1bd63d1173ae9eb9ebfd0b1dc6a883e12e971cb21e5a2c34c52dc6652425af45074ebd66e5ef54ef72efab2a6727afc0ae79d54506eefe9d0c4711eca7b4041c93c942c1b3d775986926599f75197e57b39cbbed273ce72ce6e9f8fb33c033fa3229fe416ddd3dcab77794fa3d18e89b019edd8e346d2a1cd26bdfbc2d1286f2fa7a4a07cf4d0597c9e0eb777ffe8b34417905bf428b1637a8613d8debdcf083345a1f675a2d1431fe9de178eb6774cb799d0491fcd6d267fa4e7f6be70fbed2eda66bce7d104b948a35d3edc30fce59d85a0cae4aa800172a5dfb87eb03bdd381dde4d6b9a86a512a6c3bffb0dd04607dc938ac10b989a8ee163df4a57b9261ae5e39108e4bc8f7458038e9ebf10dcb9cb5d0ebf73373af7d1b9e76e547ae8d5eb320f29bfb13b4e9fee7d279493eedd3b7a58daddbf3b14ad430c9d74d243bfb16f6c2e1c75a5c34c2f4597de699ad2439a46f4ae06dcdd3970671dd6805ba48936aca26918638c4d26d3b1cfe405f0c6f65836f77cd2c7f2cd7de7959d57764a6973271de58b13cce168e7974a28289af471e0c1830741f006d8e1cce8521f8af6bd1715a0c902d05fa3df5f2d471b70f7fb71f7e2789a11ddfa0cd12d6de3ddfa0cef96f5799ffd445f58f7b150764f9f66b04c87d9a397e99ad1eee2af4e87369a5440f6eac50258c744db1a7d357f7782d6b54ffb323e85bccff0748c2e363ba467887408fbc28b3dbf3bae1addb6c79db94dcf60d78e69f816c03a4717eb98ced1e5dee63483fdfa0c4cd7d458cd6d1a92560cde854bc9f5ecd8967e4dbf2b5f71f1d35558bc857612d6be7aec7da3e3a979df08e55ebef78daee9b066b467c82b6026370af31ee953d639c82b76a72b20aff03e7a9665db96cd101d7f6e8f5b28c485ae6744c7d8f3ced96cee71a3e1e2f9116692b88fce7167c183e8a45b5b0fa37bfac4dd3b3ee93b71df4e0aef4641d1274ee7b01d1f4587351be5387f33beff71d3cf9fbc628742a12c0b7d6195d1364deb99efff87ffcd021de99c8eedd5d46cd26dac4d0729badd7490dbc54922af868b739ac6c5b3a66171ee28dce3bd2fec5cac7cc4e2f12d54b8cf6d8685ae135cd13fa9a870a61e4ca6cb99eec5361e42ef5dbedcbd77a69b69f772f7f2ed4a5797ea363377e9f72be950eedb65ef6ed7dddb77c73aecf75df0ae69b28c26fbe55d3cc8674f4979ce5cd775dc01c00120f47cd2019072189af2c5e7cf7ae80bf147df0566260fdb8cf74b87d9bd30bb772c8331d5bc6f3bf634f785388ee33619d1bb8f4b11e99c72aecbb740d639ba74e7748e2e29dc439fc1bdf3ae556d5eb1f34f33a4a77c0649c7e8427a8a8ed105e536f9299f919f729bed7de14df9f287f2857577277d61ddde71d5ac6d86e2abd58d87b9b77ba287463ab4d9234ffbe80be53ecd883ec19076c5f66e014f6bef748e2e5ad6a799eedc67749c1e4d50db344d76f910eb9ad1cef4f598e3fe4481e2a38bb6506c5e170cc5c72c7ddd3afe5e5c916bb6754b8735a31b35a383e15b4811996e2a7d53f9c97b87bdaebb4a29af8c4a7965447a0e3ddfda6a74f72ee0625bc0667fe16873236ec485b497cf8d4ed20243e0c1041683b8d2342b1661099cb82242c41243764d25ec0b4731ca321e45a0428a7d5dd745860088e062e00627a8c10b7e60e0840f84018a9f205828986c20b396b5d65abb036bad7d40ce0a544c4821898f1ec458306cfb034c9fb02ccbb29ce0630d01043d3429481eac144929e1ba2c6b599665c928e0b80086b5d75ed65ad65a6badb5d622992d31c9559297948e0314c0af4eb0270adb6631d6daebbaaecb56a1040bdb5a1e298c60db508cb5d6075e447273c4cf068e88810ea070020516a811ae988d48a10303269e3861821d2abc6049d1231fc15103222242128a4031cc003fb8820b3d460041480cbec013852dd47aab55bdb02f2952fb12e1b2b31ab996c5ec4d325ba292bb244f315be296d9e164b65830132b22750221fc58420760f082980b088874efbdf7baae8bc7be9e87e08120fbda62aeebba9b0432755df6bac2957dd57b8df6755dd785a5763462821efb8299f7cb5fbc9630c5beae6b54cad1cec001158248a1065878421898eceb23222c01c5be5ac45cd7755dd7755dd755c7606782ec10400c984749182c1850d4a049866206405cb02c6b599665599665594dfc5896657598b5d3664fdcd096b8312218d1c1880044092d166008383719115650a4a35dc0f2ca5bd18a42cd02101564010a48f8202104d6419d29c971d7144b9daafac5184c75509f24a99822b230c4c489001d33899e544c11377c00073731f6f5d3118d10534424428cd542e808984024a6880254d1c416aa8f2788c414012385240716be34fb953efbe9901f98421553c4ec424c9c0808352c6e61670a554cfdb01162e82777649973ce1d242008b2bb1d91f0002a52a4892ea476112473bbba2768256f52396e7b10db405ee5ebf278081b2cc8472b707d7982f2823034c3b0ec324361cf340e139c97c7366c03995e21ea9e41f63c96d5c80f7882ecc853e4c70e87b023cf0f926cb9c96025c8b3866a8c35c61a630dd16eff60c88e3c3f00b2270876e4e9418f8de3268391204f94f77cef3bff6254bb0f7d57cbacd017d69432cbb23efcedc3d6163ad56129526a3d644d6082327f343e1fb62f88af06478e6f081d32258109ca1b54c872574d79b09b9eea1b13d438b747d65aeb0e3046824c6705e5ad35e5af35a59576563aa51230d6031b418e990d1bbae7b381449e9f41434b20e0a87de813e48a27ceb638d342570f76dcbbc9847e37994bd3e4d43499d0e5279fccd40477042d91b5ed72b37693a9db776d3ac48d69bfdaefb5dfa9e12ff48cda2213252737bafc44c7d26724ea1e5e8d4a1fe9c4bb0ca2f6d2c94461aa093ad95616c9324e8eca1ef2933f26486564117a9a3df4e1ef8412c8b64ab60571b4b9c970739b21370e2c65c898a0dda86691caa10cb221b54d865a4ada0e334f18768c52124993347b27da61d01824bd4308466f0fd280714c54dc18fd5626684b212864ee614906878c09dadb98282d04852cb30c9241b362d1278c4eb63d686de4d9565b7b894363c8a0209c2776de6c2b6f2f717cc81f13a5c9fb840c23b5267bc81fdbda9b96b0b4c4c9d3e300c64c90eb434b64ec9a5532d609561a7794a0b7ecb92732662f66ebadd75e968d5526b9b0ebbaaeeb1efb309c2c8386277209c49a765dc7f897762f7ce19b61f7ca01760d5f96300cbb979808329c69d9b9277209c4dc17f7c5be6913c51dfbddb6635c866dd896693893d8e4411125b5d66a5a41ae94523aab2cc39599df94415e435964d7a021ac7c8eb597ccc1d8cdcdcd8dbda1f7d29b1b99137372706e78c88836cc5d3fb9e926b54ce99860bdd62da67504dd8b962c991ac2d232a24bbde90599fe669739417488c8223b15838996a4f2def0e63891bf2db775a933c12013ac4ce4ac4d19c62471b58750a9b3739e90edab0af2ece94090e45a19b4c5526a5df786095a19f65a29258c7d95b1edb5b7d6ca50554dc3042d587780a39638b260d9041540767cb263135ec8d93cd9af4d464a9cddfa491ea909d67f37f9ba76fb0dbbdceeaf799a9f7a8809ca195187b8a73ecdd35cf4929f043619edd264a6bef837262ade9860a5dff5ecbb37688c52c927977c76bd978507f23c10415cdf2673594c6c3b94af299daecb5f9a07bbaf5f7a08cdc4cd63aed2ca59266a34c14a87481fbbca4dea1014f2d425990a616c2ae41b40043151f43eb6bc564b6e4cb07e3759571f95bc3f79661d42225710e45f8f8fb2da1278b589bad7552ba50110dac5c7a87bc419716b13a445603ca3fda83dd5ea9c5226c1a41ea56d25b6daa4db9da0bda905b9aabcb0c24aadd9fac9812a65b4d5c514f27cc95e3ad63761eba79691115d2aa54e20b263471e1e3881b1230f0f9ad8f781b6d44f4a6d4cb0bea2a027c60cca1b3e48b0bed62ac426986a208d51694b7411806ca98f4eec6ac3ad61d752921cd629a9a66182158c2e26c8f43e4c54dc388e1cf1b3c3e884ca1c7ce994a509523d12f5e4eb180772fdc48e8120633ac33076ee895c02f1f6c57db5ed8b13d4eecdfa13bc1ac6c674b7676c820c87a1021c3509f2a94e492d0b86181f4578d2db08819eec8580d9f436d125dab869288d0ca8d1c5b6f49357ecf985456c60cfc76d66a6b6b4277aa27167475dafd8b86ad1723705a3d43523ee787aa3cc7592915efe5ef7decbb295e6cc892591e9ebbf39e79c71c6b9c3ba3c107fe917565cd6755d96b643b0d54637b95ec3a20dd6ed0df135ac691ad6976525187eebcaaea3d9b26591915cc1ad8e90b80db068bd2cda604f1f3f4c25c49ee90ccbb20cd3d788f9eb30f31219c9f3fad5f7fa9c8f32575ebaea6a230b3592e1583ded8f35cc8eb3dfe30c639c695db1e8421f59eaec4013563cd9f4975a91914c3ff5bd8e694b658624327ddc18bdba46cb83e577a855935fdcf10b1f0611e64dafc74dd6dfcf66cbabdeaa9f609495523a6b4029a594524a29a59452ba715df76d94c2e0a0163efd0dcf9adeec16bea51d6bda67c4ad698de2ef62363bfba6926a0259004b2995382a1c59f40d13accf747278c30e63cfc64163e4ea437870c71f2f9443252fe70f464a1a1325777dbd8c1beaf3105aa632c514cb0fe3b8a5869998cdcebaaeebbadb6cf985b7be506eebda76eeb3d9dc865dc38ebf70f66cec30f4622c38e8bd970e4bd15e54d47b73d6b0247218775460709828eb5e7a7b99cbb26aad8f56d21d1c594ea9bc0e3387d87688dbd2eee3444d59e53c00cdf2c515f57e4e4ac36084114878e088236e9230c208243c901d675989b6648fc7326a9dd817b3f9165f075adc45ad2d1e5db4f89c2d748bd7cfb2f62d0e335b7cd687c3d43032269865596a82998e0966dab723722863671b8b5a3fe96482d97c95d925cf44615a3ac1f2266bb796b5ac652d6ac9d3a9e59644c63e7f6bdd4e3f21a41035b3dfca17ca253b3b963a13cc7e71dfb86f1c4e97b98b44a7a463bcf2d253546e2deb2a40a9a02ccbeeab7c9db72cb554ea13268f554963e5f4757ef69dfd2a8b10470ff6559fb0d75f7af509d33ac47dba4e738ffdc23e4bdeb356ce42bfb0224f9da3768f5b8d955b9a66e554d3a8dcaadb5a31ddaa7cfb2985bba562ca2929f5b9628c822bb66e27c616053f635c6f3d07cd817b582d096bd233e9f823596b6ff3ed47d8e263ebfabaace75e290dbdc8e22ea2177117d9ecdae2554bd9e28bb3e57a15ddbbf529371eb88b1eb719eb221dd25b58488fc97dad9d755e6de70e5d6ed637b77dadfac2340d3d37758ebaa9167dcb1bc71ddaee6db9e3b82cfb4a16b204a2d9cd25fbf0d97598c08d89ba97b1b398650f62a2b24b1ed106b9b3cb80c1217b8d84c1cebeb312ec4c7e606701a12fa8442bd7eb433de918a2991104005315002030140c074422b1702ccb5359ab3d1480108cb454644a9387518cc3308819648821001003000000044064686800005f674001eea858df457c0738acac756d2f2889c427240a393a7c8019c7ebca2786690803996d8eec27b3007e4dc57417ec38708d4f952fb07d7e1685787cd01084c55d2e1282f4c35f7b42acae7b51163164370ec5445ef31424d35fbe2b488690951bd2e69f40d05c1b7d9d69ba395fab966cf111e7bedf3891822d137a4647390d8bd1059931f8d0bbcb1fee2c2027c19829c5af25244173f198d824d9684a60399e8618df50822895c6227279c8b99d731a936e00f4d25a9dffda08786bb402e52681e77f2ee2e2eb25fa75ad0b77f0932de01d4432dda6a30cf84dfd19058d64713b42cfae73537054e5a29b84a236abbd3d6dea400b687b2ba6c528e63c99837972708b9e9d6119dbaa84cf21441fd3ec1fb0f6ed4055a49a9c03c258d707375ab52885732c93e8b5c32b2593027014f92b886a949231369e582b59a07f11e95fd4709083e544f792b42ffb94a5f21fefe3479cd70a5f91c04893ef16d8414d5af66c379491feb625d7a3484bf3307915b7fc7cf1a9289b8191c23927f641d8037e244338c0924f8624685cd5c96d6ba94a0c0477e9cb65500539f829ba22bd0d0bf7b79744af74c83371d916feb751c344ec22b42ad70279e31124c5a5e0c4c457981f9e4a00e73b9e788bdc956f041b8b11c5d18b803a07ff3037ea179fa1dfa400fc0047727ff3b64471ad5af4de1f7e89c5748cbb0575d052b86e0e15509937d59a180d894739f0fb1ab3db725405c23f5852dc160091593c70b809104871bcf0299125d757d1c508d031be94f35794cd3a98f6f48a25155fd09653e541253666877f2535b9222dd95ac761d4ef34e4334a5c902e2b12cbc7f3d6cb852ff3a37b6de2e46a7ea3f6ae9fe788a662be58807a436ba550735f0abbcc8170ffd926f1a20eef779a2528dff48c9a9d606724d25fa4aca215255a83a543ae6d9ebf88415546c2066260df8fe8fc89f3430b3ed775fa59b419850596dd72998c85ab24d59d651dc71021a1b3e1e361b99f76782480b2f9067e5592870a6917b0bb9d97b4cb60c229490aa1b00a31517e2ac6ba857aad7284d5ff9513d7ac97c0c6d56576690434e93c820ed9ad8cf2ac92927493df2d8caaa95acc465c1e675f6851ac5ef47a40bc3fccccee22cd1d98d00fdeb1febb1a1821164acdd21e25198e197f300d5c5ab36d493d02bd1c1d0be2526b2fce2d5a88b275c31851793de3eea635368887881bd0693d09fa44108e8d95d16328d4777ac0fc024a6f7011bde7aec3e3b0624c58096ba00dc5658c0c504326f5bc1f73bc7a62ccfd08a34b6bb094cd28c4e0d5dbd3850f16e27179992d488abea37dadd6d88d0f61cc577a5f49fb736ed6ad0d1be7e2d7a3a174c5eb4bf23ab63b875f16942483fe901c2165f5e6edb278087ba323e926bc2e1cc7372f333153c85c3bfc16db669c49cac6698161c133199670594afffaf28dfd898350a4d96906e193b15223e2b246f7bb12579adb79880408bbfa1007da7586c7e2c6fe53ff643d1f111b41a9d157ac12be7f4f41f244f15ae3cf7cd6e80feca505a2884b8a35a0419290f8cabb59aedc9bcb44c87d757c4e7c17ee4a8e46ab212655457047f0420f43be189add0514d372809c288846c5247535aed29d0f9f5736a1caa6ff983093c491074bfd927d08fb8d162cfa6a133b390701dc79a38eb10e48a9f83b73d754c04d801f375c4f3457d53e0a748734adbea901924252e0a322451db56d97b9a94dd65d87e96c02a9abdc2043f57edb8f266aa8106043fc29d2d0a033c731528c0260a0068da4e3e9c845feac5f10df1ac449fde101a561bf61fae98ae3c90762801ce3c2775abee029bfe983ba5ec4324f29d82c99fa4d4a8202f9525f40b31c3da0bc15b1e456e59bb5a27e84ccab34cabfdd00c39623b6ac658afc023143f075da8cdc1519dd3502a787b7642120d4ca78a7c400218262bf1960d4c0ee797528368d7b19af37608369dea29394d1b3d951eecd65ad54c473b25e3df3c63328e80dee39fb106b3e5bba7b301c69759a1ebb5e47bc7002d6b81a183a48a66b8f4ab2ab02db22ce0f029318628b6ce8fe83e17700a1ec1214e448b20b9082f9382216ce715241dd203b79c0086db2cb2ab46ffbb980fe0ca0fb9d875e8371ea5a6a99d32c6803905be2dd506b7cbb17f3419e91119aac91813108a453f1033b77349d521367ef0cba1a1d9c5492ce0697e70489583096ba9b3a8d8806200e76e30f5f6c1e5a7c254df8a934f9379272c25d632108cb2b8fdb23a4dc71c1f926206427fce9101b247244673ca03a4c977d91ffcdb9ef448a6ccdb7dd2a6c9a3d6622b86315a432d37eed93f3d3fbf8d3d4c9aeb76db15647ae7ed18cfd8ecfbc1cbcd23278ff7e35dbddbdef36f69ef882457bdcc400b03ae84eb743d43a730bab559968fa545f96eaa2b4158594ff9f5103fbe8507a2c1cf8a62935afc96242005f4d1255e4b2d362687197f0d948eb377e23e8bbbf4051ff8f90c43b257474da821570bf9dd9cf7559f15b4842cc7c80b9c327945c687a21d43538446d5fcf2f769b07d34e307c1ac672ee8133280e079c4f977728fc3708b1daccc05191d47dc926e63453126fe8eac9fa045920359dc02587a12ddc4640323bee834e7c0c07101330286ed773c602c27fe8810cfe7cb2ede00c39359bb6cb47e128432c092cee7a7a80bdb455e3e93c27ff0bad3b9d8ab4071e7926e08a6b753015557dbadb08779dcd9548df581bb00be826e09d94942faa8ae10fda617af4daaf7d28ccf5ff819c4e3313ffab1fbcb2f3e61e8549df190589a343c267d60d7bc9169215f03a913c898ce87a28c5f76ac1516325094d18bf08822d60a6fce6cae51e0dfb0d2f7f7ac0d7c9f0c96499ad4ec043e0d61279bca1e274366f82d5e4a7705080986bda10a5b3b817d22fd8d7b8ac05c720b23596d8884de7b36d0cf1f7b68560a24ffc459b10f96ee7c63921c0395b930f5e924cf79621ed5367efbdd140d3ac830ef3f7f1ad406ed39648be551c191c743884dbd1a83a02ac281468e7fd816f2a22359f617f85c11b5078f851ee39b41017fd053cc9b6367586cb2366f8883733989cb6a3e3cd7942e1fc6ea135f220ffde6dd96aa742385a00157484c84a4b8cd7d27afb6fcd92e4c0c4dbe415fc30b545f47fe29033a4f175092d137b4e19eb19cb0a642957d4f1ed4f575488d1ed228a1289926527bca2c07012599518421dbcc1a5eaed6859c3fae0b345f51108b262061d3afd2683059c96144b5306b4bcc95edf00d9aa7384f60147ade8531c0f677f440962ec8ec576162e79c2a61b38dc8e5bc2b24a63be9a81082e9c56787669c7cd6d1c7c3d1eef170385a4e59a496d2e792325abb02866300ee500a095a73bc7099229c952643e15b4a38d79c42627dd328b949e47fdf79cd72441001d75ecd9638dc029ff10982b12834eaea0d08819e587ec4a20d826a047941fcbc29b883f74584f282c60521dce7023573ea4785a984c1c9ce5022d3793c58c4f6395a4ed9b8228aa33d0a8ffcde055a69a80041e8f4c0731cab0f1936e598c15d5d4de8a3f62c8a19cf12485db460c9c3fbd2995181486251b83290ba8f15b2dced6971f7d76133bb50cfe0c89c8b5ec6d8dbba9cc208ce3831e64bde9961dc218801185ce850d80f7afcdae1061798dbc949aa031ba4a9acb3efea030fc96e2c6e2287853a604f2c6b12cb5e982e9f6c997867240adc348e3ad6aef07ae48c291c5e7895b0b620108a426e92002c72762c666fd328fb1d6b516dfa9e825b9a5c9ee23dc158580bab3ba208d4746cfce855675d514df3a51767eb627c38b6c91b848a2581767eac7ea9fca4ca7cca0b2cb9ec6cd7a498395edf9ef26910838570ad168bdafdc85e6d37a483d44496e188da1a3ab365f7005f3ac2654b084fac27d31a55b630c29c814d655d6ae3509954705a0dd92989ebc8fb5eaf36e656a6853b5bc02c47c97801b7add2d7356d834902d100744621081e830c208343df120b4ecc8f7907ddd0a6395bebc881cca66526f0ae6a0a15bac440af158d17eedbaf9f5440ab95c6a84bae36df5766216f071a7e5838b2a119530235a188cb262336f1d5c394b78c1af10b1fb0a09a42e68525ae7ac010572ef1103bf0595b902aa58a0ac4a6e44fc7b675592552750765f16e6c4e3a308828e8c204c802d016ec56d675d7916ad44f2a998ba356dbe2f34eca370eaa90c53770822264b54e1058037b6471ff135e3f11508649ff68884a39eaf52457aeddbc7e565806fbe277748bcdc8be1aa09bcb889d0e4ca9637382f347e111f3eb914b89fe27f20dd0c2bf33c30e246a3ebd04bfefcbe899b85bc2aba7fbe4b75612a01989c125912d936e168a9727d22ed4366485f307774125163a8a8f67177e4e2292e32ffa71020d0b7076a03f5dedc7878e1b2a90f74557e27ad9e70a15c031b06304081049969042bf1696bc563c897c553e437362af19e1d194adea8cf244193eb135ba35a12d5cf352207ab8baec13835ccc11a405c1183d12b89ff8353ba071d5193a3d16781e30508d24d96bb66ba6d1a7df5a983aa9a25a38b40296fe1b16923e40d53a3f2b5f51f563ec8ae2d9d1c86daed523a7c7f8f21a0852a5284065b5a045b8a9a717561d47aec467613f3e5eda91a0b144b25f9ed0c59c8c9525b6c42925e2ed698f9805326589d3ecb0c85085c57bbf8524e75e9c988049e7565e2d0cfcc155f5461d5fa8ce5fe1119c6d45b2b7a5617112f72f61789329d8586b76e9455d123f371d8afedf102a43308f173bb67bfd45d7897830f67164b8d9ba99e857fa24a77649925592614f2c785db19a2f6f73da9e2ac0403d3916a1422e4b8a2f6b689803db54288f3d221660e8ae8402fe84fa07bdc4f95abc80acd0fa93a5f84758f8a81bbe9acb7a2f161c8c00e5148822d1ac75d9271aee00426b917f31d637980dd3e3ed260dba7a96009d01e21354e9d7c772490fe1dca894260a210e7c7e1a94e5981e319faee51b333c330916e06b7e345c28efd64d303be0ef4b13549b0388becec31b77c35b9bffcc947cdbb54682accd571d6bac543f6a9095ea68df8191f7191aa860fa46a1420f13a320510162ad63b8c8d3879e6046c6f8674a8d6990217c4c978ae5196165fa9cdf2c23bc5363271407e7c38d72e277c3d55ae9d3ad2c45070372227738eda0f1201d760d6ed30667e115f5c2bd9cd9eeaed4413a86c4a897a0b319b885e7dd94add8aa79d74950f77b8aabd4436b2384ce6a212f39f1d251ac4da2819b18d1e7742409478a28052d99af5949056237c54c1a701d9fc5ef3f214bfa64f4f37eb7c84b4290441ce7e51760b5c81e178f934746483ace277ce5ca08a8b70d2f8a45832c40c88c9dc6634f8d507b79e7536365fdfab3d65402fefb512bd9a39a8e59a459cbb469ead945c99f806c51ed4477fd4f6e96be114143a72f3f0bb8b8c4487358bb80c727819bf78deed74e3d3b36c3f758ce25392956c591e892e438ccc5a5c390026bb3cc549db8a1579d0f769c7a657022b1390ca5597c297a34db583ae51632c2aa834fea2de8ed8993da25984851dc0e9b9eeebabf23adbb38150c2db6dc023de4178af6b4a60ace6d065d82c8c3d444f90cfabab9d35be44f8aab61c91096ee9ca1229a266fc3fb6d40f58324d70482b92372c53d8268e331a1cb3815d7e7475f9286fc06adca7cd5776f208866861266703fbc5bcc0bddc60fd5e7a63547f9363c728be9629e487dc46b8a54bdf65c6f0b395ccb16c7b5525c84a862f4f75180ba8e9f671279d58f5fca869122aedd00fa1f53e9f15a804f4f19a10f672acf1e384a4b3c5106e888ec8035b6c53b4499212ae12a647ba0954ab11f2d1bde109df9f460b727dc85372acf38e10f3c3ccf1c9bb5e05ae9fd949578053c7be2138e0ae0eb236af911be584dc216fdd33029dc502730e974e05268854cec020f24819e1d1231ee418bcc2611f55a0a907b71e6977b19934d81c73d7ca419acbcb950c5b8c8f6d8e16eaf459cbb5558ae65fbadc11dd2aff60d4d7e64a792404f16664f55c9b2b96cd9f62dad5a0622d0b88e7cdd8bead86f654dc89aafe3f73e97ce1443b7c77604f4c58fcc2b2c88708ede54c8233f3081f8facafe9fd4783004cfae3db6d1097c94a3cedc2a13ce4eaea043be97fc4820bad7d4805a0b687cd6514d580739f2d2d3c822b98712f70db41c1e3994bf99c2478b7fd92e600c442e35e0d091eb879ad2da0307875a308df943f96230c6aeb97d2c21155561fdf23e74d2cedf543ce669e9ad03ba998eeb47f896a3e9362032a01dc80636af3c6ad245c7f323cfc8241847222fc9d01ce49130171bd65636e64b9f5c4b23662dad2d8d038bfebf16fc1c73289b23631c9838dd9844033ce0b5535b2b578cad5af9faaa74659d5f0418d31ed9234cc3d5d0826aa2cd6c6a5ba65aed8a08893f9060aeaef8e6ce09325c16a14fa4b0d84fea664150b512c6c494c4f3679bd0980baba694432240df1fcc82d134d7b15f4fecf674bd666a488af40a14036f2000e42e0daa1a4aa3c866b465754d08ef4f777c1e628c2a4c465a2492ecc3c0c6cf41c17aef08b6714871b951f9816cbe426a2cfb2092132a6ba35eef628d0e0aee843ffb48fffe1f1df181e25922b7cd9f2d6e0aa27cf366a94b84f4b34e76c712020ffb52f32469dc5689a6356b9b5865b14b5f34d32c35f059011f54adfdc3307190faa609a19dc9dfc3ad5770195fbc137528c5149ed12ba9671cd3908ca21686712800deb3f46f6fc09c3c621580b7aecc84ee51aefa27380009bdcb167965f924e3165517a2875b9f31b1e8f0b7a01dfd29cd899c63f61a882de67a0e0e64fcc64700071f324bf16396a2a8a10d78976f313dd54b659d94aa84ff5e728fd2f7133e17b1e34d8710f74f1d5b05c1b22ce6488f5c0eb53a6cf6832d8d9df1939db9e1f01e5e652da0235feb25c83410a9a410ab44c3ff67874176a3a2a9b9652a8379b4092d0893348ff5ce2649de3e93e649641ceee312284a1ea7b5cce90a3be2d51bc1f65394d75f2998554d0843009cc4e1431280f8922ac4c0c0c614a6428d678a4ad83b2603c761725d7c34244029dc8b63c6982f002355a7434922ac80757d34e6eaf58b2ff2460860996d37ebfc2a5ba3f70683f212dd2c6e01298cb8ce5492fe1b916a3f464eaaee73768fa0f3f80d38f91de169342e66daceed857ae0b46c2bc58cfa3e81f58ed426d629a4771a645093529297a6199c40eb11049f5012e7c1f03fe5b874f91cce61c85da0179ededcfabf1077beb38336472f4fc73ae09e0d7a6d9a40e0b62ad5ae50f9e56b782f637df3b96d1d259c6a2c25588462abc05de724d9c02a36fe31b610438c458c01689d0f5035b3e3b15d5f510bb7ad4dd3d11fb65a753db991c80f29c9d684009840a0735eb1ad7b750e34b3ee3368df7f955d2907ceea35a276701f4b1150196759448093d343f9a956409b3b2932b2a6d1e9c50be0dbe0142fd2a89789adcb18097ec84b113ca72abdb9df6e3b33d439a0a24dd4cac46099f89c153c1caaa2fc197c27513220282ce69f800fc4fb663850167c3ad14bee138f01e1952dd5250e7fafd3b3755a636da283d1d3e94ecefdc617dbccb708e812efc207ce0f329174d48b23d8964801f5e90c5532de7460b24ef9c6a8b6a15d4e28adedb4bb44329625ab9b887329548328ba490c039e65fb6ae774e12548ced1af81be1920e8bbdd88a84fe544f4369db71a6557920c1c4e8a139e0fcc38137ab23bf0acc424dcd152d34d761df337b6f284d80cd0b0d7e7f5b35e795a5cc77bd2dc9ee0b022a188963f18caace01f281b23d3aa1ffe83845697951f1505f0390ae4cfabfe212ac29e0450216d744654854585c307054c61f910f0771ed26c684dc5a03e12423a68e5199bf56c89d39ea527b2e4639c330bcf7c2985ea0be6e4364f50794226524a66f158f3271696c5c7e6bc108e6b8c31866180b675e683c9acfbac2817344a15fe7a98e4158b6a262ec16511d7be69e531828098d729236701d5846db661d5b1ed596970eafdbb7039703d641c92de1316f54deef9a281534265c8062b10703c830c35dee1810b70e03804c6164058522e5c52cff596f07e9432ef5580ed37b7dbe82f33e4f78ef1de0e108d27fd6a5149fe2ceb30fd0b86a656e4749a3dd648abed2eebd166fc14ebb0e56be89b1b69fd8a13700b00c60a8c71a169bcad34a914e03d4e15c66670d876487aeb96703be04dc5b21e5c202ffbd015358b6d460a535d7ea62f83e4a590d46734ce7fa4aac64281457212bc1f43ad7feb4923d57e7523a4b2291cc39dad24fa5088193c3ab9972f6f17e2ce896130016effbb67356677097b72241146f4492ae5dc3d88b9367630a02a030aa97d334a8f7e6a2fb08dcadfa263045a26ad9bee87f011db520bf88709ab677a979d1b404abef7d50ea30700a0e2a509e1020e3dfeac894a42bb32f022a7c9fad03a9ed94a79394c08c5ba20592b9bea61692e731bc8e8f6267f6086a8d3c108cc3e87d3a844e6c2374d4298f11121db0eed7735d1412d832b5ff85e95b39fda27a24ea2533e357995e11bd09f42233f157467fb1de44d09b6a509ce2dbbf42564056c5a31a8b6d6858d1a894fe1b04efff6cc4440626882fd75375dd7acc19f24c319cbdae1fa08edfbf7f7a74907cef63b61bcfa143ff8a31d3563f147b017042b770f34faa3dc3e345db8d7fa2bfaa7f83884944e2c1ebdf031043f7589f26bd9a86fa979e8103ba7ffffd6243e5ded5e4b8882dd85e761989da335f510ccb5860458392fb8e03d602f65aef793c52aeff86caf997ef2638baf10e225cbf1fbb0cf826e47682debf4dd8ca835ac14bd3cf35fccb0effdae588174112fe15651a51a2410c32f387ca510492f605a25fff8afccba36ac676442c48e24c8dab99a70b3b6807cc15e26c639b37a3e3681473a440fd68ee562b8c7c863909ff42b855abcceca2862f31a775bbb11b3b7e79d0184cb14533a458871d8aa64dddc4ce9513c8acc8b8a3e90664f8379884a792136f9684010ceddb0602426cc046de704ce469d270175c187ee5dfdcf0d101641792afc1650e602a90af16d3ea8e924a39ae341f9326315534ef18f630bf1cc5064f74febd0e30f911468a99afffb08661582e864713a956cf611ea71a305512e09200e6b5c424436f21973e84dad03fbc9b9213ab7e3dff1e9aaafb03f91d1f3dc5e31cc1ca0894d228ff1a18f134f67c42de61e5f4d3dfba027d42545c5dfcfb8534678b0f210608809a6680a5f360dbd9333d4ee16e67a07a4c82e9fff31ffcd43935ff327092fbd3e741125c5c640c0ff5e3710a4efefa8c62b8b55a2dff662cc816e75f06fd68fb55270b9c15c8c8c83fb9edea6874f85be14b55588d7f5976dd26b6e3093d815f6a70e13386016667b9443755bc632827c1dfbee24d85cbf223d30e5d549a0d5736c05f436c0ed8c7cdea465ae55ff533d91060f12465c7489ab40d15cbd8bf9ce2e8df13167b2a8a8924f1af48af9c1b080fa42923876d501aa50664f7fb004731f5fcaa1b097e94b72b9c9600343a65ab67db69d31aa133633016d68a2d687b2c853f4f9544d3e7ce4541214548f2ef645b16503fc593f33737a5b253c2af223468e76125d33b3242cff8b783fbd60c874109ccf942b908dfd83d9300e611f899bc79be85c8b0f0ec378e57cd62a06c6969e98087c796538661d901ee308af82f4f6a2ce603d2b3d9b4b9af5d6b2b0cc024fc8b0488e4c0f978035b734297492651973beba98bf65c6aa1224268c308f8af4caee4088225791769afab22ae12cdef2a6d96c805e16bcdfe072904b7e65585a0ae35a2a14f09a78f727a8bf524985eba92dd8dbf3f925603dbf40654721ff0bacc97fe04e8c4f3a502c22c5fd8032797fb86efba9dde0305331fb7aeddc405878c926d2ba1d620f71230f9411c26c02d33e3f831d02a826f18ffcdbda8a4665530ea57fbfb46a3488aac757b6d0b939c381007a1ec83977500ad7be56e0fb365dbc549c0bbddd529f8688db56b9b9e01269006c478d3148935c26b18b07cbff9a2535a67e300347538f96b5e0048208aeece01c316a2acd85467187d9b7b3baa6b966f653f46ed876c86905d43e07c18edef044e87234febc6efb907d6a52222d6d5f3685a645d7759666fe884375de4ed0fbbb2d836e9419d66a6d3ba14b332a3a18463b5910cd22f75e537f0501246b7e1931dd464f952e68d8bc9b3b5195a9ad1af489715c565489f304df3d91f9b668c040897b90000569b90cb1eda455cc2ae03f83a2306856bf3c3c264c7e4a86a33844ff63c6e404ae9547e1f18a798db06de7c6364a3ba6c96287130f1786ca216d3f9fa4f05a6c3d28fd20e65b1b78f2a9f4bd22a4e491c07068442e8d4cdca5d977249f4b0aa6cb311c0df659d5238f6586e0d8f4c0e3d2daca91bbc84ebbb577bfcb6e5e1491853e336d512b75e503eb93445322ae38f92e8b4a051a4e0b514e74c2fcc8f71018f352d538bdd8a86578d05871bb2d747301ce220b31205824e5c0f2b4b80718f36e6bbe7dc9f37ca1965a57b0953aafd6d3252448491a09dc01776bfc957e9bf4a36f21b42d08108250d2741f5e550554776c51634bb1f7aa9f15c83d3a67624998a6383df7e7f14cf0c6f8bc9578a000320f71c01d4bf99fb0ab836717383de25ae3654e1799e6a418a818f50547af4eeffd82bd56e517ee86bb29670959477c8b471bee4abe7993d0615c2dc3910097ad45f4fd7e0991006f334202dfd6da28283badccf9564b5c6cbdb17f88a2af0a9649ee0f86eb84bf81752e16a3edd8c27e8b8fec2e55dc27d2e7b646d0e9138a9b4530ded4e9adf8160b03c8be86567c28cc06b6e7be99a3a1b9c7d7c49cfb5cb3815e6de2510f684cb758cbb11c0f181afc5c5e07f0aabd1b9d690bde034c1b83022b7c7858636b820c015b24dc986b748c56cdc10bedfa54da78f0cead27aeae445d7d0a35fac8a6629ff6d2c4481adc6bb090b7fee6ff332fb68318d88e2642e8e8c93ddc854e1732778d48dfcb8c5dc087fc8007745d9b890a8b528644dc011a9e3dfd728f026f48a9d344ed42e533f1e596f9f2eb882b4a91e265a4b1c78932ad458e231aaca92ca2790372b0bfe2293f20e84850ec2e1f06fbc3046813cae0693235224067fe31c9fd486d0c490f8d8a4e1942e701164d07c3a244ba79103c1d764b3551a75ac8c10ce1271c14fa70c057f7e819972575c22237354316727ef3d7303d1b20520e1baed7c9730f0d76f025b1a37141517f3dcc0d2695d5cf4b6cb13485420a09188d62a265abc221d02d68e29cafb12ff2d3cd2b4a2f1dce0613ee46c6d30fd1b90c81f52c541aeb53607d150d45ce7fb1b2ecee82529f9f698dc8d630a03919b1a9acf1b51bb9173ea3f3ad29de0b3c2578d905ed50aab04ca248ea3960ada7b3dcac153e0f52f1d271b7c3405ac1c744ca705c379ccc12315be9f0fedadd7415cf7f94eeffc9cbf9988d182161962561496c362a3026038fe95b38deffc27388325111f167f6a81422767473e0d67f8d56fd3a159ee5022242d87a3da6bba93a8d821bdf1700ac3334c24e4644364323a131fde5b6880fd80eef5e06ec94603483b1f256807a62cb4333f0d92451a8f600aaf55bde1ca35cd3934728ef2f18d54a7ca9a136ff1e1376bdb680feb6345d0f544c5325563759025b16c964a05077014528e6a6c15ebc3f5c6aed596ed99c3cb87379bf9ac3c007d8614a4aef72982c52a9da9e4947e7315aac6ac9ad93b443c74c3cefee50466e752837e2ae843eb1005efef2b148cf039739fc3f03d2db2bd14defe39dff95f34d852aded9195da475bff7d8edc7f3ea76c6860457ec8d7081b37d27af653db5ab35e39c7ca49e446ba852f53293647417950adb3d6a85d077c8cb005abff422d3e2b5851cb8c38d5173f71ac0e0c6ac3a67de0666fe45985ff92e7b42112e4f49d3f8f0c7c7f0645b9a5ab78e38da63d7a4a940db0e63c01e2b40d7524dbb60bc470ed50cf558f896032156603c79de15fbe0c4b1461b4aad5484e84fd0382c3df3e6847ff4db134c169e1dafe1418027d5069d5564b084e5cec87c43becbcf3ae7b9089da81030e357a64385600b8ad87e27c0493bfdd237b153e5fcf80b7ce2bcc2352986bbcd9f02813243a0592a32dbb7538f5b67074413b0a8fa84485d3c0b0940024956b41372f3bde707293e3f02487663bef465bab0e26468c1bdcccd89103351a71741006c68c1ad4c8e898039a1b3b76002346c61b68cae8b8039a1a3b7610036335a8f10acd274d3f493a195d7f7b3777f4f439894e26f0cfbea522e75ef099a9941729f66060d73d8d10c7f1d7ca23c16e13711cc2b03074f4944a34c2c42b35ff4ced968264231d38f33bd8465c48aac3871478f4f0cad931efd0fc2e17569156f24ea2cb0cfb714423b7e8a1945b0f0e26741bb14e8f4b601309c560b553176552a596845026d23b40d14ee07c7f8c3c7cf890cb823d462b3b1d582296e0239f9ef2f8b60e0373b40905bedb89d8911ab781f78405b090e96364dd798f809a6b457a763d272d0403d790122152fc4bbf6c785cbf400daee151d99dd2664683933d2e6a7e916e1810fb3ee3416d08972358a1c35c7b56f862e1921e4bd561cbe4fafc9557231fc991be26c975077676e93918d046ba82558a697c16f1267756ce6814791e9993a18426d8de86cbe3bf15abd8bd0fe1203d486dc8a6116556312b76b873bb9cfe3dff3b01c092d0c2c1e055b8a5429a223f2c2fda932b93288980973eae842a0dcbff777010e4201bfae46e83da3060e2bbf1369279008611780c092fb1001033a6497a5b91d3627826b488f7bc048bf023497ce8b25c3b5b6b61a9d50fdba5c15c086d880e8aed87990bf70b0458691e07bfb7481b67aa8cd935ad9a65c0fa55ac498fc09c93b8a9951aab205af47f823ec2adfb7dd1fd3a4e1229c72a7223cee4c30b25821dae278a96ef6931d9c15cac7d255c35bcfd41bb8c8c26268a268e2d56933be623af1cadcecfd1291f4f5815c3c76de58622c0ae8fd784285e421bc9eaac42ccdeb1a4f02a39ff2a0bee13eaaa849323f97f24253de9773afd2dbd63430be03edd7e49f18ded29c293bc564897cdfcadb4ab83281f8311c7ce16da88faa3f121479f8845768d7b24c5ce1b863eb12398b7f8d3e71a86c7c17d35f4ca2fea6948f6b4f629b57e2cab474750cea878d873b86005456c7eb10fc14c2728326cf29a573ee231888844a034c82f36aa84031e69607e112dddbf67c934b4a088ef9a9c46a9889f6db45f8c3f0e05517e934aef0de649aa463cb5619ebbd2d0ac422015728708204400cc568791caedc801c61a45e936118f7303516d7b4322c2ec17e5dded9ccf98176f11fd17a09b1a1e02c9fbf3f82f95a0e308bda64078e90d66fc044ec45358007726bbcc7cf2fd9db5ca673601a958b7114af5b0fa3ef6dcf1f25514bcb29666f43d7de1c6bfc95687c635fd41791d2e817821867973943b3a804b05a077d6886b05815678747a30061ce79009c5348ac22dcb0bcf67aa3262d7619c9aaa8a14faf7c610c543b311f8ef0f71e9e8f407a01712276a55c61a08915456f648d5c942ea254671dbc51a39b53d22252d3a540556b4e64bd1eba4c5294336515c62dc3169b95df1a8ac6152a59e76020dd06c0be1c93aab1b7f92074b01e4eaca9bbf56a603523fc141492c396db1739de6d07cab0036115ddd741a5d92aa0c15b58fc0f07de2e9e81e015205e88840f2111df66b3da9414bdb8181536d8b170f547330490702dc82fa8d3f03d3c96971c249a9f8667219ac4a4ba0552dcc08b6d832d839f208a2b94b138bc9a6e61a2ebab4083407c8af8a286f7e515b886bb72a4e2eadcf092cc2a528cbe849426dd5e1a212275c871480d19a9456ae947e45b9fa39865b955b1441e89bc1c63b1e728d60cd2236b60522140708a26f0836cc15783414280a90d7d0c48cddec8291b14168cdae91d482ffaec6e50eff893246c649327bbdc01cb500ee0b47423302daf82c20976d0184ce7a20ed2a14358bb7b9a2810cc06985598de9f3d0cfde25adb62a74fc0bba279a72b3f8c3375bcc12df50d2ac71253df840c8c58ef4cebb8f909dc5a52a7f4004f2eacafa76abd015a908fa6b0235c93fcdd700fa283ef554c0928f49831ed2cf3c4baaa37a3cabf6fe1d907ce1ea3226f9ecee2a7f16921a76d3eb097ef5f37dbead3cc3c532301604d5ce44cc0fa27a8147b5d03e44c0fbd4fd2ad17169f86df27ff896bb67cf68eb555b3c4db686c701df2f19c09b3bae5111340150382a4c8d90087863c5d4f7bb1c1b34221a81825efaaa48c04e18994bb5eb06f0c16ed2211930063bce548b0b3e0af3b943172c6f50be5102c79d672ad039c81f7551358d347237aad6f2e697472222cdb9dfb29536d08590293d773c8d7c382afebb8846c5650fd0c15be8ae04680b6b44e93445231f89417462ed05500062b9901ae8a6a6445650b86c99cb18cca218dba99cd83dc2db49ea1420f3594ca43ec635c07127bf2dd94ed4b0bba5d1a06ce3ebd7f3f9b719e62e60b21926137878411b0d84f751e24f6a3a96837b7c2a62349dff6aea8c2efcac9eb409a9b7f3fd85ba5e9789d4c2f9f1a7ce143070c9b201f2d5ab0f8c5c5cd91101d45707a19626df72f2e822fb1aeecb117e8a2f9bb16a6eff67d34e06390ea950b5215072b909db5cec71fce6416e64160c82c9abe1504e5e22d812aaa465f1aa526f160379063c42663930cd12ea94ef84bb74eb2bb5eaf31a8093ac47047cfd20394b34c116bc6fceb1a9c66683ffa4cdb1843722cd69ab55315d9c68d0c622fd6ea243ed2e58dd7d8313f110354192303183ca89f8e35f2a8505b1ada4bd24f2485f0b2bc0f62aa513f12a1136a23a6d8e83507f2ba91564488588c78946f96af15be50964254b28ce5d829a2a87ce04755b902d64794332c389d4e48d208d84561fea9a928dcdc2baa1005cdeccfc67c0fdb66b2cdf1980b08596dfe8d861799f57f55dfef20d5bc26715ee108ea90efe1388419b1e8c67be9b3abcd184f780831c9e0c576bcaa7fc3b9668e21fda1e2adf18facb126ed21cb350aa8b7bf260f484d0d52cf50a253b4d0b1dd7a12a78e484054ab272704f6bce849a416b5e392026324f136306b64fc4970ed0ab3651c992d94fe423d76206cecd1079edde62eb0d8ab32c4dd61ac9f414099bda4ae9d67a389f7c0b64854cc214518be72b9f374ee9e19332712350be2fb62c643174e9eb0a2dce850de02645b2be85510b1b97e9d01c4156b893029fa55b63d04a99631338562b415a0a7705f15e2bb949d738e54ae90e605cc436728840bae69132973e4ad94e1d907f0f66365fa9baa38115a4b882c0215c31e2d32e982fa6be6f26cdd15532207e2de3e43593ee21ce8514faf00efb970fcf8f4dd684dd4e2696caa3b74510babd8e03779398f8813b627fc2b2421aca4464f8f9f8cd93521dcd41f7694bd6d554feca34cdb331578a4d22a946aeec81c6c73407d52f5fb4aa9c1f7b1b3b3c30053e935dd8aae1e19b72bafff45dc1f16c272f952c3258a589517f144ab9113d1f7c41a6f009de5e5f0015a2a2766ae042214dc974f83690543326449c0fe8424ad1a4687ca937819c14a48783188c1e621c793de82107b820f940d0513b3806d2ec4b9f74c3c16b9860f717fe8fdb462b48f72064bfd160148c849f963e987233cd3e5fc01dccf5ef6cb588525b241afd3f967ec0ee31acbef416376129c19f0fac4a8b45d0f2e45430954dab8f7344900618473e43091008d2d75d5ca92684d292487e4dccc5b0251691668af522ea2bc3f6a05bb2511fa1559438dcd27640bfeeb1a080385b08c01f394021f5d1da15aa940a33f19957267edd032c842619b495f9578c4fc9e9eb57a563e1f6dc24eb5fbf38d189ec5789059ee2a66476c52145fabbe818294fa4b31e1c76a8f7739e84f0b476a78096ce15480dd4806c6c55dce45384f0b4c17dd28df1d64bb6ff09807ed15f3d562e61745aa82c0910cc2772c95c2cb6f1b98a67380cd2922ce328a699c4c957ba746810ae88f057bfc2724dbf6be8de43a28a2ababbf8f0b85e683f7ecf219d482fe82d9f138a4a9d554bae3e9dee327e2a4267d191bbbc4fa6245f0c2016ec2e192760b8e14a9eacebaf688186fc1ecad69528bfe9e71dcd698ea905c37e48cad9aca90181af48aa0cbe030d2b86579ce903f80ebbe3bf1fe0d1b4e17a412e6ed52acf16b67492df92655b4735d253beec9072068ca3a03d50cc37c28ceba2c0566a14234bc34fd81dd8440af8ab2434a7c0e704ef0d736008e2b1a226471f41b5c92562a6a1c6cb8e5e69846e191d4b4d0a6d9b8f064215e7f00631f38a8c1b94b2f1ae45f31fad01459af3df943f23c30a5504a6f3858dc7434888a4095e6038ca39e8b3fd800023cdee6c7b13e749a123cd596b29bdfa8ebd7345db1f04420bea6b8976beedca60058454909c3292da2fc36c1657cdcd3413f28dcb0bf0ac1c4977a83eae65eb92f012c6594288a17c6c42c4e9faae7a11b3f84636fe7a8b04839395a9c8eab4be1b383e3f9aef8d7c76dae041ca8c0721b90862154a2b5d3d74d99cc75d95b70ef4ab32779b9a53d6ef075e13ec447c2be80f8512f8425c92dadeef7a28f0908d1a6d08db6258556f41d94a9323dc210ff6cff3eca05e34ac5c8fd3f0b3bd4446fb8e3dc26bdcd729bc1e9a68585c01448d18a7bae6c1bf0613ebd9ced1eb7577f6f0fd81f7520f9a066f41f7f0ab4a9613f1400a3e8a35ddada84c2bc0821be8cbe0e2382e28c606d8679001fd4bc918ad3a88a69d0d1af28cc055c038ae45b4b1c122491c56a81c51b4ac1728607b1ec9c80e4c04a7857eacb835219e6ec3c66054cc102a3045b8aa4eb1b356e9d4102bb1e5913ba6ce0f67b2616a2542f80b3c61de8902a07aa54cd1e07a87a767e5c91b252aed56b228bc23b59345726933cb32748e0c70bfcbe14f21ad2b08f5dae7aa1e7e4cea65379280d88b81aa4f2bc411661c63a44239b92a72d9f10d47a611edccdb668299bf091d91807123081d34a50faffedf0f81653c369dab16cd8f7e0f1db35f02ef74e92fe925c0d6a71103abd81ef23a3e3bdddca4e5fff7d6b210e5078ba0228d3cbcb6d492e41e77ae763bb33d9c6968e2499b4b1b1fa3bf3dbfc0d057e19c5601393302f26d4c31b780b38dcf96c0f2ea0ca40284371d4f452148f5bee19e003e49be972482a71cf3ee3d0ed2f822a27c1742cb56498292670fd7db0cade3adabf969a95abbe36eccd621a637773ec03bd61ba17c211721d8550509b50ee09c2a82ee241f548b2a8f8a1436a4242a2bfc8bca4a5871df571154ec6439b1940dc288652ca9d132475d1bd56bae247aae18a62384d5870d8b13e074c05daebc0f20eaab2455ac07b594cb207509f548d1ee80dc64712d5eb5c30eab603dfff9000cb05c0e2f5c409fdd3adeba1c0107950411e5b62461444d4988a1f21ca5e2c8baa477d79e8b8f7ffc661611f872d9b153bf9b0df5a7f3a1436f3b6106b885ae0a4383cdec7cc77075c371a63c785db6d7d2cdec796a3e97826c1018ed91ad85c916dc182b41d362a56ba600c0532f8b77430a44cbc3937dcd474499fd40cc679e35611a1aadcc290c25d7bf8f077a0c1a188437d16f73d5901f4febadc3cadf57a29980ac2518ca701df4deb3c09b74baf5a928e821a06f0143c1ddb1dd7e1b7c1b39d49168440e54e14edea06b2e09a43fb832f765135353c907f11dba2b879b56ad865e99cb33c76f700487092519a401b4eee87157c914bbf21f51bcdcb61db4c17409e3e5957f54e3d6b75c2bc310b3762d6a5cfa96cb92f4678171e75de40346accdb05cc9721681ed032531004ee3bf3ddf3732c82a048c14ffa6805d28529b27ccb7e887efc25911460dfd96ff4ad85d4017e0be02fad1f6cd3af02dd1a02eff49681a2a903f0baa1f2eeb25e698604a54b5d222c5ecbe46aac9577343ac99d80e3c2a7028b3a9b3aa780c3397146eb68cbb72fc515d87d1a46bd49bc005628a5bd3df1d81a931e6acda3425117484452a1a035ad202a2c6a640c020235e215dcc69e9e40f46a8ca06a675b154bd571f930e45b2daffc877c6c7b0eb7ba8a8a56a69e71e5aabca68ffb4e938e65d1e63ca3e4943d68b72c6962acced480339199bdd44a515a64a796036451f565d7c811dcab1c5cb66669bff4a5b1c764c4cc5cf1a796d2ccbd373b12f3eaca15fe88d1818a8bcf522a84e6be39a6300c18b591a76a557db0b2f8a68fc832d334f64f0de19360344d2545b2ca89a141432ab6f9bc2cf290397f6d5b9d678b7494470e532d407317cf73a410c3c76c8350dfd670c3c589b263e3c6b91bd3fa27b42408c17ca6d968b169a1a57c894953454fd9a7a17f7b88e144df40d27320db5fdf1082117ccaea6e166d2edcaafc51f383678d6123023b002ab44d5fdd00859761f374b4e1996f8378194a86499047fe4c8302c01aa6a1e9108964fa0b1d7b280d0515666ce239f3aa360dbfffcbf80289e390c2dbc1e61a17a5329c84008cb3882d2bdb383e4181783269f259d76ba3a8354e773f691804166f483e6bc3e3e329cd8b92cf6a4ed34a725e654477030f02519114ee57327085393c410d0f2623d15871883798b6e3ef99efe05cf62d9265fdb0c0a59e12be93af03275c3a7a00ea5b5f843ab0ec70ce9e664311d4b7f0f2007a6e9c06a9cc0654aa0c4ba9803f5acc6e3e0b5a9e23fa88685bafd36b42598ac467a8aa2cc4627b648f536b5831138a257b6b0940074a8d068e2af4345e692191af8cd89fcae4c98d34974e93333c0b370e20111cf09e3b43a72e62a8145c7214d3d0008afdcdbc1fc34ba5b024ff4d312fd2d26d806dec79d154544eabed1a209ad0ad1812edbb4f4c9504e679452cedffe1982b8f52d9f96000489a740a104193b7ac5e36797a476b8853d19fd23abc291ce3d0516e4c320050d303199131e85f1999089e09426a86d37fb6361a413f5beb184a4635079ad353643e181b9a989306e54aea89a483e9112f2a78aa870f9c6bfcfd67b8370fb2dd80588b448f36114c48110210309624f94528ae5703be51a06960e24063030e5495061d7b74be7b9af67f0587822c31acad0caa4be918e37fcab497ca08506c447f21a938e8d8d49ea07e7f10a55d87d1eef0b8fefa80f7157e1d538848c7e18abc83cc05bc9cf687ea2baf4809dd213d7a70244e46217b824dca4107590af52f3de8031a07a1e8787f27d73815f8b4ab81e845cd8789d9d92f2bb6e137231506732402e242e946ddc69a9aa99187b4796197b17f2adbd501456638041e6d51aa62e49898b33aaf4160f63ca51f258f0f2b5073c9c0617d1ebadfdf473297fa6abf253f1f9de95758e8957200c95d5d018048fc3e109c77d1ad0bfdc28dfb70b0123d6d4e5ae40b002f53de6430e60b9c994cf04163bcda62a0768425fd3cef2dfc0552b32f30c0083083ed1bb8178a8b2a1ee0a4005594def152c5604b3ab65001610ce42418be241eb1e7cf5c59912b1b368426be53b7be0b05bcb5f4244c2e3244ab9797cf1a5c0f1d46e95d098d8699cd331a9d6fe8f9eb8214279198427e37c87dfde7a95b61916878ed1f2588af179d9d81eeea3cb0518373af5e28ab2af36aaebbf6a9b0a62f9687376903642b29acfdd2bfb6f1d1af33442a69f050e48120543f678e2050a885f8357cd1a1405e61285e0a035fa96a82af992a38e9f45f6f533ef478645dd673db64b41b0f62675a13518a05a5f2486a08e7eb6b42aee504fb8682db8ecb432233154a64c647bfae541f3a10a2d560a4cb849a5bd5757205b0b2fa582347539d78cdba9fe2a42371fcc21beda2a41df25d8900209852a29d2118129928172d86ff439cb0cdfd4540dd600982354324626a8b3582fbc37c25caa1cb4a5c78fab31c288eb8d16f0a43e2af08cf8fe01256aba0995d436be3ca70993a09c5222fc545b9104a39982140467dc76a2d687cc86c02f89213b74612d46909013c97a451813493c0a28de712faf47d17092d42eef0931722637c8aa3214adaed0e57fc5d6c737e6231f64306e0207757059ee512ece912197701fc9ae8beedd9b91cc16e3e439ce0e7c4a98a3714a138a400c8c5d1d7d154f384ed2260ef83955d1af8223748b392047a7253f3dee5397392c550d93e88f4981b96a5b68b021d96c5c8ada19b0f8052dd578832e0efa167a88823836d8e0560b2555cf65450dc5909b165653596f6884a6abce94bf6c001c4d3eb6979e6115dab116678f37b5a1027d0ad38168218fbd189ccbdee826cc8b8026e65c92939548cac73068734c050acd4109f8fb9347fe473b093403f22242ff615c7090b5e952e80a0b327c5ecc5fbd64089ad3e3b982b00609b6f3d6056f33a2081aabd5ec7e265c8c382cab1d5c805b73bcfce40cfbc444fe9e30c3ab68a95d85cf27fa418f5820cd6e3c24b5572ed0f91f2539a7b8a43afb135da2a3c70c70fad6d9413073af15588833ba02458bc81e76909ab2753c5ee0a878986c08e104119e57500cf824e88916276f0451c242f2c9ed77ca5cebd2dcfac6ab6d00ca89542041bd9de4ba170af5d6cd9d9a9077c4653f2addf6ecdf5e32d787487af6d3d40793e1e3ba3855e1de52c2cc72d4d6b47e9ed9f49794df26ce92d136b5a266dfae217d39a735d98a26afd6da3da3b3b5d921281b49a4aea37167d2e803d3e34ed6139cf3052f4040c59c2e95fbc2d215622cd4861464671760873666a7b4ba0191e71d4841da79846748613946870c8958283fda612008c16e73371ddf2992c61a046f5aa647de26b5c242a8509d2db6d9ae71d6816dad176ab5b376fa39a55f4ee0f14fe29625155e203bae819d614ef5fe3228403f723990c82e78da5ec04524ee7b3ce332175063ef2dad825197677dcb9fb4dfb241ccdf922d4d1d52c5aa0b3b7380cb96212276d663bc0a32f0cbe4893e3195094ab4609413dcac5678eec0727701ae7a75dd1454f8569619202ab7586d04c9ccf260018fa6eba264485d863cc59fce5be4e1b4de7dc3ecda466bdd1688a380c9ca9e3dfd48d5f16a293ef016c6eb208daf0238d05ea075764878c29f7874f1a62e2d12f5217592f297961097024edd5dd2c819f2df872b9d19409d9524e16f9e4e64e67eadcd4d426170ec1c8b4391efe5f44d055f46cb0f6be595d23545ba1a1ed6365f861c595ca730b2a4df08c84c48188a19c258d2f85da02c20529272c046013b12188e06c287133ddd5798fc577ae47cfbb4f1c2923d58ad9ed6d5c287436ddce62fea1ceabf0330b3f185415630f44d544a5e1efbc069b6f538a96d66f0b973327a4e7b5f368836375d22932262642629bf405bec5e715101a8f83188c5e45883b14003418143ed8c3891b6ca5a5c524c6a0712225335b8aaa9c2699c246d7a8c184d24ed5ef58272a304f4c6a337506294c2fcfdd81434b6e5885aa3481e5295e9b005ebb6e236b81547081af91a027f48b1da9f5a983dd0691bd3c681ae356d41038d851b918747a16bd9adf1ee62600b7bdfa1a745bf607fce82e034604716366daf24f1a506b5c9b89d961818c35cc0c9996f8c4182ebbdc0ce2f85da5deab31bb49a58d60d6bca81268e0732707444f6abadfe16286e47d08565f0e143d780442428f458f1650958142c2fc123c61e02a727f25f6d88e5842e0228d204639385dde7d6e941686af0bbc6a264b6415a4e27408d822cf8e9c2ad3c1615edb5860b6be0aa410b681e54a56d4ee89863a2de87de19e270ebe65a370e43c5bfad25e1cc14906eaff714f112b3a6fb86c64a62a5a6010eb4d086ea2a0dd8ed52d508b5ab4c5d50e8d3d86b4728dc40dc53881f93bbe81b150ca9d95b00a3e544e520a2c4fa028daac9c2f421c3f30110dd97376e08c1ac77ffc8b88c3e4a2d60321a4a71ade9dfdacdfed2a7547992352bb92639fa12277e7b5409a1979e74ff0eaa966fe4b0caa14a051ee981d020f4e6b251fd456867a34da6622022a7150cdf561c88824a071dc738d05d60218a4d4ccc77b2976501f78def591b73e6937c99d64c97701cb390befcf2a100070f384f987546f9bbbae275055b01906ff2f332528bb674d8bda2b8163b121482be0efdb40712fc08734b2a872b2c53e5268755ed44a01011fb84b56a7d0013f72df2e2f07f0e1cb33341e9a68e4ed44d9282c0db3216aad9c05b9cb75e80ae67515754a63a9609811ab5a7890489e512a80c975144fc88ef2280c796f05d42bc1be4f3e52d790f8a8fddd740b4f37c6a231ae4d45ea85936174a76ca79f94b355b491f285adb7b8510a51dd7a9ea8e715a08b0366a92719a3aa29d0c0fa57dc1e010d424e33475443b191e4adb150d7722024abb8dc921544b8cd32a11ec247494761b9343a89618a7b55add94049c741ab8e0a738a900b6afdde17155310bf789a11c8fe8562d878b3c479d09f87a6c55a03a90063ce07ffacbaacf757b8d5ce2934faf1c37326f4963380d585dc56d8ee27020f3d044004860e238beff89d8b4ef62030469c5b89299e875979fc18cfdd4eaf0067b5f8c730b5409e7a280d38a069813e244fdada8ed40a9b8640024f84c954d16fb887d6a860145a7d439a0463d55f896b06cb36d90d6135fcb19ff9c7ccc466aa051522d78b3a9d98d7c17b29373aa5c11f79927602d7ae2685621eede1940fe5ab17af0c0772f6e5c5c635651b6580c8aa1e9af82efa4edc735e2213d157bd43094ffee5476e5dc6d6c6b08e5ee96472e2c1749d45e45208c50b38d6ed4a5c42ac333dc5f325cf563303520e1adfc445727748acab2f66dda1352896b51ca75a21bbc5c60306e156bb8ddcd3db083c0679c1aca5c2b9c649a35d85cdfd192fd3786a91a43296a5728eaf8d967aefc0ef1fbf218bd57b1c96c554ef041a553db3aac41ca43e586bdc826a052fe8f6d992e589a8d83f98a498e5b34ae1ea6a81653be33e5669697190efadb66bfba713eb8725add021836d95668869b44aa12e6b76dadd201fc6fff015da73ed7f4860d7f914a0092869254c213bf3f0f793418eb509f52bc9e74f957edbbb5e82c61988196018422ba30fdcdf2135a5a4b2f46aa7ff7980b42180e6056e04a37242cc778fbe1f7a89b1bd22a1ba47a5a35a84b28f7790c35733e43e660cab2077724642a23dc74ee51627f72e8ce52502045b43fd61bc829d7b2da0d6f89f34724a4d35a4d27daf1113518115b214db407631eb28f5d817337f32745285366a6c7cbe447d9b51132fe2202f05170650430d22e7168309106542702f3b31b1dc607c3bfa534e72d3d1385cb60e6989bcd73a4edaaebb9873574649226c3d36bb7b7c1831a78b85593661d02b6c148a25b69e11c49390bd9183f2a2cf81294f03b8558c5251c9a35be2d683aed8f39480e301914ef7f03280fc2ea00a57c15da9e696291cab2ac948584c601b9276d3140a2d92b1c0fe01a44c6e1b0710897c3d8c1f5cf4030057d6b5cb4e72eaf751edfaa6f65c493c2d79190cda5427de4b51bb695a9fa36e0b7f41501ab3e56c68716d16f10b2d813eb79f2dd6810f6ad64bd5150c6a03e485c55dcab25728681548e704114923bb010dc84842846a4c21bbc123be5cc204c19c033406a7d02deaff668909a0302151cc0743d7e4389b6dd7a72d28c4790b6c69012ec68bd995a48d1b308a2656399f9847e19953066d1d3d01e516f8eab384d8322d8a5b50f93853053227a89dfdfc05398519e89ed2802f790777091a7b730dce0e15e5391762a342db296dcdf24f310cffeaaa41d857b1077a465f0cddd30910515bbc4ccaf98a670e63b19eb7ba887282244854762d3cc1bd19f93d5e1aabf5f67fc6c2dbff3ebb1368a6df5d65bfd0136dfeaf04ff77215948411a09090fcaf6354686da78b3c0092a2e9c602914710b940d43d722a490953d2b8b54933e499f4dbeb6f8829c4a2784043a56a8fa1f966fe6d2f9185f284055d204626b4bc45ac3dc6578123edac7e16299b68344beafb930fb69c2b318782936228891528314cd5a46038214e3023c93f182d8871fc7f8582c06bb0c34115f70d74c7b5120674923785eb39d9eab366a6e3136ed6ec581bb0f1a4e0d0386327116a626519fd460cf0d9fa14da8f591730249b98c9b212ef37580427672fb94ea0a44dfe893b28473571a94745901baec3b620ae8b6d125585ffe035a9a4d4368bf2c04d17cf68a110a6ea20e247e86b497bc0f22911a79920077092f0c9b3e67fc33e44602465fd018702d65ee5866fc639e44ea370660523656fb529123ee68e14d5661f9ccc0767f123a1284d33f970a56d8ec2b1a350eb646c782b77f305250f92f8c41317c3b71ccae75ffbba0beed3cede8c774b0cda97564c97987b066076aa433449263eb4951ec5d684fc6a2e72406c5477bd61c372bf826f300d58c9ed2db1be92aea31a84d9b6a3674adc262e0359783f260163b333f20ba8918c1761390915c753337d03408d39e28c1c9947ea1a58b216ec403f43f64357ae8fa021a8e0e91fdc4c11b95fdeb68edb097bf2619f29b7b75c96a575779766c4fe9f3313339f38de2e9ef964638b82a4fbb2df18d668a03ed27d692aac83316e5edfb5c7fe6b1824cc0f6dfff8d66ad4405f956ac488a3e2772f3c58433b065835eb4b37d820503a4a3e1aec9f49b82c0fe4d26f4b924e90fab542627d9a0bac2473bc4dd08d6a10ba6a4c08218cebf793f3d4bcafb0e7763a4aa4814a821d56b5f80ad604a52814c7b33ddafddc23bfc1ba2deded89dc8c996fe61a794c2e9fd5c5313c645827b885be945749db7c0964e2a1e7180e3c478fa90407afe07b42064a5d7403bd385754ac488cbfed30bf4d8f14850dc216bd8033dc2a718710dec3ca214112edaa044d0d3144a1599d7080b7c4af975c982948e32c651466c9d968cbe093fd54b76751aa9504be4bc131dbcfa833544320fd4aebb0edf4633cd16d95ce152a67dd93bad28c71a410aeabf14cfb35b3c57971da8043fcef2fb8f04ba2850595c9399fbe3ba1814e9d5a7f10849fda53ecf8302cd394050ad5c78fd918ae6e33b2db9360dbb896744d2adc653ba4860e2b6ac13bdb5e08c9059fb0a4eb426978840832bd1f3f06e812b5874da78ea97662e0cf81320147fbf9212590dcc873769a6d3451d7a345da78947115ff099484de086d9ac3e40aa18d0a9646f4d209cf3d7a671a8af9473d0e0fb88ab23188072de3f1d602c0c2303c702acbed5ca312c3cd3c0e752e13a4721c2fbeea40ab600910dc4e219c1c29519107125362e6f8e2432cbc93ee3154fa80ca09da3b21aa5f6c13b849509ebbdd11d80bd3d750e4f6cc706d787dc3d3e32a6e7ea483854d35a39b05bf8c7fcc87f00845a55aa5597a1f770afe47c0eb618059711c6a325a2914fc163673f0125540d5f3445bd5c83af5999869725711b941529d248f64c94dcea8be157de2033ab84c4c1d4294dc6ad6b7e70ed2905712a5818dfaa207d9d37b689211d42cee7bb3273c47c342784f9d07c29451690513e61a41c31cf02cb565a1f0c084e8654780a4ccc46e74f637a09e4523f9a3ca5c189f12a3469e15ee554c272b865069407d1a490527657c86d630e62f1fe1ec4718d7c83d9035b634b4c6dd02364cf641003320241e8606108a1ceef976ca12cbb74f2f6d31f39a334b84126d973dc9a28868ef405b4a067951fbfe543d2b4b43ec01c3ef8b8d92418bcd346c496ba79de68f098cef7f0d2e4438ba97c907af28f1bacd9c231efb95ea5aa8d9958cbf3c4fc88da220f6f7fd977c001f118c3f2d531b7f808a64680f277af48c1478c8a8e737084543336f2451fb83a6d7492c344483d4a541858091727d42092f26becc36c659105a7e04c00842ecf4d6ccc51a61e30eef021d2a31c338ca60da5dd9b657d39564812155b342805474f422914f4d591177609206e0e4f481774167b05418b68c068019ee394130a363f03fe08c0f6dd153f9a8926f6706dccbd737019aa2972e543bc65dbca154a7518e16b4fe8e193d46e5a940403b5b5949a0bcddb792317e0cdacb8c575d5ff3d64dc9ab2cbe3287a796538b7ac26cdb2292479bc1f8852c9356ebc31d83dfcac60ff807f6f1800fac49791331ca501943435138cc1754145b03a541c88408b8b66f8b3209cb65b20f2406b2ee038dbf697344648b99c913b5be3fd48ceca20c71b9002a0567292ab10835e6729bed15266907fc7999dc81e5f3109e42a1698c1940602272317c437b6cd0b85f307d16e84752382d791b4d3510e4544b0621cd1cff0b52a95e51682668c768e1423a25b1e4a79802811cb341ac637e5266e279719cbb992f65b1ee90b22f815b85ab03ff2a4a7aa498ab89025560e9238c572c47083f0cf2ed8ead458f5ea8dc2247a0fcf9d1bec3cf1af6becd249df1077aec4cafbe9be8f97c9c2366c03c67885d15a6b7e801d2b7866ff8c4a77282d2dda9e650c66ed6721b27f11bfe338add84780333fb3ba90bde4c481a8d18187314179ea4112bf3fb6354bae1072469dd676593c05f42b21e5599c3d556f55a88dbe80d459b12ebf726a901839b42522ea4f978c263a191089eb3383dcae16bf4411a625623e0d40e977aa8c6122b19e24aa60d5fde577aa1ebfc472a1850e36956392f61e85759d3ba628320c3f59db54f94dcad08cbe167d0c120394f5002f73f40204027d6e4280d8327680c3873f4acc89aea4cddda5ac5c442256c73afda88da1373b4842f1db57bc2c8f5751daf79406db1c42347fe85e525f43535abe91b9af64194e539f87427c7ce1e7789dc3ed163ea4a2bcf8dca9fb568e7565b201ef0bfc0b0d632572df0b79f182b59ed223b1e48114c61523d2e0d1c3bc0f40e7e026a6a1ca10a4cb44184c4292b34140265ad2510d011094dc909f5cdb6beacef41432351a50c0fc2733a83122a95ff66b03788a58d1b67aaa29f9da946d235d051965bf502d6810b1213abe73a8e68fb6027532bea10f80b945016dc492cf2a80acc79ae0bcad5765bb2e2da84d04855e245b5244165f126a6b5a63b90218c2961458905f82147d6e57fb59232ec92744f3deb4e31436a92c465f6c45c592f1b9f17c4e9c1a836cbbbfa33a51ddfbcacb67f289a32b7d403db0d49320a00016e9b47ca4fbd60ae8e54b055994429912852992938ea688045b6b6e3c861d9d6c3ace0b6ab3f8a11d9a654a1b311bd593d890eb76ed29dd8eefaa8bc54dd0d75e93e4791cd23cacbea2bbd142e3c633ad827289d70d37dcb736cb62817f71f6109d009101c7a4549221df691f9b83ab6f0c0bad417a00ee9ed8e12faed209fa521a80a76bd5deb6225f4bcb6c312354f15a7971ede9de9d9e45ebe5a90e6163b2908d3fc211324d28793b92eac706a73e9f70f446288f348814eec0342a652b9a754de9942173b2ae958623d5f00f9c0cfc271069ed8b9ccbe4a79fe639c0b50cfd23e4211af6fbf0dfa76f5f27fa9a2556c90fe489f20237153291cdd9d688cd1fb7ded4ee6843ff9a16ccb29d1391622e746173fc3f53370e9e88726aaf548d000dff3a8134cbb285b09319748e8bc03400405b8a1e86a60357f18b565110163149a85082e629cb5fc31ab2fce2aa173476e2a4eb87725375bc621cb55b4b2d2ea5141eb1c775109479413696a8d758d153911471fcfbdc27700c3a5a84878bfae6319c24d518765806739bbaba160a3d7319591921c9d14783f2b4b346b258a497b9cb6f42a1966966535a07f30d4c409cc372766ca19c6f7e0afd23430a0514b58bf33ce82656f25888ed518f6d6325f096ae71b1a69d265a3f171ec0f4217fb6fa9a927a4d8f91b5534496542bfd7ff8975c84d10fd61b182dc0cd105160bf8c6307ae05886de44e906c7027a33982e48ac436e82e80f8b15df2d866b221e2438ecaef8f2b8ced43b9159761dd53e7d0b6cf1014a639e902b22f7864b8aef6e0e41f55e751d808e5c368af15ac4390af0a27bdcf62ddc1c74bf53be85cfefb99f1a4ee35a759189dd0ab453330c45fc7231f582ac71523012b20af351a1fcaa50bf0d82545700cde82a9e29a710005202bca0649c3e5d7990e98ca732bd101855835764ed17972e7078048fcd03231b4e3e5154c86c359cd409a67e45686546c722a4d2d38f17dc52ea684352f9e9592bf9704bb96732806295f3b9bec80ca8945a9aaecb5b0e895d5c20d72f601a5444e9f90c866156e302d9b566bfaead82a48c1c3ee15f6b142a737646dcbbf1c0f1cac11008288d1043978003295b86ccfdd0b88c9698d7b19ef31d488056240a76b1393ade0730eccf258f5a4e32cf70892f3701b1005234e688a4d572e1dda23218927034785ea4d7442afec4db4cccd65157ce17ff72ae84ebfdb4908d91b89064e9321d26b96cb5116b53ab3e0cab158bcd516eacdb1c27ba4995b940edafb5201977e9c4881ec021a7424cab767f499cb71703c5289c004d3508b12c92f990d9e8a1e174a09c3aa4208445518814abca959272322e4c406683e2bba0c173c5abf6aab44f082cdb1b3c2b4f479e81e08f44bdf630422c8cf8a671d92b4cfb174ca56792e019c38aafc231d33b19984bba214bcb080e1c43016aec64305880cc21a503db9dd9e6f40689121f9961e1f792e0c4276bdf2c227e9a056c48c5b6b21334533a7d287acc33b8556e1ddb7d0edb7d0e0125b35dce515c1303a25488b6ee5900a257fcc592803bfc1bd21b697b1329d326b124f402df02f00245abd948fb6d5d265f13cac78c5e666606a663d409c29419c839221d1a8f288585195d2d8142f236028726904893aa819919ced4ca505350565057505acc224619c2b33875d456c37c873232c374fcf1000d53ab2d6e1f7667c9bcf74ad30f0b153f82502ca92182d0143304207b1469f169096919a77e2984144d289be7454788eb00fdffff3eb9d6c98a291d2a46d450c1817152fe714d8d5bfdffffffb70522081f57d02f68c5e1f526fd2f8bd9f93c313d55d5f8aa364cc0ea259782d7c5838f5c014361522d4e2c8130218356292808385b7d3f496765d141db5a50b11939f183d86c0b1828e24d12c80b152d2e7edd51268ea25f06f0445cbd28049ff76e10a58678e3aa06084e150498e5b5c8b1dddb028b084f560aaa2759b5c60e5f8ab52fdfffdffb97c34b13c247c4e5de2297f272596bed2f063729ad255ea5259fcfccccbc03da1d95dd05ed4e6877430d3f1e55ca33b6b24c219ccd60c6de8e1da1edcccc514a230146274737449cab20476c94577e5a551a702d2d76dc8910c4391de9904041410224a10cdb9d503cab7032daf8a543f614b35563da44fff6ffffa186b511f46042047662870304ac0691fec120fea4948f1a3f1be32951a2d98c9c57cc6219425575c1869c36d281580c88012dfdc9a720f23616d150aa1f9e9a6ae5808cc5aec8faffff0390feffffbffcff86e2b36566e69e8ca5949999b996e41ce430c2eaa173c54a5893a28cdcfd70b76269525a89c2542ee890c570e8eeaf5af514c33617a2c0843e23236b5403ad50c19524d387fbd9f3deeeeeb3fb7a0dcfc2d2dd3dfa7834a5ddc3d4b4a144d9bbbbbb51a194aed676d6ae651cbadbda542bc8ce4614124c4555b076d41f11459b988ebd2820f1767777777777d79806c4ffffffffbf8ce2b376f4a5a3cfc4942f7dd07786fe245240d2cfd87c3d44e9ff975368e42cedeeee1e10791b3df482c8db789a8f799e3d3f42398b8e304d7eacbbbb696c867e6b8ec57b2f0f0f537aea0c4d9f0ef152e9ffff7f59faff0fa198fee7fd8efe7fa4d597c4f47fadab2403366785288e4a26b351a5638f17a462aed14c3e499259c1670969ddddb7da58d88e538184b0897d4fa7e1fc77777b6991b7b1ab835711a703959de1cbff7f766fcf7a641204abc28e20d98121a5fafc9a6025e0f2ff341c31a8400161e0b0fa517bb276cc841fb4f0055932b6746244c101524d14a954120088c223ed2ec34a48c54851a4042257e5e921fbdd0b1e76c1ffb770fb59da5a1ab32a3498665b9ae58b131d791b7d1d9c002f6366e6d917da8312bc8d9999ff1a59c14db0bb7b764445a49e349a9ca1aadaef0f98b2fcff2545e46da441b344c99a7dff5aa2fa82495a476e656666db493c77d7045bba5b8674b2413649f9ad75414250d8ce04128e11a72a668f16161a3b451f301d8e9f8a1e0a75c7e754822d084377cdd9ee5a85cfccccccccfcab5a4a99b9a84b738c85e8ee6ea4f2369a7dde7b72f7bdddddf74affffffff8f32524a7f998f2064ae94eb107376b374c06f2dba945c29e9828254126d2d3a232cc657083914397544afeb840cebe745f7836309152b18613eaa882c9bce126131584812fb9d4448ffffb73656a2c74f7a27251576a4978c459863ae31337b00f6b008f12821d253a5290707aeb8944dd8a48c346b00825c0836928c0831b2a1632548ebe81c9223a34998901192d0c871e283c0869bcc6c0215791b83c2218d09449a136a78c3b1ceab25c5ecbeb7bbdb765d0245b8fbfeec9ef2d204248344f93ccd9ca829807b99582c952c78c4dc9b13229ad1dddd8dea94f0ff5b45f2363ec1319fecd09e00c1ccdceeeefe717c66666666e60f1fe49899ad55c5e1a5ffffdfda60196b5bcb35b396b875cb034f4bffff4c35d6febfcc6d429f9cffffff7f0f4ec2bfe62cba2534cfd32b68fb6bffcf3b9194feb7fdff5f4a997551f17f32333f01c9db4815b41d9e8433ccbd94b996231e30fb6d7b5412246fa370781a25c41a1ccd55cf86074eb4e3b7bbbbebff2b3d3005cd9f5d8da3c2057142dc10470c810c81e4751f8846a269e7fffefffff3aaacd2ff33e66df7e6249121c4028ad0510d3a0a166aceb634edbea2a9faeeeee6ed68a5dda6ade62c4aac5353c1724704787f1c4c66c0d80a02a387c33de8e9141b626ccfffffdf42fafff7bd084a7f1d25970c8f252233b59248e46d4c5ad259db19ce152aa8b68edc385dad10dace9e96ee6e9b2bf2366a9d029b2e18d2676a36469108514919bf005922c658233377460b5ad30031060a3a539608e1543c19215369a6fcff3d2aad656b5a6b4d6badd5da216f235050110da526a6d1da3133b7f84e9fd050dfd63654c36f324d91b7d18ae98ac6a405c3d435a3817302fc7ff7ffff6720fdfff7b366fcbfb5321a49c66cac5e81cd152dcc5696ddbd1b613d4b0c20ac424b6b8764a5b54064d6b6b52ff904e65921febff7446c07d810bb475a06a59d1c2484a29e18d51c562dc6bcd80c3ea49f9fbf1d3e5166e65c791b4d66e65b553680e38208851c2f74565240f9b1fbcc5a59cc45099609af1f40ac121c184513150c186d606666661d0df2362a35ed84d80eba296b99cda718a4969aa5b15eb3fb167f3f222923094f1b66dfc6cc582c89b0bc8de65556fa7215f6326a70a30755929295133e3ac802c3b000cf2ce08466c94be22df1743c1f0f284332cf3e6f130ad6d22b31ac48d226ef65c27d6f77d740cc7e9a4e45edff3f67bad6ffaf52246f23ce0e77f80262b6667777cf446061d59a8539158466f0e4c4b8a9d201648f6200582d91dccd0477c2a095d672897ad0057566dfb4263373911ef236e686909296741f8af81e9880b4e6d0ac7677f78fc807964ae8af55f7ff2fa7f0ff1f52185a7b0534afa87e19c39cfff2ffff3f2788bc8d4514e4d0f89999f9ffec8b25aaa7bc8de62dda55800f678dccc47a5c5c6cac271d7b4f92ccf3bde9eeeedaff7fb891339202fff72a1901c29129a6df0e16f5048ea9f38723889e51505ac78081bbbbbbd1dbfebf36a3e39f8e0a35269fdd356731b02c56e460c493ad9fa51ec59c24b2df3dffffffff4effffa35b325bb8b28028243ab3c8085c5a37b090ecfcbcc099f95060e7cc59ded0141794a846195c72f8a8469f588b9a9c8dac9b9999a7ccea6db5520a476a470a482aa8d66b027a77d7607ef84c162f21907422e4c542c50b288d141cf32d75b820012dcccc426666b46a4333f3ff4fd590b711d7931ba2994232f9f6ff6718916836f3b4270c17959f65ddddbef2369a597d7f062bdfdb3da2de183233a3b6a8148270313377460beae207db4e00dfddb7bbbbbb9b97f3a5ddb70007d59690a8a5182070681869f70b19194c5b77cd59d464662e47cb2008a7fcff8b3bfe9f66c2b08a79f1903133332f19a63c7e10e26ba93eba7c147538ba1d1d90ac8230a14d4d47edffffdabb7b055a43b3c94c210009c877171f33732dc985a82be16f915e5ecaf1d7411752cac442b960cb1ace150d8e96c95776fe1c69dd9d8b41c1d722eeffbff4fae1b3fbf2d48c452b58388f8baaa647298459eefdbd2758cf5662d49259cb818f08381d0924823da123a71ba713cddaa4251d4d337a5aba6ad41456f391b1b69d4ea8c0a233f4961f21258f98dbd2dd8dca0c872f31d4c80dd70e29543b374543aeaea35852850ab1a474ec99308c2569d93aa5ca6edb6cefaedda3d14b36d640b2cc676666562065666612b0bed29ac4bca0a0a7a0046466ae0990819127183c524938e20b2a20822cebee06533a58d3c176bf35bbbb67cccc3e279c9499192583365bae561b73e9d863b293eb85b091bdfb96b69a15f2841651a8ca934e0d578d9f5775e1028a0baff4ffffdb84c63e0c098786e610d13c9b89857281a8263d1f66ee4ae8e94eadb5d6da2a54e46d0c9a42da10a60a71a64603055fb80dba215f422445b09d1395c8c58d0b1d047630330fa9b196a14a66665fcf27e400c704164f566085415da9433d6ee447867777777777376fca2eed2e85707cf41cc98182a945b592c1058a1290501455556101b6820626a3dbd6ddddbaf2369a3e42e65091991f05529c335aa27e42b834a12bce1c976e5384afffdbffffff9d1421e9ffffff3f0b4afdfff0fc2f9486ead9506b90ff7f0891bc8d35b89e5cfd30c250ad3c9582283c35328840264d5528c93482c935b5600890b0a814ccf8d7a60414631a480000410cc7e2408f190814000717fe80b8b040148ac321613818168482a1401014060041a12000c3600000d120c635c16d0da4b06aed2c0b140dd59606de5f47a9fd4b3ef57b575d5d26f51e7300bba00080c07ac7be78b94e299ea68416eb440943c90d31a468de5138c68503cbccc4777ef9197ddcfd23363ec1850f28f6f5659daec93489c99cc04c432074e70c799965cc0f40b4948b86c861b31eb0c203a0f0b5830d185ea7a41b0a43919e9a77245453010c3c35ea2881eef2199efcde758d1dd343c0b5da1f61dfd0a2e3528023f1489db1768637926c0194879780042badf43f6014887b0390ce12497f19e5399b2a72ef4b9181a457d63103b03815202c7775167c851d08e9063f60b143a90760081686b71b68a25f081e0b56407d5a2cde4cc10d8a375bfcbf29d3a8fd59ea25139c010457855b66f9ed358d2c0304f65e7ac1f5276767d47e00c8c551acfc2739a8956db8ccd04c01c133b459a09bee57c46094a49414f9cfa673fcf307ed1cee2c9130aac0e04f417f8b21639cd53cc7a6ca8d59a010205e7dfc67f93c3022f4d6141b1b33101b1d178e553cc4072234d4d976100e1c614400ee6f0c151904360764cc71b4ee50e6a2b8505a40b87220416f1b8213e4b0d8247b3619169a8d7207f0835314cc29099bf72935931a59606c12136771fc9a30b757820ce6072405843fd044a4aedcc99aaff45ee0e0ee930b2ad12836a954d314b33d21f24ba3ecf7c60dc77a8a216e3cd0cc9ee8c978db8a38da3a4a9a13fd0339628af9e43721d3466b678d8d02e7147011389e4258afefef7b6da76dd39ba6b95d1bf93ab54d503c70f128796aca7ec18c875259a42f3a784dad101810b3a10c93f2ea1a73a96430c0066fa16a639ae47c4205d1d8c48876c2fdf0d9b8d858e27e68d5ddc19e38246bac9f56c05d970c8e420df361e884f49c17b10303d521b83b12f4320e15c299d72cd654e930b536bace4182999480dad1d6da068b87afb8aed36c1d53a505302e0dd966475cfeb96dc6c8760924616d170394b8f317f75317c954faf25dc0ac2ae0f41a4291f69b24b134aaca9709fb9a661812cc6cad0507c7d60e9222fe18e3e82e4945db2e170b0006c2bf9c804a002d9336a9abc143dcbc96201d3dec0c5434dd5f570bb86fb6dbe70cbd4b104d7231af66c941111223050df11e84eb35ddc6adbdc57ce6b9bbb2ed31057a50bb79c6cc38229a0d2edc4340622fd9de1fadbcd19aaf1493b39b5704f3563632c2d1cf06f1cf80517ea9e65e3e364470337eed0032cf6fc412f1c85d53e19beafcd6f46e4231a48723e40b86bc1f10c94b6044d5d65e43cacfce219bfb2019f8f10b3e768d6dc638590ff7b54416d858071fb48ffb82c7d5c4be97f53dec75eaf3887a5d2d646134cd0f4d5b044f9a002d20a0c0dc30e26d609f08ca3224008b7672f06ee172081b97319b4f35b5ef821103a4cb26ff54367c20ce7cfc23d251e3460dbe61531f87f0c74533689776a1b30ef0ce01596d5d6bf1d5500089904206ea51c73630d9073e44289f9e5f38d7620da8a56a4ce0e36010fc11872b1b2262d0f89616132e2e3585fb3d53aa94346e1223920c7ff2df8f5f0c97c329981708f508c25ebbfe3b40e4b820af3e5c1010be6653fcf2cb7215fe17f8b39d43d11606db94a352e0d2c338172919060b8099602f9d7e160e30cbe5835d7d6ecb74ff76033f2ab25b82785055a5c5a50f31bbb5821f9086ff82e8121fc13994b7486c46e40b46f288035327695c114a85826527a8e90d942033817bfbf2cec9fe35a512d6845b2178ed87ceef452e25930202cc5a1c2c88d5586fd71ad8c7f5e892061615da8d3fc5587064edb624ea8257e4ac228b178c868e1b380e92879322b1dbbfb534e70865500097665557b58b29deed9d0afc14918805af5cab6e7552d03441819425d40858d84837111a6582d1f76b4d61d04a6a602b5235a2da9fa322f877dde46c87b7419008014c8198a1e75bb17f446b3409e6454ab63cb1026b43cb8fff1d05224daa4a7e8afc61a30e42845769ea93bd6be5b79d66da1eff32c84e78bf7f2bff5c9905c6867a11a37a94968746da1516a9b382ed1fafb07390b98db3962123066192c17fa9763b13e0b99ea39fefba19c5ec94b3143f2d40e4bc580f42769d3c1d80e9fe5e62745eb4702ef436c1167a462f03fcffbee4112084a97580b61cf12c3358a9281de7e2a2514e218d8ebdafa360fc3a196892c68071e279eec25a66db0b4798da26a314bcf5358fea03a474a66bd16113eca72738f08956c5fef90001e2519c0fdffb7cef134a1d52b91403c0bd9e3a03f3cd38e24a50c9ed4148232f41064ff5b052fe08ef4fb63984cf03a004ab86c55ebc6e29673d289681a128c2c60834e432caa0dd592c9965f9731453c98ead0718209ae5478e7481390c8674c211302579f4581b0a70d752d2dd283380133fa4d6c0f1f5554711db663fbc7a177ab07be0b5431e074d8d593b510a0580df17046644e2d87bc15ca2768f8cfa4c431556c5da55d110631a77f89e1c549a864425c4d47691ce8e5fc3b8454373b2f5f342fa4569d23c8b32207177e18873f1ce00490289e6673c6b4b7be83563680da71eec142796d433cced5317200be7225df48d0e53155b565784379f39eb684ef896dc7fb1908df4a3c91f84d10133b9ffe411cc718fa870c0ee45881e026d2080a5ab9a3f649a3d3b51177b2b8e1e9bc19b195341ec63334c847414153ac03efdd0406b86f7940a084a69721d0c993f34c668a187cdabafb1f347a37cad17e20716f76c239c792b4367095ac06d6bd2f1e108d2e590cfc97d22f06226023593bbbf48bba6d066496ea02ac78f552a8efd6b20c7d3cb3d956fbcee9b329de68f727f432851e240f8c61c605f28abc2325cca2000a1008a1f3aa309a70db6b10b82ba58e0898e53e1945bbcc0b7a062ca4c369693a63009e5a42d5f4992eec71e49bbd8cdc78b1c998493a007f14901f1e3ce82465bfdefd8a001a0ae4e9349f8f8420e78fa41342e7cfecbfbd540f8a83a7729a1a16d65b06acc91940675fdc4417e8ad139d39d0a94342d7c2816c478d6bd8f4471eb43015750b08751e8b8e4e21e1cf6ff0a028219e59228f1e2510919ab2fec3e341ca792f418767fafd457028befedc204a99b7702679d7c1d9bd80f8cf9ec138a6c21c8a5aa5351200cd63992c197178b81306f53e9e8b5d0df62a41ccddbaf489af3d7dd26299b103371184c96f87a188fe5e029300a5a8477fc76815474407922ce5c18ae1bfe1110055ee51b401bff459a36fce102b473d19f959250db1907ca6d0625b5dc88909c7c204ebcfb34b7df4c9ca7a2af16abd153f50a73ac4fa5acc98abb77c398db4af65f765f06175eab273893c5e31bde5678780e37633bcdcf3178c8b628600b02c9c5c52fba20f362a328582680e733521d756360defa88a0b63b706ecf08d3443bdd4ba01dc6566293d378e59a943f9feed8b9de71d29ac83a83797f29ac0f75c73b92aacefc4affbdf7850f818bfe30118395a587579d109b8cdc46df35950d30cb1fabb0b1cdb87fa935f5f3035294fdb653500958116bd5620437535e34d217c413439ed80b6ad80024e8eb6d1e7400d3b3a42496259d084965a42f4d4af1280b406f5834021869c87ff2cd867454d3a06f3675519ec003f42638ea8b72104d3c27cee90701adc332f8680ad48fc6a54424e319c0b6db180d5e8b6f86c6d4cfea47dde1e8de83a54539e5f124bf716332863fefaeab1faeacb8776b25b424df5e6c333b499a7f969f211418d895cab39d3c62f19abf75b58ad06ac8de6544ae49c49fe32d67a83169e93589345cb78a5a7040749a5bd9e763853eb804890d9a5f60688ca627a4b96e72e6ef01199ff711b232aaeffa77e032d92d31dd8ee931a2bc8e15552073b689102014bb45c08afaf702ab7ec7325f000999a9ed9381c4cb44a1c261332b27452de83d12706175282961ca2ac792b1d211f488a827b8c1701ffa69f91e3f303783aeb50b4c77fea7e2ebb39ab413ed86b019af2ad083ea8b77ed198e94f8734f2287bc2ba9c95ce616871b9abc446435efaa0694548ec3deb4ad5dba683f803d533f9604803c66df2b06e8a1954b703cfe775e2efd4169de31aa0a02685fec6cf47ffb9b40927c40443a20d7d48d70793cc1dcddeb4a444c67409aec761a9fe6354f4d272e22c14356ef112e523df2b5cc70a7b0532f7614e46d8edf8f6e4d2106d91a356924725824f2acd8105a59983f0403dec74982fb759ace1f06860a83f9d0358f1c262082012dd6bbce97756eae0287e7e70e0025d86c3990223412c1fbc35ab4a9a29b146dba806da226039e98244aa0ce1b84d56d29c788cec3afda96011c7bea6ca9f015be6123d47cc4d557fe2241e5ba6a93fa0b0653d31888238783b8adb922d5faf52d641eca60b30ef5f00c88fb6283c11e70924b38b8bc7bfd6bec2a2c103f561150b9ba5aad9893ecab7851eceb8451bb8d81a3288d7e5cd79b7080830d7ecceb194e9920cd7810bedd892ebb3f884aa5168a1b80fddf966a83e978cc52def601c8c410064d41928412e402ab726da08a6a1c3dc7d3a761b62b5af50b7f9613c0fc01379ab00daceda4dfe9bf043ec4d99968e013394141b53e1c8a601ed481429e7be53cc025bc278101aba83fa0afc4d1cc72ef54c06962885386e02e8a66934fff290fae638861bed95a4f91692860667a184c21704e3477823d82cc1f5050a2ba971e7f912957a4b3579d925021db0e10ac8cfa1d8476a66a0c0d447d7c7f5a4aaa92799bd13ff4d18283f26e6fc4c535da0182d2aa70637103dce47e1580ad2adcb2fe5ff24c1d29b86d4ba6d9fcf7596aada56f0072510024e647d80532574544849ac92bfd533f57164166614d60905861ca196a9ce978abc5c89189a0f9399f6ac2c533e9f55f6f05d49df92f8800ae813d52f71dd0da7dcace64388f2dbb5d44d87a6bb3b078b3e29b2fd8a078f304342872873ed0339332029087df3fd8a35a942cb1194822fdbd11da3037a5ec0431795c1c4c5444ed22383142f7c09509691b269e6ccb06729fd9c33dadd6531f3be94886e2241b9f5b107774c67c0f0008044f55424a109831c26f252e278c81bc14be5510d63d354ea137c031412f02dc8133165cb04330b4287c129f2013afd6f35f7abe2219c19f8f5e9236a1755bb783cfd65020d60146f1b82713c03ac156fea2d75ccfcb57fd9e93ca48fbdabcd583642101ff2cb9d0a40d1fbba2d15dd7a21e6072c443075dd7554a16625318b1fa660143d8d407cff9e44d7905489f3c1461527e8fe977c59249fca9e2001a7276c43f8ccee676961487eeaf53a1b12ff7c6743dc1080d4b2f104192e58d9421033e45dce943b7dcb09239aad876a328516451108da19afffd7004f8028f5baf51490682dfb3408418aa950a1d0cfd0150748c39c041760dba16790ec2ee1ea48109106ee80b1b08b83616423e91e0ac311efe9c9fae39797255c9e0c60e2492121a45d402b157dd405e9f059dfe50113f6ebb3f2fcd7700dc8920ad3022397042825be65ad623e5a46ce858601c42b7ed7b8d4b2e479ad8bd8d70a8964e3d9c6468326634c8c5987fff60ebd4e46e09263df272ecaa30cdf866cb7c1b07a2c706450d28a1789007b3896aada39a541f091b70daf49fd6a528f176c83d790976f2378995cf454e02b363c31f24a279168195b4e488d5b7cc10fe53d90b74a44b3098c1146dd76c950d54eeb917b441479f5748232df78d547d1681690fafd7eb102b6c91b20a192f51280e0eefc2c612d1d67ae88d263291e0592ca2c720dd47a4ab2a026292c72b473097b5709934591780c11f7276adca1efe751609f101cee5c8380e4ea6458123354c54202da18717505f65f5ed1301b7880500f7919cb1ff9232ee55c93e603b38c68ba6c09e0d6c352293f4d322ef17fc6684493742b3c84421ef48a7217abad0d25f83666c43f7d3c6043c239c02b9527dc2566464cefed35120ccbc267155395c64217a18bf875b1daada0da042ae072d2a1b878a4029943b9e0279c0ff9d2ab37c8fa7d9f865ed09c1213bde1020c150debf832820dc6348e9d0e4d9fd2f33ebc97b3882cb0eaf14d9e599494b20ed5ba33c93c7aeda7d60124531794f4e7d99637d8d8ce18cfe8c537fb725ca703a593970a0e843f17b5f3052baf3a451af9ca663b9750bed133cab6c440025c257e4a83a2a0ca7dbaf86d21d2c3188431a3380fbbee43c6ff9e6fffa5dd4b27c2f86ae57954ab48b1af31501f88599faf7117e6ab2c954cbd6654214ede8a0b8010512588ea76a0e49e4ce1fb25806c777dba672023f4976978439f2786ffea81959e223f8a45d336086e2c963a458d0effd0a2350dc7835f944997fcafd188593466ee055b105e2806358f79726628c9719fa08c01ae12355d9f983082b18b6452d0696514147a98e1ccdc75605e0c433158705a303e664d63d3fcc4182795d41d7b9bd50c8532bcc585780d94b3516d4885300e200d7ad7ac632e72e4888ef9ac9dd2e919a708e71c688806f6b76eb6406c5dae5310c7a8df84a41b08479c1effefea0fbe336089601e458641050988041c4f0bd7b76a934fad7d982a82843f42c118a30f1900cdc5ccb209954c8370e332953535534ba1b24ab60a7dcbfafa6990c1f767e240199896c5fc562fddf1b837c95a85e080d794d8528e4db5ffcae37f3b1f9ca8beae8b78aa83bca2b2a1376fad7f727a60822cb409cd7f32b1999f71f971778aca79828cccbe56ce538f231ec4452ea421b377e342d2c1123024c708ffa8aa21ec06db18386a507d646149a0fe62b3f00e58521f5e750a62be438472a6de1ceb939e6ea5aeb67e6a1e6ca5c879c220d6cc075d246013c14ceb5df07205b3ce48b2a23a54e131719750fc359825e4b2674dd5e7c2e8ea90425772af1c7af2437f6b3084b29d5f1cdd4f3cebcfe6d834193f1d6400c5b0474a80df5397cbec77ee9e807348e6e67b1c66e0bc4d08c3a7291f25b8ccb5fd08cdb1a959505577eff08ef877bd56f85915d8f9aa4edf12914ec336cb0c7578f1bc17a55c58afc555201c9c593672c1569870f6752868ffe1262b73fb475163083a24ad39c6d013a90dcd14b13ba1b6c22869dd889a4271ef23b8ea85a87504b2d4c17ee753c9b88812ac3f502c2599ac72e37ce4cbfdbfca66ad257103815883925d71260ebc9eca679a5af66749b68d49d5808564608df59074aeca5a88d6b85fd20a24e8814c632a2fb70f9d6254af6609b0fc11ac7b91330dc853784fff8bbf5f5420a0c183a12f297e03af84c0c2e67a459fa6c15760ffa0b7ac078ebf221e308913a1f77b342ce0acafe60a10b08aea7d162b8ba4171472b29d735afb86ea89698e1785cc31830a828fa37ce5d5140ff70fb60ef0ac893f36e1db04294a87ede289dc96c2846c670ad6f834ef59efa7e9bb2d09b3c278642e81cc98b361580c035289139daf3bb34803326b53087776bf063a52d798f35d0c8fd08bec4037f7f117b7607a5ce735b4525155b1d85312ceb44e0f93db462595961acfa12fd20d4db363aebc46a27863f1a39329a84566d67dc7cd546afe94e581eee7da66b50e4809e74c5afcc1826e57d5382e405293251f6036250a98bac99640a3b9b2aa7dc071d96577772d3f8d7a3fa536522e0ed1595a9542693ff55edf3547424b8675e6509b1748e74790c6309bccbebfd65f2f35256c2a6cf8e8775a6d7941d5f8055a6215caf29a9ed64fc3279d01429b39337ca4d15da021ecf91e341bebb652190a2f74d482929a59b6c838f1595729f46a3f8f1d203dde4c8c9f8218482e5f0f83461d80b27411503e7dcf24b9122ddc491aee224219f1897da2f908b1d8d2e72791ae4a8dcdc76b119a32a98294e2e47332ba2f2b0e40d985afd08a60289338922e2288db384ca25d03ae0d48f4c411d1a36e82404d7b31130ce0099279c83602dc60d657789be87440072e05248e6c4af767d5b51556a46bbc0494f7d4cb00ca60e8b747935c25d906a99168012f6164565900462edd3e6ab3cf05b15500833ace53906f2cfb92eb9f1a758cf43b3f2630a5cbe630d944cc1c976d1bb2221c249dc233e0625a88de68a0bef69849a68975d1c4073ec01f7376c7c1a0c668855f4969c09290e791502c48d5e11dfddb406239e0b9bee84fd4685e85b27ca8854ab4e74e1dc1656b120d8bacd892805eadd6cf89227ac4f07a3428a564ef8f0725d58a61b35f2a0be75edc75f21f4038406e8623ecbf75ef2a10070bc57987a390688b51566100abf4fc4dc80eb0133efbf2d792fd0f4da55c130e65cfe6c951e63ad188fa5165281c83728f406b931392f29682419a520440effbd496c8648935808e61f5453a6d008a7d508746716cf8c52a2430925a6fae1cde3465cbc5d5932e3ae0db94100fe3915bd12fc7b2d6661628479507d4341a8548953cabbfd1bba48084c5ff0e93216cbb2fb5f9a4be6ff36067acc834df07e3ac23f3484461aa28620df33257e8b56f373d607e88484f2be24813ac8a50340f9c8c8e2d584cd1b6e444042ec5417fac38c64d9d8aa0300a0f7e0e003bed85f815d051f4332478685d029f9560f094825b8c123e1c7664640ccf2eb0e9013fa90eaae8c6ee74140497d5bd18012473fe6ba93647a031902499eed70649bee54acc2e68d2f53e00f6338e43302c043772d3b8e1d1838d8099575bc8381048519381270c9ba342559f3389a4eff782e68370da6b82397f0a43b4ddb349424e0fb907ab07216f62f75b4194ac83e697581a5f851186e8c1d45a1175d80c2ccda169ff28e780a16fb7549bfa140fd49cf09c144a5b0357e19d2c8ef8a323c02d323bb6bd4bb3eb1214727dca7c09af009df0550104237e1872a6d4abdfa907fdb304a37954c2b9ea0a7b384b6e1916b7e648ae0b41357aa80020a018de81263f8e32ca2594258653aa180863afef62a516fa3344b4ef52072631982ad9512d10bf37a11760dd58637d51290b72e78ce47addacd0edf97be49d4a0faf0d6667a6a8592bb435cb79ab96639ca09265c7944f902e83b4a3f7843a2d3724ba8be7385a48bd3dc1528baad0ae8edf737673ce9fd6476acd70c0287b5dd07e47f412b79252586bf44d5ab09cc17f194621b370b4a1a11609fa852fcfa103195292799c044d7d524bd10ddb2a1cbc8948ff6c98003be402b99f75b892ca2b6a7d6f310af22d2d880562fe25c10437b115adcc526a6dd2f19c7b5aaf10afad465d84bf7e038627e14eb42f4e6f48f05e3ef260128386c7b21f19b2c00d10d8b3547e80f72f50cce4da8a833f15ef3f0426a77a43f39b34357e4730b164a95a15b1a75c9484245a24e7047c072132b46251ec4530b16d329787123eab0cadd9636457037134db43c6d357d72b1dd7783e4452e5b7965905876926a91d8c5e11970ae1617f811e28e0d7cdc5e739a8f877243243c02bcbda07ef0e18f50a1f89eefdacf0a34bd086882408a62886573262f94938b2b6cd84f40f24381874d63fb458a4558e0d4c9184ea6bfc55df53c8068912264a11f90000f68456967ec1799b807c9fb9ad028683e74931b47e9a378f458140f6c061233a665a521bc63dc97c8d365f1e826a8f225bd1496b3109453175001805706b02863b9a10baf7168119cfbcc3a8e3385b1d79e89d1fb82dd3e7acfe806c3f48f31fb64fe84536681ac5f8e8e0f1a9ab6d03db68f3438bdb4a7b86f6bb2873d185ef2c2658cc4fbc2faf89fa5919e2238d0d6d69bbd01014e688c209b887e979c80b65daab9c58524499c570da19f331c568ac2e3ef6ff44447eadd379f359aeb09c571afe9026d531e0ef801b2f1855a1beeb5946bf819130e958c874df9911936c7d830c87b0bd9cd5dccd94b11f6daa6fa7875b26873f4290860f3655ff9d97f2e06184f3353e807cde6db5dc8a7921fa268f4dd68ff019d80ee823e801add710f43dfa2bca62d01a47f593ea0123bd5058f3e6bf94be57e02fb1b7b261e8cc099194e1b9b571cc987d9b885f9ddc4330d7d9c35724552aca3866bb04a895aa0742339a4aa50733b45fed2268b5b0a78da884ba80bba313e80e453c34388b79360f8e145ad12888176a5f30d62084ea62903240a01baaa102456800c260d070a1193f1d60d35d32e696e62579018cd28e2e940b33fd2119262486a1df00b42ed0084d75910e71cdd7f124a7553b5dd52eeaaee2842769cef28627b3f0338957f81b1bb90b75451b9edde3bc9b477931d360219021f02766a75d9ece86769fdff5ff6c2418a2127a840a87902a20782d50d0c2c41532e787a141972758229345b31dab117cdff9fe692c4e4b36ddb76a9408a982a1123767cf104614d31197b4351aa4ccbe7ff7f01bfa2be845f57e4bfbbbbff2f028251090590450c942e1347767c6165a13c40df6f7256d9dd2deeeea6419a4d8d908c615b9d32ea04926d9afe90b03f4a82a0316fdb7ee2d9ec148876473b5c688920a343a5e58b9667e9a910e8140e9daa69d9b204cbf977c164904515296e544e3500549d19936a8ab66dafe395d51c57fff42ac593e0112a1e9f271537646804054d1022da0999c5d05139408213746a6cb48892440450d702270406f8a47c206109833772301912816d1f41d10c72c0f8e5447011a1983fb7140fd4ddddcd5101ee48c9adeb85314984addc6cdd938684e2a7017d7a497b568e4b8945db4e4f1e0c299f99c242b8b1652fa669db2570646dacc199a044cebe8c2504054d60e1e059a165e210da6cfbff8ff49727593a668078c1e2c4954f0adbea8c60caf82492aa89a28a7acd626ee0fc2eba65530e591b7d84888c28685ada06ecffbfd70c28564e543b867c7070cf530e7dbac1f263ff9fe6dd5d5af924544bdb4e6dfbe8b665b36fdb4e89921d3101af72362ab91a30e50815457b486c2a0b57941729e01cf9f932c1a3abe98c3a487e464b881c4dd5b84162051b2c1e42109e19d29e95f36bd4b66df545d6c610b89a0e5738e7ffffb6cd18b3ed2820591ba384571ea28a1e92dc621a640d794253c43484c6dc6119794a3e66bbbbbbbbbbaacf1ffdeeeed087ac8d4244464bbf7b18067f944ae53ccff354d3f4545a8db4d8284a8c79eda27669662cb6bbdbab0164dbd5d9297b3a3de8ee6e0a37656d3c774f1a910163efee127da14042a0407028901a14080e8a32b00f30f9c1634913191b381421c850cd48f2ff9b685c41d3cdac54d39427472dd5118ad68f1d1db91149640079c2626728676b5aa2288b299f1644565a37410e27b2fdff6fdb5e3ab236ee781dbc7cfb82f4f4305b062da2d9953559c8b150fee96e6adb1e7af02881bbbb4929646cbb496a1e541cf13a921124a8c58f6209367afe295934ff74ec29495981f1d8a9e66fa5b56b77f76947d6469e0fd8e1290a4ac50a548e85ddddabe204246e2db7b26d6b85b0d9b194b63f37bbbbe8a884a394a3e4a32444b4abcfbffc9121ed5991954aab4ddbc0810756b72c1380314ed8317dac40accd7852515fd3344dffffff0f1b91034077f5ff7fd9966d0bdd6c76d4052ea82a1a3e39185138b8c78d8d26c782d6c9f301ed73d4e5341063b0ab2dfffffffffe3f43d1ac1414c5ba11dbbe99e7c54b0fb34e7777d7086f7b4c326aac88ce5e6d1b66f4240c80472c438611446e775725c38b60d4de5c8eb6862f1b180992a46388a4c847d78380af7777777777777bc09b6db7aa84108e18583f24290e3b645bb317bcb1a0b0a26347285cbdbfa5dfef17fc4965d819499215e8142bb0719bddf65956b924500387d38c564c8f123c863130ac1473b603561c9ee4f5b3e035b5ffbf3892b4edac1d0ab28066ab465191904e9d2ec789ccac4a317ff306bdf092f6acb49425ec46d646199a16265c59729531781da42aaa8372d0ce68062d9d32104e341b67e65a2dd77975d5f4bab2369e230d4a0824398844a0a69098746920e008c844a202ba54311070e423e722423124166469faffff7f9a6099a9c153c3339ae7168695f467a5547726460216406dede012f2e1449023aea4aa222e324542b81440047340232b1b0bcb2bcbddddad2574eb876e055b7606c57ab5dedddddddd05524185ad4cc94a41d1a29686a420a157dba6a1441efaffffffffa955db4eb1cc0f459f4dd421aa3e495b007953f3ff0fb3024a8c22201f4844f538ec90de1210b66d09e1e6b601a68b06d6961218564e80429c3019da18a2668ab9bbbb31b1f4ebeeeeee9aa65fffd4068ccfb5ffbf2844d646a2d368b6d4d2a1f87b81893d4d5161bec82dc5caab212c28ba384ee1e9c08d4c9189133e66661475e9b0ed218fac8dbe0e43e0ee6e6ad2fc54b2ff47613b5bf9f3d46a7a2e1dac02a02b9fb3f5ff0601d9feff979533eb861c1f4346764a9c764d68300960c88e6ddb474a7836bb2c6b826cbb7476edac948cec2608f96c731d7b2982b76dbbb4fdff51b63a44f9ba85b8e16e350bb3b6ed25c9a7a0800b68a618ca252a1fbe6242c4e0626a30888042e487882b81680699e9e044f2490971aed94abc3db764b5709b9d26c9ff47aa3112ceec8439d5abe8ffcb58086d7b5d6f69cfcaffcb46ddeeaeea83ed4c9b2649be98a6699a878bb299260fe6f3096dbb7c31cdadd291b571c7eb50e53bcff33c6f3ae95c783837de707e4dd4b4ed2239b236ee9c40330f45742d2f2094e987989e9629589a2fca1d3432f3c8ebc64e4436593f461e9c30749af1139bd123fd6fdea033d857a4d438b801b15595d4a3a2a6f8bb7159131893043f483242935d8cce084b8604b850b42b83722cb5694f39161f397c3833233e08196ad22a22f2682838c9b8d7a695a5462acae5022212806694fc98bc6022a2b8121a0997843f615028251c6e03a369db5e2fbcb4984133a2850d6fe8db1a22750820002375b6b157c2a7fb7f171c59cf00685980eea600096a149437a4c96ea3f27aa05f32598ab3409affffffffcbc0b1fd36652a4da2d4dda51dc795efff79423bdbefffffaf52edfcff1f1ad9fea231f8203554e1074409090e3742822a43850dc4afff2f7300821939365a44ba86a84458324204aa13dec6d214d32bcdb69156f86cf68b5e8cd64e90139459844629c5c666260eb66d5be62b633e4fab6931b5b1c33b6087dbd5fcbbbb0bc10f851584453082dc967a4b68cf698054d6014c7875262161415ca0007ee0b5e2848ea9b4532f8c502e7c3a7237a64c20125921098bcf21fa5443f15c592d2bb1807847686c267777656b11d04c85c9f2724115d4090e389ae6dab4a680183d8052731e402049ea071189a649486f27430039863431fbf69fb6deddddddddb5b1b3bb674a83eb44ce13165d5f3b78553c172389f0016d7471724e1239a20b1db81b018d44a8c42d9f964201a800231a38040d03611cccf3bcc6001400041ada78a8a8402c24120686c1805010080402416018000403c20000182008808242917ca229375f47014c9737df4c52ce9924e70fcf01c280a945a668f0c1e74ffec8ed08c40e4f914e51afc699856d54c0e7147ab45703a43bcbbf510db4dee2ffb482e0df67a600b46d0834900d61c8b808c2a48eec8f8eca1739730710d851f6a2df22b565fb2b34782df18d0d61044cd731e4493def14e0a41f4eb74f4865191be23c2981ece16c3222317349ccd4b3470e06f104627fbcc84cfcf98584681ec6dae9546bd79f2756a7b1f3184a3a349a96232177131837a0d2d210b529992256bdea023474c092973ad5daad1f200c6d7f08681e1250956fb21441a6c24930f17156031022917edd3e326048204d5382f6741665af477f2dcaec81025a3aca74c12910a6ba6400abf2853fce56fe72f8e1d056a295ca929b6e8926190c201530ce858761ab6b450007270a903045ff2bb20a0669b79fb4292daa0cfac88626dfb529097f29b4a05f18df6821cbe615ab0be97c99464f523d6b9a4ccee1f213463291adc072978fdc1fbef812d156db6426ef3bbd1e9c7220152b0ac93c7fd5cc566fc9f3bdba48b5c4b2ca9c3366b24c9bdbf4246832084585f0faadb09d1a38fe2ed95560fc747b261766c775c93aede5ebeab3cdaeac2c463f4fa9779a6f7ed85ba554ba5775bd5fc5a7db9ecb45054660ee0105c12397d19baec11da899df1819c25162ea39b2455d24c215604e98c759d46d162a59c5227ccc722d7c50b50a62fa4951d58cedff83255b24c869d84f58568ce93794732b5fa155b9b0b3e167f600405ae94841cb0fb7b0862cae0da32515ba390b157314bb2d754f08f6e5a71cfb8948c7e50a1697f07e671de8bd59b79762d980c43a1ca69be3015dba95f34172e9423ddd6f2f67d511a1838584ab93bc2103b9596cfba38ca35a053432ded85f32df26830ae68d30789a5a599d905de048ef8d37ae222c070adc3b2f2246e942ee6ce00c795f0d320020547e2a7284cfc4e199646e4131521c9656e5b037a69f4724aaf0088f8d850f6f6d66400e269a20993e078bc67db584fb799782155f9080c585274c6c00c33c58958aaab87591b85f05a0591c766c92025af69748e68202966fc8f6aff6c6571ebd89110c0e8ec237678fc5636cad940a9aa6ad7e775463f040ca409f53804aa49ef1102cdaf88742d6dc74802b409d59df29d0f83f41b13e96692e41b6a2cf91a454c4899b3f7f9403b5ee3bab61e8b792b7346e84a56d0f0a153caf882d276ddea70765ea74e923f9d5b223298c27a76ce4cab7eb116654d54048497415fd24135b8176744221919c9194b87d4c4de291000d9f2c383800f26ffd1208e795d9662dac594c50e7a98042c2f010ec27a2a05073c96bb59a33f3c96685e05b8e55b0ddd4ed67ce60f6070b1891b36578a3639347f4b5c1028cad3d774e8947556436e5c69c344fb452498f4ec737208e94ec268f0ba6f606fed30885d728c965298431a7497581cfe5543f4961b86a9ec62fe57b4dbb160cef291be039109eeb761006211268140dea4599e6d40b0abca9b6b284871306cac7069cdcf89957e70d55698c9bdf1e5a11e23ffcbbd242c3224711afed1c9a8c3127a6124c670685d4334e43274fcb023060660083166ed494d7b52ef19516e157092e3d456ac8be6e2b43f6c8107f87ac32a96168cd6ed75efcd39541eab4c777718c889a377767a765f03dba9bb253f5acb1b2a0edc42d1e347f01f443449d24a712ec020c28e31de0831f6f0f4b3eb14c17e9b6c3dcbda17639d3bb0016be540be42c594760130003409c6d6b6559d1547de07a9b3a4c801137b8905a75fbe31bf27a02bec22583dce6fb8ee574f7f96d88d443ac95528a7778515fc870c880640f013fcf9fec427c59827a8b8439b285d359528d925ec70854d6132ed504460eac2e207baaa30a92c81b5aac3a3d2917614af3855494ca290b577ac260c18e141ba0c0b46a8d1d41d1be218d679acaf62e62cd38a155732e9f6e3f08d764ad2f17c76a757b8c24692286b7a007fbbd9b0665a9f878da7681965b0091204145fc82aa81bf38bff362aa74b3f5075b7d72540520f7af6e23f169c546c179167232abbd8fa9702e0b2430e30af5525ea92fa86eb4a50a04709db8c3766eabae861b3bdfd8e51707b0d2121b89f26f15897486ed3a536d5d6aa777ca87f8c06581c36ccb42001457a678e1107753f740943264e8e7a049355c43e42c29e7c2d3518523279878c229141ebd420c6aacf40861ad501000eb523ecb08ca5a3be0b987c964345814a876c411a0fde094132a4d965e2dccfa15c54e9261018a00088e1f99b5d943add8e0a3ee16dffc435da102b2797f605d9584312b26727390ef59542134787bd2c41c4a230c012a44e8540744c32203302e136b61426ce3646e3a42ad1390e29423e69c0285e05506cc3e19a0094f02a182734b8d1b5ea4c8b2b206aaee15590ec438e0260b71a557096b2c21ee9272d83c2703f0de2e0cc09071949e6889391f58c9258c08ac1377a10cb530ba89e55038384795cae8e536ab60d8259a49b842f9c80f29e72e0fd60d8715fe5854e8ca1d187e6c1bded034af05b86505fcc20b47a526308eedbcea1d78c84a64c7d5a9ffa0299d01e073531554bcf93df0ccc4a453e8ac1fdb615033b562f892a856067b7e6a361b6877d30852008d58818f32b68053119066601282b05120349683916470c6c9d9d87d3b7b2c9d98db308fabc612fd5e5c5fed3b688421658f9c4f3c44e5c97a8e35f98418beb0129f53f40277966bbeeea9e75df8b85ae5e8d97c9dd5ec3e8cb6721369e79ad30799b1747ad9bc56e7bd58c5e961cf8844088b656aa193ace44cb939e58c7c0eef7cb551daee35d57f2ccd814ebcbd206cd124c036003aa0100d258ef8fae19a0a260cabe6c058f3a2190e379c416b260f8952733c2eac1f2b157e49cb3c44c1161b3baa0180798fb623386fa24aefd8a0a1348caadd5d59d5e9ca46cbc44bb25b57018a0015691db9560db0a7d51808bfa86a256964fa64408509b73966533f043b8a1c92538eb895c7f903d7cf6ba3b0495b30a12cad8549903102b633866309fd90f2300abb0298336cdf6ca343f64f24fdcb2bd8eed4f3c8727f9f1bb6c78631f6b206a339c0351aeb49a3828afb00ca8c8fb836b426108415e93506a2c6861215c05cd1e1c81aea4614242affabca8d85394240f64fb084f1c11712070af509bbef458f4505ce8bf69eaa5801b56fa9484c6878b24672dc019272d060c8921a9931fb3e06559c29e42d2e11df92e558aed72c38f967b2b21eb2d4b619bcf24dfd6288436fd8f104ceba9579e22c2dca9dbf0e64d1ae66dde8afba5c807b9964b6694e287d7e2a07cf8612b93bea8b9f406801948c622d65f02deda7bfacccd46360894c7a6b68f7dbf7c4d341786e96ff2658065e5167952160214c34359b1511f7647e9a0018d604aa2e230df9645b2bce53ec32c250ca7bf1c0e5d5e8569d69e9a67d764eb3ab285a13433aacda044a3695c8b6642ac35e604c012fea33956d5cb49ad5b4302362ed2af6d396ffe0e4ab4990ad9455360ac8ca98bd2f6e9a851ed2aeec1930d74346cb347d3eaa0c4c5fb848326c9400824708a0bf58f14620e497e0ed4a6e7b5e365581918c2c7e159041df95a084296ae588f209822654a27023c8b874f74673564d9ed0d1be22f214d4a209828c7b6a2c6c2c7cf685565de156625b7603ac3035cdc7b7aa0ce98814a7566ecf879c8f25947309c8ad405e5599760d3538cf67a4c94c1e94378041aa8cfde8ac7ab5a2ed43dac9d58079f40a31ba47108ddfc46ea4d2e5ca734cdf337842b40452d495f1c3b1d11bb0b48bc4cd3a07c0c53b98da0ad980e51510c19930b73b1e284d6e31a3cd6549b196a2e09d54220c91f88e282db99721ac8cbf37de18220cca2308fe8827e037beadcc90a64a4525481ba57ff77769b0d8b14e806cdd26997a38dc1a1ed74155893896be8aa91ba6e688dd531c388996118cf420a4450af0260055a5b0a90b2ba36fd00d52e52cb21c5130d531f12572bd9690aa58192fade30d50c72f7163681460f6634dba25d0b3edec3cfb0796c4a30187cf2930a1eaa036bb9163063b850215315d57841a599d27d01558771c2a62fec243c642168e964c45903378a7d5587c9608f8680abcd99889655e7c8efb654feae8489453a42d3568b80dfffc1c609ab18e5d659c8f83007372699272057af3770ee7783e11391d50ad1a03a25264723f3902a0cdcf667027eb9fc426c3a9c1d81152c991b181e826232767b119a25e9aed3f2647322520a5fdecea46102b784ef4860601013cf51ef5027444dfe6f206a8fbb145a580a3b73667d4808bec85fd58d0638b172c7021f07f97a05290a505c5e1eb5060194c856a3a604b26a30acec48d37e406201c5762014afe246eaa2402ea0579a7b60d6e101db589c16b02fca593f1930482b6e9e17fdd937047a7528fe358d9cd14b6ba00d58458094d3218af30380468812ec75627a05702821982998829c863f7e1323dc60d0d320451746fe5c0c2f207d2a2011e7b1c59140580f154afbb4fa2ae6fdf46ec895bb1e9be0323a261abd9f9336aad0481b18bbabd016c917d73eb10592a805ae4579355a16ecaa5040386921acb04d89efd8055e743aa9b28908c24b5a5c85a86ba25645eac112ff10eb0294714fad70bdfb2fd2ca7137c4af0934becea7b607e4993c6dcc88b5d5a715d52062007aef5af839ac6577434154aca4497d53f431a3f50b209c411565d6a471720b717869b63340f2f4e91459d686bdfff7014d081ad2c48e425f83cc943d79d530f43a4377d2274ce11f1fc0289f9dd9d82378832f3ee4100c28b1bf410563e298e6c87839315a9986fc243cdc6dec68e55f2d451344e2930259058b45c9e8b556ebdfa0a6e1d67b8ecdd52f38531cd4c0403cbef6d2413a87068bdaab90adae604c2e32ef8f1068d4dad6155d60d3b30be08afdfd761912215070a6e05e04bd37c2a9fe32e6796add67cf76156a0a49f3046cfe63c0009340358e4a67a63080144cdf0215d8f97208991d800580ac80965a19a9b84d171b5d5800b2f657df9cd54e398a337a4713a688fccfd67b0226e00f3b044b2de8c84b95c8950190f0f786498fa7772318d495eef0b77d6ee3abd486f90f591bb7780c3858e2552363251ba90af69d5713f6ee2a615da17724b188bba96ed1892724b7efe335a9ce81f5b22f2322f074b2986240a88537feb325752b10ae833a47d2f1ed2950d4170853bd9f6516fa50364f94afcb49fb6610d6392a05ab62403ced6c051d679f0d70f9d5f4ad20528aa4022e2a3c2cf6aebeee328c8576bdb0012d531a2c6bb34a04cce4000ec2a387679d86c64fc34193edfb658437e2cc4360cde999d66615eaaca6090babed0b3e6657d69b0e64635ef3db30f27b562de4e7f46aa696bb944612d90c531483ac86e12b254af5cf40a0ed8623b0d04c6615ace699515a9572d65f8e8ff13bb7ac9a1f5862b0fb636a632089631e0e5a95598a0b5b4efc784c4f90ee08ee3bc5e6706e3a9da823c72eeaf3ef30195212f3c11d006503dbb215d662255d632ea92c42a116d038a912a3051427827bad61adb06e4029eb1f9e616f124eb042e0f3a51e427ee684975d103e38aa031769174fa396c61c5a36b86f33a1269b322f8f98dda00b02380de91b09fa5ee11d9c347ba8f2eaa00e3b74da6ab446bf619dca60610c0347f130272914470df25ed012268e85f44db4b11f586edc6905c10f48e96ca13d5872bb8910588f0b1bdac9adee734dae68dbb62d0bb20ffa683c0157ebbe15331c063b66b017fe4e89768498a0fc60de07f0a2febf24146ab6f8638f62d46a472c406b0873a6359dfdb98b7edff23ab10f0a023e62d81d1d1844a4312d5d659137689c5890c1fc0e6555d3fe46dd848002cf337126da3c17d96b63a0dffcd664219ddc774122d76d0488860fece4ece7dc9ef691055b33b405c65a40869faef1657759274316662e1d0f9e8af2b066043a87d17f8ecbcfb3590cf6a188a4f4f15b45c2ececc2416fb4575f8637d53e8608393b82661ad5326ec420f96af6bdab805a2a8ed04040a94b5eb2cb3ce621c56b9a276eca8037ca2d4bf790ebc468b27ffba7e9502985bd43aedc25854e79eba399c0ea5ab6819d8ff83e8ebf1627c506e6774cd787e29e8d7838c43370d0871a4daebbebdcfb359b4ae7a1e58b114e53a3478f6c523dbe8cc3b89c3d7855423753a9d84e9a5231f32fbf8d9636039710f049c071b2cae20572dcf174170fe2fea275e6309a7ed7944c7fb2b4cdf97e51c7a9ba0c15a5e76b99f5531341efc8e35988e973a52d12864b341d9c4eeab256098534f1520ba23d346e7761fe463683cf8206d4cc7bc36664ec36e38e09727fa5783de0af4cb5b8868364e0c017138326d2c6d12c88ce7e671036ad747f6a6455a0b989571170fd922e8e7ffd49679f933db76b98d711870063cc0327d5d61c4511a731347c07a585504d291b2c98883cbb9396699db2d79bff34af5c4eb76475a4a33cb5d38e97783a88aae3d2f28b85f655a4ba8797789403658e0a2909f3cf8c951786a4e64ffbc9c3e64c0acd4af25d50cf6027ede20ccd5b50e77cc82631548f3b190bb8ce4b32ab14d407a6ca3427389649156dbb3fd5063940b5c4611172c415e6dfe02006661ece502c1b2a86f6dcead02b87342725348ab69af66285380af570dc84e17cb00a48e1001c06751ea0187146e3bd21e46ac3294f5d4cb6ad8cca5c3e4d3c778f7b994ce10011020724c8ff8b5059dc3179a7f9e635d00b28518c7549d83a980d93075feaf630937b938c7b28ca37762014b0c32026a8f4752d22e3722a2e817003b97c44ddb96d8557db73613d2a2f39be763196391e10f0009761b8cd1967af390cf247a045496bcae8cd3d81f3e9eb6caf31b14872ed5a63ad264225b4bd982fdc034a6f0f0ae66c3e241b3c8c85dac2f6495ce95e2ddf2b221f018fb6367cb9356a193c9e4f9f3caddffa0196940da4eaf784816cee6a920579aaa54ae9e4d858a631f22ceca5844a610fc2be592f809b3506523110ce45d3a79c25faf875221e0aa75da9ccf0ef344d0d828e75ecdd8cc619683be5321dc466111b9375afd4977559e9e2ac0bcfc996512da184e1ed3a92e23cc8e63c04977c2ca004c11c5b32decc0c82c0a541e4b38e97c8c2a519db4a26dfed29c136326bab4f4444d1b551120b86980111f00f60b4adad1fe34e6175c4aafe6d088ccafd451b07639b1838bdf843bd509983d34ed36824b13667ae106953a6077333cc959176e8e7ff8e300d434e29f31418f783a9ccd65440a21b9ca1a44d7f37405111d976c610ec6b26bdab7e6c20c82e816033659c8446632257e00a5b8507a0843f442062c09e85efa0128f68f06a89335f5c1bd0c821d0bb5404d78eccbb0025ba4ed0ca9794426ec7e13161c2ea3ca7661980da608be18c1931ebf32b999390334fa75638d6bd6d838feaf671f200d4d0da436739d2b00302192e57c616019ea9e078d0c28d027b7f8722f57a9c69c84cf905174c5745f47b272a55211e252ea61e674926baccb450307ff095a11b8a9045437ba97422c5a656a25260bac9a520b2e15df2779ae9f75ca9f301541262b248b7ad3963e0d75503eaf97383cc56b192a82d129d7fce9274b9710f8e27544a1e8375f60faaabb739949edb28b291fd29f35ca91bc226a3820fe08368dd764134c23596d5ef7c56b9bc9022ccc14b69a295e11c45866cf5ed7a3c4414ada8637f0c5ae3a23d70658770055658fe4d8998a8cffae5a87030f04db910f0f3f4fa89584ae0850f44034d1671d00c4674060fe01df76e8ec41da032bf6d1a1da8dc8adc5efb6f5226a972e2b4774c405c54905756cbda654b07aeb3d11d8ca9866351a8eef76d02f903a99ae45ee5fd1ebe9e7f66b6df114850a71e681e7291ae8daaea8cf279c6a63669a6e01d6f3f7ff3a43cafc86848321a7496c7285fabc4468df17aa922505252a18d885a0677fcf25df5d5fe4b58a283d20ec5e130956d4501b53a522d2ac059f275595320346f7e863b8405286aa72645b4875d2e844c1b6e61eb433e73cee176549821b7641e92062105722da72ce094988d1e3e50f9e3c64459d117d212ceaf3594abb54a990841d997365c3dc81fb1f00c80ec78cb6b519d8b143e70e024b0c79628e9d6be6591c40f6bf822c37b4098f853631106674f6a435bd3763911a116d5776e1919cf924fbf2c1a86e9667ca464eb12a07abcb45406ce3114f7fb4abd04993707848a0095c6c874143ea6e828ddce7a3431072023e4bd946db93c0620ed535766894c66d6d4d7ea8f44cf4bc2495a9fa1a04d85bf3f4dbe43cfb0130f005f3e5446ae27421bce8e899489ae19d9197be90811548e48baf0f8061b993bd46109111a0766300e6d66dad3fda3d573b3358b0e9f977441962df09049c14e2df49ea246d241160fd5d0e80f27caede88def221496d29b29269a548ada19a6840d710c9568738d60f5e563ab84a7c7d233d09c107861f4332cf894dd396a8104bc476b3fa51b5782ead9a1bc9a95e4eb6052215591508b1d49391157b99ddb99ca96464d4794fe3618fdcca6e6008d89776a331d50e109481020ad47d6460400eaaea1c69b76cb6068bf9d9b6ce03e2dec8ca807c5d79ff7c509ea535b8ec5b0877ba66ebfc99b8e90945738688ec91f41f306be9982bb71ce45f913db246f5af330e112e3116d9e446ebc1c766c28c2bca966c59d541e3e3d226524a29032506380686066dff999fd30f879bd552596179343581545797c8d9822adb3882bc99033758d32f2cabb9e93468e807a21fa44f3f822448b5ca1c8b5f4e7ee1e7c1fd3cd6fc889c9b8eba4355efd2143b34e5e4d89a6d644ee6b9336d0b5f1c719556c5205ab6cda0aa6a319a6ec8ff2399e34890478e86c07844da01496d51ee4af307e6893b4b7694947ab55d52adf80caace2a9cfc3959d581edd82637958c98d416263e1e293b4c2397c9891359400cfdc23f14237d5c597a5ff875293ca69c1ee15a008909d2f30ca8e72b04895e7342719d2c1b21bfb0aa5f372d3b81c6853ae155e4e49c3b0bf385c117e604a58492e2b794aa54852a65881f215356252356aa158bc827cb68e417fedf0f4b065da0506f87c81e5247f2b05a41d5b7d4162a2526542a959512142b2ca61a58dea69259c5684989a0aea1aa55ca8be8a46a8b16138f7c53659c9c937576f47ccb31fd1ba6770b2653bcc349ac4817defdac5907498ead2a591b358ab4e9d64dbb702f399c61ae8cc3e1c73b60c66919e6916cc6ed328ddf80ab62f9552e20c8000472e4aa894cefa9c518c21582af19b73b628e4850244e8bcc71913a19d7d51d082eae96140d818c00ce288aed85d1a958c51d539115ad88c1227b614c3dd830d88f8045c012d03215889170f147d1f72429fa4ef06934061601bf88c2643e27e1e38b6c906583e4465b2f58044e5e1d0c620016ac8101cdc91bf4dd20cc075c3f6a9e33d0d1d010e2038136f17069f22e082dc3f134954f0737af81afd7c7f383cab7439fcf478386cfe7a3e1f3f9b4d65a977ec0d7bba3f1d5ac77a73351c1a8aab182fd304f01923bd3ff925ff8feb42700f5082cf7c9aa9ffc41aa10ce2f642d837a5fa40a0059456e55910a66a25a2e2d53431e62d0b396993e36010ac026ecac662c020e450e829c5fff02b009198b805f1c5b1209392a1dd1178f469186ec29ec0ec9a98a04bb3041b8f823cbc8acef6c7f0eeaaa505f5be8636dc50299ac06c9c8600ea82a86e5960a638cf1de586b2c54aad726b977a907e436d110968a446037e520b239b6da3a39febd4183c7d0af4cd35c077cc536d5d6776c593a61398a7a87a1b6d9a4194424fac710850774ad3f24c9316b718b48d275bf7e8dc203fa7e9afc291491806923257f17887df9553c99168fa8c0beabd3e9f00ddff0ed76de68d924fe5200d5c92775e83405fc17f5c3c95f39490154c7399ca23499dc3b6358c78f4b0ef07cd99cf3c5fae2ebcae30676c62f83e57bb50803010767a1c09282febeea9475f31dff77bcea544d5d59cd5dd531110a1a9cc041cf5989f2e68c31298f34b95f1cf316d35b86a2188e9bd4a34cc2fbc3bd77986efca40f5b25f50b8fe3388e5b3fc981acaa9cf1f77b621e8838694206232ce27fc931f184cdc11ea104c73988c0a010717208ca0cbcec8121478f131ebca4df5e0e10e8e4a8a1881c383e800312941ae08083bef2c78189930bc5051c55c48913dc0cc2c8099ac04187221c6e37e080a3238b14f051bfbd1bdabefe8f4e98b072b80106a821befbedddd01b8293faedddb0eb2b20a5502ace553d6ee871052ff5dbbb61477ac30c276a871b5ed8e1e71557706e83148fa78e401d21e4a4079d130071b3df9e4e0f2753f0b2df9e0e0e05073a32f4746a2ef55fedd940450af8a9df9e0d4df46c1072520427fbedd910c4c9121cd56fcf061210a183b52147068ed26faf862b4cc04ffaedd56004a5067ef6dbab4148dd3d9b0b7a3508e965bfbd1a804a2680e5ac716a1071224ab5f2ceaf077c2899978b394848664e2b5e21eee08118cc25fafa3f90a1afea4b0f7f0003afeda07bc108724325a0c158fd02f9e0065dec17c80744640ea54a06fcfe1d63d8e8b118d7934b4d647d8221ecfbe5987eacb72904bb06132117a2431a21a209073d9a70f490a4f8aa96c16ce4ac8a29492bf1c047e3cfe4a844c588c4755cdce598de1af71726e85f5df4fb9fbf063641a7295784fc624aea178b899e53d27cd30e7cbdacd8a365bf17fef29246e4588a6c0a05253e6db2d677ef9d6eacb5c63bcaeb5a7118866118ee27af0556b8b3dcb1be384720ab595de1ce6c64357c1b5a762302a70d3e61ec0b4471f0914689a46b74fd98ce9f63321de79c57b830f8c31d9044047fa860e10e7518866118866128446e0bc1b385d0edcdeadfc84481e107187ec0e0b3797c543e336c392603c74dcf8d2e95d3b21ba0128c00da95b0d3b21b202f4d00fd78f9e5d88d8f5eade7375339a02c5239a02a80a40032025404899e53b99e6f782be00fbb5269c9d0c9f8e9e97a45942498277abbc9c204cfd9fcb42c95c211c1f6fc3cd7f3935828c9b232ae8a56c0546e051453393184104068d1e2354367c66d05bc3f2c0c6e3d7f4d9b63eb0a58c3d610d5006b8a56c018b0fdb668c06c41956d40a18ae2e505e575ad9788ee47bf5ffab8b17c55dd9d917f4945baf897bd3dae9a3f04bab19027ab59002a1f1ec9a24dcb5cfebe58cb3210fe7ed886402d6bf9f42ffaf743df95bde96f143b1648a8f3e1f381c743bf3afa0d7b68e8f7431c5716eaf42b63716bfdfecee2cac256bfb2f8c255f3affb48bfbfabb831b116da76f0ce78e9af6853cd5ffd9d955f7249e7dfb22cb1d9cbbd514e44f2314a81f135ba7be07cf0f0b04307d04811248082843daa12fe982c69f15a445a3e951c2a37959c4a0f2a3c291fb5cbc26871d768a99fa80a987cf157a88bcf2589d38fe7e037182c0c286430f563786272a72e89a69189a3265da3bab7fc0475117a98ceec5a66c057928fe423b1a4df79da94a44e9b92472101495390da9205a5aedc95435027935297bc137bfaa5a7a1d10c690a1290d4962c284b14a94582d40291da20a41608a90d49ad0fa4b6e4c9e0b92ef297a0a71f7a62394e29bb89eeb08082b04a4372644e65f68deeb0de447754482a1ff6b8d978c0d19083e387040bf40be203a2f2b1234715568adeeb62d52c5315054f31357a0241a25f62044fa574faf19b093df92e8c114176416d9690a8ef076c8788fae997f291fcd66a91266611de22d2f2a9e450b9a9f4a0926bf95abe96afe56bf95abe964f85079f3076291f359f8c1e1255852e2aa78dcbce6537329d3b8de101c4d7999e991e2d73d99104a805f5cb2509175feef4e3394ea79fc9e9579e7ee7e917e4f43bfd4871c4425b979dcb8ea3edcaca8ac9972f9e578f97ee85c5ab6d516bf1428a2a8572823ab5b0bd7abc785eba1716afb6c50b2d6a292f9deaa54bbd74282fddc94b877ae9783fbd742d6c2cff26a5cbcfe5c772da90cfa2515337d19d954f511c20d2954751f27180c01f03497b614c9224090d081556e0bd7c1dd3c65811137c31c48b9e17bc174454bfca52bfc2507e551f15ea4e6b4c8ee3f88deb9845f83ad35342dadfc6587124a6cd315a153adc85c91f13d4329a8ffb1a82a7f7bc20f28297633460cfaf4a57595f6382a998a0dad798e0494c1015133cc504c737890976d50b5feff1415b5170128a9e362518931ef39496e3b74753b8eb228fe97a8500c9f5fce24c3bd3821920333e59dd65f5279390bf059de1e9978f050a0df51c005f0929003e1590afa75f8aee5a8da651936ea23be96f54856d7e492aa343a625402dd88396c55533ade7c2f0e42e0c0dc8d5759a0ff7c5a3d37868a390823552a4e7d7f174b619de4c0b2e4cfe127406c88c0f3ab343675a7da7333d479c7e57080f3af04c4f3bd393d5ccf3a1333ef995677e6839970976a8665b50f2d76239770aa328abaa28f87a8b00c2ce9d9e86b25a96a54659bdd4e6aa9fe80e2ba58fa388a67a5996a49292300402460ba3e86cf98cba18f3de190c779c5660ecfb4b17e6a99fe8ceca6f7487e5472b1ff2e4180c36f7b83118505c55a765fca6b1b8b2989d1b93d3af5a8eb9ecb2fac28db90cb9aa0ca6fe2dd4dd22803674c7f520a0333efdcaef82cefcf42b7f0b8ad33233c475917fc605f745fe19209784fc2aa8ed692acf427162789aca0cafe7b73dcd95ceecf44ba4b5a4333dd7457ed7905313ac96e100c16ab58c0036fd1a2a724d20c00e7c7a1aeb63789acaaf7cc8ade7d3af051569f72cc0b853941644698fd280288dbd30f96140410483cd319a118ed26e34db85c9cf6f5a46e3e1be767031392da3d1705d865c21393d7fa6e1b86afe2ce427430206d4f393e9aa06e9f94bd2f581f4eca367213edcfa9ee9d1af3ca6b49ceb87afa7999ea07ee15fd935fd63e9a18e5f451375fc04b001776824ba6e87d87a4e62c80e3d7fe9c2d4e828c6d42859be30353af69213ddb55ad73b2a6fa23bac2f51bc637b9a4a4aea1726819572169695f1c796d350cb6973faa5a70dbff19bc86f7776354f0c2104105ab478c198308ab29a1fe6b48101cc6afe97d3e6f4ab82af333d45007bfe195e8eada7df4ccf8c4f8eed726ca5800e46ee4ffbf44fb34031fc51460325f0c018cf813f02be5ee0cd0c4209b5865c44ec781b715aa6ea518e25a7c60d265a5c02e2ab6143023413a83185105eae01c4d7db02731eca44b9e80890e45a167e9863f4342f81a611b24b3388b1431d618c9ee802bedef6a78bc4d1448291cc6108f5db409246c88853c11bd108219584c85099d444fcf7a4d20a3e02777a4796d1c8eccfa7793c7ef8e11314c2dd4638a2221ac2b5c03bdb5f9a9c861717f0679edc063425a61b5f6fcbd3d9c1e3871f6dd06db4e1da12f7e1eb6d7941424325ce8fe05a538906b8d6a4043439f1254e28d38e10ae3de1393841f9004aca64c3cb589463fac99d6adbf6c68db66d6fb46dcb69c0d7dbf28254293c0abede961754e4e33f16d81689a12a46caca24035f6fcb0b2a221a12c2b577d567308eac8d91062cb6bd10ce3c97b5077e544142437a03c1c91d0ef109454e027134f9409a66f809121a2247dc851189f3f4f054be6749a513d7899eff89f14796ad30deb48c86d6198572c6209cf107890f2ec7a31b2ae2e52039027c56e513810e78805922629e261dbf7c969862282995a41012793ecd4b391871c69c5167dc31f2f0196bc11186b8c9d483ff04994c38d0e464e239a14c30a04eb88f9dcf507e2155103f4121f2197132927c2417751a28a92652aa275429225871202adf8f5db14a4958dc8823d891cdb118e30a7f01cd0a4b07f87de227486888e80177b6f703ed78be9f2ab23c1d8f437d28c7b49685ed028ac85fd66002a3a0b52307e70d648d446fee237dfaf50bb90e38a468fa8114c72b46927b80878f1cede4a0745a3b788c483e209549882440926537f20bfff46d874d07d77272f0c4613550b7c11f37a12098aec583c291e0cef46f8c1351912d4bb3148479723687337376be30f82c31bd5052329580af228fb983e3cc1c9a3bd35fe251649901f20bbf29640e9944bda8a8e7377978bb9d4c8f2ee7649dbc8347cb4dfc03668e8e6a32e142a4c9c92402f284e23fa04e4a42e0f068a172ba7a82c27df821cbd4fcb205ed7e647c32ecc74de374ce27e2c41c4a8a2749a94a57a852380e29ab920d7856ac1e582eb478441e9147e4e13101a112a377e6564c3aac56588ae0abc823eee4e4ac93776493c70fe2ce9de918bceff155e43171726c3d73a64d9bb76ee25691c7e411db6ed6cedcc913fc93015488280bf78913e0d4682c680088fce267152b381639d4a046ebeb6313b07e191a60c75f03e403a2dbcb6971e87a395984b79e0776845bff49056b879c3e89b50125e1a909d1d70bdc3aa0244ef4f51e21515c282f23ac321d138901061de3da68440cd896cb25459f65c702f9f9f0fd492acd2af6bee170410a239ca5e5d7d2440bebda71e55c3a570a5c3d2b415866e066dc48e60bf0658a1530b593cacdb0cd9081a4424be095b04353b48675293a52032c7f40b6b52238440f8f08a9f4adda1570553422c5b782d49282e510654f49a4e40583415290140c064b5f027c2b6089aeda1dabd4e3f578e689eeb45a1ff6185b682af7cf721ad1ae80a9dc8abd30464801cce9b384fca04ded86b3c1c95901577a52b9b23ccbf22cd2d244cbaf8575edb872ae14b874aa1636d5c2a2b4b0272d2caa853db5b0262dacab67b712842501eccbd0cbefe57796f053c36ad9cb8f47bf72b8972976bc00734c868e1f6f054cedac802b206905345740232b2027c511eb657bf9bdfc5eb69304dbc186c0782e667061738173a1438a2a8572823ab9c8c186c076309e8b195cd85ce8e0029702e3a960bc148c8702e39dc0782818ef04e3b9c8952cff26a49797f63cd11d95f349b3dbcc86196ef6c4ec3703cea698b525ec78688a27954bf1282965e06636f476cc6e392643c7cdef6637c369d94d91d91340bed94fcb6e80665300fdcc80397613a4779be166b8196e869be166b8198ecfdace12a135c015b02ccbf2bce1a5723b16748507c8c8ee86bd3044e06aa0a83152435403cc6a5126217f8ad6b4fa15b4b1d9dc90108143425b92d0ae80e889eea0b8e4373a80203b1c8f8c5c8f870c1e6ed81bdf0defc608d0ef0609a0a01ba01e8e869e0baa9a16ad81a2c64889d61099680db0a628ab38ad618f00020a82ab61b39adb9a600d51fe29bae17d3a430706b755d16996e5288a2569c6ad8719b8193a67ab05257afb69a23b2a2b2a336c336448e54a30a284ddcbefa509b384c1ade55dd09aa00bb5a9a5284e5a13c57591bfa606f745fe1a239784fc2dd4f6b4d6b35ae94fdb92d600d3b4865d017fa6e815b16a59352d93a15b15ada4b826c8f8c122e4d60ad8eb5901531495919381634165dc64d866c8d0cb0961eeb4825e8d47015c3bef9c51148b63ce350f8583ebdfb01aaa035ceb2d80f2ba70482baf6bfdbe1f73fdfabab66f04c2579ccbe16ee8b8361d74906d58a763a2e3a741c77f838e5f073c5d440c77180822a6588787f04067d33a9320f87a591b2151c72cc61803753fa00ee803629d490f5fc91c0ef5df85c13f928aac6c2bdbcab6b2ad6caa2870a9002e160afbc3df7f3b2e352a5f8c73fa5a875b7fbefae28c2fbef7de7bf3c544e857bf1be0c9e67aee1ba26a0fd17587d0ef7900520fd316c8dcc20670179fe9ba42ee5ae720adb5d882985e1deed73b6b326728ca0b04c6599b7aef3014c57124c91e462312a92c4d935f987393252525aa1ef0fba53bdad055c539bef7ead274daa878c0d7f074da6cd469738272828282823a99a0a098380a0a4a09a5e4444141d1bfc9952a8b130bf8fabddc95bbac62d5955ff933ead57590972691d6ddc426e45adfa412f0d52523a3630dc89c06d8044cd433ea6a9133ad5fe53a75f8c3cacd35cad5d52cbbb4f3c2e47e5d5a3f219674804d35fed3325d1a424646cbc8a8325a46cac8ecf4ea57a67118f82a73c3c9dc1347795d250d1b37062c2ec900e79fd632b39d5682fbc2ac37976287a61ca158ba818c8c1e73a318d2987ae0eb655996b5f12a7b91e0343a1477a6208428c1eb5788d09010a1a0eec26feface2746f7dc67a5cfcd52c006a876c565be858f3d15f55ef1a5c6b92c655c0d7fb13753bf147d6321a7ba71bb0619f7e7b2e04fb2af65c00f6207c1e607f8959e8e20e5b087b2ffcd06faf56d457b1a7eb57f5dbab09f55267d9ce6a0bf79a545985912449921ff49deece5ad9f1e3c841b66fb55a0d070193daef0ea67af25efc3d3abf78bef818a765e68be58be2e8c5bfda279625a32f3146cddfc938060db4066a43bfc41fa112d02fd1d5734a737a0256d324c9c5da1e1fd3c410c7183559bd7161c4a7c9ad16f015d7ee0de3e4986dfcfde2e39c1b7776371ac3005915ef35d5eeec45171fbb20eae03474516cf5c02950ee0c9055314656c51836355915bf2502aec216bf26cb304e7e892fae661781a27843cbf03673ccc4ea3ca2a2bcaefc263797b7cca6f8a459f224d2771269442a7f54da9825d293a415762fd97e710cfcf638e0eb254d8cac867f428ea53092c931d2e8cb224eeeac86640afccb1bfee57aa39f6f82b260f2269405d373fd0a4957bfc25755c1590cc8261603f09ffe9466c0e4f19dedc7278c114d465928903ae949690aa5f4f6db474f9e2c1289d264156591d24d669545058f91d69896e0e2d34c081853536cf73fcbdff14332c44fa215b8279c9e26a73c98763a2d53cb11726c3fb9c99124c990fc11f3c8ea7e6caa015f710ea7a61f4bf09434a7653232b97e2295e16334c418e371f463388ea30f4721dc99f9fbd53285f145f17ffc13bae27edaabd9c313942f53509a1394e029bfcaf402a4a0fc3dc3d30d6501d3d08dee78e7799eaf72c59b743976f2fb4d3f720ce5f79b7a4c3e3986fa1e2d5bf9edfafd269e96f9b44ce5b7c9afaa5e626252824f156e4660b721b35aa3e555be46faad672de0dfe55d5216fe5ba0b17cb2a0a297d89f7eed1b477577f6a2ef5abfbc187726f6fda827dff43432399652fdfebd7ff5291fa2292a540aaa5768ca973725a5af529e8556e0aefe9e36ab9426abdb26253dc146955e6c32009027c6ed61bac2eea91f9d280c204992243da542774fa12c14461f7e98a670fad1df5e6ef35116fe710f0b2c8f77fa45435505473df9fb5d298b019a6525653120ff8a75a35e019c244c4f40699974265f8e9da43baddf6fd26d93ae948de0a8d42453321d9f46ab80bb50df5f427f7dff89ae8f5b5056f8addfb51a2d5d799577c99d8057a3e91386f8e39b680817663f8aaa57e6049af0f5898031499224559c80022cabfb47c8ea4659d0117a81ddc93485717755cbd710f4e75f41b14ebff6aba0b807c53bfddacf0a816957aa5a867b542dbbb4adebf1efab4937ea57480f6ad0576ed2f5fd21dcd9ede98dd4822aa76ca5168f2097bc055c9335b60967c35c5c11f2bff8f116fe12646b28880c7b2032c1d70bdc0171e51897eda8644cf8f82c8468460290000073160000200c08054583d1348a622040cd0714800c6b6c425e4444970723816820c8510c45310cc5300c02200c623404903186197204007a1c06fb340dda0985040ce53995cc4e1ef311cc50e7971e963e112d40ac5d357676faef5e16e1bc37f58def27bb330fb530dec75f4bfc61f3b75148629831669509e16d09b415ac0f6d0ea24e4e5764290e1e63186fbb3d1415eba5fb1c29fa882e963ccfa59788f4b6715fb4c98fddadb6ac5164c4da181b0195d15f519d65e24cd54a25d9ca87230ad1489d4e7326574d4152b5aba82541a38bfc72a14737f72d3c1b341a740499fb1da408a1959a3cbd8abe2f4a8f4e3bb7cc5416712efe778b5ee63848b766cc3787b2f9c6294e6ed00b378fd76ded970610f6c6a8652e86a34ec3c52d825886d3abb104232e83b11f019f447f0d86a5ab526c8bbd27b8a92e510183b8492a9cab82aa2ee6a62a38d4277aaf819bd3e6c0cda290590aa0ef4434d3b71695be61a8742cb416b5d1fc9251c231e412317d137ec14c863c548dac434460ab512dc5b03285fb8ba7e10c463d0f77fb19f20e0869c003896fa6b36d3d891170afb776d3b53465814818f7e3611c06e19631cdb6acabcd495e903657fd7d53489edca99fad65a3c5b4d3097fe39014403e86db85089ab1889ee8cea165edfffb1e5b4ae9f71613219115c507fa84ff94ac9ad6d8d9a821049226c5e10e309435f6590a8d754deab8849ed9f39fa7898bd3e43cdaa11587c71da1ee0410fde6ad25b0309ba7846ee0e1baaa81c5dc45092d2f44948d5fb7e53cfbd4839474a9e5d6868807603b1e95cfcadb1873fa18f7c1f3efc6248aa91816e76957f7c1b62d3ae421378fdd6bb08a81f470f4624f79888dcb7a55e8246439388dccfc968a484705adce64644dc69561b09afbd48fa62b865f1fd2f2b26b00bec02c922382128d173e20e19cb8d1951ded02c22d8da6d3727216ba5802470dd456df041360773373267dbfe18d537bf4d6330d8568f0089f2eb9ae245367fb21c4f52d21e86aa87bc6f7fd5fe9b7c37f8b5c3c21f0f9b20fc6a69ba7381af7c3feecb2c432370567214ccf65dd20277c904a1c3f65992eac6b85e78fb4896277e9f7b01cc0ef16a9bd053a5acb425e007f0456b5bc04ce3c92be0d14b712b45ec7e43795322bbd30c75ab25f9b1039623777de613865085cd702c10548aa79558ff3d698c1930fc2bbdd78c9f25544606ddcfdbc592ff668204ce5335944392a42d9c44c760d2868fd4a728a347f494467933eb4b4a3ef29036360faa2d8a67b7ce4c05a762e26d94f143c5836da498af247a16cf425f83ef6c02acbb050369c821c9e83aad20329c9fd8d9c7bbd3ca41a7459c6b4e87655a9def3337148c4bb211049aa494e06df50363a1f7dab2b60960029d2cb7d7c8e4fd8fd40e02c9f3f17cb42b761b4c8e9e60be96fd34b989dedf50961c16be2b0f67235802faede77b89e2496a9af2d736e9f7496eb5e4fd9d9f42ef8773624c785b65ff4b596528a91c2a965aa2d94549b5e0f7d8f6b58b16725892f51e4dc76310cce4fae1480fec7691ffed140e2670089f25c8417843919a926239492a1b038bb74f9deb6a04a6946cd40c3b6b90d5643eea6ad8cfa7348b0e68a6680fc9a1ea7b2420f07ec5cf849d9e63f8a64feff9d42d125108bf9317911a5b3aa14da41a8b24f92f88908082d3f203ad668364c806549f542b0050a77a38bc14284ad21f2c7fa9cb24384bc5257e401a3525d086949bb14d01ef09b5952f72050aff130817825a92ad4dce8a4cec87744bfb920203f2ae50eae1ebe484dc5c7bde97d3ea07f96d3aedf6280f6921353589b87a722cb312005050c20c6f7204963899568c080e6208e845a2ae2d68f29412cd5d184c144dc33ba2d51531486e9d9b5ffc43b407562f50e2f37f40277d809dc48b130722d8684e94ce91a6facf156ad335c82979315740dc638ec9a50bf359b06229b05a1dbfb758586987768104894d426f14e6425704da94c2e6e2030ca1ad721721a110e7059aac4a9fdf4e87b753e4cf3ef414186a5ae846352354c3c76567cf6eb6dc3a5de69956071fb2012176ffd468a146340f42d927c2d77db7a30bc08c6fabe6450ff5408b4bdf179039e87c75ee315f8781a6d2a2e41a154b3272c14d174096919f60feb11a037ae5a709334e1e29d623e52a546c02ffe666a30b43337d42e6d19579f584787230b0c8b21e74e8a22fe55c862ae41ae92865246b521753c0eafaa69ef4a61df5236a893a2a7332fcecac4e19f3a0f6a2d7cb054ea375179f819e3c24be68f41be1e1c5a6d1fde0735b1c9c49a699b9fc823d96f8b1d30a8899ba660557ae5c86da1a0615068315340d7cd5407ee91a520c21d2efe27765818eabc1f0e8df676b453e66b8a824d5d5f1a3716d158e7ea492c6a3f6d24bc9e48f90a641c561ac29071defa5070e12a55d99084be8602fc96d4b7aea839d4bcafdf0fb46f2810917cae615ad7c7b0cc18dd4114681f278d416875a5f410a9921e4aea1d7d2353855a27bbabac1a330d99fb39f359ca4729ac144b80a5de357011b81c39ad0942187f0d373b24b4029b43606a05e3e992a2e0625ad1cf6a88772ae03b945b2dee45116f5057d913ccfd147e790f0b332d35bf207a96c67efd7157d4e31e011b85e1d612ca5556ebcb8af35262bb62b50f505122550cbf7c5aa74a1a6b4866c5a80763f5ea9e543b1355ca03b41d79c29c1624f910271b738e0b6bba4e6bdd6979385c98e4519aaed6ebbb2dbcb7d4e303ecda2d3687e990345df8e0614ce73a9994c9eb92ab6a24e70441798f494aeeddd362db9e4f6f4c6b04317007c8f4c0f3c9df3b120c69fe1ea3bcc89a415a27fde2346faa71c8f014f234f22f4eff3cd5a5529719e510584a07ee8e5349c657b551e749643c63e81dc5fda5b632e8c3ecbe8d9027785e10a7678bba85dfb911466e0afab096ec444d09479a6e5b3b5e0d3561c25e5c26cfb0590cf2491a535533bee46b96b713deb0a0f0f451829796ca687d222b4e8fba195ebbafe0a4e01f597c0798fe61ea880ad6156c488a6f865b348e61c1081b17116e7cdbf7eb5a9bf2aeb5b2a903a1ca5509ee252df0f125ad1d504953ceb3b7a046678b52f7641aac725d0a6ded9ceb838c331dd53a507de62793ffffb1414c91dd50fc0a6268912014c612842555ac11f614e4c42a01854982b693a134055f02febcc4ffc8c300fa45ec6e66753e9e27c6f096f256b4666a0eb36fc1e9d120d4883d9d5c787914e1f111f54b65ecf2c13306109937f8c6b0fb70710fbbd20e11e894bf08c543ed386dbebb1071d7c9054d0d76ff75902042df74509a71f22102f7b673327f023717e0621197b23c7f094c079b0712117ca011cea1a8fb9e027ed2d040d522d355997f7a9ee44e9269ba0d6163848b983337c7e8eddbe937ba193cb4f2edff8045a756224f2f73099e9f74462bcfb96c870c4841af46369db76180ed46775793ae540cc7ac936f9d138702d742164dddf29572a251e57d6766825cb4ec1c407590d38ce2a6216256776e893d723510b5ab1e25b64cb6275e55baf028918af6b273886c4b659d8c4696ca9c4473e2b0f0da54df3c3fb29d097439e00aba888abc9a5e2fc244658b1e2eb01edac4395912cdbc603b6b50e4542da2733cb927f6cdf00832764bf6d5504c98b12e8f02433b4a95e947b5ce62389f9dd30b56cfa0a2a1fb33748eb09f6207d1d1f9929bf909454a7988a4e86fe80f7f4b622a53b0e8c2a5628a9a42a81fb895fc5e65d5b8b93e8e3eb278d298a30adc3ad439a5068e6ed0d4cc38f1057515ac7c928355838ebe8a2020f55d2773d69a30a7212fefe26b69f7ca175cb601114ed5f86a76bdb045f50a2b183a03060076807df13973dd29e43cf347a43016856cb642ad8295a1d075804746dd7456c164c17245f20d92b6263f38eb22deaec7a15957f0fcdc27fa52836b7aa3e50a496a0646036eee48f020bd99dce795779582e08dfba25e27db10cd4fba3873602113f0b166a1090822d3e8bbbfc0b5b151cdc862bd8f4b6118d2ba831ca835efb4b4055cff2cb2825d6b32bd08747f394a7ae9571bae3da2634c3d31dfabda614f6ab5882943dc2e09efd04bb9be9bb8a6d4578e6cb160a32dcfdc0684d3dd544e41ec1a456a1d0fdfde4cd57de347a8932e27cf8a31b39fea7bfb60e49b3fa64ec1045b41645a23650bc7fe003771028e8c047bff8a9ddf875dbb984245d4aee5603ba0b90f03248cb80c8c720b95c8f4566fd0653a5f639f830e3929e03b5b60cade829f049881c4a5992801e04aa41d729d70e8baea194f43072d605ae20d8e54b4d4df7348d213e7af10cff16c3dae934bace9312ce39a8ad8269d9f60b139b8922c5e54da95276864e13001ed5699864cdba2a9a7bd594f1308545a515b462288a383632312d2df2232a337b3d228e0ab8119599112956988abf2ca4cad72dadb37279ac08e27bd311f3e2b42b58649af8999845f8eb76217be02dd20eb49e33e6ac64f6d1ddeb31fb253db5f070f40165dc000bd27077f9dbc6931f40a56e1203b7713a6666d39e285f157e91e6fd266b5036b39e8c34f5568039d68ecb8da8605a9a53452d688b16a8b2678a6b903c4a4cefa64b2e1cde2d4135bf499bd00f5ff61b7808b8ff407c1c7f7bd4cf779ddeee917d33f0958026612f2eb7dda1227f0eff1eaebf470ed2d823dcdbd764da320cb63db5dde068ada1e53a675d9f37f0e128a64d102dfcaa0ab7672e80b79a3e1d82bfdc28ddf3b302758a7d7c25f2577087138a258b3e5df4444d363e58c469fb848e5fcf344445729d19d5f041c546efc5b2a2bd1af3427a625ef0a78983b8b05a4d34b52e9570633bf71e4eca7be52a5ae197993cc2904000a2c9a9066a75cda9812e49bf51c72782a13241c940265f9ed22cac3dc508092ae806c5d96907872e5416039019207e78b29dc9bb1a7acaf3eec77dfb82de15af96eb8524b0555d133d0002bba1f0a0f66cd0c3b5499e928a5743e4a7be962e0cfc9809091784a7e326a83b600954f1039d672a1051c3bacfa72ceeefe3ced8e939e6c80a0629cbf6d4086586aa2276731e44c978b8b2940a8421d156e7e3edb1243f65b6f64c8daeaaf9c47607949dd0ff107cbbffd927d982259ce7ce2a42020f25517ef814c8813d9945d953de2a839eac86eb07c8bdfbc0b5f2ce19e1608f8e780978d37f7a0f2bd6f13ee2e33e5dc2345227c013f83808107a3b2042c3357448983c611a3b52e16be5eeb8fa4cc04661bba35388ea2d38db20b49b7ab3e7f8e7ea9092e5db242647b727876f3abd6989f05d436bf0fed5a30b3fd81056fa2234e94b8e51c5b59feffbcc5c89642768a67e576c0039ca06a4161971d2ca829ec7ac75b5520c23b32d443233dab2bc3fd60ad4ef2693026fa57470efbdf641130619afeafc7701e3b600d41634788522b2c594dce7ccc622573b7337a826dc63d3389fdcfcf3b86d81d7eb8e532f93cc7e1ca8dab9a4346f34563569ef705134ffcd6845f086b72857ff5c8e0ef064456d5c9c4cccfae4069492fb7dbe329e3a721c26372d2a0cf96cc066def6f23611184177dd8a522c7066f32fa032b8e26a2f123d744d309dafc62176fa0522f57ac9006cc3b8d28c9b288f9414f5e282cc2639fd508fa15e951148495e48cca3f924c31cdb226b720d2bcc9161b4af6d811f1d98109b22dd455abd7794e70c1e7e729a66e3f745fafe0035b522cbc0fb0d4c61db8f4707683ef50d3030db48783601484104599c081ee639a08d2e4fbf0fc62672d64fa1e583a38ddf43c72308f369b46287ba673cd8f2975310effa8e5ec3390e3f0017eb8126bfb540d3c079b2990f07d90cde7f34008821fb68782b204f87657714db6388cedf8547a76b4d9dbab27669980c4ea031f2c1dd9c3f6243aac0ae4d50b350fe8b4af634f6cf286ec12d15e9046fe43ddb94c1dda7db4b95a64538087f1b3d8966362c8761dac17bf5f89604860a471ca2f1a38045dbcd7ef9a9bb57a76dc350d03abd08616008c34246ff7f5e29ae329e1477887f4dd556ac95fa184fff26e1c0605351934dd20a30be0d855f4fa3ff398a727bca8ff862984add842be569f4c961409e34088113c7abd70cfa0eb51c2a7352c148a65eb91211535ba65cbdc18c26d92b00baaa17a40e06c0ede054ec43220cabe10b16f68bc90c17a753fbc925a083a0ec1f0a3c3c44c509df8b580219bebebc3614a4f74727dc68850dd50ea8e588907fbf0d75bf03b546572527012d4c30adf6f4149aaaae11054b038f2d6e878bc3e6f9a6029844881da23f4e6408619c8ee67a5fff750564557b6cb8ffcaa3acf24274b11a1acf13c824c5b5307c4b38079f91d2c05354314b90a5802477cd252a872d6ad480601fab0ae90507abcf109d822a5844cf486d195099b14eb210002b76b51df6cc7edbe145a3c679dd0ab62d0b5dae5044f9b5762b04678d859474b55dffb1866c0b3aaf6113fb0dc9a085ce41f11a6b58ded3e8e4e9a5b9318d7c4ca1abea5640d8110dcb15ca040fcb10863ffabf9eb5768514e0942e4df953c0d32ee715bd47edfef76002b01618769920c624857d0b888ff6c61e2f42c4cec8128a07ce911cf57c713724bf6bf0edab7ff9a6cf0b8f4d45c5f0fa2039073129d9b831646ac70e8310b0a0106c2b770a90dd926ccef0862ade2339f122c823fb91cf3a452241011224f1e3efe24d1ff37122d6ab4c19d3ea838768dc1925e17f780e0853d1e1eb23d3c86c8e778d76702589f4df281d9ab72bd037f390f55888def2789332242d28ce20c282da60eddcb605107ee64b06807ce989dd2503415ea70e0a4e52ea820fa77d366b8ed5171cf0bf758fc14e5e1f44d779c105cb41cedde6856657aaa565c41d15ebc126a83b2a0788ac4b9016f0ca55a88a15116135ced7888d152fbb80ccf13caf094d0e33add0f6436f940bf52f674e671c5b77073976f6d26a2e8ce64d3ca23bebb6ecfd636c4bb422b9c85cdf3ec0fcae7e1dcb2857c760c5dfdb28efa813aba6c5efdd70a94933be5d093fb859b25d6c93dbd66f915ab6ab1012cbacae1bd79de719be90a4874a07d6c0f5a17aaf805983200feb420f5546276ed97cdad3b8ffb35694339c7472b0d29a3a02f657e31877d0d2f2976f6cc3c91a702edcf38d13f06a21372402d813b2238ef5c61fba206f4bd8b07eb306e3f84ad8ed8fdb5ecf8342898762e89872c8c26427cb83aa49783561c60f85922218e6d7375053164336dc938ade2bfb287bfb4648197e810fcc2ad1b43356d9e13535498b51fe17330544b1a62f1282278b913162a36cf35a6c0dcfc0f3a27f77226688607ee9e13c9edd49bd81189bde814057b146116b9e53521a2061b5b0ec870ce1d50eab5c7cb2796f75c26ec7b9962792dc8a2f4bf66857d6f33b46546c5ba9ee1eeac433b980aa34ff346d533d3843f3538dbc791dee641b0b780794374b0392ad43c78752248d40957c08ea73506d2c50a4aee24072d4246f9eeb104b0087e813224a520e61783889395857314094b1431771e33b3ba2ff6a6a8a9ad12065714047ab3ef492d4b2295888d015b1c3b31a2292f3f00a0c00225a921bd21c4854f0faa48443170a01f825d9a52ac60bc48af1febed22c214ab651af7b3a91ea940b158900d3bf1cb20fe412cadcb56943bc106c18b29c82e08fe6cc47fe2a220da4c4234da49c96ed96d24eff5795cfdfc72b976371b636360bbb722391fb78762e5e20e7b8aa9bb84d208a5fd63be1d7d9e492596fa2231b1bb9d604907d6026d1f4191071df14f3601aad22903128f43536c7bb80732cbf74c8710b9d9cb4e517506c647f1e7704b6188aabb8ad812d9476f7cf04d490384eb3ce647660b8bc565f41a5d6f362b1aa5494f140d8dbcd6c208642d48e0de616f70c9c0602dbc87432fa4306ad32ba2fc6f2823e21ae50202e557f75ec8d26b18a67644eefb85b2aba3c3ec5bd282f2567bac3473a0dfd8b8cb18d5e51f2ec2408f5578e3f8c5b4baca5ca5e5e5a9fa56a2d2d082769b3729b5d675a86d0cba1dfc353407daf0fb8d9a5fc3e53b0249cadfdd539033e87890162e2aee3f9beffb491c2a354401bb27ca39d14340fda7ec2ad98da4c76e47cca3f924ade1ccbb232240dd5b9d30d2697525e1d3f9b282b4b02b11c35433c2f4f456044e1b64e418773cc6fa1304dd0a7518f88588794aecc25d3ba5ac6dcc0c4d547c7d36e4b08ab453373036741c41ab6efaabda8bb807477a67b62f66c5ed3bf3ed00a9239766444fb8938c6929720a2cf84cf6fcb6c17fac9af54ebee6d8f7429733e08e856eecf15cc44b9f834d999f8ffa040ca00b52bdbbf12740b68a04a938560128e2c2944754ad469ed9fb98b9a65558015c7f19a573e8f329995fc1aaccb88f5500422c937951e158a2e985d0650eadb98ba0d8b6d29e543d20dc5f0b5d011d61ec162ca58ab80f8f237e0901030846c6e46edc9fc1e41c77eee3c3f8112e481c3f5e33aa941f64334344331f8350365528ad865c04cd149a363fde7b88031453677eac2e7220c8a70491fcd3308550b96a785f403e8685daf70a6b646aa324480a68959b0449a1f72f6ba1d6bff42258dfa3f6c3534217ab3cca50a451cc613fe2bf383dd988fd1014f675849f94a6bd2f108a184ab7bd9dbc64e91864a5a28712d22f65a7e23cbd623ff876d90b2f3ca5deade3de85dbf28e2e6001c2870bf5e5539a15103ecb545f4c44b01f3395ee6a9bbd94f503c90c12d15e056d1b5b689d926b02f790d9f5ce3f73dec98f8cacae7e38632fcdbe041cb073b1234159fd7828eb2cea6eb9fc0f388df76a3dacee114a24e62bcc4dd517d28f42312321fdf8e3d26ba71f6e2bce24fd085bd0b520cd9f724b3f30a913fce9072201d6546d6d3a1fb9624da726dd0687a98a1ae2862a7fb84f82ef0e7cadccbe390bbb26c6c06589d11116a6b26119a0b80aa013948450a40c54fc43b893ddda004f2d41d2a12a23e0af00269c5714953ef7839350b41fae8f2e6b1592b15c61ef4fed0ac6d65d96aabbb38f47a644c4d287283820884327c8a2db41fb5d6602b417070264d6663f0b814afe0c287c2ba28008534ac127091644b96c842a40546fdb103216a345e94cb9630602a47026a55ca238d200c25132dfeda630942e20efe361b615bffc0dcc702a20d5c92af18439ff3d462f1326878754b4e1a19c73ea167c763b0b01e1babc561da7376ad3574fd57820a5d6ce68cb918c03c4ad1700c0a0c6dc5b002115a0553839e5e46ded8c7e57d11c7a9bf89cc6690dd0b467570d88503d10ae21129e9cc4a481d65dd285c038909f0212afc762c386514337b2b864718d375be69755f9192420e54bc129dbd5aa10907f9016c36794dbd33ec765aa7a9ce254d71dfba07293880b2c10ce7f1853081c8e06948df83d8c38210d33ebadec9151537f159ed2bd585976cb15d110fcf47e977a82044ca0adaf69c2068474223393e6d290e548a85dd5df0c38c1ce94d27338110daee8c673d6bae837ee5e7b1c4d3f599735efcd762b1f6d04821c8fea54c94aa5cd14a813d67b403ce8992615672cd0c4d94e6be73304c0c03ab8528db8a590d5f7223ed0db38db3d2042bacf398860c8896770e5c13e8dc9025c71098d483fc6e05d05fd59ec8014826cae382022d23ac21d10e45783ba03e211a93bc801e1fc69151571408090fa17c939b3ec3e5bf0de3c89a83e644bd60caa40c44c3a804b6ed51fb3d1cec2970279bdfa61c96ff96f4f2c20c6116aa14f4d2025c495689ab0e183e01784002614a731082590e07f6d2ac83895293cbeb1835c7a189722903f9e199c814b05986d72cafe6eba9e3a06162b4a7e0d2142a6593fe1257daa1502f953cb67b57744fa32b1f82a0863c6992c11df226b2fb374200175038a3cb5c12cff7677c00b90458e0d0838d88736a482feb2d6435748bb6806a490937d38cd360d2958adad6a4c8d9dc9a66d8438163200f79085ee806cb88064f09fce00b4cbcbb66efdacd173a9da3fc7e5b0e5e8076ec24fb61590a5aeeb43d4513f51896d9f2801817235ecbd3df2199a289e172220538ca5bcd376f24ec8118ae30b2cc34ab45fe4a6d514f8c826cb393150da8f08e532c3753c40cac976566222dae5636f0d90d0cd94bab4c35280f0b948b55fe897b0790166d4a66ba28c0b90683420bdafa4d225a4244c4f6d0f0a1cd9239acae8903709c7140d181620639d2cc6d1f4de29a8315880e463c208724a823f1269fc738ef2107e123e2031ad78d001e1cc6b0fef8fb691c586582121bd0607246388731117e08d778113aa807c7f4062e8a7708134ecedb0def02a98c42aeefb13f50291c2fa0544024a04a841c1b70228d4df6a44dec2b69b47afe6834044ec5e943ca723ad60f43c6e21f351a4cb65e71727b839bc2edc77e2c8de123e355bd01f04dc370e9561b20f758d6e6ac987a86561fc327f78982b76844797c887520f2a08a8f29eeb75c36c4a8f4e9ffd8722e5d73b055d17fc4d5a9bf0537fa7ebcb25fc5a1ae464a96a59abfddf717a4936b3c9aac69fc28bb7825b9686359e1788176016fed44ff1135de67ce6cf722f2bcb3c340c335d3db99f4116a428bfcd5432e3a025d1a0c42513d95634fdf7ffec98b61df55b2e0fffe2e5bcc919c9831af868fa995ac20001729965d6e81a350f2451c43860a29736053d44a3b682f9639267b188c633ffcb82767006a72f9b0ce537c0b7acf5d4dda01bcc3831a0d9f7db143c056930030eda51c5bb9a13e192971be02cd487a887aba67a84909e584b0962d9ed5b0a8b49a9805f981d9b89dc8b572e2d8c134e66c8617a0fd395c9b200892f05b640ff642e6bc87a957d303f8a5f16879358ebe7f178fd85fc53ce5b7ef82ee52aa3d358e0d8207034daae7c8e5f9b0a11127b22ee31df0d5e83674157572df44160e160615ddb03ac7a0437cf18ec64c813d3afb81151727158bed07acaaf0b73806a8527bbcb7d1f57ec60b3c3356b7e96c3ef252092aa3f00763b33c515f20895ccabed80fde1b9ac5c8941bbc74ab4206d9145bbfa030127bb5dcd30e4fb4a28b2a375c6043e53316af32f609d0d00311a1dbb2cc4bc84b559e06448fd070bf750f91d55099b32e4422a4516f9686ad7f78c8138395c734d2220a167c10a4b0f47c2b6ced65e7ebfb275b9a92ad669799a3ab74bb2e14fddbaeca620460405dc3bcc26e3dec209f15ab6c3a9f89baff5acf7eb2470dfd144dda996b496d2ae1123c02e71dd580fe8b35c1eea231bd213d9aa101ce93d1f5e9f0c40f13ffc462cef4086757affc9d82213efe0ce4b8eb5319c342f7d333f293c4d42d4a760dc1afeae7627eead12bdf3bb1359625d0f23228afa1c83c5ad887176cfff62521ef3c8133544bf7fbcfcf8920168e00dc80b06645b545fd3b115a69ea68762d0525f4747be74472ac1bdd89ef462b534b296c49389f6542bb20926a0c728e596525294545a89281bb6a594d7099f3a9a8c1d215c9b496fae5c1e0fd5b6b9ee32467ba768a01cdc2431469d48ab64a4ccf1f7dfbd7818dd083097e6bc0eb4db5f937f8e7768ca7843afb07160c804a1248b675bf40ea857e7de67e558df90bd776a7dd7732e13948f1ae1f2a5415262b54ce4aab0cbea8beea4f7f7382c8dfab4466062d9afd5dda5970559cc421664e89c1df51a728e53932f58c070090f82cfa48245236220d82c28a66c57c0118e6552e436d76fb979fe16fa773f1acbc632aa815ded39ce5782e51961ed676110150fd9d718ea09195faabfaf1634a8fd63513423bd4b6a9c3cd25c9679868f0a780f41509ac9f0b43d95b4d9339c052eab69c9ba769d7620eee5f30aff85bac885b13b78a502f2ac6d30a78ca7839d48b988b3173453c0b769faaa13a306fd197f09824bca233b1aa4e7277a6ec24ce33f88af8b39fa29faf67a8e0d068c649976d57f733012dec84da87272cdd1dbd219fdaaa1fdf4768701474cbf3f316e367f474f094446409cd7a99c4c4aa9e026d467b67976c67a0b865ca9559a2912c84d3d620399e8dccdeb907b37733b033350919a4a7c3153daf1bafda9ea01850b57b39c2465839f288049de830b4b8aaaa4d6722575bbc99e88396cf4a2403c3b9ef22796a14266540062d69f60a7fc978bd8f32d04a6199dcaa6b7ed7e01173d4acd9605a7eab5ccbd35f7b4e5dc7d94ebf5af01f5b56c6b3dc70f586556bb1e947b31ab0df8e1d7e28802bc23d7b242de45354d3312d6e3a3ca6ec0d197c508a83f034a9e09d773f154f090d0f1a867c093e8fd53c7489bc853afe8052b438e951916f8aac6925b07be2eee1bea8d442e1c3043d7eab9e58c255981919359f0776d4324a5f6d29ff1c263be2fcadc9372f6c5612882429b8a12489bab45217ff2bd88b185516c8f02d3811d5317d3f956a52e5800edb4e57a441c0e7bdbbba83cc47382c450710a2f4f6051fe491c7fb9b6c3097bd0ac3fcb3e82eedf0eb1528ae24100b7d6c85adc773eed9bf5f14ba751a36a296b45a8d42edff4ceaa1c471c83e4dea31c8cb3dd0962e21659bc92623c9edc3165da7e4b19664cdaf65885aa292139816ee432e467b9e977a55dbb1ddd3ad135a0f963f664cae215f923388006c43c0120b8c84ee6dfd7a2cbf6f951b44d122d17b5f1c91311591954e5b3f9982bbee9632bbf28c83fbcad103ca4e04109b02736527ae7ea82ebe0a6d59a2bde6bbf5222dbd95022ccce1c2a46f77dbe642bdd4b99884a705ec979c17aaa04d16e1b28d152ae119466df965599ccd8c17ca0a4596bc952735e1a475d6644d3e45b6e839b932fd44cf9055955b2b15db67a398ee532671b3834501d67d5732a71d2286d357d11fadd629af992c29737856e9aac81f19743e884465f831525196255bf6765e9d9bbd225cf5f01a8216c0dad11cf9b4e93f13fd2666c4a9562b9e65a8d79152484379a74a2efc0e1d5bc556a7ea05620e8be666ef687489cb82e4045a4eb4bccb4973a98edb802bffaeccbb302006fe5a94a0158c35e312b1e18f10d05bbc83af08a53dbcd8bd5b6eb123dea7e2059b8af0d902076f35fb1b4edb42ad2c85b6fc17bcc369b8982c9bf867e746ba123ed5448398e805b28de7335e15eb9dddfad9644b33dc716eaa15158a9636d38bb961cf71ae0248cfe11b0d76f7468d6d5a451a8cad8e0a821179f7598c6073a137bd49613fed36f58cf4de14ae334696dde90d569e24a865b834e4c09c5c07bbe2a179bc1d4c28faa4d5bf1c5d2e5bfbaf6b3c3c93607cf0ea2c6649c8ffc45b35c193dd45975a36f3cbbdacb0348d42cc606ed40d49f8fa64b23f6e129e551dcc1ec1a217a6854f5299981306f723ebf0304446413052c02d1da6e7b59308d320b9cb736dcfb1c5e33f34c13cbe11877e640bc26678ef929b4a3fd9e764ce703a4dde29e05142531023222e29e71f71549b14731cf0ecf2ef0e93a8fda1034ecea0aae37da8d4776f702d5d87ab48108372ab2afec2109544cc2f25f2436b28318ff5f6978231d8f2abe5bcb8121f2b6a00dfac9e2a02120d8a0f572b5b51eca5d217abb3937bbd2113c56fdb8b7c0e451f60f05c8151d723a95ff133e7b0d5cbe961f38c9a5588f8587429c1ad24fdce3982eaa0ba74e6ac0465b7505278948d3fe7ffddd3e681cbfadb591678a6785c77880a5cef4350832f642bc342bb7f79edc1cb8a389dc6fce233889408b69418e52150d4682d9e88e94228eea488643710f9fb70f09c007764376bb9df9160ccac44b7594cb8347a45b2c3f014b4d6cf5108729d85a66a2fb33203728ede5717957af6a957b62455bec31ca4801d53ae927b6f2efdf2c21162833bff4fc5d18a04b73c9cd0bbbf25b8519816707620c47fd4f69e2862fa0e13fe870ee8c8d5600010cabbd560caa755e321dd91f3e7115848b9e80b2e44906ccdc1ee9d505dc335a70750ac4a51cb4fa5d0b869d22657d9c540b6a5d8addf458749a97986efc796977abe45798ef06c532db7334402ee117fcce6522a8ff057a3f14bd07241597c7f96faac6c5c220f3cd974ccecc1d632807fb8aa904aca85087cb02c786109ec33e5c4a462d5e423a503668e212c7a66126a6700b6cb05f70d62ebd77408f4679c810c53cfc30e51b4bf760c9d7727370a571906e560848820facb39440aee3c25c0a02b39263cd57ff68cebea6a483c65478291f191a25204036581d78476468121302a4015efc58406afba064207271b0c78eadab4c9f514a1d185204905aa7d5cf6bd43357cadf36224ef87ee4a60ac6ebe98efc3631e28b59192e076a86038790c710074a89bff0aa348b1e2eb842161d83f41489ccbd790ee4003dc8a2402f4fba23842f9a894857d9559ffcd5c1fad469cb19bf95cf7f1db44d81cab72e20a2a676bedf41d215a1dcec1a1a87c059e55584f23670a57abf1ea90578fa410bed687638fe5feb306a379e5a3200312c1e07b22c3ebc36fce243aca4a05755bd0fa7403a0d3066a7e2cdcd8f4ee5859787d7a565875ed958f8219431e35ca8d2041f5aa5a2d74c393cf816a4e5b5906ab82592d503fd0a4e045996f3a6b1ef0b090e33cf099e9c75a3a43a64d4a839e39a0b2c8079b34c724433f35fa5f05ebb5680bcdf12b4a7295e37730cdcab2a220e3c95873f49191c92e512dea00142719125f62d87c11bd34714eb87b855882d23173198bf8713ce1610844ca003cb2990584224e8d01a4aed0216738eb11e0008e3cf248a39fc3daa0ac30763453eaf78bea2f9cb6d76ffaae901841b1f41c6fea30350317e08488b26b135dd968e2681ed1d1867fd29d36e10399e81406b6456ba96e55b28cd0a21db548150056e85f71aa8bbb4b54e8ed2a6ae4e0b083330cc7787e6ab99772ce92423669fd1bea27f780f38dcaadfa0f14f35e44952425cd440d2fcf24702ac652e5f654660b9c4c98de49133012c11aee44178c7688f87c8236865f21d59079234e181ae1bad05c2b9070ea9bfc09022408547432c93cbcf00881e315a19ea7f4ef587e81c6b9a43d148ca8a96a54140397c5a70d1936e89beeae07ed830d548dabaa2751dbccfda62e004c71a7a19e925d32a396a8d050330085f099146129976b538a23bd77b9bb30804d9bb70d8015ae1862b3473a22dc7a6cc2bb96d0f6bd396cb10abe470a56949812adff731fa0b7e26f53a7d9283a3486b738cf89cd015fd8fe8004d700aa8bda7a482d9836c1c4afbf7df6440a5be3873cd809a62fc740c1b0612e042c9c9d12026a6c4322f3b2dda74ecc0433de55d6569ec8cb0d6c4bdc3d1bc9cd09c6afef1bdfe9f09d120153a12a8aa632351ba980f5a0e7a319c23b974fbf2987c4bcf518b75e1536d61c08d03cb6244b07239cb73911607c9096531960d940998e1a0c82995fc3e2a0ab036492c165e39e25801c53e15b28a448fae83f9ab05d823d1181b9a3a949c59b0c19af0fced7865826a0715415696e2a7cc3a118879821f646d9660b0c1ea3e4f1f047bb9c23f12ebe8a681fd3271a9e2cd12244e801c3d4020f53e5629e99b0202d12c394bb2244a1243fa769864040ae7f0e192b3c82e6ea7f521509b71334b1db2fc754cf549ee4107dada95339c08f879c7e5f473869b2952bfd2046b8426afede1aa990231df6f7faa9b98369abfaa0c91277639f3b4a16b8032ef48746ed480cd6557a40328e80db2206586c0ff22d56553b973a5a9d9ff3d505e39fef6b55d4da961dc2cb6e59a1812de554ccea1c5581a35129164d1f0e4e02dee4a03d9ca0a99a3c7b033882747f944cea60d4525337957132413620c5cf09abdab07431be0659ac40dd04173972c1fdffdcac76513b71f7612fba59cd8494b0a00160bf31eb3fde9f4c69157eb5322431efcb1d527ad15c5126a39c408f8ec1cd28e42d15521e7df0d7b21d5d8606ec2a67a5debc93338b922f51af834986115d9072fc7789d8a6188a0fce1f95877ff02883602bd78f1dfd6fb95eff63ad9e4de22c26b12ade84fe8c67923866e9683d0a59540a964fb026f1f501a0f08e7bb159ae5448394c79de5546699e45c1efd5098c20c91d954c49e7abad5531622ce82005a2e2a8f2e87e91664c7a6fd9ec635f425169297891aded4c125ae7255929f2520aef5d82046e6ce8693d767bd5ddc8cddae9381b5b831c4c089c91709b74a5bef59cee884055c9125bd6e33f3d57163c8929c8852155aa84fa0ef32ce7df16dc2e112c71dbfb93ee85b4261bc1ed7ea17636abd91356e3bfb848ad4b1237ce786d72938095ceee035eab66c43e1f82a5f127a798b2b67803947ab94635e65bfa28f2632cbafe514607c66efb3192763a2e9e42a14937ca1420859a50f5cc04f1e42d658aff55d9f211c8d78b9252bc9bc3d74d82e94205f19a2a85d79b7126b9149fe74462ec950113b38771836cfd0ee4feeb8515b22a90117bac2c2428a2388d20250675dc9e26b2d8701d8a9a291674de317d3f71f5e3a8bbd4e6a879e4d1781880a882783237895880d71822764420ada80833cbf5c5f89beb7a6c41a2997c269af9ea6defc6e1abecde349ee781b571911541a2a99f316a2a1dbbb21367154b68acfb8fcb06667635f3bd58eb21ff4b87446e861819dd0c3d229d7983a7a54b74e30fd4520d88984fcbf673f702e31d3e45cd57a8821933b1b3ecc511d14a95e14af1d51a3452c128f174c2ca6afa08a7e515156398d566ccf634f5011208cbd207049bee8abfee2cefa8bafe9533412d8c5945ecdc124a7c549a8d8dee9542232301c804da9f3a65ef8b5c51fa76a4baa74039e0c75b80492b75d91c2644e884c055fbf08e865024c6f4cf541ac3c88ff6c233c08b9b0cdb98fd4f438981fc181c88d721058812f772aa75f8677f5216fc1122182da4c73022e2c616f84e77345a88246f136f34fb7ffe03c5ea702fc09d8283a554fa1bc159ec1ea586c2063342035e08e90a175be6f7b826778ad050e22b28222c751ac2c827646f97677284ca7563425bee12dd04099329c8febec4d4a89d40c717278ab7c8dc5202242485cdb3da68974d203523abe567de580500695d89ddb2cac52f56fcf118fc327b07c1668bb27d072b8325732e3659a68f47e84569eb056e9a0fa5a68c581d0d00ead51bba37edca79989bdaa32ca1059b1388ef8848d93b927f2a8363eca60d3754f55bf8dc7bdea38baae3334f1cc7f89c35b66421c661fbf10a1f5cf63cc5793c680ed608a35c0ec12b7ade4c199b8cccdd7cac780451c9ff599c52459a6a192baf4f00a35881a2f53e1547231aef088f265e8c82107bb59bf31ec4818b5e4998128d04e1dc99ac1d5c74166cb1493a297a8964a9e44947fac7b8d4ce7ed7c3e25739361ff9c4f4f529f8ad86a15b1abd5f62d5deb665c8f4d9d5528a640e563b00e43d45890649086401a614fd761da1a8b690a6c9af92c29a4014bf2a68e9302e46902e94e64e60361737477c926887b8bd2d1a96aa025062261be2f6242470d95fcc74ed60fc5adf81f459b10c2eeb65ff3b22e0aee897e41413f540edb7b30f0b0a0632d5d7d9cb2e6c55a0035b6349bcb1a3831eb03398a6549a7ee78427108e50fb4484d1c29cba11daa1e42b26f30580b8453de6b0d9e146ef96107019b500a6d5fcbdc0e0e34ab097ba12944492104b375d278554dfc3984500dbd7ba022c68e9599773243e145dcc4449a88303c8a29130f7624434b3242f48a55eb804f3a8acd76521904d0c81a22d544fafce7b0d9e69af38f3200621a7910dc5bc3092ac4494247923b24d026812d49685492a6dbb18ca01d2e8ed862c09e41eda9fc4aa8f2087106fac4494ca8197d974fb03b436ee8fb1cef160df2f51df3b49f1a5bf7bf123af6049b8a9d2fd4b42c5eea7dccb1abcff23fa73531452a56c397545429726cb0cb60c51b0d5d66c01a10b3f9cb3472c1c874eef537c96a3f5447f84a98c23ca2d5dd149987725734ecef7ea498aa4d35c364bd6fd0599d4d1e8e08fe4283ddc95918e35260720fa7067d838a530f8220643566f79bf72688f20046b2ab849c9e851f46fb60d59658dfa0703b0eb4d94524ea7d6bd95f6a78e0e70816563f03b3ef4ee7a99272c42e3da3b11d4b5f6fda87a899f4d446d21fd4d41b7e2dffe307f2047295b71d97ccb059b13e6ad5f105a4737d1a0ce6b3da733713cd32af12a9c24347bf90906ab9af1e52c78e74e8ad62f5d6d330e9464b262378a127b3e0ed9342b55a9bbcd7c9d99764e2b2b8731d7a52b828ae544476f04ae7d33b6790a49f15f3587acdce9b58a6ac5ef4a6b0f6d50738fbd13bc7ac7667d902d97824716d24fa049b8587e815bfbf9029c888d8bb47a21980d0cfcf8bd98e3b1320feb49f2b99fba7651181e3a998dbf8db3046156ca6bc86bc969c063505acb5cae174e7dc946f04258ed09d13fc3a7aa44b41cfed936f4cf8fdf833728cbe72b8cd615a727b4f9329d231df092380d52a893bf75ed3be4e2d3b6649d7d1cebc6d4eb10658af31cfebebd8322465d23fb89e246819034b51cea3a527fd4544614626c5524827067e11dfeeb5ca7b324858dea190e0d4b003e3b042ab3cd040a6d8f68d55dd3d3f6883154d45805ae5a9ef5533848c352bc44a52beecbc46962f20d5534f537870941cd9642297781093b054028e1b88ca3f4b7af0ce062ab4b1512ff9bcf7261cfcaf69b3f79cb613873ffe72b7a13cccff85969f639f1dbadb88208e3dca4e930ee87d25e22c792a128c3be74b0c9a7e1892b3762db6dbff119c2343b66838e69a2a2be5fc29e279847a87ddb381fa89e04e08def980e486eb801d204a644e1e2745ec77d4396bdc9f6b63c8e3c0634996a0cbfbb92a937b49206cecdb1ea1bd5b6a04c9baa16cd2208cddc4f3ecbb2f13e1481a93a85c87317c78838afcffc1fe2ab9a5146d31687eb9ee37511437d022c17a14e24058cd1ebb3ad68d4667033268e1ee08ba4e96b52791b252171a24a44697a38933e7a1d0822107f64ef2d0d20283e001582721d22db12280c36350ac4e7b4c1fb2d6218a307a2e4e0e15280661f360c25387cafc203eee8e378b91f54fa1bc5c9bbe0a7d4e571d0d51f69705b6a66710ff9fd30cf03307e8c2884cfc91a6185c56f01957c834fd458d21f2359f37da8180e66650ce42c1548c92adb7da6a94556d38354a56d50e012262777546bbe437178ae861673b9b1a6b16cd49bdca0c9c302762199057c02ae0a2c00337ac4429b06bfb4e056f57770bd17274a21d0d720c0cb07b0dc44445c8485cd75059ae08b34ef8e8c2bf83fe53b6f8a0b560cd8fc2f93871bfc62d7acc89ea4b3e08653fc8ad7a9793cd390e45937d4f3a0b097444de4c55c10c629e2c306bfcf1ad96b945499e02d6e3cc71b500a102f47fa10a61d650bb23a2fc02f7cc0c54f7e2995a7fd4d446786490c342694fa3211b8ed359a8eec1944ebc2aa6434b32dc1dd14b5fbc832906007ab64936c66773e9ec1614b9612811def6b69bd14976267aa1df28f9cd8fa368fc75b9cd320c72b3b261476f327930e98a417b1e65aee93179b517e126f7401efc5ccc28c56616753924aa4457d40666a8c6875842db63f44034632c0d656fd5cb159f8645d3b62c4858c92046ec8318909775940a63f7894164d38c774160b14398462686f999483da1ef1f02b2aa9c4dfb19902ab685c39a2af203bc764c610696e1fa4315fed603580ddec41a70ba4f1ab72b80dcd89e7aa056dcce58e3e80bea018a71a009ab3f8d0c81a5ab47e3e89668d38ad22a1d99921c0e50b8c54fe912638b8697ddef53c39710dce9ab03fc9102da8f89505d40121aa9032b04d2ac250403617d19fbbe8316ef0ae1de3105ce90026cf39a03a78f790ae056e9305535b524a7ef39b3c69a20df0153f6ccbf3393a1ab56f555c6d2d337e7881154e62ae236817f55d628037a01b2bb84c82d654a49a6da04e804af04ed0ea441b73634e8f6a3a27e3ba8d15a54a4b52af28da9d840dc584245d9751bb3689c761b6751a6aaae8a34aad00a5a803ae5063ca0d90ea94054214e0bbabbbbdbcd81708d5b9210f56dec96d2a96a5b2ef5f3ff93f2c7ccaab645451675ff97f285a838d1c622a5a892d5685332aa249fa28fd466e5f65bdbeed99d62ca4dd2feff0a475177d27ea8a87272a50d7da80aaa029436fa0c91289a0b445a90a43226c126695d70e512573e61d1189222071d6839d1fabcd805a5ad136f0589e364a5a5288f46956a5deeeeeefe4597c75c2e19912255724717ad5edcf1d30c52be0d8d83b235483b83cbe9db374bd157363b4b5202ee724539270d34e3980615619056bc2063aa8621d44fe07643922b2dfd8248718d642591a141f93c614c1bbb6568508a626850be2cb6a2cac79078820ba4c7b3b47e72009ba495d282a4f4eefc1ac035d20a51dbaeb0a0e07f535a6b84a1c1b79e57ebbfc0a6b750e844b10208d7bcc5e186baa2a1784569ad9eb7ba2ff2165ec70af79db4a075df4a19a98abbdfa3ffadd5a96e6f767c0b742e4ac78d3c54eed121662f05d596cb9efb8d6f2e75b860ef7fce370dfa1015fca2b53b78d1272741977b7290e29bb0ec5f888c043e9002451226407c8009c8f5a3c38ca75c4195b76d8b59fc141acc0b503b96b83d57f6e480042773082fbddc93838e10baad6c3b2d832f0ae2b6ad8b8260804aebf63fc2be5e6fd23532f91ed41e6caa4df5f323e5b4424a2ba494524e1e9e96163c3c5af0f0f0f0f0b4e6dc8630e710e69c736e3ce0c1155774aece470855089dab7375aeced5f9cc90e97c6951275c90e55902b9c62d8d2ac8cfc4366e419ff794d2e8b6f6a0a2765cf71b8f35387576ec983a3b76ec084347a8542b984a0553a9542ad5aa6b2b9ecb3dab1dd8e51e1f7c42b395c4cb87d6e59e0d647151f189cb3d1b18e2c6986c3959a2481258b49680ff8d1a14417135e8b66b9c18b4c6646d65e398cc31e6a7fac5e0625cb71212e5ac7c94d6ae34e26238902887fb3caf131920a66eb85b2834a798349bb5a012e0f5a963dedcdc203195bafd35ad677ce5275798f3db116349c7aecb2fc66420a1ca5c51d9d5b3d9d634295f7eb3588593ff704969a513ce693b8b1904e68ce66e1f10779a36e5265b7edd5934e83dbbdedfb4d98f1c351865fdb86b5d8bb118f3140c92aeeb58f75f75846edb8e0962dcb6f6a96a1442ae793b555185f9cd22d8e6ed4b111c798b029989a8a8b9b3dae915d7fc4f835f6bf006f56d17360e4ad6eedb9732d68ce7fe5bd46cddff9f996a1666ca6749d6e44f5bef76a0743b73a763099f8545c562eecc9d9db93359b23653489034499335599ba94db5adb2d8529b6a536d2b493b426d39a3724669dbea7dae7b5b546f8bfa1f8d2554cef59c0bca3f4719fddd7db6e0e20f0df0978055ed72cf6a3673b967e5ba3d4966ac24b27b517d82db45522d28f97f82221bade542d6c301236edb5ed24ea030fbaa71505f934034ce4f71db7ecd4f2f9315a1ad0dfc5cf9b52c442ca8a8e779d7c71a87b9c4f779b8f42c1e251aa7c50fe57db696da608cff6f2398f8e849c2eea3c6e164f897f28b3c4059a11575927491db22202aaa43ef90d56033ceff4e836d51bde4f6091a5cc23551aa45fdceedd769b09f27c6b48cc57d234af4e8e2c023bb152635b6fd1ccf4f1e9bdcb6f7cdecb0c3d54fa97234eaa96f519d0b7ca5d481c2cccccf52a7032d209a9080872645c0c0b9453936da2997dcfe50357aa96b5007969694d2fa112a8b5ce85112502e47b94c031488e072dd49a60d89013dae0b87d4c0dcd30313712702d1eac1061715c3d008d1be3738848a8a3c200ab432b4a8b12595f0c2883abadc83a4082318c8aaa80111903089820489149221609254d1e51e242b24aa233a4f84d44f2a0128e37ffdff6f90bb06dcf4b6f73629bf7f8ffeffbf93947077ef1d9765749f7686d99db917c73494bf725d52621fd4a34cdfe9858028461f3d04cb00c208d275eb2e4a7eb86edf760f5145e4c6a8021d34d9c2064d72e0022a3e7684281651515eb4a09fa2bdf1d3a7523e682fb7bbd23782a5bf81625a20100804021de1e326a6f1c916536ef28ef4eca0239538b9ecaa1d7164515d2e3fa1e243017658a996b0f72ff7a86280e43d32cfefe5c768ffff06873701e268395110bfb3522b279d1fdbb0953c2ba8a8e048e848685a2e3edb18ea5a2df71303e09d966bf5cd82b89205596e46910142565a290ac20014cd9db6372bb9e43588c3009b95768a8260e04ebb957a7e0a6eba753ae76a042906118a172c628e6b091f5200c5114cf0431532e09e509b8bb24698c41e2332884aee8bcb3d465648b81723355ad4e876eda843117bc43c9a320d8e119f7eb48b140647347c1d72ddbbcd85686666a6fcccccccccccccccccfc84b33d1af571b28df14695fb6eb3cef263ea3fe7fce86782661b2db37b3f8addc99e81247b1cb6eefe42a446cbd276bceedff6139a4182a2a7e9b46b831d1dc435915ada38a1ee804c0862d1c6720ef71a661c17e526b8df98ccccee6ac2447d366666773501521fda848d10a8a3b246e5d6e59640836686fbe4ad939c91ff7ec59bf665b45f42d57f97a5981e68c7f4405bbe41ebfe4264a6c565bcb8c88c58d22cbb8f98552a7777a79c8ad99999f93def05330c124c8c2360c4600e41bc31482abb34f122d3b930319b1bc7a2b9ccc868b133f5ddddfd2bb9bbfbfbacc858c7ccd68509e9cecd507972cece9d630e816c30831d67c488116972775f5d663b4de858c44cd2e28628872d7b3e92569ad0c90c51a3fc7f1cddcb0d35da4749d965db2414d468a7e5549ba3284ace2e5b59e272d4b44d8e1b77b711e5b0e54276bfb1c95183c3841b26f066858711fe271d51ddddbdce80e36123d6681a9caa464a33437eb3b4593a03921192e162ba1822182b2416afbe6881394917ee2fa3d18b0b17989617d56321adc010c5e8623899900cd00cb55b697e72c6d3388dae116d74ccd606b8d2dea32b38a6e17cdbac06ee1139370a02ed7ba117e23ad10a4b6d717919b98079e1bd84460023468c8c8c195bfa66d0d0a86103949733dd3001075803d6c6d96cb4be9550f287f7cd461d64d9521a97e682365ac48b11912135b80b79391a7fb91a7e391b1d2db10f1308aeb89c3ea722d41261a952a54a151f20cb2179411db57139af71b9a771696ea8baa9ed9c71b9edbb1c2d5d0e642f179ab91c27e3729dcce54431975b897139161897aba4cbb57897737971b91718179796880c916c925684a2800bd10b2e46978379b9dc0b97cb792d9723d5cbc160b95c8c95cbc5882e27d35d4e0677b999d0e52ce872a56fbbdc8c79391a1a7eb91a363a50897d7c295ece64c33b3208aefcf75c23ad9d49704ce32fc24145d9d8300fbe6ddf5a04704cf39da5016918d2382b966d052cc03c582ce5d865661d2e2dd0a22863649c8a3e14ada8ea711fcab3a10f6541fac59d8d13e7a425e198992310b141fe2f27dd1a107fc83ba49b01c76d25d0a577bb9c775b695e7939e997a3dd56ead03664966ae02a40b9d0caf631d76c767bd0c6dd14313002f804b2cddf1037b5a5c263708046fd040a854aa017a9cc833d146a895e8ca519d130e238ea27500ca8e455d00472dccac98cb3598e71965c966228cdd0607f3270a0ae74e4ff9509fac6f1263dae81d8fe5cc45de52d24640814028140304a29a5d3fa5bdf289dd6dffa36ddb739adbff50d042a22bfe872bb215091b8c43602bad16ddb70306379abbafdab2b27f55a83fd4354af6d1b0eb2e986c3bf4726b61ac73428aae3e6c6795ccc835e76f9c4cf75b728186c8b722897adcbfcb4b5dcf48471369f0637296c6a4bb72dee16f32818671302838db3cd1adc625b4b4a95db5e63939fa6f0897e346557b7bf1491db45a2d4989fde077e0fdcb6ccc2f282bd73e6b3eb12816e9bcd6db64dcbc96e93c227ba6d71db520afaafe7474bec26afa5b8a69bd448b7108624270d752bb5f4bdbffc5ffe2fff97ffcbffe5fff27fc510ad549711cc37e3e5fff27ff9bffc5ffe2fffd7cb08c683f1cd787ff9bffc5ffe2fff97ffcb23c590f966bcfc5ffe2fff97ff2b4666e69bf1fef27ff9bffc5f33f67bf9bffc43afd2f7fef20fbdbe19af19ee1d67e0010683c16030180c0683c16030180c0683c16030180c0683c16030180c0683c1dc5611a30c95b86d9de7e5ce6d2ba51172c96d3b256bfb6df38e1b5d91a1fb4c16b96da54ccdda67f13fb72dfd1ae84120ef08a22b733eeba1dcb6f45da0d02f6935d8cf0a856a0fc46ddbfd4af4a9db76e5e779b86d595e55bf56ef58e98ad758443913f85bc56ddb126b9717270d7609e53eece25db8f08e2ee88ab7bcc6fa59184545c917feb45b9981d16a9f8eb5ac67b49aeb8461188661c862b1582c16cb6b2d3775abd56ab55abe721d0f7dc7596d37168bc562b15aad56abd56a91b8aeba8c6aebd0b0ee782ccb73d32ed9b3bcd63eb58ec97a46ab4dc962b1582c16cb6b5ef39ad7bce635afb5b87423188f451667b45aa77a1586611886e1cf58adc362b1582c5687bdd3ace6e95687611886218bc562b1582c9817a4aeb41277589127b6a22bfa0ca9d594a7b2ab300cc3300c7f269bcd68b54eb5ea3d0cc3300cc39ffdec673ffb198c189d2d8986702da6642c160bddb473730382d67a5ead911579a2cba7adc7300cc33064996e47cbe513bbb901416b3daf565a539ecaaea64b67b59aabb99a2b2b6354c636574ccad8cb98cb18298abac31dec4a575836ab67b70f653f143857a03b5d37a8d1862e3b3b3b3b832e936e504ba00458c1c9b9511005d19d286b9df671d5cbb9d10db583697147d63a3e739b3b51d63a2fa7ecdec1b4b82393a8eeee665adc79a77577732c24296ab4ddddcdf402b73f1a69e8af195d32bf3fb9ec4fd844e9e638fe84417e6f6e0794fb5c6669d93f7719492eee900cf88beeaecb92a5e5a4943638c909ff7f17b86508a4fcc418638c314a1a6c1389fc8f4c7cfcf8f123e8a77eac0853628c31c6287b1c0919631329dfa3478f1e3b30a5e576f420d8d4364992208228a2881294208924ae7bd579a2621e2effeeb3c44a6bdb581babe58678b8a66bd09bfbe0b68dd1fa0a69891aff8b5ce319f0c8a51aec50adc1d08fcb6a5ec7900ed4bf95b7693ba07153e3092554047c62c04e8d236e2b80c74d6da917c5653dd0de30608777b073dbf2ac156576dbbe0815c04369ad9e676d8da3f0fc08dd59ab52a3b3d1c56431d91512720df763c3f4832852b22852a690a4968606637cc0ac0243b6f91385926ca8a176e386092559cd336133524142567ee236222b21210e8f04aac2a7ce360c343f759f9070e6a2666df3e1f66644446957eb078566ba42761aa4d55893c3064eecb0133bf7002aae71b922878d8c8e8ecd8d0b2a275437314e0040c7752c43840c91921900804229062028a0a0c39b11e5f80b1c985a0de7f463d6c40c08a71690905088123efdf0f9d84d2db8cd4f20f1c3a7c64df890b068c9b6dbb55c3cba295c5458f9a8b07269224b8175dbcea811010028ac20809614429b580a610aa14d4c470a0260a1dab86ea46c5c36ae1b291b29703a1c51ce664f2cc48803b22892102dac024908036c29fafdac9184208120005d0f92084842b0a92d89083e99684c52729b66c7091655c86ca4a0224acc448bb4dbf387668705164439d2b22072e1afdb01dcf410c99e50c256be1a47ca282e5b0905ab60f38a363eb32643862209716d5e5185a984295c6323857fb3e6d264003d7ef0c2237be13940c83544e8719db8a85913d5aab444d70748c7cd0a3b76b020ea41809b16b816ba1ae35d2ee747c7793777d666aa6ca91c35ae1a2dd7424ef7420725c61362c4cc750015114242213b428c70c105518e04f2929311316b5b8e9f232e00290027eaac901dda133c9ccb15754bb19b629835192284847e9a355aebace510fdb0b95b4a0795ab79a1001d8605081257ab203730cc2ab3c6730a9f38e63e21e146e4895088112f479e80a1bb39e126eaf4e849a552e10b64ca7d6ef2b719454a2c3f089a09175331e6f127dc70d9cad4e6a12827489a7fa118cb83a085163626871cfc80685442477e924045073c98f2ffefd1c9dcb65095e953b29cf30679439c0127df4fed23c6e0e7e747ca8f949f1f2a54a85841c50a2a54a61d3d6743f399b9c1510ea5a31d9b8d77fb621af41ad47113e4e6fe87036c79109d111a7bdf7b049c4f71779fd2fd026e07ee202454f7293f270922d7488f1e3d7a31535cc03915cf9d4a33e172103d3a4809d59d4ab42e5dba921a2d83f7a3478f1e3d46857110bb6bf0c7fb761c53f73f76df660b21478f1e0d5251637b0d901e2daceadb688370eb56c771d906c1808ce9ee7bb44581af0864e782a8bc71a3dd71a3835b36a60ada3826da3836db0c81644d58106ea1c31549a0f4ac00075e8cdb9c73d3c0113cd0220752a82005b5551428ee9c737a0d46cf2ff7708049cbc6c74fd2c74d396a5a5a22e37879639a36d087daa071cbf3acbdb9a1d1cd90d1dc36c553fd789464b6566b675a64b85c64a8c38021792eb6305c2b860515f5e285aa715034a48abce0a171e2bf5d51529554b7d3ba95c1a136d79a76dac8b54853547f21fa7163ed5692df1763d4513f3195c96899a936d65a5aa93516fb442e9f199d8c6b7cd7e0948b7a919291c2278f4dd1ca6c719bcae770804256ca642cce54ea32cb1957d33962fc5a2e549d93ae45ffa7fb0e28666676f71f8d5edcdd6366dedc9d891aed8f2e734698999f29b582631a544cc5eecd5f3691524a29a59cf283bf9c31fe3037229179d80fb5c4589a110da3110b680bc999aa31861944392fea48435d4894b375eebe4db62da2f9c910e307feef2bb9ae6b50dace08f2adb42b2cd5891e04e013f7d2b92f061af8c4b04495618605c4d03161a92d2edcd7a3d5429296cb0bd759cef370a831320f7e7119b96889b13430235110088c462f2e9d0bd10472de52186e54856f6814ef8ae8878481114d202764b717314697210ab23c7d7252c60b7a510e4d22e56559f99b18a8544e196dcb6a24000000c3160000200c0a878442491064691485e50714800d5c8a3c6a5c4018c742c1508ec3388882180a328618430031c6184418aaa105003f66f86b27ce86a89ad85641da7ab6cb8e6c8a1cc0ee88e32152d3daa4477062ccfbf29bcf68be3a53a9fded7389ed2016f26a18d074d1c117b028d6844451bff0f467260a3e9a2fc20c062afaf291ed745f272e035c114c005be137ec9d5edfbed51b9264aa56dfd7103f0b1401297f6d5df1f23b6259d629d502ea0f75825ac396f01e5d3c59d7e46b59bacb9eb7f99eabe53ada16c3db6b15603a513faa88787591d0cfe8b2695f838873a05dabe434cd89d900d3160c23d435c76cb2e717c7a9c6a37ba004092425c2f80d8e008c512afdda0f2fa60b0a9e4f45d575749a9449a442083d2166e687b4a71a166fbcef18e8d18806ff0e03ce000fb955343a1be0dbb48ad87869e91b599e8198277035c7f4adf96100492d424907f451d8c9fd41c5fdae247a79dd7a5e43c9e25bdb2f4c572d5196667069162c1a6d6c56265521e945815e2ad703ed835e1889d8b1a4229fba5c4cc6a826a8dd3c379414fea15be19b408e4ac4e7453f476ad2621e5b176387271ec48c190ab5ea960144221092806507013693b55fa9f4e97cc4c59a451a5486207f492adc4e20493111992399624f61e114bc739e31e61e52a40270f9bd967bea6c35b70be738229d545b132a899ce8393450115f4d2a865b593d62c26bfe7a146398032009223b00e4b47f624788d087bffe2f8c45cbebe8791de227699acffe423a0b3df2b2ad58384bc0c1178bc075993a9ba3237a4c3f37ec93e221b7c0c1a988d53044362cf55640258a400eca963697723627421c420f0d13850a20cf030a2cc6b2b5d34a7d65a2b85028948a108872e21be902a265d50c5af8e41f3e8d50ea073d5a30ef31798ec152986b7ae100a9a126cc5adf56a62c53852074c23fa353765d05b1f741a3880a5f97be99f5f6469e762a50bb3ee829c465db12170c88f9a7c069f9d2afe3316ab83272b23ca0d66336d1a866c9ad8f46c1b4a21e97c66c0a8e43c213c3d5496228df0df8daa95bc0d052810eaa28724a72c7eeba4ba76fa74cd35d3a8e9db4cc73bb19a50daffa3a9cd49babffa1cb4a2017a7cb595ac37075c73933a88f6b8f547ba51c3c400d173ec633f12ba6f584a10d2a5c1f3b82653aa44c0329e51c49b33d319ea098a1128e6a387ba195ae3f661974b30c55d166904d7b41cea6ba0b470c5835fb2f4c0e403dfae98b896778cf2e580c51b72c61b2467758513bc4f90a791835411478511555020882b1fc19f334c942ae68da9f008cfcd8966d33464ff2794486823317a2edd2c9e705259e00183196763aa9912e93acd5ce9f128f352cad39ee0b14d96fe1ee42b35a40e7699ce1ffa24bb9fd8c47779ebdf8bd14ab2e5d76a1ca14120c3159639dfa62a82085068386cd460b690a424f450354cedf7bb320cc67c597a29b259fadf0c9d1874989e3b9d4b6b6848981be7d3e40ef832140e3bdff550a83d2c38e55e5146e07b803124b06811632dd86d729d748f056d08b7dcaaae71e8830a658bb4c49d1cde1618e63ee907b754ad71ed1769159a66bd477e21d82a5c569b9054b9a1de5c176724919a6b3180b6431f26f47fb1ce9088ac058d724cacde73bb735a1917fd46c921056accc0c459485f6a730825ebeb39ccf9d666b62ad65651ac94558b91102aa12bc1e296d5f8efda4060d364192b2b4a22143880b605154595081e1bd3d8802ac68a89f2834076afebd14aff26fc99d81fdf3348ba70ba56d9472c566967a214360091eb541cd2cf692f2100315d827bb0597973ed558dbb804c74eef9190a76dd29a4ae67158e861918363517def37eb6dc3a4bdb17dfa3a0d63c0658d2a8d6a7e1530d2d2009f7bdf76ebbd011c635c77df768d4e174bd7d1d82651ba3040a9f6d521111784345741cb98e733b60bcde04a9a5522e144a5025eaa4c4bca66e208399c06c1376e3a26fd8b40d69945a9aec09b4b5389497e74b40b8c28aa11b967270cd044010d28b8a5f79381d5f1ee72c826d8051f2590678b405860fe280073d99a70591590677a1b68b094e5038d0caa5215a554d05bb4a297027203d45fa820061dd61d2e3ea980f11ee21d912e57b10d36a19518f21b4efa1e5ed54b10ff24cd2bdc4946a3d4882e60641a5158ad7d2a0f1983c530b4b9727b57ac70999c4512d7da1718fadb909ac1bdcfaf5d50bff63bb218d50cbe1eaaf7173e92af2cbd69882184f91f7e20d34dc4109c05aa31efc0e541f00f9eeb2c03e9475173e8b2e984c3a6a973f0c299a4464eb3574b3debb00b82e0b7f475e0283065c7ea169a91fee1799ebad01e71205f7c6fdf17ec6916125e387cb98fc8ac5bf183cafdc642542cd331d07513a74844986a594ccc849c6dae3cc4aed21082c100dd61c2d7204093a46a2930d9481561d684aed58fdc0c35107ede06ab7bef0e7a70489affede0ff7ec38f16fd8e4aeb7f1ce0bfdfed7fcf0e07decf790cdf597fbadf0f33b823965b6f10df51caddc0dd2c10eea9c4222882c516d179db9e013f6584317e99a89d11599966c65f2db6f4d3fa10887ea1f59e6ec8d862f39a5a139e0acc4d1a7918892de03caebe11999aed5f441b0385b94d7269342516b28b0d83909f7c7c0cbcfb444934c592115a39c48a668cb583ec83ec26c4dc46869595cc780285d4fe335f28b70e36faa3e5d359c6d37bd27dfe35bc002d67b418d41dafd9ea00295d19dbaac0476410bae43f83424f18c6cb260b5b4bcc621693d43a94ce3e0dd63aa62497ea82f07ffb07467299f23b98694c4f9dc7dd4e8898871e97de27c052d2899208313a3e5abd03a4c464f3be802eca0917e3f0d07dab3992a603a26481950b2f480d69c4684a176f70906cb2f0ab951ef66cd541a48748c44b8b9f8da55a7be8226fcd0b113992c48741fbc4b28ceb667b76081e0ae4c3c9c9653d54c59673f7358bb0894da458e8a5f2111e24ec837284f2dd68b4693c23a7df7bb9e99e7d72a807ea70caf2ea8a202dda98f284a8ebe272a2f1349d36585a62761393fa86c11e49d5ac5d690911ead18f7ca62ff9b97f79010d7737cc254950a635c09e1decca788b52346ecbd96b9167875bce0e0cdcd64227b9c08b82e84d48b897833cc605c0fdc1371f76fd935c89452c99bb747f650543293be87b717104e1f01c0de75b01ef83746b6135b0432af30de269c2002650d36b6ad4af249a62f26fd8fb128ba1044fb37717dbb1d25715a458d27a79f7b951330fea9bd336286d5c7e0d66066e26417758b7091b8093806f5f93cf70be5b9b824a9323c5a4ee5e7da11de93e63d29006f66ed68daaed3dffee97c89a1cadde07f23a1f17fd322dd4c159cbd39a787026312a95f54ee214553823c58f2960487dcdeb5bcea328ae605f0d4df609ec8fcd72d3eb6f385653817c2637e47cf3bf1b57a979b04f5a635028b04b9e9c749f7a99000f9b66767136da52322ab607fa4487456068c10d13ed150eb940c251b20580c3230e47bd5406a1124172cb6206f48544eee4f9d409c65ec18e9c07428d5407b3c1328209f85226ab04f72cbffd809c15167daa91dd0abe46fb3fba6300652ccb7f9f888f526f4fcdce171889764cbb48da0d24296877ec0d18751271526584fcecc3077903b0ebe97a3a68cc6aa89e89e69fbedb70bee81d687c737ebc9b84a1466b39a49068dfcf4d7e368c437ecae96882621cb67d1ad15ef3cea4e03160cc130e354ea82967bd0187dc276d1482743ad1dd3694ea1928dbf0fb01f7d41496c5cff9e39a28d8514420a4801cfd5332fdbfd1dedf6c2764b661f2c4b067b4ca01868312103c30d749996445353e1d04c7dfed18cd941efdb665516b01db349b0121f58c01f1bcaea958074e657ceb2fb529bc674beeb9d404a6ae8827a4da5df369ddb178c1854fe840f6733af5ac34625f98c4c07c3ac040ef946ccf2c7820236a545c26ce53f88b5f49047ecb7a570fe74d3869a12ac42f0ca321c67f0c012881031e83318729eed71a5ff069a6756e05f4d4fa13eba46638bb4f9170415595fa2190eac1a3d535db8c9975a8cf8c7552c78db583980ab77c4a35c06cd240d55dc56f25aec07103d5c785009dd9b1cb22aa6c045c44079805548881f13fa4a9b4ea81d0ca522f89c94ae9481e2b007ddac4b89b23ea46e946f67c519bccd5cf7120a7b4d645929a76d503dc0f2e9bf0aafc59bd1a22c85a5cd9c3d0ba608c9078b6ee7016221dd92ace9280b3f40e74ee96bcd4b3c5222e4b49d35194152bfc9aac26e1ca2d64ee3194f9233397cd41f693d0139797fa1c63452126f819417d6dc7bf80c1426aee6df95115e1d6c682f9ccfa38e6d1833e6c31cbc23d6e693644d968140880348dcfa43c16e4a8bff3b5d487333e8f37f3c460ef00253505ad521552b56d4cc69bff7ecc9b3f26be68ead74be1fbdf413a2a5c2eb7227dd4b9ef071a376f0a7ab20d1992b01655d0d9e76deb67d86c18298d2b505a5528d1ecebd9bd0ace1a64e0945f2d4cd15dfc4ab5eb51ce12f6b7647caaaceb6b3095b364865afb4122d7fa16a6816da3453743867b8c2fa0f311b81e74c5fae9b552f9aa5991090bb3a89c9055d970e3d63e09ac0cf3d8c8b24dd5556d1a60a3808cecdbc5eafb0b7d9f87ca6c67553d319081a9b1129148db0f3f0f8066f2f392a9b30550f9c53f4426d36b915829ff15bff3de3f4cdbfc96a375c05a7f53ea5876c2e6bc645cb191006a6aa1100729028958873c6450c9e85ebba0d8cad4c239355e80a414db27d5a28171f8dd4817b5854a7d906bc5fec80e86fceafa4c739002f2c364ce2104f9f18f9afd6e256c83c6cf34daa353f894dee4c24f5d30ebc62e21833421185809459a9558e2c095f72c1b422d01fe58fd108b55d2a45b685b1279cf9228db1167c06272973f378c34639488fa0a1421f692883ed9de3d584b4c16b5edc2da7ebafa0ee9b2f912c7e334782d3392dcbc6b8d588e52362bd60d280c665e8cdcd1af389567b8483ecca871c09b8771ac981a73733df08412f3ee55446679dd9ec3eadd0f33b43ed5332d25c559689f07c428bad0df9dcc0466cbac6cc0da2359c6ee46e845956906aea2c28ca3856176142268ce2cbddc2bee8a42d16a1440435d1213537b39ddf13824c62e8d661370329a9f35eb7e96277fd9a35d632a4b033f404a9239482943b1f2952c349ddf56059234259bccc877c15bb0a9dd2e4bc28eb36338dd50c910b34b445adfe1c7a0b63b8818108cc6a36a94df8ba13844f0a307c0a7a141ebcbb9ac2fc8a32437306ee1bf6a7c74ccf9ac2713c9ecd9633f92b6004af31421bdf2cae72648185e48d2631d5982eb6755a5ce215e955cdd1c43005b68770b9079cb57701793753334f000d76e7a9d261ffa74fb8c8d825f29e71461bfe4c3af8164a6d6b7059338a43d4940de69d8a64cca00321d66199e85e3f1c32ef0057007d9bb54f21b82f72462183740604877d3d1a43536095b6e91b49f5a161804abbaecff65161557f96e7f28e0acfb0c48457d80ad0be3814bff3f61a164cf78dd6ab82c0d84dd50bbc57782410607dcd5ea80fe97085bce37f6cfade87ec0a23cb18de2007da3bef8dc15da0699633de414e041626d63ce3671acf7c4f0e3484f2c12f9d1e4b16282da6cae927d144cf810b07932f3079ffbc37b63a0164ecaddb6e8af663bcde78448f6960ea984558d4cd79694e9f1fbe3fafb968720956a119a414342d10fc8ef2d29337f05402e748ac51c82520de81e9ec435888f43f75c6d40eda8aeff7f94b2182f3c0749bb68e34aa71b87ad4ac9d72a28a3a6248b1498905cd1ab8f00cfadbdd3e85a7f0f43e25a732fa5f27b147e5ada964368e04a0fd96bf1795ae6593bc35b8c14071c3d414aff47e4aa0d072f46d6338626e89b4066bcfbca64c8412793d13e208edeed92e00b16a476bd938d342e80c98b2eebed0e56b4406b04d5f301d756b407a7e092db1345cb259fbd220419463919069239743aebf7d730d63dc52031806c6be7904a541a3fd8760c96879a49938df40715c23203bcb85232923c045121b1ecb2968183ffc9560775830e72c0aa106e77b82a4e0a8b5221fa7cffc681a6148ea446a1cb268b97f6abd112c49a2f182703d44cc8f18113b9bf913a2fe7d6d14bb04c1407b35f675bda18409e9115342f848fb05c0b4821be70bc347fe1d8cc103bede9f6ba57021d3ded7348ebd6cf58a57aa393467da253891bc25e78afc45ce46247a0745949be7a9809b01b4ae258db6b1f16e9af6c1c3fd7759422e6f6a9711a58eb2e2cc41c5e583ce2e1c8d4205da41de39630def8e0f14feb8595d6a04ccd61a063ae3504f69e267ec3cb11042c28241257dfcf82fece5c8856129fee02ae9385ef81ccc10f68c3457c63d45f0e9715e4aef1627bd952e745004686a0e50f7b9c9616d48e7616a1755f49b943c2dc4078b99807b7617d1b986983aebc480dd20761168273c5c08d88ecf89a51ab1099a1cb5b8b298bc46968a5fae8339a2bd99854cfdda0eb2697f6225c7c54d2c82bce6c5d1f7a8060697635b624c6da3b63fa3cafcaba44b3d62907b64ef4b456b5ec7b093f3dba8fe9d4d2270909bb2263087901e276b4608e35222e0b1f26436f2bd7f7379a183dbee6b71b249813edc9beb6496b7b631386e83dff25a64497f72edb94696ad439d1e85e715ea72d01508516e027eff1bffb9546487f0ba4e97e01337cfd06dd67c19f8fa7eca0288b1986ca8e083d93d76612277d8e87c5e7fb68d622d7c29ca5f28fb09b910483c620a95e3457fd0a59653d4a5331aed4b06870f7cd029e3802b4e6a250852fdc17736adf568472a0ccbf702d9a0b0ac8bcccce48adac47b2d0cefa25345c6bb97cb89c176e02b5f8dfd4432ae7de2d8709c8c7882c6134d729cdae142f5db6605bcf50f01e25d52dc87ca7e126d8bd1afa6d660d7d6398c23d9057126a5e6e21f1f74b37938141dd7ba64fac496ed710e364e8e0078ff59525a16e029465991108a0272488d82c3380007a860831cb302700404f482095e29a8c0582431ce8ba7e598699c000fa424294cd320704a0674888b22c330101f485106331eef2b41cf646297bec2b4b426d1228cb320308504f4810b1ac660001f484885c11cef13178394c7f36933900801e43408c65980908a02704b383265395b6101b1a3f5992a44d4058a6e600007a8424044cc2efabfeb02c335a01de22b3f9f6592236448f77244a16f06a8a766a55a6af060bff738f2cda352395435e85ea7a949582c23d2314214a072a214a174d88a214bd21f0085075d004d13a68046824f49fda63842640e9a0114419a1ef500f015a278a009508fd127a46d1085042e865f34188d641cfa11aa127167a0f5f0fab4f2ee80180ff482cbe5685f9daf37e984a4393abcb20d8c7cff1c2fd1b6a71d364ac4f5470827e45e15f54fac7ad6efb9685540bac3753edebf06d3e12e2684941e78b8bb6c0bf663cc3d236059fe387ffe03ef22acdcfcdeed6ec7d00a5569875a0d14611931f69981be18213a62b23a8dc2885c3f22919d454b75de84cfa4443689624b0116285197acac27e23d244029d29090277879ee185d35de0a18a57770b0b65987596b9b7e90eeb0ba4f2438ed42c8e01db144235a7583824192a78a49a23117765406e65ddf515ec5e6b7eabe99e70aa278c7ab0adeb01c8cd9e14544d935a85804a5c65c55d10e33c9c6ebc27443ac1046baaa2b8c1fbf16588702d2edf80e26003c519f2d473e158aa94d2172161f0001fcb6b993100675442687c9f681167310387704d585477d9120b8e550dfb45014bf806fa4f8114e89eba2edb70e3a64df8b055d1c22316755a488ab12404a515be4793a41504f131fc4a5d966ddcc8e6cdb665014fee87423294f7a546c1c81a1f51dd614d6c8bd732e16ba7b46886a46c3c21e9efe0d1b7d006277ca05566f4dcfa3b9265d9d4e34f417fa27e0bd8e3434bb288d5ff87883085e0fa19a95ac1e83d3ed31721da615940310a06235507c842eafd62a5dab736607cff56abe6782986e8087a1230825202b468d84ec01244ebe9568d77b8b0af56a3a04e8f1c466075b3840310f674532deed53c33bf0036559bc9d822ab4015aaa2563856ba4a3d40b87660d8172c09e5c05a9da5df0f935b2b8425b2a8249698f9487de367a4aa02a3f7f8b4560449afd646b186d28a0ea58dd640b9e3bb4f7ae3252c7b2e5cd4f17af38cd105079aef93c5b2c901ddcbff52c1785a625f9c1c9b956e7e98108f4868b36a4862e5d4e2251bddb469132ec4ea1a7d041a9bbd9cb1f5c259f6ba50e68a8e82fbf8bc54d765366db871d36c5bab5d8cd576fa8d3d5c19b4f24cfcd8ea9c0fde41a8661159e2fe175cc385a09c08337d5caf44c3e743733730064a7bfb3cc7e24494f386feca0aaaaba81640cf0ef48176ad6922ad07a311f63576fa0ad043d306f8258db0d1ec61c11591e2dc67616d49e10fbc45fc8c8546902a07e4736b176ea8fbccf41a440aad26d39a4668c8460b1b4dd18195604dac4eac11fd88986068ede3aafde50053dfe3a2e454840b846779fbdf7be6b884873b83340dd517f777250ace6e7a9d64bd237158f463b1bd139b7625beea67d0bff7cb5f7c7eb162d7e1b9b796e0c3b9b70a500b2860026cde99ac40d477b8336fccdeb26a59be84858c7412459a7390d35c4a10dcfc315c8e2395f2e355752b517c78057acf1154c5ab9fe1f36e567bbe5e9766fe766c50df5d4bdfb46913db9079238eee046ea7e257f92827c9dc698e8f6ab732ad8f61e721a7f178900fe0cbe6b1cde902e1a0d377f35aa41769c25c030ec19cb14e70324a7814c98aa8f28f9149f3b3f3a5d38a81e46a4c67e9a400c0ff5192f051442318d421238888277edda38912ff6c4785faf4acc37c1e22439aa5409e09ee6bdf41dd13a021c2e16026a60b904bddf8c2f6ca073c257652f88a0d40dcd5a5ba9a72bab57bc749f3e9928eb75ec12a3a5887a09be4df56941a68af3f737101752a7bbc2abd0a8404659c9f09065c9f300c289fc7eb9e6942996ed6196a0bb99a3798d2b810ee05d7909530caa720c834e0d76e864e1b9a0c393f6e450f2b85e1ce26250cf19331dccc5188c410904f81f60c8734d86d8658e2180311fec381cb27774edc10da33007d3da1fee9aab363ddbca5f53ca1490cb6ba21176e8007a0592b768803b352a0036b4c70a0d4018011ec12542c8d81e807ce80f36cd23f5a8b551e36603153bbc86beca2902e3808b46fdf52a94629be6fd5280cb05df36495a25c27b8975d8013e6bef9545415490bf941bda054c060a1f43ebf0c8953597c1ff2d9379d651dcfbf2f980c69d44b50cf317102256fba9d05c60d6e4e2d57d1542dcb1e82911c7e516cb71f27673865a6d2d0625c3f884d3b85a0aa1697b951dea09c25bc54d6aff51c54504315974cf9626e9ec92bb02b78da30257ce5b8e923787d7ccbc3a9b5cb02afe1d148ae77013cf4fbfd731aa2229b10a80b2988140a439ccca6a0c0cfc28849b2baa6a028fe7fbf86e2497a4aea2ad6034ba93300e1b4f6e0fff9373a24b3cd14fe73cde8bda363a2df80f3ec3790a8b140b0e147a675ea9ebfdc1d200602625bce15092dd90d6169448e6289ecf3a6ead992ebd38d81a9f1ae94ee337743499e3255493d026aa8ecd756fbab300d5ef4b1e53706ee9898a6e056276438773c6eee8ea98da144b83a5e09c9aa340cb6a47fda3c31153a8493c4df509aa51e40aeae09c2afbc441f7f83db7141c349b4380b9b70e872f5c93a3beef4af7544727b9b8860a6ca00e2a5dac336ec694e5d1aa469209db182525071c4c077e8c9319b4c176662f7fb916712334d08281e9eef6998e9b9247180687e2293480759c0633085b0745d24ffee8717919db8904e6c2af2a2852824212e08e4594d3a6acb17612378d380a8c1758e872b2e336133fbb0fb81b4fbbada33b232412f6497281b19e550088bed44a3ccd5f88720f01d6069c6b0f7efb38ae22a146b281c3cee5ba2a5cc2e48372612a49b87ee00115df8bb8543ca61868a7f1a609140ba27a7d2ee261089257815d20d65117b90e8b9a10ea35aa93fea7408f357a7dbeaf99b365e75a61ab121b57e1ca05b3baeaedf78e660c3099149e03c341e9a5934aabbc2ca1f7559c46571574561a5c057a72f1267a739fc3986820765220672c58b5b56289d4daff3874bdd0978a83c64774050628111ba5dfd49430bba13adf4f57f54d3541828ee4ccd3d4674ca609977c68ed679b09adb4102e284b2c6feb0ca4526fa0a04697aabd6267650013456c4521fdf159a3caf307548b0b7fef320eb660a243a6b3dd72d2e4b8ee1ed87a229a9b0d4e2b968f5fdb6e635c21af26316d6459f506af23ab9c08790c12b03a4271ec484236db297978c62e5849643b505bf94118318f654594a625c22a8ac0e7765004d33d9d411f6844da0dfad84521703615f859dee06e7541acc34e5bb82f226c42ed06488a67f98b17baa6c9f4c53d31828af28435cc6d36a421f2faa3235b720da1fcda4093b460195a0bae4cadc28dba7c225fc9b6e6417eff16a9cba4665e910a18c9a24070eb1b8b226693bc438e840886192a02998f5ff928ab677e85526be24b0d296de99ad7e0e371bdd4e45d32202addcabda729f19999054552f6189632abe9eb1cc530491cd750c3b3880b349e08385bd8f4065d5647a519b9f4e1da7859ae232ef7ed63b7ca687d795a13690e386a55d79f4a45e751a1a905b230dd24af62190b142f4091209fed9a5a134e0ab051b1e10950692b2aa61cb1603be85e8135c76cf23aac84faaa3abd64d39e8801a305a39a072d20471052cf893735db00eae5fa2169457288d220c136045f3140183c07654766922ce02f8f8b3cb23ae53151486a85757aa37e48a009c76002c7dcf2069e6b8b65175a567e9032a5a7b15b2ec50572abd0184c856af6de9048076f36a0909f4bfaf424f5aa71971e1f9dfdaeda4c98b6544aef441503388f764576445f960f6f2fdb75057085df3bd1ed3df7feb01637ce38185b9fe9b84f357e22b331ceecba17dcff7415d96f0e11314eb5d4033165034263646ae1cb5daf79e7708f479871587156c5f194958648409b8e2702dd5a6953573efaa3d2fd58a6d82d3d1c5c5f77e23e31c760a53efeea981a24c6904582746310a1a4a0aeff54c0c321959af58eb2b4333e88ad5f9e63ac6c373d9845d93d2c04c8bc2e0aaf292a4c33d648f3f40d6a992f117359b9db7e248e618a8d0ac5ee463bae0a490e67cd59e4af00b3845ac062cb2f40ab40aaec83d50cdd621aa21e3ce4a26d56d9cd3c8c7164c08b3a62a45c784b04b4934295e88b16dfddc44026fb08218f10eb55e50c1596bd746bb4414ebbee46caa73e613c58b8d268d2bc260643c23e194d85334bbca48243e1b2a8e02fd7c85793dc994e7b839d2d89c134ca58d0af3f61cb6ee9143535dcbf64b2314a3cc037a87a681ba569de6faf947bbee2665dffdbe424f4c5a9bfe9613fd549cdc21c31d3c79fce3648f0ccc4e9dece38051221c5520d0652824daa0cca20424aa3cae7fff4b6bb104098ba997a431af8dfbbc68b6ac3a89ca889f0054d78a44a5c351a8cc959f9350a5ce4c5ddd7947134595ffd40fc8d4cfdeca846d34a61b933edd3bbdc617abfc25cf33882c80a45f6d95f4a8fd0527e85699d12ba28917b7037b3e127d36833ff2ca7595977b79ae2072e95a313967fc8faa5c63a4bc1e7f498d17ad0567258d88a412d00f1820918a26ecc54e9eb50a3e2b8d566c0a7d82b31aaa6a98f537ebfe7c08c7333ba254d2452aada34ae99fa85d3fb70cc4d125a0e6be5785eaa9b2f8b783c3dd7c5404a598696bda0827688de6080d47e444ba14ab227aa4bb5e75ead4247a099cbee8643f1f4a5364c17acd61e2bf957717b97a8153e103f44f506655cf4216d018773418316ebf5cfd20ee90f2f3759ae745f0a8e144333f1e835346773061ddc26dfb70328f9d60e6d6c30c133416f852bbd727895bf04289c13331a5c2647615e1fd6402c052ea43de72bf15338b6ed1bc67380e4b07f6fa1d06b1cd4f98f7a7a0a17a6a64b0f3b7e63d2024d5122bc93c1e2604a5b882b1f820eac34334b4c3db56834761fec204b724ec8085b1bc762f420e4d1e204b08dd12a5b9acbcd347c6b6246494df52094fec9d1b07cea826739d25a160db5c8eb10f36c9e5c568e5c4ba65c4a9454007686cec62c23155c771bec8aa4c77b01fb8b00fa6ea6d1332e6735f018f106e3b3ed2b668cbf876f825c5b6c94d87eef46bacdc9ed0ae6845155d4b356029bb379dc92b44e28bd72e0f42ac58fea6d1b4a6dd248921268a33b06d839591d5bc4f55bf951045e925cc2d970370b63e465cc75a4c26da65fa01409505e8d4fbcb5294bfe09fb9406569349407d1584746347798280408cdbb09aa92b0298258e546a8d7b9dfd2a18d076b2a220312d258b948e0f25ea0598086fbd0b0b9b75a9e58dccf3c42bf8577d8e873a011704b66d7c2a525ceee8ecd61bf45fff835d48abdfa260b6c6b780704356ea06eb2081df9f34bdae88ec09c86586b4270a43e4bb364c3b5c3fc2fe7a44240e161d354954bcf334ea49eaba8341befb17f9c16e93a293877492591518bc47afe41a8b1de8ca29cba6386e681100aab67bb32cb7e7ddd1bf73f6b4833c2b74db908fc16475642a87848c65c5400be95b85e300d21d46cf4902cdfa777de243cfa8e334b664794a95212c7f051cdb051b8c21900174f034b6ee319ee7d53a4917ff226552370165ce337b290f27e9a97b4bef0192491291c9da00d08c55c96fb98aefaefbda3a011f14873b7b08151e17a61eaa78cceecc03b9dc4b756a2c76ea4f5751a1ff90c2a21e9144019028053e86580ea6f8c1fdfb40c565ce360fc05408595a4d0f6b7ca7116cefd702c42ddcf19dc9e9fb65f012707502861da45eab22d6ef70307144419f501be22508bac7a15bc1fcde6fc6bedaaa1a37e890722f4c4947b80ba3067ada18cd099235417b88ea40dbee7d4ca744dc8d53e29226878e02e9672315392e00d3524f8f4c7b1b3181edd99403aefce9b33d7e2c276defcd4c1d73d2f64110d74a5e222e5ec32ef6fbcb41c43b5a6a19a79934b1445b3cc7b7d128dafb78dc8bea20afb0284e42d1023d3582a86b3bc22eb8627a107b98c8c69a0e7c70d4afdd079a2fdf837ac4408ebdc84ff9b55b69f7f4b15c4fa204a4593f3cd756443fec201e679db0c558278dba37ebe74be390d67e57667d0897366a38750e1d1f15b894f4971ccb1dbafdcb9a6b736a95935c383c69ffc1b70792894c64453c3e18b3b7f85eebf1f9b8623989d467fb67022ae347305a00d531f77c469f25937095b7cc86bc6df4ca467c889104f8548ffb48c59aaa19686b2438233726f729ce399ec6215823032118c6a931b7caf08c17aef80229e697d2febdd953f3f1665dbd48c657a538c1d2bab09e06421b39e8c1075a5fee28319c514ca8a8eba6549c2d7732784157433a213ecf9a9e5b6dd21408e519e4eb2dc36bb3721d31b220c9b67d62c3591a9fa91a7a15d07350bb3034b3fe198ba09c3d2707bd41a443d09c3e549429d08bad80d216bd3ed2d4119384e52db8770685854d9254a74065f1bd3191ef366b00245ec84f5042707db65863098cf7b70896b41f1852ca9842597ab0ec6543e0ae41f36b502835caf1e4c8ba76a8681172f0e1864471dff34687ab7c9e833193655e26a923e742dbced2946bc80c462b53ff9e4cb7c197b658761a31781f5beb074436df2070c1937b66d1fed78365da4a76a1320c03f3f52045d68a261a86cc7203d050256f180ac2447b266260a916835b1159d4395a5e918f0b001a05d889d6d7343a8ea3427f6b68bb4b3439f87ee91637ba989698fa918f290827e482910d809186e108f54353b5315c2d529bc5c6254fd23e705cae86886d4febc04e19fff1153035f01d1e3e1120f0e11f3ca174705b44f00bf1ec76e1d31761a2d5bc3ba70fdb303e95b8a55fda22f412b5c132bc3025aa5a518949368a61cac769a312c457289e126369748382ddcf16e3ca810df86a2b08db29d1fc975c8d3755f01686b7ddab1ac4ab3cfa695f5f24dcf1c93ba1851295291d956940ce3db039b750baeaf689836ef4b8d13fb0251af95c4328244d0091d3b86c3318ca84ea978feb0498cd331438d2bfae472ed95a3c58d5515f6420d697e3e17104c4f67739d05df23858ed40a1db4fdac8d2277b16e1bc81635ff0f1b0fda202ad33cccbfb4193c5e12c7affa7bea79cca782dd864246bc147af36614f33b4ea04e14f0011cee709d802515f19496042af701238b7b4f265e6b3cfe6ce91567f35d8b1ee3d96324ffc1a9471516f679c0d4383eb5a2cd54364042981ea242cca6f067d1f2a12554e508fff9be4468a84c68ecdd1280bbf250b885536a9270686de8170ee10a292a898b88b28d8973e82142b8b88fd887b658ad75e5f6e6253b42337998a768030d65d98bb06673263701ddfee345deee77393b02ee4b49ecc939f9bb4b5d5bb4c421d429f00621b64538b4dc092ccf1d6d0142d338263bbc85ba11c424e5c1edfb2b39847823f384d00ebc4e20c2538f0a5f7b08e66dabfe4112893808c7a97f8cb40944896bd65b766122d2406a7fa988125267bc1b4a76601cb8c8c0fceb97378b2d6c646cc45463ce813f240c58ec23282646c26a561d1d95eac1a47a781e4bc6747bd4e8aac17e9588cc68f687c90196a760c921cb903bbde421a15e1c9d7593e6122503312422e86d2ae8f31477f77795b3c93804e48310ab89d8519022f96630a90e5af9582cec55f1cebbce624f872890fe4551d9cfab9fe497bea3346c95ae56e050d2c143ac4afed0e4b8b46243baeaa18094ac4b4901236f1881981183f42944cf609016b348ce7e691dddf2639872403adee0de9545ff58406062bd810c7477b5def6216c819733fe34b28e31c3f00981cd10cc846033083e21b819824f007c06e026009f01f009c0ce10617cc9a6fe6ea9faf1c5e5d4e2d5027701e00e5d2517dcebe89cf5607ad4004c04908b455c5cc4e02006136170c8c18a1c0cc260220d0631581183411e5ce4602107176930c8c18a1c0ce2e02206a344fe6b5e55b3481003dc9caad268c5073232251525051e3294f3b310dd0d10c30fdd8e628320796dba73ba08f3ae203d451af0dd13212755d13816b5b633f550b963a3a03089d1208c6511288c84d5e2f54821a9fe0e844695880ec5611a08c130cfeff13dfbe6fa5359cb35cb55556175211c8bf244f44d63be3311458cab225598361c0ad90f6d31968a746a8bab8d97b451643ac5c3ed1b16223a3fc1a02dc9aaee255bd56f1e38ad8a5df056840717c41479173ab3599c18cb2da1855b431028b9f2aa1ba0ff567bd4cce37a5ab576a35657bc136adb18e59e7a822a93603e993353a238bd069513ad4249252a3445bad064dfe898b026a60ed8af3c4e1269ef13a06351fa36d75b6ec3cd2ef77c9e93cd818e8f2d21ebb17b323a5091da089cf312365f1571c2c92ce7542049e171a675a7548dddb2e0d56b1cc577c6c2d14c5fef9c2606133139316041d202f3d4392de8170f4590227b2a51d1bf40211dfa969fa74cd20a8127b4578adb88474cd0fa013606a3ff0f463d8981342ea467db8de296edfb3c33ee1f6abbf2494137e5cc29583fd084f2c6f2cf2b7c5201a9192b139995b4ff5450d99d8915821a1d156d26fa46bfb91a3692d9d29fd73d5733c602a0465af4356301609307af6278caf1bbc08598bbac6d70148105d80179cff3fae2add187c7ae536685dc48649f5b701cd54336613c956dd5a0489a911b03bbbd431e20d7c44f0cdcae4cfd1a0e96ad5ddb11fb98654bd512c7d723ca252c29ec35676e2313cac34d12ab14494b35fb83300f437c0b332f61ce980230921795ea76472ad3393b7af1b72abf1cf08bd65bc50b821faaa658831e8338e0a1a12b66077127a5dbf89848042389042c0f6c5cb30bc7dd4b360443771e27cbbc12804e9f3eb3658a5cf48175594f5aa84d5b05e0aa0cb67e306c158663f962d3346144cb398d5fb5b32ac288027889e138b376d01ac62835995131fbfbffad497d8834c70586db073ac5adb58623a01b4d00bcb20fb217356f1c6d51bb504882e6db9ceb938a44ae50b60264c059522c9c08f15dad14b9f05f5120ce8ae16a7a60059500d2adbb01f27c0f17d1ab042c7c1a2dff4ea149807e91a54bb16ee6ead6d6b5d40e1403d8540ad2a02b4b530ba360f9746cd829a0191c73f766dda502af25eeaa9c9981202f467e0ea76046e402c02abf68602f901c28834a44e171bcdcab5736ebab5417b241d180a76eafbc9f4a1de90b84e764ae96a61f4d7dd93c1f616f7d7f11a6263b0dfc619a5dec12ad908d89d8ac509aa0ad0260a4c605ca2ddbc86d40435db20da5991503e6bc0a2abbc33aef5d85550509acb6f2a9102512e411b9908232b93433cfac339197fa167e39714c681115668338a20a53544566508344b7290add591f9542a98b81861488634ae9d016275fb059774bc8612ba51db74fc70b2f81494552de3a95ab657664258e8421ab927989fa370a68365a1d9ff23911fdb413c18a5260edecdccaf87feace7da7c6ee50b9def2af0a4d3aaee461dbfb03d0e26c2d174fb1e2ace786ca0fceeeee78a3185c38cb86fb6b5336eb104d1ad0ff144be1178cf2e66b79299db4b8ff6ec463872f67ed03c9c60a9799e034d526d43eab166319aa16e4ccd78ac6a2d116582f3881c170a46ddba52b0b15bda563332977885616c27f185c9f439afefb88e912b211e2bb8171d224d4e70db46fad6aa895599a533a17ebc43daba6fd579259ea2e51baa0f86769d7f3a1e78bea2879b408e24a7791a50e305d5be69036a5f530b23540b973b16259072b6dc6b0adeb6fc7ab1c0130739691bb684e885c9e3d65c5d33d917ce56a7dbfed48c623806a7c7919f043bb0418c9282ee55644964d067f7a16532d72913645c7c4b11d6a655ddf887405d146e7a3646168b2685e56d7855dfad4b97cecff1c7ae2ed401fb3c406bcb0e35454483ca6d4fb1c04b37ad6a62926f3cd887fcd7e56f8ac85b86b064458cc96edad21a6c682fd8cd3f7cd6b2b7d46b48e355b7ac480ac97d17b8c7ef3e2c39ba8dab3923cd2e2b45330cd453ae258c69de464f1d33a9d7902bb084456bccd53c52b1878b64a630dcfae88a6a623ff6dbbfdc2f6af93a93377649291c00b1c7f085947149f47a3e025218dff19d192514aa5bcce83ff24f4f8212475c6f5a088a7bc811180f6a8a0a3720db4a78f8f7338a9358215303773647acc1071e3b8630cea219c43e3d5ed98b3b5621c1d0513066fdadf9daec52037aa68071ca8206c99fa276af1a5e8f7d71546447f047e3e4831e9a82e194ed22e03bbd1120b40cd9fdd7167cb608b7b5e39e1f00abb26a95d339dd0881247b23b9b2424f37ebbae639ddfc0af949a21e137c5d11229776bf61e81d84cd49cf14166a771d4a830e6906acae044c1e95e52a4c96140f14f2ab11bac1fc7625dfb83f0a04c8593fca6b93e05cb41adb94cf6dcc546a6bd8204655d71862146620854b7edae8c7f292d88c669dc62036791319b91064408552ac2305b884edac9338934ad2d462fb3773aaaf3abeacf4cba25165ac7a4d92f604e745d92d10c76a112ea58e3fdb464fc13b3e7469e4d7652865ac9e63e7bd9beaf8a0f6282e3e242211bc2162e1a11b89169b0389c75654352be3665f43bfe698cb247c6da4686c5396e3c3db3c1f60a474292895af17c77f1e0b439f002accd6a29479551d45665269eb5cd96e5ffdf15c500c199d223349fb54cfa1a5ffcccab21d0a2e618df046aed6b89d3a077bea9e78b8956d7acf810e8d2cab3a45964da7db50bcc7f3f6efb9e02a903a46ec5a97a824d1ed05cbf6e965a9aeb080d5172a89cede1e6ecf69ee1aa8531c4548bbd0f469214c9e22a0e5dc1891cf166adf115fe6cd1a2bc2d6074614909114345acd7a66502a63d71663c3c1df7b0e5b8975e89699259041c2bfedcfa6f82acf823b5ba4e740aeb9e157e3394c48665af1e90715e2abd68c83629271e77e62753b26cb36ff5de2878743499322b631471ecf76a524bacd011f28f8c1e205dc6a6cc311b4fd6b9607190e769b2719449f5253c4fa4b71d627857af5a9345d769ecb43f5801bf40c01802023af7b87055a38b586bc93ba14e6b205c0ccb7d6483c366fa038732b5b1ea16cab84ed44d666964b90c8f7768ca62f98bd0de9cfe497a8e81b69842549a79a6c50fccb6a4950d769fdf89fb220e7bc59bbf82e0d6a9961c454b70fbded125bdd0f3146bb5a9239754a5109e7af102ec7f264ce0666f01ddbcee7697b0dca38f360295dd06a0f223e89d4250ca8b05016bdaeef09bc6ac6ac03e16dfccd13287673c26d639720e669eb50954410349c0354aef74d7b943d3ebd309156998175b4998c86e41157a037685b42c3a004da3704181ac65f962b1c53e4f10f1271a93f93bf4d8caaf32e4792b2cd27cb5afb06b0d4e120702a7eebcf3040cd539f40a611289b2de268f41a85d0fa5c069cc408bb080f3c90d092e300f81139697c9646388d8e9f36b135314e1a1ae6e7dac858ec4005262647149b2d29d5262c49c38d817ab0b61480914d496700dc8a8ffac4df3f0b7400a01eb32275a5ff78007b02ecb83c9bbf7278312dc4fe16f6a0b7c8c3b47c6766aa948251c56d3c485b05ea14a6a1a8403e72b9531919bf02ff0b6646bd073e05fb2bd9ee3ac59d9239ca628d15f0749b8b7b01ca7cec3161753591735948819b86bc5a04701f6590245fe35d0f7c182c82ca9729060bc110a81b96ff35fd02fdf561a4213b03cbe755874509ef68d2a84894ec342bb23290a8d690c328a502a706a8ad2f50f8a464447326b26e45015729da54fc13850d95e382d25ceb8ca79e1641c076c86deb6de374ce1d91304b5143cf3665c1546396c02c126e2b026973c519f31c0e9173aab6bccec151345a45c7940cc94c63070caffc79ac39324d0182e84965a10d66e60b0320192502a85344dcb3e07de35eb1fb8034c69cdfa832ae537f2a78458987b662d4568d6237b887bb9b4224c16f5f702e8ce69a5e9af1421a735777f2b58ed57eae1c99a0a0b5c22be3dae37899ad7947a3e7a3b2f65d989376b226ff55d39321e8840ff5415f417aab05dbb2fdaee235f6de4d1bfdf89c5126e96f4f4d15f676a3974c73eea672abc7dd6d4d0e8bee242ca45a5728ce1e0784ee87d8d9ccac6ce3e9096235539bab01eea65156e3c6b5568f4be120e6343b859e92d66bfe6d1b7c35d6a0bd0a65bb837ed919e00d13e406d626d9f6647678711dce82d05b0a98becb678552adc3040aa570ca183771d26a8527f4c30b4c7bd10ba7d42233a7ea753101d7af748649cc4216adda3c2202f5496f47c03272afca78f73e56dca37e1e6fc5e0b29cda6b60a9577b02415ac0beab92d0f22b1d30467b7c990ef17987567c5fcb470edda81a1dbe117a030f4b575ce984a313b078ac1d3e22e7d77e93819586287c0d7739169402a669f2f383ba9178e321d3dcf093daf8173d9d819ff8a958a79e77c56bd6266296bb8f0b6b06ea5f544966f9dbe8dbcbdc9fa2a0c15e848a8b6ff8b2d625dbcaac6fa6696d0db1ee0a22c9c99c184b4e4f20c0c39098f69cd96f24fd99324d85634a7a2a27b28c9c6954333cc8ff55f2629c2e5bc2c364a15bb5603a05a8006e0dc72d6c57f72736b23376d6516d52507e6e6bbda01d389d12afccb54941b000b7113ca35c93c610c6fb798a0f8eb2d41901aa6e84a572ecd8fef709cb3a354338fd4d46f9989ea05042f19bec90d18a89fb295913f400a39c42817c45f2fd2a41bcafd0886e615232ccb72737036bc641e3ee95f6e52ed90816a36851fa18ee5e55f898ed9a8363c610c3a5dc48d12d49d66c4bb81fdef882b91fa0c25afa47a5c2a9d529e4b4d9f4a6e8632d668eb2d2a3d427e5b08c0d8207f34db9779f68d96440310cfecdedd26f3c2916a5b6c0a9a88be6f607c3759278d780a4dfae65d0ec7a26ad5754ba7f0b66233ab79ac7fbc2a062fcbc28224b9f75b8db90cbb68c3b8f857c5e0c7c4e5592488ce7eea2d6763247581a5533d30f342f93ead329bb901bffd2f28b23350e8dd45b23d70c1fcb7f25945a9e0ab6364b2949b7c6ca43f4c51c0b0944f415dd9aaeb0f5539ac8a2d57c3d4d5c6571a100c24b9a648ec20a3bb9c2974831ceb45187c395a1e1fbade9603b822e902702e8edf59a533d319cced88936beb062902d7ef6082df60a165df4428a83bc639fc982bc1e74d3c60b3505f8865c88fa9e0585537e707ba9b9f8c85c3670f417bfbd05f46bfd9d434e86b04e5c5dd24c8272bac8f99e0486f72f6d28420423bcc721a3c7ee98e4ddc10c12ee764b7f25aedb78dfdb60a76fcf6636d7c766bb3f5c38c08e1da6bca2f2dc90b4eb741b56d3c54fbe741cd589b55417af7c9968d2844943f409c9a35a5d7162cbdd9c82be374e36f51ec42f094c35c38b0f9bc69f74bcf3c2ae63db22f9f6b154a96fd7cf1ff3216bd57083e0632128f6be89cc325b165d3115ac658b90f34f88f94c000202ce1b1b33a18603f5835c4f3708d7430fc31a1aba69d1e4492391b23533acb6c9b38e7d0a1ea9699442518044a8b9d8ac4627ef4ad2ba606c0a2b10acf356b2e2f58e0caed9815a05b9056550a3da82adcbcaf3df361efddea4abd364081bac1d3cfa3aef32af2a5bb5e6df0b978eb5df749355a44f5b3cb84f07725a22e6617913bf1be8c7203aea6b8f83b1ac16f2364602f40ef705976b5c665b85a269035212e8427661c696bab706cf0ef3185861ae665f8be94328eaf76697d1faaa3a58311dccc0cd3c77c2fd94d26871eb89c8cc9c3cb96bac81594370f0825a1c70ee8a396e00e6785e15bfde737f14afc316eea23173a6fbc201b848e8ea8752e0af64cb8a1b7bce0ad96f39b78a5d733ed04587d89575879cbe315a108bb560ec68f4de3c6e608bb7ae46eb734e4f5110f35caf75e5ed88bb94016596eb527f3f0b7449a03e4af79c235038c6faa43d1d35bb0f6279fd7a889362903ad3df2578aeeb430a44167c09b535b146e4da4cfc1038c593e49893c331587b6bcdd5ac50d00b8604a908f03d5f802a20df5dc7b59dd75abd7af9de480184fc85653c7b8f73e9e452267c1b7d2b5295940903388233ef998b935f74a93c695ada493b107cd5c69ab858d0b5ae144f927a5efd1a5c5cc6314738cb1f8c62d4cad6514f3f64aeceeb80c08863b28dfa4eece34ba08c47ac37af391cedafacbf2a3bc9abd97585aa4972a08e61a0dffd1563fc78373493517a74eaa5fd57d3d8bf72429b70b86bb6dd9c9276607f1d6bdc6885a9f6c6dd5d188381fc251077677e1593c2ddd8f78e42b02ed35151c725d44fa545235ac9753d363655f68bcdc684fc1a7ce3e605b498e3b8b85ac2c172ced21e5dbb282d9480a646c7ea35ea17335796723ba217c077a66e3f7f493ce5310c84095e04ca09264d65a8c6a8e5442988c17b65e51fc3e5ba7a9b6ab24f78cede9e1201def556738709ae5976f9f4811caeee226c28371ab155907db31af0cd5715421d7c5af93950ce2a531bf78042f7af290a8e38a1974c17de3d3c14619bff6afc6e51d48f73262f05591a8dcce81a552247ffe12b45442c2d4ed396c0d64d3c88833a66ef750c04c95f07bc65e703bba14d7e54de6b14fb2365abf276cb52c08682c31785481ad51648f24b7c8feeac0e57a712e374548fe0bdc2691e5d067a570e65a965e5d207a4a056e673a8561528cbedc36a7ddc4ae2a5f6d78b5f37db4063877fea079d83088c47dd952857ce8ae20efcdd96dee29ca5e19449a526b1539ca9573bcb70d7396d1e24cbb01cfaf592e79d2e274c135641e9d57037235070c1c07306faee31c1a0d602270d75a20b3433e528bcd108c2c34e8e826f29563e512d01183f96e5d11638fa8edc2b48cf691ea05c4580ee0a0ac64647aebb205f0b0b899a8ac820cb92517afc3ea8ed0e582f74c251394217950ab382738e31f1381062bdcddb58b035014f49297ac5b32d8649aaafa3bc40e31efe2c540d1d8fa779a0b54840b1ee981dde85459fdd832f9c698656a63c75de2323db988f1f439fdb75bdd253c8dacc623fe09ed6d3210134ee7e54dcf6b6c7c715b7627cc1072172548dbbe3c828e2a5e2bb5f09f4f5194959c76ca4531569bf983a5a2c3d73404134c9f02daea7d6a59204a529b0e988e805317cdd9ebe6d332c10001008eaf75eea873233cd2ec85e339d8ef152f81f320988b602f61f6c895030307b4a5a0284d4088b938ff5d4c47ee2b7c228d11fbd7fbd0c21c8fdd3eb36d77db724b99a4943205079e06ab06326648e1815c1c06772de40c1a33441a4ea3060d1aba6ac917ad311a191919d5a831a5a564c89021c3860d296d34ed84ba434a42420abf462f3527926e2425859b76557da9307303c78d1b2313235984965253d505f405470e1c3410ea9c5ad2a7344c8b4aa74553c2a6643933256aca0e0a7d430e1d39724cedeea65d527e4ef53d59744ca043c799ad1b6957550d56b98d47d9b4ce50eda65a4dba6c5a4d3ac104b41e9816d1a2285ee0a65d53d0509d9991019bb2352507161d90b48b2a6b6fda4585b535a6382805638e1c39725040814f9c09269860821d3b76d07a6835c9dad4e276d040f06da345f477d07a8050e7eca059509d7e55aa250b0f48da4565b569e89a9a4c6422a5392812687a60691d72a4d987b699b900aab599c4012dba3f4a913a4d83c55dac2d398b860394041062714092c020d30756c6ba8f69f0c181ebbfeb3d4a38e0e233f0218fe531700746090d4d0189197a310524e6c5035340621e803105240606ce0d10954d0e8e888c088208f2cf08cce257741df770238a6298896104c728f7cbc9482263469219346ad8b071644b728494d444d28d266ee0c8e1aec40b3139747c79f1a6860d0a765cec6ea6f03d42daa1e40310501e03773909befe05478e7d5eec6ea6f0f52fbecf8bb34fe19bfdcb3edf9c3ca87854705de977957eaef4ebb1f45bbacee37779fc9cc7cf021f5e96b332722e4b165cc0741d63800106175cc753790cdc25f97101909f5b79ce08262049806090040300f880810f4a41b8bbbba7f1354cbd5b15a62a4c5598aa30f7b29922c5989863d0415c5686c1325ea6cc5dcccb64008934f7f2eee5553162c4bdca79f7b2b997cdadf5ae09b325e66caa7ccbd954f916c8f32d6753e55bee6095835b6253bccccbcaee2df3322f2bdb55a1c1d95439cfbdca7969aa9c31722fef5edeed2df5c47a41f826816f4d4d500b07987aa549e587cb14a2f2c3253444e5876be805951fae170f84fd58f9cc7900c60f068ee829ac886c931301beb9c72d2202098e90a048ac284612318c9418e594e464cc684183460d9b921a368ec28e90c290926edcab4688eb060e30424fcca0a163821a3626a0c041d07b355cf3b9928c6b532e98a41b3b9470be615432be60ee0e259cfd52c9d82f981d4a4f286dab7de24b811c4c819c4b811c1e15c8a900df2d076f3977cbe9b174df480c2a4597ab821e3e7ee09b9796967ce09ba93cf99ec29458e0e382242e951b03cd8f0b945cc0a48409486fa9b77f6001abe7175fc7d7f105c15c2e97cbe572b95c2e97cbe572b95c2ed7c2012327614ebeae572761d5c9d7455ab2aa4e7ad5c992120e7c8c25bcde121ed354ef4b099e9e7c942c8c92895132536808826a8256dd7d5513b4be865e104121f5e60a0aa937572f1ec031e14b428c095f12620fc0e8a1e98171a1b9da560c2c6bdb078281eb2d2d59a148427a6332183366cc983142aec0aef6931aaa0cb2b426499bed7f8cedc9270747448f08c2d70b6289ac470441045ebb9e17d39b5e04f83aa873d6bfdf53d8931a23c01b66f1934082a2d2e7631144f6865c8aeaf596ccf46218d980789e96e8b1e482924541c9def478e8f1d0e361e9aa06c728a775d5d362e2d1a2da2f27a38594948c194abc9ed65292de0c1a355a583d4d8b901a366cd838b2954748493e63636e926eec08dbb1e4a67703478e5e6f69490b45dad1b381d21e971b3bbe767ced78f25d2225c46ef8b6fd17bb2b313974f46af462d488f2c57801e57b53c306053b786258e1c06258c5b0c2811d21ed5022715c423b1c178e4b68979444eba16f286d9dc19f72712d0b595f974f4f35aa1a158e1cfbacd56a542892596bd1bb188733c52ed293cf14dbf6494d8b2b25df67efa757bb38e3fcc747d6866aabc191a9399e90784a8216e186d8f6c9f7659f4b8c59be31cb0ae8cdc9238c16d912bdcc44d5e351c175a5df55fab9d2afc7d26fe93a8fdfe5f1731e3f0b7c3859d8b64fbea7362ea6780abb446462db7ece68c9eb3a56d35b5a72d3eb31f196ac96ae967efca0f5b8175c877af23d85e5683ddc7c5293e4c70540967abd1e14d7ef29cc9f7c564b5617e9c97789ec6b9224f3e5684b46fb29cc08d267c404c45a5901c1a04645610000b2259e7c4f492c599135d15b4ad203008d192c60ed22dcddddd3f89a2a1004c1fcd7ffe27babc25485a90a5315e65e3653a41813730c3a88cbca3058c6cb94b98b79990c2091e65edebdbc2a468cb85739efde36f7b6b9b5de3561b6c49c4d956f399b2adf0231061de4f996b3a9f22d77b0cac12db1295ee66565f7967999979555a1c1d95439cfbdca7969aa9c31722fef5edebdbcdc464a8b8cbcd7dd31c639e70cd22cf070268e349d1d92a7a622970362710b327a32569ff8ea93b1fac4779130b03275fa092d883d31b052e3400c26524ca48a9e8eb4722cc532d75d4b13828ae4ab1af155245fd588cf1422fa49c5b9fa49c5b9121ac289f9d88889f9d8880dbd80b1e66a5b2d5c6bae0265b09ce062c30b63731ecc19fc65e5299d2e2860131778baacb8e9bad244ce0a984d6eda85e5041fca28d792ac395586e8e2c3119fa212a08a9624aeb68210e2aac84b104ed4b0660a132ae0bf254ad0845911b125862e2ee7059a2c707c5e7c3d5839590bb9d1526b43afcb97eb4e6b69a0e2216e80b33175dda53566cf36edd2e222e9ef062b9a3e495f0977a9106b83b5a503682f88afa87388ce8012516711faa5053358868068ce223d30d02d3070c04e2148d080d4293ad905764ea6b40076da3247c329669ccbeca2ac4e5b0625398bd85a6110ad18e54f056bd32e2a6acc4dbba868edbf4824bd9ba445f76d71484c3b81acaf1b016b5b5a0fff3b420c235c6dfba54e0262db031db57d8c834b34d61c3537c445aa550ba52d21a906aa6ce7e022d9d34d9d073acabd48bae9d720e81932db62913b8b65a7447a9186ccc850c0cad56944c4979de7c73fa4888a2e1838f30f29b273440f27fe223a71c854387d48100ee790223d449c438af4287162738bfd223d584e37b7d4a75fe4d621567010b5b6e4a0821c13ca5e44fdf445b28f8311ad13eabc25d4295a2b60670c15d03a45af3080719d62eee2e44d960542c9d7296a0b643933764357d829cebe8c9da238deb2ce40a5758a639ea189ef96456e8c699255ce88e2ba3ac59d3840709169a44e0aa686ddd769cb2024a7581b6f59b740e19da28af196156b2cab56c048d669931cf1758a3ee32d6b144af84e2b841924c46c2c76a778b395de2a13294848b33b45a06a01b018b4806e3732e78dccee14836ca5413e7c9db7b42c74796289ef1459d83541a401bb91715bdeea0725398392ecce5bdeaab93bc516b6d216aee3bf228a23e72d2b98395d8eb03ac55b257b2376a5e6e6745fdc72fb41d5ec6ade76a77f5218188ed89d629d618befa4d686dfb27661210359a72d6f646a124daecefa4215ef145d8cb7cc66a4b84ef1ed0e4bacb12a6b932b4670e56838af794b9e2a667ca72d6f3f9ca2d078cb22f5870576cb2294eb8814564582929cd714121212127a614b7a42d522d68ae4d7b68e47331bcd42f1092df615823a9c413012d1703aa328255a8957fa8a5922961845a4da147f89586214916ad7b7d8dc19c5a8dc4e8aba48e415ad5c8cba62144f4d488319bfa882d6037badd17af80e0d08d4be5ed68dbd1cdd6ba476e009b14d9fd062bb852d88c52dc8c6430b07dcc50aae2f718499b7e9d328cb8498026e73451d25a6607961de5fa116bf46720c6d598c12a18cd9d4e30c8b09f10c912f769ee4b13d3c6f037b48b051270cac085f066d1167c27c678a08f3e933dac460b488848183d898cc8e2a6ff532885db12fbcb6d2eb3a7ab44464cc8dfb27822002098a62d4a0d70d4540eb8121c021495a24312aca86f117fee22275a4cd39177d1fb3a225f7ac1471189c6d8bb1ed5b8bc7b42ddaf6838e207518ceaef6cb1e49b0ed198b04c1b6413c90d61e11e14ab39e616954b7413c903a3722bf6c908974485746edfba218de336bfb737091c2f7d7feda0945d251aab8b05d8399a12ec99db17d5f67d974d6be3639a4ae16b5ef1809c7e22e29468b2e0fd825babfb149865da0b04b74df85195247fa76db6e32cc1ea9b147f5cba831778735a24eb4c02dce5c121fcc66a9317ef05e11f0f67bef2d1ddb6c94dec7a17ed0d4514add9f3a3675cee2dc5d09df94524a3169de7c43ac318831ce18638cffe6bff9de7bf1f5a829acbcc074b48b0a9c6dbbacacb63545b01b02b62d826ddb0a556b0850dc4b8a79468bbbc1a4b8744aab950124eddae2c27849954d2dee5a30eeeeee4ec70b844d8ed35add5d84a7eee5b5154c14424386b44639a26b9ba7f1d022fb74a495a44570b62e888507413c1447dd96e2a0286041e16deac5656ceb482f2e629b7a71f16d9d8d7a7129b37936f5e2b2a50051c837d4846a2c062844a0296438e285f5dbd40babcda63f6eea85e5dbd40beb0b4bcc9e6dea85b5c3ae26154ac202baabbd8e33a8c39938d274764e58618b3c359e9a56add65a5371a6cca695851e68eedeb3a2cd0a9f153e3f31cbe7c7e72763d160f8b1fdd8700d3a5536201b50c681b2006221c58b14297a63716b23c5a725e54b8a1892f61bc771bc0545d14a777676765a3830a5a90b075ceca4194522740e6e018c75c3a6efe2a77298f272faa30a152a547cb9f39ea9152b56ac284daa2831eae7e7e7c714d240404040424352dc6eb7dbd08b5a43f3008c076050a9e0cad2589afc01c6e8c2850b17307059dabb704438220d68118db5ac61538b13121212822082172f5ebc8860e73b545505148a6214c5888215e58a9662c5fb1846318caca69ea0c531ca19e59c8a542227232703b4923123a6fb72bd707ebf5f5959d9d6a31c2ae793d8c85cc10ae62732c4b1952121e72d2168009eaff30fed45aaad26b08063b134f80e66ed201e43711c473147e672b970867316572387a1f8aaf67abe78144b31fcfa40618e36d339f1ffea59a8c37126ce4293e6926ae4fccbabeeec689bcd421bc5e11447586e4a0444916cd566b309c00aa145f69f05b05a5d27345fd398f4cf4cd274d09a04983d012349bdcb41d45177a73d9804ab0dfdc5ee8eef7d770f6ca295bbd7fbc4e7d77176dc0689321d655eb13b133e4cf1536caf677df1cececed71fa248e40fdf3ba60e8d4c2293de2429e948d2a15da7e231b5d6fb5eded9750dea218ac36e8aebcd35c6a17a4593a4edb7f876fb6882cb47930984f46102c19eddb30b36b247f7e02574d2d5224b9521428de5e09cff6a0a5e1c66d05a36b9ab419722a4384c7b80bd9a16615d28010d5c9cdaa49408db35b9dc45b2e7451a49dbfd7b719788cc2fa569e0e2ccfe5702dbb5d63a42a414497f7d4d91408c71d520ae5ae39c6faeb786c5f96753e72c8ebe4513862078d21b7ed6e0e7d7609814fe7d129799f623fc1bd2fb03fc0bd6130a2c52eded41a414c95f4729a5f412e1a0a204d25a6badb5d55aeba6065ef8787682da6ffe70c614feec358d49021bff2c0c750f09c507b1e768dad8807e31088295da2bf177e845a2ede87026fe38007dbab595e2f004bceef0ded7d1d48e653d75ca4a89308f03f6fd4aadf962ae14e74fb75f9d6cbe43cdaccbe064cbc873010d82bc9913137e424ddbb42bcb92508929124e1627f29019adb384e52cbe14eeb5913d1258e185312fb235587ea83b1b56a0e03592348a500512f1898adc192cf8051a540a479cd80265cd0d3c2cd80a0a60210a48f8e051082d2499a443c91ec1c81195948762ecee4e339d9594e66c34a773fbde6fba48369bfb98a875ddd6a494524a29a5f788cf60edb51685eb5bdcb1ae634db721bce2d3f688521c3593d9ac65445a79196a8bb4f2f08ebe4856ac23add6eab8f9da1e65a65cea7876c67f4b4a894253975fe307bfd2981cb0f383e52573116beb1c155a76e79694e274f72d937dbfefa5cecbda014f53db151b7a6c57a6b68ea462b355b2870b8a8343667e1b7c0787ccc4c96ecc38a9555317f2e1a7c4cd152752586881c916131f74724f199b1519aa4a3083a4876a5511003e7d12a4f6a87e7df1eb387e88ddaffb0d12dfe27239524b94c1a7b41e19346fe1f73c2d07f2bebd22f8b9043398730ef5836106c91d9aba0fb6363b0041d3a4a359bdea2c65518cc17284b0b324615f16c71e790af01c5fc7a48ec91f7ca7fdc8b903fcb347de75b801f292b3cac46b7dc7f773a501b8ef0f24212f765da3b056536f826676c346383a24e01b046d4a27d4e124087936edc2c245cfa902622baed0c421aeb275ff0388adb06409a1762950c0092c573a7029813e31439b29aa185963860c1796a9ec64820ba441b37b03850b58549ef8dc35758d4557ec6409079eb0303eb1a253cb4364d9c167ebe85696287acc42839816c8ce7c793d21028c9b293849949a14325e8082660316d8658953c593dae1a3414b4fc9146e92c054f808d7eb4612a8fd0943e6333d480cf1b345ea1f7e8e70f9c021c31d7c7620f3056020b50c3d63a476c176850cad7420b3149f23c80ca5091d98fc04a143093f455cf9c1660259db80cf144cac5883840ae48c082f5203612b81d49b7661208c0a18d0da3a92b45d8081ac1ba0b02225072e9cc575efa63d600fb428b01835ccb44490aa6684458d98a4a35a5b588ec0f89793501ec05051c2c4851fd48c995af262d32e2f46b6ce06a240ada0854917ae365f6786c052f00a6406c1ac0369ed12cea4891b083201af94117f8c99d3030a44b080429a283f8c9b765df98203940c70c4b0c440040e5848a43013dc8fcb152454655d4c58a01b6031d3c2182e503ca115b68290a6255488a2872e48a6d84185202e3c9c89d3c4cb06c6a06c204748990121baf0e0e030464e0a3e788989c12a062860be3c118203192d37539800c1dbe24408d580e9071ebe4421440a5a555169a4ae5ca04a06d4a8900605214d3e98d27a82c49c2e5c8c30d9e08b8b9732148e4831072fd9c0361142bdecdc7f595987bcd3e1138f5a218f58e2642b65320cdb96f3016eda85e58dd712921f7c5068d0a109b1130a00c40b4d607d95a0048d92110b064e88b2e5891448921091c0440a6fc2b82913c5ab4b8098b2c40a2cecae20d960006b7e58a2ca10634ec298b00154095e736430c28a1792194aa06ba8c6346005e689ac2d7c4488f5cdb8428418af0b160e5b14e01185aa4b0c2484a9a1c949c2066406c14cc56019918118b4d06cf9b09108bcd0421a2f178eb8410ede3995095ef11151438f4bd2a184cbd5f0e174002f303523d8a3dd904a9236db3f0e97d3381546903a2af68135832c4e6b5b8f9fd163067506c10ceaac73ce2feaa73b67adb3d6fa73a98bc06cd7afe1d77157fb569b21802135ba3b9711b01bd4d1740fc0e06a33a4b8bbedad5727dc559b20dc0d9a5a27816dabb5b5d4329be52ae0bdd87934277d0e69d17a8cfa923bd2d5b10a7ce492743411800332be36fb7ec63ea6b3bcfab347aa13d5e862605737b428485d15ab62158e3db2e650d413a255ee0be34f00074d4208f0c1101e9cbaab4b1cdf9b0b427160d026c6cb8d4ddf66d35a0645ca61dd7dad75d40bd40edb7ed37d5a54cb6d80dfd656abdc13a991b8933dbb67cfd8ee0ce2073d02bfdd005d6ddbb7b6f678b077bb1b01bb9d5e1d1d8f03767d6cdaaa7550ae12e9500516e7fb1261840370e041dd2da952f5488daa542fe4ca899ed1e0d551ae6a6a01ff3e107b644d1d49385c528d217b64ffce11efd321d18dacb5d65a6badb5d65a4bcd21fba4be368874e8bae82275f42bbfbf90db94cd5b13fe9b93686bc6e047911c7c35511ceeae5fc96a7308d4495e22cf654e095b1555a410274ad3f789fae16b405f9b44664f3f34896c71224293949292c2873acd5b90faf49d9ed20fcd21b3a7e10fa15f4da8f371ff377f11eabee1770a7fdffc896cb94fa47ef844b664d3493f7d22e1d79f9537376fd80c523f7ca7faa1a99de8eb27a2cda67bab22ebd79dedee70a544f6b1f2d30dde57b26007eef77fbbcecd7a6571f6b159af58f916eb489d07081351bfbbf9d3b3be2ec9f2268565af59b19efe57b845bcb6fb3a9edb8329b8affbf173a9c586e1f3f4396a093ff3f4795f97474c483aafc9d367680ab944fe559c65504ffb62e5f97f40718cef21b53a5a6eee12f987d71c02752af9b844fe3328e40722d8239d6d84fa36374d7ade42c821f4c0dfa44107c323f7addff28f70def296cd267aead2dec7506ec12c221dba5dfae62d88f8e06b30fb6c12c9bfc529ffec89802f7e2e6f4d2098737eb1d24060aabb565b6dbd6f694dfa0f509b8e5c4bebd5790d96b7fb376cde82801fbe06e0873a7c22e19fd8cb3083f4ccfa1279023415b3d669a096cbc91e8d14679f12d957a244d607107b8435c5d9a7384bb5b6ee8371fbf6a743767577e225b038302c69933dd2e59cec91dd574ba94bcbd8be5d575194465dab6d75a5b575f4b7ef67ec395dbdb2af135ba763df7a86a5ad5ac0ff8b049a2468bfea8fba44508be4f7dd29aaf238c84f7e511c11ecfc4d1487bd6f2515b523c7fe3645d6fee7eddf9a6a258c0f759650fb1ff3069a3e3ee6902d2078952c06416c5d7491422e52edf3375da4f133a565504f219788fa6e5324912df889d47e7c223cfff3f5c120b51fdfa966fa8f8f4d271ef316c4e7fd6fa0e9e463d2eb37a7dadfaf99f77f5e0a49f3eca7bc81e08ce2086bd2ceb99d2b12ae12e5dacfd353349dc61fe2f3a3799b3dcfd3132c2d12bbc312dc96d6e3d654a3893fc47f7cd174aabd8f39e47eedc3526f5d37a8c4f3739bcdcc261f1580200882b357b2b3127cb1d206a0f3a379d303a0fd4d9bb720e3d7de69d43f84f43187ec7ceda12aad07084c75ebcf809e3e0f9675535a93cfcfcabc673ea09ef9d74c22f779c05d7b27ff9a799b7d6d66de82fc7ced35f8f99a49648b53ed7d9e888aff790d7c88d4dee7e9592b6f2180c05437f819d073c587d063f64d2bfed6a442c5df340857944df9c16ab5f3874d477edee7956cadbc81dff3f45451de40223d5ffb1526912d4e3d5f7b222b5ec5d3d3e7a767454dc5cf6bd3e93ecf06b3909c9f2ce925a29539e724b0b8fc3c1620b3fff84efea379039f9ef43addbf81e67993229f6c8aad2375f42b083d79b0b2e551a2c1db80210d7e387b319cbd21413b2b7d87da6cc0d0ce6607fed80c2f8b17650dc8ed0eee6b7d7ff6b682b952cfe0909ace021081ba43933620b741b303d79bd296dc34ad14d13e1364ce660344ad8dcd0efc7660a170ad4adcebadf556f7bfeeeeee7278a8b8deec37e79bfdd6bff5fd6fbdb5de5a71ada6df25409f3bad200e04877217c9276ab7db19395369979736afc3e765031bbc47bf6dbfe7846df78f12bfce2f7ebf19e399caa894cb5dc949c7d0d400000080005317000018100a86c3812409b21c8b85f00314000d508a445e70321bc82281388a82280a822008e2288861188020a214330c418526006184cb40e0a53b897df4e9d225dd4453ac3801de5bfb50ab840779bf9e26a4646dbb7c61c364d069abb79faf62ba41140e744fe338fb1578df2fa57f70f1a411ff7d58d5c8ef1cc7117a34909556f4bd59d4dab92d251720a335fe6d27bc731fe778241597815cee7fcd9f631626dda82e79536f53851f22bde3138223b9b24c24b63ef3b839f977fdaf3907ca61398877263389999f59fa9e7148bb121fea52f32c094b70bd743f22f4d638714475dceb10d70b827e1717a52083032452a60786fec2d4f0e3c08bfdc72d2b2b8afefde57c99a328d28a70e001b0f33a250c66b24ce676f91e63be67991260f878bb676e0541001cdb1d6f50232a981d3f2ba663df96f258f1c21cde2f278cddbc894ab2602c84696a0005b0037c09660574786599871ac8376ece7021da9e947008e000678cb7c34b1e5810854eed5948a6e9c28ad9d7d3f92c5580f76e2ced4a611e4c47961b36304ea242644fbcdd2f82a167d87ac13d85e20dd01dbed4026353b42e2f79bb0f67d71231e3a945430957272f2d8550da102b4d0447a822193532e142b74989eddaa2b9aae9c85aa77e44d3ef441ce3a9861dbacce6d79c35b6e0994651457142d96cbb4d1edddc5f58e52deb4d1d4cc2a393c25415fe83737a93937a8b58683f64f66380ea2da0828b5d56cbac36bbdf78a14cc1a447a2dfa3417e261c2b40faf6957e89fd7a6261b33ea529096b431159b7c88ac4ddc85fa940b92fe98c005f219a39c54513a0d7daca0b476177a1b1635df1cb63446ad7833f6b04dc545d47b4930cd396d1e67ccbf213c7b13d43e59d29acc027628ebb1f0b355b9d782cf68e795c84682acc3524e184ebded4f48296c4af63be35f233fbc203afa119d635376a371a6e2189d6ffba5ca2b326afd375915604934a1a5ca7747607b631847a081ccd0ed32ea891e884aa4ca53159f996818f9fb62aa94a8320fd28dc2a8642fd5425858a8b1895fc059ca85e7ade4240905b6951ed8825710e820deb52a508365c00cc42437f3f96f3618758aa781962af092ec82afab8848c9295ced78f41e97b3f74670b68341816e9fe99f979443264b54a0fb5a65db93ebef629969e0e01d9411e905e4a168d617ea78d97ed822c097d8b209763e1a01ba19d918a2bff077ee1b38753ccc394fe4cb10cc337a04ebb82e09dc0ee827b0afd0fddcc06017332838c73214f06873165e33ed9f45c36d2ea9a43693c24b03ff7b2426a5328a0e8e8be574075890c4d49a4990bbdbede1edc3973d8d741bd2e4e613068e7bea2468e409daa54357cf8af2ef9ca2407f143a8456f011d2ca0cab3d30248b2bb28e1a03ca62635006530080cd52b85b68f1ec7c2c4d9331feaa74283291f533ad0cbb5e676858eb1562d32891dcea350ec1131dc2f14af54909ff3ef6cb198a75186968ea87fa9f38907defdc65acf692dbc6975489d472618da166901e556a6b59cc4aa68d6d96d6dc0125f5ee628419f150daf2331162fdd44ad612eaac0ec9a6d998c6ebe0cb3d7f3c4d5b1acb6389b12ab93709c5510f66fe48da23770badd1ecd53ff278781163e84530b0ee59cc1b9196c366d9e04d0ee59685724b6f8480fc312a8154b8f466e03d7d2929acba3fbd4e27ba5a3465a8070cf5eb9ff47b5219bc209ce9605dc1e4d0533857d4012561300fc6a58bbdd24a8eb0e894876119e84d75d9cef674d4a4db38ab7de430d17bc12c5f473a275d8963a5c2af38bbdca9372a0af902c932e46a0d1a3c1d59db4f527dcf75ae27cfc2af1b86e5162db39741ae40ac0c5c8c3608ea13222b7ab32b1ef1e97885289891cb60b564b144a1f5c4ef352dd1db60d9bb6603a893d87a46b375b3c1ab6a8736f32f61a36fddb2988e5abcf406f17b4cf8f167fc26616080c28c8411e0d872a486c310fd751b4799173612aa0f4d8e1b19b4743de0a79d4cf882ba5a4ed4b8c6ce56e591bcacdd2ec1d24487be88024f50f99a70f7451e511af8f6ce15634b879ce1ea6b6d3466a8e72ce30fd33e363c1a93adde8a10fa7fedf735107b79079c4cd4f0ded5d986ca56d5c5f568b0144360e69346e3f4619fbbe1fbeea0f27676126ba496847e9171eb736e5b82f3d88298bbbccc340c743a1f8d8c1b73f0e27aa8a541cf2847ff0299ca3b0f1ba1a26149accb06737ba4dc91e2140473f89f2bd094508e68c1ca70880980da56af8843777c2356734b480b0db3c204d427cbadee4c5ff1b1d9b759b9591e4ba453fe61568e8f8dbcba41892b6964575c67fcc027ea67d41a8c5bb91a111169bd7860931ce267458499b4ab25b37446105a9c63b0d589595246a157869a3bebf978fd01a6d04def83a672c486fc282e738e15d62c93e4c927a97ec99c017d77ed13a805fff26ef78fdef3039111a441d8c402c3f3279f349928774ebd0379421a1962d719e3cef29665d9d2c16fa0d71f8a3e76cdd53447699b57e36f091b03d11e8b90f128e1eef56e5e35f4232e49e55eaeacdb53c2754404d1a8f131055a9704f5c65e044432217125524cd5c633d02ced3e681345e6d318983601da14644298e4e6e1a8722c0413721be168146398018cd416288e31c4f66d1d62160770c4022b5790a653d59f876a753d3a7ea22364af36e5a0421adfee9cb8d73093b37cf5ae91bd4ff0277022973ce7025f7215b155f60d31ed8658f6f819f7012e738089c7c6208394b7208b5539f64f279df52f5dd5a1a802a5010b833b5cae0943c90c4422d46c4f4342ec99f2b7781b9b5a6194ddb934c50c268f588a72543fd2115c21373283122630e4bc89bdc6023e79275596b93180a83d0e4e108ca7cfca04caee86e56c6c74829ffe1b48556ab8be90794b50948267442a5c17430fb6c6da95a9078fe285b8853df15174c84fd9c5197404a4b01d995436a42149c77038c63dbd4d6a21e3687a110844d789331170c1a98eed7d5173741235c6d05d00b8491aabb2a18a06866b73ce88fb8436f778c1cf875e7e66044393b2a451b453c0dac109b3c0f2007748b5adfc113d36c7694dbe5ddd56f85be69dda8a8544e7aa1b82f70380c5c4dfdcab9239ed090039332f44cb41295eac6ccb1c93d4de6d5f0daf9c38eaee1f777ee1a2eb87cca70b7a59f72a075b8813c020727990e8dfe7f661f4eb21fc7b2a51254342409ec2214d85094b34aa1972aa2509ae8d2da762d26c81ebe4327e826207589f21d581357a6427cf97e0812c25a40516bf32e2eebe2fd0306ade14315c64fd1688da28105f129379767a15a6eae682641eedbae5aae2ba8b908baf48716d5659f5e42513ec1fbd424b2229dbc7ec6db7cd5e33fb243def597fa85e4a5d4750aee36b6a89c5b15f12909f9a0fbda4af7c7a45db289c54b29e2b18014c7f8783f0417c01d6602fcecc58dad5443f2e278b3ba4d802d567365714fbe5fb9c732bcfb6584bf7bf1bc4914704b3a294dce0730bef9350dceff6bb79bd63ef2cceb87549788464039d1bbfa8bf9261c4338f2e14eddeb6470c908aa595286aabbb824f9c65ecb37942e525460b1b0e6902de2ec1e41e341e8d4c9965f331f180bf0a0b577dd3a232dd04dc84c6a99bdd71c42174692364ecb7a1b614da7f57ecf3893d70ee15210720cd437b6940b821c970f2ed996be5a5417258ba28f5be0f00e4a1e7d6c0891e5a98396da72a7fe9eda3456a0f02301680f6419faae1ceba417a203a1869d4422fe38164500a793b23d9aed082454e783f9748948c60d9426e06632d5f8205c90511837502831e0eda3e8cc692fa1860ca111b66180255883b444d05ef0c72b1a2c6808c3a29f905257978c3dc61551417ea8b6a0f5b32fe61fdfe0419a55f8da7cd33642367bfe72581222295f599e007eb8203e61478451bdd7e5acc23657ec6fd8f1498101bc4796c05609020520fdaf77dfeca2496cc023338aaa9f297461fda777296a9b0c923b3d6b27802e8f249af456e025795d8ce174c202ea93d89033315c371289cf318001bf9933e8324d5e47b43050643a00536798d417700e587f16a0789ba92398210c589e22de0d43987d352104dc4ff68cafc1f51d53b84d57565ef491101a2ba25b66b46d9a72da74618eb907e63585c8ae39f0e813d73de7b6a87ec3bb1e38521bbb978775a347361a8f2694599fa59df996002187e80eeebff781e56c46558ddcb1f5bcec177c98c7b6bd9f75e7d0e19bd1cd6e02c4fdcb6f8c5892b54642292ac1899d5ce24cd54ad1508e5479c3b1dde8b2980782865737c1ab884ce1cf906546babb26ba356bde744113cf342b6250fa0199e6859c2576a0291ca91cb0a463521bd015036d9f95b2b8cc62d79fd2749a2de9ba73eea04e6fe108abf0dc0a6b8522bc922abb8ec287002d2199d0312798290d91afe1352e64b6c0fd728721f9de73106cb408ebc1c5d80015b6307e1eac62a91111f7debc5b6a69395e68d14346882504d516c8cff89d77178d4274a0248a2b46c8d1e1c150266e2ee982bd5b6c1e9887352f224358c00cc6766552020f51b8097a8ec624fc874ad53b85f799aa9ea8fe407550aed4cbcb35c1f8153794188d5382e5b7a306b78e823c51dfa21ba1f751400c96b4806edc14ce40462394e0757b9e3e1f1da58959bd407bc361ffba9d245dfc346c2db78d27fc13daa4325653a222c0d23033605a1a26e2c5a56190315e9b9710bd34bcc9ddddd99fb84d8522b93bcb556146858032c8d54f7d2602fa99b203f1f035ef49b73cb3091fd1a8d3214f6d5a8636e403f1b1604685da16647b76495f2587089cbbc0234627270e742252e9240503b8a273b46a6aa36b1dafa9c621211f42d52df17c87c53f85e6120b7a8fc1dfc32ff437bb81dbbe605824494d5c8046e0d0d91161d507d36bcdfd1fa98f3c37cdd75fbb8e09bb80613bcf36c34e4ca335e60f616ae4d4cf9ad075d8a06e5b6808b835c48dede9b0e1c3d8300ba402572bb2a5aeafbe6077a9db9cee7e7822755aef754efbfc3456e226ee51dcca925856b77882f56c46e7cd0ab1dff44fee22bb22b786b368a2dc55b2b05445845b712196d242dc5afb8f9b8d02d29963e03d5da7587b696a2977b1e6487bc5bf4cfb4300d9a73755c9729bd998d6a17d13c59e88d98277713708921e93fb7b6a372a2aa56bcb1afd0c061161bd3325ee4e81e59c502d7fb3d580f4437d6f782f8a7380516cf7586ade0dcd5fce3cfc7be35b84b5d214524d8cd8fb98b48c4f1d7c06fa31c8081b83e1136276dbee336e48fc7bfb5972e53412620426a7792aa1273cb9f921549d09b3868738ed316ca7e60329aaadbd1460e3fb9d0b569ae2d25b3a3f47cf6880c22b824b0197d49985e34dc909d04ce367b1e6a5a9d85e84bd372ccd37563909e8f0248da7ee6b773b05db41ebc3ffcc928ea25ec1a96991f1245bf81c16e9301c4c925e3e387198680bb08a182c80c4509e6452af2ac28c5be16bc7b8474ca0e5f87dca87aed0f420e1a517a0b8fbfa3033388463315b5e9955b1628790f53848504d2b23fefea87efbb8cdfe8c45c3bf3c711ee2d7d31fb1456e686956fca3b14abec01e80217dee5fd31706de6fc89f11f348f00c6d89f1ee092ead9c709f0662a647c65b49a707aaee1e3c8b20dce2b4af81ba25905aabfe9c11227c2e5bdd0116b8a655ad4a18c28a736e5bed643e474e8aae6475517191d4e9e1320ba036c394a0ae96e12bddf3da286c93f513278da0848c8b85fa0772d9d9f1512a248c306bfc74ca61a7d5a6855c2d06c31946a823493a366287fc223b953c843f533b53de2020850bdc963b84c9dd5d4e550e77ebd7431d8cea701cff03481fe39fc2f56f071b528865588a01dfe10561bedf78c4363522a7685dab75b12f847be9d8076b5a8c3c7ca7dd666c8b01220f7b1cae8d5604e1d446fd00135da4c6c5c888a39b351d78975eda93f00084be8973c3f00ac61bf7eaf690ce4c509dc919bf7d1b9a20332e112d336b4111f35bc123ed1d750dac4e2308d99055000015dee839168f9a379b005a697358700f5457b8ab40db26e41edef8daa99406c9541ddf2bb2ba1da56a8cd1ad8df1b38fd236ea36c1505e13bef15806cc0f3d8a4686c8efa5cce6df827a32a6c4d018bda4ffd64434327b401612d12ac531a744966650a001137a3f42c9b47e652a9c4ac706ab2c0bd256fe2ed5f8c72dcdeb860c84b867408f398bb3481442efc8ef95bd01ea8f0efeaa2ae5ed8f225147f5706afa2aa27190b312d84edbfc0837c9c13932ba026773f5975d1db7d6f1a107bdcc57e9f95b7c139ba2fa4e6f7075dda55b29875cc8259e30a0691af8c32ca8d22e38122f8a8d0445f5ce401c4199c39a499faf1922580981b8f436df0115757126177c0ae8d37572ef674c1bb7b51fbfa6ea0e1dae7d1b074b6a9bbc55ae73684b121603c75f4c88ff13e03ff01596f4471dbcf6d3f3cf9d8084e3f78a358b0a6dbf88896caaf1975a9b5f54f4fb54d5a4f2902c870da1eff396cfe2ce587dc8f0aedd9e2dcb3c6cba16fc90a7e68aea405c2efba2749dc4e56af77718f3f1b1a4a82f38a286cb746a4c5fc96ff7017b2700042ed747556190a9cf6c017e2e220cd6ea9508ec4c41eacd0a22b84ccb044431325a415b35514268dc562f93aae9ec39485ea635c8b0a70ab2b4009457edce6579b260c50f44e913e7d0b782834897490bf05f86d568742629ccc45d9a4a94a1e137184d5fdebef86965b41ee07cbc0844706b91a73cf414949fc17ca729c0751b122ed434ffb993577cbdc8cc8da1e93b9c2cb8cc513c744132507603ebd8331e041d9dd59b7295cd04925fcd3559a8329eb3493ad247e353e529b8f4749bfa37d10642025e0c5457b7d6ae1c08123d18d4a9bbfc9cecf35e9e29e6d204549a85583653031f4c3383c03c8c65c0451b0852aff0c906c9b9f4bd97066f6c5a1582f9cea5e285432d00c67f50b6a8459b50df5a163bef325f743b5a7256c08be823e937fa9f6fa1fee9d59630fffa0a4cc06fae0b9d85efc97aa6a40f2507496ae3e143672ef8dd45bedc2d5191bace89490c49e5f3b50a9ea3cdf9488191c8e8cd5e18588ad321a9a28c49e5bfed857a64816cc90df2cad4f2219d81a429ab97f447fe4daec3cba9e34acea679e47ca6caf6ab2b89068391fd0b02f9d4fff9a678e7c572474d3e69c97be21ab227e6aae4e3e40b1586d61b897300a706957176aa4a916011a50da25c49becd3dc3c74b03848ec03986b56a901311f232720feaa9999ed2c1eff4d33d1711b0560124d555a43ca5fddf611832ee8cd4aa2658c7a6af770ae33b312396744c774d81ca775a4bcda265c002f467413151e71e45a4cda7b6f00ff1ea490f3e1eba2ac99c1a4c0035a2547cbe8cc940c14db5ebc1a24b7b3226dcea8aacd65223191b9ad86dc0f065b4315166eae0d2b11ee31db212b446b561dd6de23622f528363c1c5f271dfd6f3676344585b352a858ff7bf51ebfad477dc0f312f9c550491bbc18f754543769b2a97e7b316c0549def435d48d616e37e87ecef10019baf6b647e0788ac395001768f5573e181094ebccca77e7bd61ce0bd61adbaa19cdf190c9432ba46d9bd7ce33872ecffb4ec95e0c94f74dc454bf53c26813bc1f947f8a6b26806b77a647c54c6050049c669bfc1094c306c7a017fffde0034c6cddfc336f60f0c47f7fe0a8293e6375ff7963197827642027b086747611017196e9d7cb0053fad747c9fe0aeff16b2be45f285b74dc2415aea07c020bb908d49e09cd6456759c5a7e102a4fe295f44798c5a6777e35e285bed10f02ab31c08f4fa846ea4d0f40ee495a221ff4ab368632909b1237f478c4d62264dfe26e169c9230a4c92f86d3071bce8c69caafb89c59aa7917a10265023bcb555a7b97afd1cc6a0aec67f0f02a7e2b681693f2c40c57d1298184dfc072cef18c42cb7da971e8ff75e7c41f98f2690f28c982b7cb9b5828b277d951300246ef4ae86341f23955852185144a828622e5d969ac0e549d0866fb7caac08407aa30bc278c5abf07ddbdf63970b852a303255f9fe06a32fac0be55b5f63bc7a382936f2cb7c03a47e64b7b84d23554053017e23f1aa61ab739e06bf560eee1e0dfe292d4e4aac0d77c189023e322927e58bfd287e68e25bf4c2f4bee352a7e4c44efdf5b5d5090b4839187ff6c19114001278af49f6ee41c6ce9b4051495aa9408c70993b06cdf3a858dc050133196b4cc377be186a8586c9990b0ecc357a31d33dbb8a8311ab23dc1ff2d30262c2d578d61cd5220f568d61e92ea1bb0f4969458d447b350b24b5ad2bc7e7812133b92e6750d1bd4d6ddb8392e9e7855028a65da67462dffa8de818ef02f35e37828a1c6aaf80334c07645eb21ad2a29b126ab4404be2d2d8dd4ce779df064c17f8607a020c77327777dec2c3a9ee194774dfa615e795fe03d544a54398fe4df05b344986b4c7e05ed4e7e2c27dec870da5528f04396e340b874220493ad72a539bc1808b1c571edb4720f4fb4ae6d36046b59ac3dd997e26060fc61f5a61c1da51765d5bdd62b45be67affc9c98b79fad440195bea4828862b6eb9c897092ea1d5b773d0ef761ed9ebe386134d6e4103933a74cb801c34d7b965269ac3866505686bcc15e4b3e7da12b2916b4df7fda97ba70cd744de7a8461561bc82e631a348b2f26eecaa93a488ccf6304a04fb6fc4e9b2a6cfe2c84a1dc726c6186c1bd151db4cc517f7d7970513235496430cec9fe9d9741d3e8a3e6a290c7599ee975c7244ace6ebef9c27130de9c6205db6360bbaf5a39812f97f2f1fb29a032b09c35f3de3fd9b3d0ce4e400d0dcbbfdb9594136d6e8cc084a75c62e7895789255bad60dd21b4d958fd033be407c1f0ec2041c7c96ebb88f8f7f54cbe46033fdc05e0d6d844c833ab6f1c66d31321aba8ee98d32268f190646794a24a605895d682580b5a10cde15f7b4cf39c136c88a55ecc5d534900e8243d37dc8056122d4744960056d693faff0c584deb92e74399344f693a5535543b95af37d754165e2e6505d051ca6f1cbfe349c4565caccd225577c3173895757af81149b7a3df77568ab8b479ee3f60651b0e46276c66b8dac83abdf40b8b3005b9dac40d7fabe6102714723dd6f6107e5f1123c6d0468d271529ffa70ae2bf89dd78308dff6edcf9c651fb7e442ce59d55ce9f1bddd1e439e1d157cb6d358dcc1ddff698da98df52469d1ef6e8fb466633bbbe2cd269f4b72296199e16ca1d926ca508e64b6a0b8b8d37ddc4d0f7b0f0b62213453d305a036f242fd5ac34c2ff8b58d8f775580020d8b41897a829325f82b5d21dd17a775d55ac9078fd6474ebccf393d385f6e0dd7d54111b5b16ab8f1dfbc8f78521615ba4d00cefba52327621741b84331fa743d13c4205c4fcce65d6131155daa6548cc5d115b07e8b814553b940b2161d3c4b8173a78a8c882331f1668119baa4d003d1d1c9b873a5441079342287e00864b7c18cd05601c92f681c1af541fbdad4aa8b5f9c04675282dca91a15a10158a27acce698596e0cb02cf10ab6cb82f27858d0f3e7b5425e4d5504e0a830bb4e23f9824e3420eba73452f88c7b2422fe1c4f323f0ddb1a261dbb750fd887fe22cf83a3792b3006983d1335ecd5d748e5a50d76ef0d36b208233076341ed9bc97ca1e43d3113d62399f003ffb6372aea1d926f00fb25543b32ec34c8d9c592aff56c7d42e3f390a2e82d8104b54ae378c3b1555000d24c5fa3dccc4079f246160595902a9bbdae92f8f0208745d74d7d83d53490a6858d0ba4b80383809bc4a6b0f69ace2342ea3344a423833e95f58fe12fae14793c13557884d382d7e80e98d767e6328849656ae2d7f77887c087edd5565359b0031ddcdde97c7e61b32ddc83e2dffb073e7c081cdbd7a3b244767afb372d02040e69783f19be250a1617d1732bfa6403ca6cd44223fcb556edc555d701138372b3bf971218ec6899d0e96f9161ec13725e87ca805c2f4587991aaf0820614740587dcde5a14e6042cfca03b9bc766c4592b950485a1e121461ed07e71f3e0067fbe467ed86849294e85489fed0d177fb3736a153fa2813d384274b224608317e9f1a15ded557c333b63f1fcabf3cca6fdaebc5780d8e273b424a96decdb38ebe20b7281d5ee5860fb3e40f7c60e875a170edb46464e424b9044de6a3750dc8e08abe370a1a01f158487eac0327e832455b85b60269f6f29293406a12b7812cd94b7891a5d3e8401053413d5a5d4f00f3d290bdb275c5055f668a814a1b28cb67ef0aa2ec9466aeba311ab3f9ad8e55778e661f785c19d789d17d0086544243f4f0e190c49a10f4ba532f8321cece273e2baf62c001a5a4867356087c96ba2732e21773047e1d65f513bf05ac316f33cc0a91562f87a20daba44102bbb5c8aeb342037f498b4021f1ffd72d6cf8bebee3f45fc7f57af8b442541ad4bb2218d61055c3d6fa7d1d4ad1b72027fe09ad31fe3661fad561faa9c3f4397598b64fbd1a1769bd9b8cc6ff65e34edc3b32e36b55986a638746e8e8bab987d1aac50b067a00e5691cf31f80a369622d44db676bd9a7f9b0d7d7c35844cbc1a1130f3f5bf83e0f2f92a31b51837c7381c6a1008bd61fe469f47e7600d260117acbd33cab6f6be5c460462cc72d1d92945619e333aadfcaff6a6632cc0694519a74395eb572934c4654e17b8ab91fd275db5c32a86e264651424fea36c41bbc936255017d4298f0918660574eb61048c48ec22e8cf89a8aa2d2acc78306724eb52110f40cf65039a879a196db11d308e006df3acb4a748d1425385681c10d55c122928ec31dfccbae4ba45081758ecae6a61923b22aeb767c86d8fa40df416d536e34a8302850b75edfcbf337da46c0512f685b0dd5d278a07e1308e8e2b34881cea0053dd5363513f140faff9081220e4d10c94fd3b5a0e2fe2e417797b2aaf649b52f14450059a0917fe3e574ec8ece2c78c5bab249658398e65b93be92aba7e76a4ba6ea1ea3c0bb2af1884273a529b7c810f01fd1a71b44685f0d6b8dd50a5f591b330a5105cf676794fc0ed07be36f42110a5aa8ca0b5377705a697c28d885daac6fd9618e0c002869f4633a4a0e520081cd885542bb130a39bec901c1ad9e15267ab44e957c54cde975ea92222ca97e60d6419f58311dcb36160702158a7b6eb20828db7b4b2427ab06dd7be946c41b9d066abbb9939bab601d148ac1eb65471304248843d9b2a349162b79613910f971a6546d7c783a98441f73618387e1f90eb1fa48f766f18f05b823d8f03572a876a4dba21c5b90f41cd7266ea0dc48aac9eb18100da5c8a711532eb596e2c1fd48728965fd745c0497991c182f316aecbd4dadd4ad95705878000995ca0ad74a35e73eb5e228074cff57200a402ff4b37c3514d0db09833776cf11b86bf18595fe02a6ff617ccbb8ee9aa0bd655e351c45a3f4309aa55c5373f42be7c65814140ea4161d14bc381595fe4be2a054ac711f3555595fec3a4595fa737711c6a9517ea9ff5a01c2309f40218d7a402dbbb6729dbbe7dba7cd215b038f62675559a85f9f49fd5be8f5072523d96da1eeab653a1fa2e214757f6f067d33d7da43ee0fc78e44965e601575f4a857835e8796ea2d9ec5261baeb33ac3aef46255ccc913d1d7fc37554955598a9602cb29f3fc8ae2381d88011cb23485beab716cf64aac6af1e44feeff393d63bf15385123693b0297cdc312cb2e767e576c49bf142ecfff5e85646b2cacffcb27a68b7769da89088412756c903418c34aedd87865fd5ec89814a69b66fdf957e6eea31f0b509f2be1a9d263c7b06c4123ac311698bf8652e005a225b852631aada0a13ce7820b05f365da7b1adbef54f0ca8e44c2b5071ffdf59beeabaffacbf744e5eb907c4e5d1326ff6c41356cb0ef1f60d2e4fc087dd9f1752641f4ac37a1d219490a4da22e590ea1399422700f5232f23c569ad95dd7abf6257993156f51b5444ac7589cc5c50fc37a772bebd57223748789b5342e0608533b7dad000a98a7a7edceb103a304fddc25343a77121eafb979bcc9ebbdde8aa9652a7daf8a294719da4e931ac80e656ccdcc2c85a45c2d1b57a03274a2264776b39baa53a4ba8570c3a041cdbee096b00f8ced72fa01178abc8e12300bb01df0b0a580011e39a59d4d5c42ccc84f225462e07eda44b8835c036cad67136237019c90596e09591c10e72c0b4e7ec33088f6c2733f2a052610e78a44a3450b0bf3373b76e0a0e2417457fb6fdf7183683181e6b254df72ec5ee56507367677f11835d70f962e4cbf00b70a1f2fa8c2c44c6204b4a05e505df830f9154a2bab52e2dd24ae4f1da0a3706065b71846fa2521482e7eecd2251b7d6034a23490c87bfdd41776a1b64de5887791b7cbd6d019f207e01426743a8032afcb3979631cd75e067e3713563e758fe489dc351758a3b35505cdb1140a9cf4a4500dd9281b9306bca655283d9a312454cc8bae14bad37929f74fdbf9cac6f325ac3f03cd6e2ec2839ab0ae0d156708171f3261003716be2306555cc0dbeec18629094156c649771ba2903f4c45006ddbbcab03b8607141bde249da0d876c71ce4678e4b6565fb9b40327913b8ab250f83c91342439dea1ef686576f4b35dd29a8b58e32b3766fbb6a8b16443e41c9dafd40edd68ca90c57186973af91ca60c60c2e7403bc75604160938dbc163c423712ebd382eeef259a6b8bf894d15395f6ca6217b5380e048b718d34b97a665cc5e3c6c80344ff0235b5884ae001bf18d3049417999e6a0d9978d28757755a472f28a492f116f53a7042f04f8e2adb03f414f264ea3d8f4f2c7bc534ca17c1a2bae14199817da7b62d29c8edb21ad2c91cd0ea3213cfc01def94961dfd714f2522ed2e31e85fa6b039d4074a92ab6c914d771b8bb4119294a72c48a1aabdb850cc45f8435edb3b24dfce55b07dc98199b3b4be0a41ea1a82b826a35d9a81d604ea59c93b6d3a568369500803130ec2b20136658edc673374a2c61040c2b90a7d1efacc01324d1ab77e0019918caa119d1a81d49400e14d9e6a43c2b02de47a1ed6ea97d1217c5444037b61c446e6b1a9e5cb4c4c668591aec42e0edd7798dea647cb2644dc36be3c54e533ccb20d14088f83db94d422f41350129a7716d35bdfe0441bed9b9cc4e257bf2ceffa5a342b74d67a54f21aa147c3f5764028107a500f0970cb4eade5856f216554519368616a699fe16b490a38a95fcdfa61e929068076e7b8b25e27e35a51fce34f6a459bba9e0c0619679a1b3c0dbb8de06c5521f1ff74b93a6dacaaab46a0dfc5eb8c3dce8da92794fc3e4eb9a6f092f78d7b31dfd8b4732b04ef43a6a696ee69970cb428be80fc3c1cda40180303474c2a0fd3ab7b7a41860b64460588e4aaead352353475c15565b6587428551cfe76106b182737b4cc9e62167fcbb875db6aeb634275f6b47a3913ca3900df118e968a25464601012c845c66017e9794276530c12e69cc853cec3784871d3a4840731b2781267dd536ce49ab07dd76de4b4b275241ebbd8c634708ef9aee09d295175baecf994a2ac1076ffe6b38312637b234481d2610b5fbf3b25bdcf0bd1e5ac74300e8e25009029b9124c651f838cd85c75f91fc2b261c324e48d35291848b23b78aa3fcce469f86cc4bf9154394d776339e8c419259bc323af48964f471d0a7f43c91148f3d45ca2439cab72e8391f015470f50416a08dee6e668bda09bf8dea51d852abe2025ea702a161039e64c2898b418d0681f80663c98c2bc1cab6112760efb89a2f941dceb296e077a6e2c24143616e21b238599e2f5473e2e7acade31b31b5b076f3026bdb07563d4b19a188bd7d66ebd219259c7c7d58d3cabfdad6dfcaf26ee8507926b85faf826205a744d2e8c798a57c8caa91d886a8f5e838942c6edcc477f6696eb0fcc631974d4ad583437dbc6c3d6f4386745105a147c3a93c3f5334447874fa1cdbcffed2ae412217772273928a741c34e5b91b70168651ce44b343c143427b881391db3c216b3918d5cd9be5a8d2c96de93faa6a2f27cf3dc3e68ec1a13d8f9a922040034b5543000f5427efb39178e0b416014f1c1eeb2072b966b02d1dab68f2af612b7994af42926962a52aee8ca13378fb03e2289d1d3674dcecb9c4312aed292050ea27cfd7dac0a7a3163af8458b76d50b9d7c5e41ea3c3c30df7a71f4c51c6d65734cc31b4c643ddd65fbe1ffe73c92fa58ae40c9a5d78172d2b8e27c67580358cb9c3099f3fd12a2aa7fdba301bb055185e71b5e0396361f4520c5f0f1dc0af4c99e382041a7a4937f855a4ef9d6f05a714991aa4f6c879accb24264f88b8093461bb142d6b177bb44c05bdaa23d20412131f40b55a488c55d853a93b5c0e214f71756c3fa5061a2a5a444934f58d3f242ced954aac06a53fa622713473deadbb90be34962ace147ad07be7ebc13b6f34fce4a69ba1389dbb2a56d1b6c232fe1c9de67bc410bf11ef5e002fc3639feb4af8e83e7f592f3f11836c7d617101143409caaa27b899055119bf38629c2aa9cf269b2074a223fea81f71e9b68d9643ea9ecd20376e27a12bc68cba997ecc9b63dea090febee4c6bd5de480bfad4ddb49772e81291deb06cab1adaf9fc0581a195183b3f4c3083651ba39052d4512441e9940f52182d998d02fdb47e2386e7b044c626935c2246bfd556ba9aad03d9959d3f3a1227ffec2298aeabe1fc57a1a80dde2c455e0ba1f51b052b1e8559a73179a0ae8914bea4a1c8502b85d1fb64222f296fe84176a70495fefe4f486c0a9111f11c80730f4981ca3c08937209eb7aeaeb88baaaafef8d5aa318745a58a50cb5aa3891a54c883a07424558ea08ac52fa40bcdf9bd6c6fa2684d4d82cdbac57ae0d58639120a55f51c8e0698e4388d186ea269881034c996929d7e941b5bba74a3592bf5ebe83f07a453cb92d0ec9fdf3c5e280c8bc603b76a83489fb1479bda52f5489e2111108f9c769cb51e1ffbba11cc538315867f2bd794d8b102df5bac16369bd1811c7a5770e44f3ffbc25615f4e52d30544b3c1d8fe85c60ffe06db7f23cdd71bbe46f0972e3cf37aed3d6b7b69e89882f419fcd0b4eab888d288222b34911f242f6211553fbc300e8c8789fa803bb478dc47671c3ec14a3cf48200d7377ab1aed750d1c4b9b88e8a5e9c6b79fd7f5aafe705af58117204473870c1aafb9acd6ab43180bf5c8c3c48a82472b65a830613308e0a003e3b0b8d4c3225ed45b1805c9d80c95361c13f30ffbb8b6c757701d741749f4868a7aa4202f0b17b6773eb530c015f40ff949b40a507ecb65df38a59141a8cf107828c74aec4150b6b528c1642448f87230334b305309471e9c8006118ed1dc1a214ddc17bc5a4331e7b3fb54062b25c65744df4f6542b6036de1b847df0444a6726be6eaff80a0288e3c292cf24770c9d08a32054bb489314f69b1d3b5d15b03a7280e4123fe386e5bb2ff9703a7e03bacaa200b8f3155c13c0205d14b0c01f284132fe28938c44c99b359621b44551cebcf9b56cc52b1fb887c1218e91521fe9e0a55874bcbda374835d144122c9269ded026abac751ebf7ad6b5c06a0e07f29f88205759f39f5061814e3d981f04e5074852e82bf8c530c7782a80ce8ec71c27b89f98b3211633a09f9e40ff68fe53486c303e7904277ecd7931d05fc758ccf166fd5f74df513c99446c3cf3b4770fa459546ef0b40aff7d709a58e70a3e378095933390e8e8c0f3646544f842c323a5a9d713fe310c1710b80399f39a3dcdac315af39a04f08fd2dd086b023b8c6193771a208a134d0f55ad1736b0d1036dde4beb3c7232fcb39ca38dc56d573e126056632389e60e8af2918eadac1a4c76c0e23808067b003c7e859befe412e0b2258c0e91d28f6810ab78bef19dd9b5bac8374474d9c9b57f017ce4920340cc6018870b68252c7616465aacd645ff32ae0251210ff6279ba020db13db71e12f023682eff92c70d2bdc19a3dd7d77b2315ab63b1bc7db9b0875acef3e48fd99b8ec77189a8f776ace35ef51e942a46b5f37b56057820ab3582ca9b84325856759c881ae21210ce8e37f98f6c7705a252335e7ed7458baf3fc0d907c8802af7196c264f4a5ddba7a89fb0a5ca3481022b15dd00228b9a3c79323e4b594b0236c2d7b66ad52df8f44b72c73c18269e5c6cc26ee39a87996555c91ca6f780133cb63bb6b0ab9da482dc926d614c9f60f29d9716fdf219ddb3a2a22f11d61f6ec7e530bd97c33cba65481d2d66fb444842856a90b17dab8be64cb2fda84c3c42b8cba70c9db61f61a3f7c0d642ddfd1d626d906b352df1525e373d81f188263358806fa54323b455b8cc41d6c5eff43a896516b9e032c6c03225321be1c96609b4198aab16dbbd04a36fa2110b05381c79db7178ce53bf5c20cacea08c50349ba2599976ebc3900c8193e84b369d19bf1e4ec46748ddcb5e6ed0bb10705e55408200f8158afaefb189933db37f95ed666dae6ae25e67ff5d22a568c752cff82a7c3ac75e7c89e1af289a0c4d88c0080f394cf96356cd60bd299fa7d99fa2230b1eb98f578be13176b40a84e02a3905d3d67e93f9f669ce3bfa5a91e95ee1caee50f49a98a77afd1724cb74bec7f185f3ee120ce6e568331021ceabd50e8f0bcc2af1f7fe9bdfd2adc427d9d56a3e61ce905d959ec1611c0f2f70589d4f6855359804615dcdc0dbbbdd9177efa375806b1ce840299250f2dc58d9796ab847a1b7b027e959792c18c1ef835fba8fcfee3ffec603c8217573276e30b9557cd3092b3f53be4d34f19198924257adff6ba506c868392503a62d9f6481790b518ab1f2d8e4b0962e0b6ec1c9ddd6c55100e94ffd9c056af2dd2a07696215e1a32fe5a3d1eaf61a37a64200e43d453da9adebfb1bf3192a76e842d1a291d95e968c24f64c521429ec73b448bbc51c268317a961f62d304221e2608ad0acc476e8f77000fe8717ba03e94787f0b68402fdb00b53abdb9ae875b15ef38a7619be630369cab2d8d808c464884bf030e96c3d17c489409bd05af1317ae89664cd5a5511c38bbfa4df0c7374c9b7830804ff0e51c2ace638e4ad9b381c209e9d9f2d2c8d595789ca993e858a220d25228ffb5eba25ae215cad856e5c80cb536bdfa513aa6482bdb41f540696de448fc2b30e981872ccd7ad688c5691c96ad1099d283bb552d8d1007b252e29af336517d1379f2d73a797a788fdb4691f043e47aacc8483c9d5fa3168cb10987accdfad6cc33840a4fd47e02760c463b04203fdd197dbe8302732a9c858e74a891012ee156706c7615bc4806aaa5df00b980049a5533c01134110c1260769a289fa28e80a9c233995c4811a7fa39c9c378b89114b5a0a2270be8b9f71726c5d83d698a389434298773fa9957e06a23eb6ce726b141c59c7b486be4c1811c7314592e5125f69609fc60a05f0fc2f185c1c8293aa76be4d8e49b481d2d0b4dac146d3554b567f2b6021c4374061afc4914a7922d2309d4920a1c2a6ee35b582767ed0db40cbd1cf48cab718a566b30eb7672a01199a6f1f0afd94cb34c759c84d037d49858d5b22a671257d78f0b0f310fba5fed66fce1e6596d91bc0114acc3795e4fc6813cd8245285287813f481ee352448eeb2a0eb1a0841998603148728932b8962cc65944002041e93318fb184d98a22c1f69e0faa06f257a3b5a679881fbd8df7702b63cf0e9c04c89beae17b1b88e2676e38feb0963a0df237af8a43545341805b05c945146a2044cbdf010fce31a530c4f946e1154e9a24f71bdfb0217eb8f9862df9d1ceb3da901f6c9e6d6b47cf6eb6a2cf08a4c02ecf2c3a493b8eebda01f80f1c76641f7eae6859c1b43e340f6b2b7e73245c12cdb0ea761012b23fcd61748a2db45aaee4c05560f11d041f4c631e530d67f6738430ac1dbc4104051e084d5f1c3ca4c674f982d1cd5de61dfe971620d607af5c735fe40fb1610593d6264e11642a089e1af10b0e3b04af24b70389428579e77b83d60de94839b0bef2f3443bf0e8939a4cf03911ec3ad385ac9e28804ac9b6605c59cb2399978177368767f0b16de1ba92678cf0c19a400833736d2e030e371c0536c3d10e21ea15130fbe9e69e30e2777956829c8688043f5231ad8bb040f923355d83b020e4823cd86ba4a06f1be2eb82cc8975a93e65e054ab57785cca186fee9bd25d31a32ac4c43e6e075c3017ac7f21314b590743cc308405a8b5d9cd81980422b118f57669855077c595bb949589861ce15f206efcca056a11d04bbfd685a750153f0e28588c091a091837f23bdcd9cc86d837ac6c9903e097eea059745d153444dce1f7cb51470091e40b64556e946bef5d0234a31244aa98fadd23555928103ca3dd86256e512582a2012573773d753f65697daddedd19eb141e45902ebc79065c14820fcb4a6daa780b7ff9ca79f6c41274e01887f28c446449c98fdff7bb2abc7b12a0d745e433516b36ef3830cd68c40d9070440cedd6487832284c8af19734e99b7ac5a157f6c27c649a3816308cd28f0b23462994bfb4951073cf58f205782179171a0b59b240762e50c6632f0ae44a24d56babd8424456ac9680427a2e317b18c40e13eda940617e28bc108fe2cf5e6f281b162acfc778808e9c18c4cdfcfbcf10759f67726c0999fac03d18a08daeefbe6746bca1f8216dcbb65be68e539040f9b690004350444b8200263ce6198ec1fd95eecb1a6494fab97b7ea2e545ac0871086a0d7058f34c0108487125aea8a07a97b20854053057df1a0cc0cd2e15417c4cf2007089f86415a85cf0512f8dbe60be501f27b41908f58107c35520f4647105d3c41104c4e86ffdd3afae30e58e713776a73c416560202b23402be1b756080a049fc03610fda0447a56c7f20e6214149cc39b7463154bd1f3860bb2b8c3457c8cde607988ad427111f4989ddf8d5e2453e704aa0e38ba8d883952159f181cc35f434cc3d5def01eebb8e4ce134ae14535a3f2c8b2a226b963d000da34d440e63e55a00c43ca05a84181eaa21daa02b036c61a1c50319703cef83ff0bdb80328ae585683c400149da228608bba73c60553d939ea951414fd4573e57773c90c7c903846b40b0cfdcfef41bb33efefea0f07c13f1acc3b4097a072e23034ae243440f6e843bfe0bcf2b363a0f4ac2eec8043bb00452fa49573a32fa3b78a1e0e6d0a009d634cbca856e18f90e7c28f95caae54d5c03af983c1df68ee8cbabfb06ec319471bb2ba470eb6f2f6d250ecc3187d7d61d1c1a0b52998873cf29e4436029c7d1644807e427c35783f67de88b33011884fb7261951893b4b52bf5aef781a3aef7b92a52575d319c36e26af07849168957db45e6c3182c60a3b9bc1314ac60354afd7b0c8b85bec681b0c92a7282ea81e65821a6e10a159a650ea0d091d7762465362f2508d0203074394e1ee0eff35e20accbdd8ec803c402e240dccc3502c30390fb0d5a622a475b3358d50446b803cdd3bdb6555e39003a532591dd15053e61611d197d4806b2b06f887b8884a2d90e9cb6e4c043cc61dcccb01d07fd3fd2a80a071a7053f8b17c2d049fa6e33c9371dbca4a97d5ff25816c5810dd4781fc1472d81b984f8ca16072dc44e1d30dcc8357c782b15d0283f65764e97ad87d76035c7e1e8c0db4334f3bddc006ce71854391531005782131f5a1ab28a42e79b19184a72838b7816354b5495fe0291572246803f63fa74d4937361bb04a4d37ec839f686cb0815fa8edc8cb8cb869caed18de1e72fd2342eb28dd4f55ccd5c6eed02ad891defd075e33c8559bddba5d76632ffe9e40bb20d24937fd2044f5ed4f67e080a903896e63b02fbad10c24131cd9c3593a1ffe1cc9354751e51015424f99908720ce37391d5964bb35e991e3802d61bb3be182cc8353cee017fba48073438067f4271d4af520a0b8113b7c9ca5753019a0d6032352756814008826041a72d4eedf55b889a047ac849387d44053eaadc64af698c6854f65e75d9e3da6067a29b5622518d3e4179c142bae671d6e9cd5f820dcb238dc38ebf8146e587960ccda0b4fdd17f0480237357ffea0ab39087f591d6fbdd5f8107ed8eedc64a433991a1d2dc28f50aa1cf30fc391988c334358ff188e8e60ef4a2d8399f8f5061f560ce48f91b52d2ead436269b77cca2c4517d54027951668db88b4e70a60eba1846a20dd3093c382343d808e594fa0da732c5c8f54ce9ba7cb3965312f509c645034c61ce6cb10a1e7068ea410e0157ff5c6293e67f8ca5a51c7d1ea101ec0b1bd8062c9012a6d4a34d81104d8e4b708195c7f2756b87fc299a86d58d5f10848a7600826d92efeb7d47701ddb6e79f5d46c834d5fff48f3a4ccc1233054b3ae882ed4a2052a5d6eb4a5beeee0096079093973b2b2fc677d6b8228e067c687dfac65ec0aedae722c40a8cb06ad766d94be049e3fd147a4f2315b241abb7c98440b1edac3df0eff0e3c974be1d9dd164fee76192a9ba91be4624b9f4be332690623b775aae9675ae26a7d322ae91ee384044ab30994c018326fc8b5280949908164d4a7fa7d2a1060822a357d54975ae5a431299a25734a62f5ff1a01c1f03aa12ee665420dc19a50305e1317bb90e712e3350c1631e655e48b1641aacc9a9e3ea18cb393cc570e19227e41d4a4f64194529044e0c644c0d4ab9ca4e39995547f6e79240447b31570f88be76104908d97b6f29a54c29a514220669068106117beffd7b53d760ff49de7bf1bd97e4bd773dcbe659af332155504b0de4a643467050c2341b8d24efbde2a03ddcbfa7a13d9c8cf71f6f205fdcf32ce3f15c38ac1849a2bd0879cd08b05ec774188ef13f3102f8fdadef67d90081d910776509234990c8526ad9b7b824b0c487bfead84a2d3c312fa1688f1de6fe497b8ce6ae0f53c37898fb2d4ce469df0648d774cdf52b74f9e02c4759a01f2260292a9a900294920fd328154d3431f74b4d6c29cadcae913b2528a4fe265f84585655556fa3c405a919c6257d692eedccee7d9caed12e0c9d66a2f8ccfd1b2b517e5606fbcd5ada619afd52124ce39c68c04e6e518b84e8c4873a4b72727272540401f5e18fc3a51d3eedcf44a7be5be22925c1a7fd521452bff4d35c41c161b80c21c3e5ee3648f82480ee57797833e420ab27f7ae83b8b4597b141f87dd73afbc72892e0366763f5d39f9b819dc23943cb8d83d46e77f72678e1136d466a63f3bdf696666f72552dcf842f6b3526167ee4193ab6306850cc25deeeeeeedfefcd8231a1c33334b68721cc468323981baf91965c866c2fc9ebff7de7b7fef79ff7befadc5f8d3e97bef69da0edcf16fce3907bf216cf6d150d0b0e0f7b4cd9f35497aef7dbf16427377b38d31c8feedeeee96a524c89ece695e447656f5ac6dee63bfbba042a845d10d64bfebeede817b737737d73cc9ac826bfb7903d73b84cc8fff05c13fc473d0477677f314e4bc485ab3736f1662deb822fbbdc84997a8c26801efec2c4f124a54a9829ab1d1d6192050c3e99835c86d737274bac6f9f05932545ac02befbdd7dd0d04a709723bb911a49a8e18619816c3f977196b3284dcb93b97a628fb3463a32796c144e2bf53454184cc234a1e065bc20dd3e8840e42e8a2fe4955db7f3bb11b60eefb70a380e8dfee83caf45840630748c0e4eff8ce21741f7e808f0364f2b7abee2032f95bd3939eaaefdf51dd786482e73a5fe7d6f932ff5895512a34c811296264480792e8f0f43b7f0a00b2b9c170de084581ec08b969d0904db1c00005b487e66073637363736373a3fe5ec0e3cfdff3e77b24c95c2c76c460e1ce9d73e770c66648c7f4db50837ca2116c5499fdaa04508bcc53c4e80206eeee581c3e87d0e173148cee41f79e7bd04118dfc1d7e91c840ec2777fc1ed04733f66db9bfc9b046a443c7ee79e041ca1e6f4c4cf45e82ac31fb1e7cffdf993e2d6fddbe94148332a4ac9ce2573b9aa5a70a576b68769369bdd5925a04081da283b65a9d8e0dc50d13430f7bb46f49ab26614f51ae3361dd3ef336d33b7ddd96644eeaf0e120521a7fb1e98c6b5193eb5d683105d436414c50353c84db36cfac5eceff780f6d0202eee1a112f070f7e1d1898db24604c87ed02ef3dff6c03736d7afb1e86ccfd66a661ed09f6ade9c074dcdff3e73ce2740c4ed79426a398d51003ee6ee71c17c8c0850df260e0339db6c369b1065ff820846f27bb184530cd04179f8bd1c5e76cba69544f96e882f1ca765e5a4965668460bac732d518eb45d9fe5e196f31737aaa6609e6fa731866c2782a763dd76ea79deb7c9d5be7db4f9e2617268ad43f7c936a8c33923d2bba77ef6c035bbdf70fd1f039e79c3b12de7df081071e6262fa94690d101a7302a2322f4740c2579c3fe7eefcb95885413a2edc15c0387cfa2ef80ea6d118a53499fe4f3823263bb0f0dc3d77cedd73777fcfdf7befc5e8cae0d7663d79999777c9baf6c8dd13088335e4089970bd94af3060c4680ff6e7fc3d7fce6304208018a534056069b63de17ce00cf2b423467bf0c4c2f983fe9e3fe8ee2c06b892e9bf95f28029983ee1b3632bdb1c0aa6bfc3aaadcc5d3105731f3e8d0807ddf9f1aff260779b9b3c77f5c4eff0ba302ff873fe9c7bce9f76e213102e40bfbeef90817c1fe4e7fbebbde7c7eeeeeeb0a8cd6c910ce4eef0c84d510cc0d9d11eeab4d5d37e103e1db7be1ce8b80f3ef0c0434c4c96d528d9a88c8d3348edc1cee3575c9c3fe7eefcb9f7de7becbe60121474658bed82890ed4001b05dd669800693190dba6525730d11e8ced901f3183d335360a1f6636f34892c93dcc1c31f2545c5568ae14d36c57d005745de1379844ac765299f782dc2ed485baa8f825f485cb856ba49a34e631ecb445a6fb4af98a7914f79c7bee01e153e3288005d2bd28c8e5f8c4dfee5679ec9ff8db4fedded3939f76708c4d616e86635eee4699cac4e93205e2420c73b31a93e6244467b5c0ee273eb9bfcaf8a72183e112bb15e21a42ae56aaf099fe3bdc0a13a04c4f092d418462c2166e596883d92e1c0c6e629e2558583008beb3adddfafdb5aad7ce86e8e76f07422d6d0885dd5133acb95af6c215996608f7bd155922c574457c08ffbefadd9d0e65b84effc2e59ce3f7778e5f8c8d95a94617d3b558865e55120459420b9c608630bc903ac29317a8ea01aed6b6997dbb0de1fed39c4f8f63bf81884cfff7f05b7b8b98e6ee720eae9cc0b8fa59bbb5a82a50954ba4b8f18554a5325d65186f61841142e81eba20dcb7a18bf09c4ba6a97edde9c086d7af4441de571f4540c0afb4ad6347f89c738c8cd46235d061cf616d80d1f989e68e10ae732c8ec165b85c5a669498653769768fba5b8431f36531731521f33fc7ccdeeeee5ab78dbbf392a4243d76775f66be6114802cf1b4b03157d506e098433412a7ff56ffb96f77d7cd9aba90706b135b7ffad40a0b8fddde1e2f0b3e77593ad1e173eda463bbd71e2f0b3e77593ad1e173edc43dcbeaaeb224d1f71e8ccfe1734edcd79db8c3e7741c3ee7e439e8ee419de79e834efc3927ee3a3aeec41d3ea7e3f0fd38f951ede7b1f2765615e1b32a235c47f8fc49ac7ef4576e5ddeceaa227c566584eb089f3fb1aeaa5a4112bdae0b7bee5e55ada8014144e07efc0986897eb8c75c3472f7165e967c4e24128d485e89443fde8be24896bc9d553d18adca08f8e08b4f5cc3e74f48deceaab00bbb4aa21ffd2537c2aa8c702d453ffaa59766acb7b3aa089f55b98ef0f9131b535caa2332737be5d3d3e3ee5e14fad99dcd71fae0dc36cb66d931b4c682a9aeb1b415ea4249127defc1f8ac8e5a391877571404cfa1186365b915453ff07c17d6427ae01ba01d7e76e8022d0ffea9b23cf89f007121a017e2dff753650db06ae0242657b1d8063574c524c230d10ff8586736d6cccc75897ec0bfbe47c3eb6714aa01f66779581bc9e013eb70cccf1a60d5a03cf885388931f6675b7d6a347122558cd55f8d552e5566620454ef2f976727e709557f2976b9e56f83236407724807fc89bf21443a6a2f421c84f1afbea20bc631fd2646405ce8e277f7f5ddd5a92b4dc9121fdf31fd19cc5ee65a6c20f9372e59769e21e766b0fba71d3353c030374501fdfe902344b13b49f0f41c31b7f5d9f1b16d13db46f999b2a8f5c205db0acd5d24c4605eefd75ba21f973bc1ca1150315631a50cb1666ed874eee314224cf78a86e9680630dd3ba8b1d039fa666e3751464183d96f8a028cd9ff459862f69f8c7083d93fd34640c2ec2a66f7166150e2da28b4282de20c294da6bf7fa5041447bc4cffd86489a085e9ee84fdad981b36042f0430dd0a1194982e9d4d8c8a98494a7e132b184970218a146518e10c59f860e7643215e06466e6918f7d544daf0b8c8e1d5b0f374a459b3037bd626eda852b961deebf6d12ab04932be6b64d6831b78d62656e0b456aea13d3df99264ad39f668030fd6d708008094212a6bf1043c230fd89a815296798fe49806044ce4fce0e143c4130428f4f173e3a986ee5870cd37f7f9846ad0801c8ca106c802a43083cfdc52ad334bbbf649a19a6f1efc17db44e3199631c21f195110bd9f9d43fa34a0c3bd712b038861fcb0a098ee14a95c5f5aa441f74818323c30ef4c118221fea793fd899d140c690e37e808396aaaaaa4a8c20ff81941b8088f1f3032a4640c400a114a8192b49a8e29303599a6aa509153a6862852e9a682186266160d2640da621c896a9569838228a1c4db5c2c4124730f1040d4c28210928240c751897ead84a28add139e27dc02741635aa38363fcd559c845537b12bf71581942849bb785388990f6d84123cf4e0e14eaf7381ce10bc281dc546888137194b3a2087eeb3b2b424a04f82d4d3bc6bf887eed1837db82cf82ef71f8590c3b07d27f5f27fe7b9deaa11021e0cb12d231f78fcb62354c672ecf08a8febd495e5726dfca4a5f651763fcc25fbdf513f0b7344b2b424a04aaef2fc27affd30efbdc57291fa8bd130054e67d55692f424818f68ac34ace8e90cbaab217b6b217c68a9012014cd32e42fb058bb4befa757fad5b1e3d193d8b26b0a35f8cb43dbdc32c4928d1473ee5d113114ec78834254b7cd874cc13420433d231ef493790fc9b0d00e67bafef55fe2ff1afebfb2fed05f63fed256a11a8debfc2ae1f5dda0b7cd1ab8feb8b7881da8b032cabaa8a18bdf5ea037bf5f1b2dde998f7a3ecc22a28f3fd6c0ece0d9fde574b9aad2e3c58bdf702ce39e7dcbde7dc734878ba143cfa0fe1eb6b050e6666665ee675b9e1061a349606738bcb6634ac30609c90e42ebb739828729318c984891e732249a21f6064c9fddb39ef223dd4620513f0bc21279e05609cc8de7bd70d6177776bdb7359911eda32ae2429f8f303f853459724dd9dc9e56162b4304c2432491249cad28c192d2d2e2e37dc80d1a08143d0ec7631917e75a67edc9ca91fc998425b5430968bd08febc6a607550c1b8d48242949a59273ef41d30f595596156f744c85719958ecf5c11372c35c5c1e1806d34442912e52c23c919bdcddd5e4756122d1684422d9fc98299566ccc048232a7273993f787977dd65332fd0d4882989b75d46adc923cd24e66c65e9478bcbc2d1b664463e89462312499e30d5ac549aa1598b8b1106b864190ea21f78324065f467769c7030996ad4b0f172d21718981b3772c801078eff2cd3d4a525478e1c394439b0cbca9123478e1c3978718cb625739ff448ce9bc44bfab94f9adb06e66a355f5ccaa48ba45256df4572bb7bb007b107aafad6a5aa2ad2dd5d55d5d128452225b163d2ee4b4b2d2287c565e1ace5068c3317bde186bdc1edb6b890540180a9e192bb1af4a35b04c49b47a03a87451904a33409c1389151e4d629cb74451827e43eb63a5373a6eeee3e8c134952294b2575ddddddddf5d51a1bfc99aa31b75c7129afbb3bf3f26e4f9526f450b92205e77677df4589edab8f5ef7aa15a1eff49df6e2cf393e8a70bfda8b2b41657867761bc9d1d1562464d405c93f33abae129b0c42f25353a4cccc1dda21b7435d33a3514ea9ad66c3c12176b51720de57e39ee6e9611a578269f67b454274c45099fe6bb3f0a97fb5c532a39a894ffdcda5761fa2d2339bdb7587b2749331dbd747083a94413e1fedd1ef521335450b6d22a8c9740f8a0baa6295202d5c159b54e2e6e6e6ae80ca386e0c648061d00f21ae903ce357bfd9ac8d0f3636eb62d21e15a8a13516d818f735a689b54dc1606a6dd32e4c2784e4d3103eb9afdc90102e88887bc76c4488211d289264759c73cfbd1208d36c2e37541408932e4fe84a61ddddaffb7577bfeee6177e97099b2f12b603e5f5748c0f11f4e4dc0f86c47418cf7458cf742894ebee8658124a38ebdd3f2aafca0372dcf0f1ebe99a87ea182228cd4b829edcbb3745434d498273ed7c72a452f763a232ee5ff674a032ee9b61f67a82244fec45cefd763d31dd5feeb97759d490254be59eb12954c6bda36263dcbbaf31b787323d6d7b5528adb63d2bd3fd56cdcd053d94cab8bf9c90914fce71c77939f72ffc52ccce69151ea02b9846c8fd0b8369b2b82ea61b9aae9d73ce23c6348dfd908a80f13c9feb0ab6333d0b9643030d633acf05c635e413c685e542c2faf7a23c9f4703a57939d093bff744edea7939e02992245d3472643836b82c2ecbccb20517e493a5b433e4e313a43d5e932cb4e6317184a12180e1032e7c44586928fafc0c4de91a1d787ca0608902050ae8053d2a2ff5b6d8302464603c6ab854c7f6a48c301b3a6c21374dbd1eedc1537e12a04c6870de12d35d53a9253e96b5c7342552f895602fd2b638472f7a91a644f4234db50d8953d934d504d35cfe6e8af67041d3fa07df1f03bac917b104284d103d01f14b0810b62288d200cd7dc7b438c29469aa38b1e900528b461150197f984920b569ea87902da44e9dd88ba4f67040496b1e00c6a90b996601c354c63f6a4a12e91a1d7884088165fa63a0034694ec8e62d99c4d42622e30117ba8c8a21be8d00e991e7699996396b31af36aec2424634ca5bad5d0ad74da0a753a459c991b201118a2401aa45d4489b9402f2e97c2af3bf35a8cc9bc18e3ace58929ac3034750a2b206172f6b2f0794c9a4c46814313e6f5565f8f8ef4846f7a628b79398ca5b0f204328e90e608e284d90c29e60531ce3029cc195864615e15b6d98b7b9d028b30a6b330ceac9c795dd866ee31158d1c1695f1776750197fce4473339d22c805d39151890187a9405c4c71435cc8f8956f0e8635a43df85daf3c7f48a80c15b4e6b9609b5cc101176f0659bccb09e97cf22aaebbc185dc57bfa232555466ffda4c5f0fd3b873cfa76bb4b7c303a4049307e541f1502fca9bf2a8f01231e260d8148bfa958a4ac859808fbc20b5799a9a4ac91d4a758d94299992292937cb62c9b2438ba5d4c3138554d015d294fa939498fc540e286982d1317303a40714508a69b6885db145c47290e02f40bca97cf23f4d6bea8fae2075566fbdbfe31c3e09758d3fd4621c82597bf92b42d0228a11a6504219867c6cacf1f39c1d9ffeacc35c4d9fb999695e3b656e86c3bc0212b85c0322720a9685e018838f1105eb0563582d18e38bf5b8cec73006195eb4c12b2f488c4f8c7365fd7b65c7c9a6871d274763b41999cace6170a1c2d176c4e482c57677cebd0761acc2a7fd61c61cb2443f1cd025faa1834f98e8870ee73ebabb227d9a228c4fdbddddfded99c8b3cd85445a9213ad7345ae6cbb7ae65e4a8c463e1a358f76d4fd2e4ff40369aaca2c43b81042084b51e6a8cc6a1b92d113d7aeaabbaaeaae0f2e5555d52297eb5ffb9bcc785919eb1dd3bf77f8f2c846c87d4b1bbe2dd1dc728c993ed2b6fd2eb23bd19db8765f55558d4ce5d369ee7569bababdaaeabddf7c797b7b7bb766ad399f9a8b60618d597a623efd6a5b95f5bc94c67a0cb32cccaae272c59169449765653b453b2fec6d546f5957b6b35295f119453fa20b019600fac2b90421c012405e04cd1717989aa16d4be68cc6f15bef6f5d25fbad8310a0bdf6da9756474f3de5a59fe8263772d81a89b98d9afbdbf29bcf8d38b42dce1ce40d09a36d261bf245f2989b9c9c653a528838b2654c1b6f7a79cfe91ae7e1c9c9ded4b2234c3ea4bce8c006f9f2a6b761e3e5e18b8d172d73ce73a546e6ca43f7b4a1653d05026c1d85b96fca74d6c8786a6d458d6ce74b7685293b92829c69e3995e279c59a61df399768cc9e56964a5694307364897a7f12d7fb9e7e242c38174958dc78cbff1989b562163568f43e63c994be63d7c823d55c896b75ea6e6fef5343212f73446231b4d982aa4e86964a20953851c3d8d0cd3211ad93561aa90f261aa9034326bc25421f7a38bcb57f3860c354bd345539569d1b6de99d1efdbb660f2cb6cb321659b0e8d36e7d956686eceb371cfdc7c739ead77e6e63c8dc4dc9ca7480f2def346141f7d0ec9d263a309503977cf0508ac299cfc6667fabcc9dfb46f2787d1d0899939383e491ca28a30cd9513c24a8b96f8306174ab36081159a050baac09fc79f440bd23db70bc25002fc59178421c50f0b6ea6977ee361c66f312dbf652ea3174997e9a27d8ba61d334393a5df6ea6fb4b663b5f3eab7ca96d26e948999eb06cabaacab61a456e780251daa1d2839bd20e152666a398c49eba940352ad384167dfdd10b6bc4034d76a3ab441ba2aca22d389af6c7124baaac761f705f843f731ebf75e203a5d2be2d87d6c84e07afa98674be83d7544d10ff7ba3f2ebb1b555a8c56489d4f6b58c4846f3ad35471d74d9fab3ddcb4809dd78559da0cd802a7eae008c588a412e44d80e284214f250b12e704264cac093b9813de5fd76f76adb6b9a1b98ffdcbb69d4fa84581a4b3623a971afde6825e073648f7a23e718bd2ec60ba4cce8a1ddc37c9694edbad887b2c2be286a673fac2d021dec76bf3bf5af4e3bd8a1c929e402c109cc860c6e7226e28c802c109cf8c715e48b0f845b009b7254d4cf74962b6c5284ba6284008069cce9765be2df6a35e7104ce1060d3691b92e95a116cf2cbc2094c195e388129c30b1e7eae0b05c6429121803f2b90db95ba52da0306fef030f7e1cfcc5c2d0794ca8021b700e4a066007260e695823f572a07140f3f3ce45cdbbce9defacb0283dce9dc8e91b6d988b46d46db4e5584a2d5d91dd2ea30cd233dcbb2acd3b466a6655916ff1436334298fc50fb26bf3b2326e4b629554d694a5324d25025851c94c9d4c369e6d5ea18246efac3643bc4305551dbc13444b40b2276eee8409fe434c17c3f7feebd9955c10c1195e1c75620b9d7b43cdb6901ad3db97e353ff12651900d97ead8647cf4066abebf2ecdfacac520d517aa95edac9e3f56d50dd4dc5a5430f9dddd30426e1085d21e334e73a21f6e0441059a8d1f95e981dcb29f99094d86289561889a4cc4cecd4694f17163f8dccc9c78a4642e7611991ba73303114254e53769696ba4ab87d38e9a9d11e1130a05ccb84f66186b8c1899098a9a4808299a8832b9874f7c434732730a6a61f44476741f1f7e8c7cc308095fb5c74cfc97456d732ec617e3f3d730540b9c8bee31c6185ba42a53a4c874bf5b1197e9dbce7df8c4c42f9b98cc0a21fc2d66eeefeeee2fd4175801062ea6ea6933c17c49e69b8f675bf96ea41cc34fca943ddb96cccd7f5d86bd1b89e46bada9c9b4f1c4b46d19735f299996cff62ccfee642163e88a0a6995b79927a86d2619dd168f2899b1e7b2e8c74371cf5095f571a9c769981d4932e1dc5230b36d498cca454ed3ddfa82dc778f28239bd47defbde79260487c6ad43634fcb87fc1f87c46cdf7f109494ffdde30bca7aadbeadc9d2fd7c0059f5ea8a44d5a7c563a1a010040010316002018100a854482915094457a9a781f14800f72964072521c8a835110e4308a82903186106308218018430832353354830065488a237d5752d5896b2408c720fbf26b39590e6f5725a9a8168e701713307de3d5b00e022a8f62ac2de188deaebb9d6421cdfd8a2d546f66b36c32b488d6edd6310baa91aeffba3c207aef7fd7688ec318d0ec7bb569fd4063d3af42e097cfb07ead6af44b554797e0ca607d51b9a5dd37878bbaf72c11bcf1d0348b658a0ebcb3e7a927fc663b4a96d00cb9499c0c84e8b477a46fd5237712dcd0813bd5c621fb2b66c1dd5bb99067e4dcf881375a5477f8bbfdf36308dd08182addcc3ba06ca1c9ab89ede5165f6ef474543668ad026c6beab981aa3024bedab2bcbb0b23148410009f7be27e0162190e01d55b2af2769a7ec445e12247028e49142ce74eb5ba32ae7b4292e54c60884e5c9f1cd17d7e355b9174527ebeca6d0f34bd61e19462a5c752a8bfb58db0cad1e3f7893d6de51babcefe1158e6148ab2a798e532db2a2c97aa239c73496f7cb2abf75311a782764ed716585127835d5d6ce98bfae8606269219d0362e01c16286050ad1422819460159feaee27832e78a2ac0d93aab785516e600846cf0923a565fe115c0dac729fc56bd12fb9e2a039743619e5204e6d0297297ccc34992823a2310bdc20707511dd3f493b74ed8825c636429d6f164b6a0ddd281b9d1491139b2f01cbd564bb0d23a8356fa24ce06c87becbb3c44eb68dfa59dd7186f24102153048e0352aad471e3f9428b30de1462204e5112e308a8b6b753db1ece1645c28de498c0d70b7068c104114b06b3e0a3ea552953c4dd2a2f6b2a5f7988e397a53ac29c16669c77dfcdaf804e951612afcfc140677b140bf379381c22e2c7328407138ef559ba6947c82b4e3ae9acf13b34d7b77b18eb7da1c0f8023fc0ef10702e085769d62d6171e82bda2f73f3022895db6b10cd91f87c5ae3f1d655354edce96a94646e41bf6315f7a3ca4c1ceb09a7d01f4b89043c1700491a6ae82873705f21f3727c440cfca2fbdd6433d3f12c31854fde11d548a5c2910f2354110ec7a8bc046817a8b267acacbfbab088223323a2f06c8aa9e4955b32f9c661278148816c4000cfc3aa8e71426b82f909fb704f5eae72d2edbad98bb14dc661b060a24f8787229e567957045635719c33562805349215b90ed5e202ea8b9efcfd46ade6cf7201885b53039514d34f21fbf69dd9dd4e50f277446464742fcd304fd7c3262d2750862715ee2d96b8e300b1ed0cd411b66c192068e6a20a3ac59b5eaece2b5d9626b90813445241b21b8400f84651e9c0e3b80e6c6cfcb2e13f215da0bc8b5e1b277923d9254fab9b62e3dc5228ed1d0114357089775b7d033326608717f5c1f8f5691f8c93ca2fceeeed4289608a4cc508c4f5d80e438e9c5e928570ca81a52281317d5253be7a03a7159a2e60b88ba739c3df44041cc3d19a869f42543edc58d314d825e3c5fec8ea040b13c259c53414c7dd7c35409715c0830d3431ae8d6ff8c95537ba02113141c85f8c1b676884d942e633b36ca914f606bb9feaccaf22efef0ec197ce3448f671190480784d8a08d8836e274e09da5fbf73c32d60fb9c052fff4057b0552110f8009149ada7fc38c6f30019ee6e175f85d8e0e8a76882493ed9a91b1354552a26353467146ba7c4b1ccdac82b8e366431b124ec605e0e88d9792d11213a33fd05b3c314c42766f4e626fa88ef366fbb658f3c12d81b012f219880bdf3e68c4718f9823b677bdfd6e97c67a6c30bdfbdd1efa31638fc9280e4ac9e29f39e4f7302a615806f719e4df6ec59cac57f3811d32f89a6248f2cc30a880cbd877471b5d1aca6f0172dbb33574410feb0b8d6e31fae9c6e3c93c9aeea3f2bef31c6455e2d1d6acc6147c7b8893d1abae38b4d406397ba57107972a6b238c0d10a8dbc163ccf79a5269cfb73f7f9a19438770fa5499878694637dd865791ea7032ce69c5bff59576e6bdb0459f36f5cad9e15496fd950c8bec1312738841be751a2591a206b79457b9d8559ffc62a25fbed9ef7b42e1e1425e4ed628db889cdecf39c160f8f601c200fcf08f671f4ea2bf01b971b53946b74f912ef98b761532e42e72e09df72dca6ba08050a39e2608854ae28c50d1b2e32e4ede770c0e2ccdbedced32a9bbdee90cac3f614d3cd910052da647f348e95c637d3b2d7954bd5c7a49ce724c1bc50a0e57bcd0168f48f53600c563471ea17f9072e1d93d07f03d68b410268fab3a0d05ba050bf2ea438495e6935fc4871913527f44a0e1250d05006da61e525f64613bdf093b4ae5aa6fc8402a4d4d0171adc0307f5a4a77fea41dd8e42c5d5cd3d416c7f718f0a90b686757e2a207ca8b635e57326a1519a4043be5affb92df74adb443cf9d822de9b1633a65a697665232ab4095542c1eda72c2a1af44849c50b4d153af9144fa9b83017a3278ed7bd0db432a1f6a3d5e26c324124e80327bf4f267fbdc4e412449e86ebb2b3a176515b476563356f838f4b9a35addbcdfa0a459be7dca46da7435a44552eb3e9c26f087a4e60cd232ca792975c41462be228731708b6b797223ff204387ee6b7b6987b3fdf10c9f5ee6db76fa009a58b1edcc480f5d092d491c6b6de3b11064fa376bde58af60cd04a2ceb936400e24bdf3221d2bdb20fc0bfbaf82211b725a9a867e72bf94c962b0ff73b6e63ac342334e0287c2a092ca0a1259c333794a55af9f9d4824899b90914557cab062024fdc0247b826f51793913b961784f99950eb12b72780f9d60246c2209d0296c6214557ffd5cdb9f8c7b294ba64f1d9122aeaf3b4c856a7ec76ada50597021a5dde6d8f751d45fb73b8108291cc5b469ae5b37198605f990e0d074c6db420a74d2cabcfcc1eb09e7b078d9a34dc44957d3775aa4bac6d5e1f2e9bbff2fd8c300fb54b32ba3400a54c19a2cadcc1290b2f87d2a315022eac6b20bb7a3bb1c12b23a4725064b159b29609d00c17cb93f986f3b2c1ea679fcb067b763720c89319c543111da9940e1f0b8408647f28e554eef5156895897ea34af0b59237b11756b4300c29e798926e1c371cecc7bb9c4833bf4a54d608fd9211aff92342c3a62c8564ee2e8afd4286521de832e1b52dc0bc6c1ab1b5ed7896581b580432766e01005d3d7645f46108ff9d75ee16503b63f8fe2a2940fd4fb3d7e7c7ad1ea3f27e9f5abf33c149144776758f50414b16353a146eac190828e4747b55f3305beae230a5e3a25dbc1a45e44c6fef21704f2a3551c16d6746b09ae15c8325e6140f6257874b356c48cde12bf7df4d250eecf88c4bcb2ea5aa01a06cf01c10b5a28fb2e714ed22ba026fdd1ed2a5c35710f25f115b98b84797625e4b23619bd7d43c06e39ccf4ec5eb285a5078e7a11e91bd6cfe848e07eaa36f02ead0667639128ccdc44fe3e88b453444ac9a1769c5898864d174d7f99affb21a2ee07d2331146e5fd7262a84163f237d1dd32c496a16a976939a43c1a45d5f266dafc9ff5ba32f85fc60a62fb01c028ca32fe4ac663d120106e3a89065e618314b49484eac1c2185578000307e071675e87ad4fa2c403603d073d1f9f1696945356b71e7489d076ad9c7702f436c2e02a421faab6da577e0a5a97b618a181e70e6c90469f32be406f4fe64fc2814866a477988b842460fb31ac44450c386e1a710853562db54b91fe3eb28079359cef443da065fc6990af38c6343b39c4db6a5c251c9c2f1107ca5b3c4af55c262dc8ddd5a11a92d8cb2e069d6df2018d1040ffdaa25e54b0ff29ff702c1c30acc8f9a4f936803895b53053bd8937bf441c697984833e27ba751b4844ca3fb2c723e410048febcbd8eabcc0439ab7e4f6df07bf3e55041f496fd8763e1d1b65a204c2a247543acfd67f721d10ba03f2d8dc8e703b5bdac31254c852908135e614b05cdc876e6cd0a3960600e6c3d025e76a4c3093be0d6406edf618315819ff798d8929f112cd29a2b1fbfcf6b7d76c9f0ab6f4c484be54b3273ce6507ca696cfeb16bab9c50a47537928ffc73702218a6afad39fd78b9fe98d5532f83ce59efa47fe1777a1d56c43c348303e315cc3e7a9f691b0693af2c1da8834da8fd99aa8e082347a08b4bfe408532260df47c51db9c2241beae4e4400b8d9b1d6505d295c87d55eec98d5f3b401dbb9ab22d04eeeddf06546141e3062a9243ac3974d1509c034c4dd213fa222620aa9c9ee7796dc45785ab64a1b9248fe4efd12352f5248724611ddc866399ac75c43d569060c3fcad856327dda660e8ce6e9a49892e5e4eb081df0c1ee1e971e87fbb2ba236a6a21899c20a66b12a66bf6f04156248e0e565551764ae04b9473cde144d4a50e58a260a88b09ac6076f1f8de0da9777884fbeafbdff6fed5f74698f2ce146af2aa83501786f1a1ae9552fd81ba924c1fe5ff072cecb45003227679de2d41f33c8ead1979226585e7159d7e22040862fe2ab2e7b40ca95fb2e7da870e083f40a026a20e0a79d43184f409c06bed6ed66c0caf86876769657024ccb24c1dd9c5c3a743cfc3e4a48431154e3eeb95772cfa2c04dc175a2e683d450cbe3660223e92da6e1798e152fd1911cce5c41f95d50cd7812dfddd1ec45c1a101db034822604afe35048ce764d6237acbb137751405d3e14992a229ea33c45fc5036bd718766da604019175b605088009e03195a60e5eac36a2554584817278fa1dca413a728e0742cf2155c306251804c0ec8b47751038aa516037b4c92545da5e70183625fdfd2282fa4949c9136945b11a25d5563b5a9023954dfcb2326ab59ac16ffd44a762e2aaa4bdb0182b93af7f4fffed67a743523b15922b69c398eecabbfd5c483143f5adfd17ac51147236cc69bed2ad426b0ec0b387cdda438553e4b6aba9e5eae7d9a6e718d6724032474003243c28161808292d8bda94a9fd9234c193c54ba60da9510a771117b4a35e4ad0709adff39924db0b0c404d25d9c2a0c1d4699190380b626ef77096e77f0b3aaadd8f93b8ac20eb005e784385d0e6a7127bcced68b6f0278a3bf88aa35d1ac05273292664b0832b5e42db33c5106dabd09e921e0eabc15a44ba7af22a6df040165702e0f424ee9e73e5d6328e18965d7a8057dc1137e4fb036419241d3d33fb5cdc08a83484573ab16989818afc18413e3dcc442a888f11fad75db892d67f783a3c26855d4a52b3c92225146be1f4b5aba17105eaf0f3d3df6f5ae09f9efa9346fa17fd0df41611343c1a5779801b71da9aebc66c5bf887f33bd0302902f084cbd46cf2d0a992419ec7df2d3a897fca4b5af70b4e890588cd7b83bdd7758239b5f8a0d690f0d92027a0b9410555723be7a0a2b3e9fa0051e3ac9af0f208a50014cb13df6fd56bc580be385bb013172a87147ee3a2150ae0aa0f1e67ff6005e5aee24605fb7cd0b963a132a818358138d5d02081845d0da81768f4be46c7250657eae9d2693a594757760f088cc9136f6697a6e5d43e1830c98f41d3af31dd93f9f155e17475f16095e03fecf2eb4892056d852df6abf07642c4cf227b743043a4566660650765c38d741b4d718c73207e6021f5d83785df8f34e81b5a4559d1699dad06342491c9da790e300837461a8baabb5ff5576928d786b0c4b6329646461bc126353157c4b831dccb82f7300b5e2594058fb08b22aee2844f615cef916a40e061e9a112366914ffa6ce9860f0b2cb1a7d29d1b5f24d576ab2a09445762453f2389c2290669a0d1a5ba0dfa7082a13a28b1bcd5444a74fb5600951ded100f9805cdea583ae57c2309439c116ecda4824eba655ae50f2a38ce2385372befb5b94948ca10a06122a021cfaad9021a917aa27bd7690e794e1c6e38b434c9e6449939c60feab45af281692ad55a00249cc1eb91ab930e5ba661ff405e8b098d8c1f8462b63efc3b5b84005884598331103fbd09db4776b46a7ac646165af23c3650133f2df98814bb8edb1d107e11bf085b09b514c8c91f4db5cc644aa97f39bc5851f2e8106b06f67948ba0817c4a060db8a56e547caced629e24fbd2cb2412314b51137c0bededf86950e2c066af524720b8bf7da209d64e08b2c128d604180671ea53b4816f8dfd06a8b940d1477f2dff2f97d29438a88f38f7398e16250cd4e7e0dca03070e4635c770886c13385af87ad29ce96295c55cc7e0f3948670f926c15f3c3ac212b912a5cc1e60261bcb09e972355a8195401c13f1090ee04441d30365b25d66837ab7af746705aa6d0086a5c0be1225b950f0d365110301cc4c353683e5126bded3bb3165b706c640223b75aa39c7a95d87de0dec686c9c06f6aea84cbc73c9933ba7511660b84e4e5e54f5108347d2b282244b8a95608299f4c063dff0bcfa9643205cdb48bbd41545fea0a6c6bc452167a0d3d094644211998e34e7709b3f16a42169b9bfd2f3122299a594ceb77eed57125da0395e252e2e752e20b97ba8c5f14a4087f7ae3073dcba94d6b63cf5883082654c239962d0a6138c58d09fc121b16d9d1fbe528cfdd7571ef0eb6d96d076d17384ffb4ec66f0c1a36cb70dbdae01762289a9bc66b78a56c4ce1be50b9e06877869fc31f5a91948adbb9ba48bc127412f22b9133ebb4dc5ab6db081023c53448319bd89b975c3c17eadca8599f60cf28945273b272aa846b5d8896c4f1e88fce5cca9fa892ccce5d9e747566d8325e664301f683cee067cf5dc3b195c5ac97dcdfd5471a5cd30a988098bdf645f2bd8de8c0069463a233b415cd2ab76481f1e688d50b9835595656e40f67be9cb1bb067333f576974dc0ba929c0c2d3cbfdf721039f4cdd4aade4304fa04c2fa58f42cc2417ff766f1ebb107570de6180e73196adb3aaa4373a1254a33346321f3718659307f7e19644f184f7dad9c8ec4f8c1e5fff406cf058e82daabe8afdc757955f372a50e8c7fce785a7df5f97fc68a68de4d7c32bda042f928d2bafca68492a92e51a2c6d24873581170942daf87bf84e6fd4978f805de854771392d5d41dbca064ed77a891217245a5d7c0b6540d55160c49fb96f054688d28a7b7fd808dab2a7a5e78b13733410cc074b6baad7924023b5102f8c4717d872fb103ce5db333677612047b871e6fc6ccd2c9f2fc4a297b22977571708860f05433a4216d9d42c576c05ea6fcbec081436f322075ca2037a8403f7087cd41ed382ba8ff870f897d7ab79580487853343de6fe0d0fce0d9637a6225203b0348404675daf336bbc65268d08dcb95793b89c76501d583e080526a125b8cdea76d241eee99a57f4347bcd9bcad57a06e289b50f307d0a322374af8fee2ef62971fb2b21181aa5dfab4787f4a988cd307a29a08a8642f5074b2e25836e4f12f9deaffffbf3937a811c29664c6aacc27c12780818cf66e43bb6e33a373977b2aaaccbfbbee436d05260cf38527e1a70cccd8577a079ecc0fa638f8e25b008e06dec2942b98f8d2c9296cc53350392bfedf54ed4c9d3660ec79a77d188c4e1942c4ca2ffb4de65d2bfc31ee6daee6df674c3dd8bbcf2cb60b70997f76b0c7da3de740aa5e5bc215b4c25b3a657db141c1831783ff102dbef36f4e7033859305491e25616d7607408299fb050e76a826f64eb4522b5bb7b76ee555d427017f90998e10b8c9e7aee4f7e003b5f7af7f1608d7972ca72692e7f90aabda6bf8f92bae72d1e1ac0dbba05d68ee942ad29211e2da5246572bed4bbe22f22a04486ecd521ace94b35237e83af0ef3a666784c96ebc387bbf076a9b5ad6f7dc7d38672584786d8f63278cede2dfe3841f6f42fd2ffe1cbb00c850e62b01a877d64df891fd60073df7fe06b99bda6a624195a488e59dfe122acd3c7601e63e26aa0ad6915a474c368d71b433705d24851d78cf034b2a17f0800fb6094f5da6524427aab95065978301da59d96daffa99fb333c72e4f4013af0f1f994a78a04ed7a2f39c840a8fc0180fb0d1e06517f23600154538c8226627613933e369356d59731f1aca4b32b6ad70fba0d2a6398c29597e84ba4f500bb6f5a5b4599db9181bcb6b4916c64ca73215fd57792a715e2cc3b4204b3952a2941e15af12d9c9cee60e9ce18e464916d322868e24da1bb6c22d612ecc8999e0a1ee2e75117e6685293c7015d6f11c3a4756bad22208ca64208a348b0e972afcd8911f283402b875ac95ad595fdd5326517efef66891272e0621593049cf02d2cc5dee8c3e1490935bc94eff06e1d916646da90809e7a7467c225294d6d85591b0e4003b4c0be04a4e71edcc09c256f306999ce7f6149b52688651f40c0b65109fb00d695c5a99ee3f0e406e91c01d8fe150a5e94dcebba30ad935ccf4e7a5e2a1c490de6d6364d65cf58284cdd9588c854291aa3d51796a4b3dfc27e6782f43bdbfe4372a9a5ece4b8d9317508050db39c7a1600a941e04883f1e2cc73f4112140d6518ec898c4e4bbf86d306b9808827b2d5f094e88b2c51494c37e69fcde883b6f6abd29244d1546d341e6fcc8f6c6b0f8bd6b2e56c505ed188ce4c1ac1972a3ff62515ba345e0f37cefb78d399df842aedc75d2bad1ab0f0064d90d633c344ca47b28346fd374815289ed77388117b9c1419a89629daf9326651150196bdbb337783f1511a717540650ce3ab54280a6ef0237739e3eb6bd4494984dcf73719e572f0a18cc793c0714b1e769dacb548fc193ce02ce99a7c5c977c03a664f88ff0a029a7f5da8d819bf52f4e276aa4cf4a081137a80dd6dd6f07f80f57ee34c8ba38f403c208690012e12c851273c22fdaa0454476a3d2b2570114088bbf88f90bba0cb20d46522ebbffec200be60055169b139fcbcbd83a683a9f3d82c929329b9b2ad5fc83365a906a2e75c3e857e6cc15c992b1b06443ef2bf60f9dc418b1e658870092d3ef1212a0d14f959a804028abb0a3c0f19f87c5e2d47b6c0ce2574658def7126afda2dd45e0dfd3dfb10cbf5be2a4bf2181874861ac985da71da31c4c4571a221070edf043c64b9729b867aa49a5f7fcbd1fa1988de99d52291be204783849fa30378cd2990116cb2743cb90bffbe0ad60859fb232ebdcec386c5fea38ee3c2bc2cc20157b8c6b551e813ce043c64c4ad450eb9af849bb320628098c6fc110076ec7a06bda9fc2b5740e899637dd1c68ce778d3b0802e475b1c3457988d6e0d8e1fb5a2e16739d925ab0db7c0241f18e768c30005da30aa03ea030b7992c61728451bf694408de1bab17369f27c6456aa78380e674d145ae8a647ae33d86966a785a857f8b00c9a947c6253defc66bd6e0434955593854055ba5958201eb29544391639fdc1c7a8a93c7bb8b51f19c4fe3927e697e0ec4a4eccd80a7ac5ab75dc4d5c4337aa5dc10a08ad9604cd49500f016fb246406645c927b18c07b10ba0f00c15d00087f18a9d35c138e1f3f1b97717ca0113d18b057160cdc2a7ad3c92f9439f545a0dcb291294597d1599f78a68b958c0d5cef51626c662b4d3a2b6a257a6ba3588ade473b2b1f0d9ef271e02372adf2ff83e6d74340facadb25b2716fbb81b593fe8f738cc42f88c9d8c9dadc7853f385c54524a251e22bedf4fb500224a2b46c4ecebb71dfa5442ffcb67517a8e3bd20fbc1b83efaecd7a0c64f44912bb9dd4d6d4ade0cf658b82f2d3c30bca932c6debb700436a1ed0c0fb41d95f12c99d380bbae38a5fa81ea0343479934974d5ed4c55fa0c073bc33183b8ba7d43da4d7723b139d5c260ba4c88c554aecf7bdad36375a3c0620288d162766539ce628dc10c594465bdc76dc7e50f4b26149b8a48d6b8aaa3055947fecd8cf6007a7f07c892df0c9156b649e27a0ccedfa8632f5681eeb83f25720f93159b7e0d4a01b1366dd96d8d20ca61cc276a985bef586549713788684b4b3d8796f3da468db768fd714baff909263202f6a44a8052f3486d486ef1bf2aad642c337b80815c357d110623ee10a81ab9c48db72267c53a2c295080fcc93ee1ada470e6971ab1a10efb9c919644eee400051c98e37484c73a627506bf03b4adb3d55cbaaa82da8de51d3ee399bab895523da53c34fff08f1ef6e0e3223946550095f1b255149698858d9ef3a52a32be94755ab5c6bdbf4cb1da5567559da6be4215d2b8a77c47364dc1c37915140ffcc2a80897b680aeb56d1fc628ecf6e2e21b9fec98ce859f61ab57f9da72bbb25e02a15a4694c1480aa39587d7d8100522bdbc6bbf27c3c0cf26c4162e30df2bc4b50cd8f6442febc6838a17ff773edebb219eb152003286cd6c7e87d84cd1de17a451ada0b183070b32de36dbbc459c2be46eab69aef74741f1ea88e16bd577ce8650984f5273d05bc702bae89be9980ccb6e8563958f67156996308d7669fbb76e91b83db7580bac6ce74ba83118293d476b5c169edbc5fce30945d120f84f0a6263ff8f3779d4df2ebee03c2e740f168cbee3cb56338291cf5a68c4b410f6502454fc4587a496e92b99f648cf6658d6080bd9993fc02608412ac5dbe3aa3c733ad5dfd22e91f019b371c5e46ae3d22ab6322bc2a43e4af76d601fc519e0c1fff7b19b9bcda307a03764759e4dd146e5fe412c43f2fb049d4ac7c38b1f1f95e683f60a3464eb8e519b92e674d6451a2a44069c5877d2d3aa48fe59267a412f0d22f8c0f0069214a53e4d90dca4972ffe56ea88c5ff53a32556b53c6d26172544644ee84aa1786345ae12520720365084381aab6c0a52ff211b86dc32d237e42a1661183ba9ec84dd1e7733c9e070752769f9baf64c3343eb44995a1c81fb2d2337d6c0509e35e025b250d79b7d5cd35751de19980b4a497fb79922fcff381592badce01582457f49e02f8a25f7a3f801806d72136e84766838b49f6f8b28e3eb0c70ce261b22d7f8bffe920418c4f4e5d423651b017bdc5418f0b083572aca3c163d4d93dc4eb279db5b86eabcace2015ba90b6d9e6f33dafc0b1897901a831e305099b07c37ddeb37d873d623987c1e9dc31c6a0464629f374f2677e5a1524d3ec66474c89281fddf29769230fe20210a5e1e14df5fb890f11b0436cc3118b7da54c6d51671a3dac43674207e8d222977e5c07b03a81e5ffbd2c0e4760169fc8230f3c80fdfbc957ee618e4841ef7509f30e645e99edac789f1cd412cd7675937b3cbec0731c8efda0b961f1695d690860567727b787fa0d664fd21db5c90dd35a47659cf8c8e5cfc6349f17cee5cc6c8bb9882a2d33a1059a7e03468e8cfe24b291bed444267ec4bd60baa6aead44c9dfb226e26e650853f42024eda8ca00ec5441634fe0b27794dcae4ea7a56da06447e6b0417496704e870c826b425e1eedaf4d04c6d0f3942b7ee9cd9a12e86f273796d5c945eed9b9e1541a751b6490af0efc62b14a8c306eb98c71e45b67f7fddf397d80ed338b7a42b803691da2be1d89cd41c2dcad662c920ff7c01e37a9b16c296380f2d17d0a65b29656431f0dd79da11c1103a39494cab33c213a45e8702a6e682208ad0a8d0f07d16bd3d28529c3ee24b1ba5b89159aa4a39d64118bf9b82fac21910cd37a71d7ffee20ec99d6137a19715f30f0cf8b1702e572ef0ceb208760b753a69602758932156256e9d414585f41979a24aa13696e6ab05a918a28f9bd1610722889334a132d1175d2f2d007928453b2edb27fd9f81c620d4ab67966ecc00e6ede7f20da7be00f14d284923cb770824585e9f0a53c6a064e0614fa51482391aa25ea2b8cb39c367946756fe289f462085ab660788e48eb7bc8ca3e82812a04bad44de3b783e0f57fff539196f9cfa724b00b96db42cec0fb35089eab1367981ee09692fb3e3e9df63f5df852b9ead0caa075ec000824ef61c01e14484ead0e0b941a9f3ae90c550ffbebcfd62fe31125ee8867cc7aa7177b07dadee746db8470ef7e7aaf8d7820abbd00f1e22b47cfc5de5c219574b5716f39e428ed8c81221aa227d49a37174577f3bb329f872964f3d6abdf6ddee4d514ba824f179cdaa322f7cffe075ca8566abb65d6951e6f03d30da06927dc393970e900d1bd9c6a7918a8efa87c78ef0705201cd0ba54dbd3d32ab983901ddc0ca6b72304dbb84734014ccb12351a4cbdc9f8bbf90ca4582f96137bfcf87615ea3ffab23878fd7d19186fa8dacb58dee5e3c329cace890178d715c70de477d05d5d01d0ca4953bdbd8b794d38734dc52bb92a33aee803ba6145aa860a7228353f48aac4f72796ce3979549c986df57cc38c67b3a65e41eeeb6a20de301c0db31730bde58fd0a46a186d9b1739b26a956d98d300d3008629e7ec0e5c14f7d32d2f348160d651de810e8fc1d0f6be671ca1cef80c7d7ae4bff9486a8b157a6dc02ee8af0fb544e9c82d90121e225f811dcd4d90f22aba0146cd2dd6c39a387f81d801f91c25e51615c3673a6a41568b278ac39d3f57ab155bbdd7690b84d870fb31b88cd5cf18c1b9c4cd2117323f253d704faede033e4ed1d06f466c5c2fd5cdcc7b0357e02329dde2c569215bb7b93db27a34a5c0253e9d1468517b770900acf9bd1568c9eb7e7ecb915f86cb1ae3459afd86070617f8235206e009a4ad77d0f18d613039c92c0ba60d08b262b1301c12aab9b143d020121f1f2d406dd9580ad121d194b90785bb7606b8172d9fb81fb46ee2f1e1edf058ffb307b584aec773aa87ceb47797e13fc9fa6167beedb08ebdefadc7e0e4fd01785214f50ff997f1ae3d61e4b56546bd33e45415f230d1d21239ac0c28dea5785f1792644ac5c59ce2c86ad405da9c5eab886ef5cbbaac9d802e6f146bd94d87ff6f8c3dd0ad63ded339f0e3bd60ee394a9a7cfba4446fa66b082c2b5591acacfb1ed53b9809e14bb0435fb0f65ca9a60f7a23407b95718f3020e301c57ee7bf4406f4a9a8d86afaeedbdd600705eaeff483fdf58de2374bc7db7f002cd449b825d713dcf13f770e97f0c572cf56feb302e3c5e8becb919706fb9d6877e042414d255a373edf95e16b66ef814c7e9d5113713d6562a9b5585d9746b3849b416e246e515993056ebb5291471a7341c7a0be62f5c0fd3770f3745736560ab04acfe9a36fdc9898fbcfe5e3aa1b97428bb10b2f9259ecdf33c9c5ab11d6ffdfdc7d534acfa0dcb82e10eed1dcbcdb8f111f4313b29f14ec74f5f84e9217eef13789e9b4c89481c53adf78da3a6cc946d0d29b540dff77bf3619be14825334881d9c817f017acaf9c9d7a81e41c6904cd4cb1aa75cd71ed30cfdc276a470aeeb3874e0bb042951a2e8afe18a5004b963a9830f4a51bac645a5df7dc79d904f8feb5969892aeeac9238930de656fced1f82ef060468314d5f52cd040522272643bd57045684e416e107a2dfce278aa0a802674d14055df7d792dd1140cdabfb8c0a4dbd7362d501deffee2ad250a957c06087270e78ab0c97678d16732e8406fbad8099a4223ecb362628af924841286720693fb7c97721ef472fbd25f0033b1ae4fb29a55abd4b993de7cb84b3674486f6b30e82d9a6bab71ef95205c7a61ce43e866619969a481fa3c36776a2f35bf4177ceeee444279eb4e8d218c3457a6790cf1d3205c91673f0ead797ed92b4c5b913505d5b948716cffb4d70bd1bbf2755a3a67f98615be35c2fa6db83c511416268b55bc848bca5db22050b89b1015e42a1c3af908b5631f8297cb57efd5a0fc087881b9ad8875e0734d902411cd0f7e5b28a9b1b778488d5405a83054ffed1e5befa2c522cfb6d5fa7ca9764497b2abcfd769161ddd5c2fe7c219960e267649eb734fa46868a6a73482f90871311a18582e4b5e1143f18bd58681b715b6924db28b700454527f590a56260870c465545c005c047c9981397b854067377419cd0f4e61f9a92415b0a937ca5531bdfcf1966784978d490bff3e832d9a115c87d84cefcc50fef552698ae75c8153722991732cff95f7dd44b9c86deb428b0a9ddd3e58a044869ee145910d36a2effd341d085426b3d5c2fe01762c902b9e4841422ddddb902be802b4ba8895d5601eb286ce35557b6de9bc8ed34c44abd3375e99e6e5a07e30e4eaa36243a4eac78faeb9f948a4d4cc8f8cd638020f227d7479d447b71ce9239d6edae77a9140eca3058f25b4f7c1986694d6d4fdbc3f38c7d698a7cde5279c3d0be5339e8bfe9ae9af1f3af80b3fc6a69723cd7d7034b832b03f2a43ea44241c953d68059512f288907c12126c7ddc0510e6f7733fd0c6c077c104f946ff00ec1041794c9b200f8be2e0470769db159b8085176a8afa7cb61cb28efad87017708f3cb18e58272a2e9d520cfd2e529c500df23a25f615362a0b45b6c4a11fd2674a47504614016c122718c1ff98355553468db37ac579fb245892db5e9c67ba67d6a71abad312cea09c4c486ae3df9b350db2df158251704e7c0585645a8813f74fe6771f23fc95e0f3b8d74f9f9a4e9310a998c17eca2deb4a41fbb2a0255fe6e7220c180eaa811e3444346b96dd165ea6e0454321da7360289b9849e212dd9fbb7fb7ff742747a0138edd6bc45bb4f8faf8156032a602d3191a25f5a766e4802784656d6503d9498fd48afdeb186902114b97d0f0ff63a5c415297047c3ff62fe53cc0fa6a104d69bbb7afae99d36b176969db97eb55c030ab821cd5c00bf1815a02652cf1fdd9384f4f3b915db9604c63f8d05f8f9c86705d4588e08386abd4de58f57602fb976da64efd56905ad02f16b5f9adb1f268b9961fea91b0cecf9f17aa1d2002528389da601b8d2a148308c82f6a19ee71be99e0760525ca30faf26ff6136d0d14f6f95add3fd507f48261ddcf0b83eb131baecb2ce7e5a1d9dbd2edf112f10d413e524ba856d8c55d51cef1b8add21e3874212d7c2095b14e91fbc849ac0305b27afb913eae0ec8de66987beb1e888b97486a7cf7d3ae156e1564d5092f6406ce2eb119602f8b07971150b1e0692c719cfa7d552dbefc1bc05c05a0992c60be6dcd8d82c06ce493efd99ad9f56254da2d735c302980e142a7b4c02872abdc673f5757c19d62439065bf1d1576763abe230b6153358b3b318718f6d9d91a05960d6004f822ed18b40e44223d230562d4d3a49a34bdac2fe8683242d389dd589183f67e37afa9a0a5b56e2876547040ae6eeb795278ec6691528e0d2725a8804207d66813d0e20090b7c8b5069553a8747a36c0914b868a952c9a418583a260e88a4dd3e7bc105e504c6c2e710aea2866a4a587da7928324841a3f0bc17a418abecece84035e8a378ef9f42b0cf9e05c12032280b451360cb9f2cbc6dd6f19cfa3142df7832987a87a2a5e0241049a69d851b1c27b0e7b0c40c93b5bd6419426dc0431e5597294cab270fb5bc67328d2e67e85b92774abd653dc93725e1364fde8786c90ea8f6ef50163930d732b3154a4a7408c5a266cea3ee30f42520dbd293919a112c42954106df3c872b3f2cebcc3e0692c5b06052ead4d0613011ddd230d5581e55b298fddcbde6c9e3d0de46ad08c4b8b6a8a766de052e135b5006d9701abc21d485cdd42dab1a86e233c9638043135d87488ff82787827facfe339f2f19a80886bcea6688aad837e8a47d7503a08d8860e56c30dd1ab955eb81d23310a30d1ea0b1534809a38dc759e81d905b79a412cba739a45de9d69223d511ce32f079ac78f2a4e5b0b6da44ada6cd442b7862914f7e58c56f7700726dc4186c0d692a97012d1e247787d37aaa113626237dff5fe8a4a84b224cbd2fe1436a847f105eaf98e450750f1bad9b2e5b5c3c4f24e0b929da62108ea0c93107aca80b90f3a20801c11bc61c6e4ddb8f445e2004c9fbdd909f3878f296029c0869eb1d01e20dcdcc372e6d5306b26a8b02b6f4e688c5c6385be66ca3a527db77aededf1d891d349fcf59d4f10e77598d2b760222dd48da6f28ca9b7586b01f175a5a797b264a3acd7d491cef12294677d4fadcd6dab6467d441e0bd4209ed4d990af3799e902fc0c43279f58ff16038d66650bde3959cacc5a3ac98d78fdb03b2659d79df252fef8d7a427d56b042c6c54163452b6ef5824d891c6aed6c72e0b206932763006d60870a4526a6b674b6292909d5438b703d46777bf77c2e859cfec3b64cf7f4cf19f14fd9ac4bf91027f6fb26eeb9b9c08a45a8e0e7602f4e660a39a44cfffa7870ea0b18769aeb3b083c18d8d23407752b626630cde755e1b98c4826cb82a79684ec81fd2969612422451ce5c58d226bf4f7900c7e4275732b957f7f8d79033f8ff8915aa4903652153e5c5eb328183b58ac9b1ee5c7c1d76745058f04c900fbd8fa428be88d81aacdbbd6eca75e29eef4e48526ca922bbf10d2f10bcdd6d716cf259ab5a205fbda821e880bfee815ac0832b8ad19c872ddc2aa6ecc438537515ae3ab6d53e3aada18f8c4d4832e2a59713f303fa01a9729b229075dae0d41a986916b92ce28cd23644baf288782d85e5c22ecbfb75e427097c388b554fca6077ad871c33dad2cb6c801851dda07c0b9dc5e35f818b7dc99125630848089ed173acc9cc210a9432c2737dec9a73d99c09f642f34c2e1860ff02cd4e65e582c16ee7f9dfd6124f4dc56af5f7d7ad4139c2c67c3e7c6a7ea3f60aa08cfa4130a310bc53b8cd3caf9e55057ef7cf852618ed5b37cd326fb785f375b072d8b27362545416a20d2ce4a1a950d35ec0d517b0de0c8c3b9b1cc5cb889d41630c7dc8da43c718e42ea5aa8da1fe50ae9476e9064ea5a8d5c259b4bf893032e14c2a22c1290068b2a09b8188e733e394d3a294102da664f05c6af82e65aaab17f02caaba47a15734b392e558c3c1afad75c2b735c20bd23f4f6c99874981f1f4416918913ee036a655001819510623f18cdea5893bfccbc20d2cb4c86b6679815d6d5985fcb09553d408e1f600526c14b5fd951be153118b2d2a9960f6d065e20253dca0835e9c2b210b7186f9aa5aa3523e91a8e7a5d64b299a1a8de229885c95484e2c005a2378d763598c646acd56e0b9a2f8b7f97160e5ca042e17e5bab622268ab09b7bd7b8e7a32414867646aba3a092351c9cb6c0ab1860bb5c7a052b5bad6eabe7ffbb91f5ecdc76ce2b4ee4b10f08a182e4170ffac7f4864ec987318986cf9f1ff5bab0804781a5fe9c46df152bdde94f5dfc8cb59eb71cb025abd01c697b020be3f4b20c529b28fdfb8c49e6a4f550d397c3dc71be5c408fc8fae951facd4839e940e597c0256c94ff2fcbc6ded2dc00bbed22c830c91a2bc18c5f54d7c3d4d2ce840100669a59848153459bad00d2d6f5fef3d8a8e7cd8738b1b4b07d51554d7c6f7e50aa3be97882ac72c09e264715aff79432d7703eb150317ec74050bec2db5d6ac25084853daaa133b443050ab553dcd858a9778669605ae56363f685a12c4190feaab233d5543d1280592419e826371a843981b18334f16affd443437a96bc87645164215b79faa80f99f44c0774fc72faff92cf784fe959e8cc4d0525ceeb90a11683586aab628c730855c89433c28dbf15b92388d518a8716edf88f4dbf69ad33e1bde2a5ca85ecce405cd14a07d346f88e5ceaab80d02dae1b83601357f7e9086000b3d4b82e0c66417f5c3abc292b14d625e29f07edb16aff01b7547bb133aa93d272a7831a2f374974f33f6c6ab9c9d4040c5b58ec41ac44da3a2a3054246eed4fad43c7ac21622996c40b5208e1de26e684584ec4ed53c173df678057571d3f808bb82acabd5974b5854b78978e6e88f810f6d08f4b25a67a50aa55093c4646fe14cf867b1490de4984f12bb007f72b587c534210bb42ef78f7b63f0d1ccb0dbbfbf4c4f313afed7022c585ca81f178af3a90075c7f6e4e47c566b546d081cf1d1a9ffb7edbdf38799fb5d7cc946723c373bcda0add8b888ba60f1e357f67328575fdc9b9e423c5757097e351e129db75092f4ea58244aab65990fbb4fb6b705ff5839674cb9560eb27049413f2a638582f61f7ba4dad465afe87af0e1c5b06b2f638d0a55b1254f592cf066ac81804040560c11fdbde641dbf6ea607d29075f409f49a6bc90a8c6f229226c6b937f5d257d4d0223dcf2663e271e73b54cd1dceed64cb168afadca265e213e5990519cd6cdc6991a6b341f85344f63833853f2d5252a3ed50ef83bccba952c60583f882d9b654b7fa4d8ff29308830fb3a4e020e0581ccfd1187ce1f33487cb40d4980fb2c0131b24ce3fb9efa5cc59932643837da1c61c89fdd2138e83e90442c88e672420f93d75aae068c6b9ea4fcba0e71ae521bfeeefc20bba0c89fbbda6189a8ab4cc32e96decb2877a340fb84af30a419a3895efc3e62e017e54dec76ef9da65acf7df7ad5e82aa7762ac6a3b1109b173a71f31551072d83d3478de4def6d8daf2f11423276dfb494ddeb6dca0f49c1058f25787b4ea1778479368814f52f6c2bd5694acd24271466083390628558bbc46f79c76fc2b5afc5e7506d377df688d3092999f4a81fa37f4983bdcc659ee95ee420adda597487156a768dce61691ac4bc8fb0c378765fb66d9d1a75cd1ec484a875c852162ea15937595182270f2d582a4b1781aa52c86acf4a0ec89e0c63307dd97043f79130b6d2d9bfdbabd9d3aefd179135941c24d24377d28e9d1f804af4d84546c510d33b15d17773d70650462ca6ba979a9a285c9876fa1329e6d35697a7396c889e82a3e4771f843cbe4c57b7cfa26c6cd988eaa30553baa5a9eb32a93114af6a902cec237b759401af05a9700c178ea9bbd202963471ff5daa789b9acbeb6c4f2b759b2bc76ff5579a6daf43edc88678e8305ec03b0ca7219fb003e97162ce6abc5ecbed034ccacfff30c2b32f6912a9bef324b2e8f65720aa45f8914ede186cc62d7575008c2d6768e6da78b54b51ce15fe3b48a3ef2af63cdf1aab0343e574efdf52fe881227149bf5f358bfe725480b8921a67ae98f082062f22e89d6b5a5f7331db89fc27ed1dfd450e88d10f8f3516efc32cdfb8a2bb0fedc5fbec97c750599235e50947e2f9b6f7e70bd50f9bcff703bfe7f9eed38162572eb2214642df80a53f2fe68bcca6cfe3436dfb132ac559e12d38e5413e74bc2eafcefe1e2e9f00d64a18d2a46c00fd8f5528a007c967e1c8f41dbdc85f4f3939550b6225061dc5f405619beb2bbf2f09ed606df9123f07a41288e5cbfb831826d108f7bc7e98f0f20f12ff90569d05587330f9bc48b20a9396d483d73096fbe53d85b816b2a175e71cefad95bfeafead1ae126ff5e240c8e6dc36b48f6e64b97f5950ca8e7587dda1080b8022bf40a2e30a7ce3e50f48984c77c64171032e3b0eb56b79390b662c565f9f74586fafd61c3d57b44309da1667e5ee89cb84de6e5b83b57568fcfe9bb3651a409824e3da8163e8890465b104d78933ba1f01cde6d10ff7dc889dabea9f620961a806ac0115ef2525d44e8e939834f3fe611ad75974d33a64b215467bc024be33b70896bdb36b565dc548f1b5bb16ef65af6d6a93f89bd98905673ef108eeb2e54c680ad2808c58dd534137e2b05b929108620081f1ee448e085a02c395049ce3f153ccd0ba2cac394535d2a84590466d6265c0acb2fade7f08c9bb85c7cdd05883396eb854f1d915cf9d86758c4a18d79f41464700a84f6dea0ebd0bf0efc999b3b6adc4bebc09098a4357f9a03880602d61d08699a03beb7bb2c91394001498bb877dc40b56f1c2bdcf66bdfddccc558066dd2cc9c5da1eff3858909b1d817742a54abde92727f8806a2d762d763fb51371582b3c7a424bc6e2768b9984699e3bfa85f22c913de3baf15d5e868bd0fee93595bbf14a695b9aa05e66b09a9284ae59f383bc84056dc5dd9e001f866e5972d0d6481c1930f5af53c9fc56b2a2d139965600f06b2248e635fdf9702420134d8e197e7ff90ae6287a7b7b538662ad1cfbfc016cf36d36810fbfab62a3f307b3a01ec8c23f6599e46fc933298f7aa00daf3101167f2f8213715a07ed989269144cc42282456de26d2843c50aba27ac0fd1c9e7d1be9102547861e4db7895969f9aa71ad75fbb39e6ce456d3e59c02f5983547685a7e8ba0be41eed124cb55c2ca431e576c35a9a0f610dc924cfc9ad4689f56520a8421f8ae590886ed03d722fe673b4bdec8d00dcf81c86a0834eeb25acb9fc331cf84704ddf78785b9765e370399197df4a03f1b6425caaf070b77485f6392cc25c2bff31ba0f7541235063e98350b4845a3b6eb94ab4f7f396274f6db21c6622ce9847e1134c94ee76140330fb484e133ebf4777b1b0eb8a4e17c3afdddcfd1aca0b87ca5c5ff8c7b33d315c3e6c6b3105278042fa4fd8b1a330a50a4f85319ed32c4eb388c75ab3a3f24521c68bc2a2d97ae2607294705e454381e2c072a6a730d63dfce7d8b383658168c90a758f1ce1d82fde2562d6e7213959750ee3325dc9c64872b8c69a02d58c225723a5105bb38ac435cd40af991541a24b8d9980c96f0ef738ced223b3866984c92725e623437d14d09fe9a39687a9c438c2930dfc95b2677de2481a1cc2e279c6ba08118ed4091912cd243a1212141579b234de1b09306fc7ec235fc29e4320b6be34d4dea6ca25bb1833255aa62d19884bca44c1791afa9a57d5cc1449993444be064de6cf6fc48e2a960013e4f98a549a3d54a4a650bed62907bf25d1904a28bdeb3b414633f794fa7a78ca2dac57696c4aa5eb13e6f36faa19cc71d70a81fa0c885fb53c3c5ee9ec8ecc2e7eb10801871edc933298fe4080defc4b01e03300c54b8eb333eeaa23c59abe2597f141ee2f3cf8065f963ba9d1d0404317c8958ffd3e3e1ad9b9ba580d7541a1eedd4f48876094dd09aa97f18ecf4d0c02773994fd1f80b97440c1fd87b27c8314e0b365008cbc78a0fdc3cd645bfe65ac0000942ed19511aadd2a93024bead0014d00bec88064afa883e2086caaea465a38d2f687f741114740447ab6e292518c10354af6d87b1840920a0a287fb1fd8862f44087a3331d836c59cafe400144157fa9baf60fb63776c0edb266ac3ca7226138ca5497b17feb4981f147948da095892f1cec7661d324df516fb197a88d3855d523a27e1ec5c49f8a306a44b45227923584d3ab6cc7e7d6e76ccb255f34640017b03bc86d583c477413513cdd5e0ffe88c9a57e8a882793e2238c75ad86fb8bc1615cdc5b984fc7601eebc028aec29500e2e300a812630ac7150392b93e3949d48af372e05ae53c85ccbab2119388fc717eca15000670a4b116db504377c52fd2ac1120bdd28644613218d1127d45e36e3fe4271ff4c392c0a1545ea8cd8dbfa16ca79640a0326cf4e79d6ad61ad286b2e16f38f3591e3ffef7d4cba45b00e610196bebaf6df228a091eee0507db6dd8d14a06da04d820f3699bf49e85ff86fec39541265008a007b06f6202858e2dfa8137a9c5b171061bf67d1630afcce742988582e2b81e9f1bc645a41ce0eea0a4d7c85f58cc0496183fa8d433ec1485ce4dbfbb6886800a4e2b29534ddfe7d9d8e3789e32c60f01a204eb783735c09391f4183d04283337816face081b77e059357cd7b3d7b00197c80f0b28653b9b35fd408cd8ed3768e8c758f500034d2e92614ab953ed3b3e6667682c819b55bdfac751ffcaef18cc30179de50971a8666d27541186eff156f1388577795d8926726dad6e43a569f7837a65f3dab288f7b78ab85b75d4664083b508ab8b1e830e7457c6938e5adbf79960d4ab5728ff3724720f583a2d949e5bac8e4bd7fc46522dca8eb8972dc794d9324631525aa7996d18032b7977c13ec540baf4ff170704082b397e42d04e1db9262b34db415df4f751501f0be8a417d65b019bc4178679f6501c79f880b404e46d6994ac6982053b5cfa503eff683684aebd27f44513441a6307156a334130cdb03ea0b311408dbccf57e5776f4727855a7c73da5d882777c305ce1597e8ab76d5001c4ad1d1f1dc78bc0cd217d89e82e9d2196d1eef9e6454cbc16f4074f9ec9249afdfb4ff4773f43ed24a136b4a8f600e8268d207320ae42798681dca86ff74d9716ada009136b856e3a73587eae5d216f691385ad5385f75c09e5af1daf8c76263a08371e4ac5ac244ad45297420d9888171a16db160c383a46600c0b3e94c61d0dfa151d9a84e54f1297d535d764153af63f3f0a3a29ccb827183698d923f16cf995a9a4c54b95b50c53e2e17f531b983a0d3fc27122f8bf26792c1641f3f6df2b840fe080542157e6a1643be66ad315987f07bf03c096c18382cc67b4ff2ce7b66dd2514c12d39533c27bc0272865133a33e514ee395a3946cc3ae2ab40f54a23b994c5b8eb42740048d1db05737e92167c834b5de1f375aef1d67c46c045db40787129f53ec14c81381831f6b650873c472334a706c0d7e61ce41d62797b247a4c1f4bd39899123dbab0ec2f615d1ca7edbe54645a29ec5ed3898c65015700453a9c7d22fa96117af736c0154789505788abaa6e91bd29fa3b24ab8824f3efdbb132e28657822aad1958c909e7c7a25dc58317f29302ba614cad37780658cd3d34ba0427b54a65642a6c7fd76599d5356f358193ad259e672fbd7db4589de1d5d5fadaf18598ef85bb3a079f116d278c52883e5b41d9e9f70f3f9332611d60abd954e5d6e9bebb8a1af03399215ddeaece586f535e5c85405ee3b0cf64ebf085e18ac9b0b0e9547ce6df80b8d524cc9d3cf1093b3fe438d03f96502ec4eec90866d787da991c7ef2f40bb95abc52a0abea4544e1d4d5de9b9351b5e0c8fcfb10b1d86ba5e17c3c515550b4dc2ac4e5360832ea32b59ccc3acf55accaeef69f1fd7a45c15e9152d993bdc2d93b922c314528c0b6a0b34df37091f6d07f5705b7d6f991897b03fc6f372de150ecb74fc98bd8a0da144af833c6f76c9402aabd06719ead4db716a45255adc990951526dbbbde70103143ec769e37fcb02803490fca591ee8762ee61a5bf72c1f700e60b9a729d7d6fdf1ab2368300e2522a8d7c9201e4fd9ee966e95a32600b53b8d0c145de7e3fa715951311fa38dddcbd7f9b23666659905f0bd54456fd0816f621a2d0f7ea489559d3ebabdb2b821815c43f7aee6d5b4ea86a7c04a3ebe679352bf7f1b2582e9f655a2fc83c338f315896544dedcfb5aeb982784ba93aae2dc08c2b77aabebbd5b9138a197f1b2140a8b6c9a2573bf7175572da1b00e0d78f56ead1c1d912a02b0e5f73b96057446dd9aebe00450495f2ffbcd7d5b7e9f523fce13afb849e5de19e77fac22518d08910c6a5e457986838dd1557b796bcde0e84e6450fb684c8be78c292c36c9a62c7561f4eb9eca2209e274abf6a83cfb900388e3f1d1c997faeaeeeed434bc8c2da97ecbe631adbe7fd9a05e403d3aafb407da57cc788985bd034d34a5b578904b409072e170dfdc8ab3a720824e16347ced0096878d4a74e9a7a934470708636933d5fce8a28a1af1f42e5297d1c3290419680edd1a368d237fc6aa1af5952f8380818175cdca90407cbfacde3501c757e5303eaf1d75a07990a3f8ff0b624c2f6e304ee91395459866525491e70b298a8183ed41ce7e74a304815add89f7699f101cd31fd3cd8e3fd219c69973c8ad4a7deb95eb50ce15fbdcc08d1d5acf963c4961cdcf131381248e29b182332925044245867972ea3dd5c7866557df6d1a96e959388ce819837d137112a1d3f0640efcae117c2f6062d9153d4bfe406179129659addff7704fe5da68f8384495c78e10c8cd5cc1a6f0305c52e1116954dce9538382914f47fc50cc4a0f8de20f24697ed02201d09de442ffcfafc6ea9afc80ad15810fa3a144a24e7319fa3d1035ab44eb5b24e0519e6159df0e697fc2a752a3aa44c1f35bbe3ce18e03e9cd27f1abe2fcc7e7f3a9f0064017c727d16e62a1156858f1f9b93b59e753522186ccdc78121f282204aa385ffa51094d2debf461481056571209512a78ad69b1c77e0099034af9bc469f6359ef1fae79a65e7d9cd08ce3ad8ae9a8984153f51d8d5706f4609c67d87b6dfd935ad68af103f1b7c1b6b181dfc0e83215222961b7e37dd6f89bada652baf6b73862e747d06c81dd18385c0aefe717670d2acafe977d1461a8ccfc002a6f7b5c8f47797e3b07d657dd62200c6ec529edc16ad7b734a528a57231d086288ba7daa0a7f5fc63e9a1c6188cf257a6b4748d83acb4c2f4ec96a5aee3267da201724f81dfadc3df3eb1d41c40fe4240f6084030a821ed4138541f1739e14247f71b7159b539c682bee90eff98a541df11887c2a83a863f2205b995fb6b0601d3b5da987314d32b375627e529838cabe0ec5b7b3134893028c377f35f989503af09e18a06eaff50f16f0e99b4c02eb8f3b0a1968f21e29454471c748b86d825e8fc381de954ef44a1f1ee08cf80aa33d96035c30534a98157ba9739367dc300e202647fc3e847cd715bd83efa9694fb7a7554db9f7a36777a2ed6a9a670b1296618a2c77405240223847b3ed59b0d776e318c5f5473f9e3c198b25bc46e5184bbf0b7010a1e2744c3148be81825987e656f07d6774bf6fbfa0a8455575126edb4f4745694439dd35e3b910056c0ac2ce0df2fc1c0255c7f602cd36fa06b708589a9f6778e5f6fdbeac698a05f7afd9d033f11dc83b59ec5e1287aa7755785cbffc511230e995b06838f75ffb8bae6114ed020218128caa764cf5bdd902a882d8304892f0b95cbf93cb962cd258466c247c308472fb84bc95538f0e8a52f2a74b303dfebe02d42d9c840f7e2f3929858382cd9a69541296d023017bbd4114ade5edb7bf1def47ae7bb522276537d2f91709ac83c6f213d2226b440ae49a796b1e58b89b94ed7506928628f5a9ff037691bae26b021b752032a16dab0b43bc5a9eac84fb6a02e92eaa116d15baa22dc44ed70ce8f6f5223767edcac19af008ba98f17c64ec5adbd4497797ea9d412faaaa336b7829dbe493a9345bdeab9e417c1513cc6974f7617672459745de94e3aab2a0ec67fa380977014f1d7e811ac13a005d50fd8d9de8722ae9d96d2f73785be3aa9fe8aecbab649d9c428eda1148342bdaa2c3d04cb7dafb8d876c7db3d880985299bb7bec2f379e983a98fa37cb6bca2699e74e01b2be8adb31e7dc8f4598d265fe2c9312530a91e58a518c14eefa12d513f4eed71f2b75e76e81f9b9b288027d89d66b4195d29b9e500fafa63c9824f3dabd812077215d6b90726bbb911e28d7a1230eca40c7b5ef83f715f193dd23301cd0bb084bcbd1c9199fc9e582653dd8ba752dedba98d8b0387f8e9b749da7656536a0f0892c119e858b16ea6f40bbed217ce6094adee4251fc289138b17eb66021f6f966c9ad9657da67210f06407b795412532c9e9423621701e7c33c9cf750df29bfdc28908df149bc1e43ab65bf5a43db84a98b9ec50ebd34d9b744d25cd10739331254de0da907aca64b4fe675c502f7de612b81e1daf8dab0351eb0cc6276bcfb7f3b24a2076b03a5f872da75aa686b40aecc267ca91efd0fa9244b86798f1918a315cdef612a94e10e8bb3ec4bd9196f768e620181d02d42adfa28154b65dfb4afc6cb5ed23e213a4b7dcc68204745576e80d5971f5a316a9c08a3d439d83b9f40778b4bdba9d11c635eba7a674b91043534605503cc8899832f345e01d1c11845159ebadfd744c3b82951d3e845824e6e6a32bd634ddf96654a1b39d83f4b67ee29f20fd37f88674c47ee37f0e15fe19cd97f73f843d0e43924d4b68913035aca09df9c0128efb5abb9a9481103c9f72bb0c64c3b393e3cfe3853149367e14b89203cff23c8f9020dbe03f31d8e9777e26e24898e49907f10cef300348fb7075883c54417d6da1bfc0b6688fe50a09b1323c15a647ebaf2ff0afd8bf95da7356c8eb02e7839a08c4fd36d3a602f3daa0a471bb8afc5abf83c273834da9b5f24bcf845e9a9c85a11c7185237f5e6bfb78ebe18a3c38d8be7d379bcfd18111384081492b184839edd3a40ac4be2fbd446aeaceaa48daa58a305c116d82b3b6b0f5e716b1e7060205cdb8b1275a1a3252b3c622728a4a7f1f34ffe03d3b5c6452b82ab553a550ec5d48a7c1ee2a35a01b5a26bb2223f6bfee8a106336efe8d116d0ce739e3c533c43213fd0143bcdd35afc6b96ce1458dab6d318163d03b20cad9a90541202f49997f1fe10037c2c08ca3091173092a66d2cf0a171a01ce30031e03ea55f904a8184342d1f6256fa2a882b624c6077ae4c8034c7fb3787f3ec5e210a6c1cd753d5810d614a117f4a130523765881b3060c664afe81fdd18b8050b1deb0e1f07c5ab88c608e930afc23a772cff55c75066b43e850f49e01c84b77de720d232dd2f6d0ed6a75587db1fd96eab7d76fba20a142a26695a365012d2294c9c092ab1da4ca5c1d6240650270d6ace932c5fe4f2b8aa50a14cb9d5e3590c4a6ef60bcf92745eb8403e7d251de7c42fcd293fd59e082e9d50d32d22b73a877364025599df95f90c5b95c9e0c247744473bfd15558fdc55ca7d91bb838b37bac994b4966fbc9526bc50ea7d8681d9220d0b4ac5bdbabc4c5cfeca77b80314c1a262631e5fc8bbb62052f7e4d05de8ec1106aa3ab383d734b3dc1d256ec500cae12748253215c95125b7e0be2134c551b5887b2ab86f4463e7bccba11e88272543ea9f02198b856389249647221339ec61bfda6ae29b529a5a8a83c2d0134b38a6808f247de453b44a50564f6ed94992571c425d147fdb5578dc4ce22ea669a9b47465a212d67cc1655d1d3259e03cb4912e3206201a341cd792bac915860c9122a949eff8d8a87ec87f8d66d8173c215f890fe2930b834f80617c46c08259e0a8a8a05dd76274148e87f9ba925e13c67c045c2f8dd186d97708b124786a4c3e9af8b85d5b0caa039aa20688582050334a070969c2f625ad7d174140318064e2b2c9778ea1825385a60d933a4d9d3dd2312d3326c1b05d438a8d7737343e0708c5de722a88dea196a4bd5e4c7dc20c26ffba176ee606ce1a69844842f6de64cb2d654a49caf4071b09860846d73fdca4c4e2cf5421452661fe7682f8f38a3fdbf5dfa4c41fbf1efd0ceb7325d422506f4be1c86a3f07bcdcc7684fafbd0a235cfa71522a6f9457f3e46f2fefa45d90a615612e7449ff6350fbe817e9f955f72b52b6c4302d2d7d89d52e3125252e49b58a8e2a36f642ceb3cadee9b4b1d7759d37b3174ea514c75e8b41db59167b2b2dacfcc24986594a6696bf73e509886d661f4fd7effcf014e6bace448a3f35bb31fec4dbf4f3a5a823bfd8ae389384d8ba3349b779439fc129c118b36d52b27b6a4eeaf6cb522cd64549b26c4b0cd3d2d297588cb247919534d64a523697db493d8abe4751caaaa4bb94734a29a54bf9f26b05313424a34e441161e5a5a17bf0ba337ef4ea97bd8c50eb81d7b5c10b97e4dc7eff188452f263ce919bfae5727332e1b4a2cee9ce57d4994a5e4e12562ad1971c532b686f985cdd618139744e4f08834376401180ed02ffce663dd193c71a6343241668dd194eff0cb204bd9cee885773c03a2b75edfb99f9e495736f0bb4ee0ceddd234eabd7462283dccd6e9fdfef9ff757eb98eeceb2aede9af695d3aa1c71641f876a6277259465325c32956ed6869537cb3aae5e2bd8eac986f4badc3f5f35147fe46f48481bd2ce8634a50f124ca4d8a33da5255ca6ef4bf1a79f7ed26b62372ce16e4bede2705b922e4ca0ada95d4c291036b41b1219aa219b97a5d4524b2d67745a8a3a3e486025af5670f4a10dd733cfe11d2138377641ff73ceeae578fd41b11c83b2bdd0622082fc8515f0af5fc8e452a069e88ab95141860eb831d220bf090cd6534565b1da516c2618e8986602b7c256ab436e4bba9a179ac0f9707976ac23d03fb1883c31cce6beb7feb2ce7a7e8cb3bd1b15babfc98dd248df2393f338985016dbcf9cc70949b93cd42e7ece8708721cd165ff3858d49181ed1c941556ca8d6806563657b4c316776c0dfd0be5f43ef4e86d4f575842da3e1683fd37de1b2bf694a9bcb0a17f9d815f562ac29c9ed124b10b24ba40e2d2e7bf49f2cd70739ac93871e2843bb5a8f685277969d3084aada9065e8e75abb5dc67b52fb4d917332d898f5b3f3a0e3ead6cce59ae45a9a635273b239656ea316bd6e5a4a6bda6d5cf4e9f9db4df8234a979f6d5efa285686065e8a0d0a494efb3c64eec9cdb41517dde07c768cc971c1ca33166d0c0842d36944d3e516e186350d8169a463a8eee96a2f479f0607d804d3a00c59599c789b1f2ce8dd34e46a894de7cdc7eb94571db0b555bfc53f587aa26f7b134b75cff8945ec01fbfdcb321d4717ec1e2a454a08ced0c7d15329dda3da125dfc5f78b9ac0aa362ba3e98831dd5db9f2a29eaf060552f2f1db5ccaf6de552f4aef46a90d7b2944e29a56d6fb70a3e76609ae27a7f4bbceb3e76609ac1752d3606918c2deed8ecb7542dd2d52f3c3da7037b53bfc9ad9bfc224c396a8bf4dfcc5bb2958b51fb6a90376cc98f5da6c2d379e97397c4c71c73cc71e97793e436a1c0fcec12964f462843a6bfd5b2aa8c88edfd0b5b432693952c8f2492c4a2847d8ce04e3abd4dbedae55cac07a5a347ff9e411f2328d214cb931b3f5b99678977bd252b6fad2c5bda74b7f6f127fbee27d7a7177e77fee65df6c9f62fd245787a4bbeeb4afda7f7a6526ebf6d9e6b9566df7f4fb56eda76daecb659ad66f293e66fa97e79030e37f29b5c240cb618e42fa440466fe8cb1bfa2599973efd6ec0b920f8a7d39f4ea797a793a769d76517fa2f695d4abba024f3ce9fdf0d1fab3b81a5b09d3f79aef13d8537dcf9fd5b7b2c37acbc6e23f3b8c77ddbfc6dba77415b6f35bb9ac7bad59bc17fd2566f92cb4905b11307ad5ad8ae8bc2764aae0403473f210c56185a4c6cb88179b6a72709050419d46ef0e41366ed13c284c11783ac1bbb2f1c72b9566b5bad7f10dcd9e1b07539268e8963c271bf7194dea840ef0cf4f6a7856d264cd8160ef77467c8e9379a7d3587c12e0adb5f8853cbd8624ed81ceaa92a9edad55907dccc657777b7d6adb5a67d4ebda8e5a26e664fcf3cdb679f6335ab3da7fda97a5ef6f2f41f75b4f7fc0137fb8fdae71d5326b6b8637340213b0c46e9d2a594d2a5fc58380cb24fc8bab118240e0c0383b18c0f837c83c3da61904106e5951eebf2cb8f81c11b8ec19bab01bf33c4cf73ef6e3adfdde91732f9e6446043103cc37255d4dfbe3dcb7d2113ee0c2b2b83927e437076b0d98760cedd2a67b993e6f93008824376b0d95187d989f1f4ee656cc3c5e1767fa19fc65879b323d67b26c123a594e2dc933743bfb35a4fb8af5f99e7990406b9cbbe1c302cc72009b57e563fd9ef8e44ec0a766fff26d7a5f469ddd04eefe9b1f3d9deddefb7bf9bfe26b7af5825d9502ad9a0171894c2c6c86c71c7f6cf67ba8ccac286d6a77dfa0be73b18bb13bf36b3100bb1d0f6697ffa7cb4ae32e47c426b7bb27d6ab93b481748e0e07a2b49902e90e8e2fa7793e4faf70cca2cac6431c83866e05c28ece28f5287b83ce53253e14f89ed6a6dabf5cf2fe9800f5831379442524cd4e98c8498e6d8dca3cf1e4d5d7984d2f78c0d2baffb8cf1f36f7e84808d59acf87277dc98e58ad7dd525b732736ec4df64d6ef685372a645f3ffb2677abd5025d508d778b52d220e7b8a1766384eb3d126fad5fe5c223f1ceb0247bedeb6b9f05b2affdd991bed9176b905187be0d7e63d4a18f74e595b7066a18d119b2efab0ccea71f2f777727c76dd40b9b5cfa999cc3034e2efdec957d8d3fce511a2fad5167fecd6d35d124458136532c3377ee728e733de7f2771e6a9b9af65a17e4734e794f1d2f953ce70e2d60acdc7e09eed002a689db6154777779fda372e50f434b6ccc72c51097a17cb08609014110886c6afa4194fde5621cd2198dac14934d4d631015492a606ab5b6f54436637efc5f2e72fc9154baa3d02591b24916e44d51e8584061035a11800029a880cc1f29a4904290118bd3fa31838ca49cb49123a83662a40bcade887644850274cc9cd586354c3635c9264d469122f04f7f558ab1b8a4847b2491117a6bc647f46adf0a0c9db2294afd4289c57ea1bc12858271242a48a2ae081054e0a22d4015c3bb00a72449b4155660a112492a385c802c182063a2c9df741a808bf187baf76f07e0641fe0005d10fd037447f8ca57c2714920c3719956d9800ac47d31babe7a67a54712a4409428e982e62bd9f9788af5c3141fb58b67c7c51fcf195ca9314f8c94446d227220b30517ac5c2aece22712c18fcd05175c40c00b8c000420e0052ec69f9800f953f9a8498ac02fc11618482ab58bffc64911153eea1e69a54912914770d265a424681311111010fcfdc40b4081bb9d43a81b27acc7875328cae5704a99435c66ee02c5e520807ac787074ea2629871c3c814c9c0a008f1185944618d85f8c42c91c8c8c10582377e19e35836b1803b46f1506c0b86718a9991f83989959edc90635f541fb218665a7dc84d5ce605f36274c316020228ec28977b88262a6a232f71dcb08f86000a1b0bcd87ad256ca5eed24b34b86133855de6d5fad0615084a494c1c60d7dc8a7ecf8d0a9145df62376a4cb66c8e0869e7439742e976f3ef42e97713ef42567ea9e0a1831868d28acd8c11c4c00710c6ee84d66b871c3f9ba7c638f2fac5b2e8f77e6c9b93cfea60be2d16a593378b838efcce37371dea60bc2b9f91d5dd08d4d1948d7e65b5d908d5c010a5e77c7ebe88276b4ca30c16d7d4d17d43a5d1d4fd305e920e1d658716b9ed505d5f4b8343fd305d1b0bc007159bfea8258ac3bf3aa2e68661503e3aede76412bd5a7ba2015786d0c896b1fd505d9a39bfaae0b4a09b92832dab8a8e7ba20140fbf6ef75b17d4eddc90bb1c478613971b838ccbf5f4a0620868dbbab8273176704f5fbba093a67dd605695cdc2a86d0ad3d204c2184c41050f635ce2e28a35d90e69c4761f0e07a77f3d0ed21696ac0d6daf82d11c617bb22d27239883f25b284a1848fcb8f7a66c1e51f12b90ac618305e70bb083042605bb69c656c3d0badbc2cf143156fcca1c418403ca3b8ac45e806979999791502eb137f9888eb70fdf2e0b26e7f2b08a37e2fb082e6f267ab127c41c5e6f2d38fcafc60002a250df9e2c9e5f7a21c2fb6004389cae5ef6f08bef1e20514bc2201e3c00423e02e7a70996dedb848da61154213493a4942a08258111292e4821d2e26944248baa202abc082246901e489972b298ced07152630094c078211474092da40e51821e5c716271f3c3104c21610a03e288872b648497945147db0c588fa49107d5b98a8f15104ccdb5205d7c389a4dfb2850712463eb690d1e1f4d822e6a688188f2d71b06c9058c2d94204da8e2a45375b4839b58a6cb668814a87112688766cd184ad8142a955838d6689970e1b6c92655b9f7dcd17aa191820d16c81466af5046b8b37ac0a8ad78c0f2d5ba4b42262952232523561835262c86ac1ea88525eb8238450696c4b34755cb8713a29c1713147a75131daa06cd5cac904accc89212d0915258ad52b934828b3e2e44728d1a4541b69626892d1b1912a4b2e46254f9f11245eed8a5566e12207279360e21c7c51424188a68bdd718555864d7541b3843d7d29420d1d3cb0db8d5990dea8d1c1d6273529b015ca0f568bd26a810ab854a1f9018b2c3a446077dc98058b3182c082072ec802440eb20c61c5fab8314b16221a3259ae3823cb184e64d141cc763766c9e245258b1f4060716ecc9205114eb2904192b53766c942cb51165d586f64d106cd13b6faa063047615848e216cca053bb02c18b4b6b02b2b6adcb09a520d10b67af940182ab4e8a05584f51bb368e141cd1a36bb318b9638a468a143182d80f892f445071b3c61861c191a5bcc993b49220cc922b9a16295acc0de14965f0a7110fe1e197bc3753e21369224ac926f0506b558d9751c34d22fe2edc04659c23cfcf18d503ab9348eb974f68d91468f0736fc4bf97e0e382453552c98248de4ec82589e10c5174b5c910536c615a070be74296576b3f73922186fd8b700372813c178e57762c3663726a157ba4b5be517a6ee4c925dec542b9c61c4609ceeeedd302a8d105dfe1ea301164a3b51c7ff8c70e73ad3a94981e504eb618cfbb0035a4bd89c6c4e4e7300b5ac95a1c7155286cd27cdaa56b59a512317f2a54d8e4d8e4d0e67302ac92286c5dac8857cd28c66450c8bb5914f3a6911c3623e7d1631ac958a98392a6d7258f9cc4c45ab65fce244841bb3ac21c5065804215f4cda25bf29862525fe45dcbf27ee8ed10d5b50dc107ce2863e4edc302259b961546ae2c240928005656891021b034c995945891a24e10226a878a30b12c8a58c34a488c11011670035e161290a2e8e5880841b5200c92fd23d44b8f4a0854b083e800517af0d356440061756148101921f43f7f85021051b586cd9d2032f80e433e99e0bc8e1030e52c046135978019245d8cbb06af33f00ecfe29000076cbff9d9d18e020e240ca2938985918a71c682e08a2b20da2d05237666142e2099643dd2c7aa58d8d4ea159a184e49e659f0226164ca61168dc8d59987a58030e2bdb0dd6e547bb35aba5ad419d7e551a51022951e8cb583f02b518f41e1ec832097e72e2f329fd6c7e957ef1be9412d0ee28cf6676e766661e3ec438a330c5a52bbfdc78c4607c1ff221ca342971b152d4420d45cc121296293cc4af325d948cacc82852886949d482444af939ee3c6076c84ed449d90859f9810cc63f22e5f7dede28f9bd491a9bb329657c95958dfec6c3074a29a5943d21bcbdd798aadd878c03912552847eff420e2851fdb67a1e301b195c7d72c0aebdf53766c98d5abd5c7d31eaa8bef006254b24911c2942d8b2a9af8b629ef38468df9fd3ad96b52da3332af4c0a5818b629dbb1e2ec9b949626ff6f65e262ba71cfda7eb4bf167f5f244c6158a3f9c7ce21ac59f8e3ec7e20f8abf5c58fc49b514b228fed811c0107f542fefcbdf4e277e574dda59f9c4eda7a91f6e7f86e22fb7bf6a5dbfa6e246703b7a31e3f1afbced6ebd5a7d2a0f86a8238b18c6b1a823bff3dac885a28ebc1c268c5fc82a3e84eca5a3f0205a2535317e784131824a6e7e0c69c8dd30a6715dba75b2857d451ddfa454d9a4441dbf36c6a3071baa024c44ec51c243491c04c21cf66ffd88fa70e8be6f97f6da570635d9ed60fb37d985412de5c9254fbe71c444102d1f2c3bb9da87956ff42290034928cc23a1307884b5f79a6cbfba0749bb1ac63c29b4f6adb9d61a6b52d3345902d9d3302da5d9975ff8e38652e8976815b10215f8c3ee0020f5f1c81ed4777482927e0d31a87d11be39c2321183dafbc75318d41ecbd5a2ec89ede20f57a84b83bcf2eb2751477bf79a87a8a3750fd2a5bdd625b8dab3f6b11b882b6334b706a7415e0df035c2a75dda4ebb3c2cdaef08f110f3acb071f752a9fb64ac5dda6b1fadf6a92ad693abd3e9c667d9d33a4897f6b18a4d29d9c181baef0e09835aa7bd548a3fdacb2f624f3b892eed77ec50a9424d7bee5a60a02e6a97f616049a12e6d19c441dcdbf7e451d27a4f6292059817972b46f869998b38e355ab6a5823496a089220d292d26ec766396347e80238d570fd07083c60a1a6a8081c6179a35d0e0d22a011a4854a0a1440cd010d291c5d6c041f3026b13aba181e551031d2ab037443a8491b24318282408e38455c6cea891039b22a3a6077687152c39aa1c591d37665942014d0c967860656155646af00576c617519ca1c5953364b0c311347058d68d59ce1082e98c1d689090a3e60b306cb801460c0b8cd56ecc02c60c31c078a143073049609072a0f445777b8d3ebbdb864c7737c860773737ab6577babdbbbbbb9babddedddddddcdb9675beeee73ce39df9fba7f3eb2eed83d5673ec643e3e3d8787d6dd1de3cdd61ddb462545e72d54d5918a5e33a4db864c77fbb8f7f4eeeed9aeeab69c77c7e83e33c372ef6eefeeeeeeeeeea6e96eef6eefeeaecc13634a07aac575b77777b74a8a2137b2dca7777b77fba4dddd3234bd39e17477ec6e1eddddf2c7dd6b8fd8dddedddd31bad3e7aa4f4f485777ff6ab7777777659ec839dded1fa594dedd0dced8fd03a5002b42f0e3f531c6e8ee292a373d840ee5462c4facfc69b99c183ff09927dc4eed2f7e21921b6d7ae0b9d34fc6eecf6ae4819b3e6ccac4d843e371e23cc618bd324f8c379c8d7b8c3b502c77f76e1b638d2ab61ce21e594972d2a4ce9342fcda04f71d1b322a29688e94eedd2b1a957b8c562585bbbb4d9946c5d8d9c877f7e28c31ce289f3aa55c8c9bbbfb8987d6a3ba57e609637f36f08d3f83c72c46cab9efb42b7e77446dce57344a299f6bcfe1b60fb89452ba577707c1a64cf597408020e941e633cbb2b823dac49b881379c41ed147fce8c52f7e10732208f1470412c1e88a41a20e107067043ae79c73cef946ecc960d951959d2340229e9ee0f26c52c134984a48be62cf3c224ea2492712338375cf9cd9cc6636b399cd6c6613364508124200e68f0cd68445942d97bfb84c69105d4c8f684691a0748274073a82f7a4ae146ba54b8689a98c373d79bd2694390390e35a04e0488ee45c3cc993ec073e6907050673219772f945f0211e58f94ec5899cc84a51d1152337c2e2477e44c29c73d24927f56265e7755e2f714c973f009d111d3b19112bd34d4d1e80ce9da7c7e79c73aec6f451235d7e9d3642a38d9a4b2775521725251f9f73ce39e7446a171c5d54e4d3a3a794d0433d448588e8c77b080699b71f229ce374142226f89c0da5610cf237cc041b2e33006e6a7ac2e0ab077082d369538618113a5e5a22c3c44c279c8042113a990b27d98849eac24a4a5f388642111d8c858f8eb408e91809e97ad339279d74d229c44a0845455798ef0522400a9d39a594d239e79c192c834d093234448589882ecf39e79c7382338d090444801482663ba594d239671973ce19c6a4934e3add860d8e02f6c7423c044b91325ddef97a312c8515992026a6324d4146e69cf38b8cc99898252346ba20fa468e20b13e299d528b445ab98174a4d5965c6452521725a91453367360393a42a202aa33425e31428204890a4956684a6996cd39e7f439e79c3383492293d299cd3173ce39a3482273ce79a569ce28738cb452248b92f89c7302e924519215a45018425386e4d00a27f98405f98249180b9a0d0660c284c30d063880cf39a93088c365ca7600253ee79c7312451d7e07c020430c4ab8ba420b485e5032c5ca165a68a15d40804f4a270805342148e7e3934211175ce882e8bb808017dc27a5936597e849abed9ae2e01404bce0734e21075f78e1850428c0889980047441fd0970052cc09bce6933c72c9af4a87be66c924426169a34af50a499c46c9a46cc6c85299c93f02189273473389a3ad505b42e0b3283ece48c403d7b50935a85239b61b4087618d0d101c3b32a2dba764c40a33b1d0bbde35f9a5c2e9e1e3d612ccf1f1b22fea5758464ed539eb4ac6e9b7cb9691ec2004ed0d121cc20d3cd18e2dad9992e0404c106b2c6f42f66b4e133ccaca7a8b365e0f6cbfa6951679e34707b06692800ad0840801454ded4270d436f5e4750443dfdcb17ff328940e8528053126d051c2f26c88201326f03d008ce8f9eb5cfacab94958240a84f1a073d69756a1b00126f8088e0fce173facb2791cfe92f9f44f3951c6d382d666666ee241e4bd8503649b91c65cbaa76dab80e95b2aad50c8ba646476b87cd0d0e8f1e3edefb3ec801e10710d01524040088c04a34edb401a6f166b4c2b05cdcf8595c29df883d0d83094939aab2730448c49fa015c4657672992551ea8886d56a6dabf50f823b3e0d3bba01810ee88a118b1a344c42893da88f328a1ce24639e5c647799dd71f031ff151ea08fec24356e9df1044c29a82b0d04176724618e1830a7a8b033518589b3a9d91bef2a3c01bd6061d265d290b7da94e0004c03141146512b1877b8e1f1fc9268e180b0bc0a3adebf3e54ea782bdd37b676708094fdbe72887aec02009610736c87491c0e3a37724914f4ba476c5e1e2e9d193a465ac3c6eafd42881f3127e6e8abc7f6c8810316107e53e9b317484986082090338a1855c1ca63357364d77a7db1546d23e213484019ca0a343984162cd10edaa40a2209d111fe2dad90151e050e882b22231c620c30bdcd3b08c7ef45a3be2177b645390d81480d8149be277df7541daa3acd0d071b23bd25fd88a4c9488264c2e29714acf1cf79bd54124c29f06ea9d9959a91ce9063740e20fe5127b332fbbd56ae659f51beb0bab0ed610137ff8edb35633bc49233945939c428659924b96595c44e3e5c09c59332b950cfda8cd0bdb3f934b5fbd1a64ea43a1509f100f73b60aeb53cd939162d8e5cf42abf8d35c9f5f5c9ab45c67c2128344ea8e74973f9f17f88472c98879c218630a25d2117fe18cc5f58a140393eea9a9358741ff1a4f36b58b8b3e2e0239d62e0b021bfa14b973514e51f62c06dddd5b20145ddf3eb6c24e685068efdafb0e177b6a9ead708dc757d8d544578922f847a38f8b5255ecf621176d9ef6b2a39eebf41951f3d5cbf16acdd76217b3728456ac6305db6f3c6efced37ef6ea7dfc43053c35a28ca6a7b6e8addb36d7ff2580c6e7fba11c3f29f4e9e14e349a62afabb51e1f4fdfd8537fcb1c9ddb4df9e9b9867fef6fdda7ecb7eabbf95b9dbf3b66d1b7741dbf778c36eee11d925e6a1bf3d376f5f847b89c1edf9e32f0c6eb1a5bb6ddb6fbd296d5beb6edbc72ec9e98b300ef3f86fcfdabeb57dccd9be1966681533bcd43df57ba8405dfcf9ae3f736125ee127fb25f3d81f90075df43053a7dc8acc45c924ebf3df79ba4e19a57d24109276933453346335688668a58b8a997e3248e691f7292c6491cf3580c3331198fb744973fc75f5cffcd13e3fa9f988ceb1f25537f520cf72cc33bfa42b75d5e992ed14f322d2d51b13cf3a2c250b88793fc497f0bcb95b7d29a6287b8fe9193a25cfff6705a0c4a594564f22bceb04baf7ed23006258a0a2c36d1e7a49eb2d5b29ffff7ea6f07db2fe609ff7ac35e72e9b5d17e3171139379f9bbf55f7a31905c6a97bf0cb3e4ef3b546e4160c3bfefce24c530e8debadec2914d984f5f0869bfa1a86835383cbdd988a3489a29f2574b8af6fd45ec61a36e6a8db125b497f8f38111f35cc1c23ca7d7be951aa97b52af7d27715ef52962642fec0daeb8dab3ad557e6cedb5ef8f8da28ef63c6660f9fb6bfd8a9a493bb2695d5454a5bf2e1a63e3e53ef5a175235d92d3cf90c06edc1d7106b5df8e708de69d91ca43f6dcf3403ff5dd292ff59cf704e60394ea8ec4e7e97551d4d15efbcd6b2d5e2731a8fdc96b2e0c6a9ff2ba4a74698ff2a2b8da779e0caef69c37c5d55e361283da4b61f988af54c1469ac65d4d7bed43aa15693730a87d7f3630a8791899aef65c7307c5d1a5de092cff128b6126e609598c4491d8b30cd23228a55dc4203562907e7f6d85414adf23b21c7d1667fbe8cb167342d6d1f5dffc09f3c066fcdb4becf19ceb47eec4e78d9d311de6fa53debe0853fead7551566cc84dd7590d6b1fa2eb88f6b1be8659b1210bd5bcbc34df495c12c7fd66858844589f06ea5dad54d68faeb8e288f52c6ff5b253fd26b9a0d5b3bc1fd7fe10cb673e96102660bd3f47893facf7d4fbf310cc33f3dd05adbaa1882e7f8f87a0102aea1e0e22badc4877591f0b459d1f77eb825833345eab63a80febed3a54f79be458fc91b76392c8236759a98fcbcc47793548ea79613b7bf3fb75fa96b42ee5262ed34f98277b3a9fa2a85849837383dcf8933bb2f2885367a4bbeccf230636d6956338ad5a4fdd878595c1e44f55b121c8d87dd4e9252224250d63d0851a276c5897a4fef5bff4f9c1ebce3ecff1b79accc8551c34928a1784edc246b3e7ac77f667b485986ad44354d826ea291e919dae859d74939322304b12a1d181bafc3f2e8bbf10e7f2af62574a2206ff08cbaa3e1335d7fa5cf9caad1f9987abb2c618585fd6b7425859eb7f4e6cc8af17d7ca7d2117dd2aeb538079eac720c3122634cffa184eb044c4ad7fc4ad95e6abcffa503570ef00e6b9e1be89039cf31a44839ce3a23e2358cf19fd06763ba993f49a2cc39807a7bf3e3fa9f559e8bb4702d255bf5afbe1ab3e04575f673ef4b912903d0c6b577dd6cf7ce10934bffa429e9a577d210074bc8db1d4173eea0b630fb82f8c3bf8c2a8868e0ffc5113ee00808647f6b0bed8e109f5d5ae5a6bc5b9b53ece2ac6f136c4d1cd22b397d813bfa3704feab35fc5e24f0937fb7e5273b3ac9df01b37cb38cccd9c97ac10d6b3d82e24edca9e63b13f36ea868c9d79d57b2ac5fa7250a93e249fcc61e68bed4a7dd1bf5032ddec5976fefc8439fb783320a42bfbacdafa61ab9340c8ee0b4fb0aa151781fac216af804b70b3d50f15002cc778c21338d6ae8cb3ec397b1392521f19d63da94fc2b25747835fefa34ee65161e5eb9a1073a01de9a1eec61cc78c00db95fd8b61293098b174127bd8cb8e1d2a952685da95bd0541966d2cb90ce9ca3eb2178ef17022fbd0849bb56ee4ec3906c41ef96a18e28f7ff648b218b3e8e5669dc731fef2ea9e76b2d4aeecb31ebad96f5f28599065af65aeda82dd2cf3e0b0a144ba59f6324922492d920bf3287d96651f73b26f8699a3caa92ee262107d14151bc6259b9093427ac3d8742752bb8e6cc862c5231bca180d4738e9ce0f62242b6c8cc139a7f4906c189b68d0005f1afc6a51677e8d3a939118897964d8e45edb453d13bbda51f774affdcc2bfe9470b56f2b443da55ff187e66a7d45f3393b0f49bbb417836658d8d74f18d4de0a61f99792bccf092fb5d662a476f509909a087bc4d59896980c375d26dd73e3affa26409abf90fa0f0725fc43bd7d1cba4f7d13a0cd7a37fe331f8152de8d7f372da854335f0ea9b7bffaec97837dd51781b696979134466aa4abfdc61dbfbc71c3e6a0440ef6bbcf211524239376699f62376cf7a8af6f3ff561bf6aedf86fc8d81c947801f5a9cfa17bfb39a03ef5f5b5e72ee8c6ff05fbdd7777e35f0bf6bb5740ea515f83dcb4f7d78454425a0db29d44ed63acfdda21aff601a39ca8a42cd98b6ae9100d0400a000f315000020100c088522b1601a468922b61e14000e759e44645e2e16c783188851140431c6104308010418828c310ad5508d00b6314757995366e082849c9dc4cf3ecd89cbae9b2e6034470c8fd954b872614835aaab17b37749e774b069805a562b90482a469adb029661c10d96370a3c9fad2014815019707ac86076e4f6560ff09ee03f3e95083a16f053a1b1439e7798edd6c87bcb533f1bdb03ccd6653c4742392a789af97864f62a56ad4f2f8a711ad0b511729eda9b673354b394b7cd0852445679da577ab105c79a0eb34b2075cf427a8c1352f1903f63e12bd468dc815685a56c34c6fba940c11dd43226e1d2e0275cd8cb3dc0dc83d909d854bc6c482606696ef1b3182673d30c7611d4e47d5977026a05c7f265ac266f8ebb9e8b75f80f0918ade1daf1ae46103dcc9c5dc68702ada4cb05bdc9c2e1a712efaacc32dae07325cefa410801a0922b92ce01c72fde032cbdd4ea77957d41303d15eb604b968092c9f06b723871fe9a1b499ee3db6993e4c25d7f0067c3592743c35ddf71d435b1ef77e734be98b391f52a62cd9aca15810a3a7ebb0b40bb9bae5d35bcda467224f4d8d4a437c5020bbf219ccc4656ff5378c91f00306c3d0f9c4ad3d986b989f95a15d819f4f770d3f0a39a5bd1853e1da10088b422b00a1abc7f8f847d5a979353724430ad32e8ec56693959956e6d668b1a2bbe23ae594347a4978e58730f1acd3090c1a9fb32fe0d06b209c4dec45adf1a5210271426e8767bde3601e2a90c81809809a5682c5a622695468eabaec3cbed0ab26c16c871f8555fe97b0e7a93629f44c1145860f81b258f5b43749675d5cd8579ecdd1cf05d696e7f164ebd5cec814e259ae723b1296c6e7878e7dfa34657899fe110e886733e8a44201afd15dc53c0b5df61126eda8dfca91046250ffd89add82107d18cc541237aa96751fd49f76d42e20ea6e9d2736aa8d49086ae9c2e6b081cb42dc7a242c51ffa047ce025a3dc98c975cb5f0b80fbb426f9ddd1d074ce6fb5b6dbda04a11f03b6abc9ae3466a6bec3e3126d1bd831ea0981980171ba17f11419ea60b07be6aa39fdeca9cb358ff2cade33a7bd1580b54b7ddbc10fd7fed31c610e8554a54d6f79c3e837063bf43324fbe5efc061bb7357299842472906d4c93523f2e41f52f40fad213967a45234d33a718e4ef9337c2c672e3840d8467232aee152d03fc2ab65c66dec4304357a472810a173d20842b99a9ab1f0f160d0109fe7cf9b5464dec3015043d5704c47d0d12e0c689e79c80f0fc7649233f9da77e91de9c2a5c9a5d7d53c6d6a092a1d367fb89c40494bb862904cbe9cab5fe200945596493e1ab8d30dc51a8ca50c236c29c816e3b8286cd3aa5b28e8d568a5834eab709586f91978a9479eb6cfb40d5943fc2d0fbdd26f19c36936f40e85ff9071fbadf063004f7a6a3f3acb8f484087e0eac3495e1017520a4bcff1c395165fbbd861f867f77b17791d7ba175a7448028438f8bb0b09d32165602ec93d0a352496d3c54e4596241af5874bb6584f748e6482e04ea4ffa156476b957e478f55b37fb940ddcbe5afa1df0b4f2b3f62b9ce704935695f1bd0b02935f9e69cc2734c01d437beeb338712e9561dbbea38dc151eaac6b3ee326fda8f825343995cb4180519ebca464c3b7ebbd398d220e18ca4175c585d8615f8f07db83129e16c84c1465d6e9b11972587c92010e48030b88553cc51182cef87ad10224998987ebce6ae04cbd2e6dcda2da80cac4cf4d6c4560ab10aecdab474c35c5b0f5bc2d2e23c7cd6457aeafaa43f4b40576fedc5bed054f9057f7db892fc1a388b837b5ed17c31aca6fb90926842868ca029495e07195dc02cd9022020bf68bb137db8906f19273cda874b04af69778f32199fda8c6104166e60567aa1d468ac038fe671da1b9f0ab0fc04cc0f4251ca0ebb90394b3d2db35093342783e7674b71deb02b34da37d171044cb081658c0ef5b04ce14038cd2307a17954f5cf6ab4b2b0f21c1ce3744a5d13028f072cf56536909b68dece808ef12a5699fac08d4b21e786a2d3cb2faa2a1cab267b75330b0065438c4edc14e98de2835a4454d3452d5e2c0a18df2fb39957015fa29527c5cbb380ae58b7a89df1245807c2e9324b09e7105bb6089c83d62208623ae460c8e85beda21adf5d43c8021473cf925b13fb1892797a87476c50b0db52bb00979768b3443449ef0e6bb3dad6ced32ddfc950d6dd30a2c638cad9f17e9a90eeab7980a388a10054e0228b63f7e7a79ee1e94a1f23ec16af541a7574546ca31536cbb1c13f2a61e4391912b57bba70bacb666cc1f2ba90300f64345a02b5a80571bd7d1913604f0f3e30bedb3ff59ed260dc54d0df0b58c90846bcbaca27f6b601df72f7cdc2bc09f4520e8242e7ec2396174eea33ed3cdd9e73c0361c0e9dc51b958aec74d26657cb8384c8202ffd4e9f6386c0706863d43a458364479e2f897521b22213a36b297fb41b137ef82e6ac0a332e93928cc2208ca9f404bf5899837f1e6fe10f5b02fee1e4de1fb8074ea01eac42cd13fbb51e82c21201b6a721a2c1324dca24cc4e1c6c7aba29e28b3140d0e79d5f0e1522145620bb24342ff893df616a3ca441886afddbf38d2b7b3b9d4dc9f609c2dd2c2b5b19b544f7111bb703b60cfc8c649d3217199a7551a6257954da2a76d89305a853ac1bd7e929bb49dca9ce606bba7c82c64ca7817bed42635d1cb6bc5b555c62261daf6e8e4cc156eeab2710c8c7b5835e04ed776b807a4b9df7c75c4e34d8cf4acd25ec1467751fe1e329035f2402228bbbfca15a0228890db578570d71ea23abef4e1df690000130b8921c0a7c3b1de555581d416e1958f16a48989784c8ad86bd0aa1b13b839d2eade18eac43a807d346b0c02fc9bc6247253f62a10ab5be2043de4e166d81cd277b1d86a020ec544d6c0d898ba963a330c0fc0a161bca6b03e1b26297010125785e1ebf5ccd27479cc4f95fac5c608368d689288b849db1c869095bd5318c54194a97f98016c88043acdd45cd273ab256cd331314ec6245161cdf48762118fbb8a024adad97c824c403689b83dc7143d58d042905785e6c49801ed018cc1e9fe479349a14a21a12ca9545f0164af47656e4bf2a4ac8ed1e917d0e9d0fb491ef5f3aa2ded45eb0d238b0912ea1ae50b96aa660684204158cc1c128597535945dcfdc96ad1aade459f3afb0387fc5e713fcbf416f0c27b3afa760677a6b5b5695350410f597f7e12aec12c810500bb56bdc155cb6f293c3981a495b4a3db5a6f916d444974f7fba2ee29f4524288ae5622a2237fef771c03ab69b7503fc13d5aaf9bdfbc259a2ee3a15f70918bcc19ebe6b615f39893c42145f6d1ad57738652d99040484a669ca5fb0d4cba6d35ed2aa5080d5800198e394bc94094f1672cbeeddd09423ba48a10a2a1c7231e4474135e36f5eaca7ccc2bf96d133a22bc31c24bfc1c1f52f65e366938de021378c76d1f885693b99ec7213bfedcd4f9339a5b835194fa3ee591389ab214c237229c9c096ab903d441528992ab30d099318b5ce960833b87f27e14ace616068d770723ff472bcc543c455bfcb5f509516e9ad199a9842515fae250276c396ac49287a35b8937894a8b92c0d8a5521506ec3040fb0f9aa2369f00453ed0da2046fa079ee577898605d6aa9966ffbe7ee3d6a0950a693a0c4cc13e00867784bd7f61f8d3950f5f57a36a71fbef6cd539a3f189f0ac5e547c8792e03ab03719239c805ac2c2d9d4cc40d2904ebf486d0a58616dc4f215175a5cbd4a04e4db814c39203d67b9a494b941a9109d2024490147273367a39db27d79bb8cd1dad3377bff78daa556b853f3472a3bbbd9cf9c387bd4c3bc670acc0f54fca753953680278173a111e4217c23295b84db8fb34bc0903d1cb8ac2c17b5cc920e4cd86556521c5706a7b47d67170538aaa23c716aeb6261f50079b978de8f4a7d93d6864b765a00ed2595c05a0dcf8abe56f855b06529b8f0011a6acf44315bd6e734ce5cd592bf23860a33b7af3f72f7135e54a8f1b9e8b9df5f21960c2cef5050eb9bdf36c9459a62f9ec9898834c00e7b34669ef209e87e7db3bb5fca7dd3ae6532488a529ea1dee1070add406d2190fdfc4044fc92d30844eeb8d1a6272b170b935a8206b5a5dbabd88a30fa8129ff475b9f57ab4a52c5641cc68a32474a722c164a9304c8fe8aae9218dc7c0091330d208978b1af9ac3eecc77694845bd6f4ca3a7865b9d6b2d2cb4d9d167d54c041fe9b079222854bd7394c4bd994fd478f43f5312c0621314c94e35579b674d46c7b31c4f6c379e7fd8e05146234e833b6921db2c2bc200f78b44f2545515fa0a15fdcf3ffaaba4983ab65086ca7e32bdd80c72ee333c5351fa680bcf303f4580abb9aea8ba79e51a3f25745c6e58f863bd215c4e72241bdb49685c34a406c3928e99583c39619da6b4b2005034d78ca6185514899e0a2773400a4180f04fbf1233aef76d384806d88467cc7331a6ec3cb1aebe9041737487a22a81614321559ba5ec2845ea83665ce625cb838c180c1c8eede42bc0539bda61b41063a057451ecaf85f1e6a92524046a158fc3283333aa6b7e4cf66fc2e1ef7ff587fe8a7cc3c8ff7549e9746b20cbc7547a70ae89741e8033796b890dd42c173c2a76c7a52b926fd2b12592ca07ce699bb4725708f28a3aa217ac9f00624dbcc0fe049a8a627587a15d379fe9e4b83d2a4f0563bc61092bc609e1019e627137bb6a60a5b120d6178d6cbfe36700c245e18094f2fd110cf4d22c4bebd1216f5c76ef5ac2c785c132902b386b668c3e79e1d272b685ff2c66c6302e1e694a892d5cdbf307d40046f062638f6ea2e3d7105d21ed9871c36e2b6a49d49961f024fcbbbc6a98511570d0e67f4c3b9e5b194b86dbf8806ec9ebb88a353d6091adbddb9054d8abf3c9dd6d7f990f436ad33da9dd864ae76a1463f3163a7e3a8eb54dcbf0889475de83614e28679bc29f79b07cb7eeff911c667f6143747b3af5043a598ae60f6c3bf75a23c353b01db866941fd3a4cd06ad1ae2cc1d3822cbe9740af37abf435e30e165344061a1ba6730329b767d43efa8119ca29d6cec28e602cd3339e138718b4f67d27b6df047acbc93cae743f2d1dde5a20f02223df8a2f4d70792df940a011ef1d602b9168274f7d2f6cbae06d1e482a82129c10de0c7ab9d096cc383f27ae5b0bb9374a4a949202b7b37623340ec846d651bfd33333f071cd8080bc0c3d616165a102cb3126de4c415f70e57b733efc838efaba57f002c5e4a1054f339f11a62c60ea1b6227baeed0bbaef783186a24cec24d54e42b13c15ccb23f15c42af1ecfedbc01572093eb22b690d64861edd9bad8470fee8a44b5ee788907d99fd0d9e6e70d91f272769b2f592d09c7fdf31bbde5975222b364fc01cf5095f893d944b30c7edb6e927a36709ff82e3570a84c669e5ac9ffe47cff74f01a098275385d743b6dbe14e0881a4987efd6f0ede3397d78df7a1964163b7d3f9f8ad9848f0601b842354469ab29380e8814c14b1515c3c66e766f9d4ff516b3848cf1f6e5ca72b9749b02cf9c2a006f61961590983f99972713ce8968ea84ffc2fc92773b180a2a2620dd9f7b0d1b06e1655b9025c3042272d9b4dd5fae1783298852df0551b1292a9f2c419f9fb4e50b879d45ace95d51c62f5774c8c1ac41a336c70aad32f64bfffc6629eb0351f3cec10fe2c82585de0443a4512cbbe2448a4b18bd19448e9d36a267066346201115b256b46c1998fe2f832e1370b185de06b4b850f0c1f3820c965cd1e711d11843b2c27ed46488371cfce35aaf13498a2f7d03c015e0f50080c24cdab1f695318cd73ac251efeab732216f9e5b615ed5da7707aa9aa9b06fba227474fff585ba321631513dd2685a0b17f31c2008bb6d2f0311fbd0660f35ff6da00dbb8d81e29bd8ea13afbc98705f73a56337784a358bf6d8aa04fc5feca037ac045020486404c0a25df03d02f8ddd9d5c921a2dd1cf54397f21f8a9022c15729fbe265fff2145e92124c77270f91a774d318c811a5a0c55d9f09e4de22acd7eecb0a060fd80b3482bda7d774b5827f06296effcc6e408c46924f2e461f470d0bed08afb9c0e2d0af51defff1dd6f6c1ecfc830cd215ee8c748da8dcad07c1239619e27dbef74065913853cb73902d51e1250ef0cf275c5eb6acf2d957297051300ef5eb7ceba288f6a860f39746e2ea37d3136024776016c5d7f74b1d864408045e37e79852a6c7819ec3c87751769076df1872c429556c40c6f59ab040595900091c522c28257d8a0387e466e1b76a1acf8a4dc5d44f1396d980ff68ec6b3d94b16c0c6e6d91dd930197cd4beeddba5dfcd1ad84aed5b30238164cfd1babb4bd048c139a7093c68faaa584cd7af09d6c9c331f94bd1d2dccdfb520257ad3846ee26f1ce0086387f290f70fd5886c13057193845f2f200d7490dc61b50573622e95fec37d5d269b3953959a30a256bde277beeeee9c348c3aeb383c79d93f33f8103a0111fa0df7b91a9cbfc400952a5bf06065c6af993801b6191934f04c72d49944c58f1614abb1a5777cfa88a102a5cf6af24c72a81455f221ab95bf7178f6e1d93566aac112e5bbd7aafffabc835b5a15fc14953cbe93529257087105fb697f37f159881d28090a1142a8c0557187d0cfb7ed5249e3726c0bc8d4d20774c2b58b4e44847dfadd539ee875b30b87cfdfd251fbfa5d2f1b11e2f27de535b2c9b9eb1e4b6b56100d2078a9191ec90b29061be668749e22d439edc0579ec591ec5a32293a55df9821fa98deac69a726921057aa8f5c2ca58511772ab4b2d8407c98b62d4d9f2c38746e36dbd36979fff1b4848647c0bd244bd38f00af0a6322c30369146e89ccabcf59f00643ad422ace70962582fcec39e213c30c6e760008b8c150383db4cdfe1f3fcd902e7180c70ec86c1f97f5892718b18fc6067e69e7083298bd874ade0e8325f28cdf9b852fa5f51c78f3605e1105306b6feb5062982a9c9f697e1d6a772fcdff007baf5c548f1cf92c9f1dfae75f0ec3684f75416f65f74693dbcc623d0810a5a43d79d84410fea1c9b003582ac6988d32a7e29f924a0e5bc5b7e4032803754eff367483926ff337662082be7c3b5dfaa6c6ad8b65d89779ac979c49e273e34812299ae49b384355d3698805ce61560ec056de1853d881d6b004caea949713a4c17bd7d788d35b6c110670f0d9038a99316afc9d78e3775c0ac0169a76ba4fcee11edcab1042c0bb2f109a9a452cf6bfec3f93155e654403fd8e86347a35e89f43ce270214a3e8c7392cc507358f2665178683b9635b90ebf73831c406fd1d68c05fcd9cffce4c03eeef15dbf720f8169c7e74d220aaa5a29f1795b0641f7c35c49662a0df926f505e2a969dc4ec29cdf174bfe5f7018608157d9472a74f42cfbabe2aa89fbb1415dd35b4af6a56c27f3a212499cdd19bd876fb42cf3b182722358bfde7c7a68c6bd9a1c6b4906fb088eaf9f49d10ee16091f962fd8c26b432e5f68447c772361a24a3d42f1509413d80ba91325e9ad53d3ec45e047aee0edffa44a7673afe762d03561154981caf560ab5fcbc041fcd1a54d937e22b3bc84b7dbd9a5419e3085572cc1ec61d2f60706899408801ca79f2d73c85bbbbe0c0adf0947f3571b3efb7c48a8bc5dce3856418c915dac5dd3226213aeb02768a59e86f5ebd1d239e59afd9d4c66b2f2b6959510e0749da122d1532651fdc71d86b922c16ac44b5f2e60e8a4d77eb86d2c88a1e5316b4b553ee3220b827953d1e2de507addcf9c52b1943f870422d2e305fda91ef461454bfec7d12a0544e32e39bfe86e045df6b97b833ecd3c02d848d1b5f56d4385bbe70e81a819aeed6497b17067f732109508c414edc9bb6f66503b99ac4f33271dbf06ec67d1cc8a6f338d5f7f21ada2327c86ee434dc1da7b85e30eb41fae2002955f6ddd863b8136e515dade3170d1effe0f61bd2f51f097e4212398fc5911121382f798425ad4b88b21137adfa680f9d6dfdf298f10d8a5ec58dfc6b8aea9e5057304a4cb7d7d48c68928f2e65f206310c0890fa232163bbe2c6f8a6ecb4e00f7f57c76d9cfa60ee751ca70a50c34446dd3d7ade9b2a32b317e00208f3a975e402b19769010a030fc50cbf8d87c060abf4b6ceb4538fbcb53d72d85b2ea2661fc055fdf6256e2512055237f14551d7bb2da26e5dcfcf233479b1ef7e09c2a1040838f73c805590f52f21afff7c044cb21aef2c34225916d71f453c7286dc185718e9d78284472378594d0281f2f3f19cada04303bfd7f4cae6883db7bd40c50b24c9a33948ba97f267cd638db0a96e4fde9cf6d20bda096802830713a0ac3a9a1754acaa04c3b4a37ece724cf8d90b0eb48882b1133e14fe13aed77814d46bae793a56549f8975e2afc2466b721c1c89dfb5ca4056fafd8f8beb061b03029dd914632bbfc654ff3ff4ada5528b7199fedcf3f19ad7e7910b237c633631880de1dd64e9d38d7ea50dcde1adf5337d9ac0822acec77685b84c30ef437ad13bf44073f00d1ef9455a2ff5be270d4453e4c822583965540dc24c068bcff9aa47919b91d90cf15f66ccae5960d93c63cb287f595ad2f5ced9c24c9f7880ed109736951e0565c8b51a2896a0e767a504e9ebc1a57261d8f604dd016e02ba864e11b6b0e1d4b9ec7ca77d16f7f64207b92f37f612e563a90a3a2ed27239da325a443d5019effc681c08c3e1a59081786d1225cde3542297a1b1ea2df77ec36953db945b9e2a3174c5bf3605a3eaf96d9052efb4658c9b4599de18ac83f488e2bff5cf4639ef9d4c68c359a0cc8c8ef5f02dec0175c751630694f2ab674a571d12942463200fea4e5d7343ee96f096d2be0101866e2a7336c3ec089c7ee462cd7948ff72abf6eae9f7bb034cbfe7be0d62f74137c91ced8f4a0f8352c7fddbd115081651e304b0ff582e7c7446174d5433aeed8b8dcf0ed37682f946fe231a106a6099315341e7a1d34c0091dfd455deee92eb280cb1aae45e2e848e6253c1e33648e48466ed5c686d65b7bfd4c218bab57f435677c486ae84a1898bd278a59ccd55103ba2f550b7155ef220dd4cfb3db2a8539ede5762112b215d7e31df0a4ce855b33b0ae1e2fda317fcebc36e719c0a3bf4029198e16bb8f66dcfbca876b313a1eb844e41c69e72360c843c4e24f726de269e35d7f7a247788f823d1424c26c11450f67e00a8929a53b6005cce0017332f158c770204e846d7d5629bf93cb182dad41a7141aab35ec5cf20c1468b39b00719d9f4cd70744234d9eaaa2ba94d6c7509bce81be4b99e68a602ad2f9b84b829591e82a0dc8d0762567b6f87b26ed166d4262059a30b3a7cf82484b3721bcaf4c2ff909d9a75cfdd0680142486b6ed5cd4452cf2f072bdb3ffb0b5aa1edb5fd6013aea99222b015a570f3fd1f3ae1327be02f5b7a17b921f7916a2b1cc01d9ea6afc9bd7d012539b9c5592fdd76e142b039de045a12d96e9d54bf45c2400d070d0e0e9a368731b9d964de3aaddda62863922c7ccee743eba12b44e2c084c8cdc84461eeeab401b7565ba91bd95892223772fd517e78695fd35f0206a36a164387cfd5dcaa43935f6c9ccbcfe1ab1e69be8f2e259312d9a60cc68191fd17ea314e8d5bbc11feaff22bf4effa9f7df6ad2a72381dcf6d80b07c20be18963720665ce566073161a35927da82723d36d4f621bf6521e414020f6f0118718df4f0d396f5463057de86f83c95dba2c3734e16f3f55605f06d2eaa2be69f2d2e318f06ecb6681e2d2b096131a53063361567f113d1264b0a636693221677126f64a4107636296601cf5f7de955a82ebb25161b797bb3776f09623cdc9d2c826532898266add8b32465958effb421301ef6b3b466afff68e876e1e17be6099744f463d5db5efce54b71451e8061373298f512b4a041956f8e4181d48c688bc8e6b3df9f8e3244189a4a44aaf012f1aa0409acde6e4b8bfeb3714b9e43c2cfcc5fd5b5cdf137f19045fed630e3eaeee3812f4e0e1a1e0fef7d77ce8bba63dfa4af65085641b49974c80cfd19d6c074aebe448fc277467d591ed2344a265c9baff93eb5da78678fcc7239889a12a72a7092b873c21d086b96569b3fb6405d0274733b79517d783808eaea8aa51d1c4ba660aded0c6533bb1f38377aa6e1be96f224c4e7039584bda10146eba7f35e7caffcf4abb9d85039020743055a310e1f6df57a3c5530057e2a64a25e91833032492929123f1aa0c2ed9f6c01fdf530c05c53317305ea251be40e4e0ec85d7c14a41ad509738cab782480f845f7d728aedf55f975978cef651fa490820e2afbd9200bc8ddf36e793a5110311434e0308cf1024bb695b7bc36d12020a4c70cc276eff960bae20a2b5501aa5e432aee346deb46311ae9032dfabf00c4b4f5f6024a1316fccfe361d4be7d72a18fa6da6095d3cdd2ce4f8988f62b2ad78080b5bc0307c429e644099c8f7b521348920c38c79df30e013e1bd0d6565338697c41d0e31146b2209145389d8cf2f59b8a3b59106a0617fe3e12689f405cff2a887e2a00eb3f45c27260585058779c078495e03752b6ca1c55e56266bcd9948fa11e164e299c5212c8cd8d4be24dc901d090cc737e586ec7adae3fd02eeebb5a1d1a10aae31f3a71d22803445aa1b9f659f092569f1d2e2144fde568069703b3d0a4100ee11c47c6e237073e232b834b8d19a60cfc33421cbe1832498e4c5616d03902b0ce5144e708fbb75ec4f5ae3dfcf043892b9d99571e35d968c3c3a1b8c5311dea8ce0fcdda94ea7947113e7a9d4b01a42628bccc7348eff5bd310b589c25d725d25e0996e5c82f0369903747104f31971060113e1096d6cf2a163aebe3812e525f3dd2a0e48a2a57b87cd639b4888ed015e599ac792650436f2e92d8ca413ec285b123e04631e1f2b7a561e3c414d09be1d3ad2d28c263d41db13b09b743e3c0ecead0e9dbab1416f00ca65812d4e1f7e96a0fa7c39f00c6b43458e2779f640839e740c9cab324b00224ad01fd8d45451a6ed846832007b1f3cc214ce52a0781a31e0c082524a9153ce3ae6c0a9500c49461dafe800305c09c402261fb3f673316cd01159b216770d063615102979f10708118520161ae38cd8936a2a00a29821a7c8c710efb089cbf35d7f2c34269460ed58d4450309bd04efa68c0ad45b28b33b25b0f7b3bdb17f5ca991001afdff4c402f8128a992488f4e62edce41ff58cf5b6fd6a1946646f9650b74d2146f2c0553a8b3a7363c31e88f77ed354e7121c514b7589029cf1da55677fd77e5636ae4315909593e9458431fdd6fce00bcd1971b5560b1aa867f5e6f88386bcb6ec71b34e45af09e6316f80a42c010f3682e45d8677e2270fe9d349552ea2f3605ceeb271ecd8f12c8becec95f8275a2c522e2ceea57a0598f0b016cf499f119be5c1984f6ba12c62ae0a007a51ce2da21ba5fe4825bea32df599473e7488a57b5ad4c6d058293ace14f4a8d67b26b6fa50223db6eeb4d1b3f46d45940af61b6eea76e57e2096a805e2cd88ba2c089edd16cc9be119ef4f55f1be5ae38f0516de5351f7b7866d42848d4eab08ddc606e35a1fe2e31cf2f25b33926f1df1ee6649e71a0e5622269e3fe4d1969873ac22515c86125f11780ed22f975058f20f1274ad7c153d08fc331aaada6950b44b2e1be42fcbc7be174a4b79aec0c67d5c74e993a5e2d59da8bd0ac0e9f1acf23fcddefccd4fa63e28d141fc9f64180bac44aad0ed7e947aed3d755cf055a732cdbf1dda9528e23bd645a540f00492a1f8649df0ec2531206cc7c0e65cb6551efce46d375f594b66a5e9386f8acc3af1d35442d75a4f929b11b88eb63e5000f10502592ba7c49c022ff6874205f41a42f7d67c8c578b5af85b4b620bccc0d356d313c212b7e1c82e910a9ce5a52fdbcbc30d71c006ebe6c9afacb4c978e53d6c01ec80109a754dd8b1bbe0b4d106b0e13c4139b333fcaea6bc3618c03c049ac6c4f80b1d82ce11225a7313e7b4ef02752e07e3664a2ffe3cf64d2d251bc3812a62a3d907fe823fab013d5d529ea9392c8a5788c0a5f541eee1231a4f62d0f60b5790060da2c10c9aef0bbb35329d144f9dbe853c3783b12d6f9f1d4180340308822d20dfc5cea17801c84ac7975b2d0ae6b1916eb00a85e94da7c5de29863de7d1b5f68f1a8f647fcb01c0d521ed0678822db1087957e94514b7a618a708d7792948a0528bbc61792febdf311e8a3aa847e3d716e8f033859d527a12559abaa2b17d81e6a38730b2b63bc58200e4ba6c2d2072f8ec521d3cd37627f5248183b07fcf8587165d0f8d4c44745c41a4aab99e60736cb0eaaed0f9a0d2a0363cb32e5548581ae93414b7503524929fec20f1fbd92f440313c3e5c5ad2fe0058f83f5a6bc4ae12e4a28a2655044a2fa79541dada1b2f9991b1191f10fb9872590a9b26aa15ea06ec8a9651659eb138bc73211d64d38506ea3cfe2e4e824c6c39a6d5eef50be00b8ba274fef6c5c774184a2dfffb3b219dddb4756b60d615efd707750ca20e8ba87d71bb63a996166ee6910f641e4afeb176acc9a591188c130aeb56ed9acfedcacba35d78d875aece7a20010968b463643a8859e4d130c6ceab7c90f415e8df7256bffd38fce221adc0ea123ed5b12acf7903bf5cbe4ff5b172880d427bfdcb58ec3a598cf93f554d4c0c686b602070c5f806c513d87b54fc1493ee4eb3b688e8b48f9308fa9770a06cd1c706739fe0c70b8fbe85b54e6109f4b999e3742b926b28d214054e587d861f52bf1b657b8c705f7f56b1265dd56ba4117ee5b91dfc5bbf1b5c4bbfb55eaf7375ee4176fe5a66c8731f002e48e6e9192a417ee92010f38ebeb923962b6193c4978c2859246df5f4632b467f0bb6680d283d0ec774223d89c0b624e99137f74ebd3109db3efacdac93e29215ea25e3d4d251f00a4e0bf98da46afdf04f3d0b12af9521c839b62b38353fbb4c1af77ee9ed6dca0b95320ef1e1ec02fbe4985f9f0555d62f2a84f734341bb334737bd83e14fab45600c264683b9f96ec71658433923f2413d691868bbeb251d598a4bb3a7d280fb75fa31fd70f942d34e3e1d8ecde59025bd01574017c6c579f9885f040a7039163510c0262585e7f9f46ad2234068f875fafff05a3d78e9336d9b2cf6834384f02e76dcb2b351c2d7550f6e951facd85a53096abb00ba4e65555ca88fdccefcc1c62ca03e161c1645df585cbb255edcb5340ec30535fff1c5b323ae6d1b3feb0a72b52f6c25b9a93a87e17dc9d2018278e43aa24f4033dce07ed2e85ecce830d3aadcd790088574d859f62e107102053ec777d779edfc0fb8d92311ed581e1b9793ed7fcde7b2a4c5655736b6e1c48c96f5d31e36f9ab65f3a4f5891ca926d0cc94116ea4957f6383926e21bb93a4073bff8e570c81a49e3050190656e521caa5d54940070ebce78d6be164df70fc5d585bdc6535c15b58a9431f6b3e4e2b0ad382e5418376c4bb88dc8295994dc0dd15c72fa3ebe0a26c662343c78999ad9ebfd57f2d02d90dca2db22efb098a33a89f440b41c1789ccd29f075bd4db09d81a95fb51548704d191723c418e91a110d7aa6c4afad6618416a14145c86f8725060d931c8c63c7169f34691ee73b2c9d6ceaece12a004fc84d39096c37772db1b3a7c271ecb28a9c20bc78de38ba65d5e21b8534a019d1d4e2ab2c35ee3417b420369810829cf5e5ad597aa58c51d229a3eee0f3a8ef2181e4779e443acd0ea5fb06187cc6c00cbcb11bf776de5cbc223966a57bc6e6b9e3882b66803c6568dd49a86853385fb37d647097e4850378a0381807ca47814183763fbf4ddbd96c68eef3da6102701457c2a0bbb50448e2471ef788ee6e2e5c9d9fa901999b4349e05c3a228e454f48c54cd17e8af896c3223f906ecd2778118847a136c50c0a94e77d8586399840bcb3400a13c40325847a123344c12e45c88a4f2008dd6d0112769a57a06e2a40deb783c94f6456d24b5bee2d9e7433d2279beb281ee90e04378ac1443b93364f0e24c120517da4753566b83ba0cd3dac649b54796b409a9343cc8783bd73220fd943f6a44839786dd0269340d529a6241cd843a0fffae5f2ef846030eed06c26ea0a82b03b25c981b8cbdf2110419b10868eaf87620f44b37ab3173abec29b9d6c0eee64fc49ac2895ce8b92c3a0a24d5172bd54381b1b2847ca750178133e5d07fa058cb00259bf6f1142cf34f63871209344fd591a0f5af2d018e13f89933bf345c1622e26e7d518b99f3b5f07b810c2059b7ba4cdf7036b23d8143c9a928b2790228d445317b3dd50ebd5c19cff705ba155669847eaa76a4fbe4d971256ffa493c2d4be3587437186a00938f73d240a765841891a8aabc657aa164a0930d8cd4b8962b098c4d32d8d5ba3a0df217c6a25135bf243fadc00d4d2d43cd118edc97118a70ad9c54f9a0da682521e9c2d30513f6dbd5397edfc8c8f344ce33437325d8f82859bda285ac86a9b7459b5f1518dede25122049c342c49dafdc3b6e806d05509c621757013f6405a9118ddfa89e890536cb6cede0727868cfefde73f31176245cf2cc1901ebe3047d3b3ac5356bc036d4daa751898ee88d0221ea62a34a51e1c29fb8db46c72606123228aa16eb512bbd1d287576f6e04f7f42cedf826067bd4404b872134d0ff8cc081247a4a2c1764aac1e234384c063dd4ddad4848b796a358a1b065e9cfcfeedbb010c3fb3045bc81dbf87e020a06343ba8ae05fc553203688b54e126d96049d0b650c3273e729d689c8c17a784194b042691fc60293f87659432c4cf9fa281d1233ae2a57a5f1dccf3f129412c5dd8d74638457120e826c418a7dee5760e708a9d30d91a470af43dd9d695208b5eed808a0b609ca6ee8c3d8556b9ed16789c8c74ea3748de1126d5118d0a386744a7d36f9d6c2890d97354ff802b3ffb8f32affdc1844e8a35329e608c90335de90b5cb740e2f6bd9edf966c47ec23d0b1e16b132ba0c6cfbb7e9a9c84c8362a1a2356dd50d0fee50530a60eb0527eea8887b0bc54147eb4ecd3b050f877022c805a1c51046811a215a1e0e0bd0a4a24ea09e1a13bca2e2454825ba05e7820e2d4036cab43f6438f3eaa3245df483f9a2cff094e267fb07486c61c659e2d6d0a2e41e173fb4aaafa60cff588467a52c5f06b858efcba026531d09560fe0384b98eeb733abda99903012ab67a07729d195e8498ff880be468d24fbe234fa1fc29d14409ec282714e95be172f665429b41c07531d5fb19bea612284c2decc35de9fa7552ca927662aa9d451862bbf9195a286f39c481ff8188e405d1477005377e98fc7870540c7ac2635f307346369a419e6c3eed711385c0110db53c5178e3eddd61fe02916458a214a39c686e94d3fb31cc72604664c864f01783e7a6c1eae20abf243d05c70b55569f1bf803258d522ca7ef308a402317638c541f74d5ace9819d13ce8150340d17819dcc05224df1dfecf9ddfb88146444d1b3826e09b4df7b2122217263a7efec14e196a8e742ff32f08dc5b7173f805c895699d99274473a795045a95344e2079b023ed1b9018bda344db67df60920d2599334a2b605dfc49b04b67231c1f5d645c41616becbad0aada2968992fcf14d118edc869d4fce8a28aea28ec3d5d69b5ed198a2b31f9b9a2234d393556c8234beac5d1f3762f1248063fd0ac1a264a8fa4a035d7d8f3dfb6ea1458131443a7d138fa108d2da150bd48638f75997da79eeff4845367390f2e3a7ba63dffb74c2be25e7d992befbfac556502e4373dd884f31ce6a703a823852595e4ecbe264ed79c8bab417fb2455240fddf9dccda2af4c15c9b81ece816f5c7b1ce0c24260eb160c4bb1905c75dab264e66c9941514fba4586b1af46ba07572c6f0b481d67f3e3dcd91879a5880b1da11c86f5ac0739f480975f806ad591012d909a001fbdda984f9dab41b6b586ea143fc08c0ef4c27aca5bfb6145a05a489aea7509215fbb376821fb367b7cbd44371d4274035587add4bdcd646279d1be61d1f5ce1f86fb41640e456f421de6a6411d94cfb66e745bf9f375307964d3996f2342aea41079f73a38fe50a8e49ad45052cbe7ff774293c6df9923c843640ed51b7b6de1d0da858082521e2d510e76a004d4c5353427237cd741483389dc880cf60ba186e98e29966bced30c0f4c079e0d145e2e581fe76a9566a5708de0c242391e50db2606d3482239b0cebfce1c006724e9dbd0570910fa4fa370b09f42b09ae5357ea19f8eecf74091d120423d897335d88f057bcb4cf2fdedee4ed2ed5296475e63399948bd15c65638e69ce7447e86fc816d61dfa5aed110582a22a267c20763f53f8d42da492c0c29f54e90fb7290c83910bec197eb975bd9d29a98f858e6fd386d81cb8af1bafd6a851c90ad03d7a36fea0c3ba75201d22959c330ccf1bc1e05643b7c82a09c0b6288e49ea5bfbe1f4c9f28879a1251f1ff6c1c87111e66290b40cb6d9c224c5a3409271bbb5f53e1f83931d8578460f0249c4dca895d4e0cc5b4089b92728cb6097411fe7bee8456d3708136617b3f0118f8858c554e217ced63d3d0d879d83441f4f73f77a7c3aab03d4cbb4380b2281562167c8543b4ac23a75b95319aa3c080066734ba5e025b1ca3190106b6384723e512d85d2cff671a181660eab130b020c8f92470b0e230a21497c16eb17087a960b78255ef02000d21d7e740000d67a4ba4da817801e67a4e381003dee4845bb00620e37b9c8382060c71da363fe26f0600172c17fa683cb023cbd0b0e7704f28d011d761c393a33ed2e0b34ce288b0606f4383922c404443b77cc25aac8f22cadc0b144c0f25de52cbb1b311e2cf63954d4729c073a343846a51f585822e4391e58f4f8a314ed32c8f8235d021c6cfc51747911188e51dc5e846eb02b728deb300f70711b66c2db168c4c0785a73db81f78870e5d4e5b0343832cf7e9c0d1e01845fa17f64906e0c27f99e369031c99ae40a85c072ae921174281943e5bc19fe52d9a4b2f1f48b807cb991e149464f790391adbb55b63f5bfd07ac32400bb44b2f705c24d49e08733f1df7dcc535e5cdce16dfc8e9a0bf86d5ab21927eec9b233ab626f18def809fe52492815b6baa0015ff2f199873602e53070adb226eae0c0ad4616558155c68f53f404d4305c5cee12802b0cd354e2feb4335eeb849c6ec4f421d5a2e216ebe606c837319380bea2ea2338b80dbf3802cb6b30d6658e4319160fb61bb4b065e7dd50a781fd80c42b4a03bbaa04ec4b868332035900702d01e805782e059a0794cd20a7022d50d10321999600c0feb7c2364b4695e022e15355ff31cf86c252f6228aacec1e7d1249ae68315c91f01fae1881a75264455a5f1f8dc73af6259b0929b7e189e7faece6c51658478ee7964d59055da2750390a3941d1f8157f752e5f406008053e43169f6a342d680b86d2af1d5431bc339680c3ea80f3e18d60d3b3ec0192a31fe9e0709fa8313828b82856e9019eb4379a794630c76cc092e7eeaf5d5443a60ec5ec3983cf64bca84866ddb9fe02971d775d7bc9f4022ef6511b932633c8684ab9c1836aa28d9a79f12e8ed2613a16c263c2a0b0b3657beacebd7e5c755a4a7faa7bde802120e5c75519bc01cfd237857b75b460d46f81687da534539a00067ec413c80aabb8793f6431a25dee54f0a2e3eaf4103239864c128f80d51f03de6dd25e60ef1e277bb650d05417e9e610c7373154559e7cc58408e28df6496f76a4c02b8b2b73b1540fad81253e8bd53464e2f3fc2d003eb7bed58e100b670adf4cc6c4dfc1cdf91219fa59380edd29d858fdb406408f207b38bb56aeee43d21fef0d9335ee6503c845012d8ad2f9d797be27a03350cbe0c98a336170b3ee4e597ef89c538f5450c1fe1aab810f1982ff3f1f683ad0fddd0ecaa116f6eb83c72b95a475d282b40181dcb3055fe4a4b2f4743f43a2b107dbf28597ef9aa328dec5490cfa41e00200a7cffd092a040ef2638249c344fd5a8db4249c13c6c7c289acad303cdd98cdcd661ec4fcf64bf6d037e0c52da7e61cb7150f900f1520997486d3790f48452f7f5235e00e053f39742237ecb9c6ea0378699f2df1c373686a939838678bfe2433c333faeba146c0d8cdf7b73d108fb400980d697a6dd4028b77a6326105bcb3e4a720ce592d9fcca7da23accd95df3a59c6b6c75b74b8cec2d66e7a0d21d3f1c7fed40b6a43fd2928c2aa85dcbf42ac1d965b4de67574526de3e95875462835f7eee72eb4465b9c6963c30b979e8d6990eddcc8745b8e7987ccee072b5830146f855362b2b6a6d42a61510e746dafe31636efd2fab1a120b9583b32ee610dcb97c9b78f977e394595895dc5d7303e17a8bc297ed8140d1fa42c9cd6a99a138c281e2a1e0aea703b7fbf4cf69e2a89361277bdb03ab460413e31d9b07a2ca37f17dd62060a538a0db1010316cbd77c71089a2d35bc3cc7b69719c233b1913a9efc9a529a191248dba58e723c64434cfb7869f333e4e2eae63d5ce3b7702be940cae38bfe31028f9813f6ad4a1a095e651ff5e30e7a90df5051f670ce0a3bf48743b44d1097adc7c92dd060c653174678eb3e988c37e7c4d6463738dbde3b4019e8fa7a1b6a3c96ab9667b63c5febf753909fe548ce709497c25fc3f178197d19bc91bfd23cb001576586e74e1725c7b3844c4c4f8b01e4be791b2013039405dd06611c5afc525bbb7d9067362c7c8b8b368890d6c40ab80820a3ad8b5a9ce94566c105c1a419559125040566f273252e1faf02b1b8015da0c1e255111c78800496ff1a320a3e13ae81efd9d278ac6e99487cb87985f3b3ef908dca33be544fe385db1db958c626c45d1d52138f00d362e79076cb7d85a8d7a1475e868ad5e0ec8daf403a6c5a8090f620f327bdcaa15b2a3d1ed5ec90e6ab967c8322516cce483c675b0b2a482e53900c1fa03e71d33824410d81e6d1e4ce38bc8843c80cc25136017d499c74ec7122c6c1c101681e499ff0f61b6497b134c9d2016f578fe7c607a2ebf00e85db507d16aee1086fc084e33f270b45ce96345d232ab81090dc10f64bb5ba3782a4c5a285bde68f05f596c9c5a39b24f3d57ea9e69ef5b31e38c39ea734e658c43fc0f02bbe167ff5d48bf03071f26603d4675dae53b888dfcaafe01bf7946f4f97b0d279343a80eefa74772a974237433619d5cfc4e254d95dc194b90cafd2e817e22b0faecde79b06885fde5a7a8df78f9a2591feb3b22a18fb6b83f467ed096d6658873ce57493f610f1417f6c8327cde187252c7ce388adf21a97a4e2aa0408e2c540ad26f4ea0111e82da079e3e5ff3161ba3f83ed8d1df3949903204a589a1998560aafb1162cccc8c33cc1004a3365dedef1fd06fe3c17f671c880105e2ffcb97c75d8265755511667ac90bf13e30fcea272081d68662296359198823bebe85d70d97ebe0f2b57033aaa8e39ae17a7db8bc0eea8f9259bf265c5e37e0de9b28c552a7ebbafafa94c1c525a4cbc63de2259a2afdc33e17663186719283d53f06469e89eb0a288ea888f0560c19ec864a240b4041549f927d1e2fc2ad17c716dc23ac19a49a4a0ccca11198b276e4d083992a79445bce0b9451af103e6c4bf4773ce1e9f7b9ee3af47fabd38b03562cd96b34aca5c5fa2ab230a168ca844a8b04ebd33db2e7138715f78677e0e09cbca0a89a7ba8944d6c5882b6150720669a03b426ae5e9609ef7d5c7a8547e1d84c1f3c0aa2b348b1f9e598519d6e0dc5de9637f98be1d6c899a11ea778fd7dc133851f4c69f5f631570c62e166bf01c0e030090831d75db1f2239931e740933c9c582919ecbc279a69ff15d066e8234a75a49fbbaf2d876c3562953c504b95c19e6536ac3f2dc3627975e9e5760e79c0dfcf4b7ef1d757782bfc94ece956d7f0568e02d46770b08aa5abde3d8b046eeadc073b6b2f223c736fed0da266519b92d3b823eb5e42a318ea006379166fcdfd1c0c4545e586b931a26f890f67fc9c0e402ec1be871c4bfe3d5352342d8e83baeddb99f458dadb8638e13a8066a56d9cad9e9dbdac096622f453b369053596aeee71e554488a315e44e8dffcbca46c46dc7e9c935106ae2721f6f68c3be0e39068e693e1d0517d9ae0b0aed28e83325e91636a788d6c26a5196ed6e4274bd1b95df01dac9ee8705dc46be60628f9acd9df3a486660fb13134e32b65572f0e447674acbc9cc7894a467a63350cae46363793c593f6d5fdf681d99d5faa811c4fb83f8fb59d5ce4695aeaa6510c416e2a6f40b582966048b907071b0c0aa3a49cfed056f0f77ed81124627dabb25571d0035ff789a9c65a692032d9698fa865d46caeeacb4c1cf9b4ddf6c5313c1a4f068d30f0554cece6691df94e024c9d471ed4f1de04d21a905980a525f0c3ce78542826c65ee6b33a43ded78c3695c52ae330d36575f0b9b526e105e89c7f38ff475fc87672005bb7b46f1c9d0170ac0a67b2df9333586d26d9fd02b2fb45e47c12b5aa30d5fea81cfacec2a373a6a8a5e42e1d826294f0d4d63a2d0dbcbb6686b6115725308f5716b3ab5b4377330f73642375ffa4116852e74917b19f9f6aff005ea71a0f03d056d26f37b2066e56c8f59f567c3c5164632a5271961a44eef3f6411e435b3dad3239f60cae5a648f7be9ad37e2c62a35a56f2cd573878046ff3a18808b9a05e366cd499acb3e01cca67add593b168cafcfdaf452db05bacf141036441ea22b0488d583bb7f9d2168f72e29ef8a55c1152d1a296861a4fa0488b7ee5a121c9ad7e027229ac19d36dbd8005e6b1b17992c69218723d194ce3eee90adedf4e7650cc479a605841ebdb640f2ab949a8116c7b5968d0c1c715ecc0e50666e87ea729da085f2bf158340f7b1948f795e465e80568140f5152f8cec3574dfa2013310dcee083ee2b6a707e113a104bcd81ef300badc11f6b29ce3712a6cef95b038f245eb3db539763379b665527d2a0d06d106ceaf891ae3e3971aa12b0845837d8011d9c8d909bac37a87c2adfb5a088b849890c8e187fdc91e16c7e302975012038a5ff9662e60078dc83809f48de44cbb58a4bf9c038a3093928b846d055c4fdb906c45972c033131cc3d470394d056dcf09d08a7873d906ec675020bc4a9fb2fbc258aac1ccc631b231cdf15448edbfeb7e485eab3fa9e151e1e770658a6c7200755a2589478f271fba8026dd2586a0f241557857163cfd635d5b8d2e57c12e276a893249149117959efd4ce882d36874e6121ba84a2516dbb2fe494a9ecc0004055a6a256da5e23970a9351c0421c19616d7e0408d346af4142bf2a93c2f62680425eeb2d505779551956a8914cbf9e659ef6ab65757daa669e548d809f98ea5cd7dd8c858cf3a3078539111d589bee135b7f025acae96fce833b45b5e34fc60ed354a0553aa1348b27ec7883cf9f703550935273d41b1456f337fba91ab0dbea93cd793abf1e95bcbdf95b94d4c3cbf9d2c07e54599528119ee4711a79a7227eb717cc476512aa74653e31f9aa28ae8b2a2610ea94e58ae7c5fbf3a261abe23dfa0808ffcf882088c7a3ad1802845513f93ef0852080080bfcea05cde8e0f02108800dd8f93723900c377c047ec61b6b4865e7eb8009d32fe96c26f2c6e110e8697fad48685b86f629677b69db805dbd79463d14097e00bc16115922d3b2aeeda12d1f24fe7325db2c570250da42cdbff5ea91ff9b1ce4f227ebcfe93156145163fa6181a1587f4c32a0acaacc6a0a52ea99ee7091363d784977084ddc3d8b89232df23f83d4359742ea7f51ab05b1b1085f96299826110ddac9fa30038d962d30d1e065d32e7b5d73cc87ee4aaefaf4dfe218f1b8f40ddb05a14d097ec9b5065498731793a126552dd79a3558eab4cc3a0a210b3d290a112f7ce7e97ed43bf0b8ec0ef327f0c2bbbc1d9590e4134ea5324eb873a991713f828d7742d83e0af5ed90864a7988290faf6f04a3cfdd02841e72edb16ad0c8fbed153bb6a43ec2b22469d4f8e6bb278bdf326db411d374c2ca4d1b729ce935c7e1f2561aa6df666da6710d9e5185630059bfa5a0d2952993455837cd78b3655d8845ad38e7534b2cd31501b1652f4b4d1f01f1403b6fa14f2fba8c097a7a96ad9b4f61d68c1ab441be8d0b0e391f6228411d675df436c23642410267685d744ed1b255b1c8b75d38e6ab38f23cd6bc2f410e4b2e9bb79ece0bbdd956d225888c5b6576e68b79007da52ed0272074e6dcda87185841dee07bcd3e750771a04292acc32b0241f4288553cd4ffe1f755f023be169188b5138d9dcfc8616beb0902510bb2281c44a0cfd7326334d4fd4da1cc436c2c01b8772bb3a3d1f873e203a55e2efa07ac80d7235e76efc04d42f0b92fc1615ba09d191d79d0878f0cb46d325d06289e52f25468040dd732f50243bf105e23528f59dd73490da60569307205b73bb3f34f22b5ef8fdcc4c25cc7aeb531904516d0df1e7daf57218b64cbdb9fdb59420b6f9244a27391a8ab3868031441b02c4ce73f2faa499006b62846be47bacd21ec878ba6f7f9eafe18024be5f43de87b9aa1c30c703228784e4259402db0e8704fea0a75307cd304a17acc118fd3c79f088773e77610619a6052f1d66008265082301dcbbead3945e504e1bdf55479a3fe928f0c44e49439c13fa61866e25602b0084f8f4fa058d750138c18b7fd7f4a55a065d49b585e1d7ead1a73ba9852a54a21472bd60520a63e3453f94ac50a2593ddc13819f089a4bf9200ee7557f606c85cba2251ff57194065b1a51fa4517241f863504631069eb2d3e2db48b90951c2a11f1243b9a81dc6132599c47b2547a9fad61461a6dd12a36a475a7d9e0211756677c8d0b6d84ce8dc32a3d2882eb9790335e29b10d2d56c210214da98740c34a4f1329a079ebc064181451d15777c4bf20a6bfa1da47ce78eb0e9f76775da9dfec88ade67af249ec082560fbb9f7316c785f7668e3b409f44512e7d1b11c62d5d150f08caf161d8c887da71ece7355003aa7233d971721dee43f91057d05123dddd60f16289f6f0182269a4dc352d9dc3c5c32a6165f3bb747785f911bb285642b810e8afc22ea8fb8db4c2b5a90e2a83a70fd9116d8b4823f427bf2a8836e65bbc913fcf34f533a87ac9b68f4e3900cfa82ce5ddb16dd25e97db822d0688c3a12e40029258a7ceda18e7199bfb8fbc41bb1c8832ba66521a40470d2063aac1fcfd0aa08405c4803478196f9aa9855b1afd4f7b6dfd4258e2c5154b945401527c988e516d92f43ac9cfe6b9287fa6162230265e97759c61d0e42d7b762b6006e23c25115a276c9b50d1a8d4c46071d3ebf4723dcc2de1e6696c4ddba8d2b5b99639c7fe1270532a9ed4738a2757c3dc8a618fa49942b60b88b924f74973288412e9561da941ab404c056729c657e0156cdb88fe8a3349a04da4e5c2e43f32e0e67ac4280815023a740abb8eea6390663f5b9dc7c87be08835b9974949a80090919ad067f3f7d0ac557f0dbe0acbd2bcda0e9a6e12d36d4daa60e23062742f0a92d74d10c491dcf71a8f7b57b9a5ea761de164b620d7ec40594bb97fcc8a7c32e3ac64e1c36cf1a36de5b692c990440965ae1289ffd15166afdaf1ee59ad3e73de9253f03a906199b9dcd7a253643acf8348f7c5617bd9dbd2604249d297cf763801bdba1ef8367906a713fb043c467f1bef9ab9b2710331d1222bb6a59995c92e8a7a87f828021d986cea8070ff22941999894bf87c255067e7d42cad5b818646e09fef50044680f9bddf2035b3a6a7b96268c67313f98a1ee7c98a209c7d832a8629a99d87836d1a5ff820b46c5e2bfeb0e4947a5d6745feeb06416ee244e466532009e639cdba50e30c41f8d3e85b268513c84a25a783d5b5409874dc5a14ee4bf4c64b2b50e91638f0db190427b6ee8d71c1e64349fbcffdfdb5ce37958fa63aea3cecca28cae5575a03d9e14c16339d4ac6fe0aeb7b4863895a93abb60c60cdc16675c4e75fd368924bb856a52d80eb0722f928711d6fab4e30c97932c49566d2de8cf6ee075b8139ac785c5d88468739e523852b28303223750fe4fec8bee9e08c1fd092b2dd8d707bba650eda01254f64f2d0248f63da0a062e01ba70674f7e38c09cf6a40df12518fbdd869f6fd86416e3e2600adacd61d3a023fa651b24c8741d50a8399a0d2daabe8d331692cbe21e6f54af2abc22d13b0c9fb3a40b0cc0c9ffcc324b20687c3851b621541a8beb7178717b782b4d1f34366bc860242a75b585e16a4ca0407008ce0a74a6b72c2b34b26dd365ddab1e0c12deaa2812e0f7474ce5386b2890cce2b004001fffcbf16aef48e49ac49355773541a69149a904b9fc832426ba8a31d042f6e154266077fecce7b6482fe4326fdd068e6d83a613588ef9d2c59d8e5afa79a1b09f098e7907637d08f981ee77774814cf85bbda914ae8c6ecfd6d72d852d46e3fe949cb71498196283022868e3414bfe7f04eb578144f2ef64719c9163b272a8ca3d4339f4cb9f2e470f4b993644d3aa451bf9f82de17578aa0fb36d07f18f2e6605a2d286d7acf4105d3f99c7ce206c4334fe77e604b3958414fc74b8fb971dde7eb3590665b2dbd2d6df6932bc966d6efdb76de725a0ddfab49db1ebce892d89b7b9d62758b4c4accfa31578f3df341b66d7fd496701671215088c0ad18343c163ca32e1bc90b5987534a30a6696dc805d1553bb52878c4f48fdc9853ae4bc3fce0ba304557e39aaac84820f82eee84972f71e7a49be514bb050cc46392f13ff5e540bbfb10a08df137d073315524a34638152438c4dcc7b90c2eb12f5cedc56d99bd80f2afedb901914c9db670420402f77cc96386f1eca1e0b87cad7aaeff7c614f9d699607da0d0308237d8f6097e8277b7b104456e35fe7b1ebd00899290c6e7a622d444600e64612aeeb080c3431da28603673a7cc8535b6aada76523fa8454bcc1dc2e70816df18c7fbd66e7d949320152ff51da4badd5d40e60b170e8c58e44a7f9ee08e53bd2e2c4f3f765bb07ef0e276d6f161b68d0c1d07732ef0a66eede0e81be6ea74e4fde04e47919ae7cf4e3aa147472a1dca645bdf9e2dbec4585c898d16fdac0715a1e1dc29c3c962896fae10278014d17591c096bddc84bc6469da0813c1b097a3580bb1096b672270a167b783bb0147d473e48050369ba10c4a1f8d8c930acfa88e1cfbf3ae9eea93264a5d19333caf5cf9416e817cee7476befe39e84c1f3b8e06983344ad06a8e153fabca56b139ef7f2f1ed7950e54d1943b6d90d5fd5091c788c5deb12060a7c18c332456789b548ad6cd27bc80549a6eadbff19bcac1ab995cb03570b9d9f43e1b19123eab26c232415b711e4fb26fd57fbcb02bf94766104e76b2c9c6c67c035eb50e7be36581ca0675374bdb8054838fcd7d25ec7006bf716f25053c73b1332e0589d8597dd488a41429ce96c347684e2c956bac79e7d1613a3a21e6eb3007cf49d491adf68229021bce980da68d50ddad39b5666d1c67db89e350849efa75580c37ce85b40a9dc4d25da616e4ea1cb09a1b7d80b4f8662f2145e82124525a29b0693c041c79f31a800518479eea38b6b466a40548f41605137faceac75d2e0f3abd5612d30a629e11267d08b22fbad605ef08bf59bb772d20c9387519bd52502050c6d5480a045ae14f47d1564af4136ef95c08df093ad9d9780755eb784a88d24f7f2b8781ba26e50bae2b7a28da7510adc1af2e2edcee87b826aa89a3f0281e2354dabef1748abb208153cce9c227d837c3b1b734e96add4c9b7cccd3e47c7bb6937392ece90922a277cf6d53ed06448d61d46c5d0f908d272cf0ebb6045fd67848e7999817be8832c5db4c6ccf594345df3a905c6a9cf0c4dcd404cd4cce26248af4a9a57573122eb56f1c7c34b63bd533880a32101a63b3b85b014b5c238af1d6865e3c37a11d9eddbf10d8b7dd2780c2988c5455ada2d39c61a373030ad4d6ea4ba6db1fb0d9f026a0e161fceccd3fa309972256728ab60bd8e1ddf5f59c4e5b2e52c11a13364fb19d1b239638af6349b308c0eb2391716acc3f8750b57446a0c39e8b24ad6d61ab0c36ed025388bf65f064a2113b4d4e0deac1bbe8a75d1fcf76f0cd2626bb56b2bcb0f5fbe182527acf996546ad7fb4520b63b3cacee0581b81f7212d9431fed8ff2485ee10fcd46d7ab8c395e4c0bf309b5918785ddf43ec4a05cae6505c0e7d0e25113b7e6ae4836811f627c5380a4891ef7591eebfcf163f0c919503c6462b816625bfe9cd17d1db037680fdb7b78f910238ecd010e68ef106a7627c7318c34d3dbdf6bdf68e7378b6b59987a811339f5c4699ca8d1670997e0ecb6823aa0601e570dcc2a7e2440661bb078464cb4c05412d9c475c83f7f4746b22fb803e793625ecbfb032cabb33734d196d486108e134d79f401b156c53127e56a621cb45772332a86159d91408369a2e3f6edbdfaa20d37f017ba469450b949987f285b5e0790a0b4080d8fddea5494a15d5d95a484c719c749af1fa78130b4ebe6e5f1ea3b173aa7965a7d934da680ec662a6a4428d029cf247f2ab4c96d94af830014848852b6a7acaa77e1cf7d57c5e33b129888fafa93e4894f880ef50a53ae9387abb82e9c77c495180b8826a0963af18ab6de1517c7b8f3d5da83728dfb2a21c8e10cd4410c51ddcdf03f1e94efcca51429cb1559b0d6b94585b25d34d3bbc7536ba981566bd86aaad922b9f9a82813f85bd8a88b0cb2911536cf137964f7998f4ab4a7d18520df869d3ba21867533b443a29a7ae54f2db0391c88f4bf98a6c69d88ef612edafcf7987c6f9fd0db4e0edf854177b30711d2379d3b0bbbe970c69e7ee04ad9184a5896c324362e5bf1b971abb2860cf4923867df0d24c752d1c8b56b901c0e34c3c59d647c9f49c42935682bb8956ba1766dcd266bf76cc8f25b7b0d00102ca778e75efe8af44fa82c64191ae9b73d97b1b375b9c29074e4377758c0305702aa8fae6420164d67496d42a25ad4c74358c54e66c7449acbfa8509cf4735b049e02708eabaf042349439e1b9e7b30d13cd891a3441313dc8cf13c666918b03ef4d177a42cf221129ddf81e91fad9bb36dacbce20d935e1394f683a0dbb59b94238d6f565c9718e0107d2e093880c13b69785468af86988680e75f109e3ec46c96f370f682099fe198fa5fafd9d829a008ad608c836b5e2ba2e17e22a74796b4b5f12938298a580d26291d3968b3e710cb0ef70c6c0fa3a2b3fa2483b5af3848eb6a481d04e5659992c4a872fbf40527c3fb3c3f379525344315c33b5c1bb77e1ff21478c72a664980d803e68b0190ebf9558018a0743494a1d359ee2a59c59d33488bf86f796ba0943361ca6f333189d3c464ac587d4adeac59f887bc556065e3bbd96e006ed5119a72dc989287cf8b8a6f5d9929176915003a38857304720027adf88cfffea1906f3c80317c2aa8b09709d2fe79e4867b8d6f43d7aa701a41ba3c4e004a029855a997dd72b2a634b825f4591d68fa53e203d024b3dc7c992aee1352903d369f9fcfe395d4735253f7e9988ed6b42360da5c5df28ccb20745b5896b1b8b028f6fe18746beaec9804af6359205b378a75a466bf346a8e31f0e5bc4ee01766a33c6378cea24f1897d8070ca1b44e22235a061c5e15ec5d3b114b84d5fb10e89f18d15cb30120a661ecdc05a7b8cef5fe8cb8ee9e085d62d3cb21edb1ede1ce043a489b0838701513ac4b9f8fdf1122b848686a5882aaa3f828995dc049d8f7a670d78030f672175725cfc63bb726e67d1678214d3ae86470fc305437baa4086e5e8ae3b01862d340b332db4ece0cb4984817453472ce0b2b3217153eb2728206c0d05d49919365fec5e2d4bd57b6c7374544ea92f8334adab095dbfd265498ad86f93a490c65faac55f5af1b90c83598c39b17791f3c5077e884cdd05cb6baa191a27e37fec15f20f6f56db44d62087e89080e024d16e3155d42e408cdfd9f7221d70559c8ccbe94cc4f213a076d240899557419741dbe29350c71fad69ae8be90ef0ca4083fd2f5a38f430e9b6638dd83608a7eca0d2affa23ab30fa1a86894b1a6f3a48a891deebbbb9f77f33fca1a491b06c64935d3d68e627cc272c2751bb558c928eb042973a6c7f294fccab5c4a1994bd7ef09b651aeb1364f61cfb000b86d246b25f4417a96c04595038d65153f852f0c2e895af6e25d97e1675fe5be779a02902b646c23a1d460f1508d6e470e0d848a967652275034cbe3b4de2e6899b1ddc43e936703c998d47b999ac8e4c8cb16877c2b07d429aaabff8e4500c210757dd91e8e98b2174ded59d21b0c6983f0dd26f8a44c8d188e75d21d379167afb9efbc7b33a1a15e4d0bfd68179572571344e88f77826b30539e8776d885733e6b51f3f4238b7be08b35b15957c81e9055a557136082ceda4d66824b57e180d5a8215bcf9110c8ddd0c03ace82f85741a0f30d5c8860f3c62eb365702ea0b874a5551524a011e647a17c302b6ce4806bf05169050fb7c3a96390fe621c5c6f834094bb4313409767f530fcc5d39c51cfc2e61253d789a44fc0aa6616d38b66a1488d7b07443094add6345de2af78c87e79b09e12e99698413db89a797eefc1f137210e7c90df254598d6443a133200f2fd2f88f36982fe30990e6d28bebca4e0a4ea5d05927f463681a4dc39934aa5b50bd0a89edb368e9ce923cb37d9e22892b6d8a06084c1e6398cd9569264fec05d011d13d365946b9e2c5b769716fac50016663eed1edcd0de7fcdd248ea7ea3f2d0de3623b47282578646854907f18d2bd9702d48f1e1dfd30f066be9be980e06c1491c8294f8d0706cad8db33659f804072caaf036ad02f45ee11ea3cb5c61952369966965d041698394b9d4368a64460e08e19b84167f20507457df9985f2c4731408ae50ca637da61839a1149fbed02606e1ec8758366910b3c90f29fbf4f3772db0691ec5e21f925b344f6b8a926931498174373feb48def7f8c4d1d4902b09db08080866f334ecd4925339b5f10f906090090f270930c7d768a2d2a91860116e28ac44b53aa77449a02a72bc3a2b33f52aed70010b70e2bfff7d0379740db03c52706267a1bb51c124aec74a0b35b2dda4b06714bf5a8c812b82fdff2b01ce12c9588464e46ddcab961b4d1f79eab1f4561e6b643a4be3581de5adc7abaee0af7d62fdd21f997eee19f29dc90bf5c21d807b5decb1672a423aca798d57666dfe326397a8db3173158199039b651c3f69bfd412a1e15b2046730209d18fb6070f2da2b185d3d67709c494bf454f775ebd730b222a96c6289db5780f24ed6c933c730bd9ab1ae0febb2261d123d0196a62bd008c794f3d87d4025ca6318fd8716cc3816e7b71dc74b426b0ebbc8b2e8212a2c801d2541ac984d5cf41e465be5e937edcc524dda3be00a2429ed137217f13c9f6bf127fef51c487f900d9ae19372aec0061e0218c202063182a80406e6cfe89c833bfc1dbc56e642085d2cd9ab2ff376ac0347904c91291d669c73606ab38a1e8697675163232de6d07dd22466a93b61bfcd768e25cf34400dad34c873ae7ebd8393391169717e7eaaa2d11ceee265adae7fa0145874acf769b718b53aaad6621b7580c88a25e35dd4b30d12a0e5dc71015211708514f6810b5bc3dec07755ae0288d0868f7ffdcd3d203a401906a02e3300b463ea98de78355814833cc90cfa4c5ef637f7be95b0b997cd76c1738e6bc8b6b5058572e1f3eac8598c9dfdc5eca4beb8c1073fff45878dc38a491fbdb3911094fb4e0192deb247c10be3db55ef899537e10ba8e1fda2b9e19df954fc7bdc3b97430f4b65c6bc01fbcc2f74939fe4049c831750568a9abf43b7a5c1ce85e6de67046c5a7de342fd128144157911e901ca55ed820bd75a2632e2c9bb12ff0b68d70c12938667d023ec9242d88bfe182f4b4695db0ac55ec0cde7a06f02170dd09544b625ffa304f99428a7e43c0d53c88b567aa060424032a523a765c305616a703225431f3e8f648ad17fd74b9a436f13d00161c46b2e41ed452ad38cd689aaad0cf51966d10abc3deb5421fead36151309eae6b0996f77a6280a92ea02fb8114a61c46b975c745595253fd19290b6775b935b4a29934c010e0756075b073f55737a9cedd16b2b2dd4aa2d86667d4966b831d62a11cf8ca4d8af7f5e108c5e4d9f577e7aff40a15114a209e184938f04e1566d3ff8e3f6c4522f256145cfb5f31a492492ffe027799287e1a36f5ed739a93b8f73eea2775e928fde0cf3b91b9d8026542dbe8a6e0c5a0709bd3c68cee9335679b071cc670d70e3a59f507d42f3a0f1b495b6b09033f1ca5a48672a0a160bddca785c641d392f3269d5ccf9e91136b9f99a539b587835a7bb4c8f519a74ab83e26ab2cd153f23155394f8199ffc8c45f0f054ad9aeefd600bebe1c046d8c3628bf319274da489abe953c63a69e6f4fb8a5707fbd130893973a6006ed584d3a7d7e9dbc893bca66ddbb68d46be35f59197e45b546fe2ec2be7a4fd9a11f6a97e4e8fab898aab09e3f1d3e78c40f06afa8cb01b73c6e26ad23c4db3262caea6379cb56af68543ad9ad36d0f56e522e37a9ac725c6cde5762dcca9f2e484952a3925959aac52244712068deea782e87eac7abf125e732a2d09482a12804ae064dd72a4943ad555e34ae6008867a4dfb86132c98a23858823180b319a4c3ef69310c2e20a02c12d08210cc220ac7516605534700130ba0a9bd7fbb138d7ee57c2d3ebe35c300f3edea05391c0d194b05dd6c3d6e8e67cf421b0a3edc1c608816011360f9562936e6ab5985aee0bb556b143a506a365bd33ab235ab22ea4b9e86e97bbdd1522ddd2152ab9bd5088a7743f1548f76395c0dd8f84ed7e30ff411d91462fcc7f0c044453c26599548011a7be3cd26f79469a5c3a0bb1e5e21cd3a9de2dde0edbb4ca51d701dcea1d9577433290e936507ced9424e41929654758d7db09745e6a1bec18dbb84cf16989c158ab64fb4c6915d4d9a08437c5009297dc0593db7bc3bde188165adc74e1cbc42e783f18834ca616937f5088096472c4090e74e9d052e7849742b155ab5044cdb6f12c145b3074d008d155d118015bedd3e2e2dcfd58dbfd4af811d4712750f57e7009f9a222e1445382cbfd585085211d8b2be9cd233b56abb5a81d2d877ad6aa98d7527825dd7a54665083ef2a58c68317fdeac16c8b17ee784cd8e83a343f7805553c233d46d32cc9cf3427e8d427960c6e71641273747cf5a8a355af1187c6e27185459bc12d386bd915b6818ed2f15fcb6cdce16ba3f1f0e12cb620af34af9a6b5e61be7a5f69567bf59eb5c8a077f2ec16f69b2f1d6d595df6eceba6b1bcfdea0a21f38ce6ed7dde00e8aa7155bd2916d6ac3a8babea100bb79a0c5ecdd8a6653c53bddea832914e5c55b71ae8a066555a6571556badb57aadd355eff203618d451289c482c8a9bbc06cec866d7c1b3157e6d8fde81e01ad2ab92db9f59b76f1ee7eb5c59b638c711bdd4f247a19a9f936e9702d8140fbd9a717d49452ffa1d4234c165b44763cf5bea2814a71e727e6504a47dda3c309ebdd3fd193b5d6ad9bc692df348e1b13d2f85351dfdd558253e322f992eaa24e9dba0bd24b5749ff700fea4baa8b74bb0a5ed1ee01737c8d8145888ca7eec553ef209e6922292936062352b0d00ffd89ab9e8e75945651fa758f8ba8898d63728c61872080851652b09bfbca6f5b73f07e9e0c9c33cf7cd0080aace6fc6df26ef17698039b348b54a289a1851e4a21b6e1876eb9e51cb3480ea1c9a14b66b5b02cbcf2dc757ed3449ae5e2a616ebed096816c94f222779d6437927cfb6eae572af46de5dba5d5f2979e724ef4bf2249377be45cec52b39ec48be95bcef869c37794bcb4db1c0dfe23ff85baec9fbe043efacdfc4eee2a1c76fbd19e6976e73014de8fc2692ba91f3cce63771f3a28f3c231e6c698d055e4ac2de67191fce7b92fc1e11f6e370a0a586e8005dd375a0aee94330aea1611bfad2f2b08d01bc559b6c25d0354fa8e1981855b3fa6ff00c8a464513615dc60ed313f693af83754519a00cd000353526b622696b6ae24afa7ccd0b2d086c572a2efbaac11378685172969b9ca68d2c10bcaaae7db530b6c9f1d52d1387f97ff7b54c5a55dd03b21ff7d5eec07a55acf69f85d9ba896c0fec6761aaeadc3d5958ccf2c4d674516d98cfd79eafa36b613c533d6586a53e61230b1bd91cd1b54caacb26d44c32138c6d0cf02dd5fcd85089864f1a1671b271328135685167ff2a07e116bd467a255d5e295d86679c88428aab832a42189356c9932ab62820a57422261efb41984c131864027562ec81b00a1b32e7ad712559e8b344645a171b255d08446dc34e7bb245e92d31a5591fca9feca0d223509494522f398dc1105b70a7e47510c72050eff00c75953b0a05441b052a95e7dda0b4bb0d1457b488a7de456cedaa7145bb159c785360a0d469b51ce5892bea9d73f7538134a296ee701a4f87703f1ba925a15402698775a97f2ad09db8a29487fae9e90eb6b900946fb23c437de7a97f1002eda05ac66d009e361b09fd08109e890e5dee7081fdeaa95b2a281f3d866df86926a4e14184fda4fce1d12aa9ea1ac6c07ecd9304841cc30c35349394882c72f63e91dfb414358410f6c02d03f04abaa8a6032043bb337c1d5dcd7549d8af6648e44c53ecc74130107117aa6bf706cdeb4d458741d545b7e7d4e99d59a2c2eafcf458b331944d52b56afae890607a3d1c2c7cf6e68940601446cfeeb1155ded8d425f9a16b68177003c43dd634b64e38a5a9a9f9a1a9ada948aee90ee08f72abd97763d0d3b5d7ac70076550a83c58abccadd6808fbd13cf509031e3e0290b1cdf6918182c02361d0c6859d0e299cde69e6cd1fa1990b4c4836842b431487890ef521a788093483a616d30a8f3cc0eba36f3d806a8ba7156bf9c4164ad6e269f598d1e4a3b7a2b4a6b47ecc78f2d15b5ab4845a57c897194ffcd70afa31638bff5a607c74095b06a7595f95f14186c947ef4e015e4ff57a3e0221f6cc4ec952321d586aecd4d891c191c1611bd664429005919f002f3a283a17c1b66f39f7eb11b4e5740d982e2e6c74ee11f4310519cfc4147288c8fcc8e8c4568d980c8e0cec4a948f2ec344a689cc8e8c1232319927323d325378fc08f1b882871629e4b00df4c843641300190290f14c74199c1f91e6b576e22a3af5af0710cb8767620fa0b88a2d9e164f2b096e893cfad76ae2634bcac716958fdea327b644b7555128cdda1e3dcfe32785205a3c3c7e52c8d99cd8af478be7a3434f86871e401f7bb478e6ec218bada9d3a3c719457cf41e3d7a0c60003d06308029618b07a7a7599f0c0f2d255a51a0d72e83f3d1657838c0ab258b8d633e7abf59c547ff26153f5dcc12cb47ff24190ffb8f85fc33c5be32401fd88feefc0e4501b7280fbc8ade812444d0c4475a828ffe51290c21849035d267c31f296d7f0466892e673c13e9fd44ffd98f5e89676243bad3375e13a85941cde22a57641f7d5a695683316b165729e3f5d1a99266c1620c41ac528612d125ebb325faa459d1888f53ca7f54898f25b669ff6122fa8e9785e181ed1954802a534116ba5da20bcb9d65e20e6151a78e0bcb5de90afb55f61ba678019329980882182930519302133131aae8a0057f1c7bdedcb2d7ec6881fdecf313ef2c1d173b8f1848430347cf319002cb3333036d1cf399e694873ea5c44c108e7ee34da7551d3d197cac20638c9d8f8da7832b406fc9c27ed6d3e103fbd5facd9fe9370de3b7edfccc6867adea1f1bcff51f545e27f46e3b2917a06b3afc0fc9c7eb64def91357ed034ba46557bec9f8f6b641d89c6f9e9db8fa8185fe6d3bdf6e7af56c10420863d756a9dcfbbbbb760d98ae1cf0aabd07d68b77323db6e6add55a142ac618217d4d1fb686cc2ca5b764c074715c41181fd86f02cd1c4c38c97838bdf844cfb0f28d6746e2bfd5b37fabca33cdcc4e5f71d68510175f68f2049a14e4267dab9d8834b2a668f22f7ac9b758f2648c9c897393c9c48d4623b73eb6381a79e946195d4a19499cc90b8e4aabd0b0f25c058d2b5e00cf55d0d889ce5549a3e7fdb94a1a39bf750a886b1387dfb4e615006858ea752517c0df3e3b1fbdfa5410d53bf5fe761a26f025f9f624cf2f843a6d2f42553f5dc53131ca575d4a66a9e38b94a43e65953cd3f98022dfa4a7b9565d763e60f55255753597a9eaaa97cfa97a83a85ebbaa79bdb62afaa8e1f57ae1f0f2eaf8c21ab1b5dbc4c022d46631337777ab686abcbbbb23101bb3df7489450f9bd4dd956760bcf3a755b06511a0028712084579e2092b5c9a133aa04154e36206016a3cfc4c2c260e5c832c94a052c694274340130663608162e4d03d8d9051b624b62a450d81a804b58a9327731a31c60ffe53f1a832060fe6183618438a1c319e8401e5a700551123ca8ff05c258c9cdf78638ebdc7c0f958f469161463d6dc8cf21f07c568f2dc2c58250cd9c32b1e42690223f2c09e9f19e67f7b74fb10f675521d4e6915acedd38d6181d46767f63e236fe4f4140201a1851042082184f18c0becc7571e3a845d08bba97688d31732c981fed91f2d060305060a0c141828305060a0c0408181020305060a0c141828f4493bc77c33e8e1b36f597010b268e606dbb4439fd0516c231ba60bfbd92e4605f643fdd4e1037b670c886de0ff7cb1cd6806f9785dec906247145f94948831c6b814691b8db5ce9137f2adeb5618c9d1a494fa6761bd9f27b9cead5b61ceaa69274d8bae7dda1ddde8fc3ff2e06b4e7d13752b5049359116292a874b2929d72cd7dcff3b3dd4344d36ab52af5556a8792a67664885bf8e11d8e85c183b64a08581f3abe72a6068a1f23e9a871a5780aba26d347239f2ea682437ad5b61dba44824722e47cce4a4c837df449e2712a2791191957254ab6c16773fd4431f55f3fad55bdde3d7369148d32eaa55a713686aa6b7762bf7d06bc905165a119658050c2ade7bae12841847d935da3fd88e0e21ec56807fd325901d5b0b496a575d692b32d585fd201064a056d9297d0a7db67fe40dd9544a8ff9f494cab9f048289ab6b5c88ae8a8a45b34dc6bb31e6ad6ab5923d734ed09aa89a6d1f8c04383b0e8f5152dc6d4115693bd44f5083bf2e95fcb3e8baad5455e2451d24d14f1c84b01a1d4937cc769da88fac837ef4b02218474e474e437711b01393db70aceb48a8af15669fe5a2c69a5db2f4d6b1a99a6c597e6ad699aa68958d3344dd3348f5ad434cd3f95eaa74b62fa467a62694b900f604c6f2b1335e795d4c3a92e4a9d650bd645e7bc474a4c3d8e2be8012b5b0d402851839dcf31207a1e3a090c97588e7100e8e10bb65a35065b443a7cfd27bd851e3e7bec84401c9e7dab9e129ceaaa2e72cdc3a92e0d490bfd5a52e3d29cd9e6a341fef41410a8f3f3c6fb2149d13b4473fe16acab7ed45340464fef91d2cfeba43ae3e069fe4bf21f7cc1d75bc04287bf9250b889cad5a65ca4d04d6cc2e933229dab577be2c1537e24074a4b0c0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0dbd08cd1b514eaa55d168e33a52c99a5a5c4e2f356ca06ee048e5d0314bcd70cc6ecc50331bb31ab397990e981db31ab397d969e6326b9999663b7c669ad95969469a7533f766dd8c9b6db3d1ccbbb3d14c34abb33bc2acceb4d90831336d160380190048907ee3258cd971de801de5a45a158d36ae2395aca9c5e5f452c306ea068e14cc0e0e07cc464f8da01a382f3c27a0d30b05fb761d367abe3d478da06f4fd5c0f9761c374e40df8e3abdbeddc60ef74c5d07e37ab6a00d67c4d3f2d3c1bedd468d2de8db5fda4f239e6f77f1ae1501895e7547fba133d34c64b2a53b4289ea4c1f299339261d4a92b26fefb811624831d6420db33eb1473100e8600f079564230090c03d2986fa5042222122a084b6ff48a4184986138d4491dbf8468d9c39e7f4a664ffd1428429cf55b05cd1e3b90a16227e8b8f2e037c996d37bc8667c35fbc1a7ef25edcc53b39f55cbcc5a3ae79eddcfd18417f816d6c3874c8c336351cbe383c39f4d82361cd55d168e33a926f9ae6d46b71178ffac973f117efe435bc17b7e1d5f01b9e8d8d5bc243b9e68dde2406b7444b143f34e9acb894418272aae281171394bcc801b3b1979949041b4e8b498c8f6efa283eba7d97261fbdf42e657c7412e7dfa9ca47ef3aff5e4cf0d139927f39aa5649476016f7b6918fd1a34afe6d381f1de5a57a78265aff26151c413cf3328b21c24e2a1fdde43e631be9f126daf06abc9c5c688b2907eca3dbef65f6d14bde976ae2a3933a6e1b892a0ea8c458ff935e5fd36eec7e22cf06ec9078f138a155e32ad6dca42004db1edbbba79bd884931151779b220dfd124ea711ddd8b55a4fc8cf1a3b6bbac2e9d05bca2bf2e5a9898b7c4bcb33a697de6781ef213537086021011288e1052df8ca6bf4026e71549b3dde00cfdd48efbde1950f8efd11188ec4c03158190b435a887191f3d11fd02cfe174f8c0b9f2702065412a48465e3980f3ee0855675d4000f1f74a248d9c0d07f10f6c1283c5087089482a56c80c93794e23fee3fb8f3dd2cb5cba35a43243a25e2b40b028198e833568e479cae45220c7d50c14687d1edf479659466c96b4f22a02272dae49e1aec9b8e446812042b61669a66b19fc03e8499f9d62880d9ec87b0d0e1d03682153f52dcaae62064e6185d2835839e35d9cc87ef9e35f9a165331468af592c72d014b2d2138cef1ca652d6fb89228b220a7513a737c364d9c31dd1e533335b2b0faddcc036c54be72fe00f5f011b498965efee4e407777b3d0dd0c25ec0fb8e539d9a79022f3545d3004f6a0276bec20b666086badb269a49094d8e8096061c61811f0f5e0b59f19858632cc20dfc9cb352f0ee79a9e8a0e3f05a4f4d14bef2403f3d6b8e2ef1ac0ce03aae2c0d0c69233ac68728614540f6728f15cf858148e2c5a68bb1145c2123333a75c68577bff77c33be9eb71d54860cb275096a32c2b8e2c48207bea1b944052075308a2d097c2a0095fb00dbcd403938b3d752ac43631ea058dd1188d79c9391f7975863c2e6bb5401367f24c9c99c4149a381367e2d85bba5388bb53689b4222a1a71e694f0e76ba9ccde24cfeb00de7d4e58eac8259b4095651a72e8162cb74296cc73fdbdd8f87dc39d5ea626d0b0a45bda967993db11573e28a7ab5a89c9ce8137f622be5428b9bdc746f30798bb30f5090908c6d5abc1b72687117b7c1e5a65c30b9b8c9d965438bbb5cdb0d69b935aebc242eee56187244aaa7e7347bbe7be28a7a4f4ff7744ff7b410041292cde22be24008d43835344ec33aa7757aa779e83612550deea0e00ee4813d30067de04fb52894eaab1094b230e4051888c4967441144eaf676fd50aa321ec377b9e7a0f85257151a73a7447ba287cead284eab752a594525a6bad2c804268e8f4d0b5da821eb6874e2b740b7760e20228bc80c90aa1941dd0e01e7a646205a487de5ddbf3bc4208a95842826f54acf733a27a8d8e8230e58df743d180caf7fdbce521ac906483cd739cac640e461d21a4a43cb487c6a80ffda140524a29e7cf4cfac4c22c8a0256edc4d6e8d2d7e7282b793c8fe7aec4c1aad8a65fa37307a2a05dfa8226a4806de8a539f0cca539583a8bad58abb52894bb4aca1ca1882373a48edc913cb247c670224ec48938b127e2f44eec89b1e8137f22500cea9ddee99d06924142b2597c419f19f42132eb57e334ac735aa78a6c45694e27cc81395007ee401ed8036328a7af975e85b05f0d650a63bac33630b692b838265d34882363fb9fd78b4a15ae8fa7cc135eab392ad71abd5fcdf7bc17753f7be9ad44200af1c21ed884b681176e8067da3b662191b86ab79685785a45a4bd610f54a2556d8a81e5ff20cf34c1264358c0600183058c1530563033730d1bf70603d47046d9f9802fe367c3b78f78e5e519e78724e583877c1e304984dd646308a307f3c3b18df4d8288099628568875cab9865f811bf4213a0ff08c0b3663af1fd24104227f05956d903bddb5ad8036735c416037210e36c08cf449a38abb0ec303dd8e81f957d74ae32a6694122c342e702fc179338a10a027024d900b8cd051f33738b0eabed09d58d17780ec68e1d3318520e35b6a09754960d8739b63cc1cc5ce25c58c0318ebee09ef984a9415ae30cdb3d4fb1c40c26ac24e305e70b1b5b28f1808d32ec04820a6cdd810d2b37a8e183ad3440b5c06ecf532c69c2c60d96f0fce7532c2101aac79a9ea758f2c349c8d62c886045cf53ec10c57fcec4d69881a5cf53ecc0f331fb3c050e67fcf63c851138ffb914b0bef000b6f242d69b666783be4187599ec892c6732744c7b7139c23d8d370c14e0809354b1a697c1624b2a4f1b113a2e3f9968ec0a7811d3679d909a9cfb7c2df682724fea67195675a24ea0823c718a3b45eb4e6d353d0ab534fb9505dd07ce2b4aabbc6d51684f5066aa10603a20063eb004a62aef60e621ac05cec621c304ff410251027ae3a951a0275bedfc96cf8edae7a597605200538701273b12b89067ee013e453039dd7cbc52e76b18c87798a4e3dcd552fa5a4ce92b20023c5de9a13720859d790d8ea3cfa0b0f8041024662685675d1c8a3775e4cafa2e6319aa7d27e78929f542b3d7554b350d24fad4205d9c87934c8a7f7266a5aaca24d54453eba295155a95af59a6fd3d3bc982a96dfa3c7344b8b9a46e5949f3df1f70cf025777e133b21aca6c147000b2dbca05df91d14f62562d9531ce826af9225f62627798cdb467296310241dfc2f3d035af9245e739efaa64f97993b76f228ebb1f1cfb926f24cfe42d41f6e3d80d751829763aecdcc561a02df7abdcd6017171d3fd48ee527292f7a35ff3962dec69a38e001612d02cf7fcfdabc93f5baa5554bae16e9ad52f72ad1332fdc70fce3b27dd0fbe4ba9e4e2d4743f236f3afd3802b3bc8bbbd60969b989a5af08e91a39bde6d6357fd90581aeb98b47f29b683d23a794767dfcc899524a29a5dbe67ded9b9fbc22ed5de75bc78988b0d48bb49b3cd292536720ed26170f3e89b402a9e4f9538f7f2395bcda2c924b92574b60494ebaf5bb0bd2fc29209d9bbc73d356f23ef82592c9fb5cae0fe6999233cf90dcb4798b67dde4f193ec4d9538d2d5388f4fd059c62db1d9007bcaf0a10a1712a6b2c407ba884e4cc122072e244c8506472831c109a0bf8247043ab18a3096c4641356e4b8da43410cd736391b9a8717162e24700b329e70f5135870e047dbc2872576ea122a3b36706d220f9c60c7b58da880f2e36a2f050326e3070d21ce0441310042c2b5754318f1c30c22a10115f4b8dad361c5b59560132c9ac45c485418428830ae10b82e00f4cdd6c08b98abbd2b04d7663a81951704b528c9a204392e5758f072b50723e4da4e3008e2658a1520806a4471821c786c70d1631b38f1032a071ec0201144a0a2e3da7070d1eb1374b1821c577b2638e1da525cf4600f6645c85c5b0e2e7afc230211c45c9b8e28d288554105193baef65234b0c19758d17121e118e4300224523b7021c181d348f040118b20f8018ad7ed59816c842b4340aef63c2e5cf1721760030017bd06c209961d974ce7c8a4c85ceda576e042d290ca368ee1fb19417923e922af7a77d560d8660a48f5a9f9ec82cc29f2342c4fbd450ec335827a38fb7a9d8c5c764e70dc457d763e2890f99486092479ea317a292a7f52e7a097cf297a83c89f17062bb560498daba159158ae674f9b09b41349f9a548096254b96a1ef46036a5e8311fad37382e3ae91b7e7647491a05c4754a79d8f4abffa4d07993fa957af3f6f0d41949c217413366e562ac61b2409843c605e6063ab86ff78054b36b6507105a913a0c797840bc04e6d0862bfef17c3c835d6bc1e6cb87640e23bd8a0f9e8a2e20a3abd3b30afe2fd62a8978198bee765eec1dedd0ff8ae19047e772a2261a01d3476b8c20a16691ce1b241faa494524a299d73ce39a72fa971cdbb83bc47f44dc51b44f5ed7d55ad62245a92ee6e0aeeb79294314654abb8b24b9805fa442d7c9bcd712724c60e087bdf22ec1f75d164e66ee31cbaeceee7d2886fcc5dc941173995b708bb6c8ef3c8ddda3eba9b68e4f0b50e483fdc2e773fce451b739cf7b5732ebd6f24849d8bab48f2ade4dc1e63bd7cb9551ce7b543af6778deeafd8c702fba45d8f9a10b69e75ba4bfea2287de4772b839273befa873dec7397411c933c27d91cdf96df88de324c94ba5fbc9e7bc93df71ceddafbaf40d487ccd6fe2c82b72e41a99e1dbe191f8d28dc4229d74d801d93e252f1047b5eb6c539a072689b601d3024dc4188051d909a8ed530b2cc7711c11d15b21c7105272a80da24eb655dc476cb11a7e341e15683c12864732174b35b0f0a6f003d4c0a4ed5036ec295eaf9fe205fb4e01f4ce9b45f6fcfc1b6c6e52a7b141e7988fa605fba1be5550e81014b976fbc3c31db611acb0a52dc60431b698e3fdba20251aa23347f6e6e71e5028f9408c3b6074e448e1b881b251e3e5e4d262b22552c76d2351d5e894b1a5a4676c35d87eae42842b1af67701aa68b598a7079c55a8d42d5546450a15cd88000000008314002030140c09450271483c9e67daa27a1480098aa64a764e1fca922088619031c82800003000000008108c00cc6c00004af980b93fc96add1bd16b77922ce1223dba5f211093cad1d1c5a293fef14af359ca6c5cdffd5e72c934b3d0273432818c7f563a9dbbce65cc67a153f4d7069b5e4862df1ced3e96e510846d4750faf4d93933e533d55db8e6963d036f43c2b9a3dcd02909c423643c2714d1b0e115393c5770f226737c66de09a3cb1b8cb7f48f1426e0102d27bb43fc0b9dfb274dfd89e9d0065ecec154f71383f02401f1bf2e86057382e104d51bc8ba149f551d8fe1df444b00887d7c1df2a985b8e319f9e5139dd68c9cdd4913b035902a4e46639209e6f560499049391c93339dc9555a1f3646d654e8a552d884d3c5ca126e4307ac21d0c21cc6b0964f29a2cbcbb618118dd507164533f8edf81f13241283f8b0c10a0e2323b6803904d5914685ac0488f78015df64c91e86b3a85f345413a005fdd81eff7c556d8557f4700518535a3add448cfbdebfaa762faa3af4db8762385471eae49c48871318e2b0570cd09db284fa371ca6dd2c93cd12442df73a5c1e23a77c1b23f809d0f8cf598d2301329ebf406354757f2ab3930b68e2f2354571abd4ca2e887baf16cb2ec892baccfb8c11d85673c668734b58fe396344053b8a7bb7713861317417115a2856b8b132dd24f99df7ab74ae5bcda4233427a2c002380b2cfb7a65236644de35d77c471fe0346ec9751ed55be1fc19c294e68119ab45ad11dc0d376ea60ca5c82dca28821cd845cdeaa2e6937495f77a585b2a9e93b75b7717fb090fd90977a2123a0c71194c310b0b305e1ded26d2f191912568b2d6857043672b1d439097d985b342ea2ae10e64956d818018081b775ae4ed8d7c5b23246dd315ca84246fbb8ff1f11d48ace17b231afa8f6a11aacbd91da8ff4f671c20c2d42233dfc03c407ed2ca09beef840db1038a2af60efa0ad7f4bbf6edc4ee31cb443138ef1fe24b1daa33adee68492701ba73407564dc31b293a599ba3a10d6b7f51ee7fc8d98c6d3323059bd369633b0546c74670c42b0c8098947434c9803253cc69aa7146024c45218ec293a654cf93d46122b911ebbe5044f5e7d6539e0dd6b6d4e26e7d6e135c99f5ed2dba4c8740ad61e70f7d19ed6ed2a60271fe3067acb9a8ef62fd9ea3271b7d6b58dbaf0927f08e7e7e2558412a3cd0f2921dbe1b5f7c0aa3327c2f4b02090fcf4430e2e8d02379898a280b55fc904baa95336805e4cdc9bc0dedf4b6efb30397d45871f79b206b13080e079fb98ea2af37647d476d1ed9533b8ea55b5514fed4c6209ad708692410155ab487832cb07f7b850d0275e501ca66df4d15d2c3959743803aa6c1953ed75ce85bcf6d80f5d76763f1b24f0dc9435f0312761384d8df0bb0d333488a1ef42038028abc7440f34b3cd34f4508180559b636b0cf388ab9aaff1d36fac625c6a0d7720a7d0459c39abe857fab9150e6a266db49b3ffb3d422be26d7fd64eddaa0236bf248544321b33aa21dc31f6b411560133f3b3fb9f7ebfc0a6f642e7b6a37834d6e5f4b081b604b2e6c32a39aa546f6e225ce2bc5a8f09a200a9ade04fe130cd859d040bce3cea837218cfd0aafb6dc01841dd0ffbe1d78100cc3cddc9eefea15b9adda73fb9134306ba09988883dfe7e605b4e30318ac24c2db944c9b25b8a1cc3517e2b363597ce32d51bc5d64b2bbd75b779e74d8d0ad4903299f3a08c4fa847eb67cac51cc08fd10e6ee4a1f1496394d254a610302140ce7a5996984a0831e3df8c762e309d74094f84eff08547ced62d21c907cb5cbdcebdab3941accbb5af6037189e32497dd6dff7fcaa0f15b4c743ac9f7664ba0d2ac9399394107826ea872cdee9e1042321abefb2517ab1530bb2e65a4943f5fcfedc42b0d82a4b08e902579a36c50b23d80de3aa86d470eb7d7c03a7a55b2303241d5a70aae3c26ea299b9cd4ef88c26d33f8cc24b6d78cc0ae3ad25fd5677683f87a01a549e13e68cc84d6294f0d2f335a6b4bc0076d96e76cc922aee195ae55920ed1b4cab341d40574d6209875f9b4c227324314123b176b54226c0cecbf3c4a1bf49222d344607101bfc9f6823cec87363f09c83f0a809223e4aa4948ae548f271c3a31b50f87f8696594fec5de0be4ce4eff8bdb0fb01253a63c48203b5190b3f802099e604d7f9910e2b92a7b6944854a516264d3d47f886053e0b33a9485322966ba95a42b81a6f41712280d2a818fee83fc68ac81bbc8732b83395f105e61b7133c2fd1a6d3efd8cf00f676f97810f6f946b380c66be74b0175c7a6c2f2f4e6c94d0a6a56a54c843485b8e790d76d5050a85beaddfdb48183d253c0fb0df611dcb0409cbb82cfbbcaa1928eb2c50158fadccda6151ebb1e5f8400ce5d182cc62ec6237b3a90f70853d077b8fe9afcf082147bf1a184a446db44f25f73d0aa17313b9ee9aedfe87cc703986737b22ae5b5e8888833a104ff817170458505d49bc9cb96de1fdbeca6d397e7ce1118f0a024ff030de7efaf502d91af99359cce2741783bfbf993f6078f814538d5e45f24ed935e587c2440a9ab9f2494bf49468d34562f02dcf6f3a312ed7c23b03a0275aa9c4c6146710cd285e6452f1563e699eac4a72fee9a292cbe509c48d065fd8a823f0b7216f65f2019c535b53dffcaa60b1bb5c4f92ee69dede98b66e00e800a7a2fcd890ff9dc0774ed915d0751c550ce04a81dfb5df9c2491406f7774304de8a54855def66798366610fe372e2181768e22e66bff55149056a1b21822c34579d4b1b36cfddc88c68701d94a2d76471af0381facfce36ac1526e0c0c5b4849627ed045e2a411195fb6c0d6c4081893450672fff1f2b81ceee8df981f6d7546ac261a2e10b88aa4afe9b818e4548f087f897821e100d57f03da25a118cb3efd23813b1931695d5b3df71e778655168b0398b7a22e373024c3c2c69d14e13c3349eb99f1b5821a6e9589957a28244081dfe9cffd2fbfa80afc10e7a3c2cb275ed8c5c0fa54edb2931c9f5283fa00dc303f94780ca3a47a3fad1f17d4a463fdcfa7e5dc9add12e9ef3ccf1729eb90deafe79e8c48992495f5ee01259a0af3bcd7d21b54dff081ef59e5c4052b20da55806a1889de95b5d382e350ba65a57694847102f99958cb8d39a5a1320f759444cc635410db3cea322dcdaaf4fe9fc4faa9e42d65971494893a1e7bf52b8c3d491c532d8dfbe95a6e18f71675c35943f7ebfc390f7101100b4bd6556cbffe27545112a123ec8f7b626ce7f3f8c2a5c968eb4dfc9350d9b7083a9671db46bc609ba2dedf85b1ba782c524cdd3d3dd0d176599fc3855584f8e8f2c6bb8b1a28d99856ecffc04b1700ce250410b9f97c551c99c69a9022d29c843cb090868fb16a0621d6e64cbcc4f062d63da6f4d0813f1e9e129d229b6482ab40ac269f27387dee6d18583a98bc803c4eab63a056cef097b009f22733b45a4363a16ab63d0e24b302c13c3729488d435970ba74d17d3c368a708be25f5d1f0f8cac946836941fa0540891ae8a912218f5c16b2118235dd3f3705192fd2367d8523bb01b8d5f7e0a38b8a2373d38d6e6fcf7b87a9b09f54b1cc612c21a3bfdb2bc318997359d6744c15a2715324f1277d7a577da75ec71ca5ecc3d6b2d8eef1d3ec410454310259ac05893861878c3944454ad5f48e13b412ca467717d447d972c9a33ed78bd5ef8b824fef7373f0e448ae437100cfd42c4aeb3060a3ead16536a3e603b210ef04bb757c3f44522f9a9427dd5d31eb6b9a5d6198813cc139938b015f96c4df971b085e28fc516197d097b5a5e409704df1066d1bd69548385327976b4c5ebd94d17ab8bc6045634bd9f89c12d0098492d8f75a4a6e38dbcc6030b7f55306263fe0203826b90eeef0bfa9872d4d69d4033cb982f6e7975ee328a1c8e351b3a3218c5a0d0a93f4552dec30290daf73952de487c22d6dbb777f079e90e39280ea86b33a3b302676690404545039dd2343c2fad9588aa3a143d5b10fa1e047d5a161877fffebd8d9c28e8b7bb0c70320b89cc45a02ae46f4eceb6a4648c84eb339c92025f8298200149e9af1177f218f0e5a96aa56d7c79b4adcf37768b66b9d5875f88e1a4eec7292f1513de68eb4620525caaee9a512faff83b71cef50823777fe35b6736787f5edbbff6024b7fbeb0a282026e157eab11ebef73c7e38b730429b81753c3b5c076fa5becc419e324b5bf884fea339ccccf20f70dcac546e12078ae97e50a801c5fbd3de0f68321fe4f87aa7eb8e8a71123dd98c1f3003806af8a4ce0554e9859b5681eb2ba832854b042f18790764b1cae5d0d5d71558fcd80f2ea8c25b69247fbb9d3e7d79d617e364f58770fd01766f708605b9843553aba7994e0ddb04c8678eba1affe914c6b0faab877476104bd1fedb390f1c5bed83845bab065d2e5331d679ec828d3a30ecb343257f7fc3600204dd9dfcea2647952a3e46c05a0e02022ecfd0c56d995a1352d3aab46e8c10ee037bb1b3afa26af747f702ee9e5fd2db10e840c536214ddd13a4061ba0ee0af9afd2f24088993382a819b977ead29c64481342aa29dbf69192c31eff40c5e9ab0c80bcafa81f2f4ae9e463c0f7763509020948682532be3fb031bf1842d443c5ff903182ace867c65b2ca142ff1131d70f9ef3d8ccd1c6f46dd17a14c52d558f4cc92797220151cef45156146df313f5e5d7c0a5e15ec157bf2f90062e26fe9f0dd882392d4d8c0115c11f629e3618817bcb68b8b17e6915c1a83f14560f068e34cb437ebab3bdfe045f740a1c00eb726b79c9b4dba0a62d68af01a1f503e427d1402a8ae9dbddae99ac7f0d103ff632b12f0c5c74b673571806103ac101fc611630ebb2759c016272be34d43cc3780315eff7fdd5ed6069399961a45a073e1a3f4da8938dbc61e48ace08020e92fb88ec8c3e44e915255ba59b18f87ae67f42123bcf8e6b8efce90ce0fb0572e5a17c31afb8d8e401a629609978e12a345d076286ccc8d6e445f9df93bf717da95a5b31d609baa81cb4e27e7bc90b3a21c054048beb24a615950f9fb9c0626df2882ecbaaed5232e53636ca7d55dc5628efce12876f209e57b993497833d0c11dde077abcf01f93cfe4036a576748cb5dc6f2f43978c148b45d676178ca0f3c1b2489b66a664a080c794f56e74ff00759363a830cb08423ce7e9707fef5f9e7ca8f053de0c0031dc88002102800810f20c081071ef0000316900001083830010e1ce8c0031930095c3fd2d467f1ae5013107f490aad782a2698e50e9ae701da321c1eb890b1615d5d2557fb8583c5ea07ed0bc4eef4892a82209cdd06ee7153780e72bac4d7030f405600c2f9a07dc5cdaf7017d88b9a2d0e456ef3505f52f45c9a1e0eb20e529ac1a945453e73a896f0234d390152bbdb68bd1af7d260b0bf843d391365b33d254015fe0bae9ba0f628945ce16d3794bd7e444b355f11b3bee67543ceab047ab3fd6a378d8bc38ebd99d4422919395fab13e7fd9b12978223402edd51cea56dbaca808ec055b272f42a1281a95e152f993d5b767bd04a288182e516a2d48c815ed228e353d190e75183910410239075eda1e2c6a3d6c9545c99ba9743b8c6b0f68b372e9cca4d439400e0e138e07395c3ed9720e71862ce4553351b8299bd0c2c7a3ad5f9f7a79cbc708b9b1600bf701d2eb97f4cc8d95a8408e293b806e3814e3ffc83e2c2315c7fec48b603c9fb85ef8d4c19a389ed8031f00d17aa3e7e5f487db86bfa852df6e4a41150b8b55ab48fa3b1c390cfa3938ee971d7d1aa0c6df6d8bdf945a081f71cdb52fffff1dc6a5c927956e1fe4cb15279cf33dffab99bf9ac863b4bef64bef5707de6adcedcfbcc6b75dcc9dc406fac9f6b32cfe99ff956ff6ec9add3fcdae6a2a2400448acb5964d0c143d09b567de526e3b53e4e5e2f284e15d8f26475e991b7d269ecd827e261c44077f1e197c06f50afeeeba3eb1516c416076abf7ed236e3f924d23d21a91f623683e62ebf96c14a014bd6832e236236f1fe9f6116d1a9186497a580bbddd38e2c6236c34328d91691e490f3bd8fbed23db6c246d23693f920f1bbe79fb116d1b99d64c7ad8c6eddb23df7c9a173659392ef71ec63d37a7aaa79cfcff3cf27fd323cc0266e3890d8021918219a5df05142f7604286441b10f5bc9244f8bcb525dc1dedc2be786007090f783b9db5c5ce4db706cd75a3dad470c5ea1f9e39c4fc84f7c03b058e661310bd19bb29c13c5ea02a9a35a6182870f2df0d9910231ea77715e10e75fc1442ee15f214b336922946713d7e2ad579977de48351f401058d14d79472dd3504aedf9c05a3f04d5ba5fea6cfe53ca20ced2d813a547011e3d7302f69f308060c341ab28de56f5d7b88efa81e32610877f3518ae5b5473918b959a96d6a9274bc9c35435dceebb2b51ddb271854104222c50cd1cdca164cd4a8991b0f6293e1a403d8a2087cef0c5c46840cc3bc472138b00727699d5390e6e20b02ed48305552e76c98fd3ca01c2e83258014b495f032cf9f21296296e375ef46947a2a1205f73094715c1f43bd590a581fc81f0e014add831bb131df74b0272dcf14d7a2156a51aa3b9a696073f04c7baaca5846ce1df19ab7c735cd349f03b56a524a73c0022e12ac9d639f8ba4f9402a2cc020f473aaad45db03e2ae791426285ab749d464f838a94bbf127a51d202555335691a69a8127a68f041d90e5501da64c0f2a159f989e6439f915c7f06bd7ebb2e6d1d8cc2e50a9fdbaffea4e78efce29773afc92140375e295dab0c3defe37f756e854260fbf66891f9e97af9662808bab02c24bf1ea14d85135621a956a30ae81c26b11aab5d9a09aaa5313ae6cd70a01175dadb85ef8b6c4fb18ce37664a8fd053183b9c200226b94b3b350b9a0eaa6ad0465186078d1de5458818a99950187d14761207bb068f1b2307e264be1fe27e4e9274e61ba80fa406372a8ec742f4993a5e1869d35b7ccbffac191aa27055363149577a8c7a2305e32be8e529d4283d26b5722bc3eaa66d77c2adac42d464a4cfb561c5ba522fa27974a4df92a2f77a8ef864ead97e3cba975059dca2d205aaaf47d73ae16e612d810b76c2ee790df1caf479d8738e8eaf24e84df2f975af4eaaf9f2ebc47252660b217db178398f4993d24ca4b298fdac858c770faa4ebf25844db61ec948b069b124ba13cc3b1e2f8135da21aef5650a5531f86cc84aa5966a30652806378a2ee1bb90c17e0dad747dff7c576c101634f53a40404028f8f289798ad2762dc0e8ce31df5a0084994376f54541cd77a480327f675b135ae6136ad371e20f57d8323c1d1364121e44b0b32a7250d6486fdebe8887a9f00c261772c59a116b0bd8c1bb044c2eca34b0b4e4467d755e5eba834f520b631e9b2c03d3ffa4bc59476d40676b05caacdbd72be8ecebb4cf5b1577678852a00787531dc10d1a2a3f14da6dfd76a8aa24932356ce6cf20d144a55523730597e2a312649702f9d16aac48a06dc00a8dbeaeaa23338cfebdc89257c79e22f2a23f6e23ec9d43f9e5c9b42673edbcecc6eb5dae015239d85d6341a5af8872b1d7ff44e9433de909302879b33d97dc45735d21a09c5e7e3b17471037ed7538d997326f03f68e22cb17b13a01cfc8c2735516c27c21d13ae571939281eb12310b4bca6f166ecd07a1a0710c51bf54b3e6e0ae6d87a589bdf3d84d6ca98f85d48ea1c7b049a6b434a311a01ec60d7b071bea5bf7fdd1e245fe25f6f74c5cd15423a62d1f13a6e0271c392ac635fc683ec1a2a0eb9dbf81a5d1e98a8ffa272e02d07d94c0890f610eb4c3af2721c4252f50945c5ad73ee59f313fa7264de68a06e101b5255bfbdcf791c7f31fd21eb775141994c33f973477fa56a6e0311e9076da925a941420ad2bd8d06d3157a494a5868ea10930ab0d5f8494c86dc72395c56eb0221366fb1dcb19477586067d088c4712599ccf7ba0c666dbdda9ed2d44aa7c6e623fcf8e990ecfe4bda49efd795788bf89c9059f8f48644a9e82e7ab40fe1bd5691466f80fba112a9f6c3013708d431578a19f8f2c542599d460db1610e16b46273ded575d32bab08d393cc55e0a15cf449de4d1a3bab8bc1ebf11b6a96d35b399d6ba80339b20872d782fb22829ff9daf2d29fc7936fcc773f1257613436a285efe42cf8995f89cdd90290cc5674ac00c1eef9f613e27232b4ae19ccda99780e39c65b72d418f2e648181d783f0207dbbbb82571f100b292e6da16cb66309a101eea94f783c4ba47c718c0402e46c26831b3d405a75fe50b762bd4fa06a905f02bae32d6e41b4b669b4396e25e24e1dbc10b34932edb5c9cf7adb8d82f961f5566ecfa200b4872be8ad7ac6c9c1ea6ddac136d4ec646084b6c10e0f59bb6e4d25fba47b3d12f0429e36a5b8c970af29506be122d6e6a01785e3d2d1e7b5241675f69316367c83aba37bab7272a41247ee05b9d4b00fa03dbf92be9beccfa0761235ded8782716c4ec48af7282262c0bd0e00ea1dac48ef79a922e795d59c286420ea473172b101a7b550164e0c52a59853896bc778ad419a4840f1510ff8e1829f2a22065a0232c78f119cddd47e580cd12cb9ec854bb22eb98588c79059d9579c1b734e0026e45f6ee9b51e1951d16a5ca69d67587db92533979ad7dcfa8793a34cbdb2a5f743fb297b6d591bbb9e7ca6501b4c09a9262b7e3923b7fc5bdcbad442d522413ad9d7340b859952c360a5dd54fc995f98b917a821b124d0aed8edcf0cadae420dbe02f08a3ca58682cc46603e4ba526e8feb5561e4404bbec7a593cfdabc72948c7927fcaaed746e4e897e467dddfedccea3bf0017d5fc6bb0dd28c01f7837d07d225de5c4af2acd5b83fad8d1378b8b9874ad715174074dc99c5af0bca1b7817c72c7ecca3511f0b61c0f6eace7c4cff8d880d1067816b5ca539a0f7bb6b594506fae6751d22feb6eb02cd76296c85f13d5967a635d8824deaf557609fb2456b2093883774e850f3b951134271fe44415f91ddd55574b034a63796982ae1c14adc7b365e93b10882245b8222552c140f55cec12d09c2ca56f5d88d6810178a0889bc14eb2672bd0007fdbde377ff5c417ff784bf77c60d9976af697af6841ec858b6ee81042017c1081ed8501014d91b4a158533d11dc9bfaf49359bc06201d3392f73359a40dd29f0e98e08891ea9446f4424858323bf177042f062219244e8ba0c5d49c0eeeac1366af9e46448f42d3401919050765111fd4be8d466b021f791085ad9d2688bf5c2eb4b581a9ce2072871870d8dcf0a2d0e52c19281286a4a41b5298022cba04a9fdac418fc557c18c4dcf002811b63c3e372f4e3023ad742c53cc18cf4103f8f623ddbbf2f7f991212f01335d9f99fca315403b03fd4eced0aeae32be3549300790eb86aff8fc3ef7cbf4f3cfa9792abd305662ad74aedb68bda3f593a2d9b6df64e02dbd61c4333cc88e1b9e194f9e14d7b243a75eda00266eb5e2933c94bf9fb39fe2fff41c5e60ed92616bb5759e38363c2689f9327917580859560475ccfb0c992acf7faf1fefdb83cd5e6510d4da4de588f8c2a1e1d8a99f5489f2928b55b8b36280e4c28e62c54d82c4f50bc09c7e9a6e882b9990c4a0540729390aefb8d4d96bb9ba6714d6544455d2664ab0804e602dbda4ade506a8276e500836c7070f90699a9e83ae91c79b6817688954478de9d20df2e7c6f22b0228d69b2220ca524e238f0bec2accd28ebba07884d7aab812659ffd5e038e0880f029c1e56d4a4df32615d9363b2b2043b2407ec332109383e1d7d746c5fae061d4ba2ca8383883d0a2579aacf9a29b31508e6e8607bbbd6df52d35eaec7613af448b41e9df57747b6569623ce63a9ee7a56cac9cacc9d4537a1b69365ce4e3f10d975cd2cab2071bdb604477bd4cdd7ef84a7e35a324434fb4a983422f1499a350230910626890628512e8af3eb2592e35f468c60c060b86569b8d6e04a83db1385b9df462f0fa5a370c7cf076d6c2d36311060a4feb4cf5a367ed80aedb172da3cd5c17605a2d472d55e558024540ec8711854c537308cabf8d28440acadf8924d3c7b7c3e45018603e1ed8682278cb2a30ca9288770d6d17ae1383a6dfa669d3bc33701d98980b8990f2de2317be29c07582f6821a58a61727c0c5d2ea1266f627a5b4d5f337b18070484633b3de506cae04e86942da0e4d3dabfaec158e4b0d2eb21065a2007bb6f4d52d072c559fb5c609fe5e22b03ed3321e6e3f70fc994b9c7df5f5cdc146f28c7c49be0f70a1dd79f3af79faebcb749c071b6354b3ed0e4c409ead8266826c9098192f8d82a6bf30be2d2fd2e5ecfe7b25b21fdd9c0d4e6ca02acf001df5a88a1418b7f6d18085474bf4b7f3f8abc210e087cf23d8bfb7cf802146bf8695b1e98ea47cd03b896e303f4c80500659f9210f4208cdb4c5859caabaea39638e0f41f5b2873e015687b75299aa1d09b502330e8ff6f4b9ff3648c47420130ee6aeca79fb0b5114fe347f6ab8145e83c0d1bfeb201997333e61aed3169749092cf9bff9d32e7ce04a84258ec8a1c5b0a42fd8153698b24b59a004741eaf1b2f3625d0226780a0f8305d0866ac25a0308d23978f106d7612508cc9549a072f263bf580e580f08cbf98ab480e144d37cce0c2b3e5f3eb44a13f215740d3be088abc7c3c4dcd925d3710698f2c57a6f19028b392f4ef8bdd312975506a9f057dbe4884507a624039904b4a4ff021954cc024af6cf014b2f97a4b17aa35cf176c295ee9cd76057ce7dacd2935ba4ec7115e7efe01f85eeaf96ba004528c40b91141890745df7aa5e785b88c20834ce44bd161181075e862e4d90a06c34669388f9c65540ed785266d85a6648c7655ef1f5fd5e8f788fd429c02bca9d9521a1258f93b3f285727b60286e47303bc7d455c28d923747fdd575410ad56318e93a734a38ca11ade6ae99c6ccb11f3ee98dc19ade3f4c9dc9d33496cb7bba09a780d86cf858fc683290f3177cb514a13f21ce20bc7d088d59032720854c43cb1d2ec0af20209b366b8ea88078f801e2c11594fa0942a0c366f87eb6e0ef16bfd3dcbe8e09c024f1bf198e071abf2fbb007d2758755a1b5504399262bbc00580c1736573c9d636eb11cb33b214b6e588256fc18e305bc0636d6a6801234b92834137ad0c4fba8904fee9a5013786d249f0109c8128a62d29ef5f4bf4418450eabad22fb62af7067faf6897e2e0cc0a20576a73d9597b4cbc8a80d167f412abcf69a2a216b9062f3e17324ca3f6d17f2bc45d06c1efcda153d5d834c56ed1523384d979c44aed46b5265943eb67e83a9ba9af0d8036fa4f30034d0f1d7516734900fc026d95300c0efcafe445a16c0aac5bac16a25639b7bb535af629f32512a749302005dcd51feef31ab6d83540d29c9ac7e34c464dd00cde1de8c064d204ed9c377bb224302bcf5a2688b2dee4b030abe0d6b1b042d708fda961ed4515b308931e6e468ceadb7e294245260d9428181c118aa074321b2467d5feaece3fb0b788e1b0b8835f4d515e473c152d8b3fd16332ec5516322e3ffdd7cf0b35f79beb5707c9edce13dc95090971845f51afffe437c46539c768d315f902fd38688aeee68cf60f37715da196c89b22419590024fe50093dd4b49bef5b254401e6733880490ce25d0e45b2f3914fa1399d333a4517942f2bf09696156953304a9342ed7b0513a86b4fb8a390558869f7490707c9a215cc52111a32e4a1daecc94df8c1fa5a314d98e0881c9448ccea60dc5d015d5a1fc27bf10175cd33888bcd28e7c1c24e5bfcf533ba29f61185af565dba5cd6176af88e461db3043918dea4760bf9d79cde0f59a3496c705323faff5ed781a19172fa257815389d5f74d6820e85a641c5679735c5bda88fe5965ade7f579ac31513ef4fbff864e864352dffb4d20ca5aa191e643b09d7e5c067817ac3ec3a1fce40fe5ee2a41beb9e6ba93b2dd8e4994546624014e32185cf560b7023fd5108f868eb611d0e80f8a3a65fd10a55f2d4aceea40e2a532a2a27be41df7368210e48e28b3ee0485355d15a5906f141de54c3792e3e7a7277e07b0c3dd45c6dbc409104e4c5870762b8f4ac4ca8150dcf55793b0020e5d7e395adf0e4db46c5a789d46d7cf01dfa5e881d69dc76004846a0b4a73301065f0d98734a8fe6d3e19cea9cd24289d5b52f16a73b0d0a88ee81f6555ba42ba22c0b37c85e854524583251eb679021aa550b63aec38d6edab7cbd9c7a7a965702f015c39b3b53e2a1bfa3b451eceee3e26d0decc819348c93b028a9c9d7e5207cf542dfa4391902f8e88162a4ce9f8d82013798ab7653a60eeaa84531247baafacd393857fd01fe9856efcb6e14d1f33a26bdeba4c013cf18199895a06e99da97ce95cb096a3027baa0e6c6dc39e0dc822ce622f5d40df537d2741192908f5e288dba1552ef299481e5d70a208269852d07c3052b8da07d0beaad3c96c1c06dcd166a53e50b9018e682e6e51f33c0d7efbb8faee72eff3c843218728c47580d61e792c2c4a9096f603e11acf22e10553c934c2d9e440c28644a77bea7cc49e3ff9fd80845150f13aa13330a817ce908e14e717ffa11455ee604a929808667bbb0389cea54a6f1a68c49ce028eb7ff6d1d9191f9c223681bbe96c08e4de46ee696c099d0296bf3911073e945108b5272dbdfa95b60d04cdcea10706c6e5d578513af4e46e56d0d917fefb3dac8abf7fc1a6c59262f38c5f0a96b946c95e7a0e53d4c1696232fce84aa7042c65282502943f84ee21f590e62e73ff073ffe718ce3c88f2a7d64bcfc029d4e59a7a966ad802a7ae0c8979d3fab5386d0be20fe01bbbc47137eac46975457ef6c20480ec585bba536563cd321380c05ce90109bd2d0973c3849ceba53146582efccb88fd69439303a59c7c3a3c186608fed750e4444a2ac6231589fea6e445219bba0174f7559dd4db935a5cbb6497ca2cf78e1a7ea0cbd038c9bedd191ae797f286bc4a31dfb2da7ad9bb088a6136fbed5334e9137c3bc880740a555ad034c0531761dc0493e5218431a026d05e66c0c89213b7443fba8ef4fb7d492406f29512a2223a24510c2386c43d2291ceb88e63ed4be19510ae447b1c9720078a5fc3fbe98bb8806986644df6b2ecf4519691bd1e23578cdc31fac5687ca2d2c8378762158d2dc2cecb34f1a6ae402617f792ab356a454b73d371d6a39c31aff330944411913bb3e423169028d75a165f31de76e397ba12a673771ca3bf030d77a1adddae6e81731d8272f96b05c25c399d12a6c6a0993ca0cb4d04cbc182f36a60721b0f24cab5ef54b95171052ad259493c47b4d6603fdca1c23474d7e320f5be0e8cce2cddbb88dadd986e7d1fb5c019a2b8ac83af2c410aede716aaefeb40ba97e9f935aca673b664c9ded7810ef4328d5c38f572dd6bf5787366a4d44ac000496c88b70d5c2fea2da4eb70dca109d490d16de8ef25a28ca994624bcebeccd1cf1b35714867458ab81c70558172ef3ffa828402b8785daf0882d9d3e5b02026ab9290dd9e89b7fe5a3fd96027ed158d23f05ebf4c575565c4e41df8f00db4fc5cf66c669955e3c5ebe2d792a6bf2af0375af26dfedbfb34a50cfa2cdd30a88c7a9478d9770da79ab6efc86854c944a9ab27bd398a1f48164bcdfdf5056f9f79b756712bbee1e1342ce24fd4227a95017b24cdb19edb3db2ebb55c950695c15bd63f897a054822529db880e198627f8ef12605d0b38c200c8c6bc8c27e488f49806f2993eefa438c4c6a6e1327ee8e53c600377dae095695550fd4abc03371ac7b97d38eca6d2f68200a6a53d55c284c2402107e3ebce702816ee32fc118775be05b3fcb5c41ab03eaecf8ee828b65cdb07df22dac63d483c4ca0e2ccf2deb9a2c87f3e3c455a263a4a28150d16a10f23e5b107a913b7739ab1a51a560fe638b7ce151ca6405b2e23f600986506771b9ef2b9fd7129185be04367f06095fab843931dc3ca9683c1bd343f1e5282f88592fbabdfab73f62f9c2b2b5489182c6c9c0378726a0e09b296bfbeb0027ec35e23fc3eab7799c7651ac57fba34823c7b0bc2038b805422758c70cb631135d6660eafda8d2d658ac04b8a0345a20b0d41aa60719301319248e879a823d94a7135bf743674119f5267f3746eeeb612b7c7bd9838880c0d6be1bb95dd831bf8446e72bf08444ef3390a73a92e31e6dd52a7905830ee38dc8d309e65d491bae67d314102e01bac3b033499c064ead38139d7359365d46213c4faa77c737513e8ee66da8a7f40452f03c1a8c16a1308f9991d4799da037d4de763d34418e7b6fdabeaa9af1349f00f767b30e8afbf9ff3c1d3b28b54a2c399fbe4b8edd2c51bd986d4535a71671997e25491e5446865e573220d90418751bffad5032998a5d606cc80f3085f701e7878dcc2d8a7172fd892bebdfa4518eecd682857d68610d311890576c1d010ca51f3f251cd992cc16d821351fd2f5f543a7cae858fa818acae0f3e1b425211a1676c5cf6f866e7ed0335180573a11ae6b2c7a87124b034a8a0210be2031f2d90a4d20527c0ea27c564d336b5f572952dfb8e064208609380f6f7356bb44bba66b173701f1fc1301e21b8eedcd7f4259f9de08a865c350bc431d0cf1f71ee9fb477edf91d3fb9daca263879f92504ff1b73ef2f35fa4c93e7f928642942c99fc58fa691227238149c64b73545254ac283404224be2b1f3b1a744f2a15b9ce019267e530cd4f76a528ace26f5a617a86b55987edd745e7a14bb2ddccab5975c8f01dc162bafe3f33f49c8a6caaddf532b9d2fe846fa486501118878e0526c7280bc520240d5e0b1fa346094c2fb2c5519cf00399f909447270d4449d035e4d3d0804ca890dc3d00f9b247405e5ae3d7d4eca6306c678dcc0558fd45bbdec2520aa11fdf9ca985fb35364cf31336fb1a1917e6fed5818bee792386b95b2ec510be3b844525467631892fdb3beaec5930f898774bed517686b57eb73df93d1abfb263749c0b18f9ee2219c738f28c23d05e40b0d809b9f05877b640f64be98666ec8f25c893e051f4bd421a84fb62ec0d1a7ab494ce4eaf6159b44e252c6eabc5e6cb862a6f9a32b848e001b5824056e58c7a51b2a486f8962eaea77314f155d3360b00a8b14ad885edb6f425ebea053dd4c5d1857959c8f37cb5e502e582f368cef4a511e711bd30cc3e0d8aaed81866286a715fd3e171402a9235cce3a030014cdbb1fb014ce56dc96107ae2be2d51d9c46eba92562bb02bc26ddc6b1bc262ded6c066469f3b9cb0047f021286c1670e023d9b3e680b9406f89ed79d47361133f96ebd1c3f986ed82faeff65228021ba0b6e2bc26c25536786f1dbf97109ff4ffb82264693b876d49ec36fc9c98fb8968ce06081b6eb43f687087a75bab7adf537edfc1a75db728756bde7fe1c1a9b28e7182cb0ebc4442bb06117b81668b031ad94c4ac09299e70063614e875471d1ae0c1641e70c17135d6bf0ab3c48d2163e5a6220c41781f503965eccbaa59c038275ffca88e302d6170fde65c973a16ccdf1a3039d96da84fc0b861dc1c773b8a503968fe23ef5933ee200247f200697dd8df2443ea68e6f4f0692a5b0c8f467db5899b56b658f7e73c41edd84f6164e5ffcea745776bad2e04942089882dcecac61f45ae755950732c96ab2d39351b7270ebf60b2fc35efa3f4e65abca4b3d6215c2e6b68de4e4f0a4a38fa791bfa26376652ad6139242714eb8f553ce4ea3d4b5eecdcdad49322da12f2c6bf25d2a920669040276ea3975207e383dd7975e74284ba850a6dcbd0c036000f43f2d51e42b367f80abd37c09c76e2adbd5679aab6ce77e32b68fe35bf6a4416606fa34a5e011f62459eb615d1c5b275f5bcb8d6ecfbf317019b30a17f685214bf01ab2c11022f3afd6f5f1acea83defdf4a045fc9d70e4b62e1713bf60729d16bdd4ebed4d85e9683890541063ecf84495a90f42c6ac7c469723a1d128679b4a54a92ca5c416cb6d0a947dd09995490123d0302431ef225b802d639483bb7022c1ee71360e546ee6563ef3c9f6c552052dd338aea2f68596e403e7ef982cce4cd8cb2f1a99a7445761f8ac10ccd7cf1fc00bb4bde34a98663e01fdc923ec829411327a0f1de9f862dbe14437edd5bafb972aafa1a8ee9ac0042789bb500b0cb9f7bdd9b44cf52c1ae9823709ee1829349bee9017e0ed17cfb3ae8311480eb9fd07d014ea9fde59dc0a45495ddfac132f796f196008f9e5030975d2978dc3704b3a1b9c3b224ac321564ec2d1b343a2eecaf5aa365e52105a252b607d4b987a22b37357cb413d4ea5cdcc59a0a4cba0e91e110bcc6093e8092808cd755fd866bd0df9d9fe80487e9af899f251c815df041d2aad4c706a4fa568be87c9e203edc96c9a06b080c096f3ea266b0292852197c7dbd4eb15d90b39e81914a45f991ece86f23d1b0f7581383fb2e98f7a23fc0a5515f2c1b52e4b97168e4fb9641f5078df91e6b6afbce57bd84867c7fdb36cbb1709282c071d54470153dd62d132de12d2cfa72c613085c2d32b7cfe6f401c48e13d5fb3d0aa486f52fa3355e0d6010a3c55c05d00507f46108bac2af6e3bb7ee17a99c9c7cef68130cd20e18f7805de1c80b9aeb3252ff168b2e26e84e804ba23746438b9fb232fe5fafeaf821e2839216caf0318e632880c367fe2e32e27555f03eceb49d49b3b522ac8ec14e7cb2b0b5d99f8c2e2681691b71ac38f31d06eefba9c3d7cae2b8485c51800ec713dbe51ea5e92ec9c36c7601bfe5ed72222d904e43fa115bb43db719a8d69333a5d8890712b8ee7eb378f717138624c5ff9616ffd2dcf2c94a3125bc9350f6499f2baf286f14e503eb52733d395bec5b305ec29aec16362efb9c676e9762f27b01a62b91ef15e8807203058f991137a3a7996f01d46327df044a9318c44ee1411f7a01e385867131b4f2d930351720255a30c6a62ccbccf8119bfb1ce63068ce7bfdb1ea5f12cf2b007b9c01d60aff65eee0b114193a05ab6f811b82b9eb1050b51319f2c0ab8e10b65ec1e6f722c8ac8328c8c26754780590d0afd3d5f60e80de4a6c9b864e56eaf5b60e88c8671e4921f9f906207ed560fc24ee6fe00457430e3ce0ede841f1b04af2e94fdf25048db9483121c08e2a4d778146e6656ccc9393904c5c74924fa800d9241c9c0716200ada18bd54301763ff34b964b62ea8cfc026088279ced7d9709c88b7b058d0a68098552a78670c2a8ae65f27d2d8121062e38862711813d81d7b64ab8634ff23559ef1ab315e10bbe450788259a30a7e21c58cfa55eb925704f2c449d7a6c6c0b018c0945fa130136d149efea5e0f5339010cf00d47b111723c12a00b347c078497109a9f0b7d4c18549d559ff27a9fb47f340afb091248799105de02aa0ecad79b16bf94aabf1a0e0b7ca743423df327fc3acbacbc3a3894e31c4342381001c08186cd8c60f9393a6d0e560dc16743f300b63b39ada0ebe66ea13eb2ea6f6501369cceda12fd62d29053e4662e880dc6d11acf9f14287411998b0ba93b23d266cf084d9a55d5ed1f878e75616622e8e519a5bef31ccd4dcd2118a01f77682768fefc3ea5f82d23ad4cd3f60a872a31ff84a32b708c6c9820c3dfcf936ea155947b4141a99ede0a2980467515601e40e0a25fcc77e1c8a486896f66c1c59d830ca33d3cc80eb2abf190c96dde1847ba1aa9b4dbd0e807f71f27f0054c8da91cc9ac6276b321ee7907781e3afe6b1dcf483307f634ba02eca28e4bc37a7b636cb56bbb095a0e43d5f6adb44a35a2824654acc455d91fca7c3b3f6c4d2a44f37d26c851e697788bd3e0a8a5a4883790f8a6c61bfd0c4412eb87aa65ba9787cd76cd6485896ce87fca69882f51b6964c7c8bfaac7af98d09b7acc6c71edea16a0abf6617e91557e23af120119741656cdf126c3b2fea1bbb0366d2c721badeb4a57fd02c8cf26ff6c4854e977a25834675570dee3373f8d7bfe128c2b06ec99a7149c38aa5ec304e53f8b681041f91b2172aaf869e98ccde635cb56e6023a81502c3f04552c2920582209ff700eefadf024231a44b77403d79ed5b0bfa7e5d501a40fbc4b5761a988b216485aad0ad6e0698449691d3ac8a56ff8ec7da5b598762a39691251e14b13cb9fbdb0b7544e53a05a20ad78b6cc82cb7afa9d932bdb9a74120b496c96fa7e6df50ff56754431b0c78b1fdbc039dd60b97bfad5b20a6fb4881809c4e554db2630cdfdc29801b5a40277ca367ac9e42b63bdb091cc8890c90db9bbeef73fe03e270e652f3dbac14416868b8b016c2930faaa48dffc33de3cfba4274064e727826babc7b127a4adddfb3d783f907c7809eb9e255466835068a6916569c0aa4e88fa02abcbb7117290c68eee5f37ff057705a15b8ced2ed613183073b57194636bb7e01fff4e6fd70d9eea10656a883727c49b3cda694e60aefb1d86a607a1336bd1c2d0fe09061e6ea37d926cbc5fc2c01c75a833417a62eb8d40d7f48f1522b0cce5f9ea808fa5131584d584e70bd240e4664eab2d948c4a3d83afc0558e7c5d51d2add299263a5818887a1529d42e10705b8af8726eb3db8da6a85adea9ae738b94689384cfce94103826d2024215d4d8743ee9ad1b54ab11ff186d776b2c24dfe77bc36ddba879e607ae0c7103ce325792c8d85cec51abd9b2360e847c8243eb7fcb8951dfcea8e1bb0f66f71dc975533d86cc6c6794bcc367c406fe1aee409cbec7ab195b9712f37d72fb55c38d7727fe83eac7a6e5ab435f1afee043f4768391ee7a49f9d4416d8ed8b19565d37f200e570355369cfbd5587d63e5a372dcfaaf71eb203d34e1f01c7e602c1069c42141ca2c73de646e0ec959277d6f4bd0058dbca2c327eafb3ac249f9d56fe3c96d0788c7d3992e4ca35f7f6c26f53b7eeb94bfb41993de70ab7c1165e7751e0ba1be81b811b4f686a40e457f00f1df25adc2a00ab56cd4656ae6709acc41eb9e61e1f7cede4091dbcd108104f145ee7cc5d95f90bdd7ef45455bc96fe6f19f121988b27bdb4cce1e97b6f5df0bc81eb746637cad9222973ef5c30a3cc1cb7cd6127d13309b5c16af8a2e46010eae2dc24332e738598c0121fc85ba40bb9cfcd9b85f7f13dbd2824d0dbb20a83b02b78e525f35bc9f0627a1d47a1a6c248b539732978efe1fc1b6d2038b43c3d386a60bdfc914b1492b49a188e9ad9bbcbd201e8ae4483495a90be3c4f659ed3e7b4a91f3478ba07ecf516ff80a369c82e19f3f73257b7e6552dc17a5b4577f7eaf03e30b94ed867f02fe64751a6959d9493dacd072ff1a9facd8894e732e2404116bb40662bbf0c1afdf470387336a418b7171dddd3104e2d117b4dc251bdb881215e34922584ead2f0b8e2ad077971d672166a91aae72d352e8ca375283264d0dbea2a72882994dfccfd869cced2c6689f05b3cb40c60a26200d7691dbd81e9b1cc49084ba31ccae65f1c19ec7ad1f39d9df2023762f79c5bb6890dce12221619effc4d88e03ef0bed94e04a1c68710e2c545a669858bca0e3ef8c92e17ea2868323f402bdb89c953e0cce9cf4d9ce53a81e4b1a2fd16934109f42f51ea50b51f4a3ab56592f0876f8d2f0fd32cf06b305fdf0bcb06bbe7c73c570e8754a33ef666ce111dc1dcffc44b05387000ee620bc2bf62e7df0a62ddce155259b905a0c00b1cc930be435f718e49b6cb35431953bde4b6b8749f6e2e4541eba5ec5baf83ff5c356d3acd0bf497f3151f624005a8e604241ccd6df3e1b0156ced2c53c8b043c05e07a50fa5b66c9877461d51dea77760d2f90ca9fa81b21f8140f45067691e702127eb29ec9984eb8e18699022373d9f5133ec90e828cd7e0c241a84934acc43bbab5c88beccb76474e788ec9db38063b91a198b0ace62bd996e3b7bbffb02ff77cd37bcf2183eb7926d0554c1191e4db0d08977850c2f1e0188b25d8b7dbfedeac0ebac963dcc5e4756fd9a578c22f5bf84990dbbe499e7f62c6a517655e242b692d51cfefce656c2e7a96cea304b6140d5beeefb39fa324afdef9e9b3329dda7f057018fa77ed4920083f8ca25f3c5b5d68dd886eca348699410cf36620890f156397a365ea7ae219fd99503e71c861a0aa85b2668511061f7a8dc9a490b4fc5d97e5c9d64787d77ff3a339c4877b9a19e08d0cf73d8c8de2210f0a8d0540d0b4bdb45acbb4dfa5e14435805033c03cebb29a7184bb4b9be667712bdde16660c97e268f1e81552e6993f4a860fc0fc771d11e7d0d3d4c344f7b4abb263d01a5d8d4d76ad751f2d2438e1ea884f0008e35ce8fcdf8c0dca4263bed77e643293d80e74984c7ea56bb1466ab7d9576ba4a42e4550cea2eef3e17848e022cd4d768e41aadfa0c46f4dcee8e1b95dd2ab3a2e0b36bab7ad5a53d8ce2ae6e42cefd5f74bbcd04bf47492df16a0dd2f22f41a05c610fa9ee48909972b5cf50e2ee50ed90ce937b5b7eca426b6be19fbf8c61f5e18a8616067255189d6d5571b39c4a4d25d000a20a111eee940cf61f19d51ae4972793b83cad6fefaffec7a87e6c57199780bc6046f2c67dd4ef74dad06adb02231c698b9b94673466d07cdff0e243c33a4b9aa541d60b82d54667a9436a19e97476426d48b3538ee317117617c25947d2c7bfc62a8bbd46cc823ad7c48190362fd77d241ef0076d823acde237ce7e0b8328dfdb41d3636667dd0c42613594ee039a05e1e10965575d9380e587e94f2afde700d4efaac58b4e101ad7bcdb443c8a0460d881b704d0cdd46226190901b1a0e090e7a96f66f4a5eb27f4b90c011900292990ee11b9efabc0beac2144fe3356e28abad4491cb7e4e6ec457c4cb87def22611875f1c43ea626e19d0fba8108c62ea44cb7bc4958dd3b1f2d21af21931223251e067042f88b43954c3d25b4223ae7e93fd8bc72ab80e4ec05318332061e08853b702b9b5cd59f0ce3178c443e462465ee406022195e8510208ffc502b17821ab65f4ce5a9463f981260f6f167c66942cc1d15f56e556210cf975ab9270023627a01cb37f62184c5d71f1f762ec3ad2fac42a718cb0b1a6325078e0f65966c9fe904711fffad073e546f5225eeeae785b29e7b35b4a0fb684ff50cf3967bff32cd45c82ef311040dab6769e7f456b6f8e503ce2167bec8ec498421654c5edf96cd070ecf8739125023bed5e3d9962871c27c8cc7856380909b82e20f6d15ab5c903a74acb26ef760cdde3deaff09f992df233a83d117f430376e9dd49fdecef2471e7ab8d615de313c1ffdfefa5e811ac2f7d36de021123cda52af8df744ae52c25a43ed7b608715ac1547b58b35181bf01345af9a5843d6e5eff37c79d54821aaa1cb8a967a1ee74cd1d2a76a6d79373430414b281ef5abb54523581a8a692c2579a4034750e7de581fcedb2e14eddf88381f6fa837d385c24d9ce33e56b6b27a128fa5720385b2600a88d894c234268a7c8b3877ba4bfd9ce2e602cdb5ff83504cee325e67b678c5ad2bd9019c39c1248bbc16a27d1043ae8586b1916e63dfd0e954db99044789af3ad6c3fbc501898769944649a4829887a2bde555bffdb47d388fcc2dff922962134b8e783d18794a066371cab4ed05b39dbe2810bd0e0ead2f252587af31a66bafee3c60d943d5e22a7b5745acda290ffa9cbe5258b7529429112424971ecb3b4352ea601d04a037da987556edccde7ad97a6b662f3a67def0d4244346591daeb4b4bfbe4c26715891a3c1da35beb66e8dbf5ea95a97c3eaa5669754fa20415e51f7bd982137e4ecbbfac29a88932ff335f808d41932ed8fc24b59271d67f28b7f9db7db2145f1e95595ba59a55e152cd89fdd8e848e9607d732d287bd2556da42e296add48ecaa35ca86ef2675483ef2b935ba42f78923b854521fd83a20e45de726647dd9c7a1c410a397dcc083b1fab7f605200b68dcd099af71164e3c92310b23cc716ed9ec396f90f69fc8987ef7a31bf43d75b19022e858964610ed17c306ce7e80ca04739aba0b5cf7eecb226d078be50dc0e675dcebd5bc2bd7948cd3312dd2da0bced37b54d6cc30bb0c93a4b5320ab7820da558ef99e0477000c235eefbedfce1795a6776ea7b52651ba19476e8bc8b86c16f567388dd1b5e1a5930c0c2c7a438d55f5a7e230c18c345435b6a0830dff8ff6302372ac26450a1c0d2e42e8b9683b53d3c681c219543be0c09b4b0d86b45bbe6b742daf024f579fb362fa1539d11ab9136f0cca1ef0290ac97219df6ff122e0bb03de3e220a03babf35b290f8ee50294426b79510a2e90ecbe85690d1d835734d461641cca18c20351547909c7d87d4b3f78566137bb94f469f62dd2849c9469323f84a410a4c80adcb1cbd72f0ec5f71fb82ecbf6374499a29a3968f58172816c533479a7599b1597845dd7df367f71cd1ab8dc9075a7d6425671db0b99d452deba0c0e86690188d19ceb7cc9ba044109054156c9458d255b6820f76b9bccb4ba783d5405e9ab0ec2dcd88099e217b5c0df222aa5863e564157be345327749c127af0a12271f798ad9092cdd849fea7642208833fbd24e039a2271dc8be2416f2e5980848640923af99c8383def8089e4f33b5925af297eb440a6148970b927c790ad4a5cfbd502e15eaf688b4455fae69e93ba4d0e20db19d821b4821a615b4487afa08a369971ba54f70412fd4da4dbb5bea398069b73f8438cb42e41341c533d050f972c9391a76762fafe46b9bcebfdc83154f7c8b9d5781a90f6f18f81aea60919d2ab6af842c01aa5c5563b4ea0047af6278d7ca930921b1890280acf65127418b1d8e0205d1ca2ae40041eb1b8c9b9791df211a7c82ba82838b9008eb101bfdbcabaeb8fdf20e961e7a62d261bb32f058b81d45dd796e4569357953a9bcb625730c26f9862723ea8b96e5caa328ae363b5ff55d84c6582293573f706e26084dbc37b90079b9215535b2da6c37c0ca5c4bb4991e418f816cc94a46a58279d09205599e81cc81858fbf018129c0011d7cd175d177072bf3d32cfbafd4b700569f18a2dfe6031ed62d3c2ded0ac0f2073aa002e8169cfe7b62e04bdffd392b80a76f9a8179e0026fd111402e7d29ea234ccebb1dc2b5ccc807906f25d40eb08774549fb10e20c5cb39a4e70415b0e9c66cf6ae613297669b67ac60a7dd92bfa92119b3681b3aeae60cb96e82d2cced07192fc745cf89e3bfc09efb6ff0ab2f0bd120809f8fef9338a467f4130df0f3a1961f1c560ba9ab83a5145156b621b6639b60221554311036fcfb4cbe8fad0018751e09b5e2b7402b7c17317730b0ca7c7882f92aaffc119e72c7eaa888f167a8b63da414a4d5a9f318d905d770d1892060598146b5468aee87deb00a5048016fafc58058bb8ba3956c75a0350504830e42e0f777fcd11f480986faea5c88389f370a530483d6fcbb4503ab46db14a039680e3332091c8c0190068cf1bcb30173236302815b34bc1445810fde8029c09c693517e5554aac9902b3df039bcf9013911035ba33290335df560d05ef4cc2812c6f542bb2a1030c331245b4a4967b0d40727ed42debeffa3cb8469068ca238289435339e818a7a159cf6538e4550406065743b2fa3c48fb8a6ace4eb53f3075bdc41a7c8099e44dea8f009f45b93baf35a4ff12db119f9bfbae9c24b89d4fc1a1a54c0518529b1b7de81202d11d0c44d72088ce14210a43274f3d97ccc9a581beca73b390841100cfe516c210649dc00a8c7ee0e6e74d33d0f7fd8259a880c295b06865c203fa175c62ace71a9c8613a49217e508e644d83a52c2a414e58a8b543b77f5f7ab8475e148c43948ef76959aa713c05885e2f1730dd08c08d231542a3a84ddd13be07e811ecf683f3771f1bfe33768b02c9c3600f16cc506bfb7bbfabce79ad801056c09164e25751d33672ae814d8d1efa8680c11abce7c8b12ae7a23810a94a31b24b01c31884b5908d45f2fbbea0d6b10d2a2630a72b94ea2d513985cf238f96a17a49c7e5d217db397e328f1f90bfc5116289b480fbaaa887623dca7cf3ce53f2db582bb65a25536ccc25cf037e8532e268b7b62eb0fd8a2fe9354ffca2af3945887ff2b4098d1119af7cf793047b4c143847f1022b1e14f6cb6c62224f6df602a81416d20c1f34dff49294dcd3a0c24db11654ba6051b1511015f0a6177452db86e43fd0ead6066c2d453bff79ef8e4aee27f2dcbca5ab4d8cdf0ab527eeee47e8f394499d5dea4a1e1fee5b5f57f8b35baa4ddac8929a201c57eb5777e9cdd1ea5052e262428ac148a647f655c414198df25798316b0cc0bfe0f3aea880535bc6d0d66020b1906b4cca0bb58c53713a30e859af6f14eb6b16e12cf4698251100f1cad5d392c024bb3209b3254162660d1359207b19b1c99496acbee02b723b5375a61aa275207ad77c7bb79e0715e4184dd3539e6146d69039ec10c8d5104e1465e5526228fb08536688b1bcb894538d7e58765fa81d6fc2ef3465cfc0875b49b90c17199a3f69dad4c0b7de17e2cfa6e77647b91aa30d011c137e0920d4f53438af894de8aaaf068e1b51f83104fb80d8c34f71bd7130dbaa35d4c83eeb4c8dcd2117e156331e27a1f51c83d265683baf4cee7ff1ff02655aac288935e74175e7bda4846a31f1b47e122d83d09d7fe870c6f2f570fc0109c287a95c58df2f6068720a1698da5289bacd324b62ae4cc1cb435128f8ea0724ef63787467d783cd6844420efcb77a4a7271ff7ec58d676faec6d0d3f8cc225570bc8bc0aefd591c5b28ff8e96ca3287a267690d01067a7cdbb82f833e7e2042a235b70a0e205fb33bb8b7c19d58d1e15561fad3ce407f1d9b31ccb637775e8b07bafd3330b207908a90b71b5dc7bfb139b446799525d4c87f42192214a504a6920ac804887c7e3bc4a0da3a0c821be0f3be0901d035632a195468a5bbc0e0ba0790ae3b60e9751fd0afbbc0ca73d935275d14b36297b90912ba3cba2602946a9afc2a76091c3034bb208303e9cdfb8a587f65d9336cbe3275548fe64a3bef17b20b42a7faae191da99bd8285439f6c0fac7309ce3644192c99c809b28c833099f22c9ddbbba7a11b43a3ecf05298cabe30b6eefd8bbc1290de1f80710cd0ac08fd4e6d3faa09b0ec206d137ef89cd8ffa3470532ba57014415cbb74ba7b8e677861efa808999bc4deb81ff540e29e7a02d38be5ff8d6dff678cbd7cc30c97db120bd170fadbeee6bd5668ec96bf42f2618a3374ac8135095062d04ab110fead844428a755c9131d894661c1202217f5cdfa0c0cc5371ccbb9dd0fd1a8a1cb4365d71d91d34324e0696b836ea7288b8798219fec5f95606b2fffcbed21d9504627861112fe20bcf0071c02eaea6039acddcd391191c37697eac56bdd1349003114e50ed8b1fdb399714c506fb7aad68fa11b006af6cc45c5010adbe5fadb7f1261302b31ba918bc0fc57d559148863b88ece607774c19fdb4bde1f8a73ca453ee53da5b54cf86bebe2a9b505c6c935e2c273451e6c81225adf984f8c65dfbcaee5c71182626957448d310a46bee66368f36f518371ec4fa802c331478464c640073644655718766f23497ee9f0f89dae361e7088ca11f49c03fad756995c208e425207bd815728cee777436c008f5a74640dc2f1c4cfa536fafaa61ad89f518e027b9429743ab3783b6ea4d4efece343854e7e0a28f9406a96cafde6533907611f727918aa9aa12d111efc106741288dc8bdf7de5b4a29a594018b099609a4094530f47b9b8448a345af054b4f64f77739a6975fee5949ee19892d10077d8cd2c71ff76ccc4774f139155dfc958d6d9928fe0eacc702dd2efe8ff8d40ceae20b4aba49755d2445aab4d445314cc7401237442193c58c165064e2532d3408ef0ed07dbea45f213efafd70cf2eb867a2b6f5d0956fa0eb3ee8aaa0feeb32f30b0558cfaf71fa95a1b4c0f310151279884a09d54eb5946b2aa45c5369c937d50d7753e57c374693666022d202172f8841891ebaa93aa5745345f40383059a1b701852011132534553d5ba7d5365531d4d31c51455a47a4ddda6cef8524bbe3039b5cb27afdb4f1105d1cd9499b2e5267a3893ae6801974666a66e60ba99c2a57229a42d29205b52417ea9f3836ea6867667766a98381313ef313575fbdc669f9afcd6adc97126cf5d21c410902637b800e50899c9755fbac9797c091fe147f0119e84d3b8116ce343bc08e7c3fd7067b78fca61e9264ad7ad8932514c482103162e457880a22433514c3c13d504a4bb893a73af40f970af400181baa170dd4451f9a18cfc8e30118a8886e26de9e689a95bf3d4eb476031338516b616780122334fbe5fb76fa2ba891a42e2d4e5227152c14977daf174f3a474994e542ed309a9dba7e6e9d6ed6eb2d2cdedebd6dc3fbcd4cdd3f982102fe81005105f9c7022334f445eba79a2d58c34d1cd93ed7484654381258aadb479ddeea51da69b9b8c968d646e2dddfe3eb3747313756b6e73d7bae5a28732469286a00cc9cc6deb626e5c0c94999bca922d9674519239a3a69be59a7b2b79f7563295351dba59de6e17dd2c713fe0d2f4a26431849a249959e694ba59eaba35cb5da974672887dc19ca22e49a72a82cfa6e9646d7883c738d483564ae5b93d499e44e8315d8b66cd1832f89293293e425759364ead6247bb151669264ae20a35c414a218d4823e451b724941f79fe48a2dfb8fbd2cd91d7ad3932fdd0cdb1c7939464ca112854b68c9199639311dd1c7d55ba39fec6354d8c5b9a188b18a18c5446a46e472dbdb1d61b6de3addb177bf6a92936756b8abe147453fc8190048c122558a860b12233c7934b3747a291361ad92d6213768b2846d4222a894bdd8a6176226e27e644dd49136dddbe29de442851ae88824518262473a6db500d2edc758b0b794cb59b19eaba0db5d821210c764828839a35e1108c6e864535b0576baaf9ba7df0c6eb269833411d8d0986730c1236344d9199e06ed74d90090cb305b8640bb00958041a8146ba058feca97fe069412230ddd4bb6e4dcd7bd22540a6c6040b1b92180385ccd4bd22baa97da96e6a3550e82b5008a18f345293a6d57a3e531375ab8bbe6424ea97ac4446ca5ab252b77dc94259a262cd7c9ea09b99c8ccb693cccc4775c914754915b877a67edd99b8d667e826b69938a7ea2646620103d9a2d73771906eff87cf6ef1d099534daf5f9b7d6ade5bb7e61d73a48916bc00c144179979759d886ededde5dda50ddc2353f4fae64d72876e917d9fcef69ace74fb56cdccfa40b7f4fa26b5548e6a377c288dfa66edd27d5430a59b95a9a9feec2976b3ca38eea7578b017a23e728baddd2cd4a3301931d50f025290b94283e64662522d24fb023d858c8b23bac75d2ed733ba49f80a486a5172ca939714166b5e45aaf7f6ae23a50b8ce0e7c861237f1aed79f91c44d2c0537b1add73f45c14d2c859bf7d7eb9328e02626eaf5535ce4f3bc71339fdd02d58c1b22a7820c6637139d41f4fbabfc399f658b51bf7f9178d2ed4d8e9bf7d6eb7faffe0c35dcb44a70d3ee7a7d32093f9d6418feba819bb5a9d77f01f1faa195056ffdb85969b7fc3ba0dc41cf396733cc50ea679c05ba9d88f8290857fdc06d76e0364fac8aa9bbea086e64b8612a776650af446a44389ca5e1acd52ed18a566f219e819785e46e3c9ea6b138c84af31fc2f013047b31c87952727ca14684edaad75bbf39d285b2069c1d1483aa49e5c3b14ff23464a77366662251e5f6819bdb8853a532b74edfe6b440e62eead9777f175920739f7b2888637cd3e358943296b8c518e30fad95a180e0ba9bdf67f8e0a6fef5fbabd51691271e97a19b210337c3d3171c6c5e288de878f510c0904e765f21fdf0e5c48c19289a2b559a64b40b2f483a598e16329d2232b208e7085502928527a348c852609390c5a81510fc92394115529f008a0cc4cb3e4112204e03d9229ee08afd84ea07141026c21f6313a11924e0128c139d8068300ca911c815271b7d7abaf7de7badb5f6c6b0ccfebdf7057487d29216d14d02983cdc25447463ba05cae3ab65249d066d0305ca5a48887de5cbd4466af879273ef671eefb03c0805112959c0f4161a772ebeef826edf98638bdfd4c5461c028896e68f7b6b27cbbb1bcbb3c3a0b947b16360405ca4f5098fd5dbed15a293e35e19fc3f2578f12c8502fa678680fa3c95c7a8af23cd1b3402285d5b7f506955d07c03e32677f20aded20b4d6628c7153bf1753a18ac35b5fc0c3b8939444651e49eb50aedbb7f5d69a8666536b6e612808b59232d746cc290dfae290f2d75010ad35a7e0e7355d9332adb5d65abfb44efa9145d757a6b5ae62a7493f7c479074c7ccb89bae05bf4e75666e9df63ad5b5a0483f2feedac0ebdb2d906867093b6e6c54b305164aa7cc77c3ddd889083046d04e59b5ba5377bf2c81d172caea57de95d56adeb3a99ebdd26ad5766f38a6fab877aa7845c89cb2fa97d67463f5ccb7869b6eae4b0f3a997e99f5ac44ddbce7b5d1cc7b5af39e383c86a434839f3328c5aee75f49a1d47307b29c33cbc799323b4e1041843ec2cba461fabd3846eeb7680cbf60301cdb859b4139fa8df5e0c0272b01073e197e9be4234ca7493e8cba079d269dbd7eff5a1d264c841e8b5d258850d770df1d22ed93a82a764c7dea93bf9e906e37093738ce17f4bb495885b483fa2c177cb6229b4c06aeeb4d42799f383bb849e0f83cad10d2c54dc28cfa25ad8f9b04b29e393b596bfd1958f4729380eaf5552bb9419d37c6274c8e2751d4a87d3add9329824e29cfb6627a3978ea26a8f5aec0d7cd71a499e1f52f10dd1c69610f17f674a25c9ca49b234dd76aba3683877395e2d329f615a937dfa905591fd1836dbc1e34a8c4e5f12e0f3c63613ec090fecd91652e57699057421a2b7f3f8bc6c867c12ae5d14fb50840f307c633d25c45c81788bb2f73140288be4f28d48908c4117d0b62a46d92d224495353d30c1790e50eac6794d2edcd5803255ff6b6830e230eddee76bbd4e94a1159980571b8dbcda686561577592cce2f05a82eb7a5d14831fb1445915e8b8b53598637463e2d471ae58d358038eaa30a82f291268eb4b188eb05e2c626dd8e49ba2d411c09e26ca480b814eab479498e5ce421c875e6985f6e2b906863a000679e05aa4fafb5b6d6b3d7eaca600b5ea259a2015a8c52d024236394d88573036795d845ad60c2029c8cbcc1c9e2091d7cb2ebcab122eb82a43b3930150a750e8ba9500e0bb35f73583b46f09c2bfc8e6fdf7e07f4860a95867d0bebe9b6860bba7df383be4115ea0d13280dfb0ea06a4a508c1a3199490c15b275c9c2987e7c8350ec1f5d1e5001dc20086efadbe132c1017b26be74b84aa03ea1eb00b4833ceae3e3150596fd1e3c2a6473a6e026fdf5ef20fe6e3399305c81f2ec5ee8e08bfeb8eef77153d651a1cca3b194afd3ad5f1e2861887fea4b507d272bcfb50277b56ce015bfea9285a57ca825dcb0576a7b27bbb1dc9c25dcacf6451f8590a6c6526b2db516d3c719638c57b87ea62c98ede5fd7ebf0bde137da28ffa54a1a3e968389ae8cbbffc9ba1021eef3445d647f4e0330d3ea7e06838250b539d60580b6bbc56699041842258247c89bfcb63bf4992f3147d97e7a68ae8137d20188a41c42e481a6fa4f1441a2fa4f1401a8f16050d147db851863188d88548c385341c48c3d1aea09520103090e6024110046ba031107c896a2c68a58353c01d3aa843077118c535a138c4c3a2d3f298a3d168648b3e0b3bf9107de5ae4d4d3416d0441f9188058d88b6050d046d54a20f26a681d1aed040d127fa449f88058d856fc3174803c52eba0569dd3e8d07daca86f7b285f1e43ccf33e7c90b62e3d9a4d84ea22036229b17dbd90b62ebd9a8b09db41f369a8d8aedd405b1e9c2b46063dd48db6db4dd6c4836b22cc9d10c413dcf73b4bf1b1bebcb1ca5f4729f4ebf5f69b33fd2667fa3cdfe6c666ce3c994a4880ca58d89b4318d36261b16360d26397fd0d8f8d6e66e6c7cd91a0e35bc50cbd572dcc77f1606da2e8f7d5bab7135dcc7b25dd056bfda9ecb0d8ed895fbeda3cb529efce4d8f305c773748d4396c77edeb3712886f1e4fccc05b1e56c6b6ce3789e4c3f6c4c3624db789e67101a2373d84619489b6eb475fb369ced0c6d0c2fac174c4c864e3431b458c285157629671ccae097e7cb7093d318bf9d531f22b40ec19c46575c4d78286e28a8f1df0c6a5c31ce3c25aa052d8565bdabddd6eac4d65ba93de11bcbd675937597c7be2eebb24e5769bd24be16678cabeb44bb9032881ea5736487efbdb7086ae9257a82154319d612d6159610653898c48a092f457cd412030827abc4b07111c7b28183486078e129185e04e1a74e93805cf1e2c6554fb08878c8c40b245ef0ca4e939694a9823ca50141a7871a33c07c70b2c20312b7816a884707bbb1e29201e47f8a1933b29041290c275e904ce5ab0a99738e2a5db589c2abb5d6a6b8ca5afaa412be62819d262d11d34dbef43f3f2cd6cf8fb5b182e86ef73afcb46beba18aa993a89a9ff4fae68c4e5f87dec8374ae309294549d08cc1c2240919fd1c9605e200ac6b403f7d5a23c63d8bc8cc20267a7e9cbfe4f95939affbd9e9f9e93928479a9e89f2e947e6393f0f23288af2195fcff9869e89e45a44ebb9d68c08e97986bcca3172feaab340b92af55c992a122064c8f58cafe7d349cf4172514ccb4c4babf5fcd6f6a367d31ef56ca1e074ba9e6d906525cbb34c3dbfede9004551b63e196fdadf3d938c72beb59e8df4bc6448d7799d2761cf74c6f4d65117bdd5fa9e56ceb75a4f6fac032b784e905af0f9c045d6124037577d836ee6741d1dd0741d1d9da35ad73942a2eb3c03f64ce7737272727e017b9633c29eb580ba0c1da47419af803d93e1444fe960434f3dcc02e97cea7b2c508df1542af5a50c16352c455011234b2dd175743e017ba6d36ab55a8f803d6b8950daa8e8e5979f6381525ffe9732be2ccb1c9e10295be58cb22ccbb2d429cb2fcbb22ccb9cd247e92a959cddf5ae3fc09eb9b401f62ce773d0f5ffd99ebd8e8e8ece87b0673a3e7dc6167dc61760cf66b45aad56abd56abd6ccf5a60ba8ca746ba8cef7b26238cc156c3a9cfb1672902ec596982208a908c213a625302836c7f2fc90f7dff6aefbda7f8949a68f1d2c44d8b2c8b49a2dba96df77e1e3b8912bbb5ebde7b3ff5edadb3ebde66ba8ece0f60cf7438e8ad245f7aabf5b4f502d8b3162b954aa552a954ea71ec592ac9949b0477e98d3d2b85f66c5323902cf5fb2b20b2fb3916a8f5f7b1ecfecfb5465c244b5cdacdb94cfd3e0fda84e44c37a9cf05195d7312d16a48aee8b48bcbe97640fa52bf35cc9599b5577df7a9597f498674d30ee9bc698bfafdae03eb3a3d5de7a98e8e8eceb374dec69ee9a452a9542a954aa59ef543531fb467a91d11232ecbf24bd68f0865f9b13d2bf7de16888d448a7df7efbd9ff5b3f703edd9cec9c9c97910f62c87460b09afd5fa1612227aeb571668ecad67b5bec69eb53ed8b36bebd80205759c235d749cf760cf70c29b2356f49b9b9b5fdddcdcdc1cc9f2d3cbef60cfca7bc44abff7e9fd9f3dbb34f6ec86752487ce7a0ef68c65644857fd4aa5fabf31ea53a9baea7f6e2c074a16184e23a2d8a2854c9559a03a1283aa545d550daa94ea47a55a19e1a19700e865a9442fdf67cfca9491273d957a9afa9e3d4ba17a688d6ed6e877cb0d64706082830f38387143927e3f007b769be83be9061cfade3e96083922062563528045b67f833ddb3c7ba68a61039a1ee3617b1663f57c4c5fad56abd5ff8df1d5d3950db85e7ad0cbd760cfca7bef7d00ecd9cd60cf56309e16d161fcce9ec108d36d9e4fe93636df6381c46ef32b1bfbb1f91c0b54becdff8d55da799e3f643641d26c09a28b12297c0491d9fc125f8e98f1c5a45a86fcc01972a4df670d7141bfff448ae8f76137f4fb3f34a948957e936a42fa7d0cf6ec66214658d8d2288bb27c267a6984a697e5b3caff299fde588f971e70b892c515236b64e5bff6ac74ed994d98fec2c885fee27fcf5e98e0079224439e2401c39a2432bc021ace8e7ff53916683f86f5fc2c816970824f18e314e67163a92a452f5811458a1f88c87066c235c4806bc02a8c6d30a63a2da8e85961b4821f926458896ed6da0b64c0256e01af700d4bfaea573f63cf564ccaa41a7228cb2fcbf25965f932f6acb4a141886ef33a7b66539974d57320ba4a85d4a50a1cacf0909490a93e67cf54383df59406197aea5b7b960abdd8b4664832c314330cf5fb387b7685f4fd5c8bbef7d3fd377bb6599c36a5f38fb167fcb4d44fcfa3e8a75f9d7ee774a2569c6296d6a0d9228932b2534ee224c30fa7d39b54a99f4ef7844fcfe36475279a92534a665235fdb4a69f4ea713e52283151d3f0cd6aa896ee6f0c450ebf759312c014315fdfe8b3dbb65f92b0b04f6b2fcd59e9518f40d8396bedf66cfb66acf4e74d7514f7f1df5a93d43911f5aa0fb42278b4ed049504692bf4392ff13b44337a90e06f2923ec84d92cf8304d24ddad444d2484cfa204f24f934887e3a3ddfb313121d3fcf758c9da8d950c40227b83332fca83dc3377d17d1d0f79ff66c2ff59b145997ab8ed45f97f49babfc6669410aeb7506168eff863a0bb3ae55d7cdc8c2718d1bc3a1e41249b22a85c94b3abe1f6818ff80dbe1c11af8dad9c0f1fffcaa87a14d0f43570bd4ae16f8eac0e5e2eca276f11ec6e821ab875a7fa843a0b5abeca176a97a18ba5a24f9dad5227fd547ed1a7b18bac8d7aef1c9549975201115023f8337db145ad4330e74a1e737411a58eb79eb749a3fea7580df03f55c478a7fea6502990e5f443d078b2c9051de597f3ebd5979558b05da55250d9e5c26afbacb338502552b1486b340356761f9f30f0e17b630fda18bd4f9c9aacb552b9aeb4769f0f5d241f23228e3e899eaca93dd27f043d2d504f523eab5eb19145f558b6beb2c2cd35acfeaa39e13e041852a96af008d551d0b7c99154ccffa6556dad3519ffc3b2c2c3f77659ed6fac1930addb276e9b0b0fc55d7f3b7429e0e439d43cb5f75bba39ebf2addfcf54cfde510d44b39633359bbeaebd831817f0ce2582e00c3a431feccaa165b6b73698170b7abde29d6aeaa5d6f6739ccf566b1c3fa5a6bbdfa7ed75a67d7771cf63bd32edcef9edd1a5a18b61889e7e57080d1cb38954504d39a7303b75f75c6f0fbf83ebe586b1b9e0a78fd7eb9fafb3d4177a75f9d71e6edb2525ebad7553fefb252d6b1287e9babb5b5e5c3ed5e1f744a6caee52a04252cba4997ea5984b2ea59896eea49a49e2c0b543bfeb74062c76fb2a2e49c6fce37e7a432bd9e93a2b0f57c73ceb665b3fd5c87a80ffe0a84fae0cfba9e250bd77347857253c73c74af2ba560b968cdd4eb999f245f47f9e4ab4794fc3dc82ff34bc7fef2cb578ff2b7fd4b7ef9663d972c90077017dfcc52d4f1c1c744bae6ffd16572297a3d3f4081484053b0402b50d60e8857204149be5e419b076ed21ed1e5c12d2bbecc4185aa8f4e63b5856a03130d5faa907ae2b0305c92f5d4baa620adc3a161ec454dc7164aae1ee190827084a014846355729f8aca8e9a6dcdb9eaa8e8eea5c29443b69595f35409575a8d760ca14c3d606ab87189b0becdf01d3c5a47f007512108d8a0b108a0e199e142b1ec58c26b5898983fcc9449d7743f412cdc0e1e5e943afe0ba0b0c29a4a548d3a7e50e71cb8d660e5681b132f78537a537e39c8d0d5f0c2e6e5b6636a8c7f72b5395b55af07a27b7bb0d97ad09d683d90c9d57ee8826ef6748c6d69790ca0eaf6615d0cadcb5a575e6373b539db9b9e2d57fbab3f3934d0313c679d2fd7a8d0d871076a172d50b33a08a67c1877f1758c699297b3e39fc0097489a7a59bb487c339811fcc79cde76497fdbc263cc5223891088adbabe769015e0a2af46005c13ee4ab9c9e1a3942589521efa60ba242160f1919b9413921c7e30228f4f8cfb36858ae767a827094988614eb7efc50214b7d67b511a5b0821d326b8ec705500812c79c420fead30d1147b21c4972fc9d9e9a83470922fc04e10841c7044e4001478d9fa01f1bcf466b9a8f2471357d6a225da48d8ea0e4907e3b9f9a354ddd0c7dfa0c827dc857393d357284b02a437dea5317e993879aa7a447b3e970375acdc81115a49d523912d5934633a2d18cd07a31903546b2dc4f9625795a5b6e92fe7ea7673f395a0d90bfdf24f777f3a7a580576b7fdf2e8ac11f321c471e2588f013846302b1d3db1f491cfbc31ff7ab09ea4992acf17363a890bcf8eabc3f07fd9c6a9ca51579bc9f1bcb325c693e5ccd1e41c921adb04b41851ef6e7f28863d6f53c6b114f48083fa7b5e5f8530347188629f0d891438411745c0085157a84e50821e408c35528428e10684c15c7a242165392acb415789c80820ad508598e3b4ad03101cba2bd9f67d1b05cedf404e1e071811e0af4b44074872c47921cc972d45b0ce725e4087a584f8d1046d83101beea39bd38cfe1b0df3ae33b830dce644992d40536dc594a51eae58f20b867f9717836344cb1c4d7c0f8e59be3f82ab3fd96c87b39ff8f1a1cc52175fbe56aa7270887386632f8ee1cea53bfb4b0075f2c5f4d4e37e3dceb9db130fb4de2d559cce12ba4db62d286b49b2044b016870232bddea41d8e88d086b02e49ca922566c026dc799e31bc3440f1103cacf696ab67f9a830780823840b267e307e60428a2198e8193191041313605e00c144d28f183f23b64082f329564ff0d2891828e0309260dcc0717850322287184a8c1802a38c11425ee0384a8b17354efab0f01b256034e12fb258e121898b6d880b4d890b902c7ae05c0adf6e8918441c118395225e3ce992b4cb92b4432ab39b92b46b92b4b3c176b59e9505aa3f45dc401e6aad756dd33a298c93be3b4d0a63e4eac0f2517b5e8cd981d7cdcff19c4e93764033058f01c38a053cc6d10e56ba7056a74950784a4069627de12f3a4d82b2268b2833c08858b17ef00e3d2b304cd060c1248b17be16aae02b1c5854f88d9415128fb14bc34f62b634998248932c585df8265a91e1a9212f883049baf9f0c46d4b0c1a6e5d90dcc4b870a302060b6e5db09c70fd834503175d7871050f6988e104c737b06e1c238101c4899218375e769ae4c4c90d4e7c781811bf03c3fd7fc0232c1fa0288a391496d2d3608a50dd5a06b384b6382ca694f203d30204e09856ae4e93a40c8929f8f9b005ba02c51c2d299b2e5ca50489c2210d532918933aed780ad2d86c2176a813f0f244449322e89ab691b1a9642292b850842068630a8ad0b52e5904217c60c28c4e98ca245949938434a480226c5bbe246de1c1e8346987142411716b29913b3ec82a64103143c8ce0fd287c1ee4abea29bb07b7940d99979a2264d910b9068dcfb3f3f5680d8a2d7e74abe601c82faff6af3d1984157a8e926e555e1a2d3306092aa98c9ff73632ca0397f3c9041410f576ccc80640624a670a20a17c832acd3a42a59baaad3a42a386cc589231da8ef4b182621989c59aaa4d9b183d48182d3a76039be997b6236336f14c5f1352dff722f379dc96af4902eaa692356cc23cf022d51317ed665e8a33ef9c7377326c77cc7c7e2d559243f3fdd08205f94876508ea92e3b01624ddd1afa427b64ebeb629b235fe8c63093736647b39c2f4eae3fc274a3542b03f426cbffd51932c7e05a5a2db2773a901fde49bfaf54b539ffca5ade5bfc6a132c989952eba4c0cba767c16463ba71d9b2129ad0388d2231a0093267832eb2a6d90b574d81727a9b5b6d67a2f154f3494c852025b6b0ba4cb058e7b8aa43bf7d5bc20494f46562aa6e064d795f343465a158c71e29391f7079a4d765dad2b329a040d4df864644ea2682723f50f414ef00459986ae11033464c95080f4c3fd975e5689191639a247ae8c948f2284a938c2c4beca25faef06a323a04d1122219792ab1ab3ab1a362272351598e386524cf9254044f46a6c454d1c94855895d94d7022d8864a44d895d3d6896353cf06425a059ccfc8e90e1bfae1e395664f589313ef064e48b12bb6a93114c391909a3c4ae1a2ffc0bff027f21bcacac17c25f080fe2ba6e6d78c28da19ed24fe1c628ebbc34a824d2e957d1042b433d79c28c15f0bb0da0bfa500fcf5ebcbd4c04b03f87b54287ceb81da7b22af28bc61c8f9d7427b5469d877f1afb590b3cc376147f5860d5369d8b747d5da539354ead5a30432fe1a7397e776636313acd7edef97f94c2cdeb8ebd68a617a2293b824f2c2d41be2174ac3be78840dd3ed9f6ae08180d555605919eda2e717a53e203de1c62ed7856a480fd8b3d31fb719feac9611dea21fbe09642d1abe0ef1c3fb22f82ce87fe960417cb54cd01f7e8bbe74e8960ef04d2023c95b6e1ef8dd0da832b0455f85da5be5ebb82ffefeda43578b3e0b51c86721ca7edda7af56f93241fcfb3ac4317cfc2ce0576bfc9785dff2f1164982ff00f17ef82ce017ff01210bf7c3a732da048cdd58f8b786008c607ed0f18da08ebf874d41856a548bcc3ad4f15b8ce5d5420a18bf0b8923205c22fc128ac0033792c27d6c2fb6aeb30cc1d05e9cf1b5b9660a9ecc0e749b2d076db351a1205b93ed565ffce8084e1b59af557571a3ebe2b5d47963a92b54087753399b4a215121fde39b29a21ea6683d4cd552b71e3eefdd5859beb8cd173e8ba987e28bd743ca33beb8cd961fdb6cb723141136d7093e21c32773a05aeab37af8951754b1d42a9587ea8475795c5dea126a3ba242f55957ceeac359a39c95566bd546856e7af8f5a8de52362a14d4c30f3ffc7a3bb2b0f05d58484c3b892fab876f7703aa0c7f85551bf5093f7c08d058fd4279c2e7f1b870a9362c366cc0602ed7cd4d2a05eb3cea536de84232431e96acb35690075acfa0b539e31cd4916ccc9eb85f7ba6e7acd4b3e8c22e285c49dcc4fd76665c2d2bcc607671715f2df09cb338ead3859bf556b3b8fd124e687e1e8f0b178ac5860d18cce5bab949a572ce39641712a74ba8cf127a431f595bbda15fb70ba5a1840a5121b326e9a6ad492c2eb438ea9395b2cd8f1a82e737eb8d166e4156556a0b6a0be7b912e1d87afa9bbbb87beb719a0eeafa6f91d56fc994afef51d7daa8eb3bd4b5d65fba2a99dab330fd2e285c493cbffedae4aa4d9787b244f165b25e59572e74af16b805d2ba92e960d90b4bad515bb8b56fd6db9ed92a2eb0d7262a54b3d6862a456daa4d366cc0602ed74d6daab6bbac70b392d9c2c12da88fbe6b9ea086e066ed91b140f5c3506badb5ae647aa7dee96d2f0c39bfbb8a29a438bd866c8f76b05fd609ca63d1d0bae8da85ae7d509f012cf5d18fa587a5d6f0f55797ed511f3d8348793c2e5cb0502c366cc0602ed7cd4d2a658a97467db4ce41f790742a2939e79c33c62188314ea5f7aabae0f57385edb06e0c7cefbd30f20bbd02b14da812efc517df7b39892ae9695f8c71c6fb7431c61997a88b31c6376bad33ce39678c3326391e535454d1d08682ab7be9d52f328c1e8c710c7c59d4dedc4befbdf8569c9c7386618c33a6ad53901424dd31796c142f0fc5f8663ba114b3288e91b20203d31739af8028b501cb9b31c6aa90e2babaf78a97e292df7b71d66018a2409d5d982c4f25de5b979a77a29444d17b2f155321a882edf735402fc67a95b7939b31869129bef7de1839dbfb61ce17df7b6feaad14a7074d06b5188e3855c553c8adbd80aa552b9b322b1bd5eb33ce8b8541adf55291018c0c3200c08b0ae60060a541c51a87f96fc6952947b7603c17e38859b538c7c958955565b298109719ef8a3a91a1386a10752277cdb97cd154b9775999a04ebb249988a1188e4c402d86ad00f4a05a383d3e279c8c331326f986031ae50d8d1f92d6db81e841f520acb0fae407b5e690746a80a07572cae4b46258b7e331e572b95626ba18d458af5041fd63826343a8ea72b95c2e97c3e116e8066c13c58c411dba4a7a554f552ecb94ea35b163131d9d17063bf483a8c5d95cb72fe36675ecceee76584632b047d666330000b6469606000daa0630318bf884ad787878f64cdc006791de1f3e9b90d16471d6e1e5300520007ba67b7c38a071c594cef7fed6dc9f85dd3561aa965a753aa59d85f5f0a76f2f939aab862c97eed2ad692ad6e852a934eccf086fb0c1063d1c74509294e7b025a9a624d574fb1775da4b2579976a546a352af6778dae11522ee7c3014a870a0ec7018d93915aabb5231bc941c9c19ee9e720a4d18107f7323169f0d62a0dfbb99d9b0b7ff687ef0fdf5abd712fd3bdb5fb6b92b33f6c848baefd55231aeda783b2165522b20ed5f3acf4f78188428d7a846b8413008107ed8100049e778d8e127840311d420edd08b1a07c7ff727e328c806c64498866b3bff3d301b42180fdd35f71704e7e01b38acf8376eaed2a8a20b9f77cd65ba313c7473f5c6cdc2bcbfdb848b6eef6a7173473770548ccffbbbbffbcbb9572707dbae095096833224a5e4cd1a27b5728994e72603998b7a01f74bd7ebe6ae1ec6bf80e17ae1a2972767f5fa16ce8b8501063b317258e44e063032c8a0e2ac35a801b0d2408352833dcb8ff3a534a7b36777099dd35261ec5ab55452f0943c7b06feefac9eae5e357779eccf9ec859180e14650dc30db8cdd3b7b556a9542a1cf53b43c9981064633b4c1a41414ba40a9546900ed89eed87ed03d83e3eb9410f0dd46f04501f958a4c3ddd24d86ea34a91242f4bf2a24ebb24c7d70e1528939a201d414bad00f4a05a383d3e279c9e9e1e1f1a4135cab46385a0a05b7335973bb8a3dea855c25aab3571df5608aa376a9525aaf40bbae180467943e387fce960ec40f4c083f0830f6ae8724835ebd40041ebe48000947339eaa32293d3028a61dd8ec794cbe544dcda08c0b1202d48b9970b177f9015aa374be2152a76559d85d56ebd51ab582bd4c73ecd383684aa2e97cbe572391c9c168eca76a0825defdba05c7ea8d00ff5b9f7bf530c42ccf93dc3308b5e6bc55244cdb2a4565d2b586b1d02865ac35ac55a6b935ac75ac95aab16276a2d6bddd56654eba95654ad95886eda5ee5f5e91daaa95ab718e9a9548a6b39d3517fda33d4f7d3692769f1a1ef5dcf5e96e463f2432c3a9934440e9d24c7711493b2e4ba2886610882e920a8b5ce495884f49c31c6374957a5df6badadf7eca95a1ed99086475b746b7f149de8f7c8969c6e1f7c4dc16927494210c9af27418ca9e1e40a99a42082404a9374858b932248b47b3eb13979d24914b9a1e0e6f74a5f2d13f0e77fc07dfb62c1fefdfc62218a0efbf7d5a24f652cd017955159fd568d7f7dfa6259587d196ab8c9eaf7655ff4f2d0afd556f0a9abee2e4fced9081014ad682bcf02d10fa261a8542bdaad6a54221a0106000a009316002020140c884462a12006b23855c507140010629846665c3294c8c3598ec32886828c318610400840c6186398299a1901ca022ab4c5d1af2267b4de869250e1612cbaa351274ee5d2b5babd74b3585203306474f6502b2e3e8b71257b6ff4d2a7a4bd86d3e8ecf80de9d4cf5d9ce3e50427d3197d1370d4654fd6c972e4eabfb4b04484a168b353e465080b97910cb35c8d36d8e44674596c1d2c7dee2ca55a11c8959206fc9c95ee5653bb8156773320c13cd0aa4603f2a054ac3f707860e1d13a9156e1a117b545ee08786075361a88584630f45d4b56c564112a06b0ed827cead42ae0cd21d1cffa45b6d60b962e491ada7690c2b2a3fc51aba2d371e61f5852cf669f8e4b41805b59dd9eca5ed7d8ce62b3d30285e99935a0899cd78a8271a6baf859e4c7b4c8a9d20b131a30c0fe6cf7eba69b1be78eb6bd5ecd7d15554b53f6d4e4e9a4ec9de9a8ef25b5f5560b19bac37a9a22d2401d44fd9058e8af183ffcba3472754b86edf158faa7ee5610d49fd19769d0f03a6ac33e76ede8f8670f142f340070b0dc76fad5d952ab44d654e10e1401bcde60913fb0809f183c4d6ba68f0c404d63a1d7cc777af31bc4e17c35f07d6a473bfdc5ab24dbcdd57dae1475e435582d5cc1d7d3f607b3b2c1f9bc75230d7d32774bb395a8cf3efe73d76f0718b91e76d0bbeed19e80ba3cafbe501dfd8dd7c8ddbf0b43c2043045c1cc4c72c2d90433c05c8e19a5c413ea02a7feecdd0030b826e44d881fb90a8864b3e044994fc4da735e67dbed9e4d0ff980d8ad4a1d02b8a2035f2ca29cfcc73cb9a6c11b87b3acf944a9ce7ff0bff3b28d5986bceff709594b644c057067636c5c47e550661d21143794269585c8cc3ec2a23a928916511d2cdd69ca7cba3277eb3abe4222518f8e16a2ac94209370377bbd3e16808b1eec1f8c1fe7c13897a315c91ba03ef14fd0e37a6d7fd80c6e24bd3e3cb73fc0a4ce5b6a68e0989f46377209c50cd34df2f237d2b7ba41993a9d5f7ab7149a04029b66bdaa4bdf0b640774ba701b521871b71823a71b5878cbbf79f82b8378a919facd9ed2e928f51aac1086384e846e204bd10180cf9c59b33927642e17218d01d56be41eef04b100e56c38734df6c3787ca02b040f7e942cf735dff23865880d2508cbc73790ddebdf9366b25c0d992c3cf778f9c65c9c0afc522880d3b88f98175c4b3dd6b3f6ac86615b64aef6472d46ac5b68a97ddba8ced533773c90768f1969ee4f73cf680d31674bd21677118abbe8b27629f4a76dd49f1739d66a79cf3b3d656a97b77eb336352ee7515fde7caf2925f39bb5a0a7809d58a312c7fb74e66cf5894f0aa69930d1cc6a2d6faadbf93e6b4ae7cb01ff7dd45d6aee82c0df717235ff9fca032db6cbcdf9cb7f39db8a36697af6243a1baa86be3cf8ca47e7b8414ca07bcc8cd67788f981a7d10fa987b81416cf1e02b5fb73dd0f3ed4b6be82c4e5e90272436131807d17a085d69e47a54008e2149f6d06a0686c52888a707f441ed2f455f43cdb8f3cb425e3d599f249d8286490478178a5f3d1e2b591f7f1bb313b4b09909f00eb1044fbc987da424c7242e1a796ce7d35d9013edf4c49c56468dee0b116ad3355052050f3e69f823ffdd52075ef76a2cf41fa326a03381bb002ed0594e632bd1aa4d08f1eda0580708bf80ce7f768bb5ce3a556bc947504a74db45162a2a3dd0948373406e48a04c18d4c31876c3138009dbab2c0d87769df30f02fd75c6dca6de4c248753a2c290692e67361058f233232ff929829ab8d6694af68dfb1049541cf4b48ead0fa2e4516e32a20583dd87f084d77ab85f69e013d79cce946a62e5409bd780d92d7b079517ed6c2de3e5a9b0026e025d09fa4b6046a02c8ccf9e015685eb6db0b89cf88d617a7b5e2ea6f97d93f4eb9e5b1677e4ee25b7a3c4ab9fc88b990a2bf54df2f40a8957ac86cfcb21b5a9ef4be9c2f61d24802820e2424eb99cf7e03b11f9ca0a77dacfc921f2aefb91d94ebeccd082d29605acfe7434d24fd237698dc86d145f63c39af686a1082fcb43abc5e54c491a5a378c959cc2a2ae26cd4887ce66b2357468fce978b7f00a0e7be05f34270900367f7f14f82faea9c66aabaa71f292c50bbb74f4aaa0e1205370b68ee142db57a5d933ca07946fef08e78fbb5b760252facc89a8076582ea131b2608ccd0ebf8956b45155edacb2edbd2d63257f9276e3e6100dc1b7377099335efc0eca565407fd97e2a8b2f23d591d71b73bbb49b5ba9c2b01dc43a65f6eb4eae79495dc5e981ac930799f6559995864f141bb380df53cf8f54d1ecea4c6630737b847cf1d42b03d0018c7c82f70b5c5c1c26b4631b9b9fcbc15275cdd58c1b828637578ef2c38e420a489644964673018fdd62f568cbfd94cd1ac470b999b2db3172489d43522f4084432edf3f00553af23d6bd973a3525be44a1700d2d49610e33d6348f81c964be39f530d9a684e419d98f1c270fece7f5af84b0b3c02d6873b0b3a7d7f3d6cfaa178b64fa967caa90f129d33cdb6323f6f8405caf2f021df561da6b0203e4a602fc9c80954d0472940e607b0cb09dc05c1177aee6a43f7c791438005201f055e2f21ce37f3717e0fdf57fd8f1e2b4f5fcb7f0e61241021953be3dc68c959ebf83791a083b13ed1195d861c7b670ce042dad295ef6c22f52082a85b85ea619fd44d3293bb4b7864c679e3f472d0c26fed49ca4cbd3976cc7b95e8b0726d6143562fba60914fe60c1e36db7f9df8146159330dba59b1130f63517b54bd2f526927e768b44b8ac49d771a99feb0d044079fcfd2fd3f36faec9b85db77b0b92a0984087a2c3afd9b49dea05d235fe13b3785bbe3af544c0eb321a25716bde9c21341cf088818c2c160ee11641fcd4f3d267555373c53f2681c9b414d5e27bd3ba8b776ee073e771ddb7cb9a5efdb66e20e1728f1b271b4b4b1d73d53840fd470333d54a7d08c688eb65d3f835210d683079eb36b89b6fe090496d9814efa2e78944f434018a8e75ac9f06cdb43014f6bc6d1f088679af1e1eae349089c7a9424b03a79871c90a9110bbb52162b40287f5c50d6d1a24db34132d288136826bd6b6017f033f3998456c1e31dc13573678cc6d903a99d0f4c2c42d861e8f873019df8e62c269e755f15dec6d1e002164d237c1c306843e6f2bb05d3d9640b5a73fbedff4ad43cd365a970848ab865e8e26924c1d31a69c0a845799883c9536fec244270797140ed117d08e8d00e2d882c2d346845e62a494873daf52739c768f53beb1069a520212532536d46487e2ff111ea743fc4955627347d2c7f0c0d3296f0a3df8a9d32d721fe8bf5bdc85140a74b07052965cfbc0c72cfe204b278c1e264e6aab9f659678f7f476443f0fe14d48c407c4f4420a53dd9212810ad8118b71a4d72cebf8007f9066ce1436f70ec266a6ebbdd2c4305d114b440173c147466fc8262256daf0896013e2d2ce15405a563cc1c52650956c092e86d6ff10404d58552eba8c50967b4ce5d2ae8f49936f84c60991322bae11071f9e719361be3e68d435e898fa0ab14259b11eb475cca1eb762650102cfd8ea05f3987a4c55536da05c5e5ae0ea102d31c861a01a964b732b425ed37c0957406069812dab635192d6805b8667a08613112afa8ae66b4f6f3d79e04a73768f6ff5f0170b441e0ce883eb87a8162c85adf91aeab70b9a7f974d079258835edf40c2c06617ea37fc52a83dead15cf028210b936faf17795eabd748463de184290ea676d0aa891371a2fade2706fcc315e2f425dc5779f021c247aa9e6fcfa7bb1f167ef81d52a775190866458707b71550872236dc113a60555ff1f7c6d8ee3d4982341882ff496421dfbff80fafe5f7eab3eb12d10422fc987ba73784242ade81ed224c39e970cde596ea1e6777afc1d2829a45a6babe701b77181d626c8057ade31ed0b51ee983063f47d61da009a3baaf95511870f42dcc6de6622dc54f4f43e84d7d3bdee3dc5dcdb9343844681ab3bd2c051d762c16a4ca01009ac07842e617286a74b51ded45f9dee774005cfad354299cc711f0c4eee24ab1b496ba0c2ef2ef8adfa27f16d7322336f6bcc854961eba26dabdfed07b3206aec4582f1977f4870ccd7bdc2c427106acd71adfe732b009493ab9b14c13cb57febc87300934e2b7687db049fbed530fc2c5131e2013a5796d5148d2361626886a24dd1e2ed87bae5da75f9b6c35818366b05d3810a63ee658a5ae8e9b31efc87561e71959ac5d25e02fb7e9675025c39de790e117b9af80edf6e607d13a9bec43d0f0e1101d5696248b79437679e9d2ddf3fff7f2f471bed3b0135241f6f34121c9f8109c5a45ab8a53eeca80e80d46a010e812fe23fefc0f91887920e2465fe9590249c4ddf4fae854e346d4450e231cfbed1f56a3477bae240bac1a6c62e204f15bc64c3e0a9848475c36a14ed7a057657181bd6c846ef5023f0713a049533ec260b607aefdca58954a2f6f2c1365d224beea241cc8a10f8d69099c81cb21f435125dcb0362bce175150f8a435be332b1dfecb517224ba234d05f53fc0f63e55b79bd1cd0fa5dc0620f17078d6c3929d47196b53c8c5e33da6bb11837c91f391646e437012c042c54e98f06934dcaaae96bc0662fc9661c6e43b821a330ba45a05f620f36d9448867c5c866b224993a9246a1a3039f85b67d989bc409d5d6cba56534c1096ad69c5a146e4b27fa9d387db89388f8d9d197db706b2dbd62ca6583c235564b29405df6230e4f96feb64fb3ac3b8396b8a3a64648d1bf3221c13eb2363e0a62da33b11b1177d2b0d46eea66c016afaccd19b9a10539f3945d026ae9ddca28d3b42df790bb2d74b67cbb1a7b897ce391a8e85664e563df1e6a4507c07966dff7f886f17c081b2eab3814956a5f545a207e8ddb31c8586dc3f82b6c3442a354310e1e50b91d4b1cdec5d9da78ceca4410411b12323b4250e2c7b194f510a056efcb00da989377315195c5099aed11640e54367ac548c5fa1449cf887210d8185e90737ba13b82231391a9473f8665b3497481e36db3fa864de9818563b0802fe3a010faa0e7c215fc19d35141a16f488380d92ee54e146f88f6a56838df206ad10c58a6470fbdc952a529b7c5798f86b783775345e34443769a44a6e83d37ffc584093121cd01dc6eed3a9cbe5ec34a2bbf60ccf9b7e7932030fac438a2611d2deb0075e5ebbca608692c05ff591ee082e020ed8d508952e873bd01b5d5dbf65224352d28c28caf2aa6ff380b28c2cfaeb201e5daf62778a7ef5665217bd1fa397efd5aabdc205ff264e8f55e9a42b83c1e04bfcf480a929fb2f6ef2db36e0d489ee6d169290d050a6beef14079f044ede19422b1751e15d4c7840c2c124a6cf45327e2206f71c1e903323013c8bc991523438c6f3ce80dc9fab7b3b0492feb4be811129358b7ab96fefa9129840cb23fcb64a75d56b39ae54a3ed60117f39d2c1554c70e248de5743a04cc7b77984ea2b3c34330dc8b412c50864c21b81fd9bfe641f7d35d15c127e6b7b1e4c8bd392507db21c92102a06ea91b0eb71f7a504cedc13038d3ccab5fbba38db87a3437cad64bd10104e279f71872c45f0da94f9de7487f5c105ac19cc4110ccfc48537e56eaf6f0cd54b49c693a79c3ef9d5945a8880efee2373b861fbbd48ef41eea99e5571002bfe6879fbb0505e6bc4b16ff39c5d3f7050f8f630b7e2fccef9333f66e68ffafe6fb68464fd6f317b0acde8de768a1fbde92c44eb20d48d279421cf79411d29cf6b147ca14273ed83412145a726a40a910f32bb000be3aa0df2b9bde548b7297b11c0f0f88ec800d808f3b0edc2f862b1ef61e35363c7b97430495e58bb60c3bfd42d8c75e0f405670a342708c6d11dac04631f6e19338248401fa17c6c8f586de3315a88196a8649e24fa5ec0a432744fe894bcf91ef4eb789bae92f20e62a55198c60960b013428027d5b63687c36f069dba60c7c16bf9251420d78276b92920cec947cfe3eb0e155e3091d16fc0826f7da3c1937d46013cb025ad438c21cd551905b282a05970fdc5bf043f14207c34a9ebe6cafee48fd1f59c7f04d50462049544c131aa1ae694130e33581c1d9ded06b4748d14ef6b00a1ef83555f5899f9730f120a09ccd62715b4cc9f41d8682a63c1868cbdcb463ecca7b6bd2c753ada22de21878dbc07303cff1717646b0718d8dedd1fb0d892ed0d18e01bfa7c9602e802bc7f47f813ae0710674be09b506a2ed1e7ccbdbb94f1905cc1a1dafa9402abf700c8b90640ca531093eb33029be6c1d616a9c3874d92485eac46b68016a5416931184414ad146f8f6c0cbbef4000d60812d77f547b79a6de8fa551d9c5a226ba140d4184d6f83f9804c4eac54a7d5b1b4cb3f18ab30615237d261f6eaae10dbe2656a37cf9679c5d09b525287aacb046dedc206fd9c64f1da89930eb74d918e035845428fb6b82277f4ca249e52db0c259cba769e3af34afc55c69e6d58b080c37b2406b4fc0f83f941e003c20a04c31c792462f2bbf2e5a284ae70614e29e2b40a2dcf24091045b98361e2c308d0c718fab54491a4cf744556ea4fb02c4d6d2bc71e0e8fa9f478a45413aec83db0d4f60475a5568d25040b57e4a1bb3da0573c4e110cc87c1ddb5b564bb40974d3f591b35defa050627989280083b19be8a42df989b973d257f3628fa6c1aad0a6b9bf803531820816ab2c64204bbbc1610e0aa5b4f280550cb2c86982ed1fe3bb52266cd02a5323c687a7eea0dabce6238088f41a5cf4fa794a628ddec3e54822e46e0245e8cefb0f9bc341a0504d1a76b2d7ee898e2580c3b38ac8eca50a87f41e8606da57d4507259e1ab1c56eb9c66b8e6d44ec95fa6c8649c97612d8b5a6e8ee2bc0fff1d36c634529a1503db5fe76e43198cc3f2d91bff047e224f9bc62075be8e9f8384b192d57dfb41608f4e514c94480ddea6cf6f67b08b8ddb65e37ba5ccad02aea0156d8dd4c02f3127bdce4202f427631229e58db7132787b2496d4564c60bfeddb5de0b76cb6d05e7c915ca565a6fadd2342e9fe626ffb21e8dc5f9cd92b4c774ba6c161684ebe164ecb55fde1ec8b76f3febd603f536f3ed638fb285ae4129efb28e37cb6c8e74f148c764d29ab07a41327045f24b55e04a8ba345daa2cc5328ca0ed7f405c342e5bc37eb4b5ea7123b8da83231881fb5383e8ea0689f5e4bd441916e580f04bc1282ec6dce651bb2667cd559f3d420ccf9c888989eb8580727e05a66eca02c5aa0b590441987f981484166d008785e9dc0b3e10e9e6a2f49c2032195e0b350812d257a12bfe64d13ecb07494cd2e58dd61010eef26ebbcd09a59b75216116b49b678f9eed5c54ceb387c5030444f49efda9e7ae549f34e0570088a60e81b2877f638eb646c32c6431e0b44dfdf1300b5b1ecedf9db0c4c6342ec8d6c017e0d28006b5e79f3908e46f3933b1868e0002d31c1207dc261a78b66c59dd0c002ec89891756d4abbc2c3babb98451fd7cb12b1f29572655ad6dc347b1de79a6188615545d2898095d9bfd7f5f63801d3aa86f83ed1e278836eb011c5a957705be49db62225b3345b6a25039980868cba70b70653ab5b11f38e685c381b563009376d37cc996e398187c0bf6138c355a9c8f64205e259099335797d0b0884ff87896c9bb40a18030ec6a4258770d14f09ee6f7b23312d74f0ab282eced64c966a5d71c9ff491b91c790169bee29012472b218f72c30155724bafa1952f3b29c219e49ad508a0719e3de11ef4a90e2bc430a813da498d1d1655577a453b84da38495d6357f709de4dc8524eebfbf3e282afcb184286a48c9a772b68ae48411c94fd2165f46549a930de3890e19fe1f640e05ab6de1fc8db897a84469030d1209bb20d3ff2b0013a28ed8e6435a3f834d07e6f1d0ac6e1c4e3b844feeefbb9536ab9daafafd267738a89428c8446d4bf564258fd9f7a73c1be738644e56a68230199980572154f04515af84200fa129ad113623b75ce2a47f1887f86fe37056c37921cf7763ec418811cf2cf6392e604a0f13bf799bc6fdd971facc5e4eefe2a53db93f57a5f46b2eb40e1bb5bbe6624e0fa02ec4798cfb53895429ad7692ec72cea8e4d9006f6decb619e2bd733596c1d90fe34283841cd484f1dd197180a507817ff27e28bdaca187a463117b5e5a5f7301168de09cf1d3843047cf5eac22669946c0ce8be71743c0d788d0050306c047ee3daaf5c5e18cd7b811fa330fbb6064d80614225e21790a03aeedc826b52da069db05a3be2314b70a80f668a46128be8152086d42ac05bb60743e08eaf7ecb7e25d4fecb5829159668530c6a5caab37f5f17723caea36cfcd90ee93e1b4b69aa9c67a1e51707d93c5389942dbc5415d5452a505cb0f89175178cdc9624cab4d89cf9182936e3f27be80426790838e666a3f9dbcda26b8d54560e57ffcffa027979031ce175d0c41fb298e86635c8bd156cfdd8994ec2ea7ad5c0dd70d3ef9be3fe6a2dfae5f168bdb89e63ea584b30380e20061ff5648042f524a3c06832ba28a3b8275ce19c304d18a6228d923e06ac1a43aece8f385a87aa5d4936075381e7fde6c5167da0e82088b1ba78d48684373d842c05d586d387aa9be925ed742974bc816db9ff9625ff7187186c8a124441f4011c9f7dcbfc3292309b8b320ac76d728991a9272636b195b70695d88df58316d2dd318fb2c9fa4aa41ae5fb481428bb2a98984785a4999d2215044fcd080e3c7ba4a2843ae6142fdb89acfd381be32587e9c1817c51498639dbfd08ebb6eac5e064880ecd36406368b952c27956b32fd6c76399bbb9412a3a1cec80acc8199b7a262d082ce763e27fc04e106260490ecee64c3500ec4677ade5247e40c29c3620588f2c4e23b386db9e790ea70c822b8a7f0cf94b768aacf882f45ec7f6f74e7bb172692fbc4567d3429965927d5aacc204100c16d70ac153e0be2db85608b23d60036d1b08982c89e704cc077e1b288e6f9aa260b6497398dafb579c8ba746d7c8e09ef61e984a4fd6af0a3400cc812760c38896400f8b4778feeb8abe1033a0447d0042e2fd92a966964e1a03791fcec83bfd3ab89c37a032de3fb598747973803021ddbafe08328bb837c91df3b3438ddfb77c1d9ac67c2e3390e83851fc45cceb4dab2fb23ec307145f1561542bff93be8f2cee9f56445e06d47e3b018f1c83dea148a424974d159dcbd38789a5ded97da62c374b05e841991525c5a34e3ada0f67ab523eb9c43a01e78e4b52348b763112798bb8a1e0faf50d6b615ec0622b3ed8363d592de10f9b797b0634c31b7a3757a7c8fe060b011b12947e9540a8c60f34dc7960ad52f3a16bcf2f10b92671999187aa002c1ced8e45dbdf01c3d0ff319fe0142e4431b747aac5815c792634d91bcbaed26e0534482f1f0399620d430cad797632fea253e710ab9872b74a3a94579e12a1a05eb2fa3bde7f17a46161547b3de8cd5f17543bb3069aeae39986b80e264b7af8f60cff102a0af5aa508963fb1bc07960650279b2031dc3c9e503f414be6c7dca12a6801ec4d83e5b3b58a5958a14fa7814e7ad0bf60e6b3d45f7a828c40df2bf4e9e44e7861bfc52c5e711042b4658feb54b2e0fbd921857bc1867a8c36dc87fb0e5305487b0c8c2728ed983bbcd25cc9f07957fb25cd96f18b55b87db708145ba0b2a557d923d0483b4628aeea8eadf50a0c2429e19e58361571027da24b1b368df9c4cf472aecc9ddbfb350ea050a8f77759586b1d7241371ac3f32a724b155883ded53c26391a8904c5c216b773d932f9e61c48458f94d77e245a8f3fd963681d5cfafa582b84bf031180beeab8a0502a8e20e08bff0e8f31fcdb150b88e9d309ff74f89ff255e02476e16bb4a9f3ebb227b0d0669a6d8ae20dfc99d5d56ac3798b4b994fb857fa0c866e7a896b207388eb19a824e736ab8f06e4acb8c554b94010b855dce92b70c25b340c6d48e4a724a2b60a36f4190eea2d12eefe08c50a2b04e2b7769b92218436f0942cb2538080497a36ad54b599d236d6795b2fc0a4611baa301aedfa7ac221ed8d63a50e9aa3f3bdba32b210f7c8b7c08c2daee11f6f2dea8cf90085bc178b77e30ea0da28967336579dc6d67f10abeceb4e5cbcfacce54132c9b8ba1e6df9490d81b2e1ced0d275a3c3cb673c84b05eacdd0f2e5ad8de390bcb07ee1cfdb614c6495f45646da1b80cee46f62d8da62fb2a11b8b77e98bfde641a58ef40a7ea7e69801cb3a3d38c441322947145033e65d154de15a3491b2b066f0a6228fab6b322a3146afad6a5affc6687789596785eb287229b15585a084682b8ba3669156de55c21d23e696bdec03292ace317959717ea4ef3a1f0a50c399ead3da810bdc86029ddad317348d75285f5ca95c45bb8a6849534c790ce6e76c99b93a9b571782b4b11602d4fda8a05dc5e835fd85c94806cfd73623df0b392380b58a213aaff4f3e2506d44aa82ea55b4638c518ee901c62035bf950321d09111a9c2abfaac888bf326b983cf1a98b44a3b8b7efa23f4d67c4a1b80031a64def9ae9b853549e39a20371e46735169a2343116e850109ad58c4570e511c6229d505e58d10abd358401e46232a46f7f57389944f3ceca14236638fde06ea01e96900caf9303fae0d8685a2b9595a16ae3c2ac8f3f4dcce43341b48627b229be2a86d2be1d3c2eed72f9f4d56a137b1a07fcabbe9369fde50846c17ca9dc3cd2c0afd3a4d6ffc1558e85ec86aee9e6c667a709133ee54bc19c14d629cb35f8f172b24a246c585fdc16c4b38b5c920ff558766a3e9651625cd2357a47dd50e85a41df5064d2f0830fbd46f8e38e475196503327de30939d35b70db45b73f36c50b7cbfc5781393d70949265bdd6c6e14abaf4b3261f695491a3cb456be4f9246c502c607acff4f3e6266739a1a9d77448a5268a13d75f4b79d31bd8281bac19a2f9d70fb97cd80e5b934c3223a123af2a4a12f01e6e59ea032e40245e6507f7094074b781c8ea61ea0792f60c06f85f7c45d8c36f8929483d47b049d3af5e5b8faf49d825a077fdb09680822ebac270a9e79be376b2b4aa2cab5921b44735dfc8e9bc98e2b48b8784fd102ffc264db0d61642ebc040cf651e4f1908f70c73c78e940efbc15dab45f67fe44ee0135b16e11738fa3c33e2111d55e52410fdf7a1d266ac337e3507084264fe0516ccae48882e1da6673823a9465d3e61400848d63ab2542e2c8bc9555f74532b40f69d995cf06fc1ec74c8e5b10ce7410842dfb010aecf9e9d8140ec3deae048e96a83cbd001eb2db90d6ee1dbce06f395956a554a215d8b83c21ff231e70abe137752b80de7a2c40cfb058279e97a13a39d977bdbd12fe732b5f1b8682649230e756585a7f449c200ac7478642df99e8e8368a4daea7ce405682ee1dbfd3ba627035e8e0b84d047091041213d0aedbcb0537bd4ae769bfa9a37ffa5b2cf600d2e6e6e4b542dc5944583f5a5a082786579303426e7e13dabe74d7739c6b6364b14feec8a1cb7622043c0c8e54d191130660488e3288bbe43d692b650f78ed448e576791f51b026afcfd76f38634a2883a780254d22a3a4b779bf224b8f4832a3dc17fcc536f7833e467b4a1ae2c978a28b7f2704b839379d719e8d87f48010584e170945b34ccb2c362e7bcb2d4f719bc4a3133651751b08b94b29d25ee1b6df75dc033b5ab4f8875f8ab82a1d520d4e800e57a9f65628d5a8c0e24b114c0989e2da098b344cd185707917fefb842c14d3d927cb7725f669c12a1776b7b4f5d113afe357a134935738fbd7656cf4e00b1b7614ffb54b76cd853f16cd660782669417d12372cfeea6ec1614f2e08e46cdb580c092492bbc7258ad93240600390b6d8c526f6ebde4daaf372a3048ae1f4294623e93a22e3429b39890ebc0c90315cb72e0f2e56b1c3f809b7d6029c7c23d17d20ca409c713a9cbd7058aeb71488b3d2909564c8ea5ed2fc00e5dafb13405e1d8d1c0d0bb86435dabfce9d3310c95c29cd76e0137f398de27c24e0f4d48bc9615c5d85784a6589a4a600ee096ae1db007522f8a7b00e7820dcafd3c7cc7a1087b0f75911bab8b4f133cafe7aa0f1b54f399aea6c48aa4172744f53b24eb10195a161ef8a7a1e76163ce2e0563b0a1f53360fb0e5316d98cf0e64c4d43b867c2e8278ea258ccd687ad284e90800249116bd78b130f1a89a49f0c6fb53b6741fa9fe25ce5931ec20d6f8cd2213511b189b3858cc418792c2dc30d17e2ea7fcd82509e587c74f17b60a253d66f00830ba8dff046741fe03fc1b463eb3840c9cd2240549b615ee242d7ca14c128aea04089ed7b2b91adbc5c44cbf3e2b2a31901494fe20d0206c9a0c23f4077004f21a68ef2efd0cce334da9ee471710df077637fcba8bffa7e81b48563286574be56dd06343fa27d6d83e2de6468a40435efd6d79980ece60944404ed9c14296c409be2fb620efb04f769262e9242972f87f630ed89e23c6dbdf488aa04f8e5f0834e8eed7db5dd1b5d572b995a53123a79b081b171a172a4661b6bf3a851a1511eb3e1e8df93ff3d01c4be13f85e7895f5524da7c54c8c6d69de137da7e0baab8ccd19a7ac04ec38d54d2bbd7464f44905ca278d631d6ad1472553dee53ca534de13595dde452bddf8541a363429daf2c374b445c0c516f81e521bba0bd2bae95e795a267052a4dfc45a34ba522392278625648e829fee72dc6ff4b7225dc4011f0e570273048785b316b3e7d298d9f995ab2f8ad024b33f42c76a70a8140fb48536bac5693b38146846831d91813a20c4818ea769fa528df33fa0c362d22cee88cf94952d81fdb0fc7e040b1211b4af1f6d23588b3b867f09b8c5bf20c75295698c7e40cedc8f4b256a4ee0e205d02004ade39c85289e5d28e3c017efe8d1431838f6075dd78b90e94d700ca3d1c914a37e0cd0645a46a3c776ae3d1df7fb39f2790c8241592d2f248bcc4cd39914069cfd17e5d4c03058591554dbd4f10bbdecb324cefa84b30b3ebb39cd182f547a6d260bd03e32ba6006422d4193c103c330268b8fa66f1827185560b72ef86c22728503d7906eb387960e7d50089b079c87b1d885be930c913fc277eb1858456f8072318e3c06609908f80c3ac02bfbc15a1b0529d7d3d3edbc3b1415113d3b35b0c2afef8e0843d552ffe114e10508c2905f80ca1901327fa1d01c9094c8a4dda5c40ebc3bcdac1f329c362e1f012443304d9608c6afd400093238841814f93c6330cb449701a525ce189a2623283696348edfda6cdf9140ac3fa03d01e2aed5468646246852920990129a2429d2f290080afba248a220feb7fdc56081faa5d66b4920a5272f98c0681d5618d79c82a62fa89680e909e9ee8506a6dc3830afca8ba1aa552d7e8209394339491fc5a4d471541f1db6d26252039ef005166b9f7c4401ebdcafbdf09928902bec0beb675b012b0b2489ebb8c0ce4991a05cbda3d11003b51910ba1afca8c80662ec5c5c2925ebfc8358df18787dea8b3fc5b68287e39d1510bb477ac36846e55bc035236c071c8a1ccf381250d77eecfe0610474026c6a153691e4e18286b5f57b1a14f1dd2e7213442d329cb3f5986c9aa129d4fc60f91eb33985857c10a03d24db2bc219f854e761bba42a57d342dc462bbc3f487e8aab5d3c476e014f6f9aca8172115f16681766a163182d3edee2aa52ddd723eacdb7467ffb1974d398e40623f45ca988fe73e2231c63c7f556c5a146e574505ea585ddf1f45e386fc5c457c34df0b3a18edb70a993651411d2ec70ae9b14e619e0d8a20bd85fb1cce00dd3e22098a6686bad2bc515b51e4476db2e72b0dcffac8caa0cc9ce0a19a04e0da1f3d139c39ccd42428555186826741f542cbab96275d1e9977a579f585bc70fb1eebb00c6c5441e79abe0c60723014fa502414d6a2055893cf9d4b4c4a940b67ad98e268dab24cabb9318748c086b49292f3a0dd6df375502080db95e988b06264ee33999f0ee56c135d9bb978ca51f5d0e888b9492772087698fc421020f6a9f00c504060e79cf56651ede6ae9ec075ed1383107411b3f1c923b6fb09297811b7b5d70c96b63ade87d32da1338869bc9a722b33b12588548efbfd298d0109cf459517912e0b0ea6a9e1f7a33f91dd6d7e98ab9a32a68a630855e820069e704b2bcee1f60b18ce1217be9331da119c8ce330781abc5996229351bdfe4b4688cccfc8b1d3c74e7c6061eeabddbdede64fc00cddea81614b80e23d1f2bf0dd94f8b13cd69784f35d9a759d7e290f9863dd21bffee47dab682ec0d1673eba7064ae4413c33839571b838abf5786f73a4983badd1ce6e794c72f9dca4fa69f851c46aea6a8aa49b8376c5950810dadff05cd1ddfee85a28b5e0bd5297ead33875f15de6b0632812bb80361046ddb221eb9b6df33f020ecb470266ea083f43aed152875474a7c3421f267a31870a69cb313ec0117811655ffe3c1b91a23e299a02d4e602a754fd4704453877a7a228bf9469edd023a90bba54a0ef44084a91682208396e26156ddff4c707223cb45010339be226cfea2991cda4904a4c2e06fc7326a41bf412af61ee01c459ab55267b0ba1a8a6e5b7b7f44ee742ffa9f801340b3460cc171e0c7be2a3d80ad9056ae8d43062e0c0e501f55f00f1598e85153530bfff860742f24f75d761b99711dc3bfad6b71ed4df815b56a92edff7288e7d293492b0ecf1ccf835a1e081786db38ea5ec459d9e70bddd4569f02b90f944cfc01f6c7e63b708f5ef14a44658dcab7fa02b7f27eb152b1bef812967f3e18a539efa5ca932088b8fddd9f1460aefc075df5e93621072aa4d5280503154403befb48ec83831172aa280437a37250597c3d7128328ca59f19fa8fc9429facca809d294b5a7d802a388f2a71f9859a0ccdca9de294a372933401ca9f22ec33c24a2bb2212445677b66346850efa9f4a7a856accaa8e8cd6ff60a243d60af7ca659245ffd8eae3781312733164979fde04b3d186c1cae30e20011bc46f8c1a1c2856b2593877b098c72bd08a895c989db0c6c4c4bf500cc50a6465f04e4a0d446b6f4ac780d4f8a79d7e034c15de44cf5eddbd4ea50e8bb4e4ea92681707599d7039ee6bbbea540990489b163b828019cd6858403c88a4b50200b0c643059d55bba2eebab0001b620311d1f6508b95714d3578a1559df8433053bc551dfb1c05fb42b8697b3dc37d774cf4fda28b7c95341636af2a04be82c0ba5dbf3db5b9e1293a8833009d184a9ebe375c0c81ceacaba60a224824082f6f7b9f8f9fd781f01d00503e3df14830844301e52c2056f0a3d1c853488c4de4db70801d1c40ef15f792d5d1530c78f00145b9dfe739808c15c37fc37768c2f591229b4556c164d026ce92017a7fc36ba89726bc72a16ad04c80748336cb76a2755daaf7d89795340a7266d03497b0b10d66123c9434852d34738aaaba761a3d0465ab4c3866db06cc93d417d9a7f3653a8f294cc57888b90a15f085f5b627406378da65a52129da5146d43f5886da0aa4da5f128f07c631fa2ace19d42e4150ca043e392b12baf09e401d5c61819d748cd3a1213b19b0b0339ab3f85ad275e8a88be35c114f4a9b4b20d9099a36cb3935256cb2d07c534a02c67a2e2c674750bb1fd35a5c04314e193018b1ca7fe5ece9d0b1ce91d20b4314ba6df9c83e85c0aab100709757a711ee4df020aa6ed02cbda31019d79f89864d431612d71ef9d35dc5280ac27833aa9251ee763f78c8bd4765824182aba58ff406bc79e1e534391a17db527e9e3658b55874b5df443f838e0be103e5a361bea27fc19ccf893b812828c9739d67373865139b4a8ae28253d930fa365a244dc60ffbceee14747f6cfec7ef5a662bf04613c67685ff382a8b0922889ad12834d7ba053f91a5f173dfe48ef8944deb4868ff4fffc38539d33ac78e5fa91c378be01acc90daff786de9acb95a9c77dddb008b55b279b857d27f9efbe68f8c922ee031519ae281409b8f357d5d25fc3c63cec0d4cf811fea25174eb68c82201d475af3f6d9e6fe7a03f6ef3c1f13ff587629d6eb95c2ea803f2367bf6c0e613b05de9bdd73b955823fef963f1fa81bee3838c24cfdb8ba07bb97ec65294bcdb109c9397e12099bafbbdb36f00f2ce82c2de4bb5ddf375df3797d553c82eec56c114fe41259c493e30e330ba650bd4dce1c18cee079713ce9ac29492e679c1d61f9b359c72340770cb085f8a0274134150f7829c0f14b1fbc1f1296b265d2e96f18af08634cbe14f29a81d2c53fea95981ae1ec0b49283b68e8bd80ed329fa710f5130e2cc371ef12c15e50183ba6390fafaf6987517daa2f3edc77c5bf821e22117f74aa20f89b27aba845511c76d616f16fddebced55c4c482524dabe5f50810a519d13e55beed74f1a13aeef62a2aac358a2b5a822b47186a979306f8615893d710ae478cf81a50275442bc274fb56f94fbf1179aca92221b66992365546e3926ec8c7ba5daf34060ff35111594b35b7bf127011e68e72bad4f8adae7728b39c573fb0e26a9bd47ec12b1d20f1793e68558b9362657b7ba56fc2a0b64c4af54d95a795c0b78c3fbca6e7f9548b94a52242d087e7a31405de070a2540b020ef8ca613af4d3308286b41f19410b100b2cd3a2177293f2c9d2cf535091d9a19c2d8ee45d3088b6b9e62cf3536a7bec231a045e0d5bafc58821510201822b50e81dbb97f4dea0a150b90b8da9c0aa95d7100e95c9eccd1fee2fc680bcfec652fbfc958dae72b92dd02101562995ee811de22999ae43ae1351cb05221316f30fe2cff9a023417d9ca0c35db256c7c7f0776886630249d21b579bfcf87c7ed2d07467f14c15f1dcdb15aea3e7d4dba6f226ae0a7f9c41350ef09d74b6085ab61befa2f8db962de9bb6a02197e9cfc811880afe20ca7eecb74dd3248f5695d626ba69d14f10345b137f012b844fc4111bc38dbf2949a6c40876b48a762e638c6a892b852857544cb21be142568633bd630f26edb3c65a60479b361e8f4a40d939eaf054ff47df81e3ac411b6b24183d46cf41e01867243c46a5e1b4d4ee1240b5d71d0f8ac4a3c6013a1e526cda4cd24abcb3422a42a2ca654c05e25ae6a12e129008c18569647af579a0366b911a3c06f81cb9fac2eb317da2b60c4f8768c9a1163517bb818d170b0ba524ec26de84cb54518460c9e8a1ad8f465c440f4942e29238cf0289fbd6aefa38fed9e2af517062b2961690c99a62dd696dc55054cd4fb156d3ab44a63bdaf5cf2a59721e9bf85507cb45edbf6b7f8e2e66d8ad89e8bedc765a8f1c9e4e7f382b0b3e40c4c42ed291ff9dd6014836ac48d373b82a0157ec055942a9f05a968b3397309c6faed0211fc9cb5060b06863c31a5fc492e327140f66be7015b18906c6dffd2c5f00cee05051ebda68e8e0b830b071751544dbeb91b1083d1f76e04890eb3abd4c2aaff90a5bee4400d2126a486234c8b0353fa4e7b09a659ba7f360049d8354ada9c1ed6e73a64089e3a0da9cd821f54aaf6d800aa19d2b94a033e5de517956808f000c0459403ef1232d9291a9116342a2a1aae2ebb33a237b778dca4e6b86de7a3365f66c0305b5b56738d294e995dadabc7b2e8e8ab24b52e5f33f429ad6665b525529ef429cd40a771eed0ad15e67b7a76d076847025936259b71078c5df0c2dea7afb96df795dd6f9eed6bff83ad03bdf2dbfb2b4d477d5c04e05a241650b618fbfa900df0b0703facf6ea991c56c00e7a90032541bef39a9a6c41ce5dbb195c2db167284c84d497b18d342dc73b5216cf0e638d5caf2e057f625b57688050aab236566f61093562164de0c3565375f206b7555793e0b2109a2852f84af491898164641412c606d510d08663f9a57b7142796d12623f8c9d900120bb8241f3a78a8426d3d711bd41a29c013efc730ffc5023106f61f984fb05b25aecfbf24cd712738fa6e8358201eb4174cc1cd338723e5bee56e63df07fc4ddc38f05b6dd16056ebafca72e717578fd36d608ee2860687205ed9c834ac4e0019920752b0141874375972585ece9ac224ac8ddb43c4de8c20f5a20b69550df556a49034dc71bf99261abb1627d9a49da2d56e385a34cfc5dfcf67a0889856b6193ac3b9692c5f0991597801a3464a1a5bd435452f048215e09d69448e536941972e8bc54ed7967a4ecc5cd027bdce59ae449569827e17e9a2022ccaad20d253bd36e8bdebd6e3fdda48e0770e781e3a842f094ef98c8bc546b32b89d1664d02844c407847ad1feabd5b9fc8e7e432012ea879d3f7880980baca84cf8cd41e98802dcd21654828cd04b0330b45d8e9b71554835a035c88d9b35033d6c83c4c12d3a27e7357acdf61d1559235f292ea6df017ee44b407befad0460f2e9d035d3980b66b06857b18f5b3815297d2416be025fcb07740a25c714c6c08f4560f58644fc17cdce410bccf32737cb614db3495697a8c8897a34e2cc019af2a3ad72cf73d95b2e8f97f3b2e9695fe94ac83c038696a8b1cadd0d219a89a0b76a7165a7c40824c10199e8cdaca33923a9b91cbeb7533e53095778c9d2208444777ff222d3003b081e063cb84bcf2e2f0afad20a0d70f3eea0c97dff5214ece5537508e64aa78af8a2b49ea08105740716bfa5f810d1d31439ce6d22c6d5c713b0cdd8e581fb1f449c0f6edc852e415bfdb9c31fc1a72164d35efbde77c7c83ce6d3050c3ac5815f89d3c77e45b111adc6072d59613a290e2db6a2d57b529f3005e91360e58254e7d686e10f721ea2f3339b027213f20e27d906c209123dafe19b80f6c912171aaf984a37496ea002e09b2566428e7eb7a4f36697a825edf924d4b7fe4b958704815bf6686d82f4ca4aaaefcc973290968daa9b2237b2f69f8fb60d039a9529b1017d3674bee646d8c5db63bc60e71d913c0b572754eaad5cc998445e60e354f756bd6ceb3384e10aeabcf4926570bc2c7da820ce233ede0ec323027008aad1f52dfeac67cc9db0435bda334a707ea7f5082c1f1a0e94fa10cd1c9130108c2734293130835275de9ec05d4b1239f0ce8e10395998910e21d5cab1cae1c9562767aa28704bf694ee671354d5a23fbcbece6f3bdc17ce9144c4a076320339e70d312ff3948549b87b12df83f8822dc95581c85f7b130d47eeba681a5768223ad4d74888c06e0a5886fac7aa148cfd321490be46d13b9a90e906d01bcb1d22dbf3d6f2ff150d9259e4990a984bf9fb74890f710a90acc0b11b9890e27ab808677489e2137fc25b7167e371b7ebfe1a24d001de3a7956026cee21fda0b564b37dfc327dec46ea39c7fe4f337adb1ecae64e5c9378d307a84c61938dbc53c4723ccd6195ac48ceb118cd3cd06c0ae0556287b8e4ecb18b25f0f112af7a87dc439701f91431281cf02b2aa7252944aace06f2477cc47cd2dba95dc732b68b6074f3ac9891a131c4e6daff0023bc88fde1aaffc92c9ea4bdcf58638890788578f2a22cc84a937fe2adc86eaba072591b5473931d2f4fc8689ffbf6b786fe5747cf76f7779597e55b87cced3ed6d71347959560a6eb6c817bb0d388a7da16e97397b75d9695622aa631f1c6e97bea31ae45802a9586d042d6504f04c38796acf550d3a4d244712984973317d059f2f3e3692afbe03c9c08465492ad51b66798da7ac122382870dfea0785b7317278ce1582329e6af36e9d65e4543f0ca3a81f0675197859bf3c2730218b6dd06822782e6093952f966272c093b158eb382f4370b475dfcdcb64ef47ee6fcea1ac5fa35c298754d92eac26b35b30266c2b4bd80a41130ba00fac281c98e518c9abeb010da8b125c248ebda483bc36c58078c1132be99726174ee1688b6b7c4e1e9c59e7bc308be3201b2faa3732c91a3331bbf25482e4269175cd3d6f57c593bcc1eda406db19a8734d425a28783216afc3113e3c5645a1213cec49a8d8bd88a2f287d2718329cec4a11ce1344364e246672794b8a1be627035ccfb246782a818564624b21d55b0a0c5dc1f31ade0906072b18fa400c949c26ae71489962ec000136570bb1bdbc40d64b92d414722935ae64cc1f77dce193c543c82aee5aa00dab4125d5c0ab1723953f63b547365a203799d7984052c6afb579e07d8068198488639e19689164d373eb57abd8c7731536542bd755680b235e74d79b04610f14c984dce3ff994857b722a1de5fb0c6e44f3e81b1c853220345ed6ca59b71568165f80c92177a9ec4f7dfde3b32d3352fb8eae4f4a83e7a587c8d30064d79a5fc3d462450829ada7e7a2217dda5e53ee9013e03f406ca43c07296e94bac310e9a6d421c43318ba15a3226ea053bef26655af10005f630d952b00171f6180a60a5d819d7b2b31c2f2227081d3c20b4b4606770ce3ebbac88cc7245938702756694871a53aa788d6217d1bee5d2e1197206a66c1ac804774492025fd8d70b54d84f5a929271e5944019f98444d80f378d1bb2a5c365164c4323f2c34380ed275c1c3b773680dc64a5b238c00d0c9ef63e453e8b5aca1a661296768d1037b736e54cd7d1a492778c11b2595f013a997ab463d1c659a5d0824d4cc70727478686ab05e3ff25f2ccd547709f300fd2aaa69b564f442575bac7fb5157ea6ff13d81b3d4038f811b1b54630ea66c5e55d1581d38ba06f1990ada6b669528526ebc0530ba43d87fe7aaa158efde8d0668795d3704c8f1e122994594ec4b3774965100aec86d0cc06b54dd6b5274c15cc13089f9c3fd8022d1f07651862aa9deb3828015ae6e680ac361596ff9ed7ca1150f2b32a59eeb778a44dbe81de3f2930c80c7dcaf9bc260095fcf80cf4b8953f7ae4598270a1160d0f9b6773cb6f446e0724fedc02729af4c99934fc9594c90de8d4eaac98c7baacd432e28eb263e00a7fbd265beb2c900186184f77d2a54b324d64aac8eb795d30315eea8c6540c87bf75698e62aace6b4f8758c60579b2f8dd02d3c4a6692adb64782f58d7ae1a8254717a96059c34e3f32ea410a7915ac74c7bc38cbcb2083f7e8da2b8c9443244057c09af3ef69390f71fd680eec282a1e47229c11476111cb5416351236046c99cc5b63b91ecb402e0c4f2fa1381baab5d7c79412b531878606831eeac8d219a25e469446e7278ad940b203607a32f5658a370859aa50f662b066d3414ec7073af29ddb00994a48694c1a6de324d9d004a6c63b1ea9daaaad10c0e0c69a2d7a11346309cfcb3e2de8f5814f5cb4845cccd8f382091d541db4f444b3e867060403d2ccea8946ce4917899d42026b64483eb0893da4ee3b0604cafb8b99b17a6737788b9537b9466cb838d78c0017ae42a399742290fa89ea0559469810d7b3edb50c9ca7063c97ade5daef5303cdcd4e32156eb69d5edeeee85a2eb826e84de609cbc6fcfbabd57551c07ad30491f1ad3db56d55ac2ec1c6175402b7c5ab3bb19550c85b3c0a39d24dec439b39a96e5c3688dd544ca44b54285119e632b9bef741dc1ffcd06a346ca4044c20a64db825c9aee12d72148a0593bb422d34091b23826b2cba57f08d975be95683b08bc4237f372cfb41cc49c27a5c317f335b874dc7d00d4cd98a978a92f97f9421065411804bc788a4098f4f7fcc9c0d723a26ce6fd45e0bd3585324cdcfeba7cc0669f1a0abec7c926d371851e4eeda7a599cc6e2bb7427f6ac5575af6332c89b5a84ce1b1217bc1341ed0ba1231b67a9d695b67dc7c28ff0c48db38f1c4e38d8c21e9971afcd2aa0acf3d5975822ca31a52515ebc65944b335b40c0a26e7bd84b9d6bef0a5710ec71ace09ecea539d6a1329c38024b5c306a681cd5c4f77db75d108da68819996955a66b96b91a9ad78dccfb45832e177fa2985abc87de96d894a35b49a8d9fae18420e27f748ee92b2f6b889d2bd2bca02700972f9e0e738b9044599e47349dbea81680f1411945e752efe31d6eab14a94ad17255014d6eb343c19471756b308415390013409f434bc169a22234f9018d7b8e70fbe64540d34ad7919161f50238eb12635644f6e2b78733eacc637e011bdfc2f508c21c11d0f3f7186bdc7cb50d49d6ed9d9041faf64ea383b6690fd02a89c0bfbd6a46778a9d4b43411d79377975e75e5828e477b53d0adf76ae142bdbe875cff9d6372be6cb1dc1c93972c85baff80859df538326a5dd05707e15d5ab3d2898bdafa4718f6013238021b3c8d8e5f88848cf8028830465c4ab7ac478a47470d1f32aa1e1e43cc1ab442e2cc11d80e72b47008c5a9f422789659d7a15aa88207ad4a92af8aead2c2557148ed2b29cceb9924f2952cc1567e50abc7a8046ed9f187459e913359eb53ad192ef11c243331c75097cc03d0a98f2b57518e476f43189268e4c4d0fa2ebb5b2ed0bcf6687ac138230ac48b9584d33c2477e97c3b44e390b40b78004e859dcd136356a299800af8a111403110a3894ebbd2a3e1880f8acaaf3b591b5bc0088565f97d31223f3b31e1b50ea19db4d833f26b3dc3fa1779206472257c8fae5d470268823cc3f0d5f0ee03d4f45b39a00b33e1d4518bc7a15481cc68d178cb37c68cf25909c1a26930900d17774815f7395949ef78667332489ca6c1760944f6d086f78dbd4661573f1d87031b8dff85da2256203fd501a8c21ed4a5bae7fd266cf622be0b91d5a29d094e3832c5acdce0063a12d5ba275f642fee08a8d08bebeaad7a0409b160d9e7f9cc9901c03142836030a1e536b8fea0e01c0148b15e2638481ca7191580f381d0cd7326bed61b68d4ce1a4d842cfbf90b519e758494c168e276f0701dd2c1820640242c27aac5b049dfb48c77d5d9d909332831c4516fe91f5b9380671339e19bceac6d01616b896d954e860d74630826bba9a3b72603c625d5e237a1b2d0198a5d7261be9fcd320883c4e8ef3c242b7e4d5a2985487a10df796165ba71b86b2fa1bff69f94c9c31ec0e622a21a52846735734a0d0a8006e03cc729e1e188da41ec96dd12f48fb2ca28f34759126bec966a30e69a7850fa70885fc362d24e4c6cc1db9838a75cf7becdc2b9750b8f270dbff2b108fafb982a97746444233f08ffa636b697d3e1cd25aee2ae4036a76bab50f13e3089f5252c238f8015cb8ac8a598a347564dbc93c3c5144ff5fa1359538a9b71646b9f44d0213434189b02eae80b707ee5bc1ac8ceca93e9cb57cbc299070b2181c56ce129127ef509acfffd2cda80a9d01c9c4a0858304a3cdf69db565d3de6753f3dc1ff6ecb476d8df8879db12fb03c03ee9f2dfd79173952640524850e9225605c6a4ae6faae3a3ba22963eeea841723558c799bfb5dd4e869b602c3128336dd502738059b622fb8eff7c931fd052edcaad530ab8fd0fa875bc075a8a36b7ad0412b62a39dae38c2f1c30f1ec2408be565e14e0bc6ad28f588f8becd9569868cdeed1bf43f4e88efe0c6cec9a4fd5187a9c234aff7f14ca930edd697a4542abc98eb79cc96adc48dc52a9b3dacb029bd2f39bb56eda91185769e65a8c31d29c9ccec495d220c4ecc9ed91b2f7f2cf7075f465a65f956f6a94686d3f2fa89d0ebb7b8af4f82293550e72382b391cae5486d84e0aab8963fdc6a27731f3555c6c114434fb2bfc24deb3d9268adfd857ea4e20a5706bc4588d398138a7f1d8d15dc2e1fc447a38d1148cfd1496c7e630eef89604354b2aa9123b4e7d2ca39c2313c64cc31c443a7cd1b2d2dce2154775e91381e825d92bd4648b9050dc8efbb5db41f5f566095734d1e2313b2c0d5cae7707b1e5afd9ef2201313f07b1664a9c5d92817ddb38b9c27e18a86d3a811e75a816266d4f3088275ffba2b8f8a826adbe1400acccba29e95e1e9e52d02f482c8bf141cf3f63f0a4d1873fb916557a6ca4541d18f0814364586bd7bb95cfc9dad98e5522eabfa42fcf14198a8fbacfa1bd694eacd5299d49f4a06022e3cdca8bbbf6a6c1083eaaa82154a50a3a6500bfb712bf4446274883e0c32544cc144e24ab5d443a436d5f29a4c64a866cda2d8006705cac0abd5de6b3a0a15bbca7dfcc1d3297f2f62924a495e1e9bfe333584244a8a37774a687655ff4f56713e38b0acfbfecfa49151744e53c67efc7e144daaff158e67303015148f7f677ffbb5692514c1917546d7056045ae8a3762dd6ce3810e32d007235f1c29421e826b0de157db48252dd5ca3e93ca24ad591ded8567921c94eac8044add55c4949d3cafa638256a8f4e312b34029a59e6e6c78df2ac1db941eebecf58e9f7ea7e00267c7b2260a41ce7a2c9e8850728b54cdaea22166a95885522946504ee430443ce1821300207afeb9f672c832494791a2140620b58df4a5f415ca5ece53d0ac6fd5803ab3d031a7550e0312dd327c10fb1acb3be69342eedeea9d560858ae853fa9b70b53b536c821616a87d573ac863d49880627590c2117a99e77dc95e938b8dd0303c28dde48467680a8ede9f78f0f006cb614f088e591fe17cc0548680f941224a88988c0c430d1a260f069faeaaa08e75d23e573581bbaa478bef86a7dfff4480cb6006b15cf2e14dcf1286e733cd259691a3972f8314573fc3e594ae73b8e120705733cec71d113de3fa19942f4377524f0de0909670cf9e7598c5e05023cf7377749c75aab4ff8b89ed741eb728081691c32c780ad086fbb2f48f412f6e49cda7f62dd7cf7c480119bcf58b4550ec1ed2dd7b1c4122645f3c1e9b66849e007b0afe9890307614fcffe82fe4063f0817332366e8784f4c184e11cfe2200b1740e297fee6590c9cee67179d231b6989be742919274423c77c585cb4a8389916597decadec118e9c57a4bbf51ebb52e9b6ce0cd06727351727eff997888a3561f8cce15f38ff4f2f08db4d95419621a82f1130ea98a6c521f963d232892f465909b8e42453c593ad43c6aafdf512b3940d633998556ba453c5a74ca8865c9d4fbb85af5b9e9b53cf8d63ba912fa3b9dfc29955a20ebaacd8d96d8621dc1f7e31cfeef946bf769837fd3f48059b3d2798468f0b8fa073cd3d96d11abf2ce107d9be286d47e865163094f508d97d029d5b10ef6a43d2715e4b952b62d3284293766c8e3f1af77918d5062699d6e4b679f2a26cfe7b8d505046e7e57d3c3a72c391f68301d38ddaa75d9a85f5edacea98059e703be12b8dac45601bd98f01374216796516d3f1a436eb1032a5fe6b89e4a24d3ab54c9861ad805dae3e3d513a8ec263bbc12b608d3dfac2746346651b73010a9d7bd04ab5f4daeba5ff6fb66111bc442ecac0f8f8d66387b15647fd1d681ec9ae11b0c371c10b8cf0de1b46557b71c2065e6f18a90a110af8241542f5930d9a8610fe5e2f8420fc110395a472e6ea7e3505731023b260513076e12b3370dc35577086ea9593cf746599ee4288d84d16f5184b920ea0bdab10920c302acd98556bfd5699b25d6560ccab6a50351940cdc8be26951ef7e0169181226cac365d41c63e43479fba66357a0b75f53f867096d45f3c7d55064bae5c1899274c2beb58165cf7c7b302f470e7cd28bf56fd11730fb095f18c943d7f06b5e5cda4a638f043293619b80ddfe5509cf1b33d84f83ed215c58a7707add6f44e01915fc160c70dea33c364840cacee856b4773d21e2b2162c619da781430b466058b09ccf4302c750576e8f928b781c5648c636c406899a8bedae18523a5a0f0e776acd477c22ad0bd4eb61a62e2727bc525478363704766bd659512d6fcdf56befef25a04459d1fc1ce2ff69dbfd25230aaea0d4d4653bb33725c100e2fb8afe958cf0fd46b669694d0dca9b71401a4ff78fe774ce4e9b5ede7e8dfaf87aea0cb5dae12bc0c25b4f474c64557ff923e2344bce096a5594c3328f162a372ec36fde785ad978441da36c765d0418ad19d8a0bec11da5dffed2b5e49e9d228c8b498d688424dfa1414e8d0c2346456d0becb893f61880a85f40a1883fe738fc6f4c9cad1ebad509c51794df4ee20a5c7f1210357164ccb277b17126668c0d4cb224186523833029039871afc5d385840f952c260bf31e701fef00062249e38b085a51930a6b1f14ae442acd2d81869e9e450d321624088b9a79ed76b06c269e9e7b7717cae35c8a00bd5a001b84b8b219e0ac284e5cfd28755f82e15beaa652670adc06ba5a887a78f6907d6c60325fe7c3fa37328f30f59e5aa508dbf1e1028096e42d7d5c06249697abccf11df4030559d9ebde31b5ba305ccaf018cee3166b1699eb7597860be93ba894aec154c8b4334d1e8ce5cc13443bc07c8da3bb18419e02bb407b69385988efb7f68501f7d53ddc06a270af7641d1341869acc557ea930c5973bba35bb7d3ff42a7d04f98e558b93befa85f260aa47da0be55b04571118ad11f62be8a4cd23487058234240a7b39e3743c13c645724709269ddcd6ed7fa70a2d99f23a6796e932cfc2184fdae0c40f2bde038bb4b85d35793fd60d252f9f84b35a335e48449c03dfcb65493099591c927cf7593e6a0942a7b198e83135436cb7780b21da59ffc13c4f9153243fa3efc529de54f4496a0c83511f5b2690eb72ad50daa5e760d9cb08797872d62e9dd4bb89aaf76eda23b1aba6d4a6792508e2dfa297b2150f5ec46bc83115180f280efb486f6fd2fc91d78b2701007f26b561005adfded35db004fbdf00f5f75529fcde84035da28eb9bfddd1ea9a63b5fe70e26f184209f35bfe0247e7f0298b2ff015f1e8b1517f10933d8f1bd7bdc21e7cd553bafe50028dae9972da10d698c384292111d07e145d8c94b5fa274fbee88fd4093ae71fcecde93a665a58b0f9de22450c7da23c2f86ba87fb89213f504e9d5ab3479221d7075afe958f4ce09113cf9fbc8e842623d9ca2341b474f4196b16745b5fedd4e61598ecac5052144c79900f6a83979e9bf7ac39a21e9f7c5475941555d38b0c9058cfe77ce8c9a79696c4dc6cc520ea95ef910d9ee9c9a134a9c4a9910e3f2fed81e89c24375d1616c6969b0e4a8f01e4a93bf51536e1152906dd885f8f0dba08bce6ad8d0d99507dbda6a5575d004c735fcd88409c6310903b153051fcf5423e402313ff9038ba29298cd1d0647dc845c7bab201d9d84cf9ee824632f4dd3b762f94661718f4cb1bbe5142fe9dccc1a5731cbe89f6aad34fbf49af15331c843e92b48b41c33d708a3fbe3c15e317143251173746d3f3654d224ea7ad8135b0d6d5f336eb3ebc2633b5c8045d1eb541ad9cb5efd434c172982ddc0237d11722035bd4973f75a64a41fea3ad4c5e2ad9c4524fb831c82908054058e4a3a89649e12f543cf2ae504b9a5385454f4be4c61e09676094e68c3fb07fe9a21e87a659d9ce0bba40f25e1f9d19940fe33a89f875b752bfd4931401ec0ddde8794f2006a65999ff4c49dc705f221a12904130ebad48313858ca25a3e1dc74e888e302d0d7f33751cee30eb658cbf996fcb910a1e0e619524566c260a1647e29fe5051b14eae5fbec503e29cc3b25229595fa51bc6bc256470b7a570e9600e74233ce60ed916ff960061cf9c6cf8bf14b8f44884000624191e53edddb140eb8b9de2157f3e95605be2b827ac78adeafe65e37ede96e08e341aced2aaa5a0aceab8fc1fa4bd162549050114023e21e4f737314c42ded7e1a4f9135df74bff4aba8c8496553517fc8d1a32e28798e6131005c4a72653ae59da3e0f69d0011673750bc0edbfe95f4d2d1df8eecd669c0cbc436c832393f60a6615148d08daa50fdd84b8b1e28513614e52f46f23b7a063e17927a9abdfa91822e48c500f7006ed7deded32836c66b1fa27bad5d82d1b55bc07be1cbaefb2c3c70bf85d84dad49daa02636908eaaa2d1dc1a5cc4eb5c970ec50ff65282bf421cc42b3d1656c8e35e7e6900721e1d3a1fa070c2c5de14725d68e0159109d2dcaaff87145a35a457a7ed8f68c8fe2fbbcf014f4505fb092a06bc40043a7b17b45920161ea703dade5c930dab3caa208034575b58e5c0ea3c12c5471fc25cdad501c4163777e71faf2f103369948181f1479079d286879b16e2cfd9b2a346ea214fe97b902bbcb55d72a10eaae515f84948dbbbadc92da59449ca650bb60a090b48ac961f29d5482cc581775744d93d29336e79e782123c297d65f72519fd7b8297ce29c11c99434e29a5ccd7b1f639a7f628476ac080be4729959a6a2a8380ed755ddfbe4b0b7a32873462638c73e65ddde7758b6bbe1f69cc3ad4689a91543df324ea62ce5e0dc9f0f1e3cf234a4ee00d4768b4f7476880783340b0a7bc9774f8c811edbdf44ee00da7d7fe9dbaaecb9bbea537dc975a05504e4f87f4fdeb021130e88b80337c1777e078658b8d8707d5bd8e0ee4e93c9cf203e2fe33bcfc12203fe8e59780f81222f240ae1a61b0d45d647d6f7aef650095c8f0400f03a8048627f25d4532b597f792d4a772f4d0c5c35be1e3f7a347d4dc9197b929fa24c3f53dda14903bf2f0bbe7983c8cdfa2a4530fe3b980c2054dd7f47dd4169aee9e7e67eba7fe047a7fbf184cbabe079ebe8239c82fb4f7f7fb91caf609d02f7911016f80887c0d5f43566289fc925727bd99216c338b3d3b7ab218305d354c97033a1eab3475e62725ad92564a33b4c00e786ee6ad8ee7617c7d25f2e5807c93faf872c0a71c00e218dc948e4b346108185fffbf3b7d3ffe57df0b4b0e903f25f5fd84a41e46be1192ba21e7d40be18aa0713c5c0181374040ef8077405602f44b5e37556c54f56f60a49e83e9f913060ca15993e978a586345d31690683c17a981dcdaa58e1b8b80bcfe7bab884286d057a3e8c54e56042de9a457abe4f97a5d1be201abe868f1f9122cf80971f11057c0d2f3f22097806648b3475e6d700da9a9c52ca5851bb066d691e4eef6b64813340e469f80c007d91df76a6a795c9acec862d919e2e09aba0253afd0d91a0a08f2f1ac01b2233ccf0f1d5993c229f7de2e19cdc1453189c63893e4b64f22c91b7e2151638d1d13fcf7b208f72a27298babe25f2d60a3fdf3ef196f793eb0172310fe7af00dee0971abf07dee0cc31d1a9cc0d79c81d650edc01e6e99e469697a9767da7ea3c8f08185f40607c79d1431ed9d5bd92213f435652e4813e0824f24a6278225909918735e03340e42510086bc067a0c8c757033e7e31dca708f02ff241a0fc1940f944fec73b9887dc6ceaccafa0e4625ccce7f74af7f8fcc6bac7e7f7d53d3edf339b2ea09fcf795134cecbf814689d7838bb542a05f41e68873c9c40f967ea8cc1cecd620f37c55b5db6578787ddd3ff62389dbe7e4031fc0d1129c3cb8faffa010df91b22fe400f943300c3fbc7d7cf2402c6571130be7cc06dc201b78ede91b6757a401de47c1b7073373dbf01a012f942c077d693018c2f183afa3220cd5b6a990e87fbf467fef41d12d7323ff3f503725fe64d3324ae697e73b32e459e047581e10910171aee0c5cbcb59d786b63a9d1c02515199a21e86bdeda5164f8d3cfbf49deda516018f2d68e42e44fd9846422f2d68e02f4349b6a33177e3e3725c8cfe78e5af8f95c15203f9fa3f5f8f99c151e3f9f43f2f9f91c969e9fcfd9bcb569bc007e3ef7c55b7bc607e04db62bbcb573bce98b874a3f7e3e27c6c7cfe7960af0f3b9312fd80a423c3117132cf3d6663d0e28deda00f81b452a48d199e9293f472c5421c0cfbfb4d4cfbf56503fff2261f97efeb5796bdbf8f0e7df2fdeda287c0a3fffde727efebde2849f7f95bcb561fcfc2bc65bbbe6e7df256f6d9b9f7fc7786baf7ebe09e6addd7a216fed1d1ff3d6d6f1a7df037899b736f8f9e79ba0786bd7f8fff9a6226fed9bc7f9f926293294d0d7027ebe69460346ed03f688c0bbc0151ef0154c39e04fa00726e118f03c205084c7e728b2c20ba0888f0b45821449f1c83bc8fb1411c0bb8ae470a52050e47bc0249c8e12011e34acf001a06167a785bc7d9e07909e1d1a68d87921239fbc03c005c91bc8efe424570079efdc220208c0fb14f1f1c9bbc7f714f1e979212320794729f202a061a7c8e7a06127c70b1905206f9f6fa147de3d0fe47940608577d14083ab070d3c68e8c1e3858c56c83b0a0def82000d101040007abc0b3c3ec8f31809d2d93c8f2327b94142463f827c04a55608e2c9fbc7bb82747ca48c043d7e00cf0c59c868e3f81b39c9dda9574105d7f3bcf08d572127c17886173272e58de3714ec2b970015e859ce4fec85b851bf4c2f1ae20972bef151e07b9f00b191520ef2841ff9a4185a0d7c949b00a3aae2fc00a79e37f3d8f911b79e3781f33a4bee62477861b75851978665881e7858c70e41d6586bf612435c35723386eacf03f78dec7a3c0130c59c8681b609ff0614e72610853de4fde27540d6138216f219fca49300c27fca01a5061f8cf012819b290d1d63b851bb290777d544ee211e00b73129c42dee1e7a4f04246356f027006f89c9ce40ac93be7c2c00201bec2506bdea9ff60a8df0b191920ef28303c0b32e4c0f027e42438e78417322240def55948e5fd3d011ed500fd3f328457869f940c2819f206848c52aff38e22c3ff342094e153684098c20b197979a75e9ff246fdcf7720908c9ce45e03e4ee04e49d5ec83909063a8001bc9c84cbdbfb13d0533009a7a330a02392858cb690fd8f9393dc8cd3ad28cd9f777e9c9c0413f953de36dc0bbf0292f107c8492ed0cae64f40270a74a22f64f442de51801e87880ca05f11c191f1424636799fde86feeabb9c84d3516aa81172751422f91231008caf21e2e55df35d4ee2e9bc012123af7b212321794721f206a821e71d25f62f22ff3909d65162ffaa216f40c828ff0b19c1c8db7b183579775ff33fd385202d00f9e1a3002ffdc302017a80ade701ee781f50c7f78003f815c0d6f3803bde05ea780c0ee05360eb51e08eafa08effc001bc07b6b2e9c877645315d7914db4016493150fe7d30012f92220d0cf0012f92010e865008964130c2050360900a4f1010067fc0e78f339c0188f0364dd0001205f0b7815c09bd701637c08da480144c15f0bf81cf0e64f00637c06c1076b2899af056421231ad994e4e19c914d5d3c9c8f03dee428402f038c01944d34e049af400b450f3d10a36d40ee4a2c8a18e81a7012d1035212340cd07a4009a52a68b269e6e19c01758051f3a8c01c6417bad2d3ffcc13fdcf7bfa1b077dfa8d449fe86f1ef454728af423fd9f4973e061490e9cbebfa3a37e3fbeef3efa81a78f1f3ddd9ff4febc6f9a79cb74e4a62a269ab7ba9f6fb2e22defe79b90bcf5fd7c13166fa17ebea9e6add4cf377131254d577c995208023b4c470e89c8cd2072546238df03391cc470fe07724ac4703e0ae47410c3f929906b2286f331c86589e1d4a8a8bda84f3748ec58b3df0f4a630b1c03c81d4d9df943408e3675e62300e4120072495367be0240ce04724a1e4e058014067b25008c3da0116108f64200e8af21607cc5005a2164dc6c05b425b8c0a277ace5209c85018caf9b24f053d194e6cdd1f4e466aba19a2542fc870ee8ac31c58e5ae74ba0ce0f72179e3ad5723ff3dbe032a28b3345959f4bffdc3f6aefebe350213b2a6947e9471c2cb00d4770f09644d55772233fde2f2790cb2d8b9422a5147de0cff7eedd2af08e371aade81d6f11490c511533a42bce8d90aae9d36c43162efae68aaf1a75a0af64a1691f1daf608969e964097c5f7ef46d7882710d7928c81359f33950c74e8d2160fab71d3945177378b6e839bdf90501a3e641a578782bf5f5795020ea6b7e0f27ea47bd38d7a779efe8ca13eaba6479721e01a6bfe551ade22d5af7b276d37dd427eeab8988b3f707499b88b49469cd81fe444b5b13ba98b36dd8862c91f4b17905c325883d32af7ce8e485692939a2a1e932714329a23be75c1de9398b7ed040c2fb72b414d174991edfa26d97e6af60447abecbd836313ddf04a290c26dba50b88529dca60e54a770ab11055ec1a44f0a44c41c3ec49e148a88ac990251647d81370a36223d1f05db9cf351b055146c19059b9e4f4349cfbf1dd021882e2feac4a48fbc1e887788d8a231627ad21451230acc0d491d97d01c4d7a52f6b261338540de21dda5e86f59bf1fd49fc26055531d3f17240ca6a5949a060fb07f472308ec9f9282fdbf23a4d09d364c1d79877c48cbf0b41775256ad61ba88797e3683c810253a653e77d1cc6a9cfeb4ea66b27e7c472d35f3eadf4c2f39bc42db0ee267ed41ec11b59f3b310b03f0b5dcc97684f716fb3065e36c7bc17f731fa12b20fc4a540f4c565207e48bdecfbebc77f1c08b81d48eac7109b630c40a429e8ac4c925da4cd61718a91637414d2f1e597e8badc0d3d73387ce7d3388458e79e6a8a71f9ba730c15a2bf29136de715d4f30df7516f1a0fe5c7d56ad28afdcd63ce9afca60dcf7c33bfd85397a7cd4324f07dd38e450277ba9b4969495b195dd3364d9c376b33c943fb3bd3356d53c7fedc993bda72de2afc8be9f53a2ede4e7e3f6e6451539e5c3cb41fa96afebeb63d6bdafe9d964ed3db9f53c41e2a62ebf4f66711a22bf53665737fd7d07bdab8a813c3fb2630c61cf75136fafe074eb03b7917bc6f3299a67ff3de9f13c8cfcc55daf7a48b3975af70dc15a34ebca1e99b40aa632926c97c7333eea8bbbbd441bffe5ed955b43991748b82ea18ee9f107ac394d2e84808483a5ee98205eb78a54b151d7310f08e341f41bc15eef0581bef8dad830718a987f5c10f8c1df8a79d534da6f72f7a68b219eb6e89e69ebbb1938bff25d2fbca22affe8fe370896e8d28f091f6291efa9d4596134d1da79836d3f355b3aa68eab814785f99f6bf45de924dd7af6a4d08e381d48cde9c936621a58b2fdfbf2ea1a5bb9d4a689f481c6184769f4db4ff0c22ba38dad4a1c51e9a775d22424184967e2f5e3dc7d1fc6849cbefa44429018546d150f1080648141d2133828888a1d92496bad190d9595141441df912d5444b8922424b8932424b893a424b8942424b893ad25276302ddf332a16a3071d6c52bac4318de3d2c58f3978bab0c0f1658cb106131c5f2eb14aba4bcd8f39fbb36969b1f889c51338be8c9f0d05483ae51f7376078b3fb3236930524b5acc271a4f707c0aca197364bea183c53a2aa4de509f52d0fe4d90a8fd5b88da3392aa2d20f58f3bf52f418d28e89e4c24142ddf653ee7d064329f4c22af819b87a4d57297de3b5d5ecb9970882cf9525e53274fdd2751274f82deccfb644f52cee371d11244dd50295b2a49cbc75cb4045536192a23335dca5c94d7224bbe3ff1293e73993f71295ee4445fc62c8951ba7d11a3649352acd0aa1c5129b28a2c8a62241319a362a49084e120b968f95ef339695a6ab9231624cd23dde7a4b45a6aed7469a5cf4969ad96566b39ee5ed3899e4ed3e589bb26d3e9d475de47bf6fbafc4e9de77d1f0a95f21ce5792ae539c69eab5c6686cecc4c973352821b47c2a8b159d1d56aba5c51ede0cc6a86c641181eca9a68436d6ca64b1bf911c6957fe38a2e7a473bf79752ce791d3cb2c3634a5902267600456f8914993801144d99d8c1134d23ed52dec6d9c134ba0be9d7043ee2e9f92e67538e6e3c0ba938b897fd6df3a88b3a52313afedc41ccd92898d61cac5382d434b8e0a983e460fb53d2fe2d796bdf4f69ba244c8c961f51302578f1220c175ebc08c3c58ef4f856c95d98025eec711d634f9cf9b312ae0cc226ffab95b4daa4bdcd64a466adb5d6c6888a6249312643b527322edf084cdebd31c9248665ad46abf9e007bc658cc9900f624fc4f1d0c8bd3619b34d9181cd5454bbb1da68239843b43ef087772a107697129f5d0fe3daeb21423c08b124613c2839f980a9a4b98f255f1338890de02d93d01e7512426ed19079ef522d2ef994a59a10fd18e3f3f0560df2c210a0bfe177959414a4437b1378c7251ff13b49a3a3e94da68f43e02d898e863cac35cb9814c9647e22e8a44c3e01138bf968027320b2fc4121bc25918903341e629c13c3fb415f4c0af5dd41babe0f49f4e52edf1a748d912ad1f565355349e4e193d5ea93271ed0af369c3a2b1d3b754eeac97c3163f464be701a4fc790f6e30e6f84d8d0f3a5de417a66eadc14dd9573d22e866395465ae39cd663bcb5c6396f1096e3601666390e666196e3601666ade52abda65367392eea7cd7992a0ea41b099a94d66a6dadd65aae7a4b5ed3a9abd61a515db87a4c17736ced3cd3902011c948504cf23ed4dd09c31f9e9477fae257296c5328acaaa84f25e3d1cf7318de0ac4c106ceebbe68922bd05763aa776c40987094f3f19c1fa871a1fdbbfbbed384215c1236e79c73827a7b370aed51c0b4634d2304357ae32449a104144fa030020d8c110d6e908498142835810b2cddcc13d80a217b49973f64ca0fa230d86b877a7fd208b4fb73df0f97594112f4fce9822792c8a1e7d7e9d6dddd2bc0822b2fd0820b990bba78f916da5d46bbbbbb3b179c25f4bee1c4144f70a102042180404510a804a18b1f33c3404a279ee8b9033ddfef8db40f8f58e085091814010459ea129539e7cc97eac904146e448de9eccf0c6128a2eba7ca7e8c58682bc1e82dbdc823eee7db9748b2765fcea68e7df958ce66b31948bf0365b6c143fb9552f0cbd196256cead8e7401b3c9ca0fd2ed415b461ead89760a82968ebc01975ec9fc02eead8bfe04c64d997e0099a0355d013d4416a8f2cfb51e6a0711d6b4408b83eb5c59ea8ed90b5d65a6b7fc874fd58fb3f44e02d677266397b5fd2bcb5a38de3621625fb7276e4ad7865cb51152bb28bb7a296d67eb4976bd1db01a60ff0941932f5d0713b2b2c04bc53ab9529f6482d773ccc7b45b3c36520729c3de2b9e78a57393b3ba9d823dfcad944ad3105a63f65fec755d676d1aa52aa54ea2db869742af52a3087c97d2a253393041cca30f7b63eb8038c7aee7140626b55b47d99efa4977aeaa18c1070ea55ffe33279cf3c8e4c0ca6b7dfd50883a3e6b0a6799af7cf85fb34cf4df1fde8febbe11dea6a92d1e61bfb51df00d1527bff334f376afb38f6449af0b560dbc22abf87f57b95eafdfba1b237d857c97c9192f914f72da472f49026ef997c8446cbe46ddf02924be1547e94fcbaef4c51ca89fa2c07c61378635f02fa8237360f09a74e7d231ed67f7f25bcf216f795070ec4c1c395cd48722a962715588981923e41472b3180e939e3048d19bce969bea17b996fe8de4a142aef234874f7d4d27bc37d7bc12da5ccb133a0e9479963634087c8da266d7a8febbe4b7d37a90cc4750aff8f7f385d32665b11b9de9f07cdfba7c017c0d03cb4c28a875620b9e054fad0976fa58ff4895a5e8d317e9c31780202c638a7febbc17f3f6a0adee00c84bef4a1d405994be3610c323987f9c517b8f3f7ef07c6f1c7555b308a7e3f66be97016f645e6a999f016f64543360bc91c9f4aa4026307dd46f7c00faf77b3005de38e544450f91c0a8fc81269943e6901208f712ec4c4f037ae03ee269fb2630ea1923e01f8fa77c539fcacb49ee6f5c789b6fea04b146197531a79b318a952e1c0eacb7a93367b57938ff938077ad5117f737f28580f8d7f51d4ec11ba9e90e77d958a3eef257ac148982a14a5db4a7f775bd549a453801858e34279cd0266df5f6a4acb163f806a2ab46d4f1bf117ba4cb04da738840fb471b4268ff4eba87f2679c982fa5874e71e0016402d79ff7e5bb046d30029ec2e0fbf26f0ef25c36e2c343797fcb6c83043c742b7d2ec84264f957507ea0144b74dd2c68bf42a8cc21bf662b73c82d302d001dafd4b0e84e7a401eca9739c8431c3cf43fe27160908757b0e5fec77140227de246a22bbd1ff5ed32d5435c023738c01f97d8a28b95527e9e875e258d8f9fef00f28378e8ff82b7a4cc0c6fc1fbb2d061c1fb6aff8955352f5669b5b5d66a69131aeef07094ba3b97e3d7ff717f4963a53426e5cf5af30d6e3dc67887b46b91dd9352520f5f4b4adf561049d552e61bf1e4800c9046e6907fab7fcd31b2b087d2dd6bb5ab9bb7ad52c695a1dd0b4ae97cafd55a6b39ce729cb55907aa6b4652290e28a561e4b120bdabd5bdf26d947f2fb803f3e713f39bb0d5e7dfd9c2094791357d359b4114592770c136b5d8137f7644ae2317ff84a3d8c3fdfc13a8c416b65111c3f9fb842a3a8347b1c7f453e636c304dec7366f82a9889d133d7f9ea8989ac8dcbc80d113a4a2a2814791b5656e7ae69c1760d3a5a7233dff9a86f47cae1b92d151f57f9c8b40269db1e4e1a4019b314655c3f2f78c257d02cd544b75afe5b5e07d5f4bc4f13052fa524a29a53c126a19a3c3322dd35ecb6b999e17a78ee73af2dda9c5a1a7b5536a5b6968f6aeb7583a3e0e11f00dcf5db7a5828ede8d4caab526d0be29478fa73058befb9f723ce1138e3af34f799ffc4f6f8a316e2be71024b04c877054d273ce215886d0426f8c615ebaf95d12b07cf7ebde3141ba4f9f81e1fa32b630439980e5bbd4b1e632ce9c2567c95972969c2567c95972969c2567c9597216215af75af0ae4b7ae77f804dd229ead4d17477a57dcedb75efa74fcaa794524a69a87a224fe0563dd1f75d45a4af130f2fc7494e35e42106a3ef002686bcc011b1956117288a3da7fc43dc424aee00fdec850947e8fb498a624ff7377a6608bc91af7a7f0cdec81ca53e7e2b4db127f5d2823e0efad0a6ce0f642b9d0059028a628fe9b7608ae386557f7f07a51d9690c090c49004117b5236fabecf91efb82fc11d95a0694bd3f7dd47d33492f4994d9dfb3560d867a6efde9144a38a1d88d8af8ff30eb6a973ffc777b84d9d8be3a69a02fbdb58c2f1c3c65264dd8f21056f1b4836906cd46c6cb191640309dfb40d255745b1c3fd1dbeecb08ae92b06a655433a86293813023c3384b14def48052c02b21dde0778ab6254c0b4f757f0c6390297c02dc0311a3442df076b6ca1efd7588a3ddd6798e71f7207b2179d13d3f7bb0e9c356062587385bedf84068c8d5b64ddf9b96628b22ecd2db2ae16bc55425462ab46c943a19a2ca4504d137ef062b3c5072afabef461b5e4c5120f359f1b28a33dfbfc8de3f6fd15dc028d4525c6cdaaa2bbaf51f296cfcc6bb2882d1f19c4f0fec539aa69c2b6d9b27d564b20ea3bf0f43e33c7c24706b115e3fd4b84cf4d69a9097d7fe31ce9c8841d60125964f9c4716be1342f50e42dd4d7a0386ede42fd0e5f76f82c78fbdcf4fd596d14bd71dc96b6f050e3818c5649816d2c797871dc70e0b8c2c3fb587028e1b8dda7a08d3121cc431b4bd2c77bf87c6e1d8d93b1c54cdfefaf60c93a8aac5b89d0f7f70d2ffafe0d5bec49fd9d58e8fb1b0054f47d0074d5a35f0b339a7a2615f4fd1400ae2a365531554cdf671dc59ef9f76d2cddf4b69179c87901a6ddf47d49b4a3efa3c0aba4ef7f3afa7e67f26686b0fcc1167b68f4cde2a6efef609b2eff1b3f2fe70044ea530e23eb3e901bbacb51f0af5637c7a973daa108ef70dbe18baa86fd77ccc226856835ba986929bd566b95b58a71528e5f34a5f46bb16786a6364cd76a45e321fd20481e048ba67fdf1f63b9d9553f98ae8bb1cc7b8571cd43ea09a103d5334c8037aee1a4a48d933c0c29ae555a0d27057921a70718851bdeb8866bde722e49deb279cbefd3c75ff0cd5bf7e9cd7d2c062fe131de128aa998a89c78ebc6e99b404f079bf3a501679c12a874312fb454f21a9409cceb039ebfa97697d3659df4e7811746fa865a3aa52e43bb721b43a7cb62a06b60acc8a7d3650a5abefc14a48fd4f2bd1c1a9c30d4d2655402829b1309bd14df883b38d473243c0c81f79551025b91e4e1e0aeac38ba18e7723b37be1b1e4e296394524a997f3c9453c689e280807758e303344cfd98b3699450422898e6649c114453470715f3d6e686ae58417431ce45c12ebd760905bb435c0c051b42c13814cc699478d05d185c0cef8fa828f6ccc74a96bc3627032f2895c4a97323f30d02eed707fa9a9518c90c54df9495c4a9b379942c792d797d9e019e996689660b2ed3284d1d7f9b696e53c73fbe687c81f7b74473a31f514b1ccc5b32e66f29c6f8920bde5c4cbb8e344adfd2b714594fe04d73d3fe344a34b7ebc1a84ba6db2d55d8b4ff8d904e61047bc597d17ca7607cc9c84f153e86047da9d11c76a38bbe94b28214a4c104de11a83efd9a8d4c1d0f52ff2b8f28b407bc23cf101e1e30f7f9c53d5acd6645532ec6b933cf09bc2fd1d7b0c4dec561eaf8e719608c95882cc74733d92cc281c61758e6192ef290084681a8e91ace53309e614cf3d6c648f8e8c85bf14a154cb43fae82573978be5e0478df9a16b5ca195130a54b737a94081b8e78e8dd116fc523ee4756b127c6279274b5e3cd862edaffc8873a3ede62f56afc78a314071b96e8848ce203f12fcf870706a1cd6001ee3ed2768c803678e8eff88b15ed4ff3132472a1e1783a81463cfcc9394b20da72c2a0afc18dc4d5ea79766c958e16e61b342bdfe12f2304dee18c225c5ff2c4cdb3c3f08b14ed7f03af7230c53431a4318c7fe37b4a340dbdd84494513a4dfa029343f4f38e79e786dc893d3494282998662989b2740216e19d1343f9f242c1f752a1a4fdbd48937282363881f7bdf7e67fd82c827b20fec5fdd3602434c68875ecf0c79101de3b3c18d41175c8ac43d548a8c61e7af6ae172a26903244f34642738080a3de6f639d32dc99d3bd15c3f0a948d2fe393b3c5676703e87eec4bda37756384450512547cf7bf17c9a6f78b833f34e98d38a51871ff18653f54211760846fd543addf1d01f87fb7ec40f6f707b15a5e72fc250f1042f794b49c95bf10a1542da1f8bb9184729262575c182242badf562d1fe74ce5bdbe1dfadba4829f0be47d716592e5559e08d95eed17499fe7258e9953012d17499326e22eaf8df2d978302672f44fa1e5d936cea800053d54d8117f9d1adc8ab7811de7ee4485e5454e452bc15b3947915a741a13b5a80b717c9f44c823e73295fe4a11b4596cb987d965d8a174929f2590d21f0964313ca9442246545f90a86cd59e5d20919d539637cfa133c72876eecf80eff1a47c072eeac3c741c1945e0ffd51435ed8f13eef00857a1f6d0c31bde8aff9b07cd8dad437f20c0bfc2343520947098628af64762441f889c2d61a19d72ce29e791ca7196b39ce5ac4b8964877f0c186015161846a7e828ed42711dffee07589504bcafcc14bb323c9b4df124de2957365bd160fa322df395619a4d78a6fdf1d17b98468bb85308697f6ff53772a88e3fce0cefeb3018ec033b56f04dfbd3041989572461a3f8b2f9f16b88a0916824c848947ace1a3a8c056d02632f58b43f4d90912fd188efa8284882026f6c3db0a507484069ff1a06eb40750d1ff08e4952e637a994c4454a29a59432ce8f35d248238d514609c62cbb200127cd57ba5584a2d2a5524ab9692898c3a4b2cb97a41511f09636a97469d0c957cc515d81af8c6d8f790c8af677265e849ec42a171ed3b907d8df612f3f3e97d59031da1fe8621c9af940fc8bfece0dedef17c843dfe13f9384f7b54911e02bb43fcd873a6e18c907e25ff6630e458900dfcc8f7ad5c511e65502a6c9aed86ada1fdb86b43f0e11946884246cbe106caff8c9b7e22d9b46e27a076a52bd703bf2b07b010c6d76d2732b453aea17ac40f2565482c6e40530346fcdac9b74d9e693d655c5b586997386e1e25e4a04d3a9f3be55778a1f365d89a2b52a815e6ea6a8a452096330fe5cb024d8a1d227ad31e2aca24b1a8c696b462cbdccf834b6642bbe977a6fa4d3638cd2548788f3eb5fb0ebbad3add56028ac0191ebe7ebe0f9f17e7e7772d3dbe7aafd5aa01f9053e49c9391e3388a517b090662daf3bef30385660d8af640a12aab8112dab4a48f74344d9f036f8450ef46aa4457bf934460a2ef9ba877027747fb5b93728ec0bb938da9b2d863ea96624fad9dc7454cea38ef44a30478d6ef64babefd5ae880a0a0bdbc4f1fe917c37dd3ae473ada8d90fbf66fe6b2876d4ce862f5d79c2efbf5816ca671eae47da4a369776f3284319afeac2fdf5986ce810051c620033c3caa9918b6c80ed9216b9db8ebbe5b28daf4db3e29ba5316d921d55463736d56dfc4725603326aad95968bb44f3b34e49d965c039e8c58bbb926131e32675784ce662583c6fc1bbea9e1d35ab7434eb0acdd8c39694c2a8b6ac8a2e9b24eec10182c9b6049a3d2ae665d2607de4d01de94c873cafd0dabbc558cd1a1be18583533e32463b1d80b419066706860d87837b4c72b3c5e71c475dc848394b9645226635861258764330367c6e5b444492d248d1a96078f87325a7c95138caa4f0806747d0be6170283c1dbde7eb5da096f94fea02823cca071c2a4c1b8c609effd0d83d45bb24af94358fa5767b6312d5e0ba69f235d1c120a5e941f87345d9293322b28e590f0f7834a1992f66c6391e55ae09c0ef5e13491d156aed27852a9e4e6914b7902258a85f6da8d95960dc452b001188c1be0d56d08b8e607307820948323304a472c42b39a2d4245d8c800c7d0118b900f4d609a24f4806d9e9802c358c2083360c10f4530bac0363a62f9a1090e7ee881468c1256487846472c3e74c9e203969a257c75c4e283959a2ef8d3118b0f35a8b9826574c4e2c30a6a3a80a98e587c304209d8a423161f885ef061a8079965a28b39bb46a6fde7fc628c5f6afd52bb68afd37afd0ff56a621d25920bf265ca9dccf623cabc6b97167074f47056a2a983e41d11d2ac483574273b02cbdf1ad05a70d5d2a345bbac49d8d683f6c730a70285f64719115d32b6894a42c962cf9c19a2166b6206a6c412152524b4cbdc5047cc1075b4c88a45967f3c1ac288a5197a93d132b6e99a41c84c2264a61132f3889989c4cc3c9a9999312e5e1feeeb9cb63b79af05d77f2d58d69f9f9009ea80e46a8f2cfa5c06f49491459fde4c6d470484469f66325560f9bbd2620ff754747d7a9b215242d79f414417b64d1dda8cc51efa7588a318122ad8114b4628e95a6fbad64c840415ed4168ff3d43648a814380590222a36d3ecd68c838198d6dd375a1b87951028397b080cdbb454c3b6c08ed158908884ad37c6b317a8064bf1ff43b4e72b34606c3a88b39fba76b5a41bc0a73afa52fa7cb682bd552092b69efbd99c220411755c060d245106a88baf041177358f99cb00b2a7aff0e937957f86f47a3d24d4a29a5d1892d38200269c3ea8289f6af31c1182e9ad287f1fda0b3eec42e7a60422604172518c190f5e10741f0e008345002cb14db03960f5421025a6f5abe9635165934df8f5a63b16746cbf7d8e37afecc1c43a4fd65660c2e70410aba5c51822070e045615e180d514ae9f763dae0028b1f3e67dcd2831048e032032a5a48f1a25f04d18281ab782be2ed9da0374d138880a20961b43fe6428bf64fadde97dc800a23149144152650f4a07a310403272814ba6c21660bbcd3e403dd69e9094a6b0f0c180193248460c2080b9857ed42d3ff10cb105a0cd5e84337a0a7a6dee2b15484f61e3c87a65b68d194fe104a299dd207494cc0b20431da9fbe80c68da3050d646851d37409465a4686e5afae686184a693624a25a594524a29a5937a2b8bf9e2c68e6643035cd3a4ce1001860c33024fcae015116c346152a0a2a2361346ca0cb6c98449c24b7db1a9c25c39a1be14e13040f0be21c05261bca8bc220ca1c284417556107d0eebfc8457b69a873d70327929ea44f0dd2a62a7147cdc104c3040d929902e0e6ab582c665b1c4ca8717a52ca2f02184a499c515d769561b6bbc9a6269eac42ba67be2e306e54a165f744c7c8071b27aa107222c3014d0c0010d1330d6110b6c089a28b0cd52cd0fb8868e57be80820b7e1daf7c41c5268669e878e58b22a27ce1030d0dab74bce2051661bc30028d13d846c72b5e3861d3c40b2b3633c054c72b5ed0bc389a3a515bdb45123e27a43a5ee9c2087aff8e135dccd919166f9e8e4478ab642a99b7241455d174754febc974bfe6365dd856e325b63a10c4903eb5a1e9d7d86e4a9a7622f8a2694d109a7e0d558222c3d101ded86655310fa90c884f2016e321c56354309550c6d8f6c543fa431e522955442a271e1281b76a68886e6cd3f4a9b5aaa3e99232a8a96445de8a57b010a3e9aba478cbdd5bf14a18212caa249aca8a945dd144abba68fab64a9bb736eea2e9560de921dc8522416f7c85a61d115429b268a792f9918ea6ebbbd31398995551fe0a8e57ae90e97db58c46a2c14c24705741314225c34d60a494112959e745db6e774a680be3b98f304056f4aee61d17ef923a9bb6553a6133a99452ca2ff596d25df56a1eba4de272fa6d6b96c87be2c96a6ab12705ed6f61568866c522dd68298774ffeaf209c431c0f2e790957133d72ff5c6c9b73ff79cb3de6ced4e5bb3b578dff4dd05eb0dac5f5e47fde5e6a66bf3d6976c8b88ac130b6526a3d1e64b7b642d133bab36eddb6e8b6467335bb3b5950d10628d8673d65aefbdb7e69d2e0b96415c878e261fd0575b9187b4cc39e7a4b58b8729ddb94a0abc2d51924a75a49ad52ed556c39648fa48fad4aaa6a88e668670cd92b7a60d0c05ed0ff370a98ef1d06dccc628b5f1f53743e4cbe69bb9e475554b2c71a169de2a9a76d54cbb6aa63a524d8902ab667948085dccd951cb4c732fa305fb6bc1f333cd06a671c0748cdf9d4e51d22ef6f89de2216be621f7ac29ac23130b8a87dc9bbe1fac1855102317397a801aab762b3ee57f1dce0c01c3f89acda2d56ab6d0dcbf8e62cf7dce26a9365d3829ea707f2fa64d176b3675b8a769ee63b420cf34f7ac1a18a064cd5833d614d691b7a6c6e1a26de704678bb63849dae274d116a7a6edcd16dafebe59d256c6166d65d4b46da2adb5962513d2d626a6adde364cb4b519d2f63b9af4711768abd29625cb34ad52a9542a8c53a9ae095dab0ddad4e1beda70e20a5dbf82892d1b350f3b245d67bed67c0575b571c5f8faabe85a7dfd8f3d345fbf7e3f2a4c7731c03b75ecaf403c756c0a04984b81005b1d41f9ea7092b5473a1aeee221876d1e72ffe331c01e495387fb15681bd2a33675b8efa0e08d937012eee22d9baa062113d5943008bdc3a1d089aebfc398ae5590d0f5b78d99ae210842d7df29c4749db14113bace88e9caaacd805da12b8b860346d7cfb4d8f37dbf3392ae1be7a62b8e92ae384bbabe0d288cd0f5b78d8c48d7ef6836b0d833a3eb6bdbc4b4cd90ae3d9ed09c8cae1eb5a8c37daddfc389e8cab01e5268ee717480374b8603a659b5e9ea51cb300fb9fc83cd3d6ad3658366a3e6f987d8b2e1440cb99904f8cb26e7f440e62d99e7be07971e49de423df735e000645387b3419bae01cca64ecd5b032022da7082b341d395072c1b5068ee6940d64c1e00110f300ead0044b167e6b95b864d9d99c65260498a4c10a16b38195d923781a5e0724c7335306672be11521ff551ef23a63f407d940eb00cb86bb6e0be3381009f74fccef3bcef870e338fe46a994f8100df03b84665569287dccf80ac5a64712f03ea80046b961391c53d6b4bccc165161716abe6e534817b70b568524d51d540554526043336d0dc9c73aaac0a443dfe66ea7bee9b8f0a22b63a27b1d8d3912086dcef6aa5080ae6348b3ddd77619430c5628ff73a48eee4c45b2c9a7724882d161331e49e4b2a4273385d98fd11017eef81f259347fee594cf8731b87b6336c092a499a53a289e67ee3d034f71ea887224b07997bd4ba9c17e024b48955d3dcb36ab1e7f45c97e90d9c26704bed3d6a9ae36e7e1d4516f761b0fcdda3a6a39619caff9692d1f03e8dd2d15b9c7d0f6d4a77b2a371b47738904af0bae6db0f325df4ed0f992e7ffb41de9a36e6adfbf62d136f71de32bd7d4c8b3d39da7ef81eda9c184b78efe81d1d3cf1ef742dc51e19cd7d4d9aaef9dc57db74d1e7bede6e68ee7e10b04a1646babb94524a39a57477e9524a29a5bb4b771e9404b9bb23c1c1a7762c43307058f5223f085882324d65f4dcdde905a53e3a8a3de1bd21cf0f0ff0897ef089e4f1e3a1a364963c3f3c3ca47f2fc6ab232b1e524cebf6acd58aa6555692e7c743fa3c78787ea4185c1f57f1160f92a69fa305aebf79a2dcbfd99f27c7077cf1113e9a2e973a508aa303ec5fb30ede0f3a7ac26bc173cef99d74e9ba023434121add6bc1168c2a9a8a898e48e6e6ab1907da77907b158d89a4cfa874cc75a6456a9952330300000000c314002020141008052391482c0e3451753d14000e88a24676541b49c3248861cc18630c210408000000088008c84c0300084413bbb0ba59e02f7968bdf1a0051232f27eb160aa5730c3c6142e642792033186123fc8d213c866bc596f536d526a8e8d820aea0d56b13e70648463cc11c28a70223d11abe7d3b0362218aab6130052b6087de52a0b30233ddc738e89d2afdf97572bdd17e711c16651dfd205830b2d53faa8fef62359e9a1bf47842f7634474dde289518475a2e01448f93ed5a118cb5aac1ff82373f1642fac948f75a55ce32dc867a6c3e3b60ad3d88daa517bc95def24d1f4cfa39fa0d87597850e58d837f2606b89a1df714458e4192c203a94faea348afc595812e4e462919f871d3a23a7cbb1275afb6b4ceaf4c18a6130e22a0cbd4ff323e7003218d62de1df7897548b8d7f6559c154d6b8c79df8641aaf1fe0995b088c5e9701385f749cef4705474ec2901390ddc5a9f1bad3c973a26e3c78ccb8f7e83f323b1ef6d652e7a3ff61c483842215c4c70f968403ed04fee04a489f07f8665232722fdc8bef716fb2e70abfe152ee82b9fa7f25e93c54f567e8099908166c7d1b7a13fe8747f478d28c63afe3fca1feb0c1b8aee8ca101b1e9c466a2ad0cde8e0cd3917aab414a27c2f1425cf3633ce287ad9a58d98280b9fa4cb843c1cd32f1615eba31412eadfa2b4e7ffc812b8c1d7e5d02fa7223a45643b2a40a6192eb08ce39ae62942190af7cfd32a73c5d8faa977ba88df4dd542d66968837451adcfb887059c6ed44c7691dd5d9c0dcd7af7314b4b39c869ab8c0aaf2c55db5dc08c4c423c36728e0a287d2bc5f82c68696580c73875ed0fa782bee48798dc98927316e13f98b02737a0fb74e529c7852cd53d89843b97250135e2dd1e2d588c8cc453a33e03d3b14da79538fc8cbe74f32e561e4859b7d523141b96fa99b2631d4b913528ea69104b070beb059047c8e77da4520af0775c5f53194fb33c8b5bb99f811b2906a813943cddfa6e010bbda8656f986ca90a64c509806c52960b8a686a5bd8d8c9c410b2ae100a26289bf5f6a72f11736b7191f767e651ea46a84dc3a6325b3083d90ee33bb6988fa219435b59e4f0a230df0186ad04db4f8f5b352227d4479a0b766ec823d06251d7f7494ad05e793bfed2ba734c098a70c00b5bbd86828c84f8c76cb72158d4e993991b411964712c510d3a8338d94cf8e7a92c00583e1ab1b3e29f7543c158289dd39a92dc42465f5e6d0ab00cffd8836997c2683bbbdc3bb944b0fbeabcc868a27f1f70999a25f15251db0731e6feeafb88d6445fedf28e8f36859e781b975528a4f54cae322aa55aa71ced4d8337c1c0dd3abeccc234a9379ead4087ecb704702d902215a53c319101116b74078039d373ddec32a161b39a7c1a830649514a71df0a52fd691dbed59be28e6698ca7a8d6e096e98ac551ba6b34e3df6689aecbd089a811d2c0ed473e1dd49c13af0e8906e08c79b105e1586c461a0a7ed844cf77e228842c3b70efd4f4523844b90908dd9a45fae814ce8c355cfb689b798523e2666dd65acdcc45d9c8643c9316b6d8650d694ead057777906605dd239922c7cd4a3dbf4df4a9c6e7f64bf83e950b87dae0f9d547dc1387c5cab3fa5e8b1d054dd9799f6ad65bb51d2060d01311ad365ce3a546bb4eacb6cbdc1f0ec1870fd401fcaac7721a3024e6e036ea8db1fa06f605728b9671b9efae7d8bf45b55f608f80936a64bed4598eef747bc2ffad61466aa5fb9dfa5568cc52b56f6953b40ff5aff233aa6d926c95b36a4d427eb24b502197840b8c0491d3abc741660cb65378b3454fc6c403e888a3d9f053062078eca46ec081162fe8e672c83bd028b20e826b8ca676665be4b33ca5a974517222c2c6a193b4efbfdf8fdd04e59799f94cbee075be1ea83fd29bbd5441b0a3a33358213611d08e521b1165789a6fbbaba3bb49ea2b958c41b21feb8de7d77dc48f2fb246e4a3264442203e4199a91354295a35a2ecc2a94ef8f89a0e20d138beb30d2389616697a2b96e7a2c220844a217e453ea8af0476fb43d978c2e6d84f8a557c2115ef6319af704d967c68b515ddc58b4f3fa5e83e01fb7e1b1da78f37720b743061cc51b01035fc7267a6506529660824c80d0433b760207698b7dc1a825e7fb1dd13617fa46df2862eef885a7f0e8a1b190047cfad397cce29710efe187b333f507e7a89e89f0459bc486afb700cb4aef59aaffbb64cadd8b955a409e28adbaaa58fc9fa60a95b867101eb383608e1398befc085295bb34d1e14ec9ee496aa4420f45148039e8b995c262fb62251137a00dfc9d915330e335c54f91688e4be9cd2bd257f102637ec5c05f5a0bbd037a27a907e18885f5cf052664093b64edcb31c48ac7d2f2e51bfc4f5b3ebcdf098827eb153a4f9b924bfc9e47172734a97aff7a418e300635c5a6bf5773359502b00bec2b5ccfa7edef17e4560c7ddc35259ad0342181fc966157ab7b335eeca1491dbb507f2f183c0a13e3938383acf083b391f38d82f04f1c0535555fde93c3cf868e3d39ee8aa574cb03acdad2947dea0e911ea4359beaff9512ec391242bb72c5636a001e41f50b74caea66b25a5aa1dabeb4936b18067205a95f24d524afb308358e3368bf2b04eb46e7b61c09ef30f915f4e00150f1cb74fa0339f2cf9a77da45cd94b084a172d2d57b431ea7b63426baed23ca3af1f880bfe09895f503ae9800718c1dc3d9bcb523d80ced23a8c146e2f7969d89bf3e37fbf3b40160621e269c3609c4dd93c85eb8e598fe2a5c30f7b6f14f426c4658e6ad78ff0812de5316c284ddf69db0bbaf32d30eaeb26593513b35731da6eefd5d8149ca53cb2ee5d322f2db5d6cdac2a597dff258130121770387470083b42ee468677fe66ceaf6f42af5bea2e46efd3544e8d030646995cb29c33b6f52b6d1a72a6318da7683f4225358a055f8bd612929985ca903f926d3365829049b195c06c651163a439a622f711bbe73eaac3950063dbca623238f52d791a9602ad08d723659e7d160a2741b7b391d98ea91cd8c855a59dfe94683444f9db69e2dcccc13ae29695a7c9934229b482a3e767b98edc4fdcc8dcfbf563a0b6ffc69934cc66da7a02c6298e2364b7434c79847bc755e7697cf70dee1a0bcf1d6c804ee54f591c104d97e9b221d256deee626213f07abdf13dd9827f123e527263df9391a11c30b95ca4bd982ec3dba5df4b7c460ac5bb8f6b20a04ca955c171176fdc5d6db86485068e6a301920eec282cd44638f2aeafb2d6d0e45d3cc57abedb49d151e0f0950166697cd4afc91fd61ae41a9b48694d6888fb8a7f71bcce2b71808a4089916c6f8837c31eb0d193aa9d957fbc58eae9dc90a194b561432016cfb4d7c0ade404a18fdb5e8302561b67a0397202beb99caa42bf420dd9b96b732de54b5bcea0ce54858d93edf5623018b0de8d570188ca1aac06b3504a270805ab6380005742bf46a1a064c2ca0ac1a0781116a15b2ae46ca194aba99dca8dbf649e57a4e5d7f83e86270fc10e42ddce3afab31e2e1809aac388a028a155e99dec0c5039aaa701c666855e89ad52014bf545b3781e6d43fe8ba62eb7815d72b9453f0fb0ec5bcacda0e91d4a056bc4c648df84ad7d71a152c54dade27fa17fbe128a9f872b08e81c2a2d4fa94b82703cb8aee25b12d79500040392bf61706c678d1c392fe4849ac272bcf5b4501eb644febc7cac99de3a94a83b90ebee9e872664acee1f1f236bf9c20c638622452ada8c69533cac21bc8adc415ddcaf8d88bfd6958af6008d463f6746118f57ef7c8c45c66248feb2e60cba2e7e7918b8d03079bcc1a679b7d114f4ee74176e83810a1e410ce162028d928306d37e2d05587aa0bd40302fd0a285dae56be6c53de9429d9cda6909cbbf5cd9affef809050144b171c772875260f9d4949dad6eba820b03cc88c8de8caef727935030438661d05c4b376e990f21a262ede2a27ce94446dd46e9571f0c04d27cfb768e0434fc0157b73ae1ffb648d97988780ec6fee863290333885073074608a6dc23998dde341587db8f9812c928083ee6034aabde35867e47d9a2689144d624ab62b8733f2cbb7c8a8ff276da7d3932269f2942ba27a7c57db940d4701d55413b04b110b62da7e32107e1389c048e47bab3fd5bb1b583d6cab31079d543ac5fe3a44121c73b5b3ba8b748780fcc2a9c9ced5850993c2d3c8a8f03f939d45399b4018c5b6104c8a6107c9db57081fdbe9c85230483ca71947e9ee459d370e47afa48ef0095bf8e8120f2db6fe6bfc4276be420ed9eb1b023f57368a50f7a4ea89616597dd25b00dc43291589271f7cdf76737bbfee59f2a0d92128cc0d6a1db2e5db61c4a5c856acd7c215f58255156bd62619c0d14c5c3e615f743ee2a9365f6f16915d8cce59204bc81c365ea90c1aad06688d6d762137858667db041c3f3868b7bbfd0524804ee5ff7792e343d748823f8acf502f7154fce315e06de42bf08a6e4934db74fbf00612b0032d36680a8cb8f2d064c1d767e5ae37c7ad8d77d177a30175a728b5ee5a88bbaf9683ac826c578cb526ec442ca93f64d58c861eced24928035faf54dd1d9a8782a7361d9148c64136d0aaa916af5f1588dec6a79e81c6ebc72821a4045d6110791f2f04cb9e3a034c1ccd51714f810e966929f1fb2776ebd43044a6e4e2fcaea04897dd1a1a5c4ea9455f84296225b4f839342efbc28f38b685bf6ada9f539ad5fa035d9c573bb459c129287a34db59698b61899053b89d8d4cb06ba17536ee748deca0e76818620032fa916d455f671cabe849fb30c7d88473791272b5def5514f71a7326933fab444546e2b9f9ebbe39a121a97824248b64398bc14dcf4f0f22ff8f43aa1dd986ad81eb1ca66b3359d4c15830e8030c41ebbfa996463567a9f4baa07166692568ada060d5280310ebc5ed664b039bb2e425a17ad15924c43b5cd76026925b7b041bf40811a3e1ae72a247d7e4572b573be00652e018d497d5595e84796453621487b46c622da9628ce121d376cbd368133c98c3138a51fc746e03d80d3549bd15ef3c99dfa405a4b755d7995f66c31cbe1123806cf11d34978dcb1912e36396cd91088757f31210a20a6ff9371bde36e8772b4d425a1150e6175412bd10303ea79e976d3a33730ff134b49141a2c41da3e9605f05dcd0270d45000c54b4afc2b36637315b63a5b4fd30a4b51250a3baf49f903529e575a1e95e7ec5b7996a7249b9c8e0cb2818f597e567e22d1bbf3d043c162f180a4f6b0141190322dca7b35ac87145930ee5c948158d162f01bdaf5a4aa8ee2497ed0eec7a23bc3ef545e35d88541ded44767b484e63e4b57f0a2840ca2edbbf4d603482e936f3f5e03a6a2477d80261e667688ce75b1dbd0b34197eccacc4ca1f4e006d48fa8736f61fcae21803e49e5193af0ea2b2ad2d7a95def8bbb0711814cc58d0af270ba3acd46766d477fee20a829cfd8e0e19dd08af1b28952786bc7fe725c44dcf0a2c3ad267acc5689489694810d6c5a994c3abec1767b206c3d419e98770640127f37591b9bef1a0ed12ddc1580cf71b5ba62225afc9c9513eccaa62f327d4d294e7e654220124d11a16755f9c8ba51d3922d6052581ecdeb2e42346fdb41bc284b3696cd3505f7dc87508be741c5416c44780bcf28f27c190a443113b2bfbaa9068628dd48e5201c450f4d9bc109454f86a3959aedcb5c2b3f32e81a49aaa9241ea01e679559b41fde88e5cb50d87f7636fcb6d8cf13448c74f149259e35e25365ed926e91f9802727644085965f829f287bdeca5e9262adc0bb1f6323f54658a18b530691b06110833c50dc8804fe33995dde71e51280988a20a302012349d1dd5ca50be150946e9a3d933dd4bf01661f642f273eb1a73da8b44626465d3e9134bae9c27de5f124511c49bcf0ee720de913977e1d2f06ecceed33a45da852f83916aeeda5d79ea0548c309d4451d3499732341b07dc5c76b320dac284ab02c6a743cd4e265e17c11649a8d2b2dfce6655ed21a9021529f77908abff0f9b753b532e6c044e5b01465a4bd9d075ab4ff4637bd69e5e8b776b33b8dc46a2ee9d195ac07fd7e59672fb29f0a3b1db1e8695021456f1aaa9f75e42ab516cc658e58a3440b3a5e4eb042c994aab82d4a051d7ec3b844ef0936a3f8145e65f917ccadd290f64cb16d79cc3e73794cf494830f84d96c1fc2512a6364978152c827cba3b45242c24662b1266f61ed1a548542d0aa1fe04079309bd0b486d64932b311c3827b6d1b65a2b2e55a3ccc4f82e28505dc545d2924f1cdce116701d26812481ed3760f72578b9b1361342aaa0216dc12dce2285281b9fb75352469eebe9929fe51cc1edac441a0fec26c22a3913fe7f11b83e197cc2a4aec89a22a4afc34748bd640e365b757792ad94960f533fab3aca2f58e85d56eb18799b6ba0f2704e9ccff715458ed429cdad925e7a3ea34fb76d3a9e24cdac72a5d5608956cd2c68a85586faf6514d03405a1c83f119adc9fd3d63078e2a6a5458460dd2a6cf4b3b983e5b9bbe43a061797187a9aabe4824e1f362dece25a4f74b6b987163ece962b28b2ff5fe91a56f0fbc3e303dedd0e6b0ed491be3d174446e2a7e1b863e566cf2adbe2b1d5dfe55616c1a2612e89eda0f4f60dd9b881e3af0363a1751dacffba52dc2d353c1609147d139b60882ff1eee9d321e22413766d7b3a0670c8d3ef830d4d1587311374677faa37cc0657f86e676803b3c2314364b7b7d516fda6a0e881cee2beedd9ae1e9f1162e14f429535a77ad0e61e43afa6205a96c7a2c3fe8acb2ee75330b10a71b554974e4e1c25b325f99401c454dfb19c5d935b9e5aba50d34b2a2817cbb05793c54712fadf21092066a2b11bdf40c0890635c8ed8b001e277baf994f8236de0b407f4ac687930fe76c6286e6cc09e83fc9266447629defeed13b344ef098e63102f5351392f8aee9d48dcc8243f90f5d6ed04889928525d97e1589f7d03ffa6a68f0a18b12efc4575ddfd07b27951fd9dce81b4e9df68d62274d8e07d26165d582bea5d5ddb090fa2dc4cf0bb819a0d1d38a9ff3417d831eaede850fd461628dff5d637d035c5bc1838601673305024ed8c5ccec89be117769781d2d8009577cb76533fa463e2e6d493e27dce8d09524da3af7609d44b5fcfccc36f98471ff17a3c5e73bc6086127768b79c31609d6e4e4dfab2e128c6e52957fc91c4c5ba15468d7cb104c3d59400de888316f10e04ef3f0bf4d3a430400717d925d22d21d4ecedda32ffd51cad1416afe1361ef7e41ed9f5e0ef4afdf9ea58d11b9caa76089a60e9f60bfa4bba4321dea13042def0d54da5719e0d55c3caece76c3137c133bd5a455f33d2b290abb68892aeee49db6a44625f6f5d919eeb512b63a296fd88ab0a7dc27b18d665124882e6b036d6eda7aa88b77590c9cb1f57192413aa80b67dd3fe92ef030c7eac9487bdc93289c45443a4f313cae3ca849d53c4046a58e70074cfffd3e3dc5579f7368936fa3ad76c55e8ee638db2264637743203a0d1fd2aa73d7d5b5d66d8733d25640d2902569e521daada4393b8f7f366da43cf125d975680db733eac1098cf022112f1afcdbf182469591061333e035e3337c6798ca02cbd20536ae229bb0fd04835d7d19d8a6bee7617f0ddaf516d452f7614ff4c6057ce1790bd79de02250b969df6530dd1d6ccb1f334eccaaa10ae4a2ba5a44e63e3376857d21401eb045695863ec0ba5d474ad9192f189ce2a0c9a66a7f7da7862a73ae7cdcd1b633218dd623d21cad72974b3250cfe0fc19ace0d729e808bf752efa55d207ce41992adc3acaa62c4f2e6f94a5947ca6292b1a1f632c470b0e21e5117398d067e32f152a0368b30e11f02bedda4e8507e504e4d98848db93deda14351eccb2ae576ac9ecce11088c5e063b18578aaf8ad83ed137ec2f06c623b31b3e04c5736595e0a39716ae56250f4b0f50d362083cddb833541d0a00d88f0261d8e136497f31bbf19c4804a6edd8c3ea7802950a5e3aebc898855004ee1d4b21c73a40990237bfed4db96dd2484624a61c5af4d34320562475671d349c28c0b3c62c8705a1deb2292e7232028aedd634eb39ec6170437b747e5575b73584a15385f060e8ea329874a2ac11cc5714aa81333875119cb1faf0bb7d7f07d5322a07fa4433b3cb10004e0ae96acc3b38fb81eb3c64970f289db164cf5690b9587ea192bc60fb3b022327a68b12594fdb42c1672c2f919fcb1bd6ba12b9b861c437d9f2b50887e670c4b9c7a7dba1bf94d2c96dc4f6331d920076521243964d13e9da5575d3541bc6d16b2fa27467a9954b28639b999eedcd089a5d2156962a7f4ed033cbfb971f9a57ffa37ca15f608c207446e1230fa8ff6c0e6bae097eee9df285ad081a3374928e29618ded4d4c93caa1ce36ff6bef25c45d890d484899e09f4072787d0ec2619d25c6351210c8508e31514a4808693d8187f495017b771ccb823e7dee246b6b2b510bf7cfd4d30ba19d60f749bbe8f1e4f2765a050b70df661662da6ac4e59e309c8695ba71d117ecde5f7580fc598ef81b0494b80083e264a1026770c355eae1d92fe4ffa5990e551aa930112790ea70423aa4b32355c60e3e2bd40d94f4545bebae5ca78e6a800edfdd85a07a670c690102302c298a452c02c01386d59b456c9cb2eec451c4db63d8795e2a028dbcea0d70622bb2c9914a61e3ffaf4b90787c3f1c42572c611e94987bb80c6e6dd03091924e72af004af54e5a404c8a73c9996af992a50e8815e0b64b35c5e18370e63144a217ffe02acb6e2d1ad001ff0ed1dd645f7392f3aab9cc32c1e1db860b3683a27fabdea505257080ced4cad2f95e4a636513d55d96ee084aea84cae350839228d3df47893ec6540bfe8eb2f231dfeb342f45604b7c43afe64d31ecd0b3cba7205f688ee400d10444e0540b563c6ebe4070625d04f1f17039867801bd421108887a04561aacc904565bc52367015458ddecefb966f7b5d3f53f04117065563b7c74b0d11e334981d66ca74fccaf968752d2a830ad6ca62bd2774098cd9619549b7262ac1abaa1a1404953f19eec2556266d482f1e01209ac4d0b19f992b5eed46bc4784ece2e87f9cbc180f35ac727328f410f299f6585d54570536e9af71212f66bfbab0642b9ea06f85d7b05838c2369c145d304314cf1f6b278fd138eb7056c722d7e201a732abe7631237e790ad415aa5e1192464fecd2b2e8d27eb4b72cbb54453ae90882cdba2413d676cde4272f231488d5c0e4a4edd8908a35aa1fc581a7715a6241e292a93c345f6e9ec283fb02fdd36dbab0db81111852cd126654c49fc0efdbcc460ba4295f372f9521bc6be252d6645d196166e3f00434466050eca9ed0816b01ee07aee1a4f0ff96fa2a692da13807672878cca832438b25188aff50025a27ef6af52661b779897e367627473424c8219d230e0364dc7fde8a3953bacb6bdcc5629b986b96e1ca21d3e68ea0a21b868eb254edd520594f8228ad38760427e3ac94f8cb262d8b7f8faff7d56734f463958da4614e61142ca28ca109133b6533030ee267116c70016f5b9f4ffdceac5ee6301d6bfcb682b275f8988d5859234f31153bb5314944320bdb2c9f96bee2f7626528df52e5c477a1ce42f442e6564d1ec7c696db0edabd74978ef5c05d30e27caeae0ef4c7e13d4f6e879c05179ef2d46300e201b69134adb6519b023597ec61e505a02faa6110fec04ef6172a0f9de89dd4919ff0cf6c10003228f4cb7f78326b95d0e999cdf79bc61c1101f18a9a3956191e4631519f32e723c1ad126cc65a8ea86c3e520480595b6ab155ba3eaed995744df5d002ee515a4df74649f001269e82109849ba1ce35eb115781cdffbc04de7f5697bd960d23582279e64e06c8b98e879c2f6a307570f4c824a9887cda982df66ce98e38c75d6a25be4e182ec2bfc0dcd31b7062fd26c4ae8e0c7468815205bcd2c7373ef8e4c212669aec36b82b7400e6d3567d0aafe47ac87380622d1c5d95604d361f92bcd4c22f92006c0d544c1fddfdaa5680d2b43a92aa6c7ad4142e914a7260afa7e80c153b2555039bd3889fc1027a0e5a537165d9107cf30cea277978f3ce131452644bc0b39929f261b0766ba48030d95101dcf178b65ec0c9c6ea5adaccfa3dd99bd8b40bf4338045762b50e245cbb21fd792a21a1405c9dfc0ad00f22f6eb90a3c0f54f9822268363ee89cff01163a019ccb61c21dae7dc2e869c30089baed83efcbf3c77564c38370085f1aa006c2bf0328ec5e8239a9527598d1bf557a1866907c842551055080c5dea63308ee54ae60a51bd47a590ea46940accd6353cd3933399161d418d29230e7c6018c51355a4515fd2cf36bf5ce6a899ea44085cf590121cd494aa4c4d4efe6057d86311c6b058d09ccfadf77fd1a23e20725c1497858f8bf71627558fdab4a4400f8a6d35f3618c3e47ae1040695adc4cf75bdb774b6852fbc68d94d8c9c89d7b4b1cecf66453e2f60c9e2c48da842e10e0d4bb4eef020c196cf6a8e7d207e218c0a7d6de52870eb9d4ee03c36f83f40568274706444d0c0a6b54d9470e03c85e7316157127b2cd030d1d408265be7ca37b0aec1047cf2d65927906472a5d03a03e5657cdab2b6aa11a54a5eaa957267758af916cf4be08867756fc9188a9c99324c79effd16941d805f2c0b2c13d1fb47ef1f69e5a6eaed7800a4b984fcc4b01160ed185f2375d8f7bd34e405ff6ffa3321112c412dd22c31b6666a8d1596784942f19f456f3a98a45ffca8e57d8d506fe54913ee7897ee7a02053ea1e81199339bb83c13a665b2a05509cec12b6d318f98489c8addb416dcb71040fb1e51674d96d2eac29b78662681c77014a07da525346cec1c0cc99ce5e3da372ada28c563f814ce4912eb881a8605def90715acaead492da8fafb0db74c6aab5169915693210a0047fb0a1e3ea3f48c24b2bc132391b988f9ea1acf4db7f9dfb221998c0716ec57f3f6bdb79f1fd185c3e46dd46165a06f73a3305efce6246c7c3ed3b866c0c81dd074ad0389fffdbd57d9436482a7200a06e123adf3bcffcb59267211963a40c3711a0bb7a6ea2ffaef65e132bc0660ad7a97c1c08f47422afc360478360e8871f7ff05ca879dd332d09a2d1235db08cd43eb67858173d56048e1a43136facf39cf888a8a38787d29f5dd26d3c868a43c5676d9b9f858422fae832178b4a844e2b39b64c81d65803d5344831620ffe563c005a17ac45fa69b634809eb50501b6bed8550fd2a7fb2746269e8c8602fd55efe79b527c40b3aa5932a24063ac64bc6ee094ef2e431a8a62346aa992f71eb225a0aec21af5a7123ed6108df571ef4749464c359e6570ea48226d1782eea74c8d97195a6eec1620302f1dc08d263a6154cdb48c06faec2f08068845aeaf546f2e9621f2c7725cde4a8bf108bccb74b011339eee4324dabd0f8378b5c4080c67029757526699b2014be8efa2f224d4f91e2cd682ab3aa607819d1b6d531ce4cbe91e39a2a307c3c737719791cca5bfbd69a0c57f23a078ccff7ea72eabf74a017d9afe844a5ea6700f6407370791729cc33cb8c5b75823afcefa22eeb75e1d698e90416353f1cd2aa56338152400e15a51e45af45764edb7e1421c3f1178f9c514a6f083e924b69e8c1bc6f20fa2c304ac9a4f337940e82ba5396aafe6ed4ec4a097a380d45c996896bf2419bb9f1bbee8b50f6678dd13537fbb4106bf9435ee525ea84966ff2871a9d6ad1a1e541b047c67a5e7eb40ccb9ce4da057cae61a1928a38916a9402f2cdbf43abb10ffe8d009e4c915d80c9afa37621e9fd8f80bda482fbe34b38558d9a7cb354e79e5721a9973a5f1e2b42814d973c4c65c46acd048fc901523912785d85f685372d906c117a1c2783496cbfbba4f69a9a656467ef16caa9792b0b61452b63349fb3661844938b8ccfa19c481997579f39449df4d7514eb4cc29212f849fbd3c7e15d1db1db3481a661f8d0f4cd63308dd57b3da6763786f2cf0895983d8f43609dfa9c75f7c6cb1d9f970db42d2bfb3390c45f8c93ac46ae8865d01b218bdea54d7f479918889c927a474191c25d4051eb6ca7faefc7281cdaf5770b1cfb7cf84cb0966578d162f777570a1c965ab1e90940508799315809707e2888b19dace8b9b90202e411933a491d1bf9359d5c094f36a19e9b4d80edfea43d49035edabf8ae4af808516ff5ddf6b4c7d9e5bee95644738c495797a5014825827541313963292f73341f52723a5a4956cfe23739983d9deaa6a3fbfd469c178eb645b10abbcdff52c48b14c70f80a950cf00faa45d31dd62ec4909cf77cce9d570ea1f574906247e02c5f9b50b88054df46975bb94544fb92634e4f5e3df8ad65d2a717afc39a7cc5590d20c06f9fbc0744efeae04861e98569423bfc49f32815bd15ccd1ba199a45ed65881549d56199dfab813ea6da0f5621dfe743d0a67bb487a5c9ac62e214a9581b89cf59922ae2c3d377482e51fcd0f09e05f8b656291a31112b18b10dd8b893f9d1d736615572ce893cf958bf9c8c889a6bb25ae41e3cd2a44f4b0cee3b1e6aa571f0b27ba2ad33b4d37f401447bb73081ec6aaf773829ddeeb863c4fe6c6e492e4e423877e0d69501b89661102dde79948da5f95ab92d65032f7a4b41a03173d897e4f7beec128c87b362dc15bf1916b03a0253d1dc0261c8af1e14a0ba96cc32f8aba57c4405ba6e6f557d4c2bb0dd1561bcc71f6f6310ee52801f4f83c93ef022a7fd3275c06ebbc985861417411cf4f5d46a0902cfba7f67ac03504e4dcbafcccdbfe2ea434f0e42bdcdd1fc844ede063430b11b845ef06d4a16c3e6e1abeaaa48ac8c2048dd33c5478d8145641803e59cfe1911d4f6b2b726cfe9b3c2927d6b90090b936199ab069461d0d35d8ae7f87a1f72ab54fba2434c69656b42a10c90e6808b7edd9b9425fc220c37b323c0a001d0d85df584023f553075a15fc229538ed55c67ee4190849b5834c9a355b32168b6e2baa7dfea1f7b031d9d400f9b185c1248943bc3f0984f428e4495516417ef1b9fff820d9fb02d057ccc89679f8510dbb18034e388a714a692b37fd207304bfb215860e79db90784b8c2435289f04f34c4edf02c8170c2f58a4a111da61c1d7eabaeb91f8c0d39f0d1433530b07953a850f8c991b17de3e607af268a473535fad12ec3385c7704da184e312d4c6d9f5da823245839ae9bc172c135814c61c76404dbfbe2011b4193871ddafdd1a12a4d5685addc4b8cb8d0146034ddc55013a6a4e15bc13a6d6d382864a0344600f016a75da13cb2a8b9a7a1ff50740fec191fb0cc75826b2e8270e8c295b5853841af911a3998703561645b07ad08243818585b6554efb9f1e806299dee65456f6dca211259ffd17bba6fe17703b1d44c117e4cd58d753514ab75fa730d78f32a7c08a45da442c280c7d821d019745d0eb7a3702ba9caf6c7c82a1ba0cf009bba938d4c8319d9e00c77074f5e4121408a726d877ed0e42d510c74019399b15bc3a495181c2100569ce2eabf4401e9d7cfe7513458f7e0efa15cc4c43846bb0b8dbafed21659bbfd25bd04c4de6bc63b87d730b4df3989c72c7270657c5c02dbaa9bcca9446418ef30e613db829139203581a41f85c0159f948fcdd7d8f5dd948aab139662b91b15386366b6d772cac387a60f0a486eb548c42706bfd74c534e27e86f3adeca70fda4dc58ad462ac15d3f596fa7d60e5be0a12e47806debe07fac00394890307d2a6061bdfd9f49b1859427927293436d9f8a5521a6bd37f2cfd816932cd4dc86efc35faf7210ee4aec1d50c30fc18ac92f0161cbfcfcc4c41e6bebbbb9241b6cdc30027e7eb935c3f7e896d6c44b83753caed4dc73e45ad6f69d0552522af793594b7bcfabb024562f80916077d3fef8790edf27f37e66da74945422126510ed9a11831c8d19c40fd6813cee0771201ccad0135d67ac82435264f893789839c94987d79895dbaa0d032afe444de3ddc619c03c91735bbcd2603523a901988e08e8e1ac5ec16869372fecffb893bf1f3510e40be10e4a0ef198b074459d79eaf3ab60aa504f7d3733404534d02ed0b077e5c19588616dae8b1ac1ff37e4b0d6d52571f7fec32d34c68b68cf469230423f50ddb28a41ec76504adb9e1c9a678daf84ee484a9475d0e3e3a1fc856df1d92ccfc25476d4910be04bb738cd4aedc83547fb1a37ec70257d4d62677a42495cd775e61ebb597e9d891f8bd516a85bac14e684cd4f3fb9eaa8dfa882650a72ea73d8867422cc595d8501f685f3ca09bafd952a6d3eedf32849ae15bed8418c2bd5b01fd09549c54eab7fd4c4e44947f79daf488d0959c1a9ddf0851f9a89be092ae77ed03298da4d14807d2da07b0135d4416f023db00969827cedd89444e56f9635fdd0c4764fee91c9fce1da83dc6e248b2a0de08e2fd6e92c1b8cc29603300481b5338050a1be4dbbc640e356ac4f1ce90d4e4abb3e8b5c74f85a3617252ee1e2f6db635ed76b8e10c7176223aa56d890d7574bb3b47355fa7dd08a7c7d3cd9aefc0f6a25f94986c5daa3e4f6649ab134f8124d7a670d84466a637dab29ea4a713dcc67f54455586825cf469799d99b1f4d9a04c9d730c551bea20a6318aa550bd31bfa6581da32ca0a0482306c4ab2456ba55ffc8312c0a7a6bbf8ac8e0494b77c65d3eadcb231cb233cd46a2532cb84241210a17c7d7c56dc8c9002266726468bbacbcb42cf2151c4450a0e741b9d7ec4ddd46c860247c0ce1e1ab3f3c3fd38c37d55ad7c174185cd41e3c0ac386161ab5ae6cd0000ee83672b7024e1d83636e8cb36b54f55d46eb7864a9d01deb2b1e8e0860f8cf143768f5ccde59aaf106ea5ab639ac2f91fca920121d7d29c331b8b4c557467518091948d2e6f509bd5fbe28f6eec747d46eddafe87008670662ace666af84fbd1610648e10c62336909842464bef35513e37d9111f305df1d956e01ca6ae218886f848797476b53fd51ac0dfdabb71c33139f48ba8ec7c265f7258d8bd9b39736987c192eea49f43947a490dea1887070febd129f2553a5b88efa5c9e6c99715e5bbdec1a6a89b4fe9a55b9b43ab2508147eeaf26506b6ef20525abf385772903beae04ba8d676a9ad819e22180618079f91fca517b7e356697a445d92a4c7ed67202a21e20652d8872510a73b4925e28b425e4c72d3242135af02126b8bf07762279e9f8af2eda4f09c1190bc519ea2809b3a27ef87e145314b988a7a4b5b96998059f3f01817014ac33eded5cb181dc54ebe0759db8faed3bc5b82a3d6bf9c930bc84470f7f95dd795ff301e12c3c65431a2fa80ee84ffbb47285a9f3c8df4c9c5b304f1de2e5e4b196bf33ae03c75c2a522abf490bb5e3128b6e6c46e566e3ab04e217730b23aa2a0d2578da94b6869a30b8429a665c4ff06919387c7ecc6ef58d54a531585c6d3c4c63886362783767fc59909de1235fad7c35179483c837be41cbdc49f3722737a84965e865fa02debe818705dc9090ad510c7d787a3b2d2d746ced8e426d82183b95d1ade16f1795d01293b4a0e9e348d3b1d6d3c484622b1d179e10760b5ab7b41ded2862ac9b880d1734f3b78f4e49c91d54d49a59d245096b70e5e10f0b426e472e7d6d9a152cad1c1cb36fa90b3699e878bfce6f4befd04c95e391a73a8d09ff3ffd8ee72e48d513c6795147dea9408c7257e863d1568377577f9ea891ebe9aa7e6699813251a3837d10527a3c28a983826a4a51b958613521117cba351565add0343b5d8af3b64df7503309789616794efe000e211ec3c3e15e35a20e2786323ac82a5fe240415d28f268a668a42d30f1998c4061a561d65caed40bfeb67531a41d447d5b34eced0b39be1124b0d9737aec87699cbd505e5572c0c9db91c7a1be475ab428c884662dcca7b5aecece410ed416d233a76d1f9e5bccc5cdfb158572730526d4e9b63bda51586718e680a55e6a46cad453709e10f17bd67d4fb3b5b3218f019f50886ec401dee1428a25a50b3c6dbfa520d2cc5ae3b76281847b508e5e17a570fa2d2b07c1fbab19483f1930ce20f9fd79d1a22ff516ba59c1679b622b1a5de03a8eb2444a2372ff5e019cf64a59a63c67b5e6b806c63e118951035ad227626f3006651a8d34b8d452b21318ad27f31b23df44a853d676cdb393aa7713b1a5ca095b3aaa7cbb79824cd0511f5c6ac90a677a6ce50ccf53ff144d9ab8cf4417775b63fc82d93eca7ab4a92b1b3b7a3173836f264f12098891299557088e05fa532a215f8d92064ac2525f966b5e0e695ab52555174c401210ba0f4064a557916d06d4e9999c8b6d97d35242da04a2d9399c66c56145511021b51a50ccab36809d3aaf00b0d20c27bfc6e54bed6fd59e05026df46a224f02101008ebb1d740f9c9a334192c2d1c0f29085002b1baa3e239747a2c94d7d0fa4f2b2e0fa7c6d8e4c0fe1a72e038190bf11117ecea8d30841cca32db0baa879c882e6d43b7c5003747c4c2fbabe4f291032d43ad6a04fcfe1d637c8c9ca8cc1a04b96f4020f7d0e5919e8ad89cb8d879f3c4d129ca6038fea237cb1afbf8c939edce64c4c6ba871bb2fa9215fc18d8970050a1f6ac4597c557ff691656b81e8be1936b230ae9e4ad8f5f69b90b5a17df179773637c39e2ee36407fe0c8400fd25e6a0076aabe338730a8167b19710bbbb8eaabb9410cbfa6705e5e1d33fe1a364ebbe5096fb6925d923ebbf010d37bb7ef95bd0aee0b2fd4d224322be7b22c92342d15887f820d22bb12698cdccacafd8b104225ec2fdab94a0359cca22bf0cd1424e81deef67b67e510179cafb1791bcd83003c0db9aedd2a44f460d6425ee86d44d09dd56b65b347b0b8ae875805fc33606f3211f873e31e3fda8ef8581fb302fefdb00000ecef27d60a4d3fb614959d9da6179acaa0d40810a173df622d0be01337ee479e66bd240d72ce161172c116cc84c70a795d2ed3d8b2e6c2eaba4c4d28610aef704bab1b9c0493f4396f8917b605b96e31e7ba3df8df1981281a0b60f34419d15de8939aa588a23097b9f028e52579dbf94c129f72b88771d910bf76fbdf8f366de54fc07e7c1ad27031e340fe3af093b57a16eda4008e987ee215534e4901b83b41c58312bdf61d78850afe8bee5da29d373e278decb210906b180bb6f95a8c96490d532884c8a2c5762dfac3e1b8e5d2c77fb0ccd7d0102739dae656b315065cb7d168414d5f495f7cbebf7ca75eb7fe5c34ba30f126b94f8552e4ff80ef00cb5772d3c01d3326d3a744b55eefcaee042de374cd5061559f83603c2e72112ecda14dee44ab7da58b2bc04f53b879e142ad57da4a6fd1e64d6b5fdb46f3decf1a80cf292c230d1643a773f306d09f0197d19ba10a2e91f044707e23ba8761e84da2ee17c650c634cc602ef433d4956b6358644609e4495c835ee779e2e92f5ff9e72901be3cc29884d87e0f25d5d1844a7b15c22cea177d8ce12b08b1f0d40f15f031fb0c7cf999b528233cc906494310d0e7c6f12466b5e8d4524186df0fc2f6715e19ad7376053756b95bcf421f70717745a060e4ae2f9b1e1a1cf84baf9d0d0ebaa31fa77754f7f8f364406531556540e4f147285796e018380d1b8da8fbd6bbe85d48dbfc0b79442cb91a97f679482982e13d1f0e442ab71797d69d2312da7a2a226e9d8b49e3713b00687f13b65398b10a5c7b6fb2a8d16c172e1f07265494b749193e88956930aa1aca7c81a5a1d799a8e746992f4ad6c0aa81354ebfbd1e52ad802d2b5d782660003e072c35deb2d1be23660c5b175a67185c4a253ad36777cac484a78b05b36458f3e1ac789be6077d9ceadf5b08c1c6c8fd7d04051edbe5060ce2509468d3311cdab5cbcbf110becab3f86d70beedbb34b8b108dd71ac9c52c4caef424ed35a492d52697dd58ade28ee6019892b69195ee07ab85efedb18eabbfc46fc689cfbad17b68830c0847d1411526e033ae6b00802d11c31e99d01405df92daccb542e151eed6760b6f3ab87910183d51fa26cb4230a29f084bf6efde12aeace003c29e3f7723bbdb9a82a7765c6cca54839426b685d73fc7791563183ba91f6c52d901a1b914f51720ae4ab7c06b799eb55910218df8fc6a7439f43a658bb447f521e68db8a7760e27561a5507f90410e6d013b3eea980d4ae24a7e33f30dc26692057ec5629db9ea023480ca6ebd5ee814d4c603f92d61769ba75e545400262a7071d2102af9d6d31225da1c90e6cf5ec12e48dc05d5e49565633db8317f281b3c65912342e4f8418cde8a606076b560988506e66942a22c171b4504e60ec8e5646d9d7232c71bb16458ea96d70d04202f76ca3a96e3fa6278bccf63f79b7090700f93ed361632668cc87c1ddbfe5477082c98f065d1732519d2e5458e96baf63ba7d83507f723a07dddca8616e9c8acedd65576bdd52e9e6e76e2cb322c5660c51212c34f350f1fa292374333095b104ef4cc432eef76e237187d92cefe9d2585a4e7f120c7ad9866a2d7837d2c1e95c234bb6337a75c5fcf7dc1b94d369750689b59b95cf5b9aabced442ea992b12852e4bb7e774a705c163f993d3b59b09507b13adccaf096d91af5fb5f3560a985be03d7616ae727d39113481608721b3c81330c7c5b78969ed747b1dc9d1f9f3bdd7217188f207e4121f11d017f2158cbb574d089d106264008cb00951195ee3af737b04cfb0eee46a71fe6d8f59c71b5f5ca59945369be1fe6eaefd6d74a0961e3b83b5b0fb30a08a512d07255ea8ce88100ac34702d087a00c5ee9f0d63133a7edaf6555f0aff63e8b36b1c0ac1277531aa2a48fa12417344448ff0f00daf0c195f1ccdbd35c91f96822b7c8fceec8bc3225532686632c0aee83d5a409980b654f99fa69b63dd92a33917523b71f150b2307d177f428056cad564edf8ede7d1fff50c0034c10e18675284383919ea7899b82a25987221cf85ae78ec1ed2ae4fade97d62339a1c82fd5e8648cba9a661147e62386d7d516b95a70d2ec51dd720fe3bc93b75e7940c8062178d9bbadd551cd66186490e0c5f482c3c74a2b6556965b34f37456822c58dfdc151802eac3a813cbeb04631c0c8b3cc7b554606ef9385916aeaa86857182b11d0d751d90ed6be19ededd88a1d6d708f1d49b0b81eb975712b7eb1622f57581a9b5a69fa6554933a3297430dea42867e35830c3734bc4b9e854902c1fc0b21238776ad92a0326219cc372c9a6697cb7367b16707ca0ee79410d59e9a18eae1f3496862d5b816490d4b4d6a09b4dec5e025c33f550b8ddc0c3cd95ad2692a5fd5867aac166e24b70c0fb8e6aaa2d4f5dbc33594e902f53472b380cbfdfa4238a8aa39dbee27b46186208ebe398033c92e6c5ad713d7ca045e195130c106fb10e65158a4cdc2915a037d0b8408443649d9ba7ff17bb21f45b5a56b56433d2b4ab02716d7758419b0e46523155d352b2963e142e8ed9e8bf8b77b735d49aa272f0f46e947fbb825ba61d2f2b15b67ad5ceac2619b3f3e42cc5073e202019e656b006d1718a6045510c0ffef489c0fa72815f3c0394a4480e7e55cf78d6487bb6325644654e79649d158ab98bbab5999545ed324baad54b4dbdc8bfc0e39bbbc0fa6a2f9454156a4dd62b01a3d6c898e0e7b50b2dd3c8460ca7bdae8a4abfa703be87d708a99b2383d6b660d4b2f6b0ab8c3d00cbb620b86302120783b94f1792eab693b64398e6681d1d8684405c2ddf77b42e23dbc0a270adf580d8fd9831fe66493e05a67523461c5dd2eaa663a3f821498646f4af9aab1cdde1da0b94077b6c0bd0073242ce32b9bcfbaa612bafd103710977dc33bc9b9d599b15a7ea91a32c8072b88ea8e456a9c158d1ccb1a19252f70a7bdd40efdb06574a14bd8764edd30d4503c6d45c2228f607e0cee45b4be8db24c7fb062c1d95360b8ac5ef3fea3c7b465b722164c06a23e29868af52846649b45ce4a7c7703363a610b723865139ae7d5c7345b396584730afdce78b0ca5dd9ada9589da86ab121d073b4f5f5cd8ad96ebaababe2fe79daa6e6ba9503a27c00ba85ab5cdfce396e3919b3b484cf5497728753aa8fb2643bfd194f3a9b8a3752477ed1ec5a28bd15b467f52c0b0e0827b74faa7da8309c2c0b666b7ac75720184d5ab8b3fecd7dcdb510be827e3629278455fd3f1c97df5ca27364a3ec8830153ef823db4eff12ec977062a67275298f71b1bf51f20e14b419b6361530ade4ec38d4bdf0a77c79ebba065b1de2283d3623ab3982f13edb1e0095b82140c3f2150c412b813c5e90309faae701f0cc71e8c3b2bf2c09c0840d1c0063debafa6e7c18fc7b942fd44d6209fe71af7548a2b6f1975a1a82ff1a9c3b1d50508aaa02bd0ebd51d7d02138b13bcedcb5d7d6bde8f7537299af67e04db0d6f96cae7919ae8ed437bf5c20945fdd63bac372145eb1c9f42e19e1d6a2c1c7e56ded018c674d3f23551b5aebbbb4dedadd52cabb5450531a9096c495a0761255b5458b32b558501bef2231750f52efe8b157f09c7aba69c22ee471787e6816b1a659d9ad24caba7eaceceaba0305ca16ea2e4355997e4d2dcac5922bb9c3f3fc26879bb4b06aa9dc117d2aadcb48b1a5d4aa5a619513a125e6db34d4feba62cbea44d5760ca766e99cb1d3b401a1936b9cb14f5514962dd97216c4becc9c0b2ea4ead3a05c67074eb6c372dec81ffc89d601c7f4311f66176c6eb52d2e1934db0d932f4c6d68a2ec860220ca7902867c7cbbe47cb97089e1c70f8ef4e44191bbfe65597a96219fd8384cc86c2adfecbc4107603b8165507ab5c12cc4c80bc39ff9102742d4af7336cc8d97a58917bcd1ba4d18f60132bf082dd925b91e84ef39f0f03f8335b2f916707ffc76f2a6f0845c31c8f4e0911592cdace4bebc204beb03705ad4548a0e127a3b83beb204c80958fbc98240cd115aae39ff941a0cf23b70666b2d69e3aadaa3526a087beb1bbde295bfed76fbc8bb4734f1cbf68d9b04b12fbca98123eea3070a211d0a61544715208dd11d5349bb418ef86056abc68cf05e7744d204e5eb2ae9355c1d29718bbcf450ac9c6d6aa42f9f38f699971d2a34b26d1502af3f3c32bd6708e043cb060e67d7da761913de985da37a3f6fb211a97b9439cb356f3b120ba662ca8024dcc885f378762b2ff77eeb679a8d390cf4c3ae030a2648b22da15d94d271d84728b51361dfffa2838e8153832e43c78a3c44c27596c69347424faea1e0b78b5f483aad97f5f24060b13731d645c17e2e7fb801c492f03c109a781438f973c30fab715c488a9e9c556703cb1c42659c9d6c438fad8aa7cf6f1d1dd3c70869ae6d0d599311e1cfac02707fc9a421e011284a577466b843850def93071c87f731f9ee26eb282ff3d6e9b7e34d7a449380ce02067a28561171dca783a7c4053ad0b2b0058dd421df4d46c45cde4bcbf44d815e2dc115aa4d7b34845d081f6c19c09866a64a3519d62e456919f8b106fcdfb90d54f2478d3ce396f10c2a012c54f3a9b1ead42d59626b025073f576e79d72b77ca46d2f671ba28a0f7bff571f766431c4c25c184bb9f22d85c745cfb5625473bccf3d8e7f48e32df687abcdcb79ae93a4423944cfb9d1ad79a4e7ce55bf4e27912258d4627f849e288b3b0dc0a3255c07e6f1b1a10f4e663cce6e61404d9875d1a83a10486dd31b67405df4b6079d8ab4e6e84660503356510ebdf93a183b5a4a1cd3150e4f7b68a1e7145972f8e02f36f4f29739ac403dc1766e977cc70309ef027b50d663f6b672a305cbf342755f5a802093027ee32862d3cf5f25810abbc42d23407bb6842e0ed3834f26febeba9522cdff920ad6c34ec38ed0c0b9943661fc1b87350001d15de64c751edf950cbdd07c508a62e7ca14a19b2baafd4f02c204a020e90a66915386bbef3dac915f66f7052e10ef02b91e814e29e7f5222e484f6030822a8de4d57809e0453e5c8c9893050abbdabf05a3c4938246f8ede447083e34162ea4b61c164fe18bae9a13a09cc4eef4bdcc737f8e4be335c92b186185b660a364a79436d4ef8c1a9b4df146a6f0e2a111f8acb7531192677398807e8672f8412c928339c9fc8014177b1359893bebbb9870697e68ebe2aabde9b73596b718d45334fcb7b4581f3901f7b569087b90a8984aafd2c015c74adb936cfbdfdac3536b5818497a2e831c2e527ab9a46012183b663f8803138595f4f5388152254cabc4099d4639761bc8056d0310c18c98ed76f00ff812c669ce58b59a20c51f11809721e937c5ba4e75c995746be6d51532bee992594fbb8c01304582364dfe3bb83a7c3d7bb9912e543e9bb5c9704bfdcf37a78ae2fe91874c995ecc2d5d1e8f2c35b373dc1879f6f3cbbd7ef7cce6340c5c4c8f0bb4bbb42399258e97b8d455832cca9832435483ee2b28ea342e0716f100545f6ca827c6780a2b72eaa964271ac6708bdd5c87142cabab2ca65ce66e0a337430891f5c26b8caabffacd71685202dad4522407963e64612cc06199cab866ac20ed5ecb02af4494678e8a16a4e743d286f232b2d8994b401fc8a28e006033ef11596ba04ecd1374cd5706610efe8145d087479f83b9b063b1252f985ea72c578b04f3e44e8b7607a84370438b8c4ba87e7e9f0defe095f5818456ea499c8dfe614bc85f6f9779c16bf2da637a62bda780a865728332d95e0924ddb4f27f3e84fcf37ff2e9ef9568536b61e37078d6291051d04b97a20c0c4a82f6273ec08d19f425364c7e97ac05cd441bdc5daed8d3b4acd1c9ca74058214814fcf5fe9860b15434e7459bf2bf2d0688e53761c26c88ee8f62b972827f2829b8111e03e9fde513fd804146c218be11307e84bc5552c46c5e4c49197d79b4921079604db44da7ddae85973af81788a42b6dc92e4965265e55d68786aee6125e8c400d25a76235d657b2dc71663766e377489fd295dbe07b611e3cf4a699bbfebf0cc6a463c873c104314214f954c32dc7cd4765bc5baedc77a5110f1737c90077918ac06b9e5df127ed3beb760b5788e7b7228787856ee731ca03fde553dafd706fb1b134c5d18ec6e95a30b8226fbd9082e88408f0440c9c89e72a737171ce91628fb78fe417ba74698ca183c0e0ca1b1f432c250b771f921f9bef4ef2692db80a1aa3b60e8865f3e7b2cb7f192dfcd8545dea6a84dc6bf2471ede014197b80286b31693a3033d1969af3c9f5b8703dba3fa85ed2fb0ded4e79d2fc77d4624ac2acb2770127cfca85ea195bc17c561b83ced8888edc5a137a81653a9c926aa9b3b02c361f37379325f2e8c27288dc3dc6cfe49340be467d6e796ea6d90a7141ac7967ae57b0bf2194c9cdce91263d12a42a63665ca10ac0b8c499b06524110233d990187eaa27dd6beaa51db8653d19e4d5ba62881da346c8586dd981ca8d4df1ad71698e19d4ee2182296ec5380a7206afc47bd2b3251605eda615e18e5962f153542a2fc09f50961c90e901d285ad6c1ffcc6be3933df37a79fe9df1274c84a6d9c6bba714a5fa63e89e222c7d8d4fcc926f8c97c1fbf58cba245b462fd5f48f82561fbaed5bff9dddbddb79be7b8380f10ec1b26c1f2b4ba50c65d4f4a620781ee5f742720dccb9ff549b1b548e17c53b2541af6843e304a4cc39ea5486b42483024347149bae2cebbeb20ebbf9dfff342f7cc6a21d8fd830a68a74310528b78e13f1cffb16fbf4654424ec7fb73d834f3b783b15e9da47cc319ca52e202b160b899e48ea4047b8174e41ec8ab8b04d343a838c3f9bdc3c95e4920b17c24a3f535a1362d81866ba5c893e7c8ef06e06d78c2fa1668e951c59b19611d37deeb8eb0804cc50a0b3904ab17dc2bdd5366c96643cab24fcd1105f38db2d63b741475129fc6cfaf64050e4c63dd4887e607a634400d2201cf831eb754e4048b5cac68e1e70d68ca79a354b0072b195b2b5a77ab24d1564db04d92316f3ffa83a1b9312fb50676e17c8711ce8dfcbd93f63e31000f558fc3f47091609c4c0c94c9c6f4f5a7300ba2cf9541f3fdf298712d7d5ffaa388c3c7e5849db4715f0e526e1f235a090b1419b237619c17511fa3345351c5c544eee7cf26e42485ddd0b3aedaf215c51019056923e3fd5326a698092e87d09873f122b815853be191707f7332b6d9f70f3d8df58a575f22b82abac3e1be67bea2bc9056416cfca9b73f687bafc25aec8be0b5d2d247d6ac6dcc07eacf9c905b8887cf0a91524576ea322446dfb40bc76789b5dc6d5099b3a8565a99f58ba1f0c880cb006de30c91eecbdafd10316992d093d9ebc27c3dfc2a87bf392f55789cfc8286599f30090a2d333e02903b8199cbc63edb86909dc9e010b62191924610fd44ffa58542a96a15f12be3862ea8bf72c9fa39b67247d0016a732fe81bf98f20a79324ccbd4f7341afb2985853a40420ebbc2369d32c9582fe0314b856daf8cf12f63a53c4b7a3f26cdcd4810c9043e3de84a01bfa4c8d969588461fe895c3045402bfe05f4436ffc308692c049e05a65fa509cbf6fad79c029bac5d9e0173c94ea8ffdefe8d7e69a86603249e335c2bab9e39c9abd8fecda2d07267a3c9e7248c4dfe11fc8688cd6c2caf2313eabe45a97009fbc7bad8ac870bda4888f7f051231ed50cbf3ea5ce6b21a4549b2ad66b9c510b66b9bc2e10ef43762ed7ceb307b590e7a4dfc3ea5d6abea660ec6b847af338e6da69e00f4b42b388fa1d54913bb11c0b4bb266481ab351fc865503010ecc62b3ec9db587f73042ce215c95455f7e5f6c480447b5893cf92beb06d917993166375e5f31479929639188eba198d779a3a8bd25ccd46ca20955ea6af07590add1a3ff98e984204fd6a0c292fb69c2e4953d6af159f2212d6c510d387115889f012690f0c4a3c72a301cda2035ac4faa0027d79fc663433a10bea629dd70cd6b129765f9a4ff37389d83ccc777d3d722d3404bf87d352709426e62b1a886f69c167e78e6cb1d5f429b0380b00fcaaa068d6efd3258e89a3ad914406eda090161f31ad6fa51ebfcea306e87e8a73b8e290dc5d8f582d3ba1eb67dcdf1aabaf08c82b8254ffaaca2ee0ad045cd3f7805607447fc878a701e2d1f843dd679b2eb6af5e5a0148bcd0c9fff9863e648123c3df88476448f38472a8bb4918b61b10c7e4dcdad5674cf4d4f191709e3dbc9a3091638d3d98054c08dd02b895fe08244781a5a6cba0eae55d809e5b442bebb839c8e46b5792a69fd366b9f3620065cc2c51eafe17b0f187c7fe2c999924b22d54c2c6b09dafbc925138ffae642534019a00c14e32910037ade7d982d8bc027669a7493d1833c9656b42cb9b37a44c4f8960d0a73712509dcc91d9601e7819f04e8fc46c408b3eb8e072d47a1b491fa82689d5108c14060f764afab89ad2547ec642d91b4beff01140f2baf49230c0fe6918c52c9add2844e3874513424f24295639f440924ea75c2cb4f94289dbe08330ee7357948e4ef94bd3eedece6b5d0d60852cf80f9b6fa84d84245fe811e89f8a6d824bbc7c297b8b85835a6ae3b3830871e783ac022a84a9373054688abf0e2f5e74fbde8575b2a20a954dc1d33cf68ba45443580d6f45c73c7b7545db2456b527804594c0e21d5b66817bf0081ce00cdcb0b8ebfb0adfb2abf453abda46251252cd394027a8bd327731c932f66b4ab3dabb73108b7127cd00d081f3dde654260c8388bc3b1096621cebd5f4d57ad5a6f1b5cf20814509ab551ca883871a2a564ccc1933a4f7a67c0e44e5b5bce596ccdbb150c78264997a5b20c06a97df3f65502c2c78a207e595922e4a1eda1dd3761fb2cbd74bc2c5b1713eb213203037d2d87825cd821c359ff03a9244f7ccfbd3a3a303e66897ab379dbc38255f093e542e71c1461a8dff9ff31fab705aefbe3e8179d0e62c63c06e49b471a3290a3e4848e4626788041b161bd16756d304a2dbbbbb2e106cc21b4eac13029b45f000658bc5744132219c918642510dbc081be48dba405a92eb2cc77376eae598a485bc517f80a4d95ceb56e5cb2fcc93d433f0fed4c02085363b7b1e95c40b3c346ed85151aa467ed2d4ebe1305595daa1969633ac9357418fc80c8a2ee57fbf144a8e68aeea40130e6b092d00e09acbea0a110602dce979c864d3a33d3569c34d121ac95f75860cb415051892b487bef2e45f0c9e2c0569989e7e1833571d35536298ca04ec7cefbe477cf32ade8df4a01b05e42d8c895eaa7902004a1f9642f16b411382ab5f1f9469c9c9bd294a22514e653965d63f33cc930df519e283568e94f12974d17a8f323b93b17426940de87947e80c6da8612a7f816fd2797d2da390b0605e5904747f9a18c2020ff30fe963a28333849719e34f832d9f3404ca5e828c33cb81d0c7093756e773d335ff03fa9e4ea52aba715e5b7f9a8173b4e84015f4a1565f3e411d3c10de54093130d4d8f9134219b6ce570e29a63aa3ff044e0aa8d95cd664fad460cd9d84b62b7f55030f51135f00aea5e5762effd25d453c2323df2e6109f69db96553a763534d22b0b9a5c1dd5e23835107a1a0940c0a15ce5be21c8a5b5a122362dc4c208799b20f74a13aa602fe059e8f13c9c8c37b1f3477e0074c9a2061906d02491dd482c04d9632ad707804fb5552829dea7b21216f15f40a105f1052a1e17b41100334e5772714b520afdd99541e1f558f2e392301018fcf848ae7ccb10303145b9d62f77d590d88af2fe61769494bf0012f7b954cd251070a8f113f0c7ac7adcade67f541ceb99b80d961bcd3b09294e2b0f0b8649cd6ec0607e6712a731877ca6d51e85447b6d022bc28e675813e38472f103f6ed99811a466e70bed611120a0f6d60f5866f6336c88e88b299a117a5e7430c3ff7b322e2e42a3285445b49b9369a949a58102be10220338790321cd54c0ab412a60f197d72e1793de7ffef3056524384d17c00bb890a537b7cf8107d90563694d4811f46c1f1c5a8d61edd23feafb828f588b960b08f66aac1f0fe2c9e35b076e6ee94c6dcc408260ec7839c35241232590db5f969df7cbf1ac97f7c2fd718263012d3a5c21b457b6384ddf4b0ccc4d9f4d9994113abaf297c4e8197b803297bf5f9fb95d7528ae51347f4eea06f1222a0a68b775de73b9a19ef444d466be52c2f7bc282e4a304603c32ffb6b8f4fe711dbc4976c44ba13da43a0cfc477afa0785aa705a70e456b344ca2315692f41a69f6da2e72a80d1c9856c590cc4a67025ccc1b2f820eeb89f387a3cd5ebad3f81916a5ae6106527ceb2b416ef28505e941da71eeedd91d3c2f2a42ab09c5dd0266a0f586e4e3bb08c7a2228a0f4d994152e0094637134789d2b8c708bb54cf5c8d4f5e87d05b88b2d1228ce32ab8b739931e9183df348bda5721982ce7777a98301a1c70c11f8e2fffe00451470d472b925c51cd7a581948359b17275ca79c612c29eefb984e1dcea74f57c2aba66e8289482fcf146818e70725d7f598f1c1fd3380b24d6bcdcf53e71c83a93be670a623529f9cd1d3092023fb3c906763e46b273fd2d801a5a0d5f57ca505fda79d25c80955fd606e4d7280d5cce4f00825379a8173652e80436cea4d021883f56fe873b08ba640a874d8c780497a6fbac64cb672b9f320af284245ab352d1c821b439f912a0200f0afae8c11c2b5e35eac94e938278fe7a4d07afeab34738cac384bdf73a533d776d1ebe7b054fcbcb28cf294171f236a0a847d3608805d3980d13c076485ffe3f825536d88b712bbc2d63dcdc242c6fad04e866e3b6dd070e99848f113a8c808b75b69eefa3004dbd96b0b877eec1af86df4c6ed88f77c159f514f21fd695f45f708d06981035407983dcd834ff027761f9c2da76f64bc007767dd8d64572d410b24438ef34f5ffa1d6fcabca9a2fde15c468da4ccd422fa0481b7f73b4a38367f1aeb7731bd6876db8ee9b116cf803521d07e8b44e6c505dfc4f6c478c4c1198a5111c70eb62f5313fbcd23175e452b8babe2bb0428971c7332209827d9e749a4fe0b0085b001f952298a5422d0d0b71e35be6a34e660b018194f87644c1bf0d90ec1a73f537938b3fc6a7339f5e0e1970c6284b92ce07b32254f164d18091c0b89daceb6d801065c80eebe4492c4a6e7b147c0893cd7b3026afde5c1e566cb6e7af794dd4b765319a2b547baf44dc685524fca03d28001915018c20f8daf21d3d4fb9e9832a43f9c07c8862fb73e3294083263d01643f020bb02b090830f5a083666693fb30cd8585515148d445b2158ea8646798a5386e5c2252a6b874a0b0558354412d229732c6170338596d242f869ea36d0352ec19370588dc752e7b34186043ce2abbba5a7eda7a421e1ae04f8c1f989ce86b38430fa255510f91d8b9554d37a1bd4b8801c8d51f34c59c3dff4e908b5abe2218f1a130eeae498a589313928992eaa60aff878abc1be8bacd4d2770354231a7c03c0218aacc92189abe20044002919dd0acf92d1fab5e9ebc6c7dad368817aa10902c0f3a223d91aa421b5637366168ed100a08330be56ecd3c02d735e37cab8917a61451d92d8df728518f32754836a02529f129bcb31028fc0f6deba5832c179a35b53fd747dbdb3a69537a36125490167e60c7271b0e67f1d54947b7f60b0aff60f96075c55df3a5aea381cb3743da3e44cd2c76b05a056f97587452acec2e25d787cd680114b9b176b85c483cef32293836658e2a246a9f2648caa82160e9d8a2d328bc292dd4acab94039bf2e37680a71e0781aa5a53546082487ebc22f1068a55fc99498c49bc4970bf058aeca365fd0c525783f46a58fedd5873a987836009c4c87c3b2af5a66c5d4e7d06a20eb43d0fbf83fad47ef147c3422f3a2f61116d65c601e84f36f3205cd0be639993f83e1bf4ab47e943cd0a69eeb3506864f85579c4d9bb74d1732e07055d0efdb2857c9eca903c7b44747068cf0a7647066219fdd278c2b3fbfca12670f73bb6fe5f6ffc94a99a241e472796291d5a45fef3cf04ad851d4b62a77f0dbee18cec2a9e8dddf922a619828baa8fb8f9423015cf0a4901a430b5d2e29132f6212c830a11e196c6ad7150a16a2a2aada2cabe9878fb81cce07f571195d9af56f921d9576a8974fe062055426f89bfd533588be7c426187dc8d98c28daa2b33fab97a1b0a2a2e5c06a5c7706076b1132b42490ab2e342747e68cc9f34369c2848ed30565090c9ad5898296d2f95853470fd384380bd3d13cf2b4b49fcf01cd4c4b3728a24cc7cb6d223bb9df9f6dbba07f2b7ecb6ff423ea1cde43fceb2f0826b1bd5a673865d9c71d2225e1f74dc63b4025f3974b1f88608ccad716913ba5040ab140619569ec17365cfac3ba047033a393dd67e89896fadae184e2df35e2973fec4c0b469cad39301a00e6b5996474a3bc9e728841a24f1cedac1847b645deecb01e24f59e281c30e5ffea56746e6e91963cc404f09d53eb4e65e3fb53595c7e2f56018e932dbcb72b4f3f69ec67c1243fd4a118fa3162c72774721d004bf3828a27ad8016b37914d390139970cc34f787fac99529b90259ee3d4ac114070136edf4d0678f2b8e020ef965ed5b2c1c49b448fa9ab3176e62e5c5cbd0769644650f241629fbf4e383559433a23e36a7b0f13c33d6d9dd73c054cbb3309ca7ce8909b3edf0ec362f181cebabd0384b6edfc02c655a81539b39f4c20e291cc849594ea7c68dd07be175807c4806e67541ec05c7ff09676dc1ad18b274c31cbc84526424411479ba7efce6d9f5b155037b78d906646491687f8a45df8c9113242c3c450856fb55d92ceae3b12df22d016c0b24cabe7140d1df93403acca561d27bb35e171cbe3954dd633b2c61bf185427b2e618776dd9d03d965d62e257f68a51692a0ea5494c8f665ae7a6fac94dbfa854d7ba1470903befd20ddd70cdeaacbd91ec3277f56f5a1dd4df50200e35d0e24807e6a19803d164500cb3a04126b9db8e870cb8ecde675b0b0c4ef7b58b50f799cd5004143e3c36dbec971299263f8f3abb5d5ad41d1ad2c0619db6f9d69c1ae52762c1ea6c915ad1dbe0c270e630bef60cd00c8672c663b7fd3698d88faa47a58a0caebc9953fd6c7598ef796ae6f6b7c1a333c363553c59df444ae90e50754476ad1c4fdbe86d6a0b34513de9eb3bac260c765a2aff61f622469bb6451a907057c5d4cd17c80349fa99173bafbcd9680513023d8b74782a67b531ea70ae9289b6e16e89ba7ac3f6f8956c5344c05272144f207220c88263946de274b004fe16f08bec82ef9c4a805fdd9dd01d5bf6bb76da9a371810224527fb6a2aa660c0001084bfd1c659966bdb99c86ff186ec4021236e78c1c3a1e7014860fe3210619f610e79d6064378e70d70ddcc0673cd259487176bafb4f968c56d838a71e9f054ea6ae1b47064ce1940d38510f3d6bd55d97ca239c00231ab25313217b390b6ede28e418dda7630b1f96a67b875516635a640098e0636cefa0535a7047d29ed1f22da7605e1b0a379501f051c02034f8c8175f9f380730f676ade5217eb06cc780943e5524c4081619d605a7d093760a779e1fa1a07c71bdb2aba5a18cba6baec1eda85c2a8edc5d1350cf54585e1a480b85df324bae5eab5fae5c403b06e41515532ab7408cae4ee6a764895f1c827c01e5f48b1587b09bb3e0f1434f99842c94c3a8faa60167f1a895ee68db9849c398269df58e15153e18eb57510d3431914b29edda0e7fd83f9adb7fb8b441bb46779ec00b10e4a1e281fb199f09ee12706daa5690b7d93655b63a1c6ade6e7127f2d0af940e449e69a22a7ad1526e3b2e5ba4a2cfd0da304320f8b51656f671a6918ef35ceb5177d7b9d0702c6f45684b6e62e9da1cff7fd083cebdb50c1cab33f97a930ff2decd26740573a9ebe2ef825c64cbc9b1c296dd9acb191484bc6cff12fa8eaf14cd7bba54502e6492bd499876aa91d587adec327690c20cf443255d0c0e0a4c04cc29bce799e35adb1793ed47e58a357bad44282d8bbf3f47e540bf5e9fd521d80b3b83c738ee47dfa936e5532d185a074f69f75d65ab2bc43631338dfbf05267e45fbaefcc6a2b2faf7658976c2955598a907ae6fca00b6daead91541461b9604f7232c5f99bc7524cd852de838440ee540313ee8c5f2ff784414dd67f02dad2e785016424037855bc870269b287ba5a1d6bfc1afaca5ad0580f2eb03406bca2c0434f0c521614c4ebeecf6a408f75fedb2a91b7b9344920ce632e9931013c8d950abd4829a7f068b2542040829b48fd75783bff3d992d12330b73bda942187a3195bd47a8de9edf40231f3f35c70a18d0463f1b9dc663fce521bdbaaef7f21b82d2f9f6b7f64582fe4664c5d8d07dcb5dd8bd561ab3d1a06090f7c247a1ad23fe5dd09d85c6968af72e722db83813bf258579de4c225db3eabd0787f702f57c3d278968974476a015d6ff8c4661d019511f98d884e66b397f40b20d1c3e83c478bc28744cc005894cad0030f8f4a23a58e66acdbd02f058d3ffdfa3402a336aa845561f6651f2dfdcd2d0ed925e3e25df92d2a4b29a75d68546a264750c3d2a83ba9942017142f0a6d926f9fa2d49a2c5e6b52c922666b1ce1510f339e6ea9b464c9f2e2f463004dbcdf052749d53bec9059b19f5d4afb8ec73c09f85cce6d04a5dafaa8129096433c763894b0524c51acc27c5ddf827c31a730e8f13ca425ddafdcb3dbe76f4d72e38b434e1ea372a31f6950d706dd2d44889bad401e144dc040e168d087e8f4363ca4269dac0279e274906212a2fdc689483cfa67be2fe4c3fef90b4150f234130ca353e94768a9146a8ce955202ecc80497a8f2b1c07998ee76e8ad67f399ea70546f1bb97a4c8c8bc18d52122979ae94a1dfc21977e86797ad09972563bd65c8a210ac152650c207d8cc1a063071ed458bf4b3a67d750cfd49d5abbc21359111603ce4edd1a2a4ecd14d0c294d27168b59d7c032a0b6d7b132b37926f4241bd8f0ce53a03face94e8e90d7575ffcab6a80055218308dbd02c9bbbbc4772317288f8b0755e27eb5ad9a84adfffb32a0c442574e12eedeb06778a65595986583a3de629a93d2a49ddf52994e2fce20c0f1660446901b58c2eb7af3bc279350949bdc7104f389f897a3e9173504b5a6806771bc7d706d05e74005fea7d4f10b11998c186c7d5946bd81c022d510bbcf71f1746dd1dd1b3fec0b6bf8cebfedc3ede93995472a1d6c18061fd122bb6ed3939b89652d807a29fe4a2b043c31242662139b0ddd19acd042260f977ce37bf4139ae0f01e7d0989fbfaaeb912755465504929f83b4ff106505f790531a6b2aaa3f0d12aca32a5a9c79134562ba1bc4ccaebe8c6fbbd6330654df6600d7327796d777ebf0ff289ba92a0a0de32d1e2232b8fb8c07bd3c8677a61d5daa839d9a0491e66fb0574080fc04e1abd77d7f3c81d8cc69d5217c5d2a2b9a9ce1d101292d9e935c1fb9f88630a2504335c32528a58f5a8dae75a114875cada7bb882779b95b4c1a05e638daf3770edca3e7222d123af5f43342141010f8f1a77c8b62f1062e150bd4bff6d7222577eb9364af96a751ee3b6f104cd9c8aba41aea28a6f10aa64b95fab2cea0a66daaebe3fac227519858c20568fd45ae00520ec4fc82436009a7730394a93fb96dc5b1e6bd60897d851203f8c3066569bc42aedb3fc532307be41c2d7ac6b3785edc8dac99de7a778c9410d806d17404ee5a097d1a068b612e880de635c7df2421898c93b2273d4464307835743968fc82bfdb67d0250d90c70a235bdca9a7382de954749446a9991e86e957a9944a29a5e832730bdfe66ba06504dac603693c1caebf59340c7405a4a6e6b97c0b44c37898313d5093697a06690e7c6fa3f93024020ca703aa00e3a0571040ea8a3a444ec4811638a0143bb832b77918c8e6ff5aeb5acdedec1e4371a08d4438ae1bb9403422079aac4dbe758de70d78fd412501c7519aaae1a223c3d1c023fc926d7127c984a78a13d45592ae25bbc996324919a209b909e30941a98c236648bd218daeead3872346825fc216098f631d0a4efbaab1f489ade8296f6b75e20f19dbaadc4975dbadb7f0531b5a5ff5c17edba9400f21f603ec4d1a4d2991ed884488ed61a47e807d348d44f387135b406c8b4544c7a228bae816a94f2c9646cc2247467d6223b9fd1a1d619d2b656a75aa5c9daaa491abf77995aad08d40b55a60ec385c4ce5f5f4d461bd4f5608babbbbbb6d77b77777b7043a0565bdddab954da9c6236a9592b16f40779dbc2a3f7295e9448b6b1ea99c349db05ce54e481f5bc98737a3fb1371a4ab9ee52925d5e787da83ab8c95246b5e3395c9a9edad5c68acbcde9ebb62b26f71aaf1887ae2ded911098d242eaad8aa85a9a78509aac7c4f2f2de506ba4c31cfbeee4954b23e41c063e723fb7950fad3cad64e4c65bcbc9478a12746b796ebcc978902ad40a527db0b33ebc05a942ad8c715060ba5aac961402582e6ff9912be5ad94894d1cb1e54464bc653ed1e2898c89940bba911e65bcad9c48cb655cc67ce2322b5212768d9b16ed51c61be9f5c63295dc1e6ce4865a639071abf2482b19b9c9cf594225f9c40b9035968ed6d66a8e664ba9cc711ccb2babf714dd01196ce90009a474a7ba1bf7ed6eebe4e424c4dee4ead4c406c0902e5fd96e71901f7d3e146557285fa3b60623cf2ad4f2e1562111b24dc67e0449a3d691ea1831ee62409956a87184e1f23c71c4177dfc144a1edd5e5125baed8e0feb13e798a093db8fd8aacf0365cafc27442a369dc946b7077b15baaddceb13fbb6f4d903fa70935b63a5aad0e8a4032a6a91b0b5a0db83fd274b8f539ec8185b231964ec32aee487ea83dd2275cf9234829050b292246322b93d38bcb9d22857bef27ae274779b47a43b5939941b795b407086b0dce5472c53e52ef3c8f26e8f8ae5b6ae96a9fa602d0ea8e737b93d16473e92db83614117db272e4e179c8292eaf1003f1d2a678429e0a256f1a2ca2e571ae4ea0513ba9bbae1c518777baa7bb0d6dad0626b2d6e20a823bfeb3e5cb716ea083691f8703eeb62ae4e55c428d528d331d500b2c6fad6225cae5b2ededeb241995a5f53a3f5e26ead5785b6a868d936776bb0db73cf7b4f58d06e91f9a2ec5ba40ab572e8768b6ab7e4ebb8b73025b7e73ad3f9739e8ed3a5c4e6f84965bbfd4df153ac52c55470533040459745c7a18f18c829e5d15b26f4645b9f2c6c31abd1169031f43193aaff41492b05ec224ee829ecbc2e9ab63eef8f96e8b7879487aad1af93e3ff2033aa460fc394ad55a9ce75521036a7ccd6f5a9e240d9581abddebc2a74a50ae11e421f3deda51f6472d84a01fb88815c963c868fc51f6c903eaf8f2f24089b43d3a8cf123a77ceb2fa645dcbea4e9f646cb6951194b54fec55477aba5fd86dca414699f21467e5a38b9e327156260a67ca55e689f1688a51b6ab1840d6587a7f75a4f194555ba4eaba4ba644cae998fa214ea0233e2861f24edee2fa74fdd078ca318db372345e736a712bd078cae69fd2dddd297f527d70caac4eaa50b3ccdb7b9abf5bfa74c2746f0b080dd3ad5d9f161f8d8b40c4eb5b32ee956069fc32f589fda594a9d5915eb2f8e9e5a1bc4645c77eb1348afd2e35ca72a7a4a67c798d56d63896e5687324cdeb7827d507fb7d9a76267d626c74a34429fa987f72aa4601059db8a3d80f300d72671ab395c052a617a7647b589b2faed15cc5173b25f57275a2a225a717e744050b0260481ab5e1d87d68b4947139f6264eaa10cbb13fa942a26337c2f6201dbbc559a49763ffb9359863f70145edc1343ee3d8bfb897da25e5e40d9a1a9c9c5a5d6d9e9c5ab7babbb36363a630588d79c34c47ece4e89827a763a6c9e948e674cc40646ba85e260dec6bf7a43ab2f599da67f940d7b7bea6a1c2a541d92251d2c489ed81bbb532875d4c99694523db56bdbf57df486266091358261c0e574503a1702d732850a639319b53bec20666fc35aa4ceb131ecb70a81d193248caa44caccf2d4a1f62536c8f9a3b29e3af81c92a736cd067576153b6070bc7f25ccbb186ae7382b2dbdbc419fbbc7eaf2ee5292cc4b67d57c8ce3596962b28ad0f51f9e8b5c582f844e5e313d1538e7334aacc31e5a29932a9904b58b89cf2f60268c112d599068cf2e2dad667fd9a188dd2b68aa45a44541637e9e89ef6eae988ad4e47d7f0ce4c75901e9a692ca7a7a7cc3427a7678f660ac4326b0e19d341fe5629333d7945f0d3ed69efe11cb570cbc43bb72d22f51afd8fb707e7c83c72390bcacd6576ebc8e532ebedc1bd142fadbcb686d85bcb3c6f4fb5dd9eeaacc7391a7de52b4c3ece91ca57646a97e2853e0cfc5b7fbc3dd5f1eed6ee3896250ce60eb43ee0ade1200c76f179817d8158879b7044b06b1a7381ddcbfa50c0d7714c08dd86260e0c4ac6685820c752dc946fae229454cb1c11cca9142e573f6f506ed573dce615b97a9b2bbaf2b4ddaa542a15e92ad21c552a9563950d5b40542be0ac72f2719fa2ca45958ae56257556589a2e815d77c5b405462cb84d1ad29a68ca828911ce93365230250351832805c529dc998ad3f0e0ad545ef7c73d599526daed1d46d52905b8335b9b5d1432be3589630988b293b1a298de5082b1d061b616118866118e4f6843854947629741b1128796112ab1c159c1350350cc3b0d61499baa99bbaa97b43af2112244a1adfa0159354b282eda5cf2e7db60dda7eea13960228ccc58f8b9dea94f8e4b402c355cb348ca42f2f484d19230c23875e9e2087a1c342a7285d44d992d3d2f107eecfb5d763679f279a5a811411cc20df93835c9d722ec82e06e8c8d5498a13b9c4e24669a00354722a9565b93a2d4183bccad5298a9517ccaac864ae4e51a404595ccda0e5ea94f365961ac09e4005ae747127c003408012012ec658238c249a00230603d8820058b260210235b8a49060d6a69248adbc9466a08006b983dda30a7e4f57c0584e943837879e7aac6bb5dd5e47e38b961865eaf9fae7dc5003b832bee46bcb00938b5c035c2f16d8e8ed31d6fb3c3f505abfa911744ddb1bb75ff8d284f83713d65a3edd6ba3bcf7e29b04a45caa5aeb2557f836f559318b648daaea237e3b96e5bdf7de7b43952b358a39aef05166ac6299b201cbe5704f585f856fb4f0db735f636d358a61786f8d93bb5c9db41292b95c5d286cca35f4d99b2da5751b57d94113422babba71c188c5948d0a84a9d0c4f78a240ceebdf88cc62859ab98d27ac91aedbd2498f2de7b49720c160e638c31167718cfbc6ed45c737495385645824fe58d8d99970f1f3e523625f5579b94b0209532719f2973dcbd70d8d0cadce018717af5e2e484611158ccc98131b6b704f79640153ee157a9523d9d91755d2d20371c3170c481e337c71d58eabc4da55256c913a5dd796c43fbf6ee4813ef6e8d498b3c2565a0807b1d0c9f6a403e0be76790bf7befbd77e52a676868c8960c6c1534062e58bd75a29e611465a84cabab3ea1133168bdc5d51a0671b8d14c2b540e79b67cdd62afb5571da260668db162151663157239c10b2e0bd732b5a5e52a2b165188aa2cce085555520850d930a3052b5f988932706ae9aa21075c9955827003754c952862152b545cb800038b4b63026c4ce1c0145f0efd05fbb991abd394054ce1a5c8b0810d6c3066cc98528a0d5c0e8372d8d4a359ebd9e6e964d5a68e3e4755882bd3f33461ccbc34bc92c330d0c8e1888bba6103824b64f0c6d88591435d08701cc8e1d8231380440e658e48852193fa1ad580304b64f0c6d885a1bb21c8b5592ee9f044dcadb938507b420f412e0cdd183b3272e80a93c3d015d5801c8629d375d38132fdf909bd7f2687a9b2fad83449ee26838c826c3f820839a2a48897d02f6e20a70e1b3895029a6c49b97a4e15aaa2bd67901be4870fbbad838862901f365f90b6fdd0d1a70d4af42f4aeba4885329710c57262992a2e8244992e28a7472458aae1a45921455a328ae2c069a208c09ca0043822e60c6a8f54b69fdd65a6bdfbe32b8bd6098317016478c321dab9956171d578c2bae18638c63eed54f8fe16a16e9133f95d5d3fac3b87d06506a946309f3d809c2602d5f8235a8faf48c1c410203a8348237286bb5d7cb92359630180c56b2c6716495232b0527be1a80c1ed623dd6a77504a4fc9ab6db735d489fd619d02b030cf6e204612c4eafbcf75a7bafb5f7760cba6f9300c605174a76c773b57856c9ee78248b6795ec8e773e59d9b410cb2616d411eb090a474ce82c275d8995ab804651e5ca53aec41c77b6dbd364aeced33d07a6c3c771e73930182cc7af4dc172bcd8a53243f948ffa224730d7b22cbe2c66fd2e71dcd34342dce74d204f7736bf6769f90be92f196f9440bd135439f77b4e1a5ac7dba5cc64324255ace7225485f15719d51d69693aa26a3eb5947a4b932836ecfcfedb9fed3e43c63eee4fcf9f9f93963ee1e3b3d66712888aabe9c1f58167bcc224ce17e295b866eab88c3711ccbd1533ffa1cf188473c621fcd170ebd7cc1ac984a85183b06c2db45aad0ca7362b906d530648c31fa6008ba2bd5fb80e448c97a05c1721015687ca192a30469809922c519154afc20df4fd0f91396b0d12ac9181bdd9aaaf68461183a6ea7b37db9c03d2918631ca652a9157087a9542ac4b87c0589c2d5c00bf862862a8d6097b26d9b691886360c2dec5fd6a2d5adc3ea7d59afad1ba44f1b46596d5b31c450c39620c8edb941aedf9f12e41bf65ad9494f99e9e3b4b7c65ac369961fa958a3b74c272d1fdd092b65a6a179c6585fefb74a68e5d07496d7db93953bd182c8f5528615dc6de5a411e92cd41b79cd51f538a143c961084aeca98b4551bcf8e2d4bdf75e7befc5d7de8b4da33e4d505a27414e7ffc8cb0c5198ca00b5e06aa3c5160034927dc7befbdf75a6bedbdf7a66e78efb5256e1c61b09fd75bca409cc2ee4e2047af31d2574aa598aafeea53e5d584a954de43f5b2cf9b56579925a8dcb650e8ac325f7ddedcdf72b3b662ef4569bdaa11c6afd65a6bad1593608c0e066072f2922b4b69bd9429ab2ed5b081240a60c118638c31c621c618e3fe9918638c31c64d9011e6013f8221bb208ba3899465bd2ec9c2f28267a3beca1cb01b652b7798db84bd6c64982dfd359ad5d61577d118c0350c43af6628a6be866138a4fad45a6bbdb7bd586f967dd8161c6a4e4b5b6badb5d65a6badf58888ebafd66aabadb66211dc5797f671156b24e7553221810176e9d2852657279d09cc6841981601c0dc55016c464d9834092a69d28449b089bb7b13264a824a9834c1570993f3e7c58b97199cfcace9390e83e554161e88c238552bb64f60e49488edd17edd8727dfd36b8a19423fd85c7daee364e086f88a18df54197ded4dd9da6fe869b71b4182dd5a6bddadb516c91fb9b75557351a397245ece4e424440dd716609aacd55bbdb0d75ba06c122a11e6ca16ec5e2c00e8850282edb831c618638c31c618a77088316e5be2bc7f468c7718370135ca404482acb1b4c4f94059fd290084acb12e65325036f60ebdbd25938152343a2a52feaa5010cba8c86b296d903eb139f679555e6f2951a512631d6395ca578f940bbaa9ccaf3df8039f9e8cdd87e8a3fa600f4dd8cbc3c62998f9e5b089910a8caa0f763ba66bb56d048355682663b7621a8d4a1ba96dd04689e9d37613278d8a2c6fa9f255a9c6d2ad93a2934e4466e532261197975e9a4f4877992e57cb5de79157d6ca9fd825d276d0066d986b599eda251c312d1ceb472b6ff9118963cd212b6f391152b646d9348e65d9d4d4d4649744f081d0ebc9172fdd5ae9ae2e5fe66beb9db4cc57befe43995b37fbd5a80a354e3b940c549df4d9a44fdbb5b92623878d70b294293308a8cf09fcccfc5831a8e3e7549fea29f3966573418e8dda2baacfce4ad5f56985ae55aa3e56c6e2cf35d43896b06aa6f7b6694443e91693e8305c22f063ebb5b5836c88397d5ed528f61117fdb59447325e942a13d6a7357fb4dce5d8c597a9318e76b4a6ad2be9506ea2b5eec4e52cd19a2d6f12474cc9f2272e27fd49cb59de8ebd73bfbda15ad348f5c1a4bbfc09cb5bde9e5628276dcb744284e52d17c62dab547ddaba9c880ccb5dfe84749976222e96bb4c8b23492b98145285da0ae195d81f02a5d8631a14407272028b700b1e0e55323544dc273ec258e5e3885558e5a10a6315566115765591238cb117c1a1d111c6f577843176d28100e31087e10505055521d1ab9f5d5379fdc9676ccc195f212ca77cb160a842d8d6ca01116a1b851ed62ea05cc0c7d18a9fafdb4f6dc8f5f48c7570d1a520900b13fe606518863698086b541c45334d39acbc8eef2bfcf1e3672c0006ab9ef2b0a9690441841e107a72fa37b52784559fd043d7c1a5e2df7d4118a725daa9892dc024913fb512563d7596b7272ca1eaef3c6fcaf4db13fa98c3f22c61391ef31124fc098172787185c262739e721b45c9f78aa2e8570c45b1bb8dba8d8ebad508a5d80bc6053da850893036c8a57657f3089294913edb56effa484628b7edb522396264ec513472a4a19c9c9c845c267e501f2a128c451aac46bbcad4284520e3684308f017867f050cb2c6b29996d8acd5275592310dbc9437b082acb15b594f22e24d347f7072bb38f7e2300cc3300cc3300c2feed4db5bed2d175f0ffdfe5896a55892e2980aef38de9b0a6f1da176dddddd6dfb8cb00569f0b8e82ad6085676b710403a6cccd86fa842a363c7a10aa97cc5eaf36215ce825b24b432acfa604f61f956fb0cbde6da0a4d23d2acb707a70065ffd8c69479d43db41ba5d6b6ca5a6b57ddab6b7484473308d08f3e9d9c9c84c0d098756c73d27911865aea179d44fefcfc5421eb403a60b1577d758735880364e4b0d631ca574e5f20d41a0c45aa4fe837f4a14380b2dd868eb3f6ee302c28284844621ee9133f14a5682ec1e71964755aaa10cc6639838282023d3c124f9f0767f2ae9b3b9397b9d172a268668e181e1e28094807c407e1b3bfb9e7677efe0edec1f738077fbacf77e03a9e03ffe07ddc83d7e120fc07fef31ef88e07c143f81fe7f13b1ce843f0daf370f4815c84af79d0a3dee34570a10ff29defe13e5ec8877ec747781f6efb2127fa11fcf636cf4fe43ffee6459f9d84ffe140bec8833c095ec203f1133e889bf025b8903fc15178135c8517e2293c0a6ef42af80a9f82b3f0463ee457f0a367c15bf8214ee48fdc856fc18b3c117fe15df018be88c3f02ff80c1f83cbf030780d3f83d3f032b891afc16d781a1c8737e237bc0d7ee471f01cfe0697fd11d7e173f0999779f93af8ea679cfcd2ed15207c008ee357ce7ad22defed157fbde9e1bfc31e87df3ccb6d166f795ef330a7f91bb758bccdc26dbec66f3c8d27798b85f3f0368ee46f38934fe2b4d7c903782d4207e2b57839cd6b0b480db0cf46e2344f7744e5f61d518da63941dad78041f23541a43227786b03f0f69ca85b63e2ed3970b70684b7e7c8357a33d012011e284bf70065a919e20d2d0d310df5867c434d43bfa1a721a821e05070288a08479423d211ed8878444b444c443d221f5113d18fe889088a084814248ab2e16c399bceb6b3f16c4b36265bcfe6b335d97eb6271b940d680bdaa232eed6ec955ca36152a32f5da36fa55173d7684ba9511bbc465d581a95596a94274ba331a6466db4347aa3d768cd9646697c8dc2b8347ad3d4280ea74659bf46575d1a259f1a9df1d26809d5a8ec4ba33a001b3d02a6d11c828de220a6d11bbc3d47356ac4dbf398466dc0355a0352a334e41a9d21a95119748dc660a55118768d16516af4055ea344b034eac252a347591a6d81a95116b4343aa4d7a8d1964657f035aa02974653686a548853a328fc1a3da14ba3263c351ac44ba32540354ac2974681001bfd01a6d1a260a337318d666fbf45356af3f6db984689708d0e797b1152a323e41add3197ec389331d3921af5a16bb487954685768d8aa0d46810afd11a9646d1a5467964691488a9d11d5a1a0da1d728085b1afdf135fa0197463d686ad4c7a9511dbf463be8d228074f8df6787b9197464f6f2f826a74662ef9c1ae91dd998c1900de5ef4a5d11d7a355b1a9d797b8daf6740f86c36009f31f1d96ce6b8056466f6708e967089c0afe9b6c4a1dc04f0493c89e9a492d105b963fa8e933b3c6d0c4d0c2d48034303d2bed0a0685e684fb42eb41fcd89d644e342f3d1b6d07a342d34265a16da120d0b4d89b6a359a1e96849b41c0d89869b8d9945cdc4cc82333033e0eccb0c6ae665f634eb32fbcd9c664d332e33df6ccbac37d332639a65992dcdb0cc7833a5d96e6665a69b25cd7233a4194e36268831cf1c001fc498dda405f143bcec2543e9f0f9cb91cfc01c3e83c1e173f086cf628c7c8eb2e1f3981afe86a3e16f4833fc2d27c3df62f81b0c7f2bf2b717fe46e46f2efcede86f2dfc8d85bf0df99bd1df56f89b0a7f4be16f42fe86c2df4ef89b097f0bf2b7a712fee68584bf4101f9db971f7f0316fd0dcced6fc1fc3731b6bf4511fd6dccd017e146f822241f5f94d4e38b74425f6445842fda057d51ed8bd02fe2f145405fb4e38b42f82210bee8e78b3ef8220fbec8e78b747c51075fc4c117f57cd1d31779791d2f92523fe899099403cadd5a10b72684b703ed6ead89b703f16e6d086f075aba3522bc1d88e9d68af076a0dead39f17620dfad3df176a0a65b33c2db817eb706c5db819e6ecde2bc1d08ead6ec11de0e04bc358b84b703056fcd26e1ed4051b76691bc9d07eed6ac12dece23776b368ab7f3d0dd9a5dc2db79ec6ecde6bc9d07efd6ac146fe7b1746b768ab7f360ba354bc5db79f46ecd26793b0fdfad5926bc9d47d3add92adecee3776bb6096fe7f1746b56e7ed3ca06ecd3ae1ed3c80b7669ff0761ec15bb350783b8fa85bb356bc1dc5dd9a8dc2dbd1dcad5929bc1dd5dd9a9dc2dbd1ddadd99db7a3bc5bb354783bba746bb60a6f47996ecd5ae1ed68efd6ac92b7a3be5bb357783bda746b49bc1dfddd9ac5c2dbd1a75bb359783b0a756b96e7ed28f0d6ec156f4783a81834aad1f776744ca3a6b7d77035a4466d787b2d574baae91ae5f1f69a954663de5edb356ae3ed35a51aaf86a5511a6faf2dd5b2347ae3ed35a64671787b4d4ba32c6fafd5b6d47c352ea4bfcc6dab849999490be2cd9ee9339bcc9ff964429940336846f1e078723c3a9e1d0f8f67898789a7c7e3e369e2f9f13cf140f10079823c51315c2c17d3c576315e6c29c614ebc57cb1a6d82ff614838a0163c158940c27cbc974b747b693f1fac4225b92659131c9b4c87ab22d329f8c8bac49e624fbc9bac89e645e6450b22f32a00c8c2cf83231b2a83e3b7bf9fcf4b94bfe7d76fadc94b964dfe72db997b57c66fa9ca5cf769ecf4b19cb675e56cabb3edbcdcf563eeb5e9f9372eeca67dedbb2781b166f4bf236a82bdef6a4f4b69f156f6baae26d3e2aded6dbbd8d698ab72d49f1365e146fdb59799b0e8ab7e59e781bce89278ad23d51b089270256792228269ee829e9897e549ea869ca13f9a43c512ff7444c4b3cd1529427e229f1443ba427d225f14439249e0877c40f45e17e08ca0f19f143504f7ec8c90f15f14344fc90eff6b40ff1434d5e881f0ae28778d22853b3278baa3e4de30de073987c0e109fb3f339c03e7bc927719d4fab1754727b8e4fab1763727bcea7954b2eb7e37c5abfb8b2e493384e708b323d6d72a752a3a3f78925372f9f4aa612e5cde9695ba29324e962856f9bd8c28cdf786b4bcae00d0c7683a375032b832b160e7269c917016f0fb95496555aadd50ce9b241ceccb86c94385679c65f4ecaecfa6c9b249b9c658531fce049bcb66250e20230718e0450c1a072134092c7394ae24b7c89e9c44e71023376b7240ee5e6b50564c7aecf33f8f233d828cc7382375e5b40ccde0d1c379e86fe32772885bf8377c3c31bde43e83b767db68dbfdcc67178fa7ad9f8cb6dbc87d70e5ea32f9a33f862bdcee02b587bda5f664ae3354ed2fc8e9adfa1b463b76317fa0e1e0d901ad78edd0ea54e6f98a68d9983e3736ededcf201b397cd5e8eb9a5d174c7cee4d2685a03347ba6afd1550db0d19b5940b9a0600c57de9cc28a80b0a2dc03cad426c7cb373ced1e354df683f696dbf01a4d3191db6db0d8e46c926cc66cc36bd796b80daf69b23d8678f503ddad2d318172376a9a72db00e574b9fdf52fb7b121439bf0f5404934c13e3bb4f1a6f1d4c6c6c653a09c0d90ae511b33f55ce39d96d9c6c66bad0df16a4f8ddf70b2e6816e3cd0eb81727df68d1a2037582d2150f2cb49cb026a074a7a9d51347f8a01cae576a0a446539b9c954653b3079403d235eaf2367b8dce7897335f736a6325b7979f8eed2eb397da1820983bddb1cb9dd6007bc9d7db0f8f5bdd86943c6e75830df158a91c26f97a13c0d71b8e044a6f6c0ef1867820e001c587b658a6c029595da32bb7a45bab72ebf68a2a74ddb6dc9a32354a8bcb3d026f6d6ff1def3765fd35e81850aa8a8e7152b20df4641455f44c17445141428634c180fa840ca4a1a52d032878404e6fdd4fa8593191b806241165540008a1fd68041174026a8704a7a220d8c6774655abeeebd1ab897878c08d4608b0948a0498a1319bb633f31bedd8da5fe68ec41fd330116f86a708135bc888222a5bb89a20a0f88914b23091a344102d54c5299c26048e46b5b8b2456556cd08b8231c642f6c6a9ca964b8024d2acb756f2d65b537e6d76335565733455ae5d99f236531f5e59347142af39c8b4d671c66cd790a2d1284755ae3756958e1e8892444e7f526194aa245d5e0e435816fd9a3efa0cbd6bd569420bfcfd9c1e04fb29ef398e375894d83bd8a8988e0ac995ca3be8a3cfd0fdb680a4ba6139e53e562928836ce10d4f61ae32bb5c66fa32ad973e43b74f7d86ad97cf98e938e332665a6619bfa1e4f684bd747b425faa2d973b51a656a7b2a2b4409d929f9ff37487c1ca321c5f663ad298693963ca9837574aec5db2e8ac20428c1a2d53d64337d2a80d0f1d898d7779f9aeb7f529ba8cf835a75ca6e8a9ed126b89662b4bd9c10eb6a90a43e732015f2c8759bcbc961863fc079baba9aa3e5d6aadb59aacea73a5283b1b99b78bb556064c80c932b93a250573b531d6da33fe4624c8b1d1768c834da10870980470c0895a137f4dba357c515b041d004c00d415f1898adc2883462802570812e0faed9ad6e822ca01405d4e264a57b1f8bb351715b527f42b4f390122940344e01662b08bdc24aea1cbed7415578cb1c5f6a70659636905012e2f1180d7eb646dbdccf05332e7006548f345990ef186b0344a439a3ddcf09ac97a46152cc0d97cc34c6dbef13ac328d3215e1154158279cfa09eed2baf15cd09de7851ae0ad9380ecf505528f43358e339c1ee226706a177080b7d5f73e338a0aa4f3ac4b3f1e5d0a6c6c604e6d04c5f4ee3b67b33cb4cc3dbc5cda7988c9c0ef1522298b20f3d846e736d01c94b7a7ce0013e9a34ca748847e343bc46817263ae2d1a1328e90037325012506e0365388a294ff24b7c881725a6834f6deec06f4d548d9825ded6ba7f51f616a42e824aa70f3ba27a98793ae61e80488245163c7b4588264310518493274640c11d814412484a44592227650a952426aa34a173e20928ac4421c5143b2aaab042e98a206c6ed4d0c06e70b056e44cd9b2e192e189e5d881e63b3a1cc901871b8cd850030d33c810030c455e20e2c2510b2c0c315a41851484a0708209414a2001c88fa25be6c1463434828f1e422204d5501e403b4200e1e7030f7c74e87060e69cba1dbb5bdb91b42329b7cfcc1bff6ef333a7f91b5ef3367ef3340efb1a67fd0df92c5f3d0e2f576ee34b6ffdcc0e1ff31ccfe3fe3b38ed73b80eefbef334cfe175f023bfe3377c0e8ec31f711bfe0637f238380d6f83d7f0465c86a7c167f81a1c8697c163f819fc8587c18b7c0ceec2bfe044be88b7f02ef8d113f121df82b3f047bec20f71a367c153f8155c853772143e0517f22ab8098f829ff042bc8437c183fc090ee44b70123e88173d10fff12478fe22bffd0fb77d761efee6436f73a2e7c17dfc908ff0442ef43ebcc78fe0412fe4227c0f473fc86b2f82033dea3cbee6213c90ef781efef3213808bfc33df81fffe041701def81fbfc07aef33abce77ddc7c24cec1f7f819809c005856104c7c006eaff820fcf5efe1cfae50b2a20a2adc4e21451456a0786b050ab74ee89aa8c24412952952724b4451022909248ec0bd3dc2a1bcc5b9110fc59fbc11eee49f7811efc489f8227c8827c29bfc102ec437717be585d07924407c12e7e06f3e99bc162f3fcf20077eeed89d62ce28f3c425b158d82c2ccf5e11a2c9104414e1e4891150704720910492125196c84999422589892a4de89c78020a2b514831c58e0a5b85154a5704317b9b1b3534b01b1cac153953b66cb86478623976a0f98e0e4772c0e1062336d440c30c32c40043911788b870d4020b438c5650210521289c604290124800f2a3e89679b0110d8de0a387900841359407d08e1040f8f9c0031f1d3d27ee443a73a6699aa669e27bbda072ee3c00548157853ecbc866d9293220821e18528440b997877e06756094a9d91b0013201abd8d72904fa7f11ae5c15ce24cc6cc83b7d3a21aedd1b9b5246693f9339f4c28136806cd281e1c4f8e47c7b3e3e1f12cf130f1f4787c3c4d3c3f9e271e281e204f90272a868be562bad82ec68b2dc57a315fac29f68b3dc5a062c058301625c3c972329d6c27e3c9b0c8966459644c322db29e6c8bcc27e3226b9239c97eb22eb22799171994ec8b0c2803230bcac4c8a21acde1edb231335ca3346f9f21cd72b3a4996e6665b69b29cd78332cb3a5599619d34ccbac37db32f3cdb8cc9a664eb3dfaccbec69e6650635fb3203cec0cc823331b3a8d9181a8e8644cbd192683a9a15da8ea644c3425ba265a131d1b4d07ab42d341f8d0bad89e644fbd1bad09e685e6850b42f34200d0c2d4813431bd328126f27737c8f2379538b99c55c32b1983c53c9dc99564c9d9964e64c2413f7633eeac57cd0c73f9807fe97877a2fdfe57feff44dcfe57dbfe57bafe5993ecb2f3d96e7bdd2efdecaeb3ee9738ff4381fe3512ec6830ec681fec5a1dc8b3f7917ffb993373917f7f916efb9963e5bc734998218b3e9a465e97c8f27f921deef090a189cbdcd8d1a1ad80d0ed68a9c295b365c323cb11c3bd07c47872339e07083111b6aa06106196280a1c80b445c386a818521462ba8908210144e302148092400f95174cb36a2a1117cf4101221a886f200da1102083f1f78e0a383071c3389c5c2666179f68a104d8620a208274f8c80823b028924909488b2444eca142a494c546942e7c413505889428a297654d82aac50ba2288d9dbdca8a181dde060adc899b265c325c3439ae63ff59913cc89ca81cb91cba1cbb1cbc1cbb1948329472f872f47538e5f8ea71c503980398239a274703a391d9dce4e87a7b3a4c3a4d3d3f1e934e9fc749e74a074803a419da81ddc4e6e47b7b3dbe1ed2ced30edf4767c3b4d3bbf9da71d2f3b503b5f76803b6076823b6276a276c638ce913ce749ae737777f72c7dc678645c365ae50cb962e1b881d1d4dcb0f959c6450581504fbf265f8f698967dbd974b69c0d171504423dfd9a7c3da6251ed18e48479423c2451541c4104d8418f20df5869892e4047bde99fee9fc1c600e981aa08de50181354db76679e610cf6661d6db23f374b45898f5f6d434253187b6984a941d78075f338eec7bf0211d7fcad193fc120fbf33afcfd38aaecff67014439c25de1f60804657a6433c1b8649cc138eabc0ab4234cff1dab51ae07b3ae61c40304992bb357b257cf180c4e6460d0dec06076b45ce94321d8ee480c30d466ca881861964880186222f1071e1a805168618ada0420a425038c18420259000e447d12ddb888646d8b1b253e26159cac2a4a5b7c5c7a5c9e9d7e5c90bd41760119846dddb8b82b76609e0ed45518df6e0bd63b7dae5f40cd634754f7bbf998e3bb8101f843779217c886fe244fc105ec413e14ebe087ff24edc887fe250de08b7b887e2f688b738b748bc3dc26d126f91708bf43609b74abc45721be5ad126e97781bc56deeed126ea5bccdb99df2568a5b2a6fa7b84d7a4bc52d136f93dc5679cb84db26de5671ab7bdb845b27deeadc3ef1d609b750bc7dc2ad95b750b88de2ad15b752bc8dc2ed146fa570bb7b3b855b2adeeedc56f1960ab7f1a6b7fedd5af1b60ad7b7dc2abdb5c2632fe33cef727bc55b25e7e1791cc9c79cc9db2b9cf69e2d011e8909c9e4756ea4653179d68d6c16936fddc8cb0aa115a8e4cbd76eb292a1191100000000080013160000200c0a060362c16096c699aaa70714800e6da8806e529389b32047511ca5942106184008000110111821216c1b1a803f1f71523636c5638eb3c254e3d509246039a0150af09b586dc8429d9fdc4a1a440ac1ca0f65c4f12e76a2069592c1002b0ac89234c68d7871a89ef769a028cca74c4b7a0626e10f2deb3534c49603d0f2cdefbc316e8c48b18b84e05a58e7791eee6885558cbfd97ee3b6882631eae4c1b056db3b35a5bf62280d7e3f90e30f80b77b19d61929d8247fd5950fa5b10cbf6c68a7b6e16d41585b936275bea17b5089a935577f33456e537b56a8aea558c32818aaf86063b01ea545edc384f44631dece12823661e3cfd0d0a6d411c50d00af0ba9f2f80948529a394974eab20a15d6ed22d6a76b95e8946b2b9dad8c6a55085238fd001b913be6896f759265614e179b3b60a4e3e792580f8995e57879218c6b4c5e5189b51abc6973bbc640b4692a477974e91055e5b18c6a539b86b605b5159ae56d92852a13deb7b93739fc22f6c7eeb7017af3b949223196143b7a6c47a458e7c24a174a3903e27b21a154aca38010c925cc22d42aeab4c67f13657e2272bd5aa64886d4407c1846917a6cb9e495d4a87c163fca35ec1093d4134ea5a2b3c0ebeb72e7a0a451ae4468f3836210602e50811c320b60acb919a2972111784a1f07c5993902c251a5277e369af89ea1a997197339ca4fcc42062b3617ba03b63f00f06d5a61afac17e45b4ee1bc981c4977da51ebd8452225466019b37debe39dc04bb120bae05078f3fdedb88de945b8316a2f2db903633ec625a9604ea8dd913fab1f48ba4b9b8066375121daa3e9df81615a3015f9a2b2238103d5b8d49af7f86c32cd65a02493d29237692ab992b8fbd23c487a5d8f8e575c9761d3bf01b1da207bdac1e3a2dc7cfa3752ea325511f58884feccf918fbe78d02e6843e173dbe8395f7da81a57b0f41036f419ce33574af0247faca293e0ea385914af8306704f952ed24cbf3cd751de03434be1c7c778413807301f813e1d1a5e6ccf9382c12f3e21f8a43ded4cdf2b4f28cccecdf736e0dce1483ad2545506027e10a59f9f347278779b0a24793fada8d365b1b5217740964f1298a8986dd66e36bb9c5e88f80ab1f6741092e1cac134e0ffe2e4c0b57864c0006cc509dbbebab9c87fa148a8b234759e9a0fd816bc8d2f78563fb8b8d8da76af380609d2a251f5e57813daa0ca88afc4967ec0ce26d055b6c4ae5184fe3ad727e574ac84783b629118e53616d29ccd39490b5c25438d4215c092f2a8a8c52a981f097f0ff887845e98a14c2d7f7610479ec96ff5d75891972a9e8d25ee8fa07edf03cea3dee2067982ee029fe927572a0af9e7b6bdb5d921e6380373c1385cf77ff5fc2cf2e371d3f26225e04e813021f7e5e7c35f51619ddffb677590de44095403417b279af76603b8d90f0255d26adbc90e793e155b383e62550507c47c3a68f8bffd009505084f97f18c6682b6c425724428543b74626f0a2bc4bfd6d27e8b73c2665a66a19c3808328d33bd2dcf9dfa2530828d7982f1fa6474882f65e59cb060894cd04665f81c7aeac73862221031c55c2c1ae5d386d2e73f5d8157e628475806637e8029bc59ca6474037439943038bfb74103d4dbd579053ac308aa873601f2e84034765a3b48df4f6d5fbc1a9d2c63fabcc3c9ba5ded2ac6aea490a6a1e19d562e1a064047f95edf6af276c59d3733f3997be490051babf8a61dde77b79c0bad50ea5db19c95fc8d182ef2a4e0a7347332154030d02567ec367a7631cc10a243d4e86a8c1102703bc0d56f7883851804d9aa8f977c8c05500116ef8231e159e473910215f2c04b7ab1be00759e643816dbf7e13d410052333836982b837299f185c7b48f58a013304b0edb569ff64285ca36525770456cafe3575bdf38964dbe2ed4d04d7366d27f4895f05a760998ef359e2531eed144c6ca0ebded2af203b459aa4583344288e111f74cbe0ada9f72d83bac192d07cd3824cb9591538c03922b46d9c8979882866eb1278eb7045dffe8fc4d70874d2c881d05e61ed25940109c7c53db422532446b94211cab497cc0ce0a311294b1a39be78846056bfe962328e8ba09b6213f8fd018e5e1ab422da43f8e626439531b028b6bd8458ee6dbce98b76bf4385ca15102be3282bf418e77ac48cca697f9f538dd837631ac733c1a8662ac14a79f1c37e32c05000243838c68386ff61fcc133548cb4404d626c01bb7f15e1f352fe236892964fc78a5a9de65acd24b9282c3e41d1fb4563c3d6e9a5190ac62fb00caec6e16db12c9ce22640f2d2cbad1dab265c6715681664d6156ce1bede6d73cea840af3405c2f066d0f4e6c8e6b7b831ed286b46b14fbe82ebc8bf4f1302c5c32985322f7f5dcc635effb9edf3bbdfb5b8d7335f67ed46d7799bd1364f6f998a650dcbbff465bf873b94ef7ed7fbaefe7c9de71bcef7f8d6513b662d7b3f34d821eb602fe618ab0960a88edad3fccda8f79da9a8d2aba6787cc2d0bd17f130ee11512504f6217e380d0a022c8ba89264209c4d1247d47e81562df8588528a540e1cf275093096e93105d3402ebb73da05507eebd418c34a0600c9cba20a22ba02d0a9c9620b622a0ee07fce440bc19a0d502fe9d40b410a09fb50df0c02a227805e52216ee10641141330bc3384e5b9ff9e3fe9a9a8717fc8ffac0958baec0a969af4bf5912730be458bdd4433726860d52d3ae3164b6454a0e5b6d09f5b74748b7bbd852eab93d1e7342a6c965ad47572c86caad8ab45d0fc214d6d2a93b768e26fb1dd5642c76e5cdb786d4d1a9ee928f5b33ffd3cca235ffff9e6f3bdef75acd739afe7d646ebb99da936a7af9c4629779482a55f7ee5e7c33eec74dfd73faffde4e742ff39bd44cd0b0b2f800cdf4281749817a9a4f340cf8bb6dade1cdb9b29ff9fc03280ca6cd59afc532d0457806b805a17b0f48c45843fa956e2790a0a6b4c622abb86b5b92b2f8efd7ae6ba98e70150cf1da021dd1bb22740cde263415097bda05cbd15ad833f41eb271a6d1b1355c483d891b02d4ac90b5a93126fa8947b4115a8b01634f1971c1885866e3a6d565b8ed4924fb84aa5c54a19bfe19b95c3923ef1845aea162b61ea896eae36adf0f5f67ba92283ecc282903c40cc5c7e8ca5a52a79a3f52b978352fe81acaaec16aae00bd9ae9b2f958917bc51c725f36de48213605539dfa9a74744abfa921d7b2873718c09c54930ec038280e01765f486c7ae47c80112b738519b0db37e2106045e91406b28565d8514b740a1e43e462984909af8d089ceddb08908990170e9062a80621255880c87476f68000a8b7890981d877ea0002c069185c060f8b3863e5fd833419e79dc39a0ce29e6bc429c19deaca1cd14d65c90661e671e28f38a31bf1066872f73e832c2961564f9c79501aacc62ca15a29ce1c9dbd12497db481a0d01688a510aecb1f05aebad939a250b12f900a298e1105530e4100ac568d3ca2e1dd024711e554145ca61888181c2a60ff5e279009d15be39056b24345317c4286717066409432a7480f2a04b7670e49c0a11c4471de65037351884110658d0f1c4c38cd8a18773d120c80a7510419d3260489d129163b904a657bc4ce4c3aefe7cfde79bcff7b8d7b15ee7b69ed6b28dd776a6da998e721a955feee7be83799cef3eb7fb5ef77b2df37ace6facddd11b6fb3c82f2dceb59b6a6d61b17635ab0d9d3b6225a2dabcab8d88b547b5b6b31861d714e51537ff7c2705bd5684cc4e8f5c7d9a6b8f7f9d7a6b0b2a0e4f159a7ff973610f77bdefea7cd7ce891d3508d9e6be1bf55c0ef11fad232686a4dc25a2918ab6a24afb801a68981692f40f7ad3f46d196c52783b1694423817725f201ff3b31b6e959ea1b35779ab6d95093d87ea9f828eb50397918c2068a33493f6b2808881e2ed4430fba9358c8c1bb4f99a295096ad187c6e7f823a86138fda87ae5403ae6d49b35215e73496bdd9f7b0f996ed25f46acfd8576708b29e5d7f5e9eda3a4435174b691f45ab5e82d4896712d69d7bfc86a3b3eddb078449c5df24096a3b9a2ddb3fe98159526d7ba250811749209316b33bda8e675de2c1da0f94324fcdf19b65f4299461ec07f1b857ef26de880da297c0d6ac2129ee3220530523a28be68468779e6b3726b09d8acef524d330a0ab9c7c3595454a56a7434b806b8ee9f5431d36c3dc722ceb13aa2b17b9442c8f21e303ad1b5d9c6d0569c2257c69c6c3c25cd0d6f813cfe9a312ecc6a40886f0dde5955206a239ec255eed3db134bcc5c6c4fb9c900476a0b87e4c49aca22ee7a1949548ad779f302d61450045807f094bf08ed2ca593de7166eece885b6397990f42a2b5814e1e4cbb77878a887adeaf4dae7d597d958bea3cd9653e0b05ea18dcc94b0120a4725283fd98f5d0373879ff0eec630042ffc158864c5ccfc6241b6a9360d28813b52e58c82ba89db435141635dbeafaceda12f46649d4bcf428c9a51536145f056c191e4d18c7e9b965ff8ee90b73038e8b05d240da690c6efe09521e67cf11dea1df882d8d39d094b274260cec456d9e6d25b10084599cfe9e04fbc9a37821c10764991d8cdde4e23774d1d20616d5dc6656d220edaa8cb71274cc221b66a5c560a191042a79d67ae924746d7dac2ce1be1b599a822f1bcc05a813c941550b89aa91853f902119b816ae44aa2b0a0657151fc4a81400e45eb0ae4415680e2ca6c454cc91744d80da811578982158dc58af24b8ad0106463558cca6540fef07ffb95c84d8260c4cad05ad8923150f89a501d83720415115b95096452523558749896271875cd2c2b9e880042e617465f8644b0d3087b3546acc962841a061143e528198dd7921857148e476067f40189bc58a89b9db84ed00628699145d934c27a461798c88a84aad189ea044da092145914cd23a86ff4808b9c68a8199e980e68012a29f145c930423a47074864c4848ad989e88006a0921059144c23a0e3e8af455e2cd4f34f3c0bda99259d29ca59239c3fba1991cd0cd5dc89e680665e49668a62d608668e5e4ee432432d7f6279a0955f52d9a294394299d1c912997c2a2628fc3d54cd186bc242cc17d974bdced6ca649d902bf154d59229a511c5c84e2192897a293d04d246179d8e43820aaa317e54370f03ea84201c6ab079d0331b18735abd08a2a506aba89e280c32090148a839e4617e6c50c7e9d810648c1a68a15a51304022047ea06ec30b5cd8560907e3402613d4aa80f205e0a9df65f9e9ac5ead78ca6abf5a7a84958f2dbc53dd40971d61d5638bee543390922358f178c19dd40ba4dc08545bed4d562ff3b7a3a811f995998a98900f884c25ed6c8aa56a37abca410e723c7a8671abef34de7f745ad1fac8483d58901d80385a55282fd28218699235d7820759a4b8371ae329d9c7aa7eec5911adaeebea2cfa02ba3b9b6c08cc86f18d297d2e224a7ae15e260a42510457859be8e95f83bc57cac0343763d8e72668154b97e90dbd15270f15d99e4db68831ff1fc71d5c14e291c9a7461f5a80548968d261a60a7dd300200522a21ff9ea3f90fdb3c7bfa8f61f47ff2cf297fe7ea4edcfb77e21e9476ffe50c97f4efc59c07fbdfb49b7ff2ffb46ae8f9efaa64a1f1cf4bd38bffaf29f26dfdff18b14ffbdf04d05fe725e7de1adf50c35beca6f6d814d282551bdbb36b43fa7b47a5d1a59d7065a99a8cfa2750610650fc4bce956ff30066adb5826bf594266d8265b26a6ac5b3261c8ab65b10c7b0d89a3275b1462498aa3a4ea4990b1e71cf997c23c4c634cf2dca3584d2c041847dc2af85ee789cf05fe92d559f9ae339a5291f830d8b8a8c647d9feab0a7adb80a54ba7178c9d4330ae38d32c79c56f43c588162c51d05e8835831eaf42bf681d47f14ea81b7cb40af26adfe88f1f9560e550495fc72dffcce15e29add5ace0bffe9a6575638793fb2c4b301db2ba89425de5bc84d7621bdabe26db586dad4cb7b8fd8fd5316d8adfa63e0e99cd1f6d18f302239daa74b0a2a0344627f720963d076d1769be43b81c1139406d77e8e13f3d384434fd0a44d0b1b54c7f5a71acba586d7791a5f3af6c6eaddb70906747ba7d3756c55f964ae12f189b7ca247138baedd8330e2a196c66c819e2517b8c959435abed699d9f9746547ecbb15e6e8e6fc8c75ce3777b3a3655d21f30a93a4101fd51aff15749de5004028e61cbe29b34a438a9a5a6b72242595008b4661c05eace187e3b3948087b8c5ba5993f946615e92393c82437f1a3c97f913010f62be29bc30b53ff86130502e067790e6f7cf7d58bd44bc7afe0463d610c371d39a3d31938a9470c1f540aa483219a07b01f4fe4f5012313b260c9c15f2ddb3e49d58d2ac8a485b6e290e47482b6497635937662e4a1389f8104d241702cd6ea8167d84cc5485acfd81bf301402cca75a350a979a4c503255acce46ad06cc3851d6f311034c5325371a441e01f4c2fb06d178fefb34fe1a067f511474a0e626772adb9ed81f9655d801a598d948ed026fd537e470d0ea0c5ef933e0bca0d6a08d904f6def5059da947dc6e921f39ae6448df95c35d4fd411745275808fa609ad4164f68b3b769c391d90fa45baaa1e3ce454c7cb04c4979216751db668b45c7449d34b2b047dbea93d81d801347c0dcf6c231026a172f128c763d02f3ba51cc06352edcb85c27e44fd9f1931b0f24a6bad6cdb395cd42ca5eac0fb0fd5be22b3a918dd7b19f912776b265ab1ebc06eda53e3538a0f0aa25dd29cf50dadfafb985174dfcfc5abaf7b961c474e916a3587812998c7b1bc2a62c4b6f5f62819043a50674bbceef507bb7f0050a1456dc9c8c87094981e896a796aa3f7d5ea1ba36babe1d56941dd1a857d7a44f54dfae32ab5f0ca03896376655f82844d1c0e2c6942aa75fe3398569e0ec131c11f5e824f8e70f7440557835f601aeba0af9b4cc42f4f6f018e01bd3c70e9b3a0bc408140366fb49e54de0ebc23e82badc000dac6a514c77f5c41e8d5d254178f6685a1335aed7c334b195dc526fd3e760b413f7536638ed66ef8fb0561737055bbb58a4ff01b6569921a69cc4459ed06aa63f9166b432570352044f516f9dcf3acf64d9e62abcd0d9abf441811425656f5611845a0fecdf51574b74ba68c98d8034de17afe3d85d85a2730628661a9d9010d93c2f6c25d85f513dab4f56e8be75087876e87b02a1832f04e1fae808881cb229fc884ba134882053d93e9882644037bd61a583c2a0d0ccab8250bdcf54f24e63d254ed2ec3c548c330e081c2d24a3424646e8d45946466db105721f443baa6f34114fad8d5626a177dfb4597517985749aa2f92e4b5d5202f23d0e69448e9e9f65fdb43eb67090af77398b4bd00b65ddbfe19ffa968da02f886f8f33384d8a424bb261ca0298364a63c98d6f2e7f79331be520a183b049a992c3d64cdfd8597148af6f289b933a93a439c471bad7b7f9138dede16e6dc0a692a420f28a842df9752b1247c3afbc9f24c97b330fa05b0e7c30cacd4a4f9d44073f377af75a6c89fb271bc7c62caae731b485424300a977fca0d58fe490d62cef632d2c4daae52213718ec1e5e00f85da2e17c1072631bdde4bf023d53d046529df6830d4968b50323f8c553e71a3f7b01addedcac70fd7eda9a95e12e784b0023b8989c9c28f6c82d6805026cd0a956618cb915bc1016f37fc53a0b9a87e35423e173c58b875120578b064b7c13bdd9774353b4911ffe8b61ac4b6448e430d197cf60d5dd2538f49060a9cd8698e713271a0cf4b864876a1d68f38e54821050baaf0e64699d2909ab372b1d0daab470a17b57af224ada973b5a0d0da95b9f705a55ca81c086465d79fd0afbdf7511efd4b7b469add583f5325b3d60ef4c84c41e8b7a2c0240383f4574815089a78cf507134c4d6714604ce8e193c746348d413488a578b66cdbdad50cd10ebc40cded99922b7001449f31ac0754f09927477a53b659b0656816f84cb43edec6ae61bfcaf2244fc5bd625caa6add3462a490114fa129b28593bc75a1e4a9d9a943417b0688212bd630231de58324e3a78834d5c34883034dfdca4749245199f185bfb210a3903d11c9097750c94e40e4b1652488878fac377bc3045281dc7ffd8a6306ee70cd7213b63c7d33cc59259ddcac367fdc7c61301c9e11bdd9b6242b09a3b93c0f081428e91e6707659b1f1c47ca047700bb6e4716cf76ee85b49d7420781ea4603768a953ddf172530a80de6e99a819c843ac5b691a79133a26bab6d00c84847d8ad5354d01459e802f75faf18e00511c7792ed1fc3fef3fd8035dfb4e755c39ec09cd4a93e43addbe4a743c888a0de8da473f701e0c869eb17ae4af9d7792b9fbf20d1b1b66f6dcf3a371fd768515e573eb1dac37a5bc7b436f1429bbc0d0815dabd2de4fb5ac64a5123dfc0543f9da5e839e01906a0ddfd046f4b2adc869d6a275b6802d465b654dd21466c878c9ec8871045be7fb2e1ccbf8f4c7bab157a2972c006433065cce3d29669f2e103f982385f2e012b4630049ba947d0e88fc30f363b1325634aabf4dcc885674236fd28a56c02bb7016eb009a8f129b57159f53acac9e230c9a142f5ad18a51f026f2693953f516cd5d64e554979b8da78a815ddb171cb3a37e7c502357cfea27dbc9f43dadadd4aba99f75b11daf35be4382d3f2630380d301ce842c5b8358848e6931522da11e100f04aab701a86549ea1237097d24ae90b71cba7257bee98f8be1a6f19ac2e00888cc6a42bbd56786a12ccbc094c084975abe3f8902df54b0c8bca352fd4f4052b658fee1cdf07360d1781f9f5f22f9c8630999975cda64f331fa8eb134f53d21aa9ae8a72ce0bb896cb197388afbdd189a4cc32f7d16356b52146bc831b5301f052f519fc910e9fd7e20174137c0c5cb533e89bc874f45162b8481c971404f8fd02ad2429cbc3259064bff8b77f20a511d33920108c6ddf207ee3c648e2da44ff39b8d6117f0289df7863b2b876d308a177ab8884e5d9bb8396b80a5aff788ef268a7fb8b7a6849f0e2a61954a85657a0818ca3ea5ccdac22c3f02666552b464d01c9d6dcf62257a86445b80307db11416959cda0cd0cc1471cc16a42ac0dd3486610a95bd7db399b8ddfb3fe70c5a8e2ee6626c5b71c328a58d0762eaf1ba57e13d911322243ab3869f087a47c6cab1a876e6b5d11bc1f17b90301bd6c28830a9c67a3eb16df4f5d5cc0426bcf85d1e9d04da44a346931369173a4ceb5b1d0cfb4fc6d608fe401e0a80617fcad4ac091efea84f17593c7d79cdc5a6c766aa1c3d3ac59ac761a501af91b66098043eff92ba409e2c72b9dced4ce333cdc6320bc3721a49aa06415e56358d19b72099543f895101d7e0400595226d6cdf3bfffc1cf2a0b01d5f5bf5898ea64a0b7ac4902ab0b07b7ba3fa49ed9ca2e91fa817d9309e0d01e75634a7ff2262c5d76502d7bd9deb7e90351576a905ea96fc7b8ab524b19d7a82d28b97c0b890291f9a01cc47bf7f1c3e39e16b9f6524b37b1a71a9572c3e30954ab3d2399eafd5e7800f11c0ef74139254c967135c212e8906244ec9b3176052a34de94fd659e67fb42516ccd4f8aabd4629ccfda72f9596dc02bf9799802eafadeb86036e90b66f7731a3ef2bd4b131c0a28f00dfeffc091ccda22f44f694fba16bc0914259660ab3ed746a01c76a47d8640bfaae92142ddf0e38f799a5ddd6bd9f3a9b2fc54c3a81b7ed6e7ee6d47b5496a90fb6349564d52b6b8149f72f53f810875cf7392b8f90daa3587b2027ef82137ff44b4e530175066f8e2036478af358d55212026eecac7a6897c2dc0bbd48447c07e6d7e64b2e23d1bc69fc5f7e3e446327513ca6cce108acf05dc7c36152a36f722b1792d79061e5ea7c74c772c81ee04985539f40aa2874698fd0b06b853e35491b0a7fce3889f5d35cc654533a77405173862ab5163e79e0380bb6dc47d50c5c3973eba3c994e9bb216422c5ede1c6896d2b06f8133c959131cb39d412167f4ed9c2f39a34bbb028a72666c61f1630d557166827c705e8eddfbe1f303b0e6fe3215f30c28c00f7c7c6e01fc972f36cf2102cde16b3411a4a74c97e7ccde82040ea1310bdbb054b11c7af0b170265a875eefc5894aeccdce6db562ef57e0dafc8354e8a8aac066544cdd8309a45da0efab5d08469de73709c720d6d3e885c49ca408702682ee0a0343e69e9ba134b3c34aa8dc7c9216568d19bb7d4a8a914fb1ed8ded6eff609170eff6cfbf285fc6a5e49b6351dc67c1cdff922d0105a71a4da4aa08ee16b9e3c3d0252611ac30966d97e618f9c9a72aa3f3a1d2f47fe47f2f9b95b1546adf29bff5ae6a6fb7eca6225acd7265cc725aa99dd1844803fe4ee3d55b10845143b7a862036e278157e7c125b2837dc956b01df3e102510547c107711d26dac994efbb56b74b8783831a06c7dde44e231a34fd9d8eb7d471e4dd79b482498f88db40c4565f826c01e3ad20fcdfdd1baddaead261b813383002226719668bfd907df6c58d7ae064d3f91a3d38717607fbdd0115acb1b06f5915c941b417e22eaece1de7fbb8912571ae95e3bd32fbfcaf6c802340b88db3e727d0152446c006f0621205720c6e70dede2e5a95b7ff327ad5a68220dfc685e66d16b491d0b661b44a7c9fbfcb8cbecdb41348bcf07196537b412142624deba0b522814e7b5ccf1974f8f16da2554d8c0fd2d75581b13ed1ff9ef86a0635f8a41dabd1f38bc2cc81cf7d40f16a715f1acf05ca890d1856df975cd53baba636d96b19bc1ec285579571754e39a9f20c880fa9758478be2f5f752c3df00753c9c3f1198293d994e5043c5676d70d00cd09ad4ec1890d941c2355cf4d0405e28ba2a34c1631285cf7c410f645f884e1c57bda73df33b28d9b39511402692b1949aa2b832c261b4c6caa9cb6de7f2a91d3a96c65c827c086cf8569bd63d3ee074e5c304ab768146f1ba2ae6bfe783439a99b7fdf00b5dabab9cc3b1027818bb061f522073e7fa7772b10cef5d6c4e2953952750d8833473cec7c5bf1be6ae51098c2b448f2b21b787825bb9cccf30955d48fbc48e8a4a10eacb6b96dfca97fbe4d9edc0ffaabc4417f4a0ef44ec9e5689cd7c62de7d1230b317378a5e27666564b962dc2e87d3a0bd9669ad6b329d5482ac695fb85d16adef82e52533cb757d52986673b5ff9c4f04d0571188b7466eba99b32442db086d664e8e01cfb20fbc91e622dd6c6bd82343d35559505e30ce789827388590fad84c755f6e55eccec1792592960b66a120edfc11faeba838dca0505c817e70941c6a0fba4174c70166458b2bbe18f292a12fc32ce6d67804c317ddd82589d7e66d15829863673a8df2bec193b0b431f2ca52bcb084a2020463dcfb34b427549e55c397bdff3cb1db59c80ec293592ed4964a68824580ba8b1a8a93ad458e2ac7ab492d6eb4c3b098d7778f6f56688aab221db042142a36070c8b9042a82b793d57cc2a42944c05f24c78546161bdcd0b2851534e9afff9c0bca5f900a84947721a1c6a022cf0f2d4fdf701ee1e08518da79d613a4598d069a21099dcd59e44029d5f84356a20bc5f73b57bf8ceccb844a16c2f93af939d568a867bdd4b6f30b6a25c56da8be9de044824c55f0315abe54abaac9b4077b220bedab8eadf49befec2a0ff70d52873ba531b4fc5ef233e0c9530c105993f4a612cdd04c72aa9410f85a681d5b62d7c77724be2fdeaf1b83009b2509bd624742eb92d4cdda8c4500d473c227c2998d5b062790aaf8b9b50f438186e5c28e3e466405812019ac8cff11804d74da69491148958106dbf6aceebfedeb483c0dd701345d4d1ff7b2840432444b0c66b39596c1dd5d9777e489c0ad2488b1d5bcdecd139d089c4f4040cd473ef03587c396da50d6c018486359057689a0d6f25e58b8322aa89453ca368a6bb23bb065e8ab6d84b5f1c965f564cf0e042e6a7a330900b59a280657d71851029492faae6ecc4f47701547aae6db3d53a073571a76defad2906c42e6107918fa2720fa5440d0f3092655be11775e42737e5f4c0d0e3030a1f10d089b79488f6468ece5ac010e855c56997494aa92f368100a63b659f77b7fbe0a9d8bcc765041c45e696a0d5a719eba200a347cacdb16f449fc3f059455bba1e44b406c4b19f4680f7a5703195e66bd1794886d045ce00963b1d32059b95c3484290bc6dde8869f3016a4a95756e2fc3e0114e2ffb43a89fa9cd2d1f204b3c5c77c115221a90ce3a2718499680e0025375cacee0b162539d788e3bbb1211e189040945cab087a9ddc81bf7e50894f405fb6adb6b42a4ee95986545548d710137dbe399155198bdae4f3360eaf38212fa81f7a9a0706086e45848846960de45eda1b5682fc6684fdb0d421555ad933238f5d7edb3be50517470b5534272f7c47e55c0f782e1a65d2284433657afd6818ee89f5fe2c6b538c5c4cc8a42145e4d34426351551759ff1169417425b1ea577b5c92c88cdf0ad8bfad3826221642f5b9f4023fe50971f2f89844bcc321d9283428b4ee4deee4a7bb8834a24cef45a53d44f16a18c2c7ac539c443227f34641fabc3723eee1200231123120ef2fef6642e28864118866c5328644108729c093c6f4c28962a773d87d0fb34bd75842bc5d3a09acfc818cccf309db8e5d55ccea8380b4a89b2893628ea1917df307136c317b594b22571c2b1592e2d9af8d02f9d5b075bd5182d7fd1c42382a8aca247990ecd7fe656de02300cf7fabf4a6db179ffa9df2807246ee33823110c8f652601694e46ec5abc30b911d52137d1c818c9bf9f38dfe3ba12587aaf12a0225a5a52ade32d3917d4b01e20b47454c1c536bb36fcbe39da13ac45660a7e5eef741bc5881202918ca86b1740c58330d343f3aefe370d3058f88b3c9d598a585439d66d4ce1651d1b19f4e99f599a794efdce2c94fb6e92a15fc5e5d02b30ecf7b2f75a85d87469eed6499b01efeacfbc2b879aa5714b06799f782b5d23da83912c02e24a78a68cc0ff0e998867dbf2fc0c1e0deceffdfe2112442e913de4511a32ea1349b0eb673fe5677d11ee9d8e682819d24306c3ae94b8c09d7fb942e17c4f040e258589ca8a9c8853bdc226a127f6dfbd8ec322da64328214aeb1feb6d1902be8164a940c8829d7a6216f9f1ce869fa10e3c083d5dd3efc686028343153694c40e5b5f9070c161e443ff852e7e9a4deebcb146fac11b8078997a038ece220430c650d3f356283a9bffebb9b42e0aaa71bd5e2bef0713e81f8e7de63ad99c80ee387c018be90abae20f3978f6d65cb63f07b759b2ce80a3ec0169dc83a4e86b0e2cae18058be0788538ac14dec183e7132e975a0d343a5983b9bca6922ceb05193399980628a06271faeeb9c91c6065205813660dd5989292fc5321bfe8508aa0bcb453a520d061db8f8ef6023052635d6b3c27ef701ff7d3788b7a232cf9a35619eff698adba61469946c5f3a6b62c2f4b9b6424286eb31fed4e6a2febc92949d187fd2ae424d85d5172cb20f193ac96216cc9baaff24436b1e05175479ed413d574bc19956418c68e5480e8e09cc210a5d5ed72876f5c30e0234072be4ae57b0ca0990ad57dcc07377f11ca1f761c0f1944449ecb2cd86b7e710b964f2bdb42621531f096a4930766568fc0f6f9595dff04a37b9925e970988c22ff388005c36c2de6bbd1219d0b12c67e3da7e20513dc40d714a662473b773f3764e56c2b48f2b1bf188e30dfabdae665d4df040491a9b934384c322046bffd10234482b0da2f6ef018e96aebf4e0c71243b34c6b0a1b18285261adfac9511cb79e80d5ad2db6064ad3bb747f61ccba7c664c10e0c81b5e6e23a85d178297979636e5423b61d518759f37c53f3b21e28043d0aa3dff9d931cb653221acb09f9b3e1c5780508d896236c0314ca322a6a7b30e2570304808314a887dd427f5af2e677ead2b6849d290966695146eb481baf566f2d7db7b2d759da9f120e809e3151bbb6b60c305519e3cce0fd9ae78f8855a878a60e53e4e4e7b68fdec327b07aec0bd0ac7a71f8cdb814e9cc513d8ebc5b1bf9ddc86852e0489c6948b63cf953fb4f5a308553eb011aa67a47fc85c12c134aefe473c2f994d2e6919a9849558d7ae4cfdb08c5bfd176df02bac1258aa4e6a301c3117dc61c1c4cda7df389b69fb0d004a028d98f87d6b6b8587fbc7ece9ab51f4d5e857e39a0515fa9fd0c28cfa94431482b0654213575c0bd943959178110e8fbbdb1715ba611d1016dc20c7cd074e2ba52fe822a96e57560ff7d4ef1430c01ab1ea4d01c3110218fcf2b8b23ef8c4b93fa52484b3b24db8bb34a6b29a3b2f1b7c6cd6555b6cc600a1fac0ded76afb4701268d9327a6c09277286dba539cd029f13b034bd12b82e7d77d8e8ab8c5b58359427afdf1625e39b154ef5c01d3bc12ac6307a6e54da7f91a04f5550b6527ee3fc2f162b9a49bdf4592b2d3651189903d8e6f7114a95aa6439bb7773cbb211d437f2539299e7e13cd4f89ee03a4a5ecb09d9e8121487e06b6802f95e90e840190659d9d72b0dbc74b7b08a0ba3be01b8e85d81054849354f5129fee473c50d515af97faadbbc425c716a7e5dcd7ad97cd8e40f7b191b3bf063cdd1639b55519b4c8db7ccfc791a19a78a8db7be8c3fb24c059df2000fa575741586e7096ef0b58fbd980cc5a5296c783f9c4718a3c0f3f350800d97cc8f80511632548767f9d8c11c9ba471b9c4eb06b8b7170bca61717e1c8c942d82b3498560c9aac25b7c076b82bd58b6fd5197b40bf7385d97448a0b6191014405e2c053bd4112f977443159303858f0e2b3fba2332fb83e1b92c49e13712204ac0aa15a2110d93e4cc249d40d305d243d18cb89a58909f1bc4c507c552b55ad86dde3685aacb627e456a253b5026131b11b358d16e8792c1f1a983104a8e4c29b080a5213ea0843dee5499cba039d23ae22b3e688e2a3908940ed30fd1938baa157653a03ffdc9cdb3d388250897aaa8ccb1f470b6cbb5885c967dedc4e5d6149937807ea3a4028ade5384d26040ff972fbb5a93524edc3c693a9438a0938e16b4b713b4021388dd1dbc435128e943b73054b15f7c196dfe6604f7ffae3595f91551ba1344e94770c9f5a347f02fc917e9bb10e17d8b091873c9e557715e42f39533b6a01c6d970675060823a63dc557eafdd4a962fdae4764e3aa14e8d3e5d02e7c4d28a768312c04c16dd6398a41d138f7fa5c907828d79082ca0c492d8549903cf96f97e475f1ad624539b788a00365a5d1b2a1cee624268a60f2a8f8d6b2ce642b24fa3ef86519be8d8f0b979979c578758004a659484cf36eda4d52e036dc1f5c09c410cc1b5b094c14accb80773c3d74af17ffea3ab032297c1940763e22f3a33858c3d8b8a6065c77cf2f05421573cf5dcbb4910e1c3f0d60b636bfde559e8918722b10726b59aad501b20ca052cdaa8e2813c6959520dc57cb0eb78167ed514a0fe0a6abc22c317a4190ec64e8d6b321f374ddc6f4da389e5bca6798ad0f345ffb251996f30098c2257c608bf3ba64d528d83acac289170bae15d9874f38f3052f6747579140f9ea412e86d7f4f4cc60896cc03ffc4ec54405b8fc5f24e460455ba8e460e7f3c18e1fc19c23d3b46f00522c2ca96271cf2d0b7b7b93777dd28605671e1dc1a9f42dbb260a352e341cb53526d0c1833204ad4ed0767d429140db2455d5bafb2e68707e7795e69db5c14e808f861c7442f64fc97477730f77f8f2604375494d056483f85225a58f43f9f48965bd275d5e7cb0c6234c9f40079fe0d305264597a5858eace0c03e0ff8f9a7e83d8a506259f7ae7ba81837e5e7a8b1655a87935135959d87ea759ddcb2d224dc8ad07e9aa2b1245f20972d91bbec57228a6b74894ad26c5744e59ed59b1566114e335a12b17f2692c825518b87dc6a07809319cbcc9805a485979087374cd28c6b26706b06f9a7cd9849b6b40673b69861ca8dcab6f2c7a59344d2a4a7ba9a779f5dae12485aad1dbdc546471dc7be35ffa8c75b1355dd42d5201bf5f46e95b8d94ba2aedb64b4d081b93f9130ff2fdb62b1aafa35536e834a86960c63474792c0048574e657f703b64bfbb2d07d88186805a79f41b520665786a410debfdd275fdff37139e11f6ad76c0e77e6cc0857637ee043565f9ee346fd5a3cfae81a3a82d7aa5192295a5013838bda2ba622ed099b0426e5322fd26169036d3eac71f4bb5563311cd6fc44dafdacc31e5e477d0f557338537caf6781e3c1b982bb9394bd75ba74305d2d9342bf7552c02e956f0fe06d1d4cff8169342685b69fbfba8cca9af2fecabc7602ec5c9ccb1472457a132afffe6963d9666483db57ff1dd9ea182e7aedac529f872c266e403c4ba8b0ae2ec26a8101a2714d6139d34c2a2cdcac7a608d370cbd53e8874d5815c8c61c3a277208b60c0254aee869361e14febb4f5a2810604a942795146c356c3f40a6bf3f2705a7c08545e0499f2644a267383bf15fe907472a3c3ae8ebeda51e183f53de33175d36306925889e5d2fbbdbaf428f23d86054db5408525a6c365f0d463a427e42afb07c242d25d7b5606324489abb2333fbe52c04014d0a14804f8a80fa181ecf06c1f150b2a150c55343154166237930045e169efff220345928451c8dff9de107e3ecbcbbd76409d47a26ccdab6b8411855148933b9bd14f9eba85ce69fdf4d11127506ac472cd0795a1515325e71a4df4ae655db77e11d6f76ac16650816397b45bf7dc055e1d93e1de46e59f720c2a48e32044d86901a29cf3785ba159be8298f0164eb78a0e62041acde57b544588cbecc5d957b5fde29fdaa10cab4914f4df20b43978125056e1af8aa7ce32ef09cc1809edf0029f5328a17e967759fedb00f50ce12d17e4d6644cda05a4b45a8cd7a26f1c872bd9bf901026c43a4f2c837ca9a48c21cdd7b0377c7f58fb134310051aad77a746ae865eb4217d47529f28db108ee2232b4d0c21b66a3a225af6478ea7f50e7e918fdaa8b196e23ce152d789285f4cacdf77579cb6c7715de172f16fc83e0f619d17ee2aebb0ad1e36ee15b68e79c45b9441fde86ccca3dc49f9c7fc9e05abd277fd7421e2ee0eee10e72cc672732815c26ce2fa0cd9945361664c5e6cfb545fbd60e4df5018edede51123238cf74f6f64d5b862e218502e884df36decfb2d00217e717e165380ca5df9ace844eac855bc8239ac67274c41ec1f5818bade50a1cb2019d1107a75703bff9b2cb6c58a7eb648a25ad7b1e846ee0c75d84863b422d0d15e5df443d0f6058ec23820a6724a5cba99b7eca490cd3085073deb94c73ea930bd12b6014175c5c0f7f891359456be36a5c597b1e6040a8c7161122eeed4dc9b75efc925487b8de9f407134d00289a5cd1f4d4a4246236bb860374841ced9a8e27f62af414e402c987f04d916d52f665a2f8052d5c0d7a429513dede7676e45e8f11a2f74a62b8aa61ef6c3ffd05949956cd05640745aa083134498db4f15bc92ff579722ad3baa537bd8dbfcd9bbbd4dc7b36d47d966800ff32c633a70ff3017da8f3f6e8f80d7ffe09594d6f6b0b57012bb76c2b62746646e9de38e2112d00d00ddc3a16e7bb7f0d20ed18ba49847c285e5eff02700490d920574f5b7ed17c5f0166bad3c4a2e0db9bbca78b3cf293d86dc0e4e76f4ea13b7ff2c5e46222c189fc8b800387a486ee002189cc91b68dd2adc13831693bd2cae1129e83d2d5834f786b59fd08e82a2df9986b55aca5c3d5c4e3052b25ee3be88e21ca65a9d589fd67af750fcca25c72b6d6e2bf1122fd32ae012214f8db0748c889a22b32661b4d4ddcda28c801dc4d5d8840036486184944d17a151b0e44154f9d06d12e212550c33e01e6454005b88f36ffa8fd86cf874c2975786b69a054864afc38fd43fdd74c5a4229d7931e55eeb0cd80e4eee54f3b1272427cddb8576935ee74454abfade718d4cbed27080920061dfc706b4b982b3a591c624e6960fa498b019158291c6df167970661b6c92eccfff49af0b281b81d9ef677e69287d0f3cf0e1a7daa609870e00f5981619913b589fed2efabb8cc0eebb0550efeb9f05d1ee4268f23161946e3bb8dbc876757026aef6425e5849679c86dbe27f3a55f645f5c6b87d7f39372609f00970f8842aafe74210814b5323d93e5204b51a743cb9387b3290264ee1ac1640c39455c0d1421c3bfbcac53ce241269e7aab8b8236050b7e77246f4b8154bceb09af38f4a66b6a71bccffd309591e908d22bd0f8388f6c12281a644183d521ddce13b2d0832c484841bc681a3472e43c9329f52050c792bfd8e1f5ee1a444a1c60f9d568b4570547a551da65618b2f2f7965b224d76c150818b2725eae064ef10ac56fe85c844b29be00129e3f80c53e63ad5c62fc16c57a2b37538334a9ec5483b9d4f7cffc8a4f5634c52faee05a405d02687c5cd99dc7403cec650a6ffdc3e7d86d5f3ebbad02c6b871bd93c7636d1fb68e08034251c587f828a702f2c46f8aae0236be8318c45f0addc19b6f0abb6c0b20a7c900e2665bcc8c9dc2021701383894deb65b070dad4f17ffa43ba213ab7019ea916e931c23e6928abe1afa2a822da98c537a0218a5d817af81f41aaa31a6db333e5e1873d1789fd7ea2c5517eb901c6fee1c4dafe51c37f2fc6cec8fc350fa719e8d4f4211ab2d3d1b7a5bbcc85347f1c17b914f1bb2bca85bc659688f4725522f70179e0774c6f5df5cdaa07726cd8d67d64e5c552171947df78d70bd2078258f6d2e33e2a3b9d8782db40f3e509e359b8f5669a71b3fe83dfd7f96922fcd11ef75c7445a81e45e8afbcbb2c883ade54b4bdde75247d2e8c60b68dc1d4b890b4057f23cc7341dfdbcdfc7b38fdb977c2eb23edbe317e1b6b30d33deab278385e0414cf2f52bb485be654015e529dacb8fddfc5c65c4a6079eea4d3cb734a0be6055ed96abd927f3f878d00cdf89ec549fafef249892180d64f68398da8e032fe729bcef26bb0e15b9cd4741be15d12ba39e4a6e6e803ad8f6368fc264c4f0cca0257949fab9e8a5443482b57ab89b5d47b5ddc042067c0572d41ac5f718b630e7425e3dc8d51bd50a7e97690a5e5e4ec29cdd9717d77953142cb6918439f5a3818720c2faec33a3c3ebaace621bcdcfb293d29c0b58d9982dc511a0d9327b76a8a3159303fccf92d7e397d479451859a509192d3b99152e606d6c6d4024025357c6a42978c736c373ea3b8b33472394f80d22977a289259adc50f0b8b91ee44800f7a4322d13068b242f3c4e08c21bad94f520f12a3465375efec43ed1b32d2dc871a8a235b19cf3166fba760fc66badc8291eee1566e4c093c8e7d455a5f645b96512e25d092a51b41e802eba169b3f4ef1f54bea05e5196e2ae0df13b06ff4fa008b9e4e1ef6118b26cf39fec924472b81bdf23df54aa99ce4c520a2583db67aab6923f6169e2cc54e1961b9491d55b23c3b3192f526ac097425f4aace01701011f0ca9b0a66a04f8b5ab4fab9b6d3f1f845aac021b89d323df5e271eaf97ff3c66f17f01942749a1bf3c8ce805ee8250a7b412f652b6d966c9abc8d7ec3831d83c58a53ffb648b00614a678d821921a80a6622d41c8c6c9dc4bdc0353c3c0bb998bcc0c15016f415a17707e1bd393393d83a5ac06b6759b90649a0af2056af3bc48032492c8638bf36cc2ca2582e72b558742699b910d524287a28899a9e44015422178bd85c4e176ad9f578e2ca849080fd7ae45c618fdb58b9a42bb047826550163dd8a35d705800665f64b813730fd25982ade554cc53d334e9aa6e185a9e06d0a0d93d8bf94d978256f0f1b6564c11d06d3cc8911ac4217c3277da51b9609e22104eb51fdd59dd6c1ec599a1a932f5f64b8345f7a9d4d547ebb3b1015e2898bef64282d63a3b3c384c1011d58c5070190b855a0893041b059289acbef35c3a5df126f3150ab1ab1eabaceec9543b06ba8466c92ab6e8533b7f589c6240dc4ae7b74d1aa34a2049b42c71f01a75a8a394b1393749a0f687df7acd87cf6d704aaf38823849356795fb863af4b1e6039cbeb58829607e96959ec0d76a1c589ba333e542fd08ecef9fce015608aad51e991e2546c7aea2c7f2c147dd1ce4d5e3d502e474d4e852afcd670b285f2aeab454e5447336d90e962b605904fbc7b9a1032c51d1ab2878a5851f5fa4c4cb7b791d6b7f79a7364e6dbc6486db9961618ea9c0cc0b526b546f179fcec1220b5d707307e7c9f29a13ad9bdd956d3a63aac55aa599f8c34c146d3888fe2cc57baa26a208c34c16b00a17d805816caaa837658b9d9a3846a824da820cc8d1eaaa56b4face6a77a5da929508bb9bf50996aab573ca21e40ddb692eba85911ddd501b2ff47d3df0610844c38897d0ae99b6b3b687622bd0b05e57c8277673e3fdd4dfae8cdf835c46e8ce27e2ac403c6ce8d5581f089b8ec8481fa609d258aa19cf4a5e959281da99edb362b8da7402f732139aa06a38a66925e5410fc1279f96108e60b749d6a97542c266a08e94ddc6841c26f00864568b27e9f772710c59210de7f29c65e5e5d7a2f8be02d0986d85f0b736f2ba3dc74ac1b1cae1fe715d43e832639eea986f5966a51a1a008cac3548fe3a15d5a9bed1db044c2bc520a36763c0d611182bee397ae89f654bb9de1d51949e9dc9840ffb845e611f64f93b4e9be25324c941c039c44b84073f6ce4c797e3c2808d254eba6be52e8d094f09417be3342922ce89f4a87d206b7672bddf000f8216d8c2c27297d2e87c7f9c2443a0b7329313b947432e9879ee7fe02438f0b0e743e8cbb541b752569685597b644753eadbf5ad9cc4729a670e3b473d8d59fe1beb641071f6f4588aa9c05a07ee309c8b673d80f77682d484c74dfda246c84d82bdc3d477300f7b93989351c9d310be359e802b87a2d0da263a93c40db92a380d47fd490a7f3b97942d160c3e127afd411cf2100a923d72386f905b781a07012bba5155ec9d937f98bca2c5377c0f2bc45c304a8d5c0401d00cd2631c68b1a30b84fbc03ed820fb1151e9e7f973b96158bef5506cc1fc2b1980261855c3080620552534a329ada1aa6c2aab72c7681bc44d9257f51bb8174e3f63fdb0e05443620456af4c769752b14b5860fa7bd4d991f78ce82cfb9cfc0f0c435136af7a92b8350319d172bd50849d04294152ac40d4419e40d4158c4682e490e5b18eb0e39c3db9833f40ef6bcb2ecaacae2b6982c2180625135ae07e964a56640416a21f1703c1a2e01109543a88885e10f83d9d09f03c11f3267fc2952b0975bf11ec1f00cfea0948a118db6af03f4c616f2d3866af93235cd882bf42242002ddc68f20dcaa1913bb5626b4858e78582cd998e0dd652b760c4ab0502e8d654c5b6024227c58d9eb0ddcc51e78d88f875f2ad476d4e30296cc8ec8ea20011d8cd6812b5f50a458195822ced9e5f5e02a232e113569c686233824a6305d18b319fd71d39dc0f33f760421fa8c702c3ab6a10b815cace7190219675e00143e930032026cb1cd36e6008a96bbfb77cdc5c833d467d58c62b2b44adba06ee1e587db3c4788b40964f44d230ef2181945ba45773a37d189424f977d1a4d19290466c6e151918573afd270ed66704bab05e8849604d087198de12072a6423afbcc34d6af306e889956411df27ce132ca1e9ed2159f31ea89c4eb5ea778dfd7fbde199b9f32451e3a5d56cbbeebf6013f2b6151b55367af23215ea0fd2d787102b60867a7677126a1a3ae27e880e8aec3dc87f4c35600b989ec313037c137ae897ed5c930ae5c6b1647d9fc5654e346c2451011f53dd522654312d8132d89e7046662a6dc9d779a16f9e8c7d29e9dbc3567f3aeb2b3cc095f5b5e2c032c5923ef7f76965b3e6917c7c3d9fb017433a028b80b7d77f5567f808770d91dc8f36fc9eee79f4f59f9fe0b8871ad446b4b71fe9b68667129cb80a2ee7c2613533d6b58354100ba9e6ff8713def3dc08dc767794359a13eeda6c52118243e12fd4940dba31254a8e01c0bd869961e69d4e1ff5a92b7cc5b23e1ce6522f5bd3e85c7688fa3100311de910f25dcdafdf9012a22d1152ddb2533e56e890b073def5ea003e83e0fbadd3dc62b159ea0d5d5cc1eab55a7059017d3482d1a58720fd54b983469007769160336651e000ff7dcdcded5aa8eee860996f64c3b16f719710abc229a179f13a961179a991458e4f2dc9917bd23b1a01385d835748e6d738a4f107b46012f4efeb799f610c8cbf016070049769a57b33c4fe079ff5358690ab82373660e925a0f050aab3bc298f7d0ffa69df2b2364755fa0a3f4a43e433bfeb8790ae97cfa06610de6e4967e3ff29cb9aa5111b32f7b2abe3e23e2932ca15b2d67870a963a4634c769040f32adb01cbd1b3d3468733ec23f16428be0109b0f5b4a50cb266760535fc6e6b459515016d1a3db349f9a91cd4b6fe4540086ba01bcad16aa628d6a8e84703b07ed6694d838ffc7cd8885a4846c2d56e67176e6aaea6b773bc5641430418cfa46554156fdaf60964914c28379c239a04ea5b13e15a18cf8d46f561ded6d382664c81a03223b58ebbc56a74e0d5b9a3aac916f02c881bdd2e8096c542bf66d4a2cef2f05d52c003d1b6e6725148d4d6a4b0934b925cb70025c45cb96d4e2b64fae72641774fdd263d045c25f8aa5ca693783904545ecaf8e0135ec2543fac78d0aa06ec6031ae4a63efea1814a8a53da345b58226abf47ef48f61b162447af9013d2c27518250af0a0a45cd2910415852d9d71971e3afa06cb18f1966f15bda99eba66038d76aa5068b8939ccc01004af8ff9aeef0b1b9e6cff616d3ffe7e599990df120fb3ef605b02200a9be50c872e10164c0a3507f788c702e80dea5918d8d0369bb1e7877d25b68503be15d437db918058be3d3a2bd52a38ed50aa632145ecb29fab0cb65443a374918cede984cd88d78570a3d19b11a6e0f83a1f4c7fa11c9e39da0ffc1ba5ba90079001fe65a58285a0ce4f2a5e1e9565eceb1c01cea624286fab718a550371c712efd41d001202b0e154c1f40c3104b5e6ec5c02c4d68c243adb87e27b1d2af90b8b76230208ab48fc6ba190b49807c0e39affdadd66f53e8aad88492fa2531504e983e3e480db910ef976a73fe2e209c6866e8ec42eabffbcb4f025ef61a0bff1d2a8ff2139b09e226a58fef9119abb0461f0cc2c1563710f4aab1438c1dcbd2081a76b945396569489589db9ed23c9d9c98789bc784cd426f69491ad0f669af666f17eb27080c3aa7fa911e8a59dd9c12118497c16126370a232aff4a922df9a9d59cbfc3535a60824331a09319a2c6db90776bddc0d4b049625690818a13ec96a83f0a19406872a6194e215396226bd4f5e1a7f0e4db7f13bed99995331178030b4d84bc61b4ee24123c1e50c9cb3f1057bdd780ab0cf1360716d029d84009e12493c85b6696a475d181ba564906f413a3bd717d31ab26cf82ba3a1f1c31d09590c4ea6874695214e5cc2d0f0e3ed7c69909997a08e1adf470cd98ea9c54204940c93e4f63a892c9bc04099f760634a3d1c0e9f8c18ca205fdc4676dfb05c0998e99e8afbe385ec69e0dad9515de3f437faab9e2af8777d605f684d912eff9c1a9e6473de58504e30a4b37918c7d1582d14f2aace109bc2acea38846403caf725453c77a2f287d99496e0e80d6bae6fc540a81a90d392077394d51c69ee01a77378c774d6539515a25c6c120cc3d36abfe3e110df7da6011cd2cfc433a639f80b7e162391b4ca880a0e3353e58b0d36642b9b28c3989e7cb2cea7284f3110049bbb0405aa71d479d3b8e4163038d74deddf74d41c5108c390a6bcc2a732c49e9030a851befce2ace62b39f2d4d5353c5589fb51999fecc9f85d5afadf59eb13584e40a2974aa5a5f036f13005f1294cf4d5b6da3ad07018511be128cbb6c380525b49787f4e71a0f0245289efd23cbd392b4f7b47b466426f0c6c1dfdf0c3048f0e50b184adcbea486887c075b407468c3c55fbf0fc717d357c385954c7ad21a8d3541c33c7b87a2815e6ff5aca8ba81675da8a3ac671d8fe8900300acf1cf0d6f0289189160e519343deb2a69a8874c09a8c61a38ba23e5f870f6c4246d0b2a29744735a89bb8e0fe5320c175c06c1418c83d074c48107c0d7fb8544825f7138fbb8140cde96309c6a991fa3c884bd405fe5f2fec664ccccac599bf4968b64eda25e1c03e41a09663f8995154e6aa5361a9a9018931f3e5ec3860c7506ac5c3bec3a55d943340373a6e8083a648f5394becc53aabb98697383ef59623e83ef00bbf44236e4c1d3832de582664bcaf231920016d8c399e84c3f3f395b0e7508537f3bad859bbe9b86a68704eeace923467c5465c252bf01f031421e4575911dc822d34d9e37ec5a33341adfac1371e9bad3b7850c79d62e0d86552876e53665145c156c1c8e3e5ace642a450379860e660c7afd70b07881bf589c3cb9afc01f4727be20d9d92f9278c2da00488edfcd884244371e28fd525ea448ca7b24925c10d4153c096b164401670790b9bb0b2a3d23d38573d937058aa33a5ab5db185123c2233867dfd94e3203b72fdbe0a90a2d968ef5c19d0abff9b47500d1c14cb50cdeb1d9dfc0a79a9e7f0be4c25e45ae5f5d38848c59be6ac9c767576c75f1ffa26268cfd4017375a7ed55045a4908851e0238e00aa26c62561ab4ae9f2d4d41d5dd8f25d51b933e95aadfa8508f4d40ab1a79b7720b66e3f7965a577633210281d10f5b74dc0a6cb50ece05db14bbe5d3e4255d833eadf9fb55ee621c9aa077c492cb3c8bb42f4d1e42da516ccd42ae6d04b4c56baee666130a58a66cb8813971347acbf79343d3530ac392a8436e7f5d54d21b94469bac11353a3199cb8cd0a891d61fd61045046277b01c9164ee05540096ae361b0bfc9348a36ed63e051081c91d096ff8afaee9a29297caef77ab5ee59cd4fb65dbfe21abfac1400db473e3f225f64612369b75e813f3194d3228009e53028ddfdc103dd8584f7cd5f6c801935298aa8dfa5532f5a8b214f53bef6fca6956f9704ac5512460aef8c90f201d4040761306ecda965472c86451d3476a93f631eb52906d127288b66249b20fa57ae9710e3e087011e42af9c05b8963b6361adfc3c6c8dea40858d787ec6d6a13b2c3d199ce434906a2166384ffd53e7d2703b6c0ed37da5f442f7aa1e8887b1f7246f4a70ded9c3e2a053de787d8f0f6eb9f06ff4068f95b925251af29f8b7feaae666611374f51913ff03363f05c42594651330a8483b9f0157c33e7f0027d8ca4352a5cf0bed713ae610e5cbc88e9984e5c87634ead67f7214d7c91b1c4bcc71da49caa78f2c5318c7afdcc7d3e7b32dfd7a8c416a71d725b681bf90098fdbbb620dd88af54b4269fea7e0f15ff9f02d3587956e4207e20d9b4dd7dced1e03d44d3d3d1f2cbf36e10f631367ac2765aa989b7e21eef4e86a589f8d3013fc0aca11e6f53004471728998701e050a3e90d675ef43bcdd591036067de4f0ce131bf420b399aaa5a643ebc109f397168c615aa0be32d47b4600d48d4707ca6549a5733c6e7b9cfe16b0193afd0daf474fdd343066bc2677487c7533aea5fc22200e00c5c0c00fd058a56325d07ccaa8b0f7efc36656aaf8beb5b2d991df3398cbe0194369ff342e7d5120e8b8335b40a6c54af84ec2c5cefefb982c61b3962be5b6b64c4f86eaf919958b22875f17fe827a64998c6f11a174068a19533671ec53703615baf72b95bf42be3e333c93e002ab038a4ae878d79e13426dbb905fa209f36dd17107860d07103fd271f48d348ec01ba7839c9c4281eb7d408d49488242b36fc2fdeea813216d0777a03fdb8a2343e11959ca7ef30eaa240102b8531d1e7b2280aaf5435ba4a0c0d24302cfe094f29c20b190c55f4777904fd500b91cb9fb9c4c58e9a585352ec2d40c0819f9d37563fb68ff7fb22eddf4f46abbbfbb1824dd211a95eca21b3fb8b5521646c1b116f5b5f19b13a9372b484e3bca25eba8a2232956e7bb7f7acb398cff02200c113af8aa908d02c46f40795ceb632c1148fd0ac9db910e74c5be3f188539bfd29234e86dd049a5d6b1e920fa02d1f50e78d0e47d56156a1164d3876293c4929ae2d14a4b45f3a66ac7b0955ce90e04c89d72f19aa7bca10ad9f2615c8a00e6b3e291cfddef4efc36a6e888156f130652e11a645e0ff0313e28d7b58a05b54220a4fa046fca4a2024021196d4157192544765f0daec00c802be7693beabc5fb163f18c218e3e82039aeae98435db93d2a5ee036edc931594856b4f51651469a83b6084c9c4fc6c5f20738555986efbc702008e58f0818548098b92952aa8c40e6660040323a788a0af1e2620a6d716ff7175e72111759837f22c8a0af0f06dd34c46acef3ae0463b0e9bfe79ec4cff94a39a94512590e6f40f9ef3925a7443935a7a1d546c3952e447ed336822da205beffe690530cbfba17f0b1785f1b3a90ffc3a829d64d243c54ccf3314a7eb8bf5942371b245d28f9cc87b9c0d92bcf431629972a379c2658a222c5b8d71a609c43697dc7348de55fe20b3a1a46b63debc59400d69695c0a5ce8cda3d49669a2713659ae7cf795097ae14d28c8aa6f362634d26e9b87ad8b4a5d50986db8779487f028c43729195b310bdcee069150d8eaf1642b1f3d8d4384cfe5d65b064d37f1f4914c3e4802bd37bb368092ee212a60013985e8840f3dc256d88748d05c3a1cc457e50c34b05263ab6fb812bca9ed628122fcb58d5ad74417dab76463345b59cfa4cb503e5ae9fa7e07d604c67058920900b020800b33eb7c90f39e0e134c6877c46e6ed5a4abc6633d7a21e5b234382501c6720f298396a121ca893c53412cc6e8a0bd34b31a54372f0251ece780925cdc0bfa54e591744a7ae5a2929857f3900a2d045880bafec173d2b3ff4e78f9ce607ebe311747e6bdb419062fcf1c306b2c037d866234f73acb0ea2bc05839971d2a3ed23ad7040a217f9290157ef3cba996b5edf9d32f4bfb3f1951fc0df7ef49cdb7d0574f3dc0861227a56f6a8c0370091b33ac340e7375175ad1580312c7da4f6f430a0a17e9494e60316b247ce2b1ebc14eaffd4d5c6ed66c91fbb89c9704ff730ab3c88a9cef350c6b020d664aa2124868cb3b780572d315cb661bd6e9d3626b5ffb2e1c040e83b0af628221344b67931d90f63f55a477a9f3753ea873dd55a9ebac81eeb03a07325a169d9b09d1ea599876ae72bbec6c6a0f0f024ea98dcb4df6c254c1286f4b884882bdbdeee9019c1750761db4e90473451a195b6ac84d465648da40bf2f532d8555d9688e3e8a0864e3cd75ddd184c479042e1857e5053609bc740d8de0b596f7201da4a0e3ce8896ecc201e6d82c24f31352a480d44dd48a6966e0560a2f0768d260e05003eaba88671fe22c8d6b8fe2f002fc96d0c08579039cb43b2b17973045624a79a8f0ccb36690a240da356cbc7ba9274b279fe675cfacdf8eb33679c3afa9f968cbed235cd32ddef2e784641685a83e427109300922998358b5bfd6d18986c37144416d1ff291801bd45ead2992483c88c650680ab941c7a04b2cc93ecd42216b1dd5d1bae60475bb6d3d8ea5f81b2678a24658ec33190cee784d73b9c4aac2801ba53d11c67037aafc2895d40145aa503a92326263adf0f971ad6dbc3d2c5f23617e76cfe96d519b30999b4ae75ff3d9cb42814321d656782eebaa87f231da11f83f930f10d05be35e0f0bfa7421ac4f2b3ddec904bcab6950d1142c8969becbdc9965b4a299394019708150852089ea352a7d4c9cb399d7b8eca61e7276e47e79e6bddc9f33eeaa7af76eed1ce63e718870b09b48e1538426db4df9c76cecc09dcd4abcf89a530e7c4a88d5e23cd5bb60496ae06a38cddb59aa25b9310d3a84887a4a7f958a4c3941fc2ac4b29d1e4021c820d5aff217f6bd0b14c0887f7ba4db3ac350561ccaf5b3a734a17cbd7b3ab61d8fd42ac370cc3b49213bd2c0222bf948de6e4daed9783f90ff9b6e437a7e4d6691c20daa8b57a9642756bbdd6ea16db80c8c7b2dd1ee370f4a4325693b539a623cdc4b230faeef9f8ddfae8da5c4d024c2a63026448137a66e92ad59009284034a1dd1b1b4086e442bee410e20d8d46c2f78043f9ba92b4c24fc3cd84b7fe5227459ad05ecaa1077e7ae8c2cf9e59f3492c053b6f9d52ef9e96221310a309599b7273dd937bb42ce5dbb56ceb26c07913715aa89568197c29cbd7d6030e5b48a86b6c295f3db6a7b307f89466e96a504aa10f6f28829f334ff93632c96c092c6e27477d27d75073ca09853a691209f585f285fa644e35184f0ece290d4e3f9d3c47798ef21ca57254910e413cca4faeddedd6a49fd3c3d44e4bac57342d72383c9751b3e6eae54d142a87f5a801a85ca93438fd94ab51d604cea13fe44f2c0d4ecf9ac03927ff211fe5a7af0a55290d4e2636cbe9a88e9a8251790ad6280a95eb5038b91cd8cb290da272d843be1ee5299d47a1f20eae48b956c190a28dd3e9938f5d713ae5180f7329a0fc74ca315ecca93b9dfc743a65146c80511ed6d7eb4f3e5d437d5a6a07a7fce4617d79bdd22c944faf49d186e7d335ef935f8d30bd1ca3327129a0dc732e46555fcd8af13ce4b81d2857e5182ff300df732f9f007af3ca0744be975168f0f4c59f2f944fd457b7a8af5c035ca48392dfb69747798c8f7a14ca65e470a0bcfbc2fecee76be670f5d4db71f7b538fdf07c356ba859299f2b9f5eb78df3992413043e797dc180b15ad9785538b9f874cf5bbe507ee7dae98beff2d16f71c9b9b4e41ee09f5c3b7df8356fa3cd4279cd8242a150328ff254df441c547dc59ae928946b53ca9eb39a52293f7df2571fd19ba6ab3e151a9ca83a8478a34245820eec53b98f9c3ce5e17cbd9aa5bac15cb366bae7e11dfaccefbcc15c2e55aeaf06a7abe4e5e9a628f89473507ef2d40e949f505f0ef931cef03c9cc3edc66c61fb292dc82f7d2dd0d487f5f57abd72549af1c45eaa6e7942468c31c618a3b4b113e3b83538a35b3be75400e590e070b5fa211f878ccbb0a9d1ac9898e3579f9a4fa4b2ca9761ca1dd9e342043f48b35ade435634b36672aa171fbd46d050940538f9e9e1013eca243f3d9cf1b1e64844c448ac991e96f0f12802530a05dd8bf16ae50e823302f3c603b166bef0d365b644647661b301b7530f7b6c79ba0ceb938bb692560487b2c5e3f3b2d59242e44e8c319b5a0ff99965599665a552a954caba85026ca5caeb4a83490d22c5d70f9ed20c9a94ddddddd6e5d7e3f27ceddcb2bb65d38c6634a334f5944ae942df9fb4412a535cb2ca94470d62c1a17c25bda477ab88a623fe367570d431cc75c4c732e6d2753e7ea187328c3eadcedb3afdc6a456ca201b868a78787d2ce201e9a311291f638c8091d6cb75014c3dbc453d467daf06b4d86ef438947916d034ab3af691d600af1ac452e6eb7f050d9b49298ff84ef5048a352d8f8ab845523ef3b943e5339faf665def75d41b70095d6079f4a1cc81fd5462032c7d7a107d53779a9579a7e64eed092bcf2b6916f65d8f64b9f6341805cb3c83baf6ace6567bbebdba4fb421bf5fd3b106643e7bea9166f9441b3cb26c86b2ce8c0141cb30c0991c8263d111215f72ff52e69aa6794996fa25cb5c661c0e693339b72bf99293aed9d8d050000a7d136796655918ab5c0ff1d067fe810d669a0633979e6599cb4ccbb4529e9c0ebf30257431a5d34b9355a2981528751d9cf9cd1cb3c29cae83d35bdf5bbfd5ac916e428243fc344e04dcd375b478e91c90214122e010b4a9316ba4bf1c81439b975854119f885348ff1b51a89b62a1dd66c5d84d279d52cae931c6ee28a5a462f58274410f0c3e7f2c9a010893043f77c94ba3e952f5b227da80a6c354541fc62ad1ca17e0a71cf2d363288bc8233fa51222407f5130c34fc72b223fdd5150e5a7833624f9e936365cf9e9356e0cfdf4136c5054a4073f3d021498e1a76b20053f8b6ea0e2a7bbc0b353e4672cc201e967510e47fc2ceac9e17f3e461bf525ed02c739e39cb308299000e209108068c2e4c70840a6d50af7c4b3470623a87832c511273de0cc2631682237ce2aae3ba779188566d00c9a37312be479346ba650cf2fce9acb7d2bc9f39227c6ba0ece3cfa626c564108d1acb9ae23398fc0acb9ee417c58a17df3203e04f16105cd4b39880f2b949ccb414ef02d07f16185cd4b39488d59737dcb3d73102d13811e716afcf5d26d8f38a51c63b3061abccec2f5981ea2187c8827d1fc248fe4699f791ef98d59a13489aecfec008793a8beec0d9a1b612a3580b90e9683686e3d48e9fab79a35d34bdfeccf28e6c8fa0a99b55eca33489681b346085c338e33a67b71c6bcf9b6db2d46c734cf9e4de828716284113cd902c905476042640714c0e0021012243c404ac11180b04b92c0e33653e460c80d31143901164a46c0930591dea15db663b162e70806274588e842053a40959cd01bf02831048620215d20d9a085840c86b03df0f45002243cb478e224054c6c00841e92f0ec20e48802b498a25505869f2659f8d4902afde414a9e10821b420128288071e196480e1080e412da06205424478811076b862080c3b6ca089911814f1f083274b5824f115c31348537a4012a2c311170d8da4db796a98628a1d141cc1e12705914a951c9008c1810e8cd0c2ea3082dea15d76d6450db2a8c2043c34e8317282265e4c429f0ded4bba299122c450831418aed811627b4e4084091a894d77298806426230c515435dd81c7e4aa09d2048da9c00052788e1865e6ca331c6586bad31ce59e43638298d71d6fca533d23924620f57dd1d6395dda49f98a20748089557151ca0502ae4c20b9c2c49c10d5188080a6890667aa341ea91ab5501362f7d7238e48d06698833a8cc52e0e95701ad06e90d0a5d437ddb522919191a1a197f9f662a58f2dc21a1afae5d4a2b12a531c41b162e25c15fa459535d969ce68ad4a094a5d29736aa6d72f323af48d1467dc5d7bd2cfc60e16aae956edaa86239faeaedfafa8368deb8b0aa2ce40007f1d52990f559912e8e35335ee38c877766c66b45c2360d865b21a86a25de21a10c9487f6c8572c216dc1f01b4d3fcbe1f0c1c3314dfbb4c8955c7ed44b9f0ee9543a7d89fac2ed676ebed15a1665364d5be95591aa9506abe728f0e6d643da3a9ad9b04f3e85e130f211151528a594b6404ab7aeeb6ea922691b4c8cabf0000878ea4bdd54ea7ebe51ed8b257b592849b9b659ad8deed0161552b17cf5d0eeb49ad5abac22d9d65b213fe4abe539e1044e86d2166dcd9bf9a27d42873d180e03df1318705dc32c34784a62a1084cc35d7ce5b4880f15b2d3ac9557a73ccd2a427db60f69ebc8e72155623ded445bb44585d01d997befad48f7de7c0203dc5d1ea9ae57af49cda258e6cd7c552a7de35e61b8502713854e60c08f06eb7c492b2f1a0dca3450105dd3e261134921b2f50569b0fa7ccd1b16a4d0166d35ebd6234dc31ce5f5a8da0c73734582f1b022dd68af48f555afe0c730a7d926e6a314695e3c613275f1b022ad3ca4758b57d7b944aa569a253bceb7cd5d3ea416b012569168ab05faf594bddc99b67c6f69b56a106c2b14d3355c9cbc7ace39d5a2d64a9a35ddfe40817196411fc52ebfe6668ee9d8f19c1f0d6a1606a56f70102de5adf5a8d18d360bc330e7be48e78dcd9cd8175fa31b0bf346e5d637ed45f5c958333df5a1b04f4af9e46b35aa5037b459652525429bc77cce2ca341cc35a0699ae4c1ae6158a90e99844c2689b1e4913c0d5ab7ae01ccceab6da74f4a390ab2c1b20c6ad07ace01c61efde4d5a9b7abad80b889daa85916bbf51e6a96e7d6a5a57d687cd83e6153798bf4f6a65c4e9f4d4b56ad56a99c437ae5b56e4eecd83d0c63ddf4e6a440835c03a69f72281d63cc73b2051ab41ce6a6edba75d39c98df684f498c9ccf9925d0a0f51bdd35774dd6c3f609b26e876cb34259e4ad5b6f1febb3c88d990aaf75c9c3c2dc7278354b81e87a4be3b41e9b256394a0d1a226376d75632c79f69a33008566c5d75c02adb9e6b1354dd334a9c58b44c9472b1f8b8e04bd26b9905679e90d3a15fa6ac5a89c58c4eecdf4d5a0d4e8f5520d5a8f4547947c68f3daa5d12ccd6b34ab5df31b58bb17e338351ecd5bde6850f3d8d86f985dcb986bbe35ebda242ec56c2bd6549735867853a392e025b52f7313faeaa1e963cbc79957ddcc20fbd167d0bcc95e73be731ea9be184149638dcccb226d2fb7c7e634c53803939ffc19b4aaae25e9d8248ba559307e938f6acc9b9c5718bd12035bf99b9ce3acd1fa6d0605d6c93530a75f0de93487947ef2138c4dfa9934a9871b84618fa488514496ca2be521c28ecda27a68ad387138a4cfa479437b569adcb46da3cd8ab4c11af386c65d7c85729a4fc61ae931befccda455d22a09af665d853462116d1e949752ee3339c48eca3611944e5da91cd298c9612c5a455cd24a4ad0a32cc368d38e3057b956baa56e8888bae81135ea6815a1ce44314c85b92a039139b04c8be851b342cce951834698ca4fae6514cbb48df6cc9be69935d5efc514469b9952ce4818181e1cda239e79b3ba58f3487982f1eaaa8f5e89a0f4d5bc497ae9b5f2ac765e3ad67ae9147328e83ccd2787f97a5e1679a9e1ae73d88306c681d057b98fee55d9295183b5012a3f6520f261b295979e43333552655ad460e5b82c091c52a1979e99c05ab7494a331d923d698dd614c3438ae069a6afd417ca4f512cde198f71c6e9073648331de270ea85c7229f161d2ad2acb0464fb36a488fbe362ba454bebaf8cccac3159566a5e2bff8520dc2f099efc8a5c6aa062a5322a2af4e879a15d6160dca34e868ded4389a33c7ea6d0dc2f0e9edd635593d5ca17cf526d95b2d5bb6ac2d108694b259d45de5b8de66c1388d71c9e190f1954c80f5ea30319135e3f57e0bb6d1126ba8b7f8cc3723484da08e7dd46350cf1da5305f095d6cd70915c6ab1a2da459d89ba260982f8471ebf2732ff2eefe9ac9eea0b62c15968bda63835079ec0fea8e3db23ab047944e3931daa22d0de7c40661f80ff955486dd5168c5c4f286badfd3eb73060a05a5ab0d5e0cc0c0ca72d2c3bd0d417e326d3c410c1ea617d9a5c47106baad7ca83518c1cbe7ace27f89a80da92265418e28cea216d7d9fb73e6f7d1eae1ee374f8fb949bfbe7616dd994aab66aab0aa93b5ffb676280d0c7dc47f7587ee1a13da28e6520f273b04cf3c4d2208c9cd364c771b2ff90df24bbcc9911d8c56d152bb44adf64af4e5dcd82e1d5f327b1744df5ef6bff7ac8891a816b8e026acc1bdaa3a1a858d7578751571e360f0a2734581dfb18d033677a72606f6bab599857ea9a35d54ff69472945397cae61ae32f5f28ff25db238f79515de3dce4f668de489cead6d5ac18af6eab344be2d48d96f96aabee34582d27edd15d1dc5e42aa4c1ea616d5521bd241b90b9f459dd52b1aecaf1c85c7e40aec3c0c45f7de1ea5dbe70d5f2b5807db86a81a6ecd157477d61ea545b5e0e2fce5d9e49d46b7513f7c9d83e1b1a0d4acc9b15bd4179815e748e1d2d9e3cafebba93775d87bdebdc3b75ddc9ebbaaeebbccef33aaf4bf9ea3dcff3baaeebbaaeeb4e9de779a753e727aff33aaffb34f750ee759d9f70e727dc399e573aafeb3ad7b8ade996c2b3bb9da7be961ccac78e7239a53cafe5e49af7c56f51b9e45a549e6bdea9ebbaaeebbaae43a1dcfbe4bba8a052795de737daebbace3b75ddc9f33a8fde755dd739c6e1e8ba4605d1318f9b63ec74a35e347a94873a40cf6d59de732dcb21fd6c96bef9f7f6531ba519a59b39e77406c53a4059b8cfe43eaa872e3ccd405ebee4336e0ea99b5c723beefba8de819ccc37cfc97cd698629411d2eca37a783de526156ef651fdbac9e9f785f47eca4d59f651ca4d4c34c027975e519fc97be800fde6a76ff3f85debb74cdd444dde7da10ed09bac9b5c6ed64d1e4448cc72041541e002095d24210b4eca63902d211f9e1d2130bc80b383e1c8a08373dd9439f5fb51df616f03ac5f9fdc0eece99b36eedb1adcac6f5fcef412f552ce3e00d0804b2eb37c98697747e63787a51cde26296faec98ee3a4b208a2118e0f7407fdebf7a99016555e288b2a51488bd67703c940a8f31cc0febace37b7c3f437ff98f27dc82c59b26489e1719dfacd3466c7fdea3db00732dfe650c6bcd5404321dae8604a48028e461fea00bdf5f831580fa283635d4a6b9d7e3583a0f5f3da1ca3d4a12d89b44813d2bcb2cb713153a4132d86f001021be8a7ec204c4d70fcfb5c129d2f60a8de40754e9b6fa03843a3e1cc3d34a1b759ee3468eabaee9564bdc73b4269bcc9dc947df65b61cff3ba13aa511c0e9c5ab510e186907b3df7e25edc158c714a7aad22b2bcbaa20deb34bcf4ba136d4c9f55bcf4e91a92e1b87809e2dbb921c49bc821416a2955e47083cd5d517138b857832da5e4ae34783528706e168229a2c30f4e965f825e807470eea783a325c1ed55bea13428fd45a859ed4de90bd1bcc95e2f4041cd8a4908012465e8c588ca0b524ff97e717dbf58f9763a25946f6f1cbe67c8bd50b8622889bbd22cd9acfe8e118bbc7b445eba26146d782f875e16bdd7f332f4785e0ef19203221e0735e988bc940d64f4d25f5a106f302ee2db3d0e876cd9b2af0037600284a3b9749fbc55ad1ecebe3622c40f4038f77b51038e06b3655f4f315282209cfbc54cc1d162b6ec2b4013468e703499ed7e2b64412a151c5c3859beb346ba0e8ef662dba8fda8a7627851331d273dfb7470b48ed371fa281231a4b7cbd6e9b5de668c7ccb6cf12df38a335e80669ee9993e85e8152fe70b10f70214837bc99929128ac3412596575e80babe04517afa7083f7d3c1c93e1d1c8fa359b67af00bd09c5fec97fb02f402244384a94c1398d617a0138743ceef6617b395fa0182188111cefd224ea4e99c43f3b2cd1a22e4f37aebfe34bce4b7e4b7a4618dddd5f5e99f06672b6970ba1bc1b3e4f7843933e774f85b9b952c754a5d461b9a53a794e228ed68edf3c10236b914ae9762b0d26540ee215d33efdcb21dc7d99c0205110414a5bb67d6d01925daa825f98d61a5bac2bc3afa692edd29eff684b5514a897d3d6c3e932c9ceda879b0411b4bc3c6daea12c3ac872abcd768d6fc683498f9cdaadb1ccc56fad9773ba0f359283da36143e3abd12076554f6eb082978ef5b079d5931b3c7959a9d579e93b66ee80ce4bcf5c8b5c0a9a5797e1acf50b7fc8cfc9a9262e87f4f83fe44fbf99464e966fc419d6b314b85ab791468d6665d98506ed0d03bac6fab6a552323234346f65bcfdd1a0f51b994aa906af9fb5628c718e51e1c31e3638b4cc6fb4ce09cdca7cd6ed6695c4e8e30c3e162569d1cff22a739c43b0cb5ccd598e3da68abd56efaa9baa9b3a3761afd5bbea5d12d7e3afaec56cb45931db87dcb66d5ba823f3d2a24aa54e9e67eafc0557cf6472ec9dc93d93c9643279265cbf9caed360368c71d775b8ebe677df6d30b613188566859d0ad106caa7ef3879f785523ebeb7ebba7cfaee97d361afb966edab5dd785e0669fc3be0fdbdf1fb363bec96376cc0f41f0a6f9b3659c28cca7df9dd942199f953ca4f9ace4a14c2af3d8accc634633b7263771384c91cb81b5517b5769a4ef9a633a972998bcd61cd3b5ab5931d5a56fdc0e937760ed29cd0a3bf77ce6746e54e278489c02f6cfb81d9d573799b4d5cc5c755f7c56c267ceb936b3654eb5eeeb377da149723bbaafd7e76bbdade64dc4b9385c9c9179b60466024b53c7f1e8bebad67d3d50f988c5c7a224afd766b6ac091ccacf31654cb8c97fc8cf31b9a965fcbe6acab29bc310dcab35666bad596b19356af24c8e4ddee53b6f34f71195c248a4c28737dbfa8291a54a0b45d21e9cf2689056b7cd559bab542d59058aa4d01debd8ce63bed5f0c2c5552f2e35b878cb8e4dc2869fbf2e5520f4118a8f453d58790d00cee5d00200d2e4ef43f98b4ae51b0c8e87dcb6196e876aa5c204b0c968e3932dbe6a8159790bcc271f060136daacec739f79f102863e86b9c4300cfb5e4ebee2729cdce49a0036da3357abd5751969f70aa4950bc985e3411d43790ee5872f8ef23b25f39ab339e695e32193b0a1e753dc8ecdc569832ad72ed005ba409b7c8fcbd1efa25a79cbca2d1418cfc1dcc59d441b2aafaea93e1fddc338f6c57cf23d2e0595bbb8b8cab5de542a8f373a46956380d05fb98fee57ae52f96ae5d335026c52664be0ee43fb931d8143dba372cd2633a267e5ab0c44be09b33ecdc26072f8f2d299b81d2f59a5f2e676a8dcc561bef82daefa80c86f39e5f0c54f2aaf9c8e9c16ff21dfc55f726604b63ecd6ac93dcd5ae5b0e548b35c724e8bbb7875938b73dc8e1697026c32dac8911921b36443f5a7593547fae561fdfac6edd0b49e326a2e2e1973fba97ad8809d56c26339c41fca17aac790b61c6ad857aa8231e7ecb5f8d34cdf0ff9d4ef17de12bccdcb9d213bb842b943e28ceabd336f6e09624df525e10ac90da50cc54269b0fe3458abdf7a836816d820d0057a9c8b656831988720c5320ff0c1a72fad750cc36e8ee1fd1e368f45ecabf1af2f8a959f2d3aa98d22194334d20a0de28642e99abe8938f7a3d3a841a93cbe561ea6842e70b73ee6eb3a60f9610bddc8e2a86be2dfc8c2d560147aea0fd4b433d84634a1e4b9317e3d6c6edc8bf10abcb1c67332cb210d521af762bc5ac952152cbd5615be87cd57bf71026d2e07fdce015afc4229445e9143e44ebd225b2cc4a297d5a67ad2da93566bb778b1ac54d236cec46d9da9c3ab8ecb7142a5682a355ba6523962cbea0526152313bf179c8e3843676666cb996b72f9f8bdac565c8e2f46264ebf9f63d863eab4944185165b83d1b367cd14d2e9472112e0f012639c2619a47c91cdc5f8ad7ae058f443910f37fc126daca28dfab1e80725f207271f239d418bfe016c6d91762ce3db940f65643ebc431fcaf0a1ca8734be9f7872ed9289e38fcfa437e2fa7383e6364b7638f241113f0881822134d9823621824b3bb18f453e24c187285aa4d1a867120992166942cd45f38343db61efe8ad83725e0959b45aade43cec720fb0f7c619b4720fd0acc536ff95295b99301c67d0de521fba86713a4af7e5a9e75a37f700cf7cd24829a594d38172cf51278f39c619a66f1567ccf73cd7ec76391c2a0891a146863b6bea7bae615bc6959025e374c83843738cd361df73ed6ea592a663e374dc28bb4469a60dea00378f72db520d462fd4794f07c69c8efb9e6bd9a66519e7b956da36cee41bf700cf356ed33807c45983391dd87bae6d71dbb66d937d4d9e8e9b79ae95a22c79eee598a344a304e774f6dbbed0fb18a383e14b0d755c27a911298232e463d10a8e7ca8b98a9e5cd182742529fda561824397a09faed1e012146d5c9f5ed2e9e3f51b6d3dd468a84e589ffeaa13d6a77a497eba4b10bd22664796bf39b42e1bd0313cda75e24b2e47e93d9f3863629828ced05c598b2bdaa03e5d499997f4d331cdf5f33ecd6145e285a4ccb15b1f5a8fe1717330bf3cee87599629120fcc33cf72cecd552877bb90803a79b833719b56ca2e662ba5d20808c6cc0b99189897954b8b2a853a79f6d599b84d2b6517b395cede008e4531407a4a5f5a6c3925a581a623bee4a6cb38a3b78fe8efc72228429f83631114a0e8d8c722284e362e76163540398272a49bc68a5532c5223df9810d4ead9e24f12408229a729c63ba73095970cc0ee9d165d6791d294f0880c718f106cf1a19c301bca4c9c25eb7f6c6c82187ac8591fc831464212019c2834f12282a884104f7c7a2274b70d193269426bad3804f56f0a1dbb0401b8a312a197aef63910a8e3e7256aab596fe30299a92d9d00059264bbcada165d193179c5cf910fb3c821669426cd2102f116c6495244e27516e8422dedc108a42a80aa12b84b6103242d43c7a1b89372e7412271d8508243e7a479755c41b79afc05baca411fa85e8d2156ddc23100a77028988e8d249fb80469cc88a06f942741ff1a67d66cd0f0bd1867524b00a8f62050576c206acf16ff5823d04c49b3e9a3cb3ca4934ab7cfb9c2e5a67916d1de929c2b323a4080fb661ad23df9ef594788414f9f68da7c11692b26d34884ef9761335eaa8d0b7632ac5236ab085b430a6af21df7eda41b568d2b7a7e81521dfae6ab5d09696d9b2258614d486fa5109b504b9405905bdd09797d9f2e59a6aec49abd05a72550b14cbb7bb24f56a56a46f7fa95568bdf2ed30f51543636266cb986baade52fac9337ba6cffc9933e88816e80bd084f9a1471aec2fb450be5de6e7057df162b67c715fb43f3a9a73ce3a63b6f7f1f1f129b9aa70b95c2e97abde172f90e69cd3baaa70b95c2e57bd99c6991eb3a5d716afd7ebf57a6d5ce7a1ba7701c7a4392f665f5bbc5eafd7eb154444505050505090774ac5f06e54f13d3ffc57acb630c2772bb33dae2a5c2e97cb8539f9b1afd7ebf50a222228282828a8d256abd56ab55cb61852f00a4666c67309db0d2121212121dbe34252524c8a49ae98040404040404547f902492447a49241f1f1f1f1f9f209f3eeaa368429b600208baaf56b905f9e124e299368acee734e127528f35627bb0ede96c8fc9f670b667b31ae5a9a2ba4cd5c555d7565db3bab49acda0fa136d5c6faf4eea8fa9fe70f567ab3fb3fe68f5e7dbe96dd72bdac0b678bd5eafd7ebdb27d63b41d1862582066d3468d2208d066534e85a09445bd146c9dbe90bb4455bb4d52e4b9328da7022de582862e6bd7c5f4dcd8793c86612d56c8de28c7624d8e868c6623319a5d9639d6c0106dab7ea19bf2a8bf8c0f2e40a124d5459a287063f7cb042053ab8a28041134f60718889d1d818842a3d3570b1431240f0308388a48219aca0054880620b1714f5101715cbf4a2b54ba6104e886831b21548e841062da63862c2ca600893c4a2e98c60a80551aef0e4000a201b83d6f9410aa21e187143165344619df80122464b71ab143772120345a2604289195650050d88980c615a226badb5d65a2b36a90f4e7eb6ca8221b46d135da4b027de4e207a26416d33a914ca4f92c953cd202d2245004171c40d2f30c1d96badb5762e91476051ab157662f7c9ad11c548cf1152c81094c3124bac703dc445c532a5605532252c26ed756245679d09adb5d65a6badb5d65aef0fb4ce1e580c4105962c25e0c109abc41227b08b61590f19a5216362adb542701284b7a4da5a6badb5d69a546bad4c883093b0492c39c29ce0a09356580ad43720b18109a0f82c7912649158920426adb51388e964098f098d4cd8800488a02753ba4822f4530300308977c90c50c4a085101886b2e4c01ec1a48b54ebe0e0032846a40461083d54a8142747b6694496b904069c2854f0c30e2c58928427602005d604075e1252492f69d2c30c4bccc0440849ac142552604aaa64b7167104d4022b487000030e452c71c1921798e0c813424e30a902074ae0d4177c6d203b7425c9a80a124668210941048f33894ba8e098c8587483141cf0b0c007223f3628898b0d132b6cb5779ad084884949bdf6871ec1048bda76460dc3b0cba46216c3300cc3300cc3748897121dcc09c410841f6a00842b58acc0054c2692cee482266ec0450c3b28b9020713e231c7593cb6c4081891a44b6c1654870816f4001445d210860005a7e2e00776490dad04b636db6aadb5d65a6bad2d8216edc4c2048a56335663aa6badd50631f4b52a09c2d75aab4d5193202558be4e9c5aeb44c231ac6216c3300cc3300ca35976b32cd503932578a888e273030ed6822226523c7671300ca3ae24b260088dd769146c3dc6156230822c88b0c2091038d828454b8c9c0608c75ea25def9baf477afd368b529b81bc5c3ae79c73661f1626f700cf987dc8c772be98745add25967dd8dc447acd91e6d9024dcb69629df3ed0b658eea9a6b302574c1699eddcc7d946e06f2f2d843ea5956cab0e6b1e4d90951734ecb5b83019edfa8146db6653d2c19b219090080007315000028140808c442b160340b1355920f14000b81a24866549c88922407421406310cc5204200310000420030862873282d80b153b68d84dbee980c5780af3ddf2921e634a034602a65cb590838d6324549bb520c349d0f780a2b75be24c95ad5883119e1e2d5edf6cb22cf2a8b8a302e2a51851769ad64dfacf88593a2044abda5dfbb0e318191ce04bea882a58352ae315df9037291a245bd5fd742816b467d1cfd91ebd57f2ab12b6c156d72c448652de12d16916a0f8cde7828a219630d38364a7aad0b7dc93781e8e17725ccf5f655ee22aded8962a90c7cad6e20556c31f5f3b5566c6525e04a63053331f8b8dba9d53652aaa85415491ba5e2d90c0b1c5476ff5fc8868f93c83523b9644e49abc08bca256d0abe6461b446308e63b8c27b739fd4e3aec6337221ce47a1bc487abf2b61c1c5834c6601c2582d9aaae16d13d05488dc0918524ad778f38022910edf65590b3aebe6798358939efcc3284661e8bc12c52062c5049ee16597f2c20e6479314bc5a1b4e67873d8a2c000f74af8ebd92572c3f1ba7cc8b803b9eb1aa9e877ff6d00b96c981a8c38eeaf6275166cf826e59e44bd996290379f347d23e92d9060101577979f6c683a5fdfb8dd9550bb4078881cda29bef3ea0dad7a55b619ad5e4699d6b5bebfd59477f0013271dc7a9d7adabb9c1398142a5de1d35ef55851cb67340300809aade69ed82ad24207d230c139aca3d84355b56ee625eb81b20ee0ca5b6be642f91be489e08ec4204200b3edd00fd2726b64a82bcc6f0ba8a41cb372a33adc06d6396b6f14af6ae14d69a023e340580072b41626bfd4fdd340bac108a697585bdf8d3f2ea6530ccf9ad7be22226311d3816d14b650361cccfb69ec7f09157b7699478be3a56a4aa4062568ff9ca79147dda5ae4862d7e1ad73941ab8b7c73d9993b0d2f55157a6d842d8868f5190fbf144bf24cd4b94fe755e051c320ef4801af2649b05ee1a4c789ac67e55819c3fdc13910ee08503abbaa271aa0bf56f62889a169c1e85742ef07990154d4a86b804548890e534cbb25a0e0a41469730755a176047c4682c29b969f73517fbe62a67682851c61a28bfd662b18c303500a10f17db4c24654c3bb347636fb3fd4fff676586f5231202f5ca66d92450902432ce4b3078a576d18f369cfd2bae01192112a0e1f73a2ee2972f89dbdb5f25d2560d29100bbf58f19bbf96cb0c2025a21edd8b6d7bd7ce822c9b5e5efdac5f62fa5fae4db3df9b5580d3bd18f746311308edce3970fda95753d7fc7670ceccbd2a8b90f92b35910a6bf46c1ae707516f981b62549749668d45241f3d31f7368b48a8d01a8a9c23cc47172a71ac0a9e9c91141eb99e7227f4d4d098f1af1530dc18a64a56da9324e5919f70f01babe137939025e317da0c77fcaa7450d35a6a4793a9b35e114a99c77a04434a991f348669ad8c7c4a7654901e78bc0d3d2f96fdf7cb0646ce9d7d819e8ddb853644d140cbf66028b05e214fbe406bbe7902ff016d9726645dd13843bfbd5d345c622cbaabbcc738c0ab7cbcb147ee5314104fa3ac7fb9a38b69bff1e4d9a28509277c90afe9c7561686b5313032f2790a87877e11484305fe0de00e018ddab38e2c64db205953aa613c6099bde45f0ec79c5efb223fd2633c2caefb7f11422c5b38090184034ba559bf78028668c163556ec0f040c1f28d99f9bdc17629f0d1cc23201608519e7359059727dba7265018343bbe8931a28f814d9628fd7055fa09bfd549e0c5b100a733000d8acdff06ee8961a5eecfb2a87d83c398239d3374af885be0cdebe046218f640268ac573eb97c37ee25c0e1636789ee048ae442eeabc72631366632399a25fcb4967fcab79d7ef7948852df63ac62afe26c280a1bdaed86f8a25fa8ba617a14f93e4465a2c28566b268f9eb30761cf8cbbbe707a1e8b00319a0240d287c75cd3afb0c33cc3ccb6c33cc3ef34c33fdb3f802736e5695551872ab53b0bee7c168048e5429ea663f6b46b3663c7b5633ce3ac34cb373465de88e300aed7470eb772f4403c1a385186e0353d38c33ce32c3ec33cd36d3ec33ce663356102e29f812d2afc6ae6f3068df0426c2702d2804368022b2d0575e2d13c52b9db3cafa88f8a124e727ac2e49e88f3aa261c70f96faee102bf814ad8bcd204416427ff4819c2ad2bbcaebf4f626c378018d9f4b60f7e01338acada1bfb204b3e2e8a7a8d37b994921a16be01d5b57b8aae4b3d72201d3e88a717f35d507c60b102f17efbd238bc99d124ec86b250c399cbbe5b57500757377af232df93907550e632fdccb47c37080ccf38dc1a673cdabc11111241daa139c14050f1ef99e44b8fe3c348a484183e9d9fd3970110368f0d25d3d3331e34686973dab6b82974d6b6bc69f59a65ecc7e8d4e2fc1e88e665e8b8aff430e10ac8f42a88a747d0dc6cb794802b726e5c6327f6aa748305658416213094395f9a986006d0ac48d928b7bfbb29da5e9b3791405607d9ffedde572ea5a59ae2f8bc8fdc2e21caebad0d87e6545fbd21c2924f0543d37ba732a602271f46b7de51e1c9cc61a9cb4781cefe599e824bf1fb6e06d08e7570a79c93af68c75d153843e4b64419de2ddabf4715fa48150d640c15308cad3b27f73bdfdd6d742799a607d87f5ae42565855ee2728536656411ffdc3cf4c440957cf556086b7a8e6e986226c3407c00f7363f973b75fc1a272145269af84ed57246068dd0cbaa85a51172c6208435d261c1fd5c20d019ff121780239604c08b363438fa98a3ea2fbcd9b4d2086b3779efd2ac770f191eb17193d6c841027a86c35b7dc0b4c24a2db4e354fe61c6af88d169022bfc9f55823e7b87caf315f52bd358328408dd7439ef0b0f2d0b0948ca147ae587815ecee814659cb5b613fe9e57b36c6ddf9a6e83ec241c57d500310a6a851e8ce466130dfcc9235fa4784a632df46d8710e01235f5dc7dd45e6bb98fa50ab8d10289412eb65b9e0f2a69646b0a38dbb5d552b169b05618f4354f86b4ca00f884ec574e17d285816e68e388cfa32ed69b832b51f1fe2ba1f8eae34ed8ebbfaad4857adbc4bf68ad4fc2474eabcd8f71f6a1a285a66a86a91080e42bd127484b8f80de82b3755be152e177a1352f414e9b10b5f27358ce52bd8270e08abc2cfbbc674648ecfe14f1525088a0320cd4c60f072135873fd6866bc8374263ee86d096ecf42aaf45584199e01971049742e06171e597c7aed15a42bca7a4b473820680d664e611246ae8be6493d20334ed90567bcb1e414a10d27f9d11e92ef916f5b87fd70a6f46e6a5d37ca533a80c7a45d7a101c1462a02cb0a5e2ff404c46db1d1866c82b0ce369ff92a5b8789742a3e975cbe90fd0a34e6f3d1bf78a45d697c69d89d6bfce339952a67254ec476009a58ca747a0e3178f4a3ea6146911bf3d6c55fbdbb63be9b00f21948012fb0547ddff1250b522cb37885b31838c3920b25c73322250b277d37a485033b39db2bafe9687e043c541c31540e13c0262c150f076fcfce11424157483c664f10b14e13833bbb4c29060259428455e4f74371aaf1d812a8a97906fe72c6be99d1128657cb92541dcdf8c0ac260856010873716b023b113f2103ab44a779652148ef2e4a286bf7818a2dba5e290cdc65f8030b0063d40fa7c1869db0720274bc30214eb0ca4d205e6dfdf07cd86698bd971e840caaf22ca0548405e28bbb56b36000311d8d5b5e89ab69433687c24c2e9fc1510ead340a8be0731b180de0e0a843f01f14f81777ead2df155de474990d3acb6165362365b699966a6b2fa7803e09f1faa42cc99df38cbdd257becbe041e9d6232b00c0efcc092b08d58f5413d1803746be96dcf93183e1a59c3fb62cf180730eaa9ef5cab8925d300a4e1a7252566f7cf5afbca11954f14b909c6f0a5e4f2340d93262d09096dbf417437eb00c313a5626a3154559f6f00fc5f49d52c2adf575293daf2e6d01b371e7e0d3bb92f0954ed4b2bfd25d0f0c771dc5f10a2530bdfa766d1c127edc57cb1dda985b6bd3f62d868524d7ff139aae3b5f0e3f696a43d37b4ad837e724bccb73b36df5e6341003d0709942f81f14521ea7ca92ad58d39fda118664fcfed564f00c61256ddd4a5605762ae561f6d04c883bd1afb371d7274221ffc89b9369ae9863198f4d09067b99a6e115137848eb5b8ecfd1293454768fe888b239d6bf95e768818be055fe41cdb8eb474edf3139b434f68fc4f5c73cc81997ad207f755a232a1e23cc6a292af80f02370f2044234dc2408fcdadba3de093e69e3d0ffa94c0beacfdd4421cbfc90abd547b6556998aa182ab54f1b00f31d42edfe348353941c2d92c79b7a6a8a237a18b14482d8f9303d6e949a84f3ddb84bbd72c0473b0e30494ce52ecf8401d3a82f31a5e92453507f33c66e013d1b811b3f02e74f202199b352ed0006fde77286dd4f0063349c18a3fb9241bebb19c36e6c74f259b4fb6e9eaf80f129001318d73f9775a2d27400d2fa230d896f75ef6338d2fd31582de318a09b3806cabb8ee19bac143862b12447ae7e4d36ee56bd56ec7b79720007fd4c94b48c8dc3cfc18e324a65cc4049d8818beefe17730c4c9dcc4c24ce67bc04d267fec82ef6a8a025e1671e9a66c38d6c3db865fbd111dd78cf49c4abf18448639ef63170f4fd6d0f7938cf5c45dc8c3d12b55d119ba85180b188fb31eca00ea6f430f71853eac27f08e23169ecb429927b4abf62d1336082a17e7eebfbcdc91af83a144760691378be61ca0874b3a7b24dbd605281a3a1597b2a5715315343f9a95b3d4341f5954dd13416cc20e024c9be1f5feb514771dad473eaccf044d8066397b4224fbc20d40060cf5fb0a9b187949efffa09a5fd6cd9bca3edc8629393ee1f3740e91159ecac8be813485b215fbe1320fd0d815fc09cb348d485609e7541a996f9df1758e1187c82526cc0d379081223cbfa66c7da3444e45724169d3a9f4df20d9c90aca9ba5c9bb91a2b351b3c1bbc0e7cbe7b68fd7b92aca358f591f3ceea6bba73d6fd7605bf2c7869e8548f8f3dab086aebee21e37d3f13429e514f33d6d7d3739c691d4f580f0d423fd1b268fce5a87cb309fb12fbb2a54e01ee1a424137f417f036d357628ca46ab14eb4ac435649cc3c316f4845dfac0c86f4a66751c241470426dbc681b0a51e6632668f9f7d0263ccae237aadf894cc22c8043d58fc2cedc14c0d23d5770b9e624f41f4c226bcb035a981c9a2f183bc9e1e4473fcd747052032b4f7eb99546155cb7601489ebe790ecd56388b4a7ba9dcbe1db83a18bcc7944ccd372a5f22a4da8d38b372fb01256e1119ffb703e98771bf94334dcdfae37a1e1f8e54367b21546ab457f926d9bf64d482bc1ec93f7f581627a6f8a79327373f8476859994fb8acc4bd190fc4e5c863fbb16ee128ab72fa34f605236cd58b3289f6c7d6e7e49c25b2dd4423f6a213e1c26c74abfdee1d1319a4f0b933653ce5425b0de6bb1d0da75947f9c61f597a4ae5d08b158a1e5db5d07c0414b8ade2ddb7c9ca802d89d583cc36890c9df5d511f481f70bd1dc583684d0e4d9071160ae33cb2e3a68d52c900999c534508b8973b669194a58a9721db51224bb6e346cea9ef184b92b9e4b413b7b803b12837ea2bf217e1d3aa590324c9706fce1c2c39b1b59b249255c348b0e2cf3312fd9a9af3aa38323265f86c7a2720dd2465ae1bc85a55968bc1f619b154c9e5e270d6523d5febe9b5f74c26c3f1e047bf5e066752ae93210b94d4436864b5e6fd91574f0d24b6778a466b35494a3c4af00e44c120043e5b96ee8fe7307f4553d9d7aab71b261fd3fb6e13a8318352f1ebe4c97ce0f3ce958f9caef6b5cecd4264f4addf6d0df3c1c220ea2f65d252e2fed181287155ed224c7a8ecfbb73359523744bd33d02de94f972e4af4b2ed66166d8fe4576962495bedd85cc1b704aa17b18a5b2e5494893de844409acf61df454e6942138e166c17d19f787ecf3ec42e1d1cda6a6869fc7021c66aacb8b140d394bbb26411cecba7df6e361e0daf554b50753b13b19b779bc80559d3037482493fb654ca8437124d1874b39e63ec4e3d4df6228adc6705b8421af5af7b008584b9dd8419560ba9243842989ab5fe431edc3c45e7a93734b93d212b7a845ffa4d90ba1546ee1c5cb1c198948c5f73e7aedd06ecff7e719893e30cc693b42d39345583048b28caf10ef3d6b8d541c8b62e71d15875a41bbade0f1b94ad06b1620f2ac365e35033780fad3f4e0c0b5767b396f1d154e8ac7b68dba0206684d5420376f7570a5237bfc43df819e348449307486148ec48b946888f3737c7e0d003a79f7f5943e1c741b76237f2fbf5e486e5fdd169223deeab725fc67a176f4a336f1f12172acf8f50e8fd618505666bb3e7eae9cdbd11575d399645c1064e35a2d53265a46de7756efcef25683f267f420e617105676e5dc2ef42225057f9da5528ad82ed65b52411c66c614a98aa4ad2492723b525eed35279a9e13656e96fb959fae442d2b4be7f08e350eb2db1782c5710da94401180e516adbf7e4a6ddb0228844e8df57051546a459346aa36a644b0234b79d23fa636805162de6961be76c8a33c627bbf291f8b14ecfc6a1de34f7ac4aaa08a4700b70ee99d446daa9d3ddcc353c39f3e41dc914b6eeb568503c923fd87ec873d22ebd292da7eec612e267ef02d12bbbbbc5fa509ecf8b3aac004782b777ef0710135b27b19a75f90d53670972d496857a36aca67b691672cb7e2d79a75ea78d9927356bd0df6f98ca4069323ba2ea9430ce23cacffd2b8cecbfd7affc299e453d12114a2fa57ea59f0762132def9f591013fa42260918ae5e1d2e680b1cf3b20473eb8ba04068f0ae0a1a7ee8841c07540ed00ec5e686e02d9acf9837bac254a59270cc5c20ba5fcbb2f653bdc18b5d89dbc2c269122ed08d5327a99ea08054c0ac1daf94ee3dbbf0f3242aa4bc0baf3c4c29f5e74941958a3514b1522b73c55da6c7eeca290c3241e591032ecf46f7ebfadef7e8caa6d175b55e498f75cbcc0aa24ef6636ce76534f0d62d41edabd05dba4894bb0dfe65d56c5d1bd8b1dbc583f8661a84edfe4b74b1ec602a8442f3b810f45ac1d6c1f2a745a4e6a026cce00f864692679e92c4d83b017031225fa819019ab23781de149a1e00a5fb88afe5fd8f649c7d2b2c21668acc95f0bfd99249f35a5da6ac6518e43cf1d27eee259b7502d84a2a4aca51e0251fe9c12dde64a6aee83d688af522ed638794ab5e1ed3703d8f7cec3e95a0dc45c7a159b4f5ff39b07dad590ad5ca71f5e11d25fa2a144144784ad420817f05b8bca9ca92f09320d61a499b0afe93eb7cdd7a4218a8b639ec305710589eacde7e69048d047ff51636866e331982f2bf72d0b159e8b2cfa8b9cc42b079ae65a05d13ca3af9c8a0c11bd2ea094c8e73f8630b60186b0a36e257e8c18a918e6764db824010196c1a50f85d5cfcbd993b8bd489768d56ee560a09abce699f599d48cebf95d56e92d5bdca41f8a949e56a8627931a8bb7e91d8e715bf29bcf5f4833e3efbca1c13b6782c1ab7a2942c66890e4bdf218ce463942f53dabec96ce2c2d5bfa0c1e968df05a2e8804043341638e2c9c744b845efff2cfb4e98549c43f13a5ab327e4844b62bd2d69e3a8e874898c40c6c9d84e080635744a36c20ba4b9ae81a398d4fee309f35827041dd936c05a6b8711cb84af06c9c5f83b938074b0b70302a85cf783cff201c004289084d80d8228bb09e96435ff48a9000ece63b890c150b5a2076bfae81036ea2acf8056aeac6fadb60026ccc56339405b59c27c39b8ea695a5f837eaa86b0acaaa19af388f0f748de401d0ffc6ec802f6a75809039a3f892fc4ff9d465ef2cd80a8f7c934826f819a0e5b775823c2d21d4ddf53bbc882605033a62241c100002e4a0db783b97ecaa0f03ebaccdd378a89a58482b58ef44d6965ff8c9b11bfbd9d223260aafe8d39dae22fc0957511caee2b7d280cc07c269e33def69c3f666da895563ae5acde16e3a4dc3e81fdf72d8353d343176330c411668bbdb605991ba7450a1022f97b1bd1aa6660ba22f604436d54e48faa8a27878eef14ba5bab606d6419d209d539c10b89e95bb4ec403f103fcca3b76c98f2ae1b384fb6e2e64aaeb714f95a4b1d03b54fdd9cdfeb3ff6dfb688aa172bbd6919a38fcc383c05894392fd6934240e33fc8fb10857d15c64587fb019aa2e9f810d51002577470d7ae84aab3090f742d708444275581b4099c7a6717a264e81d56992f957772894e7ae257b9f4bb54ec17c72e30bac7d55bad0aecf8308e628fd18d1683c0130076585c22bd8aa32ed552a2b6f5cea4dfd8bac8a2e53f6b977f61b19733d8d673d7f144c1eed122a9dcd7d62d21ee4809d00bd905adb09f2ac11e7111fcb142b3986b77c6a49133c17c46ff41a1cfb025d3a0763f5bae8b10e589a3eb3b0b3f18d1de68389bd11c15ef083b9c72742fd2e1b9b1d8761707616eb87a59e410a7f2fd5d75c26b7c8795c13c619aa2109410169f193babda79cbb671b23b7b531dc4513a91e6bc95b972df432e7d8e9b44ceca97831943d2171ef2e86c9c9deaed1d69c7e9fdc480bf2b2eee0a6501232dd63fd3a275c22cabfd5acbdb56b725cb904866a37436946f317c46bf3a4a5b2cc83f32a2de03016425ef89f50058cd4586ae8f6d4a1b275ac6f8c7cf8509796676e655fb9100669055e5273fa1774ebe9084f5ce1d11f283cc526ab94c957be16cc23862f890f40e87a1b151594ca76b210293b1cf180784b2a52d92ecd2dc11f7d0080f58d3adf29dbd13ea7e19a69dadde20e8dd5140402d269a1c6649c6c1936179d3056a63f62bedd6fa923d12e543e307b6098cb078e5cb7dd70a8dc3aa447dacee3549cf6c81ac346e6f0f153d23255551e0349c4f1cd21f0cd0b3505a2a8d7db742978a0547eca716422376d3606af548d12e0f3142cfbc37b3086c43c961f4dc8b52186acf3360299cd053dabd0971f43e2657094cd0f69a2db9138eb86eeddb7885ef77045fe4df5b22c1614c1b6590bd46e9ef635ed2f8a5eb23138c023435772afdfcb95336a39fda2cf6307f221103f221e53482b70bbd25ba712cc165a5834a3841452aa850c7576458e935042e18b22f7a5cfe4272f400a78e78e07b30043f9840449f278f5b1181cd2940cb0830081299d65d048772791925dd610b78f25c45e904f4be6f42487810439ab3ef05dee276e1694e24d2a6a0d31346fc65fb08a3c251b0101042aa385f4e9643a33f726e2823e697a81fd4424e79f81b156277b5b575f8d6e89a4efc31876f7d6c311dc8e7f8557b3c77c2ebc7aefb973529dea1166177a2be5d35f139480f4ab6fc8db969558cfe37007a0c5015f4ba729bd2246cec35aa0f8621ae520e01bdeccd8f6f68ef6103b9c2645fe324956e35806e3cd3329dc37e0fea14ae2196a76fe7a814f179e8c4d963c2572d38d046091f91f7c1e00de87d240a440ad61da016d2f9c72d23f6bb54618cba9ea1c7c689f28102ebe63b61f5f7f4fafa10d7ca471f8c384553a8e044c8074239eb09b39debaf1bc1f729bdb08e2fd992eb1ec43cd88992ba0c1680639e0b1a74d28fb2532b10758b5ed6c4d1292e7d050481a775c80c31540108ce85f64156868ff9b5d79392a911dc8d50d61208f4214281b9c335f45a3f4c9af488e0c56fe8f55a6a11127bcc006a4873b0455aad90e207ddac2171eb31d876c080768515abc18180b48527beac7667150da37804ed678497fb8498f417621b122b5cb6046013b701033a86a3a821fd3855f71c0151eb2cf214bfe4dcc63b211a18505a2f2264ac297e6797f9aa8536bcc8f93784ba82c58d928b76efa7009fcfb90e47fac31ca073f6314a615803e4442c962bb0a930cb511b433de0932a4eda7344b9306cc1415886ff047d0f8dcdc620060432fc0743f4dc5aa0bb062817f2fc623111fa10c45471b341a642ad04dc7bfbc7238337cb306002943d6dc613920a9028ee3d859822619e1545896e90ab4f80db778a75ce1ed0aee4cad40c8ae26114d5f997500544232f076d835434c08afc4814eabdbc68bed99a28fbe252b43394add3ea2e0444da116562681162502cdb315e731137278bafd897bdf8cfefb4802325673ba32a0e60a2de413132e887f7221698a6d4b741ce75c52faec39b40e92f7b1ab2db74d281f29faa1b77e2ddc2abcf2e13da71072454c24da11f34624a20acedb0bd8af4d4e4e10c18e8497e32000ad3e3a08e4cc8f958c591f43070b303c9d2832a373c057cf05db609ec35d90fbd648f6c61144a7d4793abfacd11b91a0147200a60c977b24e70f1575b0f0fc4fde5ca6012ae5a7a308ef484558eccd7096699f60daf2f4438810bea813271095111e1943b848703938c698d5b6c9965a48c4d439b8842c8aaae3d06ecaa2b7fac0b3af3f981174830a03d48d467b837584c9bf72969a415a48c16d9be4441859005bd17eb5b31eca629f9c172262748eb00a0c8361386d8fb3fea2ed0bb16350a3b881aafba51fdb537e090ed0d815de783c0dc9c07a827bcd92b6b9b21aaf1867e04aaa7405c03e85f4e8b924682dda0679efa6fc079042912770e62a4447dfcb4e13110461cbc8d01814bcd677aa8ab779757592cf0f191a516ec6b7c85bbd255e4858459bf20cb47a05223a1075aba6d70309e06dac5195bbd3d19fc178cbe6bb496e3ec262210ce9fad44e9af8ffcbdc19e84b905ce832e972e2a0b76c3b113a7c5bd7d5e95bbb04cf55987472e2484c63285f2e6a19309c1fc0784ce9290a1bb4b96a9225bdbd57dbe55a992aa78b8b836799ab567d0085630f9e7b3508ce962bcdbe20773042325c5106b6b97501df9caa51c5ca6ddb6e8c5542dddd4c8d273914025ae7940fd9cf3950879fd393ff48679d1591f63087a13b3e9f87ea935e7ded9cb686acdb8d86c68935b7fd99231d98ba766b13d561163cbf47314833de62726fe7e14f28a3629e6e121619dab7eb00a4be8995c594015c3f11e47816d6271210fd73bed7b26c2e45d20d71b31998961b6ed9d8150febddf73a2f3357d7de268fc5f658a03d0d9105f281b6db4d3c4f697f2c019de02cbf0df8592b59b4051fd0fe60b49f362522a50505bf8da95870aa0820adbb9f4684d0c1c4a30a3a2e9bf8ad7b66035755c4ad9ca6c7e081ce518a03c3aff22b1d9e9289c3d24648f7178f50d64c8506f227875cd66c847baafba026773bc206b6b76c6101b042b38f6f06ccd56f72085b2e1cd154f563c3d53c0b63be6bd54fce73a89c72d39ffdceae02a88050ea9a7d6234a3dade6db795956b34651294f22832fad09cfdb90cd8eb1ed86f9011e39565507c8ece91b76b8e1f8b8147d0aca277621fcfd85924ee776105be1af97d913c5ccf235cc4019b6978d9d749998f576340f5202ee9cb8a3d6ab2ba5a6f9264b086a0818fc016c4a6263eb522fb84f2973c4bc97b3ae0d61388cd88e62e4049859559d9ccf34b87e1681186be535b1a4214063106fbae6d9437db7ae70b517c69a609c04ec5b43810f966a069449bb191f831107840b8e6cf9730dd9cf0e97a69c30aa3671aef35ef6b584acefc85c451cce455151909410c5f35070844c86c6a82dbc6243810583f43df433896263baafc617ec4d179ad774e494d830aacd174429f690f1f18a2a898218693091cb2346207f00c8580b02549a67e87a6e222f9acdb65542cacbf5c21e46d866f8d20fa6af292031e0458ce0e8b08a63b1c09549de8488f77fef0406ba22d2a639347ac32bf7fc4e06577fcd7f03a1d5d11af175f8bc268ad503daef3b6ca49d57ebe5ee17d6ab4e25ceaeedf9314bd9110de79fa8fdf9132500b691af0447154d718dd131b99514377e34dec87ba44e4f95b080a9270c012924282f8956fc7b2b27cd782c3949eacefa9904bcf23137e3e40499a008a3c5f99accffa255e11bd1e9fe231bffe2c2a19126fae3cb907f229e5b12fa8d05cd2e4514d4328fefcf41c7e5a41959d717f9cab968c9071c74031beb7978819a5cf081f8f8eb1c83dc632cbdafcddd284df056e3811ecb1bbd037529f53a5530d459e248def5d89ce45ce8a44703bcb46d9274d5c60d9569de55c9177fa534d75ef770614fc5bc32893bca040c30a31706e72c267849cfb6599621bed84c2dc1b9975e722ec49f5c9e8b3fb53452c9354e68f73906e89b7c32e29b53c694c2265f3fdc9880c84b6d6bed3c6e2294e58f448272ffff7c959e62c7234c03c534e5b8a94f070ba40243da1fb1bb26cec043e77961ccf1c4f9796e00ea9dae0d890ba625ac06732cf1442ee4d967bb1d122381519a1c6c8a8504a1c2229dd23982e23f1ef217a2ffd2fd511431c1965d1dbb288592df2b7cc386fb5e001903639db597489e0aac51b96e4519cf507735e8c27a294aea6622092fcb50484475073cdf32e3840cea460afae2ea29e3e104fb252fbb17f96e926014e9af272fa63762abf10e8d5028b962391cbe7107fc3e71746f386fc303edb8ff99a5518b64d98515d53cef1fcd1d8695b6f59e94c84d07e631f5c1323a3e88055bc543f1ff0219511902d013ffbc09884faf9ffe3fbe8fb426bec2e8710b43d7277ff952e5dcad9d3dc71fc3e01246b093eb47350a25f24099a25b49511849855abb0d98876817159fc2c750f11674aa05eae0e6ccf4053f808b8aef01e8e6d3886eeab5d0c9273ad24eef312b8791ef4a33c3ef82aa596963024beb9ad509a4dc494591b5eb2c123129f10070524dbd98ad58b6a1f267dda20b6387e894245f7df008d0a54aeb43347377b568316d4e31e06c254639c657377aa4c8f2e813c870d9eff92bf1e7721ea9c2a05e1d85b4e5023b484e204f16d325aabc930c2d2014c560d70303617b70a9e84e202e0c950f06caedae3d0da4f816bb4118a5f75c864fff6793c16c52127cdba04996085627283503374a6999a1f068f75ba521d3f843ee2d0b2d50f7f2e441bf5ce8cf69c0ad468e100bee46abfa170b79c5799f62269d7a5df929840c826536856a28cbcaeba2aa719dbda118a94cc328629295536252bd203177732d2ba90f28a18cce6de9a0258cc37e5026b349446d57c6184ea6d5241efc02288a32143c3053eb16adf00376d32b11608af4132969181d93c3c4708858a4ee2e434e2a436c151eed9ff913901bd5a0f110da9b6e87d96898daaed76e90c675e29eeb8b554d7499f44bd5dbd98c19c18f8797774573d357e4a4a2b05d220aeb5e8a72bec4e2325e72a0ff869a33542a520bbe6d3e29e13b454f74ce2bccd04833e51f1a02f9656022a265c450056d4706d3aa173c67672a5dad701ae8075e3185fe4b5f84bfa834f20126dd123e55cd5be1ee9b26797c25353833f2bee61d1268d2158b8068d46155a0c011a1a0c49213923bc3ce1293b3febd40f0aa344b2d9a6e28c8262cae4e1c082bfb6fe42bf66a7b4cc05cb1807c366b02f27d0602b223f6ba4b2b39743fd4c3f6f5eeabda320be42c71ae94a705cb8cad4eac8c9e3498f02a52670c5d02bbead95702f545aaeff0f554ba10227aba662c1627e0af477e9ff4a987d235e5ec94719cefc2bf337df302af3a8d9527c58a58dd6254ecb93a28d73fa0dd687956f5764ece19618621a680c92feb08740762097937fb3fc4d5073f3a0b858d5e2b7657bd616005dcd96a2fb3bcd7ce58a96f58c5b4c076e8d3b2f8faf0d04d8efa774a3c5208b8465a3a954ff886ade9516b0693aafec35792f151d465e5911527b4e1f18ffa02810b461477d2727f561fc7a0b8b864084b38fac476287b448a0ee266994af7fb4d8b4251dd6e7e0952f0500ea188a8928cd34e8688b19bea62c5c1d038799d92fab10bdb1fe52b4a4db7b1db0ec62ed2902a5856df96e500620f72b3a222cc53e887a13274d1083c3aea6f99fe4d88d048dce211e48e5b97612adac63af94cf1d581dee2a115eab391d26c7cee25ea89aefb4401ae287bc6b3799b2bb64cd7a5bb47b21094c1afc4e34b3f737ea42ddce7eda72c0140e29698c0f4839c48445f5741b898237f444f5168a28d08363e18b2069472bb55016b3f31690eb61f457681628d7b761e75c8908ae06b1c5c0ff36fc353cd7aba49e608fb43b390dfef2e2463761a1d40309071aa523e512200256e13a7f2979ee16666a084f7efeccf5d9a4c5c4cc84123e379800304616be800986ced1c0d27b02eff433459d39292702d9ae23f79b8eb5e6d897721f4dc8b70cb5dd402a27f448f7144f55dbb44fb2965efd7fdf4a0b9e4cfb70627c576f06649135ddec63f31cfb444c4d904bf1aaacea0d1544642ad14a7d85e640de56cc85ed74c3b490dbc6c1474c7f3e56ccfd452d1cd96e20b0c869d94d490c533bd53c265ebd2499b47909ea9f0c2991a8c4fdcd082628b6b518ff485e332dc5a0cb16203c3980adb325eb42c4a0a0c6ae41cdf0a6d302526f23c0f43281675a42c6a330c13670819f671b85fecbead36736df416fc188b8df9a97d9c26ccb7d3f0a7eeaa35e155d722847efca4cdfc68f2808e31848fa1220a29df4a7792a48c6e713b70d603ba0e1e5d3d774b574a379043a44c92c06097c280d28859849b2f2e117be11a667741c7516ca18fa0ac4c48dccb0046db71302fa4668cc16e18be64ed181c4568f2421bf1911a2abc6c0c4579e6dc6492fd33cd3c9bd6a21a5a460ea44f7173075da62b7eef4ab380fa13105b42889da40511bbe8098162b97a50c48277510d182e8705c677d28ad836fac4dc3aee4d10ccae542c062b333d8558321eb02b412c0a8ac4b62a6267a00f2145111b7f5696629481445316219e804cf34b684199888a3a66aacc6c08bb022c2c788b50fbb483d59e4371505a050283b3a4547f414da9c045753bc29a247bc1ba2b85af867346825bc106c887878c41d4e12532c7a89629d8f28f3336b25d02a123442dd8c8cc6dae691e857519d24d2e0dc3bf362d6b4ae3496bbbac5db9ca022db28218ff95ae8977fd0cbb46b30dfef56ab5b6f2b31f1a3543d77b433d46dde3af11982eac40984f57d09200b1f62b20e77af3a922c94438a466b9efeafdda082dff9c7312f0bf36184faf522154dbbf0e7646b648fa3d0e89e8951e17472880f2ebf93f0cea7b731370f97d8c18ad445cf7bebe9d66d7864a8de1012ddf6be514c289338d588363dceb26b054f79069bc20e14da71c6b9607760c815e50d8b622bb772a680ab967edf0cf243b2e6aad2425708fda2a61cb744d05c15ea4b06a67c67720023187b632457b45895eb0c4aae6d7e20b5958cdc6b39c7781d1cc03977024745bd43963f2df9c0501c4dc287ee1fd54408bf24b045acc7d9f9d453e3f95bb4d15aa9b95d476031c01d479d1fd80805b4961e763c6896d095fbc994643e17cf90f04c01dee193614c85847de5d1dc0068fd6b9965a061932400d6ec8d051aaf6542b0e093ad20b8adf84f47afcd139ef02bbc398fc8551f28a3d653c0af5d63be168d99b4f2bbd7b2b8944b333b0c86ef04115ff4c354f231acb95264ead710d54bae5e29fbfc5ed298066a837f48cd8dfd1b75be21829f0203a30e55376fd4decae42022b631945ed08acbd65369a505d81bbb8a60b8b2a57ce60b9df67c336e9ec60740936b5529c1dccf810c7f93af4bae9991a1319ba3f954cbbefbab47d5e58d18fccf77345bf921a167b74c8c13704e0b9efe3dc44cf70cfa1033faa8ad1ec01d6fd19c16f6707678cd5db877c16788f2482a23bfab696902dd20ae58bb5c1b9bf571cd8337d075b72179a70ea4465a32faecc4e1c016c3c848ef441640304344ba1f4bc6282f073b0390e68fb9deae5dbc300fb0871fcd811d407f4682164d19fb713900a25b07db24a036ac13e20805949d159bc4eab5ce75224aecf8240fff9986bede5caf7dc46f57b67446b5c01ab50686dbc9c295cffe34e8753afb5146b93ed9395775428c721d14a5be51d54d22a6c2ed1d07f8dc4aeae50df506c1132b0103606c2c1c2c62befa16c0949618c114506e4998958e0d89f4f59f86701b2a887ae706a6aff228679c82072226118e9c5326c61a6aff92f425f09c5ef5852e8bfe16736de6be60422505aabea750f0ef1ec5a8bd4ca9d15f218638010a277d7f056fe4a192be50809668a6863304f139bc010f374e4dfc2c11a03a95ec5f002d987deb925eb24085542c21895db488253b789af4b52edccac909c480193acebc0fb12d38a669af858623426caa438ace77d7ceec224f6ca95947cc60f54a1920bf74eb8aca417b669cc1bbf821c0b4e0fcdc4c7f72723d4008f0c7d3f35b530ca4e7afc1fbbe00db700d02d30359a6bc1d203486441982b43875e81c8492face120be679ed2364aa0b109b28c61600a254466895c4cdea7fa44050b5b639e6ffae98044b22e105890b2c081d28da197a839be743d777dcb7dbd1ab352f379613c7e123e59ea0eb03e7b994b29f147ef579b8aa43264d35e563e88d00a647f8ccf6b75c6b5e364a3a1b2ca0c1c4ed469865ec2d9a894b6ec319e1123ccb5a0febc51d007f4b2cf25ab7a1f20b35c594c8bb4e8c76ab3984eaa615d7d22188f7fa36940b488f848295720eaae6f983483f2f63f37fac19c840c72f432f1ade53795c4814a4e53e65be620e473ec20c0912a0049dc7f1e4ebc3a10a390889c48a3c8b33250257d0cfbd9f636080c134de7b45c8e0ab9d542c25127a500d10446c10014342f4e79faae9b31831f9978a4a2674029a978082587330130989c436a3e007b088635d702f42454ee604fce8d8d4092f764eae45787970d2830bd07123d027715b156e93213a3d1b7a9d575f0562fbc0b195c6ad552b64666d2a26a9cc96e4b94bf446f9a63a0b911504a83b00c6104c7d10e01fa6d7424a295aace0f1903f5d8237848d6a7da659a73b11c7c0263ca8cd013e8632620b84d39d2380ace564949cacf064b53915bb315e3423ad605a4440bf1adc5bc412ff69832995c21de098a7e6ca212b44e343a8b7892e705fccd89b4778a2eecd74d07a4f260ba22522c397e68268992c525a1b8fb363ed39e3a06e0502533db536c55abe54fd41b0494a62ac885d8c89ef4ed3bbfbeb8754865c0f4f41eb9b4536d232ab2490bfdee6a3dce37cf08e8e7c88282eba0a7315b5637af05f44ade0716fcc297202793beb0e73f6815cbfb296b6711aca316c4879b2758a0210b59927dbfff4e088a126cd70b7752e9ed9530ec4d6101521739e654c65d0a1ff2f4a9b6280d228555a986d12198da7e24f50a4dd671772ba2e405296d5177a63ed9ea9313e0893dd196d78a4dfbeaff3f4bed5ed61cc766a76ef5354221dd1090a8f607f3440e300f8cff5632a15feedc475679c3a4883b573649d30277c8f840ca9dc55aab248fa2a0f35a3f52bf7ad04d6baa1a55199b3e1f91824832f5bfb516b7f86666b3a0705b062dbe2962ee2ddf3e52683dc4dabbb3a988864c3554bf710df662718079d771247c0a7402894d181976d5ab272b459b73d126e965c29c0028c434e2235a3c5c224ce975a4c0bb8cfe356b4b992844ec2ce6f6a7f3c1e2d09bf72a94eaf4961180c3d7b74796652905d9a2b1636eff7d9874aee9b6adb22aad91d38b84ad22c120062e0ffea6b1484b6655ee7af767ed3ed743d0bffea239a10adb3ae586a03e2041bf6830ef8bd79c3ed5ba85486b3e6258d6c1c02387660902b7088232033989e7041bb5b6faed90c478c5ac806d1f6d67938111ecb2680321e0ace82e9a2f29982c85ef8f196986a72ae75ecbb5f033698410bad89950caf9ed3aed18b97ad2211573c3b79f7c6d1f73a9a9cf937b104a495b4b83a25076339bd4c502c4403eec5fc6d80fdeafaa65863f25a058ae8c25fb44fe29c2994c7889884eca5991d1239ba9aaa244b1c091bd2dbbcf597755ccc886c989b7704766a4b28ea0643c203501e94f6428f382905a22f8341a60a3843f47628446ce84b92e100a9349fa357df5aea98c627e6476a350e6f8a9d26bef8edcc41984e9a3be00430e7f7925707d39ec346f043a2695837e285968ec1c27f0278437da9f7c5f0c36bc9ac5951a47219ff9fe1160df31b301b5d0a4fd41f797874a1ce56f9da0538cda0eab924a5b2f572ac11f8d967c50bc6714a5b1e6e0fa86a9660dd9567725549334a6ecbc9aa53338b3775a0e96d0f9c140848f1c2b561e70376439f12768d2cb1eb93b8cfd93c005447a883fc06fa425887692a3f7a41f2218c9d6aa29a8b482601a10dfd389f037590e8f89c60c9fc80ab281857c1f42a5c9ca6b08d61eb0cb3f66493106a42ed3a094582568562a5ba3a7d48fa023f7ffb0a483a6be55da97180b74d4635815debadcde82a945294c5e9bcf87e0c479c1af30534b1d31a7f04919335ea3c0cb330af54ef02a0fcff174bdc00d5afbfbde3d8edfbd91cacdbcef95da60f3f98715335972056d2e74c0c92c9852c3d84ef8caf60aa6cd264b64d77b775b1155a6d83f4e77a2fe8ae355bd09df13e2f5032d5b4e5879e4d1f94b7007090914736d51c6a93f7e0ee4d973fcb336b3e82376fd05e17b1ffa1beb5730f5c01953914fc8cb4191c15fca1cf74e24eb123ec1d775f002057bd3eed32f8e2010eeffb159dece0680ffaa1fad880fbbb0a42dfabc344a1bf3587179d65719783c93897529f19d928ec15178f4905778ff0d75fc931d420baffa8a9e005d468e0f4d2948a3fd14ca271d3abf3ad05184417cf1c33f36127b0bffd42a04c8a04dc8ac84452230b3d3437d7d0678ef08a0669ad45de301b45f4692247a02b9d62436a53e817469114f80d5eb33c92b03893148e40a73681c41e864ba4e58c51ca46e6301500854376a36d9f43b15aac3a9cd4cf932288ec6d004f1540098d9a8d249b70a6eacf8d96f3d7ca9b009baf712142b4278c13153930edfc9d22cd95526205e014e75ac19b7813464336c3bebd4fd2ea419aff987ff85aa99e90c2c0c8cb19a891573349fef9e0e1a962bd2c33f0ead5bb7ff66cf4155139f109e7fed88709791eebb49fe1c152eac30afb1186d6e399df092c4c250264d5e682ab548e053bc2e49d0efa3b676eea8fbcba1ebc2e0ee668aeaedb9ad45917c5a385cc12dd4cb7521d1c070c28a96287200dee45bc22f2852f65a9d76195381582c00cfb14e019f55504ebf2b32b8f874ee3a4b83967902ccbf0f5049add8071f3d66bf560b3364b24500022356098a76a2409d784a42512bbb8f416d30dec3452078fc748e1be0e93e01c08c3a820e20b5f1baa76711e887e3b7a1b7a797dcc9e894e912bb39961bf7c498ea85ab379ba2a6dd1d44b19001160575d61b3228b33887d66eab3801372fa16b954b4b5ea81c9b5448db028a6c3baa38d85b3c5258fdab816a547a7a86dfa04e6e216cd84cb48f784c7df10c5b7b9d8031ce399de1466cae93a30f2a0769e610e73820ab8935b93a73024110c1f07afafd755351180c8dbcfde5eca5582c14eeca5c8f06979404962ac402ac926d6b1b249889abc2a701ff93e7186d89cc45b8ebde8f4e46757a5c266c5c70c26c82d2e64b025e4f24c76ad52c59d4b785e1e4e8f22b7728fa32b7290886369ffd2fedc8d3011e1881eaa21f91a5f940213f2635a8ee46d7aac7741d30201866334bc6e89aab41d1fc3070232f5449a047d2ddf0bdb28c7ca3238f5ffd054d219946d71c5e99b84f352f04e4a89e0c06e4cd1153b752189dc290581fe4e66cf89c8ad17197b99b56e8729788dc14a304e528eeac07022bd1cc484c8cd72e8ad4d95d0de7b23dcb8a77b21eff90f2059ce7beca9077fcdc486c07b29458315a069dc09293cba68471d85454f8a5f768e12b8b67cf71a44bbaeae4f7d4ae782f0fc4d86d29ad8cda5b3708e241ae5a315104984a6841b7c1b27ad640da84c1eeb6ecd3a4638892a14f412845056bf288da354b373ad99a651d4edafe1da8e86ab17aef512aa5cc694d6ed443217fbe48f2312d709dda9111da3c6fdb839690e122c348047c21c42d296c9fbe204b2ab751e6579151847fb074f138ace8c0e4f88efd0a3ea9be355894637f468ab7185484c62230798299c3339c47fa4c26a1526d64ec6304d018e5f48768087c9a9f8f686bc296cbe91f0f0a6972b643c4fc88e6a6c61ce5def31cef6ba32d540e62894adfd4d7c5362d87a03fb0ed3ccca6ce450260af9781399641148e4519032adc7666f216106a12b05e7c1ff2c6997044add103162aa16c3c1dfbb504cd0171a1ce4a132b33a9f0ac8a09ea6eb0608d60a24330427ca4f012f5ee2ce71ed5c8cc4f0360bb0dcc285030755a120d778fa41848467354c2c759147da8013057a21c9eff96463412a84287f9525d9f6ea8b2c341e886f02ceae6a03e0dffdaa00a6a42fd23a6fa036ea72790f5e3c703c0d12b001508d6bf738c08144cccfbd595353a82608b620fb6b94303298dfd0ec9e1440e9943a4a466023d41fbf395ce967d6bc79cba3b25cbb36fd2b1bdf69c59851d8853b84be2130254af3d7d3dec9b64044731de6b5f81129e5a2405bcd954363a6b949881cef60668a7db52ad705f30fdbac3369a8fe69148673e236bd7398010fb499ea2ae0f004ab1be4b91d979d485c5624717806509eb50b61c1d51a652436a401781c79c1c0a881411ad212dd12c446bdc5e92d4cb408c88ea02a0396627eefff13123d915d10b7d95ea45ed380898af755bc1ae6a3a57905880a52c4a0e94332354fa20eb90b12072622c8c198d8d5afa8c6ba1407f6a2344f0ba6d66ae00cead9917c4e16d85c82599d4e97e3074864a58299d7beeb317b215d088d22d94146da8165f497409e864a4c5c5a2902452f95e99d8e1d5ba324beb9710b1c09ca68cf4d6e2c8b15071a9ff0a90a73b69ca8c1de8cc19f5495dcc7f0bedc05d11ca9773465803387f1249ec92cabc4907544b01f4752382906786550cd6682c6b3185922849f0e840b93bba8fe0c9e627cc6c088af39250728730e259875ae04acd1d8e40672413ae74e1e26c9af594bd7e25b36dca7f36aa10750e5f63069bc3ebbc39622a8d8ceda071b739d32167e7e50d5babc2a1f357eb39ab5489de030a44ff50fd9396eee751d3f6cc47665d6a986aa979041029ebd7dca21d9808ad3476b20bbf5efe4e0ca172a6b2d9f92f0b10beba426e62d188c54902819b78618d1b3f9410685d4e6ec6db466e5e5aab2cf84362fa2835a748fa136dd045eeace970fa606013ce5499d920fe6e8ad3d087af5e087f6d4ff687779a100b5c5f148674d03dededbdf3aca2898557e9a0cd5b45002b7c282a4833c0c9f8f1af5ea30767180fd5067ee3400e6084d11457eb38ebd2c0b38b34292562d67142a98f46c73742c0ca9a9b2e15364b2da1df5776b02b2fbb1b6b2da9edf28b75ef9148520b755bc28f3ab3a065733ff80523d4263623db121d2eb7d5a50d356999bae6c86ad844211ccacab0ebcd644fdd2a9ad16c72b31c982a4a839ada2b74d04bb0abefd52cc868d9c3feac70a5e65bafa0640fdd8a6344325a00265c90a53964393f90f9d7da594bbd942911a91601d0dbb9ba66484f9a6802f4eafe5952c34532f02f5f70627ac4d845bb5e918386224e0430aaa109b58422bbf0a4834cbf216969006cce135694bf40ea952201284f9217c2761b2452f149fd129f688a3637a3ada0c03e576533ac473bbc80c813a37fca47e980c1ff0781a0f825722b03bdd5ea27a21b267450ad88251094cd81e384fd0f8d5ea9193c0e4680333513a0b6017b31c22eb4281e10ed78a629784095d15fccb9d87a7e9abb8ee6f84033272f66eaddc8e02d0e737d5a1400b2adfac0f4fd88b1eac59ee7cc21f9fa62d8498a505e32dee01b983397aeacda2bedf6283f7e160b76e977544a2614683fc6777c528d4d9f2420b5fabb7f1f9efb58e639cefcabd88b5ad2bf6326a2598ef0475e716d1887c403d70c82ea3208fb2ffca08130d253186c8ae0b6f9d35951cabf4bd78f5e56d4afea8f3d48f06db7fba25aeadf7d27bfe4735d1d7a94937c3b27e90861ea4703cfa4afb3bba859a6226dc77f1d212b8d3967a898133fa14f8ad1912d8e61516d52ccf1f43f9f5cff9253a11e44f7be477edbde806baf41306a0c7b383bdd1eeaff354f742154582ee97c6f708612399a57c8f8e323b9de94ea47d8a71acf9c53d4bd34213651d43975b1dd90d12f7fa1e82283e26ce85a6fef5027011366d3e2a286c3417ad539c46ecbdacb4584de9d87c90a9b28f20408b978b5802f622d28db18932f14adf77b853edb6b8494ebc0d148d347b8e4d4772f087470de2843f98b88734274f7b0962dd52082002d37c934299d7a87df5fb2208781cc3c0a21b79ef9f875a66e7e507d98571917dfa250f926ab081897c5e11789214ca228cda7d71a1405a32fa02b3a8b208442e6d306863a0372d4b70c27121da70220e99fea135426ce5bc00eee9b264c1d5117102e64c55a1c8cbc28718f415bd3d570b2a044f501e844924df7d424246e3191152dd4320e840b6eb5bc111e4c09cfc564efeeb3a57a2838573ce18f012eb66f32ef6e91ce2c71da600371965af5eb823b30319ebac0065f41cbcc94f74878b5e4b776b7dcbb8dc1d3921d90d41648e9abaa87c70f7c615798f1ae7a766cac350fc18250c6ef99ebce7e2b73c8c670d30e003a36b3692784156a22c32ae23564c250f04210f8db527820e46766bf210bcab77608639a5686d321e778c3cbdd807a37cc943beb7d3776aaed6cc2f8788e71707b2b8af39452e96b40a6dee3d951bed690dfdc623ecf0636a848c25c844e3871ef178375b06e23d1425c4c4040c898390bb399ecab0553d11ff618a5f37d742bde340f6eedf2739c825e5bd59d86977191b3106e2f92a0aeeeba6fbb881ea7cd9f7a06a6672fee4c07026c271871d92db0338ac5835d1f174ba77a164f9a2534af73b1832951cc4d4d14e59b76609d0a285d5d38b0707b303069bed39c04ebad6b9353a811a67ca7551541d0bd3e38c367dad19f0d4f1c5d8ceafc004bcdeafe5ec105802d63ecfa3a9d177fadd9300f14e84293c4da71ab9fd180465f7f05b400d4319d88b4e2dec0693b323fa3092515c0200c7e01ebe5571ebc4d7be00b846a0f297d52822c4948b959ea81d959d0f02aa6d2c8f4d30d02c713c4720a32001521b02ae53db0c57972aaba23756bcbc8e3174089ed8455fd8e66c2219622a44faf74b4f9ea4b7c222e99aebd53725807ecedf5d1edc1d40c608257dccc9ffd8f9a5d0094ae61db3842dda5d4067b5dd066bbe2596a9f8b1610fb5e1bce6baeafd5425573777a50816aef483e72343c6a17c0c46b5fbe467641b8b21b038e0eb4a5db1b908436bf75e6d31fcc8837ea3c073ec418d469e50465aef639f404850ec4e29ae01ed3bc16aa71252a8268e235dd08b9ae815a0436e2d08e3cb4e6913b76a153d348c6f15168b885040119271579a330a732aa49cd1c48e98e9997ae6331cbe2a7645685123e8fc33b36495ae486244a060ed5f51cd3b8df18aa7c38c235d8ba374d56e2f334b056c663047e0e7ea3e3429e3e444cfaf20acc2852c346b6409e0509750030524f0042d92f070007935c1663cfdb80a4178517b8410590618a316abe203b1d3d4e2a53d07dd54322b248e4fdc432b516382bd42288c27fe094aa006319bc79c6d669684e7401543b67bddb642e6da8f584917434efeefcd21d4cf4b063a933f4c1ce2c452fdf9f333f19163289c10cf17884a81d668fedb67b32f1e58d5c3ce55a5fd120bd96fb0c829ed6369ee4ff52df61cca08da0d52671996a6c269789f4533ec2cae1419a80caf044c4c591f93d90e76bbabdbffdf2d8484c8c0ce75d3222fd3c8d645fa613bde49928d0d2fb0edd2d280d80b4210944fa28a89c4fe8803fffb9705c19cd3033d71cc326a7630eb02c1588096ef150c316c1deac8997406c83fcc8d4e05d77e1ca4cc6e6f80227df94d093f9559da3288b55a784cc308b1cd8cd99d1f83a1154be5be3e14e55f4733f2b47eecac9d107b660deea46ab99cac85fe8337c20ae95a264f5a45a3692f745fab309be267a236533b7fdfd3d3ef72431632b1ba82b7943879941ec327592152023ebee71bc036e83c698166e61e63003a5c260d1964ddaa595dd141c1db09f784edb9c007ef46fc796dd32cf3cb277e5f1abed42d75bc5bfe48fe1127675e252479124332ee556fce36bb5559e592c169de5e0782fceb4c6319500eb1f266e537c2ddd7feafe0864b7d58796781ab032571ebc3f96990024cd45c3b2b171613420c8112814d8892c15ba524673bc4202903221606431705cf115ee76574a165fb1cc1951cf6d1803cc95c7ecd1e41bd5d849c353ce40138db8856e91b0724b666db0039d8544b435e9b82f41a5fc0c7e20f55cb3c858d8e6a88e9358181f87d9eafd80d09d40ae9bafc9d049425f3de2a7d023902a177b3f6e50882e3bfe66c302f78d9be5faa06b2043c446346aeb62d87c6844f3e4009963c45f770b1605292543a994b047cfe1a3cbd99021baf8bdc551be7b6aae9aeb78b5552e5d773d27aab695d4a59f24c1fb6b6b4b1e65af9920c34391166c9a44fbd8da9dfa18d812be12b6124b471f7ae2747eb0a26e6fe0e4931021d73bc9c4d31394230840d8589b3bfa67c6b0aba2dc13c8f46972102774d5169f510c8cdc8f68731d7e496b3f69181acaf1ba3415b05b70cd81701d48f5efaf0ad31f2844f21630143f287699c30079a3330838e8408051522ad9764a379a01e8c24eabd69d33f4337dedc0c479c1be64f0f84ae90d6febe506c7fe8e34d80115a50bd2b747b2236dd5dc742bbbb34037bae90b622abd3d99a2d0b27482f4d65d2b5dc96841e11987c78c556690a28965f4a15d7b7c118d7bc62171c3dbafc347f5c0fbeb96800b4575d19a29f1b513f280eac2a432dd40060d17c0d5c309a51c74de581da8840d4b85eb38758e6454c6c97d434fa756b7f14303933ecaff59a31f2a3da1312bdb80d628316d60bc8e94533665fb6541ea8422bb1d2c20156d719de2ad2d1a141be0fc96a46a6510ec256bad96706d73bd1b3716266e50f83550f08d43996f5c78846e480ce05da12c6ac8818865109741f7f886fd151520da1bc1172c80159ca547e453aa53761d487c37d7185ae396054f0ecd55548b8d8e8283c74d03edea04a8a2cde527efe28262b7fa1335622fd8ec606fdd381750cfb9c22b1874914a05c0dbfe2a8ea8d4a7d19e444ddcadd671fc962de0526fb33001e3078d53a5625246d883db76e194d50f56d2e04502dc58cba03bfba9c516f528618756758227ee9bfca7ac6722fecddf617c5eb80741edeb00819096ce5d3092044e962c3a25c76a6b70c51b8f560d81d0e33ee48c22c2bf792cb5b66b6af2eb29d5fa8dc04a58e4ed20ff103fc3a858bc40e913357e36fc703094491aa45366d42a5a54b931769d3e1e9ed1e68eb4397c9c5787ac55d79473a63d1e60ee4e7dce22ce1140741690c49a814f600e18294f36d6612d031091a8d2e3e9320b13a334e3f281ae8f0847c4653a5c91099cbb6e9f7f30a20d5c92969aeb914581a3b5b8738777be066a46087ad01a1e4fce5c1f939db76c06b6220342a8abb470a89edb43c1a65334bdcb671d7432e87e8991301ed5969f94d483d2e30fd7d111612f2903e35e270135c10ef17ce4061157edaf5ce83dd57a12ea14624fbad28b933d0f64993d1d7428e0efdbd3b87e252fa629b7bac48c50f7fa1a7ca22271e8527f2634b2a02186991901321621306f7c08883822d66cbf7be4a79e9b73b52fb442b6ad49ad70b38381c0aec995a5ab3772e815e0ce1379d8b54159a6ca0aaca1ba1083bdd8ed109bfcad9d2fe272e1cc9b8f83c026114c5c368002ddd18af48c4f38dbfae84ee0301fd0b977b1d30f02ef5a0775a7d8fb0ae4b010b06ba33d093d7b265c98a19c68c929258b38998df82f0ac58437ac92b946674c281ceef6743a0918a2e118da71ec1efe787311301f6c93683916042d65e7ef81fea6f442f4088ea0fa2104fb815524d271566c58cd97280ed95051ec0faa37fe2bcaea6d32a212f0d1d3394d18357486f900b2f4233fbf6910618c3af02df400099f09645dfcf453129568b544cc34a045f574bb41e43107584de4e3916e1fca63e0d8d58bfdc8b8bd0d47bf90084505dc8c25b527f7494558926aaceb19cd8e234f946caedb6b875e0de8d73ae33468b8d8018898f9b3ac10b83542776cba493c3e889c3fe5354f107770b0003eedc85ae021106015fe3eb5e3623c66ed898632b730f3748035ed57034cf2d8a76f0748e86cca59bbc512848be886008f3c6b1dbcaa842e1cf314947305b5601b87ef3b94bfa3a4f0f2338843e9effa77027644871f111747c6a94317485d9b88d5674817effd768e90eab2797d28100ab1086d9ccd353981bae6338563c775b0a1cc0559ef4dc04626e3963294026ebaebab883b3ece63959b1717a526ea82131c9bec4bc2686322371b808eab29a650e7ad56b830c6b5a9130dc5e04efa2fb79f6a6dac3befd778b98886ddc363f264d5316f4b9aa540d620e795280ad61fb5911c10a2dc4972b4a2f99b9fe877f92648a7a009508b48c632d5bf5523715f4bb4754d81c4f4c70889d08bd4efb6e970858d291362f01d3dbee33083abf0a52789ba30879698a144d173df140a3e52027edf86e4c23540eb43515ab9e6300efc96d0f32cc81769cee3c89b0674d76d5807916534143706d88a954bd16db28a19d652f608ebc54866de6dd2f4dc4a40d35873f75759e599f4168cfbf492090927224cf7cc048ec7d3266fb6fe98689d9554aa02fade115f5e509619a70359c6cdf4657eb791011fed3142d1f962811a104c7aaa6abb889bbe5f4edfafe360cb631b262a7a0dbc388e1763deaf5d000000b936d2963dd8cc60e51bcf988b7e5d048ed6f692598f70097b4424c9de5bee2db79452ca9402bc050506b805dddeddeddddeeddddeddeedddddddddddfddddeddddeeddddedddeeddddeeddddddeeddddeedddeddedddddd2feafeeeeeeeeeeef66eeff66eef6eeff66eefeef66eeff66eefeef66eeff66eef6e6feff66eeff6eef6f66eeff66eef6e6feff66eeff6eef6f66eeff66eef6e6feff66eeff6eeeeeeeeeeeef6f66eeff66eefeeef554c0f3534acb8c0a7fdbac50a708871adb4d69212629c56088a44d3929ba542f50bd926291d7b6fd7ea459c4116d12fe20db294342f777a86064cc99407b45553a9944a6e11e5414da56aaaa656d1d42fe811fdd2aaa99ea15f6047b429a2e20b6c15238d94c1acc5d8f3de84451a69e92abea0f408f99227bb2ca24dfef2b196eceec65ad29443ac655bb9f3c9f2e9d7b53a467efd3a9b8e918f1dd1f564d93191aa14724c3c3e87a703be35ad2c43402a4aa8f8f44c7ddcc22ddcc22ddcc22d3cadca0dab03cee1f17899634a414a159f8e914f220287b895e5271102ea19ec268752ae3a26fda2bbd3e1bb5667d333f5e5773d29a84c39cfc3cb11de9a5b8381f36792a060ffdad15daed4887a525182b86ed0444900b0fa23c811f90719e76605590284148514851485148514851485148514851485148514c5270a102420f72701c598a9947bb5b1210191805c2e12508dc1253d01879226e356f60f632b146d3a57e63c95dafa956fb802e909d8bfff8624cdf459d9368c3d2f450a9fff31bee79d36d2132d82707ccf8bcec98832da665d7ccac3a5340501ecca4a8d18a3d8a493ca6a6ba516fb27c12c77a639ca3c4945e00dcc1c372deb36e9de334e2dbe9c0c9ff185c52c9401504a4936e02770f8af03946148c09a0ca41da919e0f0a1c040700cf0101c04074ce0804f80756019b0ce0e54470643c052603b28e524d1d95a7d0c21d75a6badb4561f4f722de5e0aed58713b996727c0495727ed048f0854942f08f9d524ecdcdfaa1e5b0031f49f818828f273e9c08fa4183611866ed8f1b9aa6dded07ce8fd48f1dcac48fb04acb344ca58ee3669456195c4ba2017656ad48543aada5980f36573c0a842aa7b54f2dc5e8cf286361ac780efee02860516e968f221988902b656cd460616c2ff309f00ce0d11550bd9eb99f0dee6b781877bb527e35cf6f07f7f563c153884f3f182e814f86bb88bf81ed2ec2d6c1041a08393ef6d90b006cc03060ffb9738851cac5d0fecb18a8c91a3c184160567e59b619cbd76ef3ab99e6abddbb556ab14da3dfe4fc7e1f9ddf8efbdb4763615c70ca4fe6ed0b42bea7903966393b2d770a61c41e37ef17f3ddbce3363b2dcb2fe6fbddec7e12c6bddbfd228ccb79476e7630a606437e9e59b4cc4abbf05026970290e3df4a2f965925dc8e293970516ed60f27c496b27fbcf20f570e67ee1f4da4b435486e9b39b6a03f3ed7180e536787dcd184f9704948a83a2a9d1d98ce8f8e93acd393439c7ba503e4b281e8c09143cf0de4064d8638e7a4b475b61486613673203d4078b64dae0449012b088d372710271c91203fe0203d7431374b8709102020af2042acd06d1cc7715df7c326c810cce3928c7071c82b839943c4fae8b63fd0caaa48e48ea61c9fec40c89d3384dc9aec6659911f0c3e6a7ca0f2969b95d384bcc292d304d6b62c67d98a063ce86bf7d1830f205aeb1ea3b9a42294a20d8c63830c4707081c1c383ab2dc2c1c1bf040318cf6c85ae5d92103bb6307366d631e357018af7842f0501a6e0e3f070683f06dd2045c037c042c846adadc2c2188448996556473f7187bfcf46832e7a43408271810866198b535a1dcac1e411a1743d3b47b85480971933b9a2eb7f9c712424567ed605a304d20cbc9e10781493e700f1ed87fc9d4038708196c7c21802c710a0e6e36660805050505c1c0bed66f860cdcda317a6ce936176ecace9d5e685c82297a09eb99ce194c0b2f597ecca105b20455903faefc96a05c758c87dddd437697344e7a9efcb87c7a5c2d9f7485a6b4aeeacec9b3b5997633ed6e5c179aa1908c1eea425884b716a5a8acf40a4b4bcbd6a30844b471c24914a0d78f9397cb895020541e2fa817d40b2a052705270527052705270527056785924e8209de43050f34a4e0894d9a903f605c8082ec2f4698c80eb637fbba6cf5eb5c6588fbcd5728c9f1e99538d4712a9890e34f4d9321edbb6fc969bf7d4b5062153c78ae592643d90666a04cc193e34b0cc35e939bc4fe62a0d6c1349163ad1ce849100cfb05e3821cdf41162c58004999b69f812c80240b20c90248b20092160c5760019423b801cfe18b911c4116371d437ae1b085c54d8eff829232d662ecbd8926c696cfcab5fa59bd56402b895ae5f82e35d2dda5c65d6a70543b2b971a1f29e3ae9f1710ca27d2f8f8e4f8de8d94f19477a3f26e76bc9b9577d3f26e7cbc9b1f2993018bc2345ecddb986e7e72fc118e945941baf0dfb12bdcf27cde957d8493e3cf48ca588bb1e7fdcb16a095956c4e506a8f1383936a51806758a2874b8720f8e0c4055805387c1eb091272c234f1879957ec0f3a9ec396350b0a403cb7799655937c1383d2e2901d1b75f5258de509760984fe953e74a3fe0fa251d787efcebed59ad2cc8eb62897e1cc790ec5c936bb1ef78c9ace999153aa4aa6754bc43115d3d938283e9de717ae625f42ff48c4bf62d8292c3942559fe8af92abe9594252abef9293e9687f95a5474e0ac0245963f1fe66b518122c43dcc377fb48244975d3eef5f587870d965c3d9aa0d566d6ef6469fe3b47c91e58b2b9f54758c48fefcae4fc7c8940ffda23ac6a69452c17c361c95149bf02a91e5632542cf09a30d9189500ac9f346763aabcd869345363d63371ccca39cdbfce6dcaccdba09b6a052f806831dcf52baee4f17911b9c37e2376b3ac64538846df1ebec163f2e7e5b96d3762599dd90e873cf71e003e606869e3725140bbd68f0aa636a80c3961657cf702fff721fb514cb2a5a29803a468e54251f30a8ea19aaa275e98d96b7aff3d554b4b27cc245a3916a260adac77168a24baac259033343cb9dc3f9133d76210856543a65178680c5d5323bbb50042fc2a18ea35d3882510bcb8a4aed421280cf62858a14302f58179680047a09ebfce7ea7649e438865c2ed9b926175adb60ff38edf91212dd2940a39b348085e661fdb87b8c4bb0b0428900075840f3d4cc49814983d2175650c1f43570efdcd091430c010ad078a167a2eb5394087080056455093a08d1450227df089a088ed39f089a658448ee18f7de8d73ff8c183902048c3cc93c10d2714b39c025601b233974b00d8cfcc8217626dc6e4bba81901eb0101a504ae90ab0d663876a063c2d10820466819025a020240a1e7202ece1156f88cd901a9983871104f641d80fb134492df364b82bbf9abbcf46102da2c0f3c39ec9c2496ffd62d6e837a5b6cd7bbb2f88edef26a3f746bb39733380c6c2e0deb98a7d335b8cf319db85bfe6a5c497ec1f9a28c6bdfd767060fd64be5ad59a7a1d93b56cb6c972b72dee98ee6ead655d58826c3b66850570cd336b884ecc094fe68f4db777779317dc8e241d9058187041e31ce29276405ae2eedeaa28f1e63a0a832c968a084b2584d41ac263883664899452ce39e4671b02c47537749a0ea1cd079c0921d25429d508956eb5727bc0b2c2440b6b08278c683c1c2ebdb2b2b2c2c222e409cc16248511445a2b88b83a9abc55a850a162c58acc06d872436209524a39c2bf8ea61217a3542ab56841440d114f02c0c9600ae1e022838100586a30806d0897dcac2249bcdc05d89c55042805233498bac733dc120ed2a593f63a862524054054393d3021c980825054ee459393845001c9c9d84f0ca32c15ce0f543fd870bbb4f9101ea200341e324405004396602d48aa213d4380544843dc00eaa07d3e6c2f44c642a5ad30a2aa5875297a7061866062e5650827a8b8342642b379b8412238ba51e319d45a2b0cf0cccd22e223c88a119d0a91164e21e2ea444492b0612250d04236b2eea685232208cb46c4112997881f8434229eb0644564e06c111c4258111864b5480d58689121b65924898b2ce2f26211200e8637364283359283111504a0c73492e376bbf7b7777bb7777b7777777777777777b7777bb7777b777bb7777bb777777bb7777bb777b77b77777777b77b77777777b7777bb7777b77bb77777777777f7bb7777bb777777bb7777bb777b77b77777777f7b7777bb7777b777bb7777bd95f4ab16e3e0242a964827295b57cb2bbcbfad89792ab8d94b9a88bbaa86a73511735e74f23e4f8a8d08f8a92969136a8ac7a067b952a2787078f1c397cc777f00e0f5e61a0a45941d5686170573d83e55675cc7c1213dca28f512f3f608865c813976880e35f1b895d40b832cb9cf3bc48237227c71cb459a884040e6703720cb9b91851947dc604c1f8b2636c8c923bcd98a07bc7d4ae4f9f06fbd84f0e2602f5e9d3b0d8e44e9472331c74d03b8672a7e998757169c07f9434fe2bf5bc8e912e541a15b4d24a17968b2dfa1b72c06b9ee7792729659512c4b86322a765997331f46e0522e7427dbac59871a7f9f1e7b41c074e5ab6f1b3f23403ccb8f8d2625a8c1f04cab841b93d2d0ec134a073ffa973832d829428b914cb33a60b0dc3fd37b09fc6ed3fd53cff547394c1c508fa770909f6d3cd583c6d199392cbd837e7ec324629a5a18cd55a6bc536fa611886619828637fed46bf9f1f97cb52b08424e35ac832f657b39d0c0a6ac083329816b21bcadecec8406a3be4b9b9181c909192b197919229784aa9f1934f65d41894274857f1abb90433061bb71a6e9762bcf34bc92f4819bad32ee25bbb43777ce47f0ee9ce4c5d548e17d5ada066cf932da79532a71de46871f4393df7e20b6f53ad5837c1cecdc3ede81ddd7958ee1e234f932a59b7a5ec23ab1c4ebaca477cfcc983a026370b09ea8813d33d4624364852dddddddedd8e447584e7884e0eed911c243e90ac729c3cb8a52271d6ee6e4fddb87b943b986ace49ab4f0b072f1cfcdc4dd334ed5e1e1a1e14c76333e49688c0f4db7c334c83dd312c9e26b9593c451420056623c79321374d6ac0e1088cfa845c650d66b82ae078adf109cc9dc5d3e3c80e160e6a5846b85eb69eecd7220a8c8161086a72fd1a76e02687fd35e8cb89504985bd0827b1b919fe9911ad9cbd0d56112a183afcb7fbe6fb6f45a4f2f6231b56113429d6ce8de43e54b9076cdf9d3c28dfef1e5f30f478bb187fa1ed392e04c2881e46f4f7b7e73e1811d8fdfdfbdd737fb7ef422ffada00cf2ecf427c015b4481b7fba10bc2e0f21c08c3e8b767218ab61af4855fdbd6fd7643dfdd6dfb5028140a854a2a0cd382e7d07b0efd06c2703f7bee1d8499e1bfbd96af113b9900b95946e0c830dc9fa1e5d0fdecfd33d60e50ce5e863164fcdd176eddd7220adcbde895bbeee37efbc21af495330fcc8839f4ceb5e01ffa0c0cef873ef475efdc69cbb60ffff6180ce92b6fcf820c859b8c18732c1dc1dc87f4f5a2af9a97e7db8e94524ae92f3d2e21a594527a5c824adfa64b29a594d2234d0e1ae46b4610a60507e3bbe88bbe962865f92597df140287d8aba14296367740d650213b08d382ff7c7f999b6c36bee8d0466c7be7fc2337c129a1e0e48833628d563c3b2a1d9e1d1c1ffdc4ee6ecfb2fbf7f39c7d9eafdcba3b891bb9eff710925ba3810957134cfec041c10b4ccc39fab191e35714361a3d1945190145531cfdd0eadddd73d6eadd10c9f1ad2a6632a563eae04453f46e30cfe7a4d58f51fdbbbbdd7a3eee39f18ad273794da6f7c4fbf1324e4657e76a9433cb6a80c8f16f2a6ed206c7d471134dd1a5e6769493cebb7292e373aed8f94817d6746992e363171fd11489647491ed5c565aa69c56794c14e04d7e97aa542a954aa58a29d2babbfbac3ce88bbee88bbee82b3a6633edce7b65f41bb237b02115737b23c76f6c858a0dc9f1311486c250180a4361280c85a17274ed6e9c4863924121f321361939e6eeeea0c6a46650c87cb03ff6c7fed81ffb637fec8ffd49e18cc0e5b00d719d789e696e841cbfb589815c0e397edde4d5b06d88ebe4a66eeaa66eeaa66eeaa66e8aeb42218c45a2149c454a884c806d10eae9a2b852c576d2b2e6eeee53a484c804397ec436a0a19e2e8a6ec7a7dbe976ba9d6ea7dbe976ba9d1794e8a5079b1495179a171b2b2f385e6a5e509725c618558ee418539290638c5fe7b6939e6e4b8e1c7f6a77de104b0e727ca975dc26e75d69428e5fb3951e72fc39e79c734e952373ce39e74c49420a4e252825282528252825282528252825282528ac1c00613a82463e921f233b39929ae00bc85786c1bf64038f76befad9aa676c28c39996dd2ccb28d59157c321a98119e892524b775e5b966536d92b7b85341b2c5b9025b5c2214bcb95e3b3b472cc56af4c230b0f979d0c8ad04ebea17b3b5025b412c221512814e27e60cf5d47bf14bcf22add9d0262cf63e1b97fb22b711d4b17b167b697310617dac958ca4c8be744b1c873857c5fa72c6f3f512cb27fc802dbc0ab6a81030eafeb95e38b523db3d1d7e867d45ed746bd8cb30543118ff00a11b5545c0b32e7ec18772016a47f35e88bd26f825807a52025209fcd51bef0819c17f498c10f0d9250817cc204ba09134fece0831e88ec908231c30bd3043b289981121e4a7c54bb8242144a64c05152a3c40553c90e4c5643ac96ac96581161d5c48a09498e9084092bd4ea04ab15ac7658f9b0026295b3027244954998d8080225516209998409cc46ca243c644974e0243a35094f92214bb83bb95f492b37d8819855b851cab945937777f7fc6a9872cbcf37c730d98525c8f459ac4406609301dae7518022287760be671c5db0d42aebece8574e8600f2947645cebefa96932180172c5eb229cfc75234064e1b38ac415f12acd27bce8e8230b94bb1c70f580938094d6c1777d75422462440485eb9a3693bcddc499ac02fcc032359492447e8c0082139b801850e96d0440e15088750fc41106ca3f0047ce4c646e148cd911a8e603c455c9fb6314f935a43b9593c4a70c200b0da032602e671c2aa1674020f01df805b26c04f308f4df65f32f1e478a2c424fea967f4c9638c1de58c8e73664e86cc514ee951887fbbc7789ae1a005ed9c397eec4a2726359712870391fd692a56692371f8db89592dd3b6bad1bb755c8743988a52b626dcab4b516992e917c6273f510711554c451df1e649b41151af2751807e5c87b552b90ecf8ee344711c5ee3a8283cd9a34f949367eeb8c728e5a45446a7d66394724e4a27a5b56298b598b559a669f76af76e1bc7751dd775a110c622514a8bca68c563717969797991d15fe897f22af40b71f6958db260dffd27516eb066ce9eb26b0dc8dc938b314b4a907782edd9e7acd5664fbf94acf9db3927a6399d73ce396766e757413066e9ffe2355f5db5276ecbbf94dc923258155dc45fdd16c609318e0e4f4c1c9d688a2c362c2fef09a09ea19f42ca94e37568971b2e36dc9081254a8ecfc2e2040b507c21650d267de6faddd6f5b93dd1d413e3b69680435c6dfc5598487c21bf254e5542086a42fa4202fb37890910ea63949436adc6323476e570e7df509778106704cd68307231d29a6598e76118c6d6624c69c5ac0b180dac564c24b2ffd67a5e96795ead988d716659f62fb13ae5cbc7300e9cb49396e5cdb40936e54ef2d4337ac24400fbfa990563c81877a231663364b55c8c19356f09628ff172f163dc3817b0af1b77728ea381558e3b61dd755dd7dd39439e3c889361b3063cc87b935a2739d7bd21cac9a8599565f4f8319ea4947272a739e79c943bd18f524ae9a97eb5d65aabfd4a4b30f699869a3559cab65c409106bb09eb2ac7d2125c3f669b7540679b699af6f6bbf637eec4fdf61c3783bb38dd311cd81d938161770c06869a4fa6af9753c6513186cd3321947a4a3da59e524fa9a7d453ea29f5947a4a3da59e520f27bfa78c946a84dba570b4e3766eeeb4256546ef5449bf300dc04177774d7b20e922569a688af58636c5acaa902305bab57ebcb526ac415fd807b678f4b57c2d5dac0099bede107f74a34da6f82e8c6eb48bf8a21ced02cbfa211a2ef52867d38ee5bb8ebc8f01464bcb8f9e46cb8f5efe68f41e3882d1e847d0f231c0281d81e987d727d72c9f6554d333b2fd5598c8fe294a647f1193ec2eba197df45e0ca04df1f177bf30752a80747413b21081e1e886cc213014e51085d72747d52895e36b35e88bbe3c29435ded02a34ada84b132f611fb0ca4ad76817dcbd78de589bdfdfa06381ca9324869f7686725ded69cd64a7747b282a2ab485db4d532d95325cd00ed9b2ec94d7790bbda4879e5b623fbe66858140a0563860e0a0a0ad22e4acab4942e94fd3bb36f060f82119cb8a8f46db706296424030000006317400020140a0744822009d2348f6b7b14801065744454583696cc44b12407831485318618448c010000630c416886a62a0864fc624ef58a1fcd562919c092913208ef524361662960175cd658f389f9e1d59c3e9ffe463ce8bf7224ce3c9842017e41a9c2f74d8218bb569c14e892af2e5d250512719af61cd874ae0d94028400a5c2b1ae825520aae232ef74ef029f8b858710378876f1eeca8534b16dd09d7f16def995889f9ee5360e7859050d0e3ff6edfd51b42b80c32ddf8877b053af97cad5007e896be679f4fe5cee6e9506e787bb91dd9af08d88f5a9a62cb0daa59efaa830967c009f609a3f768a823eb78f7d089b4aff29002b8b50e5040313599333e013abb8fe4f1c0bc6ec274545badf17e6fb1455fefc1c622207de2318531fe3cdaa2cbc9b320e7f2a6f4c1718ca38c0f149493c8d88b61e2f81593378e23ec98176a2a6eddb01988584a6dc16f0ce68f655a8116e46c5c8884ecf1a08a75457eecbe51505c138501bfd05407441031c4eece727cad0ba2ca95580ef7e27b83153ac38e6ff1e14591e037570a2852b7b3b51f9c1d3593dbfe50ce638bdc6ceb1de0b83d81d92d71fe06636ffec54673eefcd995ca017733d95573b955ae748cbe778e4f77ee42101e20df5e9ec49bbdef63738904c5f42adbfb156c90628d771c240fa64a255b2c6d4a46fc4c50a1145f85290e2967fba2386198d15378edb356487fd8ee98bf52fec97c22514c70bf6fb1aa9f0fc5d5002d1d299ca2a45ba43cef2d45ad9cb7240978aca900c62bc648975ae9c984cea8506ae36fe84929fbd86842cc6e38e3dacad2300286eacbad01fa628d297b4c7a6627494f3b810246563c7804fc52fdbc7526e662459776c03b2f6dccffd51f8ed84d317225393a40102c8ab2d08bf38e67d354ddb7f627522f13ef98c14d496f35491538c84a2a49f26317aad905e0524db750c913ae0eece8fafb189e108494f64365bd7f72b334e0ea7a177e0b294b37be0619c0b5e1226b21ae3d7b48173e2d458478d9113db142984ac8635c8103faad7aa43a7c079da509b6c46b044f262c24799638b636fbf088aeb297c43cd23793ca493a8a55cb3006f17fb96bcd44fc7134b4c4cdc9631670ab383dd36516f947ee18d8832f4c65d905e55d6bf4c44332a4d76cdffbab403c366d283c894e06f55359b517297d3d54abc59a6cb5236907a0ad3b979415e4613965e273db737d25bb7e597a713fbf6edf50bdbf7d8ea392fb1a7f5ba37d633ef621c982151523fa51e2ad16dfda72e351b909973b122aa7504ecfdbec4baa4af0455695f4975292c0175580c5ec81865ab7c7076aa089d41778a32e1270cc545b64a28a0566760cb5b5d5242a1327a8c6c9726624f3bfaf52f37ff24b3be0c80a655bb0d3a35f98abe0185a31b3717dd3a7de8b1f179cc1d2eacb52b74bc1bfb3c27a4a7b1ee699e53d182a896f2974c503fe4de02e8c1f8d07e64f4088aa64e24c39ce44191b79d3eb3caea2b15e6aa96356715f8d86a3f6c297673f49c6905411ea424e1c02f0ca4fe1dd63d5e636c6256c206b0dd2469c7a5125e013dc2b7881b166eed8b24cbfeab558b85bfc34fa5475856649ab4a742e874a7add23e6571a9443616078f3a61fb5eeeffe67294cd4ee0083dcf57f768a66b28bb118f99c59d8ca2d2294e6f16a8de9e8d53a327fcf0c744064eb5cf8e4458251df440d4ae4199acea85235aa9e4b64a0c38abc9c729bf5526bdaf4e2c2e4e5164c92fe8de956ca3f542a24b7a3e8f61fb2fcee09d7965aed798e029628dc8633e7249cf0d9687b36db87c94c1ef25a5d703c565104b5617fe55f870b7ce23cbf1de02345eeab6bea14d60d7966241536bd1f05809c2983773ed7c1fa98b3c084bd5ff1e52bb04f4378aff5aaa61566389029b846cf2ec3ca5cd02c6fa5452a1273f670c245b42aae92f64c8c09f9571b2a0332b583cd982c52f78f3bee542be7cf8cbc7ef2a5427f452c1bab6178ffd44dd4d68a778e73b17c4a7ba8f6e2715aa72887f34be6c1f53692b653990899d1d7d88fba8ecbc66fa64287213d740b0e0fea5d5b3f8ff03383bfd47c8a55bdf040c304e3df7c981efbdf78a7fbe60f8f6788981e5e7c40c9681f815176452551b111923c8d4e00392097b2c56e5832562018b4945bfc8a8fda9b78b98cc5e184cfc74b57ba4a81771287ece211664355c44623b676d51d9f8325ccb45b9f1657c28174ae25d92455df3616e34412d596979b1018b0f4b35ec6471fc32a84e71bfa59399b2f421f1341a37cc20b022bf35f85255ec112e146657ab1bb9c4d3cf1c141617f44558dbdb665434da509079a8a23970437656785b8dde16408d0ede9e2b2f7a59a2fac95c5cdce17670bc6f30db1ead9f3b6f38d69b778647c4359a87489b1a1d8b01c06b8928dd8e975431f5e8f630229873cf7ddbde35eb18a94dcde80db507e88303dc4174bf1d91b583453a3823634bcdefd146634331965d75eba45b28a64fd65517fb3b3c50f5602cf08f67fa05d3d9c026e8ac5573f6a4d258533b3bf9af2fd366ca83a5c02b0099a5dd04a06384f852977572cb00401fff3106b8587601057cbd0430f9573702f911ff54e35e586cc241d78fb39a3352236890d8ecb805f51492a357fa2f511d7602189d052f513bf2ff2b34b394e9a30f0da6240c1957081decfee91c750a18ba5a3af96fc87b95b78307731c1a1c334fd02e867f4cee7702d25aefc08dbec1ad72128501e3ed9956ef59573cc2633f4535bd1200d6bb0f72aa13cac382bd4b8261a059e2f91d81d4e468450b3f39290eebb5febf6deab3011c96e7de598848d48a8a576744f44918388aacea3dcb39f7d39c56845499af6e9cdd9687f8642ba9bc70c3d49051ac7c2512f827592fca17bb18f925acbcb488d83e58ad695e9384aea24d59b28a5f3dcf2ed6cd13aa7c3f5d6398dda48b777d63832402c64a6f41b3519e75072f60a8a448956f405eb402aa332aff7f39d40dc9429dc23f7b8729f357d0d77050f2f2595f4f4ea92e045c81aeaa587099e49d36384961f4964f288f4908f01aa6f7a72324599cc06b67e2b685b4795cccdcd5c536899a23f5c56361c0d60100071cbb2f61ba4582e5145a34731dc7eb3e82922fd92dc8a1a8347bf077471772391f1927ecffd099e154fcb5b007d543e97e5e4201a731ea9e2940dedd90a2af1947ca8b7dfa5e119f55d462ad95e03a6d052934bc753d61cf0ef10af10d986a79c1e7862d3477b6c135d828e66e031df8fd4d6f89a56eddc551d833e2cba633189c71a178ea653a4bb9c0235f92f3b6305f26f92f0af1075c3bb5580decd184e08c0714912bda79d89423fb219f1a53b3aca531425e1450519f595efbab51c3ae3faaaba9a1a656c73f3d1ece08ca550d67e4bfbf54a696ff54b827f53a04bb4c562d747c35a4e2e94c9889cd91ed4584805f1f0b61020ef9223c8afbdbc240fc51b1e4a2dc7ead084b39c373c2d3853d7b682dc79d6286fa177fa52dafae571815f4dd4e547b9410f06c38e6ff69a089b599a189e28dc26af7df55bae1a2d1e39af514a3bdd6fa6c78478deee4302e06784210b1dc2f05ef6c145d7974194e1cc638c8206899bc88903117a1abd90aa73ad562db27b4a45964bdb9a025755297859384be83632d9c5dcc5b9d15f5111db54677da86c30b44706c46ba7dab33017a9e55b28ea131fadf4f0246040e4e296f4a76182454d8d160e99f375aaab736b6c54044cf41262b155356d8ebc73a9035ea2d8bc6a21c14b8f42e200dfe11da9bfa1c4be80af2d655716f2c7773f34114dba33cd1d925f8f1afea17b8d60cd6ca67170d133b338fa2849ea7974aef5c8f7a271444c601fd2d710b182dba69122739f8086cd87921955e416092832db385198190a0b21246ba39372b35330fafff036e042673c904c10c6aa3254c0261350d78e28bd7d33d83498df5d9d7778c63853b68a0c77d504dd932c08d88c08d88156e5fa5d6aa9425cf8447c77a34b5d593153b735cfe133f5938dafac0460d43e73e90468e9657ba378da51e60b32737a03a378774edd295573a8c7495a444cd6810f61af545f2b2f821a47de0cf229fba379d2a9d42b25120f7b50283b4570d47ee2d42f37379f0ae3d7fd0de3af792d91311490e68543d51d92d262c394db2298bd3f7a43d6a3832b988ff8813bda78fa1606c6ea26b6676868f9c002a5b7f1bb4db67b112fca7c4e2edd5a8e22e15a0339fe743540aa8e22e75ee35e4ed43cadbef2c4f38be8170714d6bbfea30dc46ddd5daec237429e9aa74d7b085b6cf25cb73232464ca4348644ce73b1ac1a5cfcc37194cc5983e3e1b1db1d33f3fe0c5257b6c8d65067ffe110ac27a4c31e79d2462d5a15ca927ef20a430401ba7458f9052cecc83ac13613c6adbd47fff0d3eaa25a6f63f745d91564d6368673af97e57eb7b66688fca3b7a9e22e5b6f10786c02d8e8b802cc5ad345fc2273fb1a313cf46ea554abe14d19aa2554f14267f5e6a13f236e7fe72204c4a141c2dc86e596c5967050786fa70f5b6bad0e7100fe85639297e6272d0f17c7a3e3df5a148a14d2627130ef4e28675e193c3acdb5acb039c21a7feb9740280eda5791c09c946c3acc90d3854ead9dab3c4a68224ebccce3eebffa1460c119df86e86ad7ff203aecbd2ab69a2df6d8443761c3ece0440b039ddf2d6fc8f863f6b63626898ede58929eda7d56883aa6edb042ca986afa2bc8e6ab7c23fb335d4ccf42d52fcbe89b9e882579f4df787eedb6811be4011b9810a81c1bd8d117a3f611503210aa5f653b1651b4a1203d946f21d885d13b053e9566df5535b737eb86124165b47d479c2fec92f77f72c72e1f97ab05836ce52175aa7b219fb89bd7463f1d8281a534d715542f063a26f1afae53fa6c39e4652b33d7f8264d040c9323a9f93ae9726cd78a9ad52040ed70cc5982c83c8534db212a27d10611fa656e5473b1ab5a4b404c2a2b0cc66561114d0c2609587e5190b9e928091d1265f9a407a31772b449ec7448d0decf50e549c02129f13915ff9b684198ecc128abd7748f11f45fc9183ff0154a7a965156dee63dcc942cbabbf9b6ab705c08a4e2028411fad07168d1f03a194effb8f6832eb9b4dd7fff3033b7388cb8bf2a722f7ed7421498955bace0259fb704df0b82ab23ca534269b940a831b5200f19783eb33e07cec6697a8540d904d0f7abe6c9a61dbd6a1467589a09a43e4eb1b10e92dd5b15b57c8cabf2b53e75434e7c010f327c01a386f8fb2640d246ca25dbd168256674896ed0675803f7d029853032bce57a54ba7c91ef696f04693b486b35b6623bb28f865e84a141fb8a4bf0c55b7c89ac5c231cf41ef236d40594551a52aac93f77e6ae0d5f2dff7be585808921760848757ff5798c6f0f2e700961b0cd40e5e3b09d34310153326b2fb1be1e05602507284c54acfec1bf76134f98445bd1e5351c7c93fc0407b19bbcd8482d9b41c874d949f245390a0b581a3c638130517f28f83e0b2a2d77c63b56e555b1af1fdd46f54bdf50035294ef46e626e71e7ef116686b0898b2a03ec46b7109e11ad0479944aa5ebdac08d9b08cbb9e8cdad39405ad7132988dfa763862d3a8b824af3a6f5d87a1fa422fd934288eee663ae42db37c305639c0737367fe58573fb1e54c9bbf42b9b265bed414e58ce2cd61e15c725b6f8583434174fcd59a77ee7ad9fb467e5be94997bd68d0bd606148976cdc77279227d6c913b6cac90c609cbb87f8495640c0f566bbeb3321b6a0ed637c597ac30d7518bb2500372e8d6361e6196e4e758707f05a4aee058483e88535aa78e7707c39769671f8d51f26a4748dd5eb8694e10485347052df305fb0235c995fa6f42cb0407e93468d4718552ee7198129e29818efbb1a1e55398a1c40619d47e7f47d5f3eca9754058d2b642c1017b9b2b2191a041d32389f2bc5dcca47cc0881afa5f88e0101ee0152c29c0222405330ea12c7f0598e2dfc7050ee109ffeb03d0e2a88a59c5dfb27f6dbbfef1a71fed1e6468678092e8ea7a5f90517d9bca15274b7128d3cf37f167ce75379e333edffca5633fc536d43fff4206c0b4e7fe5cb04cad1a1e3d8e0161b67bb00b737435655741fc1bd79d07df1b8a4b82e884fc9c39f7b594581ae96186a75170c9a8d4718e41f0343b907c157d36e36e6ccf6c342bc3566ccad51f8871bfc5930ba7011b9c9f6b6ebb56426e107d6fd3c8542df6261905b65934b8e383b2fa596bba66d4c5ab9f3a0ff1156af61de70daaa755350529d201e7cfa8507472f90bd7dbf04b6bde75f844788cbc15ab8e9e7c373f73c59865daddf4daab59aa1e880890afa914b1ad9110c8b4f2f2cc5a3047e55922e3c29c020539e589aa5ca5475fa0a3bbbea9c5a8eddf9883b0dae28646cd6931f408e8d7e3ec015e8cfef2582720965c29f474b848d0e7d1125edaf17a5330d4ebf605f8ff1e10f63e206f9c29be7ddfbc06c28196946eeb7d0299ab1e1fe40a1d2aafa1413b7d40d86a1f54b57e5b24a2e6ac59777865356e73af9dfb6b3708def4e3d283de7978947a37749abb52d35b7ab6d1e5f6a9ad54f6b7b69bbdd72929a5e634e0b81eaff1797e5de3925f3ac7cde74a3a5bf0d11b66d53cc25c123ccaaddfa8d89cc70b600d96739c097b1d6fcbf8d6b8bf0a7f8896a12452a7b66b0dfead6a0ad5faae596a4ff2935b41b5857249d00e6546784a445038fb774bc97377170198b21695821ca7a8e41373df75b0b4bc347ee3df1cbb9bcfcc6d7020032a066afd289bb3586fe27d946b67732229cc2d4033420caed5baf4157ec89f19daff313f4481a5a03e5f8a735c921c0b847409c7eb2c18df200db505349cd3b8311bd8cee6ddae9d081fec2b1eaa5ccf26423b5db6922580b627bd33f931c11723f2a8eb4116065ccb1a9740ff617ff4b19cb04c514300a8392a1e311aa334fea5aba832e016103b040d732327a2bb5639f8466340843403fe33fb8f2ceddc96f8eb5f77c92214b8d808edb317844dce20f2486450e1acadbf5e8be6801886f8c9c53e9b99b78cb168c46d8e2bf164cff2c2ba448fe52d780834c459452f3dd6aac0d2003ffaa6aa70073d616e5c76a23ef772b7613dffc6f40a75e2cc160d2504b33b6770c303f4226f876756f6a0d4d995af1274b2eea8762b9cb35bbb8dcce66fef4db738cd7ba5b9ebb1b160c4c076dbf0df3e2a280a44076d97bcb7a06450d472caf3d53b07996ffdd68796955c338bd6177a95aeac94b446866fe24ee89f7d8de27f05b5272fdc970008b40fba0539cf4da1d9fb9c224c9ddb0b01641db04301822157167a61683e8f99b8d205aff2268832e49c974acfb4f0e282a4880555d7e7eb9e248013e6b914c10cd1949980d99e448e6cb258978f73e61e960d31b62f2a0141cad37d7ac64519cad9f65abe968ded1756fa06da425af213007e866004717d136778657c91ee286e42c0d2db517ee1895a1a5f41a35492eec91ac4a36f9ccf799f16316b6b261b319badb66aa5dcdb3aa89acc7767c057cfed6fb9eb92befa1dd139f1fa5240314f484ae18aaf3e70f29d3346c9ba6f31f133a13c460244d5f8fbc576a187837483f23ef441aadbd412a08f16e304e5cd094cae6ef9b32f93962a6088c3597a047b288e29bebab611075ca6690ef1f1e6b324d11bfd984f9b8acbf5664b097301a0c291cd2d8e6627145034c516f33e58bf6137cc144b9be467861ba4f7a2026e96e490bea83a484f0f6deceb7e277d46e3029819df277794011bb3889b748e39c94d55f431d4a7299ed384f1550f0df08bcaf8171cc3f0f29ae2c860b89b78c9162c199d68b013ce92eca462fc996e3e5236ad550e79652d3c912a5dcc1d46d445435e96f3626acbf5c811db905d0991f8c0dbf1d290cbfa79f7469f256f97fc88717d7d4b6631db50b4a0f4cb0de7d51f0d9b6b7a4ccab63090904f66d8b23c45c297df3e8f26af165363d03b50df160c1b0d8917ee7c9e31d8713877f84c9a806cf4156b1ee3f1f7a449d6869795ac44d26996a991a06aec1cf5434159205b82cfe58bc74927bf60a1bf7b5c04bf45a0ef6d2f6b2964f783cba8e0bcf0854e7c6fe78a55e294a38b785b2137b36258a594993a4eb0d8d8f6af5a288f7af00cc0a0ef820928981ee0d4814700800657da9971aa34333054bc19880d699fa33e342857bc2a0430b0b91a8e62f647a021b704cb970e606252de1e3bc4a1417e42204b8a1dc2987f2e412f890f7443ac0295b25d41afdbcdc4f021ec3a234a86dca75728496caa074f39294a00d7f9b8429dffb4b273f409dd951d3a5cd82a38e4416eda547128862373a9b2f36820b4cb7857db89286ab97ba19d3581a35df4882424bd79b18abe4f27975fe8d9e89d66bec4bbd18674582a0e6577995405f5bd99fc68a1c9baea07a0fe712b69a47840d4f1a997de6b86e614198510aafb112eae5b59045f0a6bffee980288272eaf20945468542aaa0fef3efabc05303805a157f4aac2832a084549e243dcdc8d7dd0ecb3c47ed0bf2a7406841ec8ba3b5f4b2ccf3e2cf8cbc65b5f310e922c79f5eb7b37492fd7eb734cfb6bc9b0610b0b422f2694e741b4e8de7c221684762780b5283c46dc7f6d745c1697c5b59fbfaf1959c71a5516d0f7617023cb75ca176d8b1c6041e8b5f797c5be5eba8db54dd00554e0b8bc5e8a9ffe5dc266239e9611cbcdeba5ffb623d4db1bd163532c08bd6d7d60df7ed1bbba4bc32875dc21fddec24cdc87beea404780e5380d0d4d9139343c1f50882f93f444fb22bdd03dae5a3b2d1bed585c9d0cf7534bf7bbb91444bb94b2273368a5de8504ebeabf700a6b7d4a287b68aab042253cd09536e8573577c9943efe42280aac4ac5f176c20546f019beae674729b3a920f41a1368e60450f88d1b8f4e7e9ccd0110c3ce007205fa15677a6af274324a2dead5bd3bcb4868a124ccb62b8e6fdba6e593c41c8d9aa07bbb9718b2ff2ca61c2628b7ba9af672af168f60cb57e65b857ed1e86ffe11a1e98c422d4365de794bee2863406b34671b914a15d337d81269bfb04608609873a15838b1853f5bac99f2afcd4c25475091d95bd8ec78fd7263e5409885b12b6a468605181a22e531425c2274529ad9f20de4552cc35d36dabaad9c547b320b7d736fde17f8cd6a26171f4f41611e628c058566e7f0422a1cb730fd75c6ae5709c1c4f609c0f19d194f5b6ac250d33ae044266d7ccdfe026690ea4c1f37d624b7af3f48e755ea8cccddc1c841277f378dce861ae2a125d29a86017d783a6eb835a3a5b0f5b61b7b6972ce6caead822b4417361f55e2c6e7adde89ce0a49de7bf3c7b786c5edb90e22bd179b2064851ee7cff3ad982b6bce440d8aa2a310f500c2366ef33f5f43704f33cc7e2c7412343dfec6d5f0b611329410a4e7dc2196f0116cfd89db524f4a60c60b90a1aba8139388c1433f6de82119e7720e8da463b543fe8e035c8deeac8c674497589fa1724322195ea1155a581eed9ca54f448f340180fe0dff24f3253b0d19205e054a1e776f59c3cd39702d7f97e1f93aaf862e2728fdbca30c65d1d006645e140f4bee2ccf595c837ab2f4ab8d647ff3da0a1962887165226daa43553b9c5a53061d7ca37b0750d8bd58a8c71dc02307553cd1ae0400beb51492b3594bad59780c5b203314d896d8c2cde4d66ec7509c5882007aaa7508a9b0b1b979a926742815922a46a92a831b91b90cb94e596611402d861e0b4492951f34c0640edbc40a16d33f422278162609b109bf6b546a5ce303c2365e2c5242a0f5cf99c51978ae0c801a430566995c896a834452d8068daa65829a7630771fedc8366a404d51061608d8396a6f290d4dc5c8a43374f4ec66e83e2aba979bc14782d2b9381432cdc3caa9411d6029b902a257af9cd55f1912a252761f1d469ec79c1d60243414c9b8002a576330f622396648a47c568e78963c9e7149149f949ef69852e8869c3e380f4ddd2c919fe409a2fbe6a75328dde9c3d992c62d798b820e607946803d546114d9313e88ac9b77262bdf35e1c43b73c11b8c30619d9f4eec8a225529740730850d5c2d16249c89a57019bbd353b40e1165fe8bd62ee7c1c9ef8c52f36bc072393afb58dd72144f3096fa05e082dbf891d66ab2c4ca14d96ffb7e8c3952b3fd6aaeb06187b36625c20f1ea334e7b69fb0225bec58cf62b413b83a9374f40b78fe7a94ad067990c2991591b6e4d17e5f13d91ddd1d7e124f84326fb6875925d17353a2c7b6448f8d891ea5039bd1864e3d608e3804da5093d20ffb256f22ccc5b920889350868730feff634170c829d0c074bf9c2c125996cd98a4223f3d2207d298c0681187da27c3a4cbbdb49f28ca4bc4222554c66ac30c6d18bf60828d710d4ecbf201625591e4c0a48c709093038c64b1e343963b268997d1f25d7a4d43cd8aee81b0e82d39372a998e80108818ce6129b0d164e7661abfbc0a5771a9e296b2c28f62690c81a3f9247148540cf0cb5a2125caac241ed8b63432a29d526bd51851e0719b60ed1be96ca06dd4e53a9c9d35ed17c4656279a010d243157ddc4b504f577da392197eaf96917e924019e4d86e6f81fa95f9635a7325f142e9ddab771c5141cedea47ba3e745595ec551b95477795340298e8aa33ecb4be137c0095f6a9660082ec1101cfe50611424822010443911785fe66115c1a5648013a2f7a0ea3a7997cf4085125005e86dc6f69c019260fbbb4c70b3268524b10e68b6c848af97dc6010ef1f76c0cc8730ceea18dfe3493fda0e3ea8843b81583c979389752232806b4c391e238f8ff1619e307cca683caab8f458df0a548a43712defe2a8dc0850ca6b791447e550c4b9944540587a34abe394d79aeef5207a111982213904473847a61453395b2a603991c92f89458f1a96ded6cb54e1fe518e464d228b03ec3db28912c2996dd239306cfbfdb612e35e1534c2da3b75108107e542951dea986cff9d63bb7a80cc1206e60c6b0310397da8ada09a173afb1b8d47b3a0b29044e361d0830ce8ff26ca3d1ad7d98cb9edcc90fe19502a3446da68469506cc0c47e103407dd2e257522d63fba43aca386291d54ad3172949e1d6d49825f94e1724491ff24eb95446b230575df98969af9f0bfa0f1fdad21330b79b6cda6813a02c767fe81b23987409e7a7e672e62d1b6f394d9611418cea4b219a4199a179714050298fca5139ca6b7923a0540ec5595e8ba3b8114229cff2521cc5a11237d314be248760102c9525387e1881208ca24110049e799a7d878b9582951b59609468d94f63ab2a2a84b03782221a140f1a1a8236539b75f678433e0d1b1812e205001d09c9c1ac68905664c1bd842245b115dcbe0dc5cc4a74a04bbacf4de77a80be83158a582e4e16fe99df9978a4008bc8e0f3d5b8c84d99a100f545a80fd5f9d734ac5a72195a98d33f121040c48600d2f202c016b76034224e8bf027dd0fb0831b918e9a6fe8deb1e3f1ad937e65f8e36cc62596560dbf6ca63e6e8d496b36f36202cb435469f94cc1524c606e5d672ef8c7046564a366adcd182461b000a31847adc752040cfdf2cf844a803d3e6c5025ef9b89d28584e98672b4224f14ac406da3bba903989fd3b18cada5b7dd29ca0ad24fce876be221a484d1c90735fc0ae296a66b6477b28b117c37b2b22b81f2f03991580c6d3dc1bbbec89e8172f88eff34418c9fa2126f1c1bcb2e46f8808b6bdcad66049faaf4a0dc6dec09f41d90eca523ff56ebad30ca2d172301fd204a38b0bcf66c96478ec659b960d55b0aca4bc2e27279ac4062c060f5ee4a98313601262d5c3f9910fb50e096399430b6909d253d27860ba73d9d144c29f39af32806dc2e84c2ba86304654d7e12bee09f77bd47951e1795d3b87d2f93889a4496e756abbfa6e1304b532bf3cc2425c7003461a716b75ba6134b4c348973caa3030404aa97d2072143e73624cbda50b3fc6cf3b3430617b9eb188b665d6ce615c7457c7f83e8fa01818cb1ab0fe29b460520c2c5d064b26e6fb68c4332a0d72886108f8089b6306d9d37f44b673213b592030a897020656918927cddce4eb84835c00143be8900ab69ecf5e9686167bb911a62818960bf8904afb210e39cb4b54606e0e0f9a7ebf90cde6268abdc16d7489fe43a30ca2a0565c5b3ed6ebc67b313bce14f33270919a614cea271ac2bf6b0a760a64641914463a26ccc2b8bd01a12cde1cd6ee7c4badca6095d869deaf996003d0b378c5a92cc07d33c01953d8d5647b82bf36c1105384502e3ccee3aed7239b03f0159e981b0a12bab2f0120e7f8446b745808e23340503b9ab7850889fee568a59e765ac16f4aaea2bc5b1fea61ba0b4d6e38d4f23854450425b2519de40cc216579122ccb1d0b3a5375dfe64b640bcb0a0153bb82ab45ee22e3a76444664b58d5ecac6a7911e362648b87d1d204b75aaa83a4e57588c842617af15f607047a07a414835f920e38889172814ac681df4a524c7388285e832452b324cc77fc411b52ade42b4102a79ea3bc541843c12951aac5043619dbcde6eb52751c135f0994cd8c8236c2694a8e0f278637ce0b71808f8de1add4eeb355c0fda644e39d26564dc31cc035f1a21cf496a1427208e1807cab328f23035edf6a1ce1cfd7549210b884e909a397da78623feed5eec3b4538129a93bd157a712245f414a318456edaf540f62a802340c0985375684c6dd7843e6f066c539552e8181709297671f0160945f7af810afb67cfa9a3a6629400221e3b554d0e2bd3ff2a76acb99f01a692acbbb6e6e743d7358e0a32a67ec1b94937fd30b4872090be6cbd038da4202c165d73fdbb6c0c037c7f99d6eeca57e27d3525f6fb1e74724404b2baeb957efa2d2f597ac681debce527dee9d74d71aeaf94d2f86a7dfdd718712a978131d1782094095889a563229308b0ee7a69d5d66242b77473e279bac0c5be664246a2d7283d28cebdb10bc5868a6265bfbbe5d86412baa5ed0ec7fc3ff1efea45f74d3e882737057acad9d4d6bce717df80b10a6c2c1778e9996058143e7c4deac8ff11e0ad5c6b683d4b7063acd5deb74788626a8c962f216a41d4320c14f59a47a213dc8fc69932503af1ca76aabce9e537e44d0d6287f9a8c08db62a812fd760006ed92b990579cd92a27872d2c7732fd6d2fbf18ee1a2cefe8d3502d4a2d1dd699276e935d7e8711944ee003811e430c0098ccb7b1772e06f7f6389681d44a7b92ad890e76bbf59faf03a053e8168abe974630566a685695da7764c44a061bcb802394dedab0d80e1f7470220935d2a0a2f68b74e588fcb7b39a9c09b49424b32b26fd0cb8d1f20552a78e4d911eee73f157d6ff84c0a5e55f39cf053f019e71a074858053e3e109faf26a4f3350f86e8a5f1fa9c4a3c4bacb3fc2be8c88fce89105b84ef45f4b9f9832e0d01b5e86c696fcf85ca1708c0b93860273bf85d86a168d7704058a4c4d2a183e1a18fa8fafbedc98ef37d26a754ea719ecfa418e353b8355a5fc57084ed00e160746f411479abaf750f9acfd119143709f5addf57a813b74e7aff4b3063f5d6e2202d58886823c3c5f700dd9822fb31b703c2db5559c822f018f4241278532281bd4c24588697b289996f7b215a60895ef2c72cba4f817ff39d3db24d003a4947094ba9fa6cabfaa29a72452924cc72d034ce1a6a2503814f57df5b1b12679ebc391afef3e48072550c66d95097c6455cd28527facbf5872f75f0e913bb46ab26047613a0cf7dc712cd5ee00da91b7124dc8a4656c6d4e5820e569b970399e282b46b5d2f83cd5fb03f9923869ec14630d9a63c0f353144ca5b3697e2bea54fd9ee8cebf81f7b21d84038a5f034d0110cfe3edf659d24d98a1d8a1f34644f2a315616e57c5bfca79db17ad8b77f32eaa227e39c5fb5b1eadde2f4e065ebb695e8794aa691f954aea2cbfd2661a181ab7e5e4093d069ee3ef074228f67da3075ce4b08d37507276c69b6630619896990431de1f6ae7a263350db333baaafd6702f0e3c625503e8b7a83aa7b2f4cd44c36f72fefdac10a31ef3ffefa22ee8cb8ff1ef6d4138364ef87d0ba8c158da8f6427e093dadb98be7dbbdaa300f14931ef2b340fc0dde397cff4f4902a1eb909026dd520510078bcca633be0837f57c95427c95553795e8420e6186f24e975f8db7a9cf01f48958052fcd56e04a7d7da12fdf03ac68b45f27ec04be86909dfe56812d1593ecaaf11d690ddc2d4da7626bc33b81bbe2b3a2190c30281dcb73b095478af1aac3b20934bea025f0da65f5e980f0afa287a4db9a827fddd5fb6ac477035b9f9fc6992b1a38e5947794f252d496bf5023d0a8e1c7ad56acb1662917cc0c92b864639c2728a650c252a7808d568ea2226b5fa3759000159db5e93fe6bc4018aa97cfd6027254d0bb853696622d2bf9eef8fb984446704a658708f49a92d489f5b45c72d6891ba5be8de23c9a84a9f9e996f61277bc6330ced31db12f88345d1d77311cfc9fcfa5292f1a01ad59d190190311541f183a5f47a04ee8eb75dd53548b4f69e18e1f307c085395aa106755f479860a8725cb09d649c5d78065af0a321a497174bea372b93bf7d9594f6861cf868cd68a58e7aa99f28b78622af5ff0a22976042b69d116c0d0497aba18e6736a7d48c3c86d12858b891894c8f3cc80026824510845e481a1b3e11d0f2684619de9fe0dc253ea459b01eb42e90b983c6923115facff77772267f04777423882066948caaa9e416cc4e1a0178d318539c76fea8162db39e83af1148f9dd7f7597782d795751fc4684b13c52b2a86537e348c84268c92a322792e5e3a54ebc468964e2cbe062947acb77386ce2246a5140003459a66c78024d59b060ec383a851984c23ae371caab2bb617efea0d1e8a0836416917063c551378721f1cfaa1f5df484e174feedcd97229cdafe620242caf131901aa15551836ff73e8516d05605b1a5e8808237f97d70a3cc45f48f289108314dd96d4a4843af99e3b7e60343d5badb1ec6538eaceeb4aca84bfeab0899fa2eacf83e980e2ee626db9d7b142b68497b56f98ed302b26f46bda2efb870aa381463452da3a1dbe20d3a70a2c145a5e6498cde5e47dbfc73c1e11817e4715e7333a5098dec29f595eac3328bac7c0754601801205172c3f9251de05092c4a2966ecb4df90b408071adf4b7bba21f6e1c591b474c16deeb830fdfe8f0feb73f04547016cc30d06cd323c00a482b0c18ffed8fc0a32fa157cbbfa85cb1749ef27c51d81034677c88283bd54286f232109a80977d31568592413f406c624dfbdc2e59c17f31e9a5330e076ad0af7218ffa2cc60492fed73b9db25405227e938e87f4635ca4e3ab0a10ba449cf26f42588f0389e6779c217aa2b04982ab32898d340abadec9b2311c9a68e95de41fb8cc1f4efa61047069591e9bbfb272343a36886e56833dc50b7fe3fee371683587061ee3291e854f84f35228dfeeeb2e4d18f4e8d7b7c30917e37ada97856850368000c29194a10040a5b43431088251af4a6c8a4bbdd120498da1996499085b0ed07f99b421f08c5f7b3db680461f044a2f9fdbdbce0118ad78dfef022cf82608a10ec28ab7a4c357ee3fc2babff42167e45638b6e350e39d317ba2710f6e34ef35e475482bce9588dc69d1999cc8170247122b77339a43ed99458f42fd0d97de4b7581753f3ac7909742234f556b73e24deff4a46a93fc9cac260f6d983e17b2d3b63c0fec3da10816a0a877940b5c908627604ecd7e743f12e182fd240e0e0302e11049426f5a19fad7ba988fb2f6237c540b9c4c6636d2245e2ee4e9a38b3f2dba2039d9c4bf8db5580ba79cb706af070cfe3a02dbb75e69c2484d9f58b5a17955c309d872a15b87b3cd665a8657ccf2e777bfed3a8a94c987032c5ce166ee6ebc527450f8ed3375a4a312b1449439225a9fd39e6676099a06c501649e42ca1ad877306dc6d4642011f1dc77536bdd27ee3c507496da59d9d8cdb31dccceed3e830c036900abc380399186fd356c05149c5916d2db83df49918057219294b45ebc5d1e5985b5ee276285e0a425643e9e7907f130b0a840de58b84cc59f73fd381b7f15bcf84a505d60a94ed9d5fe1139b255cd76ff4f73e8978a0957b396d8994a5a2f7e0c87a062c2c9076a08cf88dd5e0761f6b81239a2a39477a092de9eda98db0e074a3bc2ec4772731aa5f33c2694758f3c75c89fdce217bd2d4b8bcaf6f366e0199353ca22722c9fb1a7bc40012a85739e01a036b8b79ca4829dfc0916c6cff7f5e91104dd8d25fb3409e1db693046334709be318437f6d6c43e2c0580d3a500456c44f2dcd66a95829a8640d570326e2e1ec76b45572ab13bc22b2d71b469a29aa3257dbf8c55b4cd27ceceaeb50317819023c07573d4cce088f9c79e1d401431568c4fc6e8921158060712dbd644ce38bb200a41a44fc24012a3a0543329842189e4f5b0d9f30c0a98c94150d2bcd944a9595ad7ee5ac390410fd376a00b8ea88f8f904ac0a0d4127240593f8b75918ad4854920e2ba11579306bbbdb2a99d5c9498998b51d278d14a598d52d7ee72c38c850ed3362026c7510f14b408952a50b38c4c96de6cbcb47b0121aac0c8dd88bd8da6d51e529a8a43aac006dcc6316ac6eaf6c4207a742545634b430130a45afb6fa85b3e49041345fc488f805a35062977c1a38651ed807d7c00b12e9cb4f728a83e9cc07af08c633b778cb24d2e74a36640b8c95a0c36a60445ec4de8c962a4f4125d5700564631e6383f97b382d804a64b9eb3d3e0e20f278ec439c6849a642c72f6b0c1444180aea07c17d6d0008f26ce7f9805edbc09cec150aa14457d2057398a9f0ed0622753ef44c0504c99a75e7dd20522526be5d84a0ac51e9b49dc65a6272590a309c76aaae1d8b4936e30a588ed814e6f53cb8a067639a8f98bf88f861bf76b02f0d03cc7a2666f454170db6566c304bda4a6a51d1ca5a5314b69e5fd18ce7c45ad3e4ba18f8536301e99e132cf6a54c1a907e13fb2a277d31abcebb2246a3099e59581cc0cb2f67e64946c93952fccc094dca42641e5b89f2c39d39dc433fa59208775d2c62deb25a26a29a2c2b4f417df97008382fa3f5c250c1eb3451e0aec62ca80b8a2ca63defba4d24dba418d3b9a680426468258c3f784207daa2d42f44e0a3df38a665cf186509c57aaf1917db8a44bdec5dcad6253426347104a65fe61de6510df5c6780cc59130a0d666ac52edf4da90669fb9d7ca6e9b71245d7eb3731dce88630d5dd809a7567e4951591010e4a9867949403f0e7c823c5e3cb8612692289a28e7dbf831afadcff87df469ec7dec44bc7a87b5ccd6b6866d48d314e503bd1e02e478039cca956e633a1ad21ab71134dbae7c8b7d86840c2645a6a52d9bccb63d2a3c15faafb50403691decaa77c0a1dd7374eb05dcd88411e6cca0f3f2a4bec509e6c466dc02acb426bb8793ccbbc45d589343401ed65717596bd8af61b26e96dd2fd6cc1953e52ac9d9d4f5e12ab3f0c8637d356cf8c294f07a202e5d617fdca203cbc611b9b99d317748fb430228b08ab9272c2f65baa0a02b51cbb2cd94fa7f2945eaa5f1c22bed9f153ffe7132f9b1b0b272a2ff2aabab5502724d419f748823111b4ff2c6c648e4e40c715b92003d4ae048a3e817d6cc2441f2eb86ec4904559e972b195edf246e93867f14c019a60cd8a8a00ded10a04ae4639fe7259d08a6fbb36109576e204b0e9016c1c2e93b51841480057eef97cb76f239c9e91ec028505d94951251909d30327ac3a9d93a2459d6003a5701bd3081d29af9971418687b1edaceb2248aca3f4e077be52986a7ebd7154664617f7a1ac124fd0d5264c0969f962241ac02b0c147aa0d4883f5b947a2f557e232742a8e271532b0833e4dc752557a8ed757cb6979b08d7db453b0d3adbacbdaa43d5c19ddc40d1d1a562247cc3e5921226315df446cb538ae2150ee4e8f58ea2a96adea5520e3e0975b7ef874fcf6eae0a0dc0c61485642fca906f900c17e9062cf53abe2db3d9556ae36427951186633a3080aea018f7b0a5efb32bc8c3c7a0a7954967e4cb719256db2b4b5c87b82493c2f7fba7b3e3153603a538665481d1a6ab4f664fc0c261a8a4f97f597a4a7d45dac6737026320502b37ebc1858c3899e62fda9a0520a51618864d1e1c8ef73cf40f601368a1fda5ae3067f2ce1215f522af9fc026b9399c1d8f04723a04528c3035d43261b6573bd4af8505f375514c3bc05f51d9e492ccd523562edd357b84197d5988e12c088ab000a61b717c3a8a592238c82e7238b79934462af2f91f8da6a99d5e60688aec5799049a9e01553542f3cb2412b73b1cc80ee3e7e9906bb77f2cf0f2bab96671c795634ac815cc06a5664a1403dc2ecc6eee46be9ec080797be0c5310170325b1964bae481cb16c49d721cbdd2e05cac171e78e47deef644d8d0a8085ff15ed09e8de7b6068ec6d5d0bc0d29d36b11627a8da2f52b1fb77cc77a3b34a45b6a3eef3b75a24c99686bd89dd1a494662947d880d8fa58fb0e36cb61c1bdd8dca4a5c6f26bb2c7ff583085ea85fd45542b04caf47e365b3c3ef4553fb34cfbe51c2bba2ee5a3eb468991265e8e466c7b194b82615b007abd41bee912c61878b8aa9f9d8cc407b6c638684d12da9a6eb04fd648cbd8f5eb790d4938d3f63c0addc89d390273fcdc80c0cc8b82c44887bf77e8e4cf4165abc2eb18dd0a62a4b1d54209728aa19e1438eed141d250545412a9e5a15a62ac72868f089d0c5acbe5ce27deb54dca464499e93dfdda1d8c05eacbfe6e7ea72d4f705a1c3a085086b41efd27a7cc86474235e2d02675db40caf4356dbe5b419cd205b359086e08272cc551c16e765c5eb88138cdd509d9e371a75e732c76ad58ccc49f27fa233afaf5948ef43cc415d446aae0d16576f88b749700cbfe829af6e871ba9df7b56d8ca7648d28a6973f9049becd25eb0e4c08a8100ac6a065fe4d64e6b162fc71f74f34372a589b63a05f4248661f3cbf48aef97df0f2494062512181c9e4d981f2cd58a6c2c76f269b25208039c2444371cda454c7bc08ce5e9047e2a8bdf3b373a42e88cb1adbe55116c211133cd11adc8a6bf5c18173cbf6a89e301b966b0842513f24f82c2cf10bafb6f1fce590eac4620cbd4a00b379b72a6d64bd8e3bd602afb2c6cd564bc83cd78cb444ee09e5760ce3ec0f6468dc1266497aebd74c618afafb909f887a05900d27df95a619460412736ee0f83b21bca422675263e93bd8af2280bf4d920291b4f3ff042a59cb8f2a8117959862743e2ece165a0b2921ac61ebe9444823880928ccb34c7a4f1ddda7442918d6f7a40615c05313b45ceccf34b1d0f04d7de7c55c5a651b4eeace0489f9f95db6e5445352128f169fc9d8043bfe99a4d4f18bf12e74849fc58507ba36f2803e7cace90bfc8cd37d8810f5eaf64c5fb25a02872816755d8b789de19c669b7dc55e39238cde1f373a91bb345f3ff17129c931e5f645074e01e83a3e8e5c302d51b3d31513793fd881c4dc5f683275032808a2caba2c2d505d1c905b16b424aa7b043990a9f4a4ba0ff690865f5901e48aabf6578f611c8fbbc4077d9760616bfbababa95d3f3ec1d3200131a7e292ca39eea689805c659dc2e6e7810905fb11a3cb2c4b8a4187b0acd97f5c395070c75556ac6d040d5ff4ca3aa5d98783cf9efc991c0bcbd1c123909057a52c1d9f9201ea50b20d2cb91f274aed81cf5cb81406119a0fef0b1f3337c4766c5fe208121f31e2d1af17f68a0b565c58c482fa906570136b411ddfbd6d43988bfc14ef86efb5b8472cfc96688a0d337eb71039dee0e00b56e5c3695ed6d561a2354c21ec4f4d77a9f00273c98fab82695ec965abb6d7c23f803395e90ad19e18cb50f3289efe49956e9a3760634573530b60988576649987e2322ba6182eba9c704ae565635f6a6536032092f52aa46b54f3b600dd67a89d37f26671218b076a6f56d91bb8b5f0582a29c2fa2335233c10bda7d38df36feb9a4344cefb8d450ea533114caa1176c7677bed68a021efb91fdfe3990ea4f3ef527f6a865d22640e0be610a233ae156e714097cdf029eb454e36c0ca06c7ec434bc8dedc2c24b6b83c64cde6bd68596ebac745555f0a7331f8d1fc9d165188a9e06b7cb39f368b2c8c5802d94553c26356f5d39cdc6406faa129c6a204f5072c05186aab716389f637f1104b8a6ad0fcc414ab9b5e2ba8fa73ea9a1bab66e9392bbec0d3a55a5af956064d0faba512fe1b1c69aa7ea1511efddfa8f5023301d5caefef01103c691aad38fef6f2fc429fca2fdefb3d015fb91d5fd5fce8db130620371458fee9097e7b41b7858322cd2896d197c6c973c28067a469f10584e1afdb3b7daa403fc4c31829f98ce51c39a30cbd79105121ca98dacf24516b07cac74439685ee05c6665dd0c9794f7a79d14162ef941a1f40f72b834a1066fa27f1486b1138b9ec33b4debfcf2c4d8d85d2d16ddc39109a252b5acbc349847ecb89381672114ab0e0181431a3f3aea5cf46fa764cf4bd54cdb43e18f873634388e4d851d77b39bdc64b91942120a32d5da033b44d705c5335b0d86894dd15054ccfca41b7007e59ed2f602cec186aa5582e8d70afc13b9a7ec7ac43b9a5232b8552a39efdd1206c9a3bd84aa5add9ed96ba4ccc35d5d75bf2811a1f2fbedea30896e14ba2cb66fa1aa666428c22528b7e746ac89872b9a67709f0b6f246b440203382df24ee3d4fc8227690b57b864614aa6dcabb0dbc560b1566a0d6568d16db982bac345daee60f2098302e4c7ed7b43a278c3c7991d9b6467ab5a953c7a0e0c305b44819dd0b443514d02a7cc33addca33a142e4be9cdb7e4cc8ab987e21eff33492d40ee2e899a2cb4037da3040f308826a8d9f06c26b41882159c06a41baeff83e18a9aa7fd1ce72c8b1b6ce286445d14820d2179a2fe59475431d4d7c83242f6f8d36a63ab5a3af4b855b1f91765644bb927a5c0549aeb16a0a6d418835ba7e143f4b4583394963c5ba90d7ac8d488f56ddb873655e8c5968ee5b3526da301d14347143c30deab895484ca1c4f0afbcfd959fa91b22aaf968d648e1db40c32a14ab41c044ad10f3e925eea3b8208d9fad8154977235a45b67ba5888068405b9e85d79957e48fb43ad322ecc471a963151d29b7569b115bfe58b1a9cfecf444cc38dc179c387248ea414fd296b55ca74bbcc72be2acaea5fd7f80711ce61a4fe06689a41f5be26623a1f4feb8d1c8b628e723905ba60c7d4989f7a9da71c3116cb5bd95280543fda2870d5dd47e9b0bcd78611bb6f580171b2154c4045e49f5358129ab2a2720f78bc3e47cae048e0e70f1a4aba336a8207c7000a38410dd2e43ff8b6702792a434c939bca88c2932192d1fff093c185f4373f480b027c0e9a36f14eac23844dfbf9dafaa1191fced6bef7b8f6aa61e330d42983d646416a36bf70fe40ff353e78c6ef20aa76903f58eab5a6cec0ace88bb04193740a10f87740ac2f30484c39fcdbe4f1b988d70f32161a8f494e4ffb0733f62e3e58f59d4e6406caa8cf36f40c82c0c4f903890ff60c7b36d29f865e2195aabd3742659be00d73d37b1e86763b559a22bd5ae5556dc102c7a1b8ba286fb74b2560f83b53929badef1fcc9077d12a3983dfc2666a8c0f362928d8b05ccb40372df3a8010fe3a5577c2fbfb11636e4a8d603c6a75cef65cfe34bcc61185880633183f18272fa383ff877add62a9f8fad85c42c63e1b308166e89838190713865041f72cd8e0eb8622ac9c011bc8ca8a48ef0388a48855dd77981145eb31af7f1cf2a0178506c874bf4d9bd2415f8c972c060ba20a18db0fb4f1060da468f2a0e76f6f0c4d65cc75aea37792fa58f71394e2d89998b8b6f25492afc06e4609a64960b59b1b7b9b1be71b2f9e18516f249e798a41e0048d3b25f8c65dea30c25729c00fa166b35c32504e52b342e0adf2b6c8606344e3018fb658ff7aa3b4e3a905face5e9bb65de6e44723d18b9279806f6a911e24cb0eef6604a087d664ee48db53eb555f4c5300f2a67ad3ef4f4dbc89c555f01e12cef3a4d95bf5f6d4709e7652872866f4c517f886db0586beb105cc297c75ad513cb4ced152a79956445e4fb3be8b0f257c80191a5d42704dae607d457105867b639bc66cdd4e3c78d045f1698bc073154edf475300f5efd4ec1d11465a34ee81dd4639a0cf99ac0df069b3583d49ef6baecc3826a1c269500ce0e9f134347188b24e8bd6068a881afb735dbdddd5b4a29a59432a80cad0ccd0d2310f9db12e54d54b01088172cd15ba2fc07cc73c92be5fddbe4c49f609efed93a71844d25854b511131754539c39f9c98928a12fe50b937b723b591bcc5161eba6a56a83b1255993cf1f187cabdd124b7fa5ef0d919a8ab4aff07134244ea1af3fc8be75bf3bfb7bbf75eddf3ebd759ebde7bf779ec6eeffd76adb5d69dbb576b7db5d6cf993e29d812227f943371a6f27e5642eaecaae2909073ee1d87dc8f14ebf36f235a5ceb63c0b4e052adcd22efcee3cdf986cf1ac39c73d6f7ee7c3550fe5a1cf60111e9fe2e3990ad31cf7310f370e4cfd40b9e39bbc91fa910c8575e725b6144c9df35baa8328d1e2e4aeec52b5b036603b173944b952ef2f3fffe8e5f1625aaadb0e2ba286de88313afac0fa78568b328ef7777fc3cce8a16aa9273cf691f2f4aeeafaccf8b923fc75d55255f42c9df039d4a4e9240d948ee9c6b18a8108355c04c7da0e9874a0f507e00a2c9e9c90f95294414eca1d45f16d793a77e7fa682ec1fa91f2a3f53fd67ca8fd40f959fa9cefd50b1b9e7fbfddcfedbd254eaffa1f22335baae320dfdde53530b53a95d94ae275322ca9b9e7af801082188d84e686afa99faa152b5b3ec2b2698290f0950a5fe287e80e2aafc2e08134244e27e9728871264975c08ccc3a5601e8ed4d742dddf4cf85312a57e1815220a3e29f57f3e7abf3cf7ef977b4c060985b871737b73421b28e391034d0161e9eebddc682f90afcc5f0781b0006d12d5a56d89626be807c2302a22caa15495fac124b01253ae44516afe0387833447a24a480e84e5dedc67526e9449b9f7bee24cbc49ff93175412fa235ddfb7be6fb5be6f75d7772faacbaf7fdd7f23fa73a575f9165dae1fa99f29ae9f28c456eb7bb1f5a2cbe5125ffcf8efeebbf85cee79f1a2ebd1558bffc285f8b5ba0b6cbd2892e87d92faa1f243e587ca0f15ddc21d0b22caeb4369a7e8e089685fa3fafbbeafeb116909ffbeee3bf7f1ee6d677dffb9bea5754bebef5de3fd164db65adfbb5aeffa7ec540b80504bfa55d5dec1a6c7debc51649bef6be3f6b142d10967bf3dd1086aff448947ff731f83952f7a630e79c591b63202c2c8c3bdc61240f6c8120f82c8c318bd5f51c622b202c405880b0702928b2942b94760a1449945f0b09edaf7707c1df9bb59f05760ed525df32dcf2eb32c862812c16f84019ec7e44f2de96177ccb224130043fec4823b9642d2991ee7b6fc3201eeb518e8cd1c39005ba5e24d1950682a8ca04e557254892dd2872245ab2702f2287bdf31ef6ae75188661efc2f04a18863f533d0c4374d55ca863845feffa73faa9bfd8dfd5bfef2cb2fb1004bf2eb6508f5c75d9fd5001759833ff35e79c31981ff3fc186723b9c4ff699cefc50f340574a523edb7e5c56f3dac7114fabe469f802a754a3be5891e74d0f4c4d513537c8469217ecf431fde47d23a10165007c16a53f5bdf3eebd77168aaaf7cfdbaa4469dbca7b3c7a24ee321016202c4058acb410f85f6dc7fc2b2eef78450c723f1c47da7c918c28d96f24971c895e8bea727df492363f550fe50e159513a8ae945f0eb9a752a254a1b453a8800885b8bf61cb73cfe51e17f89df8dcbb388e8c01d382b6bfd562912e318fbd1b3fb27b1dfa40992079597fb73f9348b7731286afdc794ff126e2fe1dd5d4ff6381dffeefc5fdfcde7b2feb2fbf60e8c37b94df079aa2f24221de47bb490bbef77cdc5630605c5cb8c0bef7067fc5483244bc37f8bd83463289248641b8bfe0afb8e4b894f11bccf29a623fb1f7c632f097f140f86e2b4ca2dc2651916f2bce9fc3fc31c6559448e12f29f995a1cbfb1c508e62cb2091b80f653c96ab033fa79141d290282eb9ed82bb70180e83739ae76448db30849b8c09bf3ff8f75beffa9897e1240a843fd014d095ab83ffe69c53955dc67fbbc778c5fc8bccbfe00f6e2ff4b11f0993e8edc22060b9df3525f336c7bccd2d70f3ef2017d5547edce66538da92e3fcf9eb921b390f65800f75a5e4ef3d38de07ca3423eb67c62e33c68ca86b8a039aca3d5f55a6a13f0689723048947b9130242abe902e24fa7a41ba205119fabf56c7fbff161fb3feabca3daea94c437feb51aeeaab029bf813f4c6803d416c1963b8b7a587d4fd7d978b144532c6c5b5489413b76baad41bf604317cfedfb3c62dd51f2924d1abc32060d9bf266b9b695c3f1016f0bf07fa1e08cbd561cb8faaf4c65d72bce4465b765df75dc7751208cbbde13050317af8a1fcd24ec18194134e4a9cd24eb972a5c481142b44e480a9044b976f81290752e58dd24ec9815389a5aaac29ed142c52ca2958accaef67ea05041fe65ffe92e8aa3906caeeadf511bb8fd1911cf961088357deefbbb798e7765bf6cb4d619efe5c37da50a87fe17fcf8de1ef25b20bbd24aafb888a25be8f3bbe8f7fa24062fd92f28e682e73e98249897f5afd5b1c76f1e287cad5819fbb2eff824471c96afd4cdd4be211bd250bdf7b3189d47ad6639423517d43264a969449cadb511d4aee3bf913458986df8d48f89794dc886620b96c85dfdde7d09f2b251ae612bd41d01097ad47575cdeef24fa335586ffaddb229138b4c5f50e03f7f8f6b1f56190bff8cb656075121d3119c3faeec3df4bc800e46f18e43ef73f523f53ae0eee3bc62587ae18fd992a3b31f4e1f21c89c43dfe964bc77df85de8d275dc87cfeadd874ff4cebd4bc87d17822d30955f6b972cdee238ae35722587c3ff99e2a3fff7e1287e86791763eb3bf923856bb8cf07dec279aebfa10f17e2133e949c7357cb18d3bd2d3f57ef640b7a6dee21bf18302d42efb918302d4212c9fbeebb0c17a56da99044ef93d40d815cd753e92263ba6f3df744c95d52acfa73df1a614f10575dde47575db69e7b90d67a16f830e3cb189228f7482d12bd3d0c0296f75b62ab45a2b42d55dea7927bd425a5e49744552665fff0c1ff01ff87cad5614bf067aafcc6cdcb0e5d2a3dcc4213507aef955ef7951df943e5de70a42e5fa0881826cb87020e72ce596bddd4b4f7de9973ce79bebdf70ee3407e0e88a876ca52b294aaaaa6fb942f07c4fc962b758f0d656be44bda9c737eaae208509a94a89562314d69e203256aaf94f8f3b55cf7a1c44e5d0e601b980185115351ac982262ea08ceb5606a02e5d405a6425065aa0a14530aa045d9a109262c822881952e812a5751fc50650a8a2a3d5840152450204195a82a4b4c800a1647a042042a5754b6a8c24395075471aa820108b8484521f12f91cb3eec0894a88d41a9bb27256e0d6169e037a285442baab544ab89d6132094279b6c45b135f0b74028ad2c31a66eb6d9e5c908d7e69cb5d62009e21aec03bfb5da5abec3cb69cd431f37775ddfa1102663f423853ebc5b9217c8b51dffcd75bebdd0c7b61664853e70195e0b72d665595626ad9781effb36c8f5ee79bdf7ce39580f22ca954ed6a7a5c46f610e103178b187b19773d65a6b1f7b83deb8f7de9b83dec839e79c7b23cc07b1fb0f6a8a37e1275355fbc98d526585ea28657f1447d93d12e683c87df73ccf7b90bc2afb7be411cade5d1e0882ef8da1f760e8a3f5acfed4c35586e447a2b9a9eccfafcafe9c7e54895349d3f993c125330e335436a273164ab49fab6c8febede7299b9d728f8b04a940212c6179c02cb8068ffd02960648957b40ab4c230be6e94ea0102c4fa780ad0141d02a6fd0aaeca1c0e3ef4e9807ff0ea7dc93a59c304f96ba4077fa9808493497e0e3cf52556c4feb5b632f9bc8181083780df146d46424704a344bf594384bf129589388762729a61e011805f2db1968bef73f9b2f66a1b632376518034494db42427bef285138e7bdf7ce711cc7c12060516e0a3fc14faaaa6e941bc50a470966180444fb28d75d21d81635194a4aede1eae12af53b591ffd4d3e409517e5508ee338ce87ecc3e783c5ffe205e6b87b2ff7e205d6240a50222a835f6b1e780c0ddcf727c275bda138ec0714a0442fdbbb8df872ce39e79c73ce3926679e73cebdf7de63f82391de9741adc3bdf7e69c73deeabdf72ec21e30355555057b00c721e190e4ec72b95cb9a9f7dc91f0c5e8fea8c8f3c235f883581a98c68f18cb0383ad69b5ee0f8c7fe49eff81793e88ed51418a95ffdeae555e8e64ab3214504dd6a7c5660ce406d80c959bac12af39cfff5f661215d2df999aeca000273c2c80014d0d70c0037a80400424e0e4c30428f043052c70812718c8800680d8401e51a14b760ed81af8856eef80ad81df5a6b7913afafe13d798a12190ad728816ba4b8940ba032d416f6090b381161840a861842133d0254aa84a0ca10709e3800ebbdb3c87133ac72c7c149603cd26aca3b017cf365e2323d8952a220c884146e0243f0a1084fc800055254cca0f3d0156003204cb1c10fa6d880063648c23ae1861b0ec7862ed399909a61041350f4171298a0828923b898c280290f98728129209892822932985265ca1353a89872750308dca0033728c10d647083a91b4871832c31a8ece0a242012a445051824a13547840e589184dddc90a56245880076c10832b80f00558b3f60361be490bc690a95e188529b06f3d0fe1b35e8722bad701fcd6f3f0594f03f8ad27027e8b24c2c75b6ad80e44f46f26516da48fe8083ba2eb9e8f9ee78d4cf4f791eb07ca1b777813e144c5b8d25e595e572f2a8b7bbe677cfbe2388298c65b291a285422daf22494fab02527d5f27439540c98ab8edaab9627982b982b982b982b98ab181c8a4ff12a0ec5a1b8125c0a8a53f1f145c58b5cf1b212c2a40cd7b45e874222f71f897effe31d3fd3dc1fff9fc8f8ff3f92e39379e3dc333eca95afd7dbfe682e61bced0fe337494304aed10f0b427cf1b09fa1517872f13452345368a668ae7028ed020516911dcdf8eeb9dc03fb0e77bc03bfebbaaeeb88c03cfa69a0d008411305f3c878f91928eb835fb73cbda8300ff8fa5f51bcaa5edac25c95a3fd1f9fc8ff482a2982bf12d83f91fff14752c98cfffcf2a885799446ea4535bea2185f55af2a708dc6a08c7cf9951027b09ff11f8c0722caaff400fe51f8018c4fbe0e451099f1e4eb002363f8a3f0ff28bc0e33c8984cd280c2ffc77092080aa4cd34f0c710f9f149b2ebc0f1d320cdc8df8660cef9827c0cdf863e9894985604d7e88e944161b2a5696c71f27620babec5a9a5c9fae0eefb8c4cccdbef63fc7b240855a5fcf60dca2ff75028f43c520a7684c831ee587964526216ef62c0b4b8f22c66a13994f8c7d6cf8c3223cacb4e4271650c12dd55fa618ce85ee235a2bb0a6644c59711d556b88c28154a43042ab6b85adc4a1c7996915f816baec0353a8b144d109686fe269a78e28929a6202dcfc2b3e41eadadaef68b8a068af5e1afbffbc2db8269a45a9e34f791b9e52908b0bc9ca481626b68d45e3525c149945c26cc83c7cf236c8a0f0af17f4469a44a8d1ee11afdeb38d23c4d9419285b433fdaf24455ea6f816a796a79d22d47b4486951a245eaee5006ee514c7278bcb606c6a3ad81f10b7b2db69ac5afb5f7969c94616b35c34906a720c467677c52328dfc535354dbca5ad996d1653f8b43a1f0bf0de2d087c75f67ebc3fb48174fc445ba206da641a485dc5399c6feefbb20c843a150fc4f24f1886e29d81122fe1ca48825abf536bc1a7c522ac1ef411fb46db5afb6150a44fcfcfb73a858722f4617e325d12d058e7bc4552cec35715505f17d9312bf2bcae6a0104dfdab6aeae0f7e7faed9deb7f9f92f891b265ff98accf7d20849002f6defbe53e8fdc23d1af4989ffe28e4479942725068243e14fbeaa2fcb67f55d65ac398eebb78f4cf8267fa8809fab2fe1b35220ae41ad1410b652f766980e44d813e2b5f86e51762288fa33d7bd27d2bd6773473209da56a2fecc31ec8a885a29992b22fa0338fa11c30f21a88d22e3705f833bde49b0394365a912e3972a63ac7d25dc772447c453d2bda7edbdf729898bc4a56a3d81448b0af5ac3c2bef08de95e75995f8b716abf41218e7acb5de7b6fce39e7bdf7de7b165c165c165c165c165c165c165c16187f4cf6e59a6058c0b0806101c30286050c0b1816302c6058c0b0806101c36208b02b300c0a917f7ffcfdf193b027d8179f88572e55522e536c2bc42b2ee552f53d8957dfd36e1ab7d408f50319a832c780450196f85d4db9676acaa21fd40775f5a16045c43155db690823ca8cb63889516c8d9cd197abd2abca7f65a0a06054c0a6804d01a30236056c8a97131562feefb349b4b8de75af7ffd47efad2ac45bb65a3f3c2bc559299e048782e250b7bc585aa024c13be771286ead3687ca1e09b5f7770e55626b4545c6ae2b2b44b4054af9647d3cd65bfecafcc3d74b868c1628200993054b0b94f2e5e46d019b42cc97ec9da481301f8897e35c08ce34037dbd5eafd74bdb25a464b14489a710b38ffc288fe2c345b949310822eaa2e251a8442b3e9813e2dd55501dc143135455b01e004d650e340544f582cf7c0c8570ced9e66b9d3cc799e7ff91ca778742f931c69cefcd37b8f7ce7cb416f7dca09d73ce1c67ab35aeb15ccad7719f6eb5388efb0dee1be46be1df3f54c2aed3e89612cb2d8aefb59ecb9f4872ad56abb5ab187796aa4c6303e590bbf243a51b450ee4ee731c5825fe4cdd70731a7c76c61525aa80d241dbef0d830061b940190873a00d817c24c7bde7b1fe7ef7569b45eee7210c5e197effafdca1d0fd2e18e6fcbc89bff7a8885ff75088bb376b294420aa7cc3adb953169f9dc1b2ff15e1c3c5f7e9a94c23f7b26f7caf0de24c1ece4d2d1f40532829c3d6c06f37cc0811950125f78018bf0ceb636128e0a59d816f15adfe7606dac254e22b3c20a2df154e564c25b6c21c4c8b7b6d88c4578579aaf8b27c57601e2954af26c4254afcd67e51355655b9071fc11f700d7efb77e4e5aeca3de00f18882ca013ae4181783fe32d856594f895a5943560550b138741f8d67b8ffc7ec035fbfba1dcff498952eeef8947a2dc7e1bc479dedf50c86bc23c3d7c4e98e707ccb3a1f6fe0dd5ed2998e706799f3d2fbff7a1077a52785425d7819df3cbb9e51b05f7f92501d0141016aeb4593e3be35ed27bfb97c62542d435fc92eb2d8578e731fc71ccfdaefb1b0a7516ef204a3996760a1627e577c11854de5bfe636bec2cb0138868abcce28b57cbfad8bc730e85604788e896f27e7bdffdc8a4a4e1ab510656c4cfb03eb01688e897ff18f6038bf1e3c75f72a7077c56387f1b680a080b109585a22a6d5413d014109512df9622d1d5097f3c6a9cedbde118bfe6745f52e62fe458e72fef1ff2d67ae7ac757e3d66fc3f53650b4c2510cee38f940ed2f5f833958df41233d1fc721f0871c41e4774d55a6bbdf5d6fbf9bdd17a447f7ea4327efe3d10820a0694a54d5266ac7f72ee193f261fd75c8d822662a6b27457d6a22e57a8785f5bf11115a194412eca3dba5c959fbd19b3aa88a8cb558937e9d2646be08f51452542e94e882877e272e58488ee2a114aef315359ac58b162c58a152b56ac58b162c58a152b56ac58b162c58a152b56ac58b162c58a152b56ac58b16265c669097b37878182030345c60a14e685c2404161a0a8e07aba6aa2f77eada6b6d47f7ce42537eaff2eb85d4f3648dbab311fb387ba9e78f9ddacb1e6f2163b7fcef70d7b682f873df2b7c31eb8dcff813dec71cbfddcde0f03e506d9105645d49a7c91c962431174302c22ca8128efc3b088e8beeabe1b679c8cf0cd80628a7a2f1397899b2f13375f266ebe4cdc7c7befbd33719928abaa3826c4fb96c3178f423ae33ee5e37f41fc3d0fe0efa94ce3eb616d4dcd4783b534605b8822f8fd3faeedc5390cc3b721cc062248c6f4e0d66aadf5ebdf54e098c1ff40f05920087e6f5be0ef91971af3dcb7a10ffee04b599ff03f19acfe91e0f7db2a7bddb6ca3d7b89efbdefbfefbee7be0fbfd79807fc7af0ffac4f2f2fa62af713e557d57bf83817fbf793b02044745f29a5300dab2ffefd3172b97fa4f4b8afe01afb1080ea52880003d727aab7149ff52e9184497189ff8de827ba5caeef7bf123615244f11345f17389220ca458c1a4c0a458a04a4a55531515e79c8aa38a11856a8a8a015151515151515151515151515151515151515151515151515151515151515151515151514054c15cd083145765f7444ce14195ebbd96f7dee5f52fdec5cbc34c95e0bf40f1e0a944b9f2b50329991730be1552c118d1f0f5305e306084218c3126247bc0881205c688862faad7eb6190482fe148db542f17aaf2e5edc3b878716979e1c2454b4b4b4b8bcbe57a6f74c15c2045162992e0011410a4a2a2a282254145451585aa8a09980c6031d831ff3d11efbb073f1eb0f681f5bd9c8f5c8af7eab0c7b559efcdda3e78be5c6bccf7de9973ceb9e69ce3388ee3602c10f16716701cbf57939d8fe00602f22d14e29cb5d67befcd39e7bcf7de7b872121e2cf55554888dc470baa3b16ad602ad8c10f2dbbdf5ece398c7bc99794586b7c337f3deee77beb7eb1944fffc7a4fc5ab01564adf7de9c73de7bef1db6820eb602cfc7ce19e3bbb5fe96327f8f965293b8057726607c5b60e87de0e87ad6f3207eeb73184383eb594fc4457acf7a22a2a735de5ce6a206c1d1872d41d07bfd5fe73ec2df0f861eec08d123d1ee711fbda76dabccf3673d2ed14c3c9c7b407535d64ffe4cfd4c69fd9a4457294dfe4cf9a1f229f1497d50b8e6e99352e69dc5043b8bd626f834de44009dac2e266d925277202a5cd3b1e6dfbdf92b38081016d049539941265c93614f22ce4c72d67a6723b9d44b4a0e7b12f1e70d3a6d21327784ab57f7421485f82ef8542017855fe51e4e457545754575050211a9a3eaa8a2a8fabc2bac7a7fd29f7420fae7014d75e426790fbc893b714e988763ee847960423c0182ea89e33e0f21853781804b01c2c295b85338cd494d515df1a6abded499780f545557575454405838202ca8daad5e40583a14277cf9f7dd55acbff82341ee03dfcd600f2fa9612210a5720f8f9269e4e738310a2c04513e1e8588a7af4bc2769e8567e157f46ff70b832e397247316eaa5db5a9a2f87615e6e17857c13eb0b3ec2bb613300f13939d05f378facb202cdd0a589c70571760e16f03b97a0e0dc36980a71d5cec1aaf879702c741afd073ec8044103e761881045e441c27612c060728c1f4dd8a34a08050a7756d90c93a3a0c9e265ecfc13d8e957b80f8c8335e8f2430791d483884fb1123d4c3eb7e8043643b906082d12cf078dcc745c8bd4f88444008983cee070dd103987616f7e534706febe02bfc047db74e05ee07f73a8cf0856e869ea183792193428c97153800c050430a4d3ccea6ef843b3d40847450f718db27f374a056e1817ad73d7db361d03880ee8ec9e37ec1e9b858ce21a4e3c5792f5d4f131766f86c70357a869ec147c0b4024bbfb001b069ba195aa69593677660b2b9b9b928d7d07927804400055e60ab99a00453504f261881100e60a2431215a9e88c1541000e0518800d021822248600fcf0c1b15393020c85d73498c116595c0184167e60640c181dca0da68820043408b5e033e3027e5c8924585044141108f18120a06c4000352adcd868200344866ce184231015208b21c060c8e69a800466453f3abc1ec507a79eee09aa21a2f8e4e08179f1795c978959810a70386541660800901e421c88400318d05275c3052628c10898dc60430fcfeb856e62ca0836fcf0e0e93a3762012c3881072c40810724a0031ce0800482ccf0a34305f0362133015f69ab1f02cea28180a7d85557073807184be70478053781abe024b011d9041849a377c695c1cd6c99100686c12ef785cbc56ec1ae2b6a16feb6873b8ec3bd6fac6fbe97dbb09563f20b98340498740c7c85bf1d094dc41030ed0df61474d0440791a361402148c01dc0a463e85e3c999702983a02e802649a6fe520d783c3c1bdace3e56d269838294c2f7068d2f5e40d14019482696fc1b4c1185898c0957c0153f762e1ae63dfec1f9a5c3e1817d841f7c0d41a5b63aee1bae15eae2d76d800e070f41db995777042306519726eb8c78960ca1f8fa159f872f2d5a066c1ebb101d009c063708fa3e00bf045f7ea5e3a01f21376e031f2d30e5b8a0986eaa41f82cd681fb0c55dfae61ad9052d0484e1b8dfd063fa0cdb8575f997b70673ce38f7cce5ce93d1b90ba0b7b48478aecefbcd5ddbd037fa7230724c934ec62601138b88262eae63b81776d052ec400ebddd420b203c41d5c4d4125237a3213e380905b5c0a3c756cb6208372a882dae1ba0004166d882ca152b322480f3e4024019902207556a00831598400437362958e0b64a404d914004ab289cb8328314305982001c0a30802033f8e8218306e62589163031410996e830342363abc96049c10323494c5b68a18424e060009817570e6a40832818ac4005464820020d184200016e083203902a54687003173cf901850eaad46006307841088478000f0a2082550e9c80c10b92388113a6530644c8155af9a00a299698410b5870c4103e40e0940d21000703dc004448870f0d575885ca121bd0800f4e4e19020200a487900e1f56a8ad0faaa842c5063468010b8e3062880944c0c901020084870e9f1e1b39345a5cda6ee1832a7860832568308316b0c0882126e0430420e0848aa103e0d0c28d8d139a10030c2dd60e649c7a78c0006ec0f100041eb094a4886e8bac057ec26e02660247023d023c0416c1d5c13919e7da7c353200f2bfc8c03237a6c7c830ee4bc3dc97ce454b4b16750b6489610c10c627e3b9bacef18e39df3b5f6b6d0c4ffc7ab0278dc85c6bb354e9e694d962b3da6dc6dadbbf22dc8a9f8ef8ad207e9ac6d7fc9fb2995a9bbd3afb75f63a965233358f626fcedeb3e251113d286b8bbc516d761e21791acf63c7d7f0d8f1b599d149e439891e0fd616f922dbabb35f97880e6063636300598cdfc0b176db40ee206bad0c2b7ab5f45c5753e9265d0160c5ce055d94da6c554fd9aba74df6346ace587ab3a99d03bad5ca6ae76abe2a764464b553ecc84efca3d5a876d2bec8f69fda8a527355ff34aa99a911ae4a4d6dd2a358d2997473c66634d57618e82660eda9d2cca2f396aed65a19ce5a4b723db8249686b5b6c65aab02797d9023e77f3e4d6fb11572e4c091a327074f8e1b396ce4d8c9a1932327074e8e1c3870e0e8c1c183e3060e1b387670e8e0c8c1818323470f8e9e9e1e9e9e1b3d367a767a747a727a707a72f0e0e0e9e1e1e1b9c16383678747872787078727c70d1c377a6ef0dcb871c3c68d9d1b3a37726ee0dcc86103878d1e1b3c366ed8b06163c7868e8d1c1b383672ece0d8e9d9e1d9b9b16363676747672767076727870e0e9d1e1d1e9d1b3a3674767474747274707472e4e0c8e9c9e1c9b9916323672747272727072727070e0e9c1e1c1e9c1b383670767074707270707092e09cd6da182b761d49b7dacd9a5474a6b6d8013c166abc1a1d5e874fc03f0b1e0b5e02feffdf5a9b82b51666ad9d01f2abf7b952a40422f70077e248acb5ef53a4c8fb782c1429f23e6bd149fbf78ad0588f8c66e9ff5a74d26a8abc8f6af2e0c16354cfa3d86a26596b5120450ee4c7b3a0820abf9a2f3b9162b39aa9c4df003e1b09e276c2be3a5b6f44ffeaec3c5ad79b0c486c696a0b7ab2d6c6c602e296c096591abf63c7e7d4fc59b3cd50e46bb6d9aa146400d65a32066b6b40d6a636ac5d6bacc5d932d626c141d44ed89cb51607513bc15a92c85a3bc48a1a036a6a4334a3ada98dc8b4d6ba58ebc25a0bc45aedd2b9c55ad15adbb2d6b22c7881b0d60ac08a398998d71cc5fba8b2dacd46f445b65f6fb6b7ad43d6dacf5aeb596b3b6bd5735db2619353e379ec786b2d8db536ca5a5b002be6ceaaa90d8ecd0d1b9d1ab6a2daacc6493363eacd8e0e8ed89983489623870e4f2c76e6d448b2c56c886e47ebcc68295dd599d199ded88c523376939e32daec46747326d98e6e6467cd68a6aae6afb6da1fada6903356336966908dc85abbd992ca1e35db4c4dcf23359b0b3220eba9aae54cc91c3a93666bed96faf8d46c4949b6d890ba9eaa6aa6b6341552d533162bd593463433aadd7a703f98885d673425f58bac6691172b5e28d6da162be21a6b69e6115bd2996e12dc15586baf15af0aacb53056bc40b0d6beb0e2c581b53c929cb2da2c3dd5da4c4d6d64b6a49bd87984c6abb39aaf9db4dff127d1cb6837db91fa34acb53ad65e1bacb530ac78576bad9adac46a4b6a3f6f34f3c8174953a1225f33bf66fb22346a94cc1449ede6e3b3d26eea594391979d349a19b3d6beb55866ad7d59f18a6a6ab32615716b2db6a92d4dcfaf992f3b6be96d3dd3b576269d34a5a4d9af33335ddfa8365b573329b515bdd9017b3760ad8d61455bc45a1b5ad14ac05aeb62459b83eca4cdcca257cf7589e8bfc8f635db6c357f29b5259d496a6a83436bad8c15edcb5aebb2a2b561ad4562a315a5e711db2fadb398a964c6d4d426761e194262a637b154bd89d96834f366293553a219cd5abbb3b6d662f007513e0bc5c454da0c5a51c577471b84c7052c40ff7d3c3a69babf801476e0728f12254bf0e75f528412274d1fb48974bf7f09f7de5b52c9023e68934a96f0f7c8a60fcadf914b38b2e983eeefefe4124e6e32bffd25fcd6389b020fe2ddf1d919e1187483421d96e0e74826fcf72f087ed73dfd1e76a21ffc3bead0fdc55d18048ee3025a08caef8d4e9a6e50fe05b410a4df8621f88d61f8def74d37c87302fe47830dea9c801ffe1d75f0fe76a40e4510f1febe0edf63500c637af4e7489d14c1127e4e8a60297f0ba57e0e0c471bf48d4ebe6ffa20ef39ef6f4832e5bf7f491d96e4f748a6fcde7ba493a60f0a3f870508471d96e4ff48a6fc98864c240bc1ef8d4d37c8fb6e6cba415d0b41de37dda0ef3f9c43a1181af4875f80fce073a393ee06264121a9431144bebfaf83f7a30edfdf208fff0d4ac101a211bb45c94924112ce117c192109cff3e2744bf0d85e47f8ec4679b709376d2a4c9228cb450e6bfbf80228cb4502219b15065fe7b6fc0246933937c61f82c1e8d641ae7648726257eac6f0ab4d6fa5ea4fbfab391ac04bd1f0026fcb59223f8f79a5f49c91f28efbc95048049c93da747a44d1a51527624122751fc178953a275ce5967adb5ced9fe0d6168a1f8ed1ee2b328fe8c72df3d1ebfcf5a203ce9aa9f9347f468e455226172cd257f5b7aa411253f6ec97d47a2fa2f6944097e926724fb16bbe04b41014d526012f17ffc0956021cce047b8188f228221044b4a6a6a6a6060fe1b398276b51e7dafc83273f608015537c70d5eaf9c90b560773b9c70fe4f2f2c25f8c2893f2c5cb0bae7141b9f2c57de122845faffddaaffd1ac10ec2a8105157d5ef11977bb4a5ab0af3601c5e24ea3a42995dd95555e5cadfc22485283e5dd05689f8ad105e28f422699e0479799b5fb8a4409cb9ca3d3768c3bcc83d0251e19afd82997102aec9344c3357b826631e94491702e98fe2008518f332d956c98c36d3787931ba90284c14fd7a2f14c2bf37de7863f23eaec18188cecc5c611efcf9699a300fe63d601eeef404f3c46c527319faa3f85fd76ad617f3328fce5cc14cc14c5daef7de1fc6eb69a6e9c6c87ccc1391f99898979179195289ccebc9f530516444d185cbc5bb62608e0842045fe6631e9db9ca5266b2eafb99ab1927d030d13899c9127e0e67b2fc88a1ccfd3bde2e2f7e618e808982798880c9532f52d607ffcb5585795c3ebfab0accf3fafcae2cae2b669aa6cabc5f26e6659e48cccbc87c0c91998ff9185249cccf74d6a3334d2f52b6466e3d3a73e5aa1a5d55b8b2b8aec035f9ca8c31f8c7181a647ee663304944e6676288d07cccd37cccf32834e30cf73263cc137894ee327e1a84e1f27abd5e238aff05ef9149895fc81f2a2edd2533baa03e2b5796d055850291e6631ee5641e156732cd68479931867379195126e5cbf750e8e5bf0f7684b89f7bbd5e23cc0bc6e5b7cb779707ca2f2e2f46744ba17b899611dd557989aad6888aa8b622445d55304794f9bf99ab2e6b6b712e10e10292fdd9a1b37353d353bda531cc56da4d06d9999e3524361c3a393733e8c95adb00db94a64248418488a8a5ec4c3a5720ebec0617ceb506a4bcc0d23aa3cdd65b5afe504d244fa399b12f81ae7c5811288a79f4e6d76649e73afb9366549ba95f8a404ad65a1b75bd1189403ad65a5b4d96dad2544867e7d602125a80626d68b413896a3e0bb5f3c8cce88d6ab32fb2fddb2cd168b7a23fd793a694648bc966b257cf2fb205b560c3de8ccc6ab7a4739dfddc785c591d1b6a658e0d6bed4c5a3295864ed53c69670d48ce19441e3bacb5b6eb3acf6689a8eb3aef7c1a376a0d558dd964356a3333e9c636b3b1516336d9cd8d8faab4ae473507586be791d3007f1e11e9bace5b6be711a1213ba8586b67b0e20e266badcdaa9e46463735e94fa5a5d53c8f5616cea3b57626dd8c52f3cfa3f5949db55349c93c7ad53cfa9a6da6aaa7fa48ce1976b89c345b0dea9069aa319d9d9b9a74ae331d2f9264657ae6709e698ff4bca92a909366ab415532910c5153223d84249d2b109a79c4962a2995b43356335d20b2ce8264673abb19396b5867aa4de97c6189a6a4741b92c43cd71bd12c5dd752869a00d4a4730572d288ccf42c526b664ae41ce2c2796476c6ce1a64b573483a7be18ccd52214535f5acadb31b542393d6c34cd72442345a195367329c670fa29a4d35cf1a70e8e4dc72e4581c2a90cd6e466631c44aa3da6cc996aeeaf942edccc196f6388fcc62504fa5a5d5c76788e3b0d6d660c51e20acb5677a5bcdb3e88ba8b5dbd011a422b2934664d6ce3f897e556f492fab99b3b4c6d289d4936363c306a786918d9680252bf6ac490ad0c3e9d8ece4d8e0d4a821b1a54447aa79c453c50f2bf2e4c0da8a6a4056d58c61b6ce6a407298d1ccdaac87d0124d49a93c55f34808111f9f18ee113770d83576a6a92df6abada6fe17cd4ea524e7abb33f8f88bc8e2243a96da888ec3c5267b559d107a9a98dbace9266b4d55a7bad558d6ab3741d52c390927a269935177c7c74766e3fe929441d4a4fa52466524a3357db3a94c44cfa496226c5666a6d96ae49cea15335897e74766e3bd253481233a9662392b4630335b53952cda323d53cda7959bba6b623f56db6d5e754d3f3c8997e0d889adad04c514786b5b1b151613d692cace6991afd6a9be16dd4a79926ed16a399b13f5a6748dea83693d56e3eb4f3c8491bf2919d47d2533d6b38cfb4474c9d25f9f8a8b513c9b6faf8a8b7d55c877c3c62ce6aad0e6b2d02ac98a3edb9aa676d283dd7d950922d2d1a8a9d4aea4dec3c22e204e1e6e648358f6a371f1f1f1f1c1b383a3834d6da22ef53e457f3cfd55c956e696a73c1565403b294da5653563365a099e9a914846626a5b6197c7cd6d4a6a4745387ce24332d52974c21ea4900db0c3fb253496996aeaa4f926a22d961ad4567d6da23231bd42553c879160da94ba692aa9a341754d5a4c56c96606b0051c3abcdcc23176647352047aaf9e367c78ed869c3101f9f7396ae434aaab99ab2178cd4a4d51c5a6ba72a33cf239d9ddb119bd2501233e9c7d00c432b58c00618944c19ce33251234c30f1b477c7cccd57c4176aeea4c4d4f213220ab4d6622d16c4a67926dc859b3cd4024b5c5866c453520349acd051aedd6c33683d12cc86623929e476b1073355f9099e70f99791e21a566527aae269221a5a4f44867e7564362930dc94e553d63b654e9e794d95cb06147cd361b4272d67ed21f3473add97298a5b6a31f010cc9521389d24d0604c76d47ecb4c1c727761ef1f191adea90926aaea6ac66a6d266e67abe203b87200519524a8a25593263a76c281d1a3a936eea992ad95299cece4d46b31da9677ab40ec96aa7927a534d1a4fce6d6855d35911cd66235ad559927348a59935a399bade82acb573a6348b9de90f8d1ea752526c00cfc20c48311b2dc85c4d99b99e494367520d487aca6aa79ac29135aacd6844b3213e3e49b124b599d12c0d023342094c26c2406beda80af9da7922594fa23f55f34c63b5d92fd1fe759065c60f568618600800901f0cb0d6de60451476a0a6164001c98a28b8581185182ba230f2229b2d1572ca6cb1f385b576aa44e5516c3593d4990cabcd2633cf1a96d2b526443365e659c36aa30d9199e75169f3314b57f5ace19499a991920064e6f92366440623492f9c39cc869cb7f346c30b35204a49e90f21a5a4f487b5b68715b2ead0b99aabeae333b4da8e5425b336e45ccd7588516d6664aaea29031620701185132d78c00a1f2c11e5891d0670e14cb81478ae1454f1e15b9003ae194105323cf4400aacc5430c18189d429d0a6ce088236a14e165811715278678618915220ee0c1037ea96460c5afc22ed5cc23d53c8b8a66b237ff3c621ebd6d7d55769eb53f8acd8e903c0b6f5b6b339969a3c9ce35764b57b5668ba9331bf38b6c47eaed97885e76fe495433632dbc90b107b0e217b1f6a41111cd6a49eb6abeb99ab257974c2434f365a73a3b5a5f5633ff48358ffe94d96a6691f346336df0f1919d3524414e99516d761433637fa6e69f37586b7facb53c7600c99029dd906c369a2803c7daf73965b6d89f3422f328669e69edf6b2f37d6a336b6bac4501c65ad38a34409c484334336623410e1b021a6b6dd8e4d8e4d4387d72d088ccf59674632dfeb00cd6da212bce0c60358d6647ccbc832834105d665565cf83c7d3a8a99d349b58aa42c140586b0d604519d0e67c8d22daa93e0f1e7f64731ac94e35b53965b61a01880071a11452baf03b3e896c7695acb545ac18b3b494aeb5f346c94cba39cf7425ba514f1acd16bb8911c97010e9983839670ce72cca29eac1b981238779c3ccb153a45324336ff0c870e4c011b381e326b6aab7f4e6fca1e1825252fa43761ac9662951f92cc88098a929a46647ecb441a59db59a79141b52d53335aacd68e8ecdc9e059d9d5bcd99c34c1d3a8aada6923a931da9e60f1f9fa1259376a632f33c1a8a19491a329224b3314c211bd9b9aeb3a4245bec02d920c54c225bfa27d1d7ccd8afe6d11a33d59a99ae5f3b956c6f9ba1092a5811c693d5664bff343253dbaa2a61b4220c1cd29328e95653fa259376d6609300a3c65abbaa50534f9bec65e679f46bed4c5fb0e2eb8a9adadc20c56c34a2a1733565339a7924e95cd525b5466d661ec5ccd454ba516f6cd6539dad339a594bcf35c9f93473ad9d49355b3a3b5213e24962adbd61ad117aacb545b0d612c08aaf27929db374edd1b1c1a94124ab71ced2d5c68eec24fa259979a634d35a6b63adcdc1c806acb5455684311a3a65b6da2c5d6fd4f3c86c9d25cd52a51b1b3a3778cc1bb29b1c1d13a767276643b6b3630307ced953e35ccd7455896eea8dbade6c48b1f32876b3ce8cccd57ce13432859cb7f3666448294936b49e349aa934a4de68b7a45bcc5ccd178c6645359b0b08b0d6f2b0e24b95556de999c31bd5664bb49aed65e6497bdbfaea3a23fab576ce62b3a2a29bfab2732d9a1da9f7066badcc8a2f3235b55963a652922db6532376ca6cb11a4beb2c3595acb53c567c9961adec34f23a707610a0f335f3687d553d694b35f3d5332935d59b6d76e44c85243965352041d6dacd161b42929e476cb2da999e6bed6673e1343285c84ef536b41e19cdce1b68e64d363b3293d5664349cc249799b5b6060bd602a06634a399670e3375bdd996cc18ac155311db93bad556f3b8b643b9b2edbaddb6ee7eb9accbe9e81e9b7fd0341d73dcd6f95e0b836d2cb69706ddc2b55dc7b62d7eedbd776f1bf8c3f6de9bf18f7bfbc5f7669e03bef876fc52c3bdfc768b6f689331e735de5f8db176bafa5eec5d24e0dbd97d35cee7bd28be177302c031f862aef11502be97e34bc385b937634e063b77d67db9f75e9c03bb70f96ee0e2cbf1bd380f5dacbdce0606ef0f77f78ccce3768c754ef630be345a3c37013ae7eb918560ab2d06c1d5b9466f7cb3dd15b8545c8bad288af6daab2fbe43b81c5ee15ebc31cef7eedc9bcb5d10638cb90b57058c35be38d9e683c1a570c39618f802776318570bc1ef5d6df18cdb2f722190ed28836b816880f3e557f71bdefc0463ce275f1cf962ac1f835dcdddf7761c9d2303e7628f73f8de7bfbbd3e17632832bf1dd6f9c825f1edde5ce3761fbe5cbf1808d3601a3c936db6b77539c637d462d61806c6f8f2ab8b6e37e37e1bdf7d593df7de7b633090fe75f8127160c79786cbdd9bf1d5f7c79dc139b00cfe70c7fbe68b2fbe3e77067fb8eb9b77beb7e7e65b73bb0bde7b755c1aece57e397df1bd3ef7f217fce26a7cb7be39f07733a6b93963acb1be3930be31b077bbcb697ceff5b933d8ebf96a4edf7b7bfebe56e0766400ec9b6f77f5eb769dcf6780189a8b82be1873dccf2bbae07c3feec517df7b6bd86e956c01985c2b020cc13e0182c8324580b18a2b6e30a9288100a6a8b244461dd8214134710e31abd81a82486203358cb6203404815a09316d00c510048d117e983238398278dde4305924e899229cd1337a97678afb049e225b7080e7b5b036459a4053a34814cc4255086c2c3cce0a0080c24216328a3c28e3615a2c141707b045b60ff6b502accdd6e220447409b1d65a6b577835b531575336c49d783026987ad73bcde2787a976740224d6620b91aee713834d8ba1ae4431a490eb34b532920034c9c857ba7590867709480c9b301946916584db08d982d007df98e1830f188e1059702b0f6740c3494c0f4831d56986a72b78f4b065013ac63e41a4a77fcd9007082897319429cb8b48278c83e9a308009467703f8027c91430f17254ef592c0b463ed7c149980d8bb1c6e24dd801e9a709ac5676e6ba709103c10a0e5a854adb386450c3a856840000400000000b3140020301810098583c1681a869298dc0314000b9496506a4498c8d224ca711042481262083000040004000664461c100040a3b3d48a9e2ab5e4dfa64ca70e9e30d1204113b9c2ed2773328d80879e6beaaf5cb1bcd7e874dd0092ad1b2f1f37e86a48fffd61232e3239d44176476064964745a38f22a628b7307b74f5f2822d07209dc9730580fbac658e1cc0bc3942fc9c2e6d0166f5e4ca1d9799026a11c9e160acfac1032647500240dc40744cfbfe01fa62e5b963786360a3ca20d4917a61dc8a80670b50939b7ac86e4be49cbecf75e55718e292d3ac664f4f27791262971e4b8789360993cab480954c0370bd767fff8fd695e1ff691220e93bdff14fe62c15f6c6dcc47a37cd2b98a18cc381033645ccba66f7a9a1b3217fa2f9fc3920b7dea68b0efde63acadd8c09582a02403cd75a7ab0b33b34007bd79faa192ab907db5bfd5ac18497ff928585ad28d330a40ae539a19389204492036af0b1051233d14dd5190630cb6830f048e3b89c61e4c689fa3fd2cbc5bb1dd82bb9db739381e89f2e8509e2be1d6a263cd04c006f74e05a1c325194b6f965fae9dc67a22495acb351fe9223e5cbbfc7ff641fcbf1e6ae63eac1497868abf9fbc81bfeaf35c88ff4dec978055b413617e1307c43808240714d5011ae891a65d6e630030be9a3aa6b94e39cf8ea1778b43b820dd73478e02cb37ce502f73b73169fff7d75c7cf5e80c7e58975809c36e8decc1d0f7a43bf0f3b09eb54e77ded8be72d7455dfd20034dd7879787cb859cf003342575063cd04abc89f1090c690a3490266eb488e762b0b6709080564b7439c01e28a723dc9da6a2daabec867f2bd7f029e646b51245b66aaec8f9e99e08315efaab4280bca64746e77890fe81490eb1dd7ea4cea598b19101022bdf5e34c4c4337224d00fae009a6913543476454690c5d2f8f33883bf5b4d82ad76c6365411a9929330eb569447a2a987208105c9889d3b8388ea8d1b121d580fb1102328006c9973e9789598991b3f7fd606416d025b93c4e60fb3200d561d4a47692d372aca739a467383ec62da6767becb87c04de46c06085451cc460c099bcebf6b669e0a8b491ccb833b3f8cf466bfea35e3458e72c35db6c2cc9856fd59fd6b74211a3a395971cf96d9b5eeb2f03fc3184768ae56cc97c2acd3f2aedce77dcd71bcad5b1caa25b68b0724c8ed9d3ca16c7d68e31b3b6c98b237667f67ef060f43386069921ad71d9482fb480a38fbb7d5dc38eb666cd6e93f17b292883f7be80950a583cc78e34708d72545aabb6076c2a9be59368545cda477dcb5962d13fda9e92f3afe142440131f327d6bc43dafa922c5d075832b5385a53110ec29c19c22eb053db8364b549f6dd1f020f1ce0fe39910a9f1e6d0e7400c4ee5fd8f663874e0356afb2f562c63141302416960c06e59fd33695b04a90fc982937517816afe43f76b0873c408d1317daa3d1a6c244aa9aa1940bfa7a8d24c6e4e0a14523a408648c53294020118bff131084b2be676d7df67abc40d8419f78b73a60ef24027933028a66ba93818a3293692304dc0af80684405f215747f6e5df2cb10122fb01f1eae8ccddcd21386838d7320a1b527d40c73928588de622f54fceb18c9cfc9166af0f770437e77ba747698ea94fcc638a11101cc21e1b9d36e900fd09c139c194ac4f5315e8f56b8737ca34b53e87a1fc1be9db9c5e33c9e6ec3459cf95221ac5bb98963d27eed434d866a3801abe68a6d9ef3ab21bd93bddae563e63a051e97b7dca3b66faba061c78bb86192e1ea832b685c64ddfcdd6b420343811ff34fc08587cfae929b6a0aa35b3718fde9013a3770a5354ab560f2e0bfcf44c03b44b741ab684dc9f2ccdb161333c55a8771345513241eb5a0d1a95a13c1fa8155c007899f68635322cf178a04dca10ef217d16a38e762935016207312014f780d1ef7a5306ad9a3cd7c6141e039ec311db58453537f6ffe2f701272036798520f0d63e39f5b33954cee7b6c1f657a7753dc68c7e31706c37c5d1c3a19690ab8674f352d6841c56d4a9f5ed883d16db7e8640398cdb9cccbd3e404c969befe33ce489653de138e69ba511e7cd4f2e6d00ac0404197c6be1e038de2cbf9ddba69fdd9b989b20056a790ade11188b3b631759e08f05346c763efd119ad25c6f63860db71620b9d4f238735379406bfc7267f00443df1788898565b8ed1d96790360561eab7af7ee532ec30930019c89f62a1950d858938ceba7676488ece3fe112b4ddae7a23edff27ecfc3fab16fd39fa35e92e35f028fa4f407179928629ef6c56ffeec759fdc3f3873b3c0a4f0a3468838bd69f8b81adc5610dadf820a4abfef2bfb902d5cf58e85be1569774f8a3747fd95fcbe7663dc5f26fc6d94343e495bdf9ce375796b06e89b355851ff0e0a77350878b7d0ccfacd885e32268cbe6ebdab40f0f635c6ab4ceff905b9291b56529a9c03868fab92bdae09d188817e1a8eb28f6b41fe56e6b04b93694c04659d04adcf3862ccd79d46ee87d5827ead55fc0411ffd4866d3eee9eaeec3b18a9cd87491f320e0ca6bb9f78e54d84bdbceaba12258937d2b0f37bdcfd1136996f267e2148e7f13bc1cec5d0ce349bf82b9f707c566e767ff507d8ba551ddb80f6617d2a032718990c511957a576dc7e2ff9e8d5a3c8c91fcfdb0d3f1bb5228073cbc52f3fcdc677716d97ac91131d766562879411d6e7a4e5da59a71dfdf8a0332b05230ca55368fdb7717013fe3b68be1e166519ef1f52fecbffc0baab0707c103f372fb29fc327c35327b54d88eb5be71cacb5a6151a38df834e9a0f8f7c0823eea56baaf30a3b2e352ed3e0c5fa551f5d9e81707577ff79f8f61bb008438a9b03134368080a97def89adf4fdce6ffb96cc714a712ae70e33f2c1317e8bd76e013801061df8dff1bb957f2fd0c8492e37c42eee758f794ae4f6a1fd9369273c6c74ae199107ded341eaeb39631be3d92463fadbff93fcf7fb7f270eccde2612ee5ddce371e586be84c3a005070ced607993bbfce743ba730fa79173e070f26fe346e76cb4128cfc95242e5d74f25cbe2dd769a202ed1e34fbdd86f7de8cc3cae7ab583fcbec374c3c7bdfe2a3cc714090ecea298f2135ed77201ab62f8cfe00102649d9ebd2a6f151913059f9e7ea5be27eb15d64b91e8bd051d016cdb2a26249fd43b4627edc0cced24cf7a6de0f2fa3e34f195999ce18d27a39547f7d3a63f908e6a137fd87fd222947210cd574066549d03fe8772c92422a5e9a96d6d6cbf40fd8a73640dc8d515c2ec7095f1ea96eaeba05f094908f88f0dce946135e4a19ebe3980a6428b662f9a34c9bd995bf7a3c47d5eb9fef922ca3ca3cedc09c9d9168c36077731c1ece1d1125186fcc11c8f53e80ece4958b4dd330d4e28b0c0c99bc383b42946a2174acb4653d5f0c2c488fa7a41911b469a770627bb03927ea15d6448107a2df4cae8842cbb61c6d76056fe8c472774e242f3f451d3d9699b6390661d7f7eb1e083097667180b534b836350db474dfc85558f6bef48093f3ecffe850d77deb527c7785552f15ee6f4266a2c2fe2cab9ed5cd72d26705d6a50378bdc43e72df381607bc3c2d85a05a5c10ea1a3f8d6146f0b95cf7c3c69ba7f1241e5e5184ec6ee3fb40a04b5fae76eff59d6733592df8520bce5368ce938edb5e79bb92f810036e6ea4e3da644a89b567f381468c12d10d22116ace8348d480f9cc1bc55c3aa05396827342b5b41b762573fe7d01f816821f2351e94f663a924204fb550ac4d170ae77f37cd7cae090d803370aed135f656ad1def89bba82575eba0e7a316fd4f9cc33853b834e84322bcf4c4bdb839dc4090637132015865f010901b475162e95203dcdfa9d148d4ac4d581066ca00466a89651795c9bb8964048da2705b2e2f62840a3240ed3332d761782fcbcaf170c12473769dd2e9614342e589738f45caf472091109067c4e841e248b9663268c3c90f92a1f3bd60a7acad879787c138c5ef20c9a8c34483d3162af8bf603e4537d2cf0c1cfe420d40d4a834357fe122797aea62bc07d21bd6af807de81610b28129015ce64dde4593468ec59fa279c1fd44c4e269018ffd64e748c90c9973e63ff080db8b0df237c2b65f02c1d9310b731b0806b508c30069ea4ef91304738689a66063123e0de1702ff2df2e8a0e8968ed1e082c1fcdc02e4d575f654cd9d158bf45efe089751b063607b7ad736fabfa03ec7e9bfb023bae46c1ea0160f8dca704b2280273d1fe06a98fb7f2cedde61be9e794f3a4cec8ad6418afe33e0ab5ac29d83f4e4afec52afa5cc99b00dc8a80b625ab7653a1e8c7625197e61fb24a7aab08a3ff791b9281950c41ee63703e76320d2a49971a9dae3b10f2b3cd1ab9480aa25ae2773b43eb7782847811df74ce6f3dc39908628a76213171e7f207804fca18cd41aff773bfffda600206921aed9ce5d623ba43618d98d6bd5c6f500fccbabdabb71a0b48e606dc716cebac96691cb32ff1fd3e976497baba7f280e6001830ec8ee4fab1274a22d69f3f4798a65b18d9ad3cb72e85850041c142d99e605ae63f844615d5346fee2d546feb557d6c92849527419c90bac5885defb8e6a2c337f3eb3dfc212a6cb5940d0c7dac854b390b3d5bea69722a979942d291962ce767a46c2b68360b79b13ba33a4308eb7bbb76374b94edf07224b9facb01732de6ffa7b23fd1f3fadb8ce373ec1d18d6868fe8003ba4b7871ccc923506957af62a71ca90671154dbf6fdf5241749c4d0cfb7477fc8d34eccde8e61db032e8fef7b602ecd5b3e66722dab0e7ddaba7205da4268a8b84c34598d91c2e18cbc7780f93ca4826290e781df39621895830e2a10d095460fad5d8846ac7f0f01e94c02bf72de80c1fba57e53d55cc7620647194cff1b17b681adb94f5d7fdbaf60070544aefb857cdcdb910452559b896b408842b4554e138dcdc68d9a33a1801541c09fa4c3b57b1e9e1a8d344d08ec07d4dd3e3ab1c8edbfc26a046549541d0485ede27a444372678a7a1ddcc3cd23384bd8edd8f5700b7ccaf43e169bb347a18730d1a61d33b38b79076700e1ff72412759a77a7ab99c2ca11c9c2247b8f950b14eb20ed99ab9421e372bfdfb5e79d98937906d36f29735471521b7d8541d576edda15167b38fd0841acbd2a7a1ebbb4fc11cbc83518b83813905869740b829443c7e4084c653e8233774bd282015897418c3650ae4a18622dfd0c630ebc534acfb0e5d9182fb0390b3bf643afff1adfeb672cd0b821ee383c1fd0bcea5c198ea48f886f75b6c6a1281d54678bc830de6a05a9e0fd88b22c067baf1df1044142b123bc81110a38dd04065d1f3104fe454e52e98f8f98b52a408e73aceb2e019c0656bf679d0a36bb31f758bf963e9d93fa102e2d9a08465585968979e05ff512d495b4aa2c1e86aafe7e72875ffed826eb6081069182f556c62833e811163b27a0c711e5c4607aa591b629231da308930aa06f503e3bfa72040c1ec2ad52a52ee76f985b72907262790b3e50423debaf89452150ecf3d602b46dc2129a66b471cd6b75da42e2d54f5dcc8f562709e8e1a846f62a13d69e5bc0832e57cdae92cf2724f0198203a80375c0b26b5e4794fb0824e41bc5eefb45d165f2f909d494857bb112356ee2ff0956c8c523e46db454ac4742fcdcf98476106ded63d465bbb71ed0f9cd7bc9751be32057c6667f1551cee66a1d499e9ff779bd31abe5e7a60be291b2ae576f5622c8c8d2424a713ad467078b8b8d6fc4c91aa05a7251f089ebfbdb79e3832c1ba72a491edb962d51419b3e575b899032d0d46e9871714ccb7515835502bb8b050968e13d8ffd2abf376b8a32e80e682ccdbd701061f3122c4fbb417f5dee0356876cbd7327244e384eb37d09eab8aa693452110ac3840394c383ed9401488fb2997be1cab110b19f57248986508a4802c4b6902d70fd8b23849f095d493ea7cf84f8413f959f099db575e7fd335d4252757e99300b374c8eaf541e41d89b0aa8a76f9c65c80eacbeaa54b9d903845f13b07309ac955644fb0afcb0163992ff39eceac21ce788e73a648d60b5bb03190c74b8440c11ca0274057a6396a82cb6429105c75d9948d5847d2c8f3d83ad3c949ff784f5d5fd32845b0a5386dd90e59d83423707071e040807c105e066b8399319890ca119c1d8682ea911db25f399c5a04ed63cee550e1857c22c2422278017e0c7b49107a2da2051adb082f562414ebd50c8aa2a3b47280caae34e97039635568bb655dc128702de6747100f017494afdaec7207ee4bc68a6a65d48663237b9872fa51a27949f850314b496ba52f8360e5734a4bd4b75caf6993f6714b4380e54fddcdc48e62a03c97315e1b7ac3e2b59430676c92715b20b7cdc3f605b2f6c9edcfa75bbb4fa1d898588fec78ee700f500daec1859940f69b787c127eabceb8e8d73c1a1c3304c07859c1eefbb108cfefe5fbe91d3ccf46c4c1208bc46eaf49913a824a6617bd01842777de64563c18e3a062afdcb44162208b352054480305a3d945e6c1d9237b1871b839f23327a7aecfce468ee918d3920d8da9228ca6d4745576115acfd6c6faccdf10fd34f6b5267b21ec86379c34e51292bc080f145e54805051e72cbd6435fb442a368c36603acb78226d440791d62c3b6c40423e7616941afdda924e4579295c43237dd7c7f2a5700dad94e51016d211a49b9b1c582bb0a7d5de7687a0c86d6f92bb0f0473dfa64d830ca817ccf3b523e6e5a7ab2112731589d379eab41c2eded89035458584d941d0caa8a1b12b4010f97fa372d5b3e6503db0ffb8a208ffd3849a1650984f7b97479fa365ca0185632f87f92ea841db48e0a13a12fbe38142167d347ccb248f213e338b27ecb6b71827c28aed8f892952d0c66c4addaf5a08d4a2a09561e5285cd5098f958269cdabe95e816551af4a59d8a925164ce39c73f6cb598cd6d301aac4c5780b8bcc00fdd0737fb20eee775570c1d17648fc15e2bf1bc83249e0d4a694f63c1958bfee8ab745f1269c242a70a37e15bb3542d18a37cac755dfac47325439ea2caf968d03a25b875bdf00378dfeaf483a8a0fbcc1df8dbcd581d8dd19f66ab9cca2014f9c17cd82c230b5730cef5ddf88ed25b9960fa0575d8c7b702f4b3f2d449aa632959d82414aea43be571bb2d5088cfc4c7d1bfe47ced24869ae7dc3149522b84319bb9441e5c9f15f622a270072b273835cd2113695dbdbb93ba94274e574d197976a850dcb003046863bd5fb4d0f81d8aab7391f73bd54e34ec1f8e7b7b1da374d39cf88fd88be35633fc7123e7f6bfef0f3bfdeca831ad6442386bc6f461796cbeca46cce10371b221577ed54f7d0190211f7045f99547d0856aa4ee0b10edd92ab44174cab434a0180a7a62006c09c89e831ad6a44fc94a3e62df148945b6b4db6a8c2c809506e9e7e54ad1e33c6a8865921667f18d190752628936760d5a80c9a0713ae153fde0dd27fd1608ca313e986221af70cd5bb26bff2e1746a058f8488529dd21c9053445027d04d2663d50d8ed795ca397963d5d203145d90aaec46e4a80a28b006874ec0a087c7a19c4981ea3473a25b26d428f01c49fb8c8e76064f3b03dd5627250467aeefed5e0800df25a0189a441c059a28dc437374e1a2f2f90ed1ba4038fe4ec6cc4008e3f2537b7915018b5c795a414a22bd3c2e252b7dea3f1c51788e692652ac890ed77951b7b80bb389dddacce5b963cbd0ee24e33b7cd4e26f80f8207e75da9bd2d6a668a14e794425011d6686b721cc749f25cfdecb7b7e4869c22e0ff48a4ae4ba848e27d2ff8df69208a34f9020425f91a4a92ee897ec90183ae9679773a4c4058de3f80732dc3eb05560011238d06295abace027d788440ac1f734b248f88ae1d1dc7fa081c0ec693fdbd7da06ef9afa66bd036bc37583ebf6c714f35a70287fed9a41467dd07da27557fbdf2a89b7d09b0aa4a4f04d68dca1214e5447f2a692b43b45fa19587405ed0362c21ad8268e54fcc9d95f24215f70164bf2770cda182590915766148ff86dea92be4822b4e5ffb8cb9942df73956ecca1dfdd89d69bb3a4a6fd0f4a94889b158c14f61c5373a05f3f5a78a8046baaff39d2ba59299069672767441b3d3b579f7966b9cab2b08b75fe6cb75b8c596c9968675a5040213d9d1bb1e39853aef8c5eede8f1961767a000753dc4ae4bb65dc5eb90e225d182d504c8d00e2a2c56f4474d4e6c1c178d770436049dd073a2067bac785e3dabc37459323532fd9270f9cbd52c9ef4b4288ec228118d07971e61d6805ec686a7303c513accc174737e09e08ca49e067fe978af1f6e2c83099bad83d1af14f3cf29b9b8c23bc776df2afa4e87a1c6931002d710482c967eab6189b4bbbdc7cfa881d15b95858326a1080acfc4463109dddf9875b34ab566a8edd31243831f9289ca422e640932a80871d78d8e6b1afdc215d9a8060eb7d94a8b8a2e29c53ce077ffc2105fc9079a57211df0327004c08c7657debe5b3f4307f2b876c5a1331e762b9637e22676b040fd8d217c05eabb90e9b2f98e940273d943a9f9149e27720f2a1948728def6330282660cb52fdd9dee3e6b5066917a475705cbb071b0aafd6440524cf1d821036095f111a87941ff186360f80cc2dc77eb29c6b9228e9d4094c01e25736b35614c706788cca2c7e7a0c65d59ebb629574203471c0808a0e123a9b483056c1eea61c54df81fdc3dabc6e21fdc66aeb14616ca3469ba8aa214260773288234333f87c347abe2346167bd5d82cec36f8e1375a80a9085c3d224a3c773510d96df26b9ee782cf3235340227726adfff9b9d78a764cf0890d9c342de0edd3d5bafddb311a083102cdd8ba2bdd1308938eb754f318b12c765e52c7538fb95394a0ea78af64ea89216ccba38a385b7a8d50444fa7f3f144b43c275ebbac1af05f8f4919803f779a94320219d3ab8f4e9cbfcbf7b168861ee7701927f6c34a32cca4a998d76d1f8e3e9699c9cec70a7bb4cd774ca4dcf7b3b9945b0fe96d7e73432eacb6c467917a480e204357b3e2a8cd604fd88bebee0176fd76ff088933100652c308eedc5a8e7edf88b1cc3e14a851189a0769fc8a3857040669fbd1a4aedf5b9e1097b8f45718d73cc2967e791c6f5d6771fd49ece392ff365a76cd3d381bfaaaa61c40e8fe87e210225d2f80945193532e6572d77a1d563d6dedfa6d82498e54ac1b43f3c9c59fe12188338625312e71d61ff05f8518ad68af24822926a6544e4b0765c5d16f62f89b85b05233530c204f8ccb0a15c34a21bd94c9434742315139473425f5fe9dfbece0be557385c58f440d8fe3f289d085d53354430188924fccf2ce29b5282929bf188e8114f98c953e3186ff03b053b43f94164ab81441c9f042cd2e933aa985721ab244f15bdc3efa841236df4f74bfdfcfdf2dc460870710d88b90b11c108756e7df9edfe90d84635f7191a59caaa22b42697a64a6bd50f1aad611ce1ed36033a0e7d000e79094911b4d710565ea06805e63ff5e6d083f7f1cc0d0fa71caf42fdd07433d1d2ce2b4d59d9e55a23e37361eb14d697b85496dc254b87eba5a5dbbaf8604da94a02e1a4d92f8b4b21289ab4ebe2d52f818adb184515370aec4d94e39c781078d0991a4ad087389068c553131c0b6c39980747507defcc0e7caa41b2fb86101389df153200c3841bc4bc25fedc835807c166d80616dddb304e5961b7da5e1aa9f2a52cb8cfaa9d288223531c0959b8c26affef6b7b02125262d9739e92fa2fbcbab8293ceb3b2beedea82c45c8557b317746cca1a3fb0ab49e577ee23c2a5b72f50d5f063b76e05a565b470ee1fb59dfbacef72ee4b02d42308f87c240fc174643b2e2dbcec128a2dcaf2ed60b7821a90097d444095639b4c65e1559fc84ce22691e03f3dc5d22a3258bd8ee508a883c4255a4e5c5f8aa2a78b17fbb4a5d841276140bf8ac48505eb3f30bc9519948e4599937dff6355066fa24dda7121f55f024ac5ddc3360e7ebc159104eb46b901b9a6e7b815a71451386ccbb44a5be0fff0f45422b4c6110df9b9e2cccacde6d1a70161b616bbaf5cb6b8ca326b8d32d979569f8bec93dadbe9c0e7d6d94f62901d0518c8e2da20792216cd986b57e520e339763a67cc46b634ed3a6d26b621c5ac7c60abf44cda04def7ecf026cc2e50d2711ec28476d7165a13815807040aa6d8e2ef089bfad16531fffceb9dcb9a75c1cb1c2610559415853299ae2f0b1f4d6e8da44dc0d4b5b4c30a0f0a401c7ead80e66c4107d4702cf4ac9c1f75ffea5209e41405d54a4445d10e72abe115518ef670a789cef7b5bab802066087b08f48303d337ebb05ed7c6fb2894cf15fc05e4145be5c4b48972d75a7d2b732526c6068700fbc65ef335c3e9f0da55e0136482251841698bfeff4693fd66ed547b898dd86ce8f334b92edfec317f8cefd87e210697794ab1ddab2e14cf38bf504636019061e50b6fd47945a85cbff93debb7b3782d1be3096c990345137b040144f402bd67ac942a779282481b15dbb9885c9abe1bcb1b9b7a3670747aab811579315082a025c4b0493e02c7ae638f9ef0051e777e3426fa2ac46b80961701d1192c8877de2ac1ebb9a68806eb8f593ea36a70f05b11c89af67981cd94083a16c5fd34ec448c5725f66e5bcd7876f278ce1167b17fa46b504a7a73e6541f50db52d52acf75b0e3a0ef71dc70a146807c515240c8cde951522a55e3c0132cdba59f2992ba27c6f7e1095ba3e6e3ae489704a0d7a8abdfa690561d27edb2172d0a23e5d6822c767510ed9fcf675ad9e1d191c8025151f213d20e18ee06f5146bfe4e11f0bc68bfb75e6fadca82b2fd215e54be3bc0226a20d0329e6ec527f955d9e1f1382d32530d881bef62b03aff71f5f1405dfd12e0a2c4a41126cca03efdfb5aca5fd49811ea553944f0e57fb75259e445de0499d6c697179aa2429d92a150a6b8dec4987f3824c84b48dd94d22f47f7f9801410c223acb6dc8a327997063277dce4f1f428e5bd87947ad280bf291fd9be5beebb74445c1fbe6c192aa3f65161d9bc22d9589cfcd9cc85ea145dc549dbee4f5c5695bc90cd96c8120c290d7dcd926aa10496f8bade15e2fb6e121202fe47df6df92391362b9596135ca1716324d0a13f3f44cff7b788b99507d0872da68914b932c0d7a2199aadc3fbcc7dc8551e974eb63420d667de350bab52b8a91364cd9294a172cb179a3946f460514f3073c46961016d8c259ba873fa1940f43a80851e971f3f125397f5a41542395c8f3434e7bcd62999266e9082cd5ae3e972f582ff8758ea9e004aa1cb1a7c50b582b1fca593bcdcc43e7a493898d27e309ee35d1d78d0d09207de9b07756eea3e23ab160866563fe676a4f49d9fa6fd69e381bfe0e338bd973fe973eca9ec73197af30d2b90773acc3c6810e6197240ee79581caaf689413cd8e8618f967d945e5fbb8192d7e53a80943924e0c0846087389dd8560615f6a78463cd3e4815dec393207c2bef2b938345e4bee3ae10a88b73d8673a9ccbaa9c810b9eb6c46687dbc54b38c4961dcf0424cc00a4deef19816f774b9abff53752c7aa13edc60a302faae473e0d895e2fb72d12880395f155f2b1257051587491987f93802c093d8a1a7e49d24f0e3aa5c3f7646a6f599dd02336c0d46303b8c708df5ec17b529e8b45a8c509c49239de7fcc4a35ddf2ea65e93c1fdbd3767bb7d164d3e103cd18061409945f2c8f32942d4cb66d2d8e6c0f4075b9a7d1247b5accd2817043bf791e171bfbd2678245e677766d884321c628f962de651bdaa5b8660bc9fc4a4526b3f5c5408411fd4dd32d526c2ad4a493e00baf34b2b5feff5f3b4527e74372d4faaf84cf258e4e8c04f527bca10e5c8f6e63778f440f51c0316de5c5735c378d8e75a62a10dc4fc63908b8dc6d92be0061565a31334e6e9538c0cac1ec46068be54c1a330eebae094122d1bbd476cffc74631b26b16ba54c772c7f7a1c0424af512585bc99f59b19c9f0d63d52f642588a73f4ed8cf469d4d1a8d215a0cce8d0aa3b80ca12639ae5940349a9bd9f475f3663b2cbe1a86cfee84b0775518bd56b2b3254b2913c26285ba45d6636b023f37e737d4e59a74ea7be2e836950e543b742d82f407e523f8a6fbbac643ed08d0e9648e08c15145599144f655b6cf6b8a464c9c6f3148835ba3ef763965f67d63184105ff0dd7dc177f1128a0d5f0dab5eb4cf1becbe88949faf9d3d1c8e644873a08a8990779ed4427134e15654fe1bcbb826846cfdb6787973297fc58ea275b31c059ce189d41067a7a90b2d8b536c3f6644ad879c6cc9b4507698649729bae9a5010bfe9a759b999edad56fc37f711cc9cd002e881086719ef840afcf9e9d03468c9c3f3f9db5d05d42b3a9341778d60808f7ca93cb08f34edf876a38174f3cdce406069c6a93cfc7f6838de2c11049f5da1636f3e95b7fb33176b2ff3faadc6d6a4165e0f29bccbf51f25f998a0a5299a3a0abb284e7778550022049009ca28eb978b0e628085a720d1be407f091b87012afd382b00912a01b3103f7b7a6e0b68d51ccb897ca209feee2d7913504c1004cff1f35de9d95b4b7e768d0ecadc77f608c7c635d6d1cc4e2c7a065c5f035359561f966ba7db81d2152d80137b6ba41add252b8cffa996808ed2fb5c2cd28504839cea6f9e23d4a4a25030accc8cca8225a97d0f9c9e783098eef9a8c100887f3be63f884e12052ee7a3cbac6806403c835644a3cd1ad745ea31738f0cdc0ff57e34c12a64f2c1ceefb5823971c0df591ace2eb1ad4802f43757e3c7566eced889a351f66bcf59a1ac4d1ad092f8d5e828bc07f2beae366a844cd6bf7688b6123c5c78da202c226e84610158339202bca222a74ca2b0e064dfe6e603bc2870cd36d25ca60f0b60f37145b5b681678299fec8424ebb0da47d1f6b35048b615461312e0188e8a00172caf731ba8941439512c9ca55f610b1c9afa987394003e2ba53dc8163b19465239d20b5eb77b0bc73ba7fb18c70e9d0566cb26a86d21ed3871132d034b2e783f89566094b7a2751772e2b2491564952272357ce9a9414a7db8910a857c3cae89257b2706c7bf4d5c0943be00b13af8ad0caa195698b8b95a4f0cb808e726c0aeaa7125dfe5bb1a30ecc8ab81761859f923d3f1d0cc6c6de50520901922b53542b9e09ac654104409474dc60d767273e1c4c8015001a66da29e51e58b95bfdc547ee24709c16c0b98ebfda7ad4e54655d486dd28e1e11f42f8bad34742fcd732e90553b33e43b9d693e6adf8029018190e38e9450191c1ac24a7d102dd6374653df81c8225514994aa70f72bbfd73cdb59809ecfb9d87a0c0a67b20a0fb243c2c91b763830ce36f4a848d1611cc7edc3763f21b1df031f6e830437b7f6a65ee203b340e35f13c47e8c018684b7aaa5c7ad98082ea93649dfd54c658421b6877cfc077bb2984448fd80a7c42d4e1a5c8b58ce76df8441c45cadd32bd73129c1ec2e254f44f902e4cf7ed6e5a8fc54664a6072e65d3935187964e7fb95d935c85632393ad8acd27991f155267452a96b0de2d0ed0d3ac612175df5b388f888b28447d93f780f248e52580c7f7cc58c131b396135583e244ea6af0fac50bcc402a23d1acdf24ef513850d422c2ca25116764a5c7686381965d9f06e295b18486a0e125ae465ce6bc3f901a1a4dc53ec1702b10e1ed794b1d8d750bdd89adcc00bb138212846195e97ef9e69d0176050d488e58ba932836deb35848a54b265c10a75527a0d6c386893dede16f4ca1fdc81ebf09a413110ca75120e605a3d55be8cd5c187af4b22f7d88e613391284c5b5037e05f3273846818b4bdd9fb7a50c6341616f6161c5fd54d5e2496ccc1bd9e49d7438bdd432204f0f6ee203dbe787cab29e9593ac526a25b244c3d6e2d267b00d3142b4935445dc2c2c80af41c51bbdfb55466a712286824bb1b532d6a344f7d15212f60cbc99d6d327edac979dfc488743b8a137c96e9ff48b764dc60811808da9cf34114b8736bb1a43515a5760c2c0bb9730a49b46aa04b2c929238b34fbecedb5cbde41d8b0618d45166c98a8a5aeae3aead6425db012a412e4a69ac020781a880045f517159afbd2fa22c81a8895fff4caa2ab2e3c68daac5be4a28acfb65534a079e7936ceda65dfc2bd47902146ba1cb89fb50f71dabdaad572f6f916fa9245bf1988b9cde3172993a83716fe0414321851145f44b607be33202529500c2122f669267d7d7f2ea01f126a29e6471b4dad8c94704367abda7736f760d43d580ecaa5e81dbcaa38b7d887d4704f117442f30f65ecc7cded6a08b063f56bc81dd55f367db491a5ee51eaa77f35401a65e5b125548bd27d17aa5b003bdd649a7eae36c4c398aa595b52882d25488e96c737559f19d09b1432c7d654a3f58b69281b80dca386237d74dc5270908b9175da4303118775cc7f8094ed026cb634d7d4069dcdc763f05e2ba62fdceafea3a709af4dbeaf54f177613b1a1186546b7cf928c1489b1ac093ceeb4a077b24a3716f1a0a9586d067fbffeee034421be576b30f710b4480c474a80ee9f2cb0228bd150fad9c60ceec5d3a53fd350e4ac223e87f71565f2ec3242ea3607b2afbf50948227c332cba0773063c0ec2ef15cbd9a744e92210f009bc3dc38afb4564961c203609f0c2a863a31b75d463d19f6f23195261ef8811fccc760a466136f9dcbe864b54cab2d947b5031beea7601166194f1f7c09830366dc01fb310b33885dfd6ccefcc5990754b5dcac76a7eb9543801429334e9344345a2a7674050c0a3eff8d134ac4698aed75e7935100f24c415951e9f58945a37116eaf88fda251759f650077ff6ba05560a313795001875733f6b34828b9a17d9383ba7cc314678403e95843c3dc162ca6b9fced119855a82936b109155796a597512baa06da0120332d7eb41e06049e30ecdc67ca6df27b019aca456c4bbc63a6541fd49ce3a149a43b45c41b664cf8e7dc730acaf13c34472ccdf500d129c45efe577fbf176d150d97c080eb9f5f71cf752bb920c065cf0ba6fe9d85e4c54e48de5f143161bd78d01d566a2b448258b56a821fea9996d9172540f5711282c8e5430563f849b5d43e9bd26e640e91b33a4982c38b1e5450901c33eb1452ed91827197e2b8a22f261e120024cce532d2b05d0fba67c9c6f6f038669c7c27088659d2b2d3021e233294c0a6b74900f9c890b4edb0e416261ec5c36b9a14edbafe1722a14ef00023700ed6ebdb6c9b704169001c969c6a233e132e61a257e6a75d2f213b98bf111a7e5b0d1945a8e88e4fb59510453b88b47ce2a37748181ce878a18ec1c504623f9c79a32beda9e35ae9068cd15a8e162c4c4b4103b29c1f724c06c7ca6cf5367f21f5a04bbd469d6f3fe73060a1a95b18fbc6a6d904d3baad07470564dac36e43891598af6e901fb3f1730fb285605eac88a991191f65b2261bda5603cab0db4f9cf82cd5e9813cf914e5c235d3de62bb0dd3c97066e84119aa48ca061f31f556ae1cb100be11c899f45ad0dd4abc278050dd162dc4dee02de4eb58bc4d5f7be8244282c52f999ad4edf4ea6306c59d0e6d32be11938ce62d2e7391f1fc2a041b9df4524ab26a241c0d6af67990c39207048b8649150cd7afa76165251343de78c32c8514d32491459e9c72a4ecb2c79e7dbb0cc8860d3bacb165c1448d7a6ad4aaa74eea84330eadb48ef77418126a42245988582bbfce5780e88b8d6ddd06790c12227c7f44ac6fac13bcab0abe33780f44837c2ec5e0c6c9a99e47a377f0b173c5f2610df8643ae7c3fa6739cb6f7fd91746d72248c08755ad2087caf5616d8bb8d693ace1410d1b24a0e180f044eaae103157d74454fc3c3124093ad994c020f402b48ba1889f1b84082d358aa61cefc3facde25f7adae7304a2a3bac12c37eb6046575aff31f81872a3511968ae5392444c476fa49c0a3f2c72f87f59b06fcf78ad7c67c3a6dfa99ece055a5444483a6cedae10fdbbd7dd5bc7a2694bcaf19db76161c2f3a08013e06731518fe523bde41154b51192e5a17894f07e30e4c7499b4331890407cd10546b0839cc986204e5db6ea82ea8e0d458c3b86111eea83fa59ef731341b432670abe7d537dbf2dd1bc61d5adeb85c35889c307d9951449fc9948af041500447ece3e468ba57401c2b4cf4fd907cc59521fc0e930c5f613fb80b796d401701a6922ddc8a540c6f62c423726581698593266d2b3ea592f00fcafafb6bb8767042d459822bdc77efbeef142250480abc3d245f0e0d78f3d485e6c681052d10e3adfe34f8947832616c1c59fd52e26ede70649276fca1f2c39f37dfbf1b3c584d1f79d8f6bef1ae46198b2dc67b19f54ed2dc377e6edfde21280772fb1ec6b47126f5e133bfaa2f2f622763ef884eff343eb4de0ad3f0fc3b2733ab0bb131e6e2ea2c06a0b1ca639bc5eb19edf154de51c7072a5ab25df1da9d279f06cc3bbbf5cbdc3bb01e0b5311f5cd3856b4bf0ce6c32f0bc8b8a67c012bc3f6f4a36f502af3ef4c66fee854114c245378058a53f3176d4d1d6bf3a053314ada9196cde184d9a2b46475b9c5350315565c5a1cd2400efdb2a6d6471e6c80e1d699ebde8eb892ee61b818aa936fa8245107571719a2f768862486cfb65beeda8b696d435831d9040eb69206c74d78e0df5726e20c63e9b3b70374f23da05855b6ad89d1d5f7a76e9600d6b53d4cd2f34c41747e713657d8c88173902e61ce15df42b136103aa2001e9fbfae271eeb062f1822939271dbc3e1921b4720e371634467594bc9873d9b7a730a5922a35ba39176a383f83daeaac53f178ce59e8701119aa1c3050a3c141a776b38a2ea8cad4d039f5f0dcb314878735f4704e6d784e31d1e748e7d6fce9a0249dfb9266bf257b716c3069a7a999a1ab21ca818be382417955daf1cb2889ced9e781fe464ea93156ff8eec7b05a873efa7f9c295cd37f4cf2b39d3eb90857e0b68b56d8971c15906ba9bb8b1c4c1ca4b08ad43ebb26eb3f8a244261be9e70a67bcc0e7f1b31041783e9170fa8fd9a698ba98e2ddedd5816bb08cb151b0377153e896a55831341f149858d081a93a642177af6bbaa83578876d3b30a4cb5fc84748398a72be396804d1e1964e5d65e5b072b7732ad1d34b79ca921486aab95755f0f33a2d1ac4a8205471b8e5bcc44df9f656e53ff150bae31dd9266c2f508e1b3763f13aa5d41c3672993bed30d943dc87aea4f4719ab5f70f72ab221ef1a9e02bc798cf38bdc238cb14368d310ee3f5bb565be59bf4fd277719741fb20ac2c2b716418a6b9a8fa82c53e2aa1bc0cb98ff7886dcd35361c28bdf986c18b6a354b42c4f18714a4cd754e557905c7e559898f77f049f55b3b87a85c404102e06051b34aa97e30c12886f8f7076e933b6376f9b069f031f065626673946a94653f5d0ea24f5c6d72d66ab3f72f097c0bbc1c13c310044e9ab34e4730dcf3826021bf09ad67a819de15edc9bc781d721a05675221cf05c42984e92f85fa4be196606996df416b575d4e0ed44c2761801cc5d4b2b80d2413ac10e30c810f20250ec36fdcb9c883997de0f249adf45a86ccc5d7106ed19b7f01509907954525195b44a3c42f57ea3238bf587751eeed332e07d7dc81b84bb61efb5a4fe33544b0aee6158529daf0dcc5008ccded6717f595256d77777104b991d5bc4dd20187fbec18d7f320daa2e65ce6e85f08ac3aeb3b1e01f1fb2fe962265e46f21407684df212042954c2cc878f47f159b6002ac483971169063ade3182be6dd1513b49d3d11366cc72c52331a88ccf84ed50861edb800bfe84ceb6abe9e113dbc1f5f2a1610832e1c0dc11c4220cfc1d93053cc98aac4d92e50afa91232d84ffea7dbceba13eb3b6c9f65136c1196ee7c78efc69e85275cc47d00624c8c410c4018b1394d995f3dc1f1c81d888d82761922e68710dbea24686f044d07b77746f52102131c677aa30a45a0750d8a54204e092a4fe1ea8872b0f869129d9a0efd60931a74e935ce7b831ffa8d92e73d1c0881eeb572464e3cf04e66e187af7cc08dbe8e1427fc773e12371fce3d5a6fd97725486bb6826101705be552aa4da2a168e403c8405dcd3e7be6b044e5d1b08eb06a467614d3f62bce2c072789b7c05e1103b275630966f914e90543b2ad205891089f19c7b8d8ff4c70e6a337ffa800728057e60051681d6bdaebf56bd46cbc986a4ade9efbe7f624e1a6d7b1191ef92ff2ea42c5d1d74dae5f2d50099b458fbcacaf800cdbfff48aa7fe70ec9f15df9e38918b534fd91818b76f731bd1d68a4c5d11817a3f21c890a143ead00d61ebddab642a067582c03851e39828f78913769d351690a07ba3c982373f5685be48c52ad647e4e5e263a6db47dfdf649cbd4351a51c9d28a4f7017a27d715e5508137090899f6d6f88b0cb95bdd86665a76dd35108f0f10f4e4241b37a71e28ae15ab47987f2eade28c953c52329149b15519661cbec3eb047aa80d5fa3bc69c0054b475406b766273208a56c0db077069ea46087d9aad7ebc704dcba0b00407c3440b20bbc5483097a90e34c8211d11170d80db3bcb33cc26c2c0e0a2a7e74563e9ed5ab9ea6bb78b0d70228e788ba80504fbefde1bc9fe23350eb02fd38f158fbc4183aea82c84c15089fb83de25d50e1c7af9954fc51e5651cc5878ed6c1d6185f7c82ca3777f094678814d7a7997c53156ecf1bf89dcad25d6053d24ce89688aa3627820a6a9d1175415de2f16dfb53d3251d68d779a671c505a5da8c320cde95db88f8f2b4936fc1dab2a69972e6ff0cf9569ef61023be9dc928708848603a8de433ad689088d86f30b2cbc3c9912870dc4792df9e7fd65fef39a4beae9c84cb75c49c17e6bc754389dbd01988c73d0e91249a65ef253cd053d56e533260e25c6a52c20c931f9bf68e7ea19bbc01b6ddff50827d3018650bb0ee230fa2be649a834dcc716c550ce221a5ded3e7403896a6b8f65d6c6c878b1b487e96420ceaf7f6f6a054d0472a2b4554a1db41a1fbb276c1480dcaed55800605397b630284d1155b301fb45f37835329c1b92f690be73279df65d22a02c9da63e4730963a3dbf47c3752b93f9ddc3b553d8043831cd483eb3b40c9984dd7b963230fc180c4849dd00fcde71c2bb13a29a3f1f46462800d7fdbd0028b4b589e75c327c3d227586d7809344f5310c90566dad71b80f77475b709b11cfa477b5dad6884da7ec620265945b32bc429b2218348e5d0031c03d0c5a6b83362bff506a831211eca774a705481e42503e660759c924c628c4e31d56b4365494b83bee5d8500e86657873089ea24146c1ae0d0adaa0ee599fda9b3c96e7a99d2f963db15a5bd7101a49df821e5bf244217cb03b943d480037b15f9b366294e9d96df6f0fa4c78da3577bcf8ad57b7f42db7cd1cbec38b64af8be9553309153015c2e3f82356d07b8df5f6516a1519fffa17431da6a8e9f45057804d0dcc4d765b35678db66a1fd93562e7cea8a32beefb9cbac1fd4966bd5a9d4c1237ad3041d6bd1655bf6e82a96cdd528a3b6984c316ab0fc7a2b64555df5606c69172df4ad04a3f14ddcdaf9b22b35649b976bbcea9210d21b97f078a0831644b9b9fa54cfaa772f54b99c3188b536d7a900180b07c1acbc0c2828a44994db15092df14d1adc7a2dec2b84b04c7505c109f5a3e303920621c24d5bf88d11c33bfdd8748bb1563055a60159fa4ea614ccf1ebfa23e474737df860488c61595e9f872fbea99db99ef2ef703822153811f2dccc9470c27f40a4a728b854dfa3370e8d5e08c00838efcabac832d14d473c74211344f7663a5e8764aae34c4158987509771b34a1b0a66ca9e112f8a589628462c4bf1a66cc64718f604a40acb6e5cc5c81ee35d58fa8567fd55fe5a9beec9fdddc3f75b092c04470a71f7054d3edee23a6b408009659d2f1e7d81a9c126fceacf21db345f8c90efc7ecc608b5f90bcbe033bb222d007430b6f85cb8a6d70feb1c451c7a5ddc1639b9a9d22eea5cc0df67ce251fc1f76c4843324c0f9f10777f64bcc877b38975e3da538674f864e10cfee9f880cf40ec415f791a6926e82e9addcc59b64c3200e2c3f032fcbbea82f8981d4efbe254318d93d82eb0ce6b623edd77f32918876a4bfb6d8d01273a51ead7e25445dcb3b9156edc51972cd16b19bdd74e49ef7ba57b1f93b2ea4afa285ed05522ba2a30134f4f4e0702af55479ffcbcf8281db3f238582b4c3a0901b47b7028453eb18f5a01b3a4f0395555e7e384ef61de7b7a42c11a25fe2b3efe85a4c3f47db4dd214c7a0a657f315544e241be93ac1640049e529e2e88a0a03db1ed53c1c7fd0dcdec8a7575cbfa1c26b8ccb01a0a9e6ff8d33df3543355b62c508049008d202dab24dccbe9d671ff4e480fb1f5944cd024789957dde91af06424d265b5d5da4e53e62927549267fbddf1ac63e144ca8f9541738612e9b5546dc726c1b76136efa378419760e41ad28f00ecb8dc4520c28e8d9d026718c0efc29cc1ad386342fd7964ed225adad90d0857c925e1f1e6656dd139e26cb3f3136bdfb68f9ceeeed16a2e28bf6860fe1d066765289babde46087e3c9388836c08f66c8dab5ed289de7d059b60927b371a14f65879ea67c6eac7c0d169b703c177c573098aeee468fcbb04b6052022cc2bd46fd264d96fe898e5d4a801abdc121bb59eaaf040b8ac73c814ab1de9dd8bb27f29dc0870046f79b0cdd668cfc011b5b05b0c1731044de0fe2761b3a5a9f5008d3938acf6b62c22c5ea12879baa34fd4fa94add4adfd47b74986af9dc78f5ee9d99a4c1785d8ab774fe4bec61e7080d6cd277cf1842cd5b98e03d8752220ed16e83fbda0cb695a2dd7cd0b28a3d2621789c3bc8975023825da5442da53a24efd1460eeb282a28dde57d649bd96b62ad4a1b036a26f4955149aa074c5bb7586c6755caa4dcb08bd531b1891a2cac752b1dba855cd47d847e6ee18a232539d2c0370602f8e30c237fe8ad854398f13c8c0003aa2354c197f1c36442cc5ff61e7ad56e34aa59d30afa50121cf13ec79e243ee34d27a173f454182f6cc0048421b0f7357cfa0e4e7c6b4ec22a6540a5e7f1bd410614cbca36e58f4887c023a4f570459fee1917f36314345df904a853a4c00f6858b2fee4b524aeb5baf8f3c13e6619adf3e6efbce9308ec717d8d720106f50785d13805db110a2ccfad61ae908b59d41cd70c0cf7ce24b3bebacd09c37330a8c584f54ccd710b96f28842f5a9c50b86de7b8a64c141ac58bbb609b20c4a35b4ce82451e6cfa1f7ec5c358f271016bc59adad0bca94007ffc632500c17626e09c5200880cc03f668eefbc18d9a3da256c2e9a8f07704969de71eb4cb94801b4f7909a1c4c041912b2636a08db84ed632311a07e925135d56a0b30b13a2448580349befd41dc760774a02ed20950811c65701d1397f24fd3c50159c0add7ae09b7c5425cc8f31b2f2480a212699e20c2c8420e22895663545f5f99dae67bd650190471c58787cd13444718df8fcde0a0dee7e50e64d0e96d71db92c64604d627f28d1d85a26dfe1781d1e901c90e270759f0b7d95326a853c54a669de46f93fa18fbfed0621d51a56d25d21bb59214d4ccc09ab7eb2e60d979f6e469f4d0cf78e50e5bbaefc1d1f077b9a6d034f1afded5dc1c85927a778d8f2bfc8b0ac00fa4d792a83f338486c9b7b71c6b4ad1ac790825ab75e8ef2a47fc4052f4c887f09223c2dbc60a04df9dc2933ec80e5b7c94b8ab6ca6cf1909380db8816a1f722ae52c0e38935faadaee508d510bb76442dec37987c9e0520607bac639250762fde99a36ac42262895c07758b617c551c59d02e6a5d4185c14b889dc522f59ebb888265b381c7468820cbb5a590cc310e87d32c5977c88f8607a81cd89b6f1cd47be58b2a86ef63f3ea0dea6f65ca1a60f73a1ad7402bdccf191875ae5369fec5db8835968cf5af3e8cb6feaa961d741be27d2f743974e9f692c080221fd27d85226f03d51828a242dd2bf68cfc824d99bcc4fe9a46f64de99876bf2bc85f5319dd1054351a374a56fdc08d02aadaf3605b472308b3720369fd538dcaf0a4c5cda8f0264400a481530b01cff1c147895ad09c924a0444d6f49cd5519e47cbbc789a5016f5b5dc61d76054de839e64ace1332f834e6a941a2bb9943e3a39c9c8f2cb64f32658b08ff704ac44d7ca0ec81e89d5b7d239ce4edaa4e74c520125e720813603b919c4c16a195604ce234da7b9827348ef6ad28d1001a45cefebc6a6f0a6296d93ea68d31178e0c82a64dbff7b628d22eef94e382cdcf0d5c682e094136191c21210d930344e5fe6d94cbce7a88b42439217a17d420e1bcf4181b6108834be38d7d3a011379129e7974184f3347209b9749789611d47c333a15a8392bc02de33c24a25f0816542df3a42d4e5861e8e3090e4914959b8c054f9b41d3af48c5ca0fd29c3a37574bd746e300d71f39911e6155e62261f3c425f58c156b477e5428aac8e1755603e99cee2d0a8ee8a32117bae1aa21ece80cfbded31007f44e72f07a27c5c8c169ee2868396328aca551fa4c3f1f3c0addc8f8d200d9766a15b6917a5256f2a62954a1cc5ac0b6bb410ec875cdd9d3bec3cbd6353959c041c7da9a8b51c3211a5f371719345fb7e7a2fffdef6d93fa8f90094b562e9217a3809026a2c044c72684b63141b9910addc6d2509fe87db76e7f3efcbb5825b9360937aa370512b544dfadf2e0114f88bd97692fed1b26a80038fe8f2112c9f190e3eaaa9d8167021ff73bc425b2ee87111e9c43dcec9dae280109bebf87dd5dec69736af06cfcef0d0260200681b21ae9bd0bbde2201e42f4f414ec12fc47cc85a247821ab1f9282f758172a23ebcf06d2455a79b9f3270a310559c4d03a411d214cf0edb94eab37cc6442218c0e8489a17bb1794a3038a5822dd5e20e95f6bca9a2f5d8cc3416aa6b20418d839ceda10b8f225803193a46d8433eb4ce26cf0bf87ba4d64721b0237998006ebb208cfb73df349258f47c1befd92a4e9c276b71b6a98afb00e39f2ba7c0b4326ceca92f505cff4addcfad6b4d51753da44734f6c47f0b8164d9ca0683b2bbcb0e377840ce1edc3501b38ab9fcd2b9555e06b29d7a3da873eb180ab11a9183a6d139a9d81addfcb1c0469b6e184072fde442ba104c6b9026cb172186974f994dbd6d5097c65514d9d029be1d628d02b6bf6d997aece63801d9b2bc44cb22fd2b8aafab104193de3e135a27aa1786c1d50681f8f42a78d1c98d11e1c5db4020682ff310100522096aef5216b738b3ab9c5223ae633c0c3c0845c45f89313b0d12c83d046239c828aa788deceaf094f811b57ef3b0c926e771828eb938cfe63c13b0a973a3c2b89c8f5edc5841343dee6f764132ec2cb540ad70dc6a0db18133fafb79db628a218942be300f36029c1b75010a529403cd7962f740308574793c52000e1bb6647cbff0f531013b1ccaceb0ceb4460c428c56228845fd5da04407fc562882e1384cf536bfbe5ca5c40dbbfc3c069bdc2e49dc466921ea63f81e9db89d6435e5b6996b668eca5b216665a59e3702d1d649a5d0782a1c05067d60e75c55e06e92c9d10cb32e7d3be65a0c2349761ef0c1863842f75a3301d2f8123a7d8caf208c4b8c5ea069952b85b3615177691ed0b3a9c281b63dbcdd119a505fa169cebfd35d46ace5313fde5de16a6578354f5abce528e8c864e4ee15a5715da695675ddf28835acc221cce5357a6009b6b01b0ff8551c0c979b0c66e33458e1a8c55a3934e7653d65e988fa1dcd5a130caf2d136799530b92bcd90ec0338f84e14b71bc2eeca074cf2cfb1a50c6a3ec9db410148362f04ff2307bd8ca331e0eecefe1887fe94ea7759be0c874a6b0650b5d3600f78a5a7f1fc6ad188667d73002d630ca9e99d4a09c5489f4df7a8b4fcff6a3d7ae7120573a5c402030876e0370092f88bbfd41bba93db03650da25612c7eb1369e73a0f5ad669ef55febb6970f2759dc2258f5f3f91e5924778531f4812674933dc07c3d1ab131b9f72e554e215ec4fe05d0c526c807b017f18d2271977f2a82ee560e75beeb215f7ce1e3e052c5d26ef98aba581f85ff8e3d53984b93235df02965debedf930ffbfd8e1f3bd32999493c43debad06c444dfb9fa40b6a6789481ebd7f899357fc3032633d9b6b6fc3ad6f8f3afc7091ca7d2dc0d5f592b64bd35ffe19f1ae70d07b3e09ba14c3f080ed080ddbc10fa42633be5b352d5fc55afd993101a094a428922a5bb518576694426d1738fe95f5aa4305d2ab96c3f07193ce77e8bda867687f8ddf8ff9b8c3d6300145f3591c08d2000a3cf5453516d1e2d76c350b89fc34d66a44e57aedff0e060e8e0962eec19f464e5e24510f9a11208f9f1dbce270cf0fb80e084cefa5c48a25640ffd2d3b8b8f9d11d3fedb83122f656303cfdd41af26d10d804a0afd130cda41d76d6115ac4d55739b2628cbaa1001ec01aa4cd28e9be515b65e2b73f7a24794f3b113cd7f73f4f702812f76f9859ed527d23f9e4b87eb4893a6c82a50c2d7a5446b1682409333c34870b6a55890b3e96e807b22b144540476eea347f489e0649084188cbef579c8c4181f9c104257622e738759f10b380505a1df374e4f517e7378c44e93489e5dbb550d5dae513fbcbefe42caef0de84a01b916a955e20c97fac0669f8dc34b7450df29ec62332b1304e769152215409a29d68b67d59d5499cdba494e95fc7f4690f7a8de3a9daf7b6c725f17ded7230c3733bb3ecc332f9a46f4d30070c57dcdd5e8418fe01b8275ebbe3f9cf96db86b0738c051dec90ee5b98f4fc3fc9fea0507799003c43168cc947174111302aa3696c9a90175437832c8a284fcd35f8d11d8d0864f77321b0663fd9c26107f42a8370b58f919e7596c6f3f3a843f3acc7c0f1f5110b7c2f6d2f4a708a701b04969e9777be14cdb31f9ba099a923124fed535e72d21bd6d12d499af42d7e01de5390e7b61b0ae598a1450b82655827587d6b926643b71ba5b9da1d15cfc0ba940ac78cb68233dcc49c93d42562c9d6c2a9801337e63b2745d5a4e8dfed52352c80a9d05b7b0842178069aff561964abd4b528d1822259beae81b5bafa5d72c4b5e9f8f9c09f1bdf690903945c1caed5a647f59b8a571e7b8b8dbe3669badea72b097d02c6a25e3604ea6af92d15a0306da7de12c5a954eb97155f6bb06d4bdaaa04ff93009bc715b6d1cdc48f867e7ee2d2fbd244ea8d9efff80cf70588a2140040af638757300a364b14cd898e5af19527070a8e578508dca98d24420b1984144aefd0017b8a0bfb7b7e2e86378ac458c82ff62ee6f469064922d0b9eb96e040e6a884ff36ffecee9066f40d8c434d6b2db6d5cb84a7d4b9520b377e8a726414743a26b415adec6627de8fa4251714e128bc05c86e7636069c6611a5d3e13dc0adcba54448f1ab0d0091fa5c91aa74115ae8db2a144ed0f35ad3d88b75427bf17c5006707e0da15d0b335df77c7c02994b9af3e611b3e197258626c22b13b446f215653815341257fc4be7a1cb2b5d1889bbd11ee49fdc19b2ab14d006f4b4829bd3b0c7202561d6740ac5d887a8cc9603ec1f46385dc79f7aebe2e50090ecd16a12b92576f0c811719de7b6d6098280c6f9cc03386dd8da06225df3dfd2bfe983ac3851474439a0565bff226853a8d7b39e0d3e540c54928dc3cac9be170047c6fa313ee72d3dac02fb73bd1db9c1c35760af0c1f9f938a2ae0014005d6373df562d44903ff8c248e227212830171abcb2a3d1fc0bcb197311510fe2673d404d1b534df3f94ed6e45cf06e9134d669284bc9fee39c8763db556084196e9c9c4ba904710d600f60fd70ec79ead3bb37b47c36373a05d1d1501ebf9cff186adbb80c330c2b24c279aaa563c8b49b2e4cf5a36813fc42daefe20e4eebc8a5dd4e95b23fdd0087e5c48e780b50e00c2cef72f384f6303c2b209f1d9d3a651082d9d122f5f305695d7e0555b08fb107901802a9864e41ac2a21172f4249aa3159b3775c0d424fd840903a552efddbf0fb07d1116391fed539189664c98e6a313dd992713574d4ff940a80d96dea64ab562f16b35a39b198ac011f63aff1ebbd3cb6ccaa828b5b052d79d0f6cdf0a075e609f148d8005e2d59d3ae8ab65ec97c00fc5420341d8147d42ff181aadf8a5fe3522552ffc18d065b95f2905a2cfd0859126c558fb81f7bfdf05b6f82b7ce24da030eea3fc2b007243eda355147990930e4b40cfd547949e037de0df335aa224411f8503d4d9b254816e3b0ba28592d222716c8aba9138c073d182c01b038f6f5cd1d8d692edd70dd290d9906175063f1b7edb032026e5a3a1f66cd3bb279c659a1e375bd8f98e5c7084cd43f491b7d7cbf5c2267e185cc441cb7ddbacc0b3b40ebc23da70f77318334aee91e553693811eb575f98217f6a0f7371a738e1b6e09766daca28cb7fc0e10b2dfcb4bdd4b7b8fb2016781fc32124dc285ae8270dcdae429f6a885c6505d5a63e82dae8b893f2e1d52963fc7e37f6e31b44e98188da9ce73b9245c6eb6e5fab1589307ab3ebdea20141915350b2cb939966208825de9808627b5c26060d15d48e7f85e3371784437c895ebe12b3e875f9702c06db589565aa08268eace66b54cd465e9a67701c1d066cd6e434d7a5a88970c752021aeb5f63a1b748e7b683c33316f9e62666a57dd99ba253e3768b6c5f8065090a4ba759c826b34c53c098629c8c127afbd4305629f0cb6df4267861dae8204b2030778ee26a5dc6f883688eaa53021f0587560224fca95757aef2cb91790186bd515c198542ecae7c6564b90162160229fd1d9d001ad0d54a74a9470568f16c34f73e41b23d70bf6c08bad0065da3e47a649ec0690b389438c48ca0227397b22115103fe35f520283185860d6ec952890e71db107258a00a20fb649f00f5e0955d9f94c21e77388f5f9bf083235f381a06249d6cdcfbbbad2464d3505a56f9da1c53761e902ca4cbe8620533f42fd9a72853989161b5077a9d9541eb1ae0d88e4cda523821a02b0f6e487089d4d4a608810c0c4579754ac12af0baf8f38ebd78ab34fd9389beb8eb342cb660b827e3fe1a5fb5792d6164b5b8be4525b24153727d9bbbed46beea4f208b2fca5e38a9bb847e53a079779f33c1d526567b7596b7199bd334259711f20bc0e94c22afa89b79456d6063a206e19efed77c04e05592d141839c862d613271db1bbc60828ce6e277553ce1e74b055a4ed011cb22a21553a1b6d3357e0b56d0b33eb23e616e15afc78020768b2fec3fc1e90c8797d8ffa0f0a9065a08b362401f4af1638bcdf08ddb4313a4ac39335fb509bcc796201ea7d8b6cbe05ddd7f0552049c0cc28ea7bd33b8e397bde9ba6e068b492dd2b6d2443dcce3541ad6aef045c2184db12c3a1bc9af035a8e7aba2453cc9bb9132404027afbbd2fa849b1c286c1c0447c0670234302782e47bbab54c73559644b50f0e34e70bd553e24d4b8053b00a9f782fb5c2a471562e49f1c663183698db706f7cc53b6a60e9a4c9f4c1457cbba9eb8fbe4ad8b5db9e4fd81e3fe68bb7098c534f4dc369b55bcd89f90062bcef0e96ed895f50422747ea2550ae47f636da0a1e39a0c7b31df2fcc4531a50077b31a853f125854dc914f7d0000682d239e35d6b9dd3328b9be0ea10e3ed69b51cc07f8ba345063a2f9ab3a80bfe2f92e1a23162cd304ca3a4c3c293826e02d0449d45314a0b7d7fe5eb8a1a81e6e6f3ff5cd0acb178c81ff478404f9acc75cafce7d62ba09ceaca107bab36f926012811d9c97c9d9dc369753c45d549a041d6740bffa19b71d9923506399cd7cc107c2d354cb6e11dd7df087c5f35fe39d9b05143b17946d42ded41bbc57719d857477d13335a84f37305fb0c277c5ea7b6c1242fc1022d44d89f8bf028fb155dcc18fcf8810ee792f68f7365fc9a2fb59ceb58bf2c4f10af35df3ffc2bb3a68d2780f3f7b47ffd692c3a992714ca2d60bbf8685246432657ecfb41642b75c7fc79d9567bb27938ac352ccfdbf4eb77efae6f68cc49ab39e16831035c5acd834f9b33e06b611e1cdbcd00473bb3e06b614e78b4c46c29b2dedc9f2e77df3f38fea26a7ed8b85f9211c407e17a7743242b7dceb74561b5497027a75498f76d6228fc3644662d02263ef8e03f7cc3909186ac4b6d7984ccb897e6c6f5a2ba2de51284c7580d966e5a0652ba8666ae3a68e1b830175afa51856d0fae9686afeaa23af166885126935dc80b7a68f7891b8326d4be77bf0126dd67bccb95b5c6c36c1665e5621b6a8b2378c49b498d680150cae39a13d1f9d2b503fe8d82fab34dc48abe8d3316580be935c1a8b7520086784892424e4f56c15e89774840fffb4b0cf8f91bb8124fdc2f210a70dbaad538d2acbbf13072c753dde2217b5b64aaf9dc6cb5a5b41aa23ec56a90b9fe81dd743f8f607b91cb3617da305ddbd5cf4b26674356e1454587aa8f30e7dc47bdd0ca647fa76a0f6976aa2cc87ca21706ea5609e85c8cd0c3188c7144e71335f81f2367c08fe117a3d6b2f57c566c82a19bb21f197324bd9cc4717e56ece26bf112fe19e08cce8c5eb90d9764d49af82110f8bc05fe23227e9ab980bfd182d1e098594c6be83fcb5b630edd38886f6ef9983069ae181c1aacaf582d3dc7c246418bd1ff0d61fe6fd7efcfc7a07d25be4ec40ddeacdb9c3868c09b5ddc9e00a32b12d051ddfc435f2d16dfdb2b3a7d6255b9e143f72a76bf1b2af47f63a972d3474725d672ec59a6e3661fba2ab1ba6e4fc140e4f5c04a15c84dbab0f56f429514ec05054ac327c6541038cdb839d0772f713f9da37d06107ae82cfaed4187409d52cd08712d6c9bce60a834d79aa12d95b0457664429692480b1ecd3a8e7a02fe4a7405fcce90af5e05fc0387da4d6a161b3af6ff509f8818b5984d01190c0a0c4b0c61da6d76354e259c480827ceb4fb26f75e499d9dfb38c5d7fd1cd2af0c3f4feabd28522f1fccce719ec5b7e92fdace66dfba70b359d46a715bca309cef5121df329e577cf36aa98df7ab32d34a8aa2ed54bec2b2a7626d50b646d823ba76d2ce62fc638dbc4fcad6e85a3a91fea9cc36eebf8c3fcf90f48761b148e17cfc512fb4fe324b65d85f563f677a6712f9e3953a7e3a1d7fd37d2557c262659ce2abd57a79ef3dd2125ebac9bcae8c2fa5f24cd38552c20e941266ba91789eee587c8ca7123d7425bfbf4a64941248ed2d9ceb573b8a362856295eea5e4a7cd0ada4012e9ef0c0ebb561effdb3ef380ce78491780a20305210285c89a1653f7cba7b4b92e7fd78de5f12c9fb2124c43128e82642840807788b13214284833111d84a58ae93ded98483c27006f538e1380ec4b30ae72b8ef33a7f361b27381d1dcbaca2ce713e430bcabefaacb14abdfaa33fcc6df69ef522cd48b359b4e1a79f154991cea6f8591bd5c6c4737438a699b96b7337a970e40a93a0cbb758a5acbfa34fb7d98acefd58ba38ff6cc69592e1e8393adefdf1f104dabc3a391bd567df0bc59c178afd496c41ea79d77e987affd5cbeeb378c26cca675279f9609ea27b5ead7be18360fe98cdbe7a98ff9dd8e6d887ef399ee0388b7c7bec55d252c9d6285ba30f61b32e7d9e1d0d4752888af791ac915797ca99bd485ff84ad500fd7abd5ee5187a56ec2ebe3fc40fdddeb240f6ab441bf7f4552de7f5b31b7784f3bc3965b8ce17bd0cc34962d28fb83592800adbc7aad718d1bce49c979c12e0204c1ddd5d256ff655ecced033a6a1e7d9579e3065b86c43254eb421f6519c19bf485d9ff56bf1bcef9d86f8304577bf5e2821f0e8f660f68efe42648112828aeeb67985ddc377c3696ab5f2578528820a228eee6abd897b57c924d62cb39ab09acc729e3d6b5e72e6d9bf700dc9a64a5ff931fefcca8fbd6a61afb2cb8ff17cddcf98c672a5af6f9a1f3e665ddbe540c30fb308e4f14309f39293e503d8dda2dbea0c2082043e2fceea52be89e6d944ef7b2f3cc370bef07c815d9eb0d777d77e60a6327cb70e53b14987a988820014f8498c6913b0fbec795e1f3d8af7f763317af325433a3fffdef30901212020a549e979b3c44f67f68e7fc9f115869429d71773f72ffcf5beaba4ad75beca99bd23be5b1396d8cd9a88ddacc9ce4d0eecdbc969d2f339f3779ec77b92f3f5e8dcecb0aecd64f7b37f89ddac2461ad96d8cdbadb008de2a10f1ed47895b4dfeb9b28ae3fcefe656df6150fe3e977384107353aece4a00450f70270e8181aa0060a4e2938b171cae2a445036e5a4888c2c18c0d4e4252b8018710ba9bc810a11b88c077cba428831b545dd820a5f7380539c20828dd4d44dff0d3dd0c60c0cd59a7bb6911d0dfaf957a941c47a1ab14fb8d4f39e2f5c3ee56758daa216c3da6b8a002140092400f3c20359614503408f50c69c084bcf977c70939834e774ed3abd5c22396010e940c63c860c6c65bbef2d68b34bfbebf2a97cc068db887ec1e0ba6e30fa6e38c557a2d98c9198e2205c385ab0fd03e604ac8afe4d91ac3104240104307f733b5620143f467ed1d5fb8bb8b54e88e354a0129e8f937e3d7d39b5bdeab2503982f247c814177eff4ccb855449817c42ad5c18613feeb8514ec4e252a015fd6f0317ec6df6747ebaa443f3f1ef5749f7ecb8fffcef299fcdaafabe48729e8f22d3fced5768e33f515112244887050a7ffb5df0379f94c5ee64b3d7f304fb7d93fd32bf31f23a8528e0dcb6c184e3aaf101656f41446743798469527e0bbe56f823fe6bd15598ea9673c43221c546bf138daeb55eb19cf0f3d4feae50cef371d79fd2161cd6c35c524b0903dd12824408f137820d0a4e5d5241492f37b212035f8f838bb97ec2bcf890a79a5bbf77041dfe06c38acee9bc2708a18851cc60ca751194cf77d4cdb62335019a73d32ec66e12461f0eac47703bb3b1f1bc9be0a11a380ca65b59ae233c56d661e55e5efafb492ee51fefe8abb7be592009f79f4f531ef5f4cddbcd935804244832142d67c608acff7e1cb0bad100f4c91e24cbc7ef87a79a185ddb88f9010506315fd9ead5ee651fc11cc18227ef060bd68abe38427c7ca5ccf3443a071299caff417c96b94b844a3c4271a255ed128118c468965344a5ca351e21c8d1255d02831068daa44ba7b0394061da0ba0ceb91488ab4d222990d1fa82e79a1ad12f4216c7af5c7d950b646de1df7c17c74e4e99184648d70596791b5ae2041ba5b05dd1d64a85aba9494a948afdc2fe7156bd4dd6aba3b4877e711a4bbbb5380829ed8eb8686ac7b1348740fc2572471b59df7388fe487b019d2d9342be97fe7c3b25749c7143f098357eaaefa5eecfd3209732a25ccb1175ad0abf5eb7d9e7f3b7fd8530fbfe7985ef7becae688bdfec37059a7e8d6baee9733f470feccfbadd6bc433c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a3939393939393939393938383838383838383838373937393739373937393739373937393739373950a0408102050a142850a0f4f4f4f4f4f4f4f4f4f43c79f2e4c993274f9e3c79e2c48913274e9c3871e2c4090f0f0f0f0f0f0f0f0fcfcecececececececece8e8e8e8e8e8e8e8e8e8e4e4e4e4e4e4e4e4e4e4e0e0e0e0e0e0e0e0ececdcdcdcdcdcdcdcdcd0d0e142850a0408102050a14283d3d3d3d3d3d3d3d3d3d4f9e3c79f2e4c993274f9e3871e2c48913274e9c3871c2c3c3c3c3c3c3c3c3c3b3b3b3b3b3b3b3b3b3b3a3a3a3a3a3a3a3a3a3a393939393939393939393838383838383838383738303a5e789139e1d9d1c9ca270e66b4591a8bb57dddd028aabf37e938edeaf1f236178cfb6def1d51abd6895bafb04dd6d4777d761024ed5dd7474f71cdd2d47a3b82e41f750383b7b85e8bb4b5aa532532c4bd6e84ad0fd1aa34ade04f2265010fe243c04e64b81bc09f4e1cc4a5490c270069599becd5ea93f98afad64a59f675b836c643674df62dbab1cff7ffea2c55eced16d76d8ac447fc94c7e957a185299f7797a999c3007f3a5309f5ea7e309627282b3176dc664e71b75efe7c366b879388a63ea18fff5bc1aa3b8512e3d1ab5f4a3514b1c0c89038e2e432e1029bd8486848e828c865e4947494943afa12b4b4247452f2d3f45ad56682decf38ccdd1def14ad3c7aa0d7d62d37591ca4cafa577ac325b99ea92ab3e93757d487ab6a8c747fc4c94f1c7a850cc861ed1571aab43412e40a0fb64a33a7577d732fc139916c0e0bb95b017c673ece6c7c24203b9ce4a457a3b4c3d17c9faf83dda64efe8229e326cabb4b0ea66ea1e0cc223ce98ce86ba9b4c13dd5d9b8855ead9ee853e299c7952c1624508bfc299673999acaf669e5e163b9448a2fbaf472d96276198e351971d1e273b31f0887e2c5de55bb1792c6f97a3e3114dec757fc9ec7dabf55728db5009671be26c43a51d27249621af719cadd6bce47c6112d7c7d576af117734849148ad16141c1e9e9c8b9754e89f257bdf8512a8fe5cb2ebaccceb0f6da2423d39384bdecf2895214528e1d1dd6abd46ec6aa189748f8d5212c277cb14d69383f3c2f7c7f0c19e1c1c32e98ca3a0ee162288f0dea1ce86e10f929b84a423806261071600d058295629c6b488a806a4258ee4e8ee0fa907df0dcc736c5266207ca8ef51cf984c729ceb8bb1c70ee6397a18feb45addfd830f419e1c1142859026bafba851472e23338a70b8b247971c1d8fd2436eb9a2a59df0e454795df12bde3dbbb10165654caed48e2fef67f4eb687e11ccf4ce2656be747709f31fc5cf79be70469013e4a8f9c01626def8830a1609584119204ce14080461a2f62a698a0052a98f9c1881f90f0f293248c3449409ed8b4b801000c219843ca03a0b81cd4b145115f3831c48b3c6228ea808d3c36c0029f24dc831b536e981df6e8dccc6af4000d433c4175e48840cc4c5450a505a10dbec06684c0b09a428a1610e08901a69cc418e20b2688c89242cd6186195ce1acf8025b410f423a323d591891c004b88c09c00b3e25bc86a0c1c91a0e9851421a538e2c63bcc00714561e8c1fa06e6c98c30d110089c15bb0820a446888aac8f5f15f243263bbd3d46af980785689e17286ef5599f5426bbb17399ba257e74b24eb6b36c5d914737448255a873c800194603604f286c44e8bf7b37e9e5db5411ed17b36a8d52a01d6282a1da030b4c4100f43b57d8a14f71617abb4d229fee53ce339fa777536797576dd7b36e871b523fe24b1d3e285475efdc1f96377ba72fd7bc9a012dfa71fcdd6c87ba5ffd8cc4a315ae45ddb7dd295cf56fca41c68105275e2b5547a783036a19be1f8d7bd874749c9bdcfdea6e400be3d936dea669e9e05f1ac92f1a5e41d3de3f742abf2f19f49f422df36ecd524ee1b38fa83dc5ffbd56ca3f2c7e1a4e21b97a343721c99f1fcac731ce7be656c437f18989dce1edbaebecc3fcad9ac5c2a0ec4ffd907ffa322a675821dfea60f69d8b13218a3a163ef68c6340c7fa4e4ee9b7cdab8feaabca3a3cd3ec78c69e89c8f4b0a277af36fb52f5afcc2675931cfb6ce2c906fef856256a58ec5227d95fa632aeb6e6bc87564e41bf6edfe6a744ec9379b99a767335682c729aa7cc3e32c5a65720ca74da5aa6c7d1322ba33243b3ac390fc5e9c46dd8f4a44d685bdffee47a5dc59d7678aab35b2f75da8d6c87b2a61388384282055a100c58931e959231c5240ee7740905240825240a4a480e8a48090900232420a8808dda3751b1454dec46d50f0150ade1aadaf5070957b6b85bdcb96d48f13a47ea0b151a57e8011eda58265360c7f7e78dd933aa5913a51913a8dc177fb57ea34bbe518cf11df0d5f58aacba45a4caa7548f5edf6d66b36455f790bafbc95a9f737e899a6abc90cc2681e666084a76cb4b73ed3ccaf713ee8d9ee35a3dfdf17d39de25f0d50745c69141d5a1a45076d141d64a3e88840a3e880a2517458a0517474a0bb0560a4030e504c0ca0514cb01ac58450a398c8d22826981ac584ac514cdcd0282620d02826c2348a09324336d0343b7a5ab3e300add9d1426b76bc5ab383a9bb03e041aa481b5f1ad546031ad5469846b5f144a3dac840a3daf0a2516d84d1a836cc68541b2668541b7d34ca0d0f1ae546098d72e3a6516ea4d0dd1e141980068206c44d6b40f8b406c40aad01d1426b4078ad019180d680c0a1352086680d080ab406041746384823d45a9aa2d6d264692d0dacb534b8b53436b4962688d6d210d15a1a265a4b23456b6934d05a9a33ada509416b69c8682d8d1bada5a1a3b5342c68ed8d20a8221a0cc08aa646591143a3ac6040a3ac8840a3ac28538448003830c28106a833551a75064ba3cebc1a75a636ea8c97469da1a151677668d419201a752689469d71a25167aa68d4992d1a75868c469d89a351674ed0a8337934ea4c0b1a75a68f468131a45160a48c682954910e0030448c964689e91a25a6364a8c974689a1a151621ed0283141344a8c11dd1d1829402a880a9d0a72a55341b6742a48ed54101d3a156402ddad11c0030f86745084080052a8027010800f382892d2c800698d0c08ad9141d21a1929ad91296a8dcc96d6c87cad91c1ad9191a1353239b446264c6b649a688d4c055a23f3456b64c6688d0c1cad91c1a335323068ad4c91d6ca84d05a19135a2ba3d35a99a0d6ca18b556a685d6cabcd05a99b2b5320d68ad0c12ad95a1a2b5322168adcc085a2b73476b655ad05a9919b4e64407ad393180d69c60d29a13525a73824a6b4e20b5e6c4ab3527c2d69c5040770be1a0830e3c0880100d00da9a546b6b04d0da1a135a5bb36a6d4d0aadadb1d2da9aa5d6d6ccd6d6dcd6d680696d8d0eadad01a2b53549b4b6a602adadd1a2b5355cb4b6868dd6d6e0d1da1a3d5a5b3383e6c39174ff48ed4106df2dcf5e019586747e4d2e30e3821d9ede5cfddaaff3d3970478294fb3a615885a80450b7644700206b24f99e297ec80cc6c544cf685e96683fd8bcdcb575895e48bafb0aa7ca657097bbd36205d727d25efce0e53a11f2a6598406207d40aaa685f556a8d1ae03636c349d6d4a71754b066e629b4b55aafe421f9e3db577b856c90161a7bffec043129ab8f3d7fe9d9d630a419fbdf39fdef4f9923152555f1cd37db55ea98de3b3bc7d4fba689ddab73da10665fe630eb78ca66c6169cd7c5f0ef679de2af1f9b6e71387339c555acd2d5c5f4fef720053b7a5f7c7014edfdec88a9e8d331a59f73178edbf2a255e256d8bb5656897cc38ebd4aadd71fdf66b6d56db5f47e16df9ede4cddabf7c72e1cb7c59fcc8fdd56ffa8eceb671fc60a3b9a1fbbb77cabb6b3dd63fc5d9721a1a3a0560b97cff47a85e1cbb674e1deb35d8bdbe2ff325f85936563a4e36fa2b1fbe39bf7341cc7e90fb3d80b9f7e2bec13cfce2b91e34ac711a904f23e531805ed459a790afd646b1492de839d52b646d6c8ab4b3f3c98e38cee232970f96361383bd41c6177a533361d33dd29825d7d9857f27e15a3b8dcc767f3c71c373872f4d1cd6a40c9d1816ed6287a38e379e58b4d24ccd4d92be17d928599a7102edf4a9ebd11c6330cfa8ebcd01e956f057774b42e3425103a52de6f7aafd4477fd81224503385154a90e0468c115ce9c6e1a4b93e09c3631f29252c8c38dc40c5b144199691e10a151278408e9718c5b23782837ca3056f0cd13d9b45dbe3ec9956d2ef8f9b8de72b5fa9549f65e23b9f6768b35feb8f5d46d81a95751679f7e83bcae4117e7a6d2652cad62856edcdf22275c9ec156b5db359e4fdb559498b930b6ce0d17a9ccc60a387ee9e7eed290c36b88078b6c0866f307ab3574bb3ac11070b6b8c11225ae3e69ba81a7a746f9e11103f6e6822d4f0ba1b88079a2c6cfaf4fcf8144318f8491868257f244983cb8f10d050838606bafbc9d08e536cb5d08879b66b7d9ee1b5990a119c70928068955e31fac25f8f72e33e981c556658a0bb3f36c7713ed85a5386cc0c577763333e68541965f47caf8c11ba49efaf57bad4d198bd724493c1058a8c255064742832b834c7e126e7383ff2cd66c9129563fc7786dd7bee3c5c91c798214880c2186abcfcf8653d127befe595bb59bf7f6c31c607dec3e0516cae2f548ac579e7e8d53ed1d1c1a1b8c42720d048e96eb19bb1c8ec856298178a4520448ce22662acc05b38af177eb0236d18a4d45d4b45c97bd9045df7abec489ceff2feaf170214c2984018444de40ac319b4b1aa75ef2d4c2cb15feb2fcebab9ec6b2e81ee2391b6dc485bbacdbe794fa77859d556ff6c47b7e99f87dd7cc7cf34c522df386e368b7cbbd85742444ec19cb17365d76ab938afdaee957cf30d7bdfd18f763f62af2e7bbc54bd0964efbb5069913781ead253216c8dea2581bc90b4599ae8956fa257b235aa44d91a31751316863348cb5774122aa38b3a3087c9d93d2cdf393aa6fe051c187baf5ca9582f09fb261aced7f8cff4924d307fb209e69182f825cecfaffc18bf5ee1ecbc9faffb5fa5e22b3f063bd2e6fb60b67806dddd5dbcfa14e4048033dce0a20b175d746f9d5b230eb4d1280e38e9bbe59831eacef166285efbdd1f2ba9c116647c13cdddac441f8ef4baac0be7fa4d439d969fd9f4e127e151bc55ca193e4602f1ac32af784479f646326bf49e0d7a8fdefb4d4759465168e62994f1bc9265146f95efea2431260a67b62e1ce411d170fca792676f247b235cbe15ef5a2aa3d869e9b4fc3795ea5fc1efd9a0fc4a1e15123b2df743ef85f0924785cafb4db6c354e8fba41c9d25eb42dadcabc594b45ea6be9a97fcf16ba9f8f622cd218ce2fad827a64cd7caeed78fb9d7c716ecfc92b605df3026f3bc731ca7939ebda32f5982e99db207f20d7b96de1f5d4c01d3fc31f7a8906f2fd22c8506867457edcc538888a9b35732e080be7728033b5bf94c4c9ce39e6962ee552d77e7879de3fc31cf44eee59d95c2e80465328b1d7b1e9fa9fb264cbdef282e9f092bf94ac5fbf93d4c3e7e30fb0afbb51eb6147c98c72aeda868abedca90fa93d7e59b6fb87c2b1e2edf8a3532b2c2a73ddb3b6f9e0dd94be5f3c2afa2f45dfd1f14156424f1c10f0258a0a80897f86ab570b514d7cfb36fb5ca4c67ae3bc1a09b9c1c1dd94cf28866efd54973488a3f58acd8af526cbf4a5f548cf9f11c72c0c07bc1146e749f88bafb54a5cbf0669c198b34e3f299ca677a7dec92639e828c112461da0812316d04c9126d0449154d44831ff5c7d48009254d34c8b2c5aabd32cff565f945abe41b869118eceabc401a74d1a0073a7ede63920412ba81684c82b0c046f5c2f44805c69c70ba4f374304d0bdc15159a767d27a99bac5be7acf567f30674caed07d6ad27dda4eaceed38a0914a828b2e886c9dee88828c0541a56ea0ff3fb4d8f4503a82854a731b8fc50c00b5c9760f47618462a8d229d61d06cfcc9af743ff4eaacf24a980cc74b2a25554b49a02a99683654bfc32f74e787360ccae377ddcf27d51f8f646fd4cdf23ec982f7e1bb5e36af07669aa55a4a94d3d46ac59e086205ea092c4dc58c5fd604638e3ba9ba4f4aba4f49ba4f28186088297d3aa1fb84a4bb8398d0dd39119e8e9cfc6480326874b0e46009c3297633b1d3f2f39e0db25f257a32ec2ad13f15fb55a26a81cace48f6b2372213df918f8f94eeb158a5a356cb6573abe52adf4aabe52a9fc9e75a2a3e3e44be7249f98e5e241f9794cf6bb55c3e525a2dd7038438400c07947140e880121c206407307608d3ddd766f578762a674529da01041dd2e83046072e3a84d0bd9521dec21c20900398030839f46d400d38dc8083151c786e88e3862e6e106f20c186376ca8a2dba67eb831f1f29954ae28164faf78ca58511e6f65585951cab07a5664b5f22bd910850176986180100cb8d2ed8516fca9e1891a9a6ae05283073508a1610a1a5ca041d5ddd9569bafb6ac53e5995a50e6ab55a6ac19cccc306606708622195c20431cbd714030eb3218c930044c1a603200e6c1e880e918a288618ce166fb8bc479ef793f752af9765f0928f390e64afa4afc4c71b59dc7fa3b2ec0030bf06101e002962ca07b0363a405123f138d1e8591b07062f2a35e923005840a604101a8ee2f527c11f3a586ee2dd7571aa9ab9cf74ec7231d691ebf847082045cc1aab65ef2f3040c2560005ee8f0e2458c7ed5cb56b2a0b4a2c4a5953235c3a3bbbd92849524c613c95d9cb3b6d901664764209029408645c692fd68ca82734cc6e8574ef17e9375ec2389f104b1f82d96adbee2387f261ad6a5ad6904120c92244fc0c38bbc43eec928efc1dcfd0b30eb0f662f4ffa72efabccda78f4a5fa8eee7ba1adc239a6b952ce66d9d79f0fc9eb1c57e64a8b700ff80a4edd2c6e0ddd9dff9b26781b862e6058a2c22064246384c0081ba35814583436932c8b5996843a47c5a296154b15410c817845ff8dcd9f5c1fd7cf778eb0d53865783ab6607e1148645246184318861f30a139725bdd5b7d74721ff5cffa54797e2526264c455e50c10b66a45e80e285255ed0f2c200681cb403d4d22addcde1d971cee2369fe222e7acd65f9be94cbc73262ee64a4527f21627f36e1eccb603ab001700ae6274c42810e3125b8aa19c4838bdc5bdec56e63b4796772938ff4be243fa3cf8315e8adeb8f24197ff08de0bbc33bc0878405e8fd7417797cf543e13e7ac0d2bf116b75deb4ca96e08a607cc0ed80660246c4b4d3cba7b9b9fb19217c16ecefbf9c57927d2c695a1ada3cfefb33eb34c251c0cb8107010e066dc09af5798de38ce59b2af54f4645f2fe7acf9d9466573a66e6f099babeb0f6604c800015d208044c011047cd0458b2e63c4cf448ff363ac44fc4c1fccce65065cd270d1818bce1617a4b660b165862d545c6041ca852a5ce09a08fef1d61ca997e1cf66931ffb47558e398f1ba7cc668ebbf6f3f75a67adab16e868a185db0295165099dae4c72aa6ce865849a6a105655a16a0c549963ab264d1380c7fb228496131011623b01c6119c2d2c1d21e4b23582273e95b1b768f6deb270a2b0ac76a7573a967c903d7192e1b5c5cbc8f7d489be81d2be9ac4f525a811218ddcda4534a45a494b40029ad7ac3789c32a56fa2b13a65978eb39fa0d7394e19ce41493e24a9c0421f2cacc102112c5061e184fc4a1ce725cc679e1ee7accd66ec55b2acd3e64ab9fae1b6c499f863ae354352bcf6f3ee87e92ba590be20313932e3880347b4bb833a7594e46803233652461d306a4a1949e9ee8df39e8a0f6625d5deb99561b5b9fbef9bceb53e5b23770e2b798b63faf116c751f133f56b31152a0a41d11345b0a29ca22357c2b842a6391b1cce7bce71a6e2d0663356f2f72bebcf679da33f3f0cba32c40a1e5682b032b3826405342bccd0dd563ab5c2132214a4882a4004238ac2d9a88e386f6d99debfff60bd6426dabecb9962252bd17678fefdfe7ab5f400bec568d3c5f3dab2fef896affdbe6765fc309f1c6be38efcc1bc91e16c7ad627899fa90f986916f13391d17733ac56e82b0dbdf99e8f8d8f8f47e42b5fe54a8b7c7274c8703645ec11899f89c4cff47af57da4fc58ba72a545b952f1e543efe883b18fe7e32b8fc8c3af4c8e209e55ee7bf3411f29dd6399bd57bbe4e874b9c9490d5971d46a899d16273c39557c7c6c54527ac894d01e42690881695b5fcf3433c7344b6f0ae504b91184436f9c8daf3c2215d73441d28bc285e10ce282be54d04ef704f2028f36037d9961970252f243c6cf97eec265feac90fa19a10b97d5162edb386f76a53654c862a5c208dd9d4a8102a914720a27f884d15db8f82a8724ce1ff348989761f6e121e5335bd51f9f4d8d0d4f70ca1153ee9411ba0b171722bad7defb76823f29291690c2a5bb33c6f467cbd5821e280584549434517ec821e9d9d46b612acfd46ef52715654994222d2c5a5e77e1124e3277d37db51a4998cdbe5add0b43cba7f54177e192c929fb2c142fa0542822f480a067d673426fde7b6f6dccebfd261b2bf2ad0c6114638b497b3f56a797494befe8cdbfaa0d578761e849099e9c79729f8cd0858b7bd4575bb86c99b4ee9517ccd7facaf3496395ce9feee40d275138e1565bb86cd5d25cbfe97e26eb14bda3798a3693b33a51f1a8d1858b676cbb2d5c36ec3dcf123c5aba0b97d51c7d0b97cd86133b2d5e7d25eef3675575e27f2a3c467654d0dd4b9ddae1e2ab95477d0b972d0c67f760a6b3bc4fc2e0a91d1a523babee2ea7cc6df5b9c253e6fdd551a313a67bbbff7d24ac9b235337439f789ce07d24df6cecdd7ea32a779d2a98c454c7ef4ce5d4f1dddf2b9dcae9a1cc01bbb72f9573b3e1a8b1556b3d4c655eda84e1547995591c302f5eebd139e220f576c34777835d9dacbfa91b29baef3755ea711ce84add709bf7f9ef18c238d74d4faa491d9bcd1d33e86a32a6bb37ff6952a51bdfc7a4f74c9d0d5ddd8dd3a92643b638b66d09ef95b87ca4cc2c908f8f8dcac7270c67900f2e5d52a4889d161f9cf17b393aa48f949905f2616d905a8db1e26235f68639676dde672f98c3d0e625792ce16249d8dd58ec669cb3369b99eb77568415056951fd37b14fcfaf64e3021b321bce74d2073313fe49d91440b585ca09952ba5644d4a491e4a88500276f7b6e4458b9744a996da5c861e9eb27249b67509a621f9fd6cef4db1da2e3f982be5acd15686e1a4f9cef15a2f43ef615c0c8bdccfb6f27e15cea2202dcd42a79238209544ec6ea64e25f981c20b505802852b280c996077c9d1ff3f2f0ad252d6e9accbdaca3c8a5fce222fbb67c2f47a9ef423f28df36b3f3046dafa6eb38fa4afb6136438613bc1bb7b760ac91648984821114a99900213744899f09332e183eed6d2a9235974b7ab5347c023412957b3b558db3fadb2793dd696a3e30ff31f6f71e59db4a3dfd78771ceda54385a38bfd657eed5b7b1d936d54a95a385f3877918d22cbecd84bcc5b1625516c2e6383b3cc370aebc173fd3ff38e73047b1ff53d98a82b4f8bcf6ab31faf79b2c4eb90452cea5bb371cce8ff315f7e4cd9cb738cc396bf3a2202d1e4e980d6395f46b3dbf52cabbbb8f3a55c214a912bc94e0a58417522584d0bdbdac28488b97a1cdce71ceda668be39cb5150569f1b2ce3b637f1d93d76dcd33c97b1c5adbcd17a63e363da4af7a485599a7cf7d7ac5d7c3ca49da1769ee36d3c5067e60e0057fb3fcb0c00f277acdf6d8869dcf327e32d77967ccabed7cb4360b110e36ecd7f3a44284830d3f583f0fe7176f05a44f4a27290240011ba90fbe38e5d17d4a010aba4f789ceee83e9da0fb6447f7a98eee9309ba4f74744a04574a842a73749fe480624809ba4f24e83e8da0fb1447f7098e4e89102455003e4e6f749fd24021264912cf8fbfcf0af980900c2449e2537cdcab2fa4a885a420840041ce0862460729234807520400732dee2045002452dca3e2a90d37ba4f6d94c146f7698dee131aa734cc48a3fb8446f7e98cee9319a732ba4f6434be5b139da626ddc53ab3570841bc8507c79dd0749f44d07d12a3fb1482ee5318dd2710749f3ed07df240f7098ceed399ee5307ba4f5f749fbce83e75d17de2a2fbc481ee9399eed316dda70d749fb4386171ca40f7e98aee13064e173851d17db240f7698aee9314dda70a749fa2e83e51a0bb83f4098a510078f416ab142600330de2d9a55d4809a04a1afd038d14085fba614881c0ea3ee2d5e9d7f5fa113fbd39f50198f6d6a5a04c8c54cbe0839dcf64a659649032e2c6e6ad23de031909c35b46c8ac808c206504ca11fff2c5bdf7293efee58bdba8ec7d2f2f4cb7fffb85290d872ab3d58a92afd6c8cbd62679b299e4052795f2a27bac4b9976341c6743477c94742ac5c5a31329fca6bf325a1713ade7280885477b2be9282969c88f1c39924235816202b5430a554383af55136f79dfc457af148af32953a6749f16803a82eaa0bdd57daaa9220f481599a108d895bec46ed6cd8a0815c1491549922a72ea6e9b97cabd9567bff216f5a430a456646f442aa53c7040a73c78a594070040eae15152daa867a629be77ad576a61d365b632f956e6fba3bda39b90674bceda381ba50f59331738774175c47d36c9ecc269477bc71236bbcd9554479c83d9c27cb5aa47ceda58d52e85e10c7226ee82337125ef57d452a6a323df381ccecb309c4d948461fa6ac9abd3554eb38dca71a5d7620e93e1e82bcc216d2f0ec7591f9d3e78368b7c25a53e287b4d09d3db141b9bc929379b9438aa14332f05ccbf145537ec3d2943a2a0e9dea280a1aa284851866c36d7d2a6093e0c53d1affdea6881b1b570d86ceca76a715a6ba504a504507ae8eeed1d4f9912949eee1547a16cd083b10d336957432ed37a7ae812a6f520d126ffe9f1067aa2863235797245f7c6694f70e8ee272a3c4151d0adcd8fc5099ecd220e3bb9a27bb36e819c24609699fefd5eb2af3c2be04942e329d2786c783ad076d4d44b825d4843fa60ceb686fe5954bedde084e1fcffa60ca6ed84b193bb7b5b85b99bb3a19d219a8e189a8e153adc86241433b86dde42010a94cdd62fcf2e5b94349d117acba923278bde5c4747495ec282b6ef603e73bc9c10360ce66a45d711e92c4b9e8fa98cf5e2acde6ad2f00767089c8ab3e426cd0d99deae5d610ae6f9de6b0b6f3490a24997261b6c706c15d0b6233623d61eac2358441a6b489337abdc6df6551e6d540f9b9daff0043d0c5f757e68edb675f97db52aeb2c61e3df71c5e4f3ae6c38d533e1d787b0e9daea8715b8623151a39bc38f97fc88d090eb27682809694888cbd65ece319d984ec7164789fdf58830c6b1197efff76302f626eb1e8b4fabe59232b340782e796dc90da7d9a0e96e2badd930c0c6d578d36c44d05427505dd1bdd9e0bcb2b5499aca88123d948c5152454bb28796048c243889948d8391f8e5fde472709620cdcf38463167836164388eb35aea39694ba66e92b6dc7ea3aa58a5305618cecd7b8ce75656eb918f47f7500883c2909a139638e1483be1a6378ef330fcd8ca7bee699dbe72f98645b2bbb4f3309c4c479c6b487ed090b480248866821526bc4cd0d18edca11d91620b729b20d591b3361beef104e27c8e258c620aba54be557b1dd38f68b35139d7cad6b3adee557b61af7404cc11a423aaee234334afc3c374fba83952b756021f1b9ea0d54a40a39530462b61b444ad9510426b24dc514960d248586906681a6acd0038da082cd04618e3e53af3b5a348b45d4e13010cad007b680538815680309b56000e08a08646001c36f1f317021a2d841942d8b41082bc31002b06d08336801f800ad77ed4313d12801702d86103e10d1072d040388006c2900fbce8debc6a7fa39a11359a911d8c70468c689a1b9a7624a5c786b5d492961a4143c9d1dd9d4b4369c08656a48a4f2bd2a315e900c4a00520490bc04e0771681d54a175f0436f9c8dea458b5f986e0f66d239eec5392b0a4b03401f1a075e64ac11398348161cae9cb35aa233f1fafabf9f36448dee4da46f6508192122b746da1030cfe4bd36a44af7dfe9d668886ad336f880b641d6365041d3604d7743694d0333ddf861f9f1c6e1f299340d5a9a903b34211dd0845c4d88085a900e684182d082781ba6362aecd157f94c567e7cc3cf54a9676c13ceac122272db3d2c77ff7d7fff655f5f58034266d380cc8020694054ada4b51f7cf456426b3fd0b4f663cc8fdc3fae683f7af39e2b33fed14e66ba4b7b330da39db45308ad9d866c9ed666baff47eb1e3cfae240ad55e89e411d36aa195c3183b1378e9b414f77f7b5df7d990ceae8ee4d065714c9a00719207537d15006a8eeae318863c6a0013170d20796fdf7d5b2bef0f1ad5eeb521f52741f38741f487da85230e0a3bbdb6160040cf0043b123bfe25dfbcc764130d7dc2e00560746f2f20d3db0bc074f7f68213ca99f1fdaf89365d12cb5cad96cb236ab55c1e5d73b4a684351ff071061f45f0b1c4c74d683f7ba7925a8fb657dac34c776f2e50a37bfb261ace950ba80b90b840002dd840ac05a8ed57b04ae4dbb5ab6b495bfa473d24bfc74bbee931841e5ceba1c1192c58c295bdd2199b3f2b9063050f58c16b051ba860032aa8a139ce4b9857a79a356af45063841a22352174b75727f8e3dc96071279ac90470a29509302345200a6925f7df023f216572f097bc96c18fab59f8dca9352a00405685030a6bb311e55e0d1041e5af0408ac11d64ee58bae37402364e5045776ee2bdfd668fa98359666f8c15da9babed70f8798242435ea78ab3d9893cd33c6d08fb6c166f71e1cf0948b003cdcc0e9f36ad964be5b35987167584a9e3853a3ea8a4098a4c00021d5dd001860e4b0709741099030373c0e6282287171de66e3aa69cf859c755665fb479243d636cc74b8e1c98af95719ee725711daa530e28726825b0a3045794a00a091c4082144670c7089018413982d608348823037180bdb19ec244eb82676c474fe27f913c0a6741997bf3bd072a023b5274fcd92438c880c306389ac0e1c11b1878a3060eb75a3f87b6c995360b6d5cacd2e938cd166926902687345c7a2bc3772523a3af369c9ebf0fdd58c30d22dc206a438fe6b8d574d2969cd7ad8ddb866ff4d988820d19d8d0c2464ff71a5cac71c41a5dd648a286186a18a1c6170bcaa647e17e36ce82b234ba482388347cd228820606d0c8018d1d345867b47146196780f9ac57a7cf8d5e9aebdb7c4612335a60061933422ab032c6284301651891b10719479061858c13c650d3bd71ce1a23ca181ca039830607342d34216c1886458023c61b6268400c18c410410c2221a022043684a0ca38c3d076141379c6a458d6894beb95d2329c6130114652182b808005201003820f0446b4f840ec03401f18c1036a78c0011ee0ba7b8bc2656c913cca23790bbf678b6cce343fc6f96e0fcb24cc6919e232fc2c5652c25c5f6d9305e30360700186054384333138d3c49971db5c8967b3af3ccc719bfb74a50eb0d1011e3a20e50b3bbec0e28bd817385ed0e1051895c4a473ce2a43af3ebd1f1317675551d9bc18a10b3eba60a20b315d5ce1e205dded791ce7250c2f6d5e9d747af60b431f29f885e90b531c4e9176efbd38ab2b9cafbff6922307c8fc70202cc3dc71e00907bab75cc2ccc4d166ccf48f993066aa6025798ea235e35ba0d9bcdf824c39f32f7cec5ed01660675b95b650e56a6d5e85e106d8e80de31cce0eff0cf29959a0309c1d886795309c415a40400b2b5a10c9e28b2cbe6481021672746f9fc5f7f104723c9d9b5bb51373230571b5349733e4bcdf58218c44c2e285850d56e5fa6259bd6880070d3cd100930ca8e1d599bb39fb174767068e9871c50d574cb9e2471518f830f063c51d56bc61c50e5678f739ce59dbe49cc5dd7048be95a1cdce396b5569597fbc9c5d72747c81362e10810b80bd795f851155ccaa38a18a0fa8d0820a4a85150bf06181252cd0000bac30451f3dfe8aa3b271379c3f2633265f206de9dc5f25a42deb926f361cf7e2c41f8ede8a8c51983831f566ef55189d7df8d96fcad886f367d64ec71b98a957a7af9ef4e9553647a5ee16d2a9292a300597290c20c51a52e0b0d9fb5ec2c95dae9c592cc5a9025354a0865eb264c912df3e84d1596925abf5fca3320bfa8afb4b8e9c9730b7ad0318111925fd64d9ea52142888c289288c88a24a14abee8d0be7dfcffa174ef0615e6d38cbcff5450a3c41814a812850ac008a21a0a8ddaf5769a3c274da78a3aa680be75f10c4b34a18ce0ec4137cc2cc38fffe3cd1c31363ff3c51e5897ae404186eef7b61559713607aeb58d5a31d25959cf0f75c65fa28738538cb6c29e31c77adffb541dcd0f664e000cbb7e253be95f299769c903ee55b91d23d96f76c4706016442e86eeb35f1d9576a028ceede9a68a10915babb4e4e89e3fcb35c10e74acec48336d93791a9f84c98e9dec6f9a063255efec03e0b13464cf404b2e8ee4b76dd8c7e30125b9b74fdb3f892254b966cd36918522b13f06102364c20649599ce1c09928de93ed32c1af2713d9e2012eeaaadf30eb55a2ed1decf661f8fe8be57ef87e17bf495675f737dd9d7255030c753a60418bd29b1442bf18212276c25cce78f124192b8a2bbb759c29c4b224c12606f9d8d85e14f123748208106891e90181b892addbd4d1912218c5386c7a8e91e73457b132c1a036eae3123c4e8e74df008161c3166ab478c47b8116a9a658499eecd0830a5cd6e8496de8c18227ba322cc6cde73f59b868ae88116011651054f591137bdfd1431a4bb3b362b11b23722624ccfee613f4480dd4544a8d0dd3f440c1113871829c4e0d0a5182e0c7fc4a8bac50ca93f841add3d04193c04d84d34444f6f4384d0dd1250a3430990c91fbe2701a4eeedaf15cb3a691835132cc3d914c35cd1bd81617a905158186e8bd5303d42c4d1b359c45d2b8414bd559b1f07090146889b6e2fdbeabd38ab10438882a843290832f73f6b14c44c49102a00c107105280408c40a8b00131a484fd1047776f3f84e9eef903d8bdd9a8fc07157a1322f21f42c094c80730a6e8f2614c6fd407708b557aed47e4434fb70fa8d0de0874b17db5442b0e56b9d2229f1ef2e57dc634fc4c4199f89912f9e091ba562375899f89f048b34f965ca98ff899fa78443e3e01abbaa84452c8e7492c19a2190000000000f31000203820160b076492f980ccae0914000163b062b0549dcbb34c482a640c31440101000001101148da108c11ac4f5f249583ae3a210e028a7af38151dca409707a1fc9fc9af4f02cdc5d0b4ad4aa219eeccb1f3ba63028b4f9e92ad69da616b96796616fb6bc0ae2efe591cf2c33bd0a597e7794f2399d7552a69ecc093330b3482cbfe65a6484975331b7a447fcee9f8c1d7e32f7b7e58e47b92d4b68301822a33be015b308e86c7405fa5b8b0e830068cdfce6dbf7f101db4315f085d78d6f470b74b23b0a5cf5b0ee8258f1c46fc3ef42938bf9c9f72eb272a8f9a689e5725e0842b9d78874bda3e0d94691c4a99e977da1850209790b82a82122ed7de3405e4a1a0ec99b5a4dfbb1b89ee13ad712ae99fecf3d39f0dd84b1de5455beba5a9fb360bfd1f87c5f5359be2be9d65fdc4db666248695c6a0645dbfb07a8a33cae52923829f13d98a978970cb233034beec85cb2a4750b52d9b998f7bcf8071272cebbd696ec85ef3c7650c371d6b38dd5a3edbc0a1938aa0c40193023d71e92d12bb5c45b329947a2fe1b479c343abbc5620bbb9b1153b929a2b3753da84cfa06ed381ecf69902dc55ed2a9651d50aa83dc121e904463ab2e26c9c3f8391d40b61aad290c937f053e9307b27f0d86a8684a75b620e3ae95c71e3c21c812532d6eae005d9143c44ceae21f3b6f6331c31cfa1504b0ca65ff7a3ad9bb480966803839427386103e54d2016fe052d0570d466cf7cf3e2c16b2ea151e957a69e6aab44339e19c42a77942fb65736c7c88e7880acab9866c225586002f40431ccb4beb0c1bc4ed1d899b027fa43bcc0b121ac67211f39a3e6591a229c9ad883afda80eacdc67d066d8155d6b96e04ff6e10bfa1737b45f371bc8043980eee5bc9d4b0cc1e872d5c8792fc7268269d74355ec2583be89e59e2e31d15f7edb21e5ff254d66267eeb0e5a17b2e3c989e0a9c3a940999176a2ef81439a2f7159ca6a35b9240adc164bce6f58aabfd4559bb09158f47fa0aacf1f6f9f3319f046553d94adb13c8c0949f8920ad63179d06b877125618ce869bbb0df994132d9e4ad5eba2e452b4c535abd9a3efdd96ca61dd9332a0c4bb53ce4b0036583f576ec3e2344db64945cc737538737c8b953d69e88545e585e9606bc7c1e300b694ee4952a8aba865eda5c16f38fe1a44a70d9c951b9be963a9a1a04abb1f9fc6304d0fe575072d0370441fb484fcc1f41011db4515a908d6697b900be4692a8e0f569e866c56efbec6b965e98075e74235a4ec2d6df6d3b5fc9cea137f0c16a5e233719cccbac6c21c68b4a7689bda7a6364ae488eca2d183cdbd0486c3e08b110ed9f4a3d7b4a47fa4d5d9a1cccdd886832971edb0d6c2d8132e1f5ec21dcdc8f65429ad5f239d1d96e67db6ba74bf23bff587f524e20eefbac8d4be1dc21e8eed209e6185aca2525fe590d77379ba69acc1febc27de1c7fa97d0d8d7d4c718f0171fd45b1d92d09567e8740ac810a068eb166d5ab99a24d8dff0bfc68d2ba32ac4521f226e82088a22d2b5ff2b49aaa1e51a395abe8a8bfd2820f44c48e5680efbfad7a2f373280816c4c66ec533d0992f683066135db1f15fc53681715437e94e46c3f80fe858e370a9e9be1538c09f6c0f669f1c1e238e67ea83dfd858e5626664c48f2ebec3f912879a1d33feaf75313accdbaf1d3e839d5045890d4e63720dd83875507fb45fbd184a30285423272254195f337370f82c8d47232a1ae3cf0fc98603d0e3a43bbbc1eec65dfdd3885c1a340c4ac65bb4667e4699bc16c9e80903869ca238e88f058da97800c65db602cafefedb661686bbb3027505c306228d55f2e827146427b2b811303b1e3a1ed92bfe8d9148f632d57316b8c705008396a84179eabbe5cda956a8ee295edc128f53025cecb5f33588249d7ed66bab46108c1c30b034d52d6debb2fc54a7998852b1c0b9e9a2fe14b6505784e03b9325e868d7fbf6ec4cb919954bc6a583e1236a8dbc40d43bfed718fd5abeebef01ca4d192aa29bf33efe5cc5531913c41fcbeffef51843b923105e5aa21837ebb0c6e688f6a42515fb32d5e03c82dfc17607ed8975c85bc4228f3a079e1cccc68e5d62ed85a9873b7f833b15abea915bc601b3010ccf1d8b1750cb9920942c0af136152c81019340c925b4b8e7972f734f042a4a767857320d3cc34891c9df3b0d04a20db63c84cadccd482d27fab4fa281775488b64c9ed6c7b18e80c68528615063d6f077bcc3b9df511ee498ded3a345270b5b2752cc50c53dbfbd81a68f9906fad2a1f9cd627b49fac50199a2e05ddc468646f2b8d3c8c62e1d130acbb1c421f616d7857f741fce1f7c6df6c0f755bc253fc6bf1fb2d89512372641a00d8e331638459637eae44286780ab28280216ec88ee612d43f572a5302515adb49e4d1f0b691f6471bdfffd277aed39026eb8b4010185e243f18b345cab04c57df6a74993f5bd3a924ec75169e35105a40041ec608e3b5cd535a43abe6d2c903aa63a65c4aef6c8ad9fcc7e7acb5966ab6c5cbdbd9c94781982a9be41450dff4c79f0a2977b95643e6418151b62273bd1602cff6ce2d8c5dbf4cdcfd4d9b3ee6a0dddb28c77494abfadc9fddcab48a8a40d0453f94aa03b90e3a9a2435f5bb0ad5c9a1011c900704c42e89a9419029614b72ee8bd75543f541c2d099d4423ffe932ea58c45631fe0cd77a1494403b24343ffbea28d97c0c5ab2efb50577f207daf4c12d9e73d9b9990dce7323408f1e51779ee494ac9209ea15ce62490298904c3841fd7c7f421f18ffbb9bf1767972287e167f1382ac728a2ce371e43d0b8fd954ae624a0a9efcd5ae2b60d4732102502ca616deb76b565f7160939f1b0ab9df783a2850bd8043395e65c2198a29b4f4526db01b1560cced9a935597912b2e6380c0a71c801b48f9b9995ae5f9a3637ca1c122b36269eb05b929893758e46fb41560974f1ff74a9cb6ee5212199192d5a3f33b2e7b0083d84b3961b712478f00f35c8631e4c434ef0ebd1e80c79ee3fe76dd5c259b6818ff66364e8f6645a3b5470466ac2e0776b909545e74ca35349947e4ab860affd9cf8671841dd3cfb9c15cc73abbf8036e2ffe68f1f34faf9c5599f5f590b661fb7164935b847ec4bbf68203f4675c36c572b877e9bc2e08bc934eeb7c505296c9f60787d19c7e64b1c3fb357f63ad4ff2a7115e57a67ecc78ac8e4cd22bc3ee52dd44fe9cf296d2874302dfb9b98d20a435690ddf6c8757a68b0523c032d11c6cc5eede5eff1c1830629adc7132a760e0d8f48050bc96bd3f6ab275d004204ed56e155d1d48dd06f591aa16652f0d57861a701d18ee4d7579e0b210f553a6cfd0e71955aef430a13795615311ce59f3df75839d2c5ead7f5d6a8ecfb42f224cd38ab495ed1bde22fea7d8b771da32dbb323008e88dbd3978d86781dfc78bc8d02d9822ada0f1e9bb83c50bc6ff7ac3c7ee7c106ba2fc8c06c5992dc5ba0c98ebf0a018f849ec4649b0bd73dd8a59c3d8a6b4d1f86bf5dcbfebf23b9df9f182bbffa3e25ec02fefa9059bf1177b96fb08ccbd9c9e11577e1103412395210685e01be1bf36afdd7c6a105c3a1a51f68ed6dfd5035646b8711369787ab6ba91530486af5f2f22a132a0062013f863f8984ccd60f2fbcc8a6a64751abe0ba306742876553bab1878de85bf859cb8abf4e89e6f4692a4825a04e19cd84ad4949376cc43d59b2787bfe739ac67d7b6278831a3ba799a17109e62c96bdccba2cf65727bbbfb9995db548161c61a4496268d800a745be058a3fb50c13c9633b64258b3bba3e81cf2e2980489dcce47773aafe6ddcb25de2b43b904211eb45c22e8fb8e5ca519b16537435557da6fbecd2305ed0f0b26f5e3d800f03efb568ffd53e46fc2c426c4564ee84cd5522e3245cc921ef8cb5fc867c22fd5fef52f83fd0bd59244dce01c7174889c616c870ffffe820f7e5e11f9a7f1209fb7cbb03b78dc882934a9bc1976a0b84a8eebe03ddd15751c5b31d9ea2a2aa55f78370bdb6eb4be0eed7cf3db8eff1d431767376f135a4bf13eb7f3f6d714ed4bafbc98339103bdac8ba8d2909b2599828aaeeb430c9255acce9e3eb31734a5060633f7d8b73b91746f0fe9bfd729e8a43e779a68d8e2ef4940f9ed037cf7373b256c1bb43fecef7abc9a9b4a49b1f99b8ce3369b909bf0b61ffab29ebaf3c3021896033c98484ac95fac711c07d1af087fd068bad79d937e82027fa592f10344349559b5d05247cccefce54e94df28fe107dcf5512644a1cc4dee6e6aa7901b054f6c81098d7616dc0c017c63ea6ba996686eeaff7fffbe8b11f089646ca3cd9f3ec41b8184ff9a30028c8cfc036bac8ca4239ed777119e08dc646d31375e09574a267319f083870f7589775024933fd7ea6a49fcc5b7b92f0837aff6ff0653509282d4c4d0b9854c6f840efa72ff4cefbcd89a2290fb18687863ccec1020395049c7ce4ab332585c20439c7f49d8abd5e79d72f75818bf0dc270381aeb8bd6be5b55758d1bf6520c068fe18ed351a54911d93ee0682623b1864f966ac13ecc77cc33e3f6a4c8ca847d9edc0763fc80fcfb437cc179c07fd1e76d8c4bc2fa1cd12c62bac6f57ddd2dfd9eb11747d0331615f45495f9731baa47c6b7fea0a37c41ec10596f32e6937dba5f06b2d28bd6c5d64f46deb486307320e4615dd7e9a149065eca479698031076293daa734179a6ee75b65c6f2bb2ddaa3ffec4e7cc9ec3bf651dd30a679c22059a3e555b55034a2e283ca6baa32f6498afb80a5f5c1865350fda6a1713ca0bc7ce39975d7b106d2412d3ec3abc8c354ad618519c83f9cccc668838a0b464b740911d282cddeed2509a8edc8d58bcc7ac243aa1f8b0e98dc1be6f08a5bee457b4e65e26146b38347e621307c1eaec1363b807cd3537438f11cd3667a03a42bc8431eadf7cfeafdd3f2db22ef2d2a5e7f3d343e7ac01b24fd766971eaec4534fee4eb207d5b5f0d2bae7797e209952c3bd5171d905691d3f662c9e36c88230095b7f48d6a3be9da030ae10c29e00959229efb52c8aa1d9ee42f3eb027e50786a0afbbc8d644b3774ee5ff8721ed804ef889a9d85a6062a912a0ae28bae230c6672383b588de7bc7485b80cb35d59a5f197f23cf52afdce24b5ad2cf8c1e8643febd066ac8d24efe388e1025231bcbc37b786e62f6e29999049be9dc7294a2e48352e1593b67f2c12f1cc7c5f780d68416cf1467eddfc732d4583cddb3374ff05de19f9b5e747959ea947ac5bcf381352ae08cd43de087d1d5507ee951dc8de3463d68fb740c7323345813b2e5ff4a5f185cd4168e60014ce157220025da313ac7dcf1e5cd5df5872535e5ec64fafad59eab5e607fe8f4bf48088e620c522091f1eb168a65962aa7ce815134ac343dd40be5a14212bd08530da77f3315cf370710e11a317fd1c55e892108e61259f04200f2471d6fa4eb951babffbe0185be9869014911e8c455029e3e7865fc49f8f6b765507ee4fffb3607a1640d20ab5e6feba625d9afc1d70d7078e709af09a8f6a6ed4b7a653b0349208f785f138ac8de8542fd25f198e0106a99f77c82db2866c9b3897dfde21f11a3d8cd680f08b4129aaf5585bc2380809803fe775b0d613a487cfecc3d195e61e5e9208103f39e3d1c92b224db8be46a705f700dea62199bb363b110610621581a7da89e9b8230e4feee5217fc7e61bf5b6c721cc681e1843a5eec5ea700f7a2d08353e4ee7756759a4d137e2123cf0fc2f17a53ae2a32f8861da5597431e0a727db7336bd81b0af2ad03dc06252d0f6e51e2aff56413eb20e52bbdd875b43ced8d25a9eb31638bd5dba9f401d0e08a4f0f2984ec20833499b58c52f3f095db324904a6a4a426921d785d4b20c439b06c25eda6736491a1d35884a1ccc915db09ac78bf5d12e0c186a972374e33abaff9cd2700ac968e99c68cc129a5de2c07f2206b9559150c133b373c88cdfc738ffc265c1073dab4267962b73e3e27185c768a81c2aca97284e972158f83f8bc11fc7eb1bb62ca957b8151a2c358f045a74708319cdd4f81d467d2ad3a845184e32fe11b0f96d1e71caa6ab02fd7bacb82da1a9f7d62cb82fbfc656cb8f6b57382ea70aaaa4a3d9a397549a0275cd5cc1379e95c8b3436d9815ad0c8161f52be67ba8521db1d2439de3b6082ad243d08a3cb6944498f95d01f6ca853273712dbe7350d06634369cfdc274e58dbb8bbaa7fff19b4be1ead3a9c3ede48f18994324f5db334744f98b877e90735cce51b293a6217bf4e530f944f78c8ca72b2375b5f7ed0a326353b6d9c87954f462854bf90e44110cf8c58053bc00c363cf9c93ca3e604cacea4a49d8a292ee62d2679ea0ae698b1119eb3d41f5bf851bb9bb4a3dfc5fdf478d55c5e929061360b7bfa543b4fdb4c528bf917a39ecc043698149badc44d7f6c8ea3e204367259ce7674b5f0feea813354ad2fc70a97da6031fbcbd63f5cea71283f75feab657c36f00f32d21f81807f032718ab3833750c969fffcb4bedab8588cd431210edd86b141d3ef03a7469ffdb2f12d18932554e08d1b30baf144dc154303af736e1a28dbb6dfa1d86d4428f240c37d832eb2f81ac3082336ef8919fe6e5e86f30f6e39250bd34aceeb240ccd3ae06fabf2f6d1af3186eaa1fc2b879543f11d6097ad8259f12927ee2ac9d09d6bb4c3a9c6151fe549d4c26df468bca4143247302f396a35bb23f70aaeb06ca0d316de3df09c9612bfce610a5242aaaee9fb77b790a9f568c60a4e6df771e47ceca646915c1484aa1906e9604d1d11def900054a57988a085931ed7ecdade097e8166ab16ff766a7c21a180242153cc0cb7d25ff8ede000b1d17ced88ceb97bdceb3130a6d9bcc6a44c0ec596a07311cc27ea913ec8d87c20ae38e68f347c0508cbe5b84c54986efccadbf8d93c1db00eb31a7dcc7239758b3ebec28edcaef3b17c9691ad5f06c57ec734f20fd2478a3c121c76bcd4e206d1b1564e2b1f46db21855d5ac8261f39d01f22e864229e3607e5d87ffa90f47bea699a3370e75c4dbcaa77b48abeab045594ba86233bc71448e183b0a026ca331902d53aff442b2ea2559625253aae4ef9446c7ef05b7fa293ecd69bfb3c3777ace21e5c9b0701fa350cfc3e0bf7dddd7819dfe6ec869ccda2b8323f679c262402e146336b00bce70011d7d92aa22daa57709828a9f8c604c2f846023bca2de4a7cbf4359e8a91bb36c3367c2b1c0dee5b1a3dda470e5a7e7154402fd1d566a3297a7b2966c5eba7c8ce0c91fd36f10c8766d5a6e9e4d6316c93e2a364a9924b0a143382ac4e6a3548501ee66f2ed0c0c17e78579e3950fc86da5a5b9174a69a1497783fc4ca4fdc0f8da6ef1f01e818deec83aa85ec628b77eef11837118331dcfc42225d65dcb5f6af857438492ce05b6e59e9c927c2be7b216022d1df5332f2f0a2859f88bb1a8efa4fb3ea58a97c9c76e15264e82735101148cd069a0c47aee341bd22a0e976a9d1446b0c050c2c3190415ede81e37d805c909cc8ddb51c08576c843b5d81cca86ffab165e1e3a110fd83ef046c0ed4328224513a1b7467857f9910bc26b60630d717e8e6fd9a3f87c468eeebea3c69ab067d3b270a94347289ec93cc18a3b2932c3677bc5e44c9a758388cb58975ede1cdfa60fe781111544461d9d4b061fea762e365c435b2242bb7f0aad38f3725fe163af7269dab7d05c79e2572ba18544ef9c566ae3c7fff4769676f0948571de78ef06ee0d98fdadb16da5076621da3cfce7426da3b95fbe622ccafc4a8243c1dfbe658df817e50bcabfc1690854da06a56cc67e3e5192ee0c00eeb80ca2aa4ca04152c2aad15868ff62ff1a58b9cf605798b324b136edbe822bea3a58f7ef587941247acd08ebd2e1bfcc8ad70cfadd57233ed35b12c8807bf44ad53ae0535ea175ecf398b166cacf9904b0684252e30ea64166ac6b7fd402ce5034c0df5b0612de631b5d6cd3dc0b4c8ac8794b3a34754a2cf99d7634115b62f5a34e3d09221a14ce0554cc6e4393978bdbdee18e6bf3dd8b2b46f2281bdef35db02587e52e519423e0dffe5907ce6d10f9fd509858a0143058a1a83b1218100d87ae163e029538c4608daef5180731352bc07203de5ee265c14bb21f3505dc9e7589b580ccba78c7ad9027936c19e8a495ffcb48f3b311fb1db09e78c1a50ff128b7c1f8b668fe5c5722c64906dcb499b19878cb14b996429f01c32917a270ba5aabbcba758d00f9ac354e59ac8d4a26bbe9cce39082d17aceb84b5fb21b75715b5403e2ef0277c332548093df854b15839e3d4f7e303d251b03efac99d9c9de39941c2d395a07576d2ba2160b581a46a88a6cfa59280f49811a363a3bc85d3c49e2a2a3bc39ce57f60d0ff9cc0b3f167c74f7cea6eecaa891d2acb5f80170913bfb1ae61c2e5adb5a2347ceaeb7660b929882f8445fbf1f9ee7919e60c8b743e1ba31bbf7841bf3a0c769316938dd94423b348d646a3857d05120308a7e238b53c5cf1759eb2b777e3a9844997503d505321fc862a0f26a7097120587671ca859a53dbe8c5a1fb24346e92cc732816ecf9391632c4e11b1260bb15ce1c5d2b66c58e64b3bed97ddafa87a9a11a62a6a97218d0ccddeff63c80d4c3684c516adfa3cdd3565e1fcb35d1c246145c6c0efbeda767e8c9c68451238a0ee346d99d36d1804ecdc887522cd792031b67e07b1daabc0b2e2b59b56d11dfb135a704763d1afe397213ce47795348f193a37cc4cfacb7fd0984f5e22ad59733cd8255ce00c1cbc5151f05f6d7cecdfd149ac7d4eb473459efcd5a337d7bba9172111677399ff2f3d21cda1ee8b57c5db7a2e1f5ec63d0405a8c662ea296328d30a5f1a6cd25972892ad7671600c0fd8e04258f9bbc0ef4578b750755e783bb2e05a40a5406fd7235ccceddc0316d300ab962a3ede3aadaa20accb655900b1dc68cfecb51d6d5e71eeff350d94108823eac49e47b520bb8277a36a288392abdc6c4720a60e308c16b3bf1075052c625425939d57b8861a395a43873fb7391d38c98e107281e9734ced12804f20d88fb7e1df64cdb5540e70e96004e05782a74901280a71b4364c96740aed0afb2a69db4b88d0989548953095c63f73b766ad92dd35c9bbdcf0b2a41fec7e3a1955d6f5f4b024a04d65146b4c2e68b9116868d3944f1b5bf1448a85f4190b794bdff28e9981cdd2dbac2225a295ccf353c0dea56fcde4699292fcf00154f12393e2ddaeb1547d2f8c4e4e6e38539539f284e9ddb127b72442b2d9031b09b20fe0e9226a9d38f464588dd4fc78701c44d688cea0e931f9387e188798bb9e2cc9b718b4a9e7c589fee4efb94359eacec117ae8d7f17bd2f67b6efbce747958b2b2b3cc523e1ab3073cbcfba1c2145ac4b70f2cd951f7b81f7b72fecf9715719bc815a7e7134b7634c70fe5a272a2bb7c89db9c59a6812e74290fb791259e7c87aeb1375a92fc775e9bb899ee668ff31d8b6623dcbeb0abc51e6c2058957961b991176e834b8db0296c1f61ca64ec134ffcb51a64751e0cc227e021c4d958b11bce9e6ab6725636e03b6dcb586dc07293dc33757c0d17e113c700a5cb25436f618f472c5e675c234afcad23c5ee3dd277fde8e6fd3aa0f97f38cdbf87e73442f23b718d5448ebb17f1b34f184541424ab23689b9b83e2ed9b5e19ba65fe7113d86875b8be18be5e601a850cd235ea5a6f587e7b44877699b16332982844e6b459bfb932bbd3300855aed05843455c101e826cd8ca8fedc504a1c05d7828a5b57f3bea188addf185571adb5dea7d143aba4634ef2ff4800521f648547ab778ac47b01ebd5f7d976a2fb87fb13f9bd5c31ddadd74e39d30d377b1c176ff3d98c77af06628b817e39007c6b975a426f99d1dee1363abd68c22efdca992e4ed15541e01a4ab787b7f03da206bd3f6b1948c07f939030e2478f0b76d97bf50378cd51121df04e8a59425ca56eb3ce4a5dccb2ddfbfd5695f9af849290888650f3284d871a34318fd0d8c032dc5addc51484f43aff9fbbe9eb475ab18f6e17599b83c707bf28303d63c15024701b8f58f93f0a260acf7b4890efda6140ab9c9450430d021d78e1d7d14eb3fb1c3af2c655abd7a476ab16ea32173bbaa77dc471ae26a8e148d03b2f523b2e92ae876a304f08b962a87b6cb60caf45f9a22adb07996f59fc887b43f564ff96a070b7d373403b3f25619ce1b879cb7daa47a732a1f3aa54821702e4d514ca69ad8c585e94fd8355c16cc57d417ae8e4793cb756f626c3fdecea7dab7ea218e3b530d23101fdcf9fbbafbaf5674b9f3fa6267eda3b63326baa25905492a092e378b1e3cfb223a2ef1f6b0002ee36973ce60c63b14c70f07863938230dc7c2ba1cf9da5ea9de3755e34ee697eddbfed55e57df7301dc1cf41962f0168ef14f17545cc6ced7b56a44ed7904f62580075d586dd4a3c750927de60486b3142e4ea465f0e2ca6daf2c13db0619db9be359f044599389c71c2edb18bd0b9ba088f19e3939d4f5a9877e89bff683d7f824d3ad079f6caa389e8357149aed6dda48ae4221a034de71890bf2f0addff612f76561fdab73ef6fa4d26bfae43d7fa23f5ddbe3dc77d9a00e586435a2346447479aca131f82236755c772d474cdccd254db12de54f453c735bbd230d603afe443aa95ab5ecd07ab4204674b1f8f314f825cc4ee03127254599f4375eb89f76f853ad63c21cf7b785889a618da3df44d01725a7fdb5674893e5ba71f85007343cc1a5619228baae65e3f46c33fb8951f6a221fe08412e78b1bc7a3424fb5005108397b157eebe90867d09a11b6c76695b01d10a0de5a187aaed05e51aabbf7c231a7acf85d146407bde8bb9a9f1378c3192c91c2bf4a3d267ce80f48bccafd5e9f1ac97a38b979bb8d34a0a9487492f8e2706e0fab80d8847e8d379aea0643e9f51eed477292294f8ba9fc6cf362483a73588be6c47ca35ec6e5586af944748c2cdf10156ef04f7da6056fa476f0c87a9bdf6cb31999e6f50591ebff6404c57087a410835680642619a851c331b736bc61d219c9318a707ed748f9e6b67a954e71b2e069326ce246e0ecaa6090bacbc5f50b13af7f5a0229455d8d023e9f26158fa8ee2abaeb003659f2d976c0779a11666a52a42b21986ca01460bb0c23e746328c1a19235ab8ee1a8af208cd2ca2dcea42157e393fab7244d5be61e678d06675b6bb38ccec82067e78001b07a771e017c1689e9345d2a970859803ca8f3dd24dca8fa6e40f174632995d622a93ea9eabd17f5637d6055a633f7003eac3e50c5ada84ed9105f4e6155d0ddbba56bdd11f94ce953ace0a9b0feaecb209b343afba40c01e9fd448910465c1c7dc8af0294d17cd4589839af24962d72d59890919f9fbcdadae2e20919f3134f64e0d624ca3b11005b6d8bf320f79a625c8ae895a59267737921d21fbae38ab8f1f41eb54d1466f6e008bb6c4494d3a5e5913f1ac524d8f99ce7669a959e16ed1cd93c831bb8f86599f09c0dd8232ecc39a1b3401c494490c21a1c64c4341a8cef3a2ef7c816170dd416bd64be230f0e6fb576aecedb829631e2df125f6b912f809a3fed8651f3e89e3c90a887b4954780e78b611cf5835338c97c5310ac380743426f453d853fc9f51321b9afce8c0c65ad700e4f72163e25249e70208451d4b3849c925cd221803627813475b89baabb6126682955e340ca2485dffdbe2c8cecac08fe49a28b41739a8defa0d5729393fb6c86b907940d8550eb4c39315d088d6943c51955fb4fb0433181a77081236ff7af377389cb98641e06c604c0a680bc731ccf0942723b0556dc622469c9161c94711f2ab11cda2f0bd5b8b405f6a63603cddba59bd8808c69a46a7d6007facfa37b0c5ad923cd6eb5c0b6c279246ec6d5fbf4442cb7fa60a42bae25b687275e52c92e4d3ce8bc2d9f4c1bf00306b1f3fa0fa4f23e725be7976d86475bef29d43f20e54b7d809aff7879ff5073e87a97a5fdeff52b7ee4c4f01850c399eaa9c2b733e7109d8661f4885a2330c9e1e98cf7a7baa30a45efb54ca9bf34f68e14dc4e39291d3329e5da4fe5657fb060ba0a6ea3fc375c0b48937bd7867dd34722ca30dac3d153fb1d085199050c6c75c07aa77252352b65c15a90cd96da31bb6b9bc0e9d7c30c4fdd018f03ff57ceaa13811a5c4c27182b8eb83fc51e93d25d38e842ecf7514a6b7ca9b1cdf0e79cbffa41e6a5fab79d380360d9a4a77128c88374ec25b34ee464d14d44b9df242a3f78541182a975fedd8ac691806070601cf860fb54c5bf968d7b4a19c1d8fe19960ea06b8a7bd6307432a4e737c343e88b55de057580c1b1d6798a0bff8b92e34cff9ba1eb86f846e8d6a0167ff07d73af7c892bd14cdb9d9dd233a5314757074134dacf66910717b5be09791f1cafb6774fbb5ccb87b3e59d3882f75d4e05419d909202efb691ce01fe154b879fb917c4d503e998b1c4dcd2cc66938709881d442b6e47aea9ef8d23126b57dfa3c3a330eba255f10f44195bf25b2e04a4512e9083e1e7bf30d7f395b623476765b2fdf6b610b62972d962af4575f519376568e232f78fccce6c7f00dd14c16600420831bdb9366610062a0f1bbcfc473b8d7f2c1f61355bc9a81e43d636ad1ad0972c76b503f8d247c1adf7cec9f989bcb8c8c9784851bb0322c701a91e055d84c64dfb5394bf460e711023acf0a9e6281b1a28ad9b80c329cab02009083e8b5312519c8d3b641d7a8c48ab568931f487f5658a8f8ae8eca1f4d15541ac1f1a24c668e0182555e0c25c587e8fa0b1ab101c796e5d0139f78ec4025f303b50871ad39a333d980cca159837483f6007af43bf300eeee6d8b00541af74d1926221d8e83feea238c282ff1e67848fda173affcb7371341510ae45247c16eb83b8e26d714bd62e68ee9c3805b6e1c1108c6727625777aa52056db65ee18ab2150277f3b0e1357745695337046ce2dd7b71bfb94201df1acf26a742c0cbe68e7118179bd4e6a131a9f95192d951baeb0571e49d4c1aa1ff416eb21578f484860d22bac69c034592ba692d8cc2cd38071afe859fdd6a7c30e1093d5d3de66e1b374c01e98ecf66ea60eb164a100bd06ed439cb9f275d72ed85b277b4e066d0ac7ac6a31c33b8d03294ac5c285967773e0cc49804c68a15ac93320a02fb936501734b7a711160c5c2731848f054028ba65d425a3e5ad0c6be9b35733feac05d70dc37f3d3658a4777a41df9617a2ed5f7724f40b90941710422b8791857583b9d54d96fc4ea687cee128d2dd53c96785d36b785f7fb02f6e602d60e0ac706cb664b79de60fa2b020f30cf09e11735d08a1f01d180b64ca64d249af95f11b9a161f1e3b493c32f2cf299e2c401c6a826ad038f3b0e908f9ff9e4d7ef15b32fe2eba564c08b6d25b2a7e128ac1ffaf3dc0a267a82a614d4c35dbb562f950dee28e8ab491b37ff5490a24c39fbd0c08cd711c63483b43b6c48c16c9f3e9987fefbf72cf14977e75a7cf29db7251086ec90bd0d9f3a4aea3ac6580313a1ad0dd57e5d1d2077a2754e82cc8f96e2f0d59395c66a65f6aa940d4e07a7d3d0e8864f7c72383b65269d00a73b5c27dc8258b8de672dba51a3cc9c78aa1cb5cade310fd113654bcb2b7e50f97605091663889c625ff8be34c055fe20d32384dc0d2f6b460479ada7c4e05834f244c09a40907f014dac651b05766ba59fa1da5b2a9340f5f07123da2c6fce628550a04d1b1ce41b2ef210f156621338f8bab341f9e019d1a636f67976ddcfdbf56d0fd66c5488857ede4b19d3d304bc0fc970ae16a8721f68d2c2ef23d78ed7dcd41d4005ffc22bc08cbe9a9c87e1a828f4c5ecae26d15aa4a6785cc533dcf9f0235ecfb247e19093de3a076b7a72b5abd3a957e2c7e1ab86688a9eb33dd268bc65fec8612bb0c29c48f8199782a88ea26c35249edb09025fb1e3fcf740cfa2ef6ce0a146320b7c5b8ff249bbe3d529ba2cde784fdb8a50044d426a7e456bd8978fcb4d1790ec024a973d8c2bc4443e33aa4a003bb09a3115d6a053ca453e9496c452d3925d91005e4ade90f0a53c0709464cb8252660705b0bd6a2b96f82f992012e8fd7858074ae3da39d557c1d8555606fd0b0906ddae178c16abe6976a72a49ef386585fc2c813f34b9e763ffaf1bde5bbaff66a32558caa0805d6699c4cda83e0cd4610d95def84434f25438ca9569724162bb00ec2a2fd48a54344c2b9aabe913ff28204e9edbb83231c1df81a2cd3520811515a92cddf8375d67213030a2d6d958bc335864c2220b3b201a22ce6c99cd604df35d559dd1ca7577d8c070de839c815c69b3bf701cdeb8d7b6606ebe605171a6b4b716b98ffd45f957d20ae6210899df8cb90c29d7b03182811ac8fbf9323bf110e48c408fde4e0c07be3f0003b369fadb8463d6cc3666f91b8e6b2c2b14784ebe27e805e9180ab976fb17f269474ed829d3aa1efd24149783abd35d340ed732c48ab4700d8dd59c85518f5cc82d1b26b13d3238235e2f39c955c8612666fc2a7e3a8281c6d23dfbab853a3109a3b5d760200d48392d6a9e4b16fb3e9d78b820e49c7a207a428c4cacd5a0952f12af74e27a5ad818cf4800c71e3660058da33175c030dcee671fef328c729c413bf1d1bdeb3833f7612c66103b9ca48c6dd1fd74ef0cae2560b3e8e08942f1cd01da3c4493973815570ba7937fe307e055689ae0434729698bf81fdd140a2a3e7b94e95693b15a201528d504d89452f5600f362ead2ba44efe64cb84048edccbd3290da4902fd4fb5c1b10b552d8f01b531b26bfca5c6d819f32481178cf4d45323266726860eb34b29a9bed98a9b9181b962a5deb87e0f787dc7d63c638107881d1f1465c2d72c5d9e7bc75e2585ef351b3395022cd0aad6e8c7ff01c07d3d0524880eed357d3bd094c89ce02ca31034e5513bef4b772e9ddf4bb79b8ebccd38b64f04a15a8efb068ecfac11b88aa4065eb169472f96af61f2eb74ca33b662637287dc3c90ebf90c77b80c7a3870885a4ede484b753758f30121c3a8a7bae36b36413792b5eb679fd5a8bac79924293d2102045b9ceb1b2eecf8b30b3717ad43c3dcb3297abf41cc9ee08cca169d6158b277f01dd2931bf2fddf80ea8090be29af7fe0830732f646b72490f463c4cb48c172f8bf130a2f2c955649ed157cf4b2ce0c2bdf54a9f702089ac549fd367ebadfabb40be2c0afc667e453e7da598ef496af8919a6ff82ca7016c1673d19ba47efbd65fbe024451e0186f1cc3b1d455ba49a61ecc260d2107bfbfc84ea3fbb1997bd463e0a3df4c116ede742917e8f012daa019e5087d16f1c2927556682de6ef00a7eb8e7c4bf05139d83024c80803f8258f775a7e0a27e783eb759bbc9340937cf1c9110ea7ed279c08576a1b0c007e16de27dbe869b0be7bdc698a6a449b2b8bd0fbedcdc0aba6b81a9c27a5c365246a6d8acb3ad5c5abadc2811d0852477800d1ae2e450d15d63173547131cdc55b46b076e7661e8e46a6002160d97fefa49801b5f03bcf56a417203b3b348295119eb2763318ac58a51f8897190caf1fb049d154b5219b5191180865e5f6b4c2d7c6be30101a1655b63fbb1980c7d4709d0e72a6282e762dadcb416f49ea8f4672ce92bd2e4c2158379aec0f47f716abb13a3bdb807fae178dffb7e0385448173d6d6a9698629d7033c6d9dd8b68b48f7ef26c00f55881f762e21de47755d356e37707db4050fbcd19b6177d026e66dcf4cd1f2f0685c34b8f3b3cc7a6e78f6a1618869a06214f2febcb44e82e5148c67756a7b13abac9bb526ef56d0c17453fdc8102bb6ad1e0b4dde58db8f56c4e48b02adb1eb56d2d4819f9426872a72360b30ec4e27f59e7d84508a5e728e279ebdac297a7d012eec331c4926ee6dff6fd36266ca8acc111dbe7a6d1fb5e188b6a27c215240c81093e498290c1b40779cc2de89dd10d9f4b089160e26e5010c5f22b0b4070a6972be7f099d6ad28efbb97ed9b000b97ec8d4efa45e77e9d161f09744c6c93a011c18dfc93bcf5dcc941c46a015b3dae031aaa328610ab1148bcb9debca0c52871e59269d917eb226f804f41a8a8c68ff7210b7e97d9b963dd0deebe54d9c3feee00885699f976ce7c4fd48bdf9a759fecfc05f0dd8a72c014bbdb8a9a241d18c039a3838648c97f1bcc4b442d3312edd427e7432bff4322e3a43455599bcf93882b3a63662fc02b621edc390100e9b60667e6fcb1f665b0a965ffc88f1dafa779f86ac619cdd7a4841fc23c32e40cbf9e0d4563ce1e8db8f8ad8adaed0706a67abfcf9c1c6a0fd60b3de7fbe6d126f1fb3795ff14a73a0c5c639f9c1743cbda66743f42b303a61160d021b4ac2297aa88f491b482f15e35e71f17edc5979b28712468ffbbd6ef129c0bccf92ac20970c834fc44b3788534828569c82252bb90ab14c692829b0c3d00a92462bb907e447ab73b3349870c41ac23fdb17a99c9590bba77280abbe3f9bdee2fbf4c8f678db44a385407257c1edabe22f1fe749f8cdaafb861fbd4b17e2bc51f7b458984b99306f9eb9419e5d5d118f87ff7db4ea7ef42b2d3c270f5b93ff63007d00d097eaf0adfe3e44a6057caa821ba83597a0f05056e62ade6597cc580216f7feaca3fc6f95df527007d10d37d12c89f276da33d99930f6e8849ce666036063a3cbf7e26618ee3b0b363381e63147a20c5da674f22d837affe23c2b01891b5b9ce5b86a4c0733f3eb635436a1896bb0e4b5bbaa846a0b2af1216c9e249c0c667c1ef0d113926f317f515e25bb45b5d52945a486eb20c8db589ab531f9b0609681c16d79ef6035ae3060bf103ef92b6bca597caf6c34aec2c29d4e2b015c644a1f2981a38377bd92f1d6fba3f14027625e83a319ec03518e6d0ca34a87ef7f3a4625abf3c01a39c89c81c179920dd1456dcb62610e5579ba36b2c6e4e5204f11d2100424efec0f0684e6c21d6f9fada8e8229c13c89c20df76f45a65788e7a043c1faaf89fc9ae3dc95f226b259ca40281c31396f13f61ae17777de52e6c79c32ebb9abd4708b7164cc2a8104222c508ef4b03a7dd605cd2d9ee5fb3668798cdd1b81d9715313eabbe0404db2fda934f89c9dc8d047be2aee9e55f4d176fc459b2f310b3fa991e385ea803d4aa32616ee35866e342bdf103454234db34a436cc48c78455eff775180d8143e56d79600eda4d44d01b96979c6d399b1bc228f724418f9c0c89522af1e484f70e6e0fe14611a023ec770693fc4fef81a3f9b7109420952c10a8b48b5d33279ab2e48dcd659fa28b41ce9eb61a972f8ceb98f16f73051695bee389ce735d6e23b4ef64f730789cdcd5c6db133505d1ee98f7ec7dd848071823e5ee86fedf2635ff3f349384c9a1184cf3e5fd2c08c8aaad885f765c604b608a56316bca8330aa50790c5500e27eb41537303fdf943c345065fb113f1d93a48c3bda4acea877dd2cdd1ad5bac98d3878dcf8a4baa9d5062dfcd72aee0ae15df9bf89a56214998513c494d93dc3af0f236ece1e21afcf51433b997d84fdd6de21d0351a72269fa03d7215ba3bd296a6a10e41f58614514299ed5973ba187a75bd40b199a93b2707a7f580f159f119f7513c16c24cd065815a8c92554057c467c38c8cd7e7846772884776125a42d46c6a0aae412b470a1bf70849cf64b60a93219afa10835683420a423a7bd8465fb969c143a7d61c0663965c9193d318ede675682f9e6fe545ffad7d205f9536c6fd2d516e9052e020f1dde3e400f86a41e558b605080fbb87e7b5af3b0f05d38569e2f385bec4c41aaa0e7fd50b1eba40b82f2ffb08c97976ff6aa25ab0332ce740aae7c64629ed3f613a27c17293d46bd714e350e46ed1bf1a4ce4f0b0624168928e5a5b6669cfb2784dea598bf23ff3e4c7c1f4be7d1fff1e4c7cffe7fdcef4f7f212dffbbfc408456484a9da641f0181c428f68f9579e1def357faeea3a16bdce5541430bd3b74a44037cb0e0a8107160cc36150b7b2bd65becf743c0f012cf1ffdaa8f6ba99ea47fc076dbd271593139cacaf71c27c0927403003e6c2e4afaa08fe1a101699e9967406336419e610e0340ef84c8f14fecb984ee4c3599129113df24bd8be814582efc2f56ad4cb72264be969a93bba6b3d8c5f06047395fafc49d905513192b6ae39c4ade89633d02c0045ad864291fc21db18d73b1ae39f98fb8ff1492bfd19aaf0a671b478d518000c76ae07737cf63d594affbc9500720d801013681baafbf4f26787e1a0feb5946c2ffb8150f80c1a94de441b67e298accc928604987033680a37eb014370b770e803811099cad11a3fd028e0eada863cc75bb431890e8ec31189146567701c929421020e7a0c52a01cbdc9a7f206d029504717708257b3b64ebf299ef3f45f99bfc3c9dce43f159244e97abd3fbe73dad3680749d5324fb9cf879e5ed569cde7d5f55cedcda41f78fe3311af4e83f8b37cf4e2346cf303950ed1308431a048dcf9c6f4f01b5d207cd759b7ff31288b5277268e23687b33e769ce537a27901eae00648d301d67f8f272dbecccb217e964b98a314f099ab8c67889896936cbfb7598bd9f930a0e43da48db64301ae16d546ae6f46b17f6461780f4eeac2bd2b1dadda2be163ef54d234ed603fca9dc144877f777751bb56120c8dd8e103f2523bc77833e111d0e6597775e4a5de6b10390797ee9985408094d21cf9d1edd05d33a65960daddd38dc315c639f673364a2d535c6200fdcc52ac00e66f512cb28066615f34644c34739d7dfabd963c9fb64c1ef12ee1ae89cc99005ec7f8a601e65c7fbd18d2bc594d4fb58b2dc513ad02b89fd0c96f4341a7b787f94f4a2fdf9c86e9fc50c23a40421406e65a3254880e5c695aa10d2f4e73e2bf840c40ca777f4e09c231e5f875e858dfd8e7a8d624a3d64e12c6c417ca6a5d61416dbd2213db29d8777070a4f232b255ec5ec9d4c4bd4f478c18586104d507ddddfccb448dbbed763cbb0220c48a836ec9f411ccfaf01f3bff78bd8fcb7177d997fc4d4147cb4fdf92b8823d768e34614dc21692ef2d604d5be405410485668c876e993d07cfacb9d66061b1e4b06d101ce947f177e607d7a252118495bc4c0bc14448f5d68c9f73d514478a7fc582a7f529a43d18e85652f95b67b0f51d82af3c86207594df7c17ef36bae6fbe8ef5da915edc630ef299233f594f755efde45c6ef82f1e84c9cc7353612d6191e239f0f990223eee0f0ab1bf7bd5fd17ffff00273b73a39cbe5024ee98ad43cae41df98eada6abd20fcf18fab797eed5121f5ec542b410661c296491b7654ee166a38641129941bf391399eaec1d179cdb601936df4af7263fa4ac89502332a0c47613380e64802a24d0d0d6a674262dccc4a141df52f91c7e4da41af0792583cade5745c547c2383019f0caa4367843ea2e4cb35a1ba31692d0683466ebc15761b1d3e31ad0c0f41ba903ec212695158047e80943ec4cdcde8afde94a6e52b79e8cc4ac1e8818d3af9c13fce7e2674afc57b0315fd24d447316a8085af8f08dd778b79499698ee17e1f611ac5fc63d0e79863b041001e0d8cd628108711c1968cd3c77dd0784109fbacdbabe69999b52e5027f686d64845834e2613d66c54abd89328434c3aa71df03860576957ce9f9f5288a5de82208b87156344a41fc7b44cb8d49b99963dd14ec378bc2e0f796af777f998fda08d9bff96f410e50a0cfc200647596f38aedc8d987e3f4d087dfa5f2f99b04fd03c29d0f4ba75940d20e3396cd31f61297f1b2bb475e13f56eae4454ea7119f8a53c0b9aa8a1e15095ad462a03a952b36f08cf0e3a68a6b02c79e71931b06b03d1b653f197c81f50bf9b0ee0358e6bcff8f12616f25eb21969c9f5e17161f3be3c07316419edda0085093041798e2d761e01c0397df834a3804c62cd56e6932c303a8e936d7ce9dbd7e0b43087b8d6f70ac2492c1cd9874f4348cd2055df2fca45c83786d4f6724bd74852367e1956b849db4ad5066e46e1fc2c7b67cd47b5a54dcbf14cb2d89f797911628266632f0a3f74bc708670207eb7d22ed5e1d261054fdce666c509069e0cb6b64bc06885728e840fceae36b8e19895cad2de65db288390f99c1c3494b34bb24d181b775b7109b16254a6944f8e66929f9654f2f489bab0f61e588fa431ec310277e6a5dcc7b2fb188277e295246c7dbea8115da1768790e3e5efae5c3f2d3fea37baa04fb197d8b0ad28e3bf20fe09fc636c90b5d48f4c32c940da45e9b1811d3b8b7eefe94c3507f04ebadf7c9c9006a6b05ffa8bc38fc8723578279d57f24eec73cc629c6c659676a05895f0cb959aa87c2ee362ab84418b9b4dc91422864ec567ba6a166c360843e25ed7e6921aae11a3ca99f534b93573fc0b89672e5a38e4256cf7746017e4dba7555814fcb74f1c77b0e4911c0c0f5726f136643109951bd656d669268f5b7e199c2d15574ef0fc8196e2f0dbb9f7d67d57ab1089051c43b00b551b9b47274ded61fc4db960fdeccb47470484e686812e0d4fa32475c93b7cdf54d09a6ed4ca3bb89018c3b0d928b98ca07f86bae8818674c77dbce5a2ef757dc6d1722576f199d6ac8fe3d436a19fd0709f2f7fe4602cf60d49ec66816a95db7187235f610bc940c45ce5a9454e8236ca2bc0bd7c270c630a29053f247975f0c4f0a14fea17f52e7acccf48ac9d77a1ffc70bf5457e983f3e977b3f26dc45d857313183398eed03aafcc0851a491f11210504de537b7690d21c9e1ff18e6814d1c555bae47aafcb35ab28c297eac3de8ac256be04bff43c55d925b8b40220561ea58f9ca364c643235b234436a37ffdd19de5e87139eb90631fb6735a0dc813abb477b39a5a701f1e57f93bcd2ae1da565ab2d84414c4c49039075c2186fdbb66bb1faee07382614be6c996b8f9eb6b8194c2b72d6ddf9c1fe4cffe4f46d336f121015e4102ea7c32bfd0f7f74d1767c568ddf6f4834bd37d16ae6bfde129adaaf7970ad7041acab64f8e85704c830045dc787bc72b480fea0ecbacc153a8592772b8386c6201641b2e462e87059d49423a2c9c93e4b4800a0959cf8f05a2de83ad1846be9036884bc206245179eed216e292808d548c668ab54409e3b4ca820cbb5eb0f7841a6be4001ebc6628e1ffa2ee8f36ebdd466380812f269ffcb8d1d0b127374a3dd7286561d2322963552244345b5d6cf7826d9a3d159aa5979ed65bc9f99ebea92b38fa98ce1551b0b4c7a001c604aa4fa191611895e69e8ebe0783528867bcefbfd4dae6700e2e5defd3df6f8b6e2e6e650497463255b7065797bb7e31f9e3ab19990c5a324b5a101e3a4a1405c821ec6cab11041c7ae64e5864fa1424064304349544e0269cab440cf1014617ad01b7ed10a35e64b4670dd3138f614400322cdacb323600d5085a26949ad864b244bd58530c49f20b777d6902cb2ae7f6444034ee03d9b054ecbe03880a3fa3fb0557692fdc085dc029e4bad2fc2fad3d61e193cd7179dfdfe9e13085ee23011a4d15f6532ad615ab15514f654bf7e89a88826f862bd10c886d86f7e543a808f9ed6108a85c59b1cff2b86ca936aec2b561043044fc1a8fe8921da22fa517c7f780e56298ea741c6a33e5595bacd5175d83ee89d10436d887125ab3481ea20b1de6e75b7f505ef0719cc0453f6852d439b4652d37aa6617799d525c8f7d34539e733bb6f30f4429bbb79bbcbf68840c718144899d32b2847f50b00f59f09ddb1ff581d89f63de45c5d377715dde64b80313579ac3d5a841a7a80b24486e20c1caa5e796538e027426916888c51f10728cf00b923d965e446dae52ead64475f1ab19096af9490dc1f7d14067e49dea865ebba0ac040fdf13071f67a2de095d12efea3698412d5c4fc1857d05b9c7f46c74e05df7d9fa266dc15c864e80efb3ca6c96e8db98f1ed0e64a0f1f3098d453cb6f91536478e38463a81815e708a93cb36c1c8e79ace27a42907b6ef03b96496f7a80b13bf883d7eda0490ad8a23531eac35d6b1ab82e51cee7bdf200e9b87f2ba77a3d3d38ec6549c87484e1b52c1f6e620a8921cfa78eaa777b88fe8d450bc63384b2d1cb9bda21f110cbf706afb20a6939563f3a69708516ef2fb111ab027ff5bc37dadabd76cc90ed0b8fb0ab0965d0eb27935897b0e5ae1204712db0bce3e7a10a80adb72244c72bedc7950434bbb31ab9ec74ffc23dbb4623cbffeedd5ae7bf2def653c2719dd8400fb495cdc3adec56cd51e1842a2e0363a05ae90c77fd550347c424f0df50098c74cb5e527bfe5cbba6865fe9292d17a9b95093398f5742a2d9e48c34baa529a0f024bcf82ac95d0553753f0e24f049bfec9ef8921995a156d8fe526ca9c01917eb599ceabb09cd7b6c92770d1f91413ddd82c058a003ea278b01290bd5f25905a8a2ebe53b9f0844834b27233caf3c1846a53fac71ce2a298f8b05396f242e04ae3c9c872baa192d924fccd7c8ed13a14b7dfe9806073b0cdd9551704251ecce8e5c7d984ec8f3d093d562f2805bc0acd6e42198576de0f4d1563416944cbd281b426e9356ac05a5ec3ce78af14f21ec477c73ffec5c9e7cdf0dfe16036e664531639c86c093f65df737861ef3e30492ce30ae1a23ae1c7a86ce04e4bfdb65a1b937f5771c44017ad42e84a51ed36aa57fe549cee65b27ecf2c645b07ca0b79b0e9f5f3c820c88404cdbd0ba06c78a5378eec599a3ce5aa0bcde45b9a4663290987fc50a912d195f74caa5f849999f19f5ff4a843ed385bf9c50b7455818a88b972e51e2336ce578121d4e01333b711b157aa875e8f580b740cd06452fd11b6ebc0a38253198da90419630aa6f058f16713db51dc4e811c44fda53c09c9d7d4ccf3b3ad347d0e84969ec01231f90a1a6fc5c37b05a8c28f794721341d39d065b9dc821db28bb101747c0c438a2b0af988374a1ed93d35a1da1c12e2f92a823b00bb993003dc0e20783777005e3a221615806a7d3075426526cb389b8b77f2eb4c6be5eade119420c5d19e8fb88f92650de3872bbfa1a857d83ddc038b67542dcad886e9174ef0d914fd62c44270a4bc94425d35db4c75aa3066164bd5a5a5b10a6a52add34caf8f4390cdb13b0ce1758d040454078f89738684e0ff53e70b1c037954670d5a14c160eeedaa69d45f30f88658a69c747af0475112c19123f9800cea9309d42c7e51f9a98b511f8877a588c73015aa5243d57cad38a19279021acd8d2821fbc2451ccc5bc26bc9eeb335d7530dbd6c0032de2f2ef7055ec456ed12aa06933e64c282f93f1864cda0273d5a92c44dc74ace2d81b9541489d4cc1e2b4c7a81c06783ced42f415d2f4870e8bf4299ad5739bd94ad18aa31498c8c1dd36ea463d808c66c76fda55e82403f005ed7a04d6bc406ca256096ee9e7b814078d8b12b7e1fd03a78455931c1d87e60b5a5e81b97d66fc03710a7f9a2ca0ab4e3bd753736b94f1b3cd37f9089e40507aac45c1f1616eeb76e70285fa96a95a63b85ac37210788cdaa601a3647a2a4baeb288bf8339f8e2ba6fa80b525a2fca481a0c5983fb15fa212e9b18ab43aa82df2a14cb231f8912f973e2c5429f118a3299fe18898dee0d4409244e77b8966527ce0c04fddb2f90f1bfe2042a9c53ecb3762974484347aef7084d26fe0e341433d8eaa7a179bb83b504ba70c4fa7004814e0eb80206eb023a0c043e8e7a1623af912760672d685220f9749a91282767ed4cda42d77a2592cfe2ea41e55ef56354b9b32eef034df32118a027aec95d2a37938889c50d6fa81b0e482953e1dd094c7cd7ba070c37b42418dd90cc1d297d52c874d86e863a64ebe7707eac82c7f4b7e643482351c8ba22dc2d456d246967c0cf94fd1be96f00490842e7a9848e6f5a0dcd6218fbdc6aa1b8b1f5723b4871ebb647b708d60b927fef6eaa885f05457e78b1dffdab25be072a81890c9f4f5485f57aaa71b5528a9a4454effeeb2ff16be0bce63283dd1a60656f210243135c108c4b86d5f7678490f19005bfa934893fb76fe8f1c8f9d94daa63fd80941424f6f9e0db48c27608968414202e37ba7854e5a6d0f8377fc85f2b2273577c0ecce0596a3c68d959ff66997a8b866b902e81af352014b888b25dfc111694aea10fa8a0c3b300723000dbfb718e09811f4378f40f27f38196f7c3f0eced1fada23e11d28fe0d97c7d8e379e9038050330ba22d223c1fc162a6135e1006f667fa57a09128d2aa2851b8e505737edacb6636a45a7c68cf8174db08473bf084fd6246053ba2360246c7d2812e0eed59c2a44410ef0ea949631c1ce0aa4d023d088a27f9d4c75c2e58ae2ec96d1e5dcbd9399d1e3754a7d06afe70f617e02efdbc0d4e0be8e895726f9d98799c068bdb3f87ac402aa111439bb9d7afc2ed054ebe45c66a18c6324e8945508717338b92b46cbd9d35650903aae19c3bdaffe2a7bf0fddfea5331edd444699afd42afe375d298aa93fd43dc176e3c1d493b1bace45b83abe0da6c8ee83f2170eef7d5376d6d81e3932ec4d0c05e318ce1c096cace3e3ff469ae6cfe6e974216ce8fc661df3099458e6c640f5d88e351ca0573fd541b384ec962c4f367ea4f8e1c00afc7f2fbe1f8f9e831b95b039307da4505012f91e2b31c8456a1f9bed4035c6ad6bb4886d1fc364ae5a6bc4be1dcd8647807d061afdf60c073c95b66071a48f12c944f1ea4ae1d8d93ecb3a91d02fc0405633d5cb456d67d2114a58c4176211360024fc4fbe3822e196577cd1dfbe9b7dd496516861ea3169e74f125aec492052388012b927eac63bbe9014af31f26aba6501dd46b17ad52d06d88dc3722b58a4635542d0572891a5458c1b22f35c294839ef3fbb0c5028a3f8ecf0741cd910428631a924c8c11a03e64d24104ba7128fb3e42774c0a25700fed773e3de716514f1e3a70402b9eb7aa0fafbbc42700e757f62045a4cee4bbd290f7642a826e83ee677f1e94bc8eb113e0fbf3737f483128573a07819d68ad6e059166b844b59121b4650b45400aae107e815897ab594d1552256324ce0cbaaa352dc9742560ee717a19312d53ff8c2109af2222391cb2092439abf85277de93e6bb148340955568c55122e0dd2d21b0ce41e37d01ac6f7d450b6be3016e4bd3a26d9a4a39508978304f7c94a463984cb84039901287bb45c7e25e9ed697b22b130aba109775aafb4b2df2632439c25416dde37199a9295f93f30bb49b654d6f10b3751a0da8b1598096290ade2855e596bc9b1cadeacc5cbeb9bb94a9cbb317728d19de7643a69067e15ee34ac4dc43c8cd626ff53d4e3f0586096078ff878f6affc63d391c8cf1a2d6c078cb367c0e76d5030a7ce871f48fed5942effe59d5acfffcd89c0f6e612a1707f44d785f1842279ce617b1f565ad5de4f50603d1c1d09c824bfbacfc17d073371374e46b0816e835c513252fafe9cb0897e525c2e481b5033240512e12b3ee0a91f40c6e57d84da7bf3bfe5fd54e2c3c4673f3092f6fd54f2d3c133b908cdf8321e857790689d0dbcb846519058e742200d3f98295bacd758ab84959b070da6be9121f14a7afb3b8864645547e48ec030851f253e34f745ecf563a0acb7d04330500d899eabfddf174ece0439d8b2d2d100303100d5be4cb6ae6aa18e0f8d534512de7751b5c231c97139b905c93d4bdcbd5dbe4c644ce68fae4b9914bbca3d2a73022894fe16cc7accb97f4deea7ea3129c0a6de819ab58adf56149bfaaee79e9a8be5b79454fe534fffacfcd378de5753ba6bbfe898cda535c5bc4ec79decd517f5358597dbdf9500936d91f526fbcb1ea430866164761193fcd2c3aa907109eadd81efa707f528477ac0620721706721fc664bd5a3c5706a4f3a33cfa0cb087d0c69c98dd153c324f7a8494f2e8ec3d753c04e8a9419da6e324db743ab36140d7f9aa87d4fbcc7c8c29b24db6281035466934a1d1dbd6d641b56f0afe02e38bf98dd8e7e59e6348ddfb051488b7a0d5d692fc160a57066909940b1f5c270cafef483d305ddfa099b335a96887148da1083ff88fccfd3bfffee351f1c31a13858e05e292632798c5eb5b8eb04cfc55b25fb76770dd208784fcfb9da92b73c0b54dbb5a90abc65fddc082160178e7f6ab45f6fd76984d99cf2a81234e9669e5fae6eb9b3f1b56abbd7d5d5d69e7383e383b2ecd0cf0b2335a398b2418267a1973303bce96a0fffd42ee5c0f70f28a1abfeb36bf635b748245ce808ac000221f25653dcf813ba1dd40a9a67d3a30ddc62edd4f68f6783130893515d7abf13909ae434be713f2bc961f67f31bdbe0fc286928715e8f444adf9a4bdf12a84b03c4ad3ddac9bfad33e7be4ac54c45f0404361d88d448659514ea0a2da5a24b7eaa10c4ed1ecd86f2b34b4ad659b52d55ecb305a1c78c6014e4baea9ff7c416f7ededb1fccce05d5b267c083a8c411de083ea801e50369c8879e87721ff1e55d0ad342d07994af3a6c6b1e4b0620994fbf021d2beb5d5e2ed5cd4c4470a55ffed2e731fd387bbc17884236e9fd3df0185b9d2466b92d70e4d671c8ab1eb326bc8c730b18df6292e75ae214a5c6b02b1d6460dd2d9850ac99e7f45303ae003c99b5292937382acadc3b36257c31f4c45e53d00800baa16b61e644c7e546dcb1c959cf4f03e780d2596b2477de5019b2be7e849862e18366ae5f6681e0386b7129ac6aa676f22da044f772fa9639a953a59d14f0b39d96896f01c06fe42a19912dc8352a43b9514bb5653064f1e4bd1f1a9662047bc30e54f6e472493f62bc7333460cab311a108a897823dfe2c62a803f0b66f1a9cb7590502b716158f3133045da990225ff758def8deeb46f652e293f64135a3355f135e13b9bd7c22c069247ace2b22f4516d1f9b859d417a31bec7ba83d3503cd725e1ef3f39d8880f32c765a22a1718f19d14923e8b12270d77c916008bb6f8442ec649a406cdbc8342dcb7f147fceb8d9292ad757963415e2e1e17c73bd435cefb1eff1a6804d556f1b3b9f0744db4bb0c77e6b2b88c223087cd37244f6be008c65758025bbcd4166131366be94b90d56510f65e3652674665c76f0a8c739500757e68ddd0aab6c4a85c28d17cab68ab3449f688b86aac45907e57c9e79d937a9de68364b0b635c0612cce836f39eee0e751a4b634836859b766cb4f65379b6fdba08b41c97443f15a53aaf9b1c320759b8062ffbe2c3742d96b4e36efbbad12f3743d796a8ca8958531e828169d6d9c664148161d1119d66b8ed8baa8c6c48c26664d6a6b425fa862e32729b18b3860cd996a9489568e34443f5e38683ce1f7f3935cba8b1c895da8d1f69e2caa3f7a208913ced1b7ef01777dc824720a7cb901e4f845eea701cd778c270221cc4d721e0e653f113192df2e752f2d592d336f25070c94f0ba629d9b0aae2fc8ada64dbda70ba7d64e76f542c45828f6fdb4d978fd4a6ab9050c92b693a57449262fa92478c46a0a9e9a1ef4ede426ee3e9d03f9dbef3b2b55ef8bc1dc2e72a1601c3fec957b226a575637f1756c3b3e5884c36f2a4b0d7cfa93f9eddb3ef7f9b4282bdd4c43bb5380f4896a6a72bc6a6c118f824979c13efeec5a2844763024af0b1ccaf0e5fef386067810d168f85ac985f66312462ad25b02852ccd05fd14fcce01d9c6fdcb66d11b9d348b3ab548dcee2747f42494abc88411ed8bd018555c69dd294db9064c5e2d02869b2c607f69db1017e0969bebc006981bc8e2ee100498be18e270742a421f204ad71a2f2d4d35a109a315f293ce9c37142988f352c1ac97b18782d7cb7d5b0a0f93e58c236e17856e672ad36a2df76491761f3fca357345184f603e23f6db94d25d46b01c67dfe20c62abfb7b8cf2d9ebcd45160a6d372852d4fc151f9f71bed0e873eb4639a9f56f4cf05c77bcaf32c5f2ab8300649ac250d65507059ec6f797b5b3d5ba36c7dadfdfebfaa6efb7c7ce3a2d1b598fd59e7ef19e218684ff4b6bdea561a6f897db963421ac061a4bc6afdfecfdf93c80797b583182ef243837ef5cc4df840b97debf7dfdad426396d87ef473f19097bb53fff947ab9fa3b43fc035edef828f0d9c276e3b507c16ca7cde462c14c3dfefbf6a54fa55fae3bff0c822f9d7a376fa5af034b0efa59cc7451b8bec477e6abb1636d78e813790d2d66c33edcdba18ee898afb0fababd56a325895d58bd4ec20b9d16681569a01fc73f4e9bcf71358db75d8d6396199d0f39af9c7f45f6399f612542ec783c12eeba681b0c5458ce05e263fdcba1f11b8c38c54fc5097a3b313ea536ef6c81f863476593236eff3717746596cb5f55c674dbfb029278e78a3f8fad710f715b29b3a2f4fa9abaa9039e568ce94a7c8d1bb7e47f24005cdd9ffb472c5f16c0a356337fb7aa867e4ff2aae296063e8c55045ad6e39762a33cf66601c61080054c65fe18371df51efa8a8edf957e2f062ecf2d3fca17a6974e32d84d09f9101f2a0040e591b911cbb0f0026d7e8bdd87c2b5467964b3cba8edd017366b1b2ede1172fde93ea4e5f600a55d35d02b3fe256b12b02e133417d8e9aa751759a74a4e7d452f6dea686bfffaae38dbf555b912511b703332e9dc5a6867a283c3d5c265b83fe42cc3dd595893cf8f7649a494c2cea2b71875ded43632d08ac50cd7a31b22fa001be0aa07878258a766e6dbae674fb3e11bdc9e27a34bb0a58fdc96300cce80517a9a5e85c532083297f1912e489c38340e8230fadf58714c6627fc2b5ab1417090362e79e081087a0cc1131a827b82e6c5685dc833b1f5ba3f6c16c9ff2d7c3fb0f74ae49afb495d2e4576eac871aae4833027eda2ac694759ba59844268e3dc53e89d4665ab52524445c6f6d1a0b45aa0e6190b4e9914fe01cebb478e57bdc6a7b4d39be82c93692c3e10cdf6c25aa4b8e1add41e4961d2a72676405a32f079f5d594d8f2f6bcdce9e2c9456387556e9b84925b9bfcca72f74133c4603db623e458b0e3880dcd2c212006c543d606778809a2a5b050ec6ee64729b9457a9964d4b9998d034ee7c40e6e7828d501043442d43e6788c80c31eebafdf1150ba013d301d2ce6f9c133068944cb20bdbbe04dd1a790fcf9a8d39cd22be89ac8f812ea5049e5550742f300cfe69e0f673c538a78faf6ee03ead13de22391392f7026d4adb18a750bd6eb7031b6a70ab6400f507ad28d65752bbab08287258f0e46c95ef9947d0ec20e7f869f264c3c593cb3d563245881cb402d9093446d14d72c9e338684a261c228608534a0b3bd6695bac13a411b0c9016c7abd9c99ccc5f52fff8a0df4e1c1e11af5e4250783a22fe6e6a32dc2288f7e8873326b06d8aadbb3437d641a10a321e60592c459dce446eb14751e8dda0c76ed97724c892a4ecbe2704710bc12953e0b6e27fbdc4473d3c1c3e284d07d554900a8861e60b4f28884e39945bb294b0c6ae180b50d800ab79208dac66f64c16108c203e6e3e7663121fd66119f370451a041b4a2b7d75d5a92ff5379dfdb13f7e392d85fe46eec7128c199e029fef7e96ffdec26a17fbff4160d8924d0206842f6142d2e4389baf5ce78f10a474bae620b9d474068c0254cde8e5858f359c3c20125c1c328de7eb95d52e1709442115ab391bb311370ff1b613238e76c5391b51067456108adba3ea612749539f9913dd998bbf77821dcac16d4f6f890437d0caa6ffb5d3eeb0795e7402eee3ac45b2abf0a03a83fd6e3c211a86e036f073fe0f82fe3aa9bef56daa5d9dab1c1c144fe7c6b1bd766baf7b82689da63db954700a483072345ce24273be56065bc0c6b1aa4d92e8338ac665294f77fa9fa2474b8d93a6b2b01f897221f6c1c55bd7071282b2adddccdb487c5a243028135f5f646c0bd7aae9311f4b4766fe2146ba3585b5cdfe51dff276c60d67383fbe9cf0301ab5303dd8c0d6e3a016be7b48191e46ffa3797fd071fd8eca6f14f987f4ab87eb308644b24ef3e89f369eff5a5873a2f42dc90ce46bb1ea3e70a01d533bfa8a620d43e17c7f3e5af925a9d9fb4fd34d064302242cf8058bfd2ca3c34dfed0e2dc300352fccbb17697b44c49a7c04cacea44afbaa784bd3aaddc013de5f4f4ec0367fdfb6d51ad3dffee2334baca00960b8125f0c0ab66d1da0151543b847f1394304e028c13d8d2474eb6480274ea3ff70b7879015efba3ce38153a293d8b883a8741804201c88d7fd82f23dbe6036aa7b40e292a7b81aa2f810e40621eba489201133fb402bf6225e003c1e0be7fa338fd478e7b2ced79153cfe700e8b8c6b4aababf6a303b57a0e1094e03e64ab520149d207afaab7213672b0fa64ca7498d9e7b85a29cf6ac617c818ca2ef8dbeb8dbd1b93b324e409cd8499397c90069e2daeb0b8ea866435fb631108f646a4ef07166bb809424aea0be8d8de5c18501ea65dd9123d0835286d2109f265da07489ee989d79063a03f61710d09c33d708f25d68f68c3d1d163b287eea23e3e3f3b8cf91e05aa108763059860bebaa9f246fe653ec3487a396da4cf266725ca40e5667991cae79298f67a03cfa3a76dea3c109c47c5f77ee4c3076f19f8dad47c0365b6306c02f976f196b920be6ea82d86b0936b2c1e19f1b9ac8c57615f0cf55c31211bee4b67d540dea925767a656bad032df741eefe6d77f318d53d241dbb75dce9f706dad3166c92aaeca4c6b4901ace8f63fb8cd3d4eb6507fe2c4ba1f2863bf0249ae8fbffde43de2c8ace9e21fde1c1f72204f6c31ee708156f9249979f783baaca789f838b4475a0ab6b0208798812255f1ddadfbc69f8a07ff8ef05e2d9236c4807587e23f6012cfbcc108ffc30084608963c88a377e8e0794860a9cfb79fb0d675b620774109ddcd4d92957abeb4629aacfd3d85950eb6a1b57f3effa021d5340caacbcf2af762d451b71395661b056cd1bb1382b7aaaf23706eee0af1fb37bd734038d8bd31bae9bf2b45a096166be9b99c45214647803ed8a5686f7680057b9fb6af6b50595d15fde3ae00231bdce44f50f5dbe31cd115ffebb180ab523875b748697cba2dd5fd77019bdc9baf52e7cc3fb89bba1734c5998fb3969a2a132bdbf02b99b9c85fae4eb9eea939e0770b75d210bda9b0905f0792e5aa19c94c2c1605af9ce4247e42d01ccf0ecdab1d2e78d7605611313e4c5987942cc909b37878e828be18dc4628054b7da79dad13540d7b6196cf6bd82858bb62c84e08c54abc54573dd39de95e7077add9c6649e5bdd459a110862a6d8da40a9d773c8ce11c7cc349bb036de6456f853af81deecaccaf25e2f9bb9bfd6e6030579eb154d4faa91370f8ad1f1b92deb39547cbbca2bdeb189b4411784e1e3ab7ee7ba5aedd5def93a20e9f703524d8cdb586c4e3638ef40f62069f379d168e1adfa02097c7cf149005d798f933961322c50eb28cb1fd6ffa0e0bfdf731ec53bddcb4fdd9092f07e0cfe96e05f2fc39c9eaa398c2de04ed7b70446baf775a35ff8d26cd0930a751e362bd0a08a59a1b5c7e73e8954188152fd94fbee57d3fcf7eeb7b6eb3304e83144e9c56a3affd63c8efb8cd106ad2baf4cad6af305f21c402131385e6a9daae996c11c9df29b50d6f5a2be699817f58bce7ed455b83c597ec8edf85819d8912c0df9d01cc455e3c6ff8df7872c6db68b6bcc8265fd05ea1dc83b26876bead4fb2f8128020695b3f8752766abf1ae3317933e7fb3e12669810eea72a01d08503b75f097e56e2ca258643e2cee8efbf27e320f0d8210fae60c52ac0df2aeeafffc9ddc87e5e4f8a2723138f0532cee52002e971902d16f772955d03068f0c78898f1c2d46fd9d4ec4d46ae34486196759bca305e2929aea13450179743f158b712472a7e16b2c5bd04a122f9a2028c7f31b5348b293b149cd09179ff13f8be9019bfd205f52a126b6515ef6f9c71a410a0e21ab3a06764def32f702cfe6691c50818e9a7e56bd7c52533b14001cac1a8f949d50f9dc901e88375642531837f30b64bc2974f48f96fa41a1d160e12fce4083bf910bc49085a79d00372427346c9ed268d2fddb9a8d95de6a6e19e54e3e7af3459ad255184b237f8b669a7231d6866e40f5dcd9d69a5ecfbfbcdb6a684b602f51dd04e8fcbcd8c5103902e0e1ef5773b34612b7b089636a85424c51546103915b9135fb365dd9be66110d8c980d3f900a336d95bbb7498ff99c6ecb7502d90b7ccf386f4dd879ac8385b517afa0059626db7e9b97484871466cb38b87af1c10cc7305e08289060708e4bee92d035498666078f02581d56cf91be90dcacb09c7da0fc50223c9f12465583d5074e3507ab95f2fe033edf06cec3e89c851154aa23e683cfa249d3ed0bd0399a5ef64a1a56f45107bc37dd1a4acdd0734c8bfb8c6bf6d3eb272a343a4e45d2164980f025b428d896e8f11d7390c01c196a04d8ae0ccd6b445c616ed21bcf2f8b40064b262e93d158089c7ec12b2d697004a3aed29162ca34effb35d2dc7a827ebc2ceb6be59ea50adaf40a150d2077ac4438c44272f6d559b6241f4c2dd9c208538c8828fe12873838227ab0bd73f482d27413b2fec7512f0cedf51aaf956bc2ab9fb26b40efe7ed59013cff88fd9835467f5168298a9595dfe9e169c082b4bc4c057194dc1651baef2ba94c143c821716df59924774153aaf0c1a4fe1a0fb35e0b98458054ec4e2f5e341d0f895aded15ff16dacfec77044b3e40c042ffcc44967ef72d5c0133040ab0be9c9702e2360843bbfe002c648fd8c4393c07451a69f8141a7d6f846c4d8d22d86d97e2897ad0710c6946bcbb78ad950c1603c02f2f746d40153245f1199c267ff7d8ce4ab70f396ad01e93d8334525f7e5f5a1cbd08f223233028c40dce8f665061a17300cce33bb94cf4edf49ce7adf47f6073791bb2583f82fbd566b320a8e7e9fc2114863e2cbf46c4d2bb0101cb250dd5390f71e1e10e9f4764b1e4fceae474ad7f5a49843e335ff39272f9bc9a6c195d33ecfc4257ef27214c749be391c71efdc846623f66af40837cb207a42287c1986296dec9e6d162d2f21dfba79a3c1957258bb60c55b940fdcb2ce3485e6e8330e3a68a665c223ed9afbfc1831e607b0c3e2def37690033c42ea11bd12b9ff93edcd7b3e26d2e416fe61e6820541bf5a2f32c8ef7ba8fe66d8373dbcb601f5d937571d9ec7676109b12d191680b7e4d87a35535a783af47dc40f0910d5b7f120ac8cdab39d222ab6e05143ea40f599bd85cb6fa8b85cbd701b20dfd8d91d303ae2b440402f2d9061a3ae1df07f0f15e1aa8a81fc41bb72163fa40b4050e969d0b16adc7b94bb7a05bb998ce25db79a7362981fd06c3ccf0de7af1de565b3b409834e2c8fd2d42026d2fa177fe99bf52b309b4fe4eb4a38bdf38377b761cdf302df5c97d65a38cceaf2bcd94b6c6b57de3b3e0e2fa9a79b1706fecf97eab144a6689544b3c2aef3fc668ea417131ef45c3e18fdbc9d7996bf65cbd239902723fc2d9bbfb56603369a84014309ed96e044607c89559fc13ef4305a3ccbe50ed31f902fb88ef2dfcd2a18386f90c60f11a3da81fa6afe1c152dfe4bc039214c0fd6b5ef91f4c678a427452ca0e0a399479d7c086c1a7615a7028ba63f5ae3d77332d0ec60bd5d36b4ae73241b4c672baa39446b8418a2c6e9fd18b95a0f52c380372fc2a3c6d45dc434ce869d35d67980f5e0d54b07414f314b0196e44c781fffe31445478fff07b923ceba770b655dc6b6329d40ed1a1e54f697f59231ea0a4f391641151773ae0dac39d45ee5f68d0ffb880ba692998cc89d29cee81bb461b1c0a593824e6202716272f91b4a953c4c89735142f89e462bc913887ca26b68f472958a4cad04bf51818182fd11a8862b038434c442ea6826fcc6df76d7a4a2b8d2c12b79b72aefbeee2994c89d7685a0434f4ed8932e73f693e6d67542f1a70d0fa341917ad29e5c08015d7dac388a6f8ce94706743ff37c1fc08b7b9af185223d11ff74cb8e309674c625b08fa962a1ec49d164043cec148c611a8cd4ea93cbcdc841d4739811b9e1aa6e2859dbe9d104d8a819cf6e7d7a4120081a295c10e8c3515f9e98b097d2bdf38ac461d6a2a5d0920c689e7fe85897620870f593668991eb347357111c3db489ce7837a6eb1c9e4e565a0a8f6921ebea673389e3a87ca93608ef5f4a249a98b0bdc3d701faf5ab51da8533ec64fe5fba908d4f55f0b4f1d34860566ac6fe0ea2242206de7c254a245274052d490208ce9d299361786a92550235ee9fc03ba748b8a28e19abc1382089646480a3c04d538d40491b9db48147637206120b3b4340b78d497f54057b60b20875f49dd2bbc16f18efa21fe0a6f6b6450f0dca840f51f5997dc3357868c873ba568dbd3397718d41b80621f7a276720a0e4de4c953b9e327d6eca7f75552736f22c78a095c1ef549261548315ae107d910e390025413708f04de61983d8aeabff95d40db864ea216da3ab617c883d25ec4c66e8ee6288d9da5ef986b56731cbc2af89a6c623c70c12565ea586c10efc955f1fcfcd99cab439df4d8e6306350eeaf177b5d2f1a8f0a78763e3c3fa3d40c17ef2a5fd340fad8460398998d579f4e6eaa9d2fb4df181bea7d6f64d200a6fe1946c3757adfb76c3da91a40ef43257ffa62ef968bf0341564697cccab0a09e99be8af199f2d2474b3f850206430df5381663a95c85c64e10f02329eb67b5547843624bd0d6d9c1ac20d26b63fef403424fbfed1f6987137774471a05d53048f22b3ab1f110336f05668aee13b3ed9b048f5d5fe208fb566cf06604ecbe5fdfe8ace9e349a00334691002315379ae6644626bd7e5de22fa5f3c2e753e4dbb7f4142175731a6bd984066255a10dce3d309ca88d3b50ea9eef94ba458bd2931462c9e18f3cb32da911235995c57b50860a48ed9ca47bdbd5559b8ccf150728bf77d5d825d224ef2a2b2a25d08469ec3549d685dfce444c2dc0a578e8fc48cd90bdb81354e283fe0c6e536a0b910f58d857e276d0e469f68a6d80996672ac5c8bb89223ee82c14507404b72f2972062899e2d6345a163db424917dfa274a8ebf3eb65fc7f046a45e59d4fe638794fca9a95a962b7c76f9d70610abda5ce95edfa4e2dae31056e4e8514cb5ca2647e54952d8ef4c653537dba955b9913fc65e881b4e8c205647e5c3bac5f4684efac762c532be5130e9ec7159af659167533ddc02e85ddcb2015b2db0d4b57f0b036739b03435bc51d20b737dc61f439f55a0243f550ee8a6e70aa27c20203ce17545e28e31dce395872945916857a14f51d68b8405c65d3de353e210f119299a8edfedb7128c83122fc958cfb54e098792d3e4e5b253c5f05be5e0fde9d4822a409ae0a51c4d3867b3c8600d4b6131543873aee37cf30a8236cf008f6c4b0c4bce017c62094121d2c0b63a2bcbc3f4a36f2cff5205ebc3ee293b579b971840e03f8ead2bbeb5ed087ae98de8a894e762019a81e571bfeeced61d14480148b71a394cb18991e9397b0db931d60ede312b98b8635aef4d381cde8cd8157ed2cd183d1af5675da6b7a1186ce30e11afe3bee60a4e6ae95aef1ba331bce4f320994b240008840fcc2aaab7b452fc33fadf27d21ebed20b0dbfbd62c63577f501d7d563c22dfb44c6c6c4e28b3a8a7bf8e052d53cb1da9166e898e502cc432bd1f56a9cc2b5550a3648296731c46b06a93f06d5903ec2c574fa1df8cd324ddd9380c9aa4f30235b85968ae7c88f274ae35b8a7060ad1b805272d351286c6fababf041e6bd408223d7436fb745bae6f21a178c25c37f6d3770e19e2796e8a58b05ceec867a36452d8529e27d466626b8a0beda3a3695c45e5425d479eddf15ff2c796e79eb43f3fadf64c9a91c9b1972150f387e850085001f783be03fea1d59b725c6c4867ada38efafcb62e5d3bd4b452b786f81294d642c9479a83e2a43d6e678587f675c960a1f62e5607e5dc2f98563ab2a52cecbc44a04881c6a02799f2fd6434473eec4a4758973bc25519abd6a8e6734f89c93ded5221160b40a733487244fa9a652a28a4e403495320ffbb2bbbcf69486b89f5035afa6d77126d7f021886b353ed19f98cf0b994e71da4f50cc61b79ef976b461d30a4d2beb815f4e48967d21ec8d3fd18ab7b28d53cdb8af9eda7b2ba8752cdf342570a8c7f394df2bdfa2cec0d2a3b388e801ef299c7389c29c33b7f14db07d51798db5a3f3ee0ffaf35ee7b65150da35fff1cd90f97c6fe51fdb2dd33b763d8ae451a38ea38859c7973d0f81c30d2f2142eb94fb854715f93f893879540c6aeab9e46f1e38b9f1be1dd038e4f603b2ab5445151873fd986f70fd4e7e42e7f2f56d4f7bae79d49d03afcea7e99907340d58e4de066139ef00f2a4d1ef264d4dd42f09b87cc0cdaddd7b0a65cecbd7a607088cde7eb2fd004008ab37f667b06b81efd8c2384eeb5cc6f087f41376ab355f0f03ee443a9eb4a17812f863647f3f0d0e6330c6145a0442c3dae31eb6b825fdb7f588c276157d595681905936c8c2b00368f2f25daf664891f9b7bda64de3e6fa847cbb2ea5f7fabdec85dfd9067cb92e4cb147fc73a366f5d98e0dade89bfe989106d37be4ee6e37bbbc2c40d9fa03754b124c6ea3684bb187c72422867b99f2d768252a114ad662f5f8dca1c8c8dcfa1bf8a8dba830a9fb75cbe39ad3cd5e55b9890200b07cc49c5f690e1605dec31c8948d91dc0f03d6c1f45218e2104c8a56a83bcde710d781d9bdf1fbeea2d78521da929883467fa2016ec042391988a1a0d7491f31aa3367c28987cd7dd932771f67d85baf36d6c69d6e598b0bd92b5c566229ffbd4a5ff6f4c05b57c504ee0f453302d6cdfc971e6f02c958238077edcffe12233ac9fd919af16c43ae3ee2b13ab6c5ef41177dbc8215cccd90ec81303af14f0a1f32d8e33282f708b9445d62439aeac80058bf3c2953fc56f2961713444eabe46cb55f23e43c5b3cd05b6c1f8b84fadb923d9173068bb6d051c7cdb2675b3fb458978092011e3149c6f59bfadc9af329eb11470683d694afa34f7c893a1e852cfd779e19f44bba0a3713d44f0e865b1732ffca3005bf93f06d9050f7a7e6c4d1fb0bdbf748db43426b02e6888a004ba1d76bd46b9947e052d6be696cf59b9a90fbc4fb79357a15bf082c99859f12e8c8e92ffd67f43516d7bc9d094dbc8b0cf2ac48709e135628180aec6c0ec9f70e312c8616437c57f594dbbd02bc8b39662cf489b1334f518043f4c37bf0b4bc890b02fb7bc0dd34682599232e3e7e25abbd8fbb6e45f837e6eef473b99e6345e18aa64672643dc1c85f0f458f6f3b18c28e2e20359ddd870eb0ac68ff0fd0ffb0faf94eb1cea110574b9d75a379ae436040f374988e0799edb8174e1cef2e5b27bdbb2b0756e1f8c6fe2c1290e7b28dd1d21a53302da2113a29051850b366a7268f37428107f7ebc4a3aa28ea782f6ddb90ac383a50c6fa4ba229367e69bdcb7794c43b880e0dcced138ef208f5f75732e9b4a0abfbb7906de53f6ffcc7f6d8fb76f8d3e188edcfed4da7d7ff40b11d9b612f02be55656f9c3d303595c7f4971b91c700304c4e7405f0f7ac9e77a5b985629667355dd2f8626e2a03c03c96a32f307e518018cee4df0dd846c9d1cc704ca51fffd0112bacecdfd6c3a62241fa0d3789c3218f29121e995ebaf015283a861fe17aefa478e8f252fb56398b7e8b29fedca5dfd9df4e1e24201f2c1e0cfb2641d54148fa0dd09b33bf99fc04ff855b63e78a4870a6d2e2a7d19553e02dc765e267f21be2f32cdc172b2ef0d356ed593632263a75118b9ea949d714c6a707b035f4402db4f93f2805a2ef6a237894802ce14feaaa171d1e0d97d47817747e3730eda9b41b982af05a3e8b37a57c501de60d78eb02ee90756311a6dc708a82583bf7b30905fe08b1a645e8ac5827453dc09b8c7436028a933daf7c89c4545eacb08d9318822d6479cc841f04334ac8658d519fe3e45985499a17995942c16b974331c175b850780f9866776feabe644b7a652d7b2aacd60ed95e7d40c653a02637955fe09a985332e8e7696533f1056c4c8cd0bd854ad0b883a966f50eb58463900b0a9860a7476f5ddaa5e636d9b8a4935f90e8449e8ebfdd1e5c205fbca4c89a35e925116cf30dec5281d904ecdda974dd892044246544d99e2398078b95b40022f7a945c7ca8e2837a0c8bf36d67fc113a40c02e117865c2932804ca0a078c88c63250d94182414df19eab2b051250418ebeba61d53c2dca50e3ff25fb7bb8b750e271580a83d32d05ba923250616c1978d3a5e7954e90146f1b0098e0b98920a18045e19e242a141640309e563a61c2c6da804a1a1fad260b7858f2c29e0985f30ed40e901a40282c80793dc14304a22a0217ad09a4b2507931640d49e1ae8b9d4f112068bf8cb061cae75aa5d3ad9d215373fb739a6cd6f361fb179bbe6aa3597d51ca6e6ca130df6b6f473cb2ee468ffc06e6f543c869ce0c1ce0766f9a48e69c9043c1cfd693d8fca9c997ca189d9bb36f4bbba934b1ba6f87fdb02b76b30b76c4042e7277678ab9231e4081eec7d64a6cfea9aa6ccc0c5e17fd6f5b0ec99ca1fda98be6f633faa3d81b461806fea1d0924911a501f3de071b4d1f48801cea2dbd07352c1a0fbc8a46ba5464fa9bf6ed4bd1471ce8a378a3c3b7ee27967eac51b112423371172f3fc4b503333c2e63cf7ddbf1fa0ebce71ca9d4006eb5b3932a2c2f1c86498ad8b59e337af4a5e32fb868192fd173314964d89ca5bb1c153d7a3878781f19895e8f97be432d4a3d0504db317102a651346dc8ae44c083bfdcb64e204e39a174bf125d20c770a647769755f45100303b0b443c3f9fe690cb2ed3bd73ad56aa901c5952f33c0d016641a6f9ae646f65ae16b456cb22112f0be8a1751b72e08c85e900d6f5c67ad4cb6402006574d312f44c4c647b066881ca91f8892d3659b8634a3075c7ca5415c16a199b1db23b951a48a1a08cee9da11e37ad2830379434225178f167e460ee5477b52411d48c935c45f89f6e385fe89c3cfdd9659c24d3762aaa039e64fb5281e15b249864606a23e7cb63e410fd9f8248212dd33010b433acc16742dd991c0ecf4d2387e54aa90171917aaac4b2cdf32ef9290b85a9670142b4897ddee28136747b8d266468abefc25fea4bbf3ff10792df3ef10218af8466af8190b7b194364f8f17e8a507055ff7f6232e3ddd85e4a2a9edac61843fd7e5424ffdbd7d718e41af28c4d2049fc2a593dae7578cfccb48b366d8527a267728147095e66eecde838e65073acdbab4dc415a909819f12ecab440c607eb76c5048e74970000cd13add81ffb614e058e32b0081cad40fa9639aa77ad3eec4e741c29fe66531c8d735b5a87878fa3c576b0fca38450d45776dfe0f52cbaa0c2021ae51219bb9b6df9cbf792d921beb950eb707c824ff395791244500d9ae86bc022d6662de91329c49d6568392efbd9447307707802cb1e5ebdb86ea1be72033223d614bcbabd8ff15b65fe679235c8020974f4ffb3fe9ae04287b4109c4eea6b1c3d2a6b22eed5df75ea9bf037f49fb42e8bdfedb68f1d7c8cc79ee0f490b5b689450dd00887f38dd65d7b6a875f9c5b0935f63d4f7dced6ec7d0e1c0a791e22626faa7c1685161b7f0836632e7cb340f743f3171870720c530ad9af9938a3836b82bfb6b183c0181dd2cf9077a5885bd1d16cf878380e8003de664c6d9bf4805afe9ef7e1006b934bda60c628ee5e85e1815668c7e15747bffd61726e0dfabfc44774df289e1798097e25f15f4ce37daa0a1f67731941bc8eb69509e9e78ab6b062b2ff3de95ac2990fab0d5742691da891faa4ebed1ed07c6ea121574b0ff8ccc3c5fa8d227b983097ca60d25cc79c25d7c62b5d9638ce464fe4c0362ce1cafc2f1ad9eb94d165012dde61a64b06673c81847d510628168cc2019d734d8a4bfcc8de903bce81ff835c1abb4f97ea41e7964f041c1bded3780a990491be9fc808fd06ba96a52732b9dcd05d9bdde45f864b966ab06a2589f424e70bcc0f1473348f26e27ca90b3eac23e938cb567882519cda4cf5869c3fd3d00f99be02175ac64f511fd5a133f4b45ef73e42f82fcf5e946b37a89c705632a9b267e724a17c3eff204a71b2d94e75080c14fadbe5a44281f0cc561f0890a7d6839e87442a37959115a7651d89c1d5fb43e1d682ca32358061fe925151c7434047ee984ac0e461090459b36003c8030002600e04002820003814013964482f09bd808e9347517b91b49aeecdeae778d9a01ed04a6738c43f6bb38fceeeeeedebb5c8ee73c0d0441d5c6d8dd64b011210e640df7815c2a8c716244d468c8e87e5ebea2826816d0a808639c5dbd0c684405618c53850ddd0c1a7df2bf842c50cc03095d5006e9d214df63a62a59f7ab972d667830c679c6385aa6a27cccf0b8a26b95a8d0a3bfacaf820895a94c55c1182763c64425fb15f5b1eeca0844e50bc48331ce658c716018631a8e0bc7c396fc550408631c2d3859705c344873843330c67109854a2d8c71b0b038575ca192c5180789159c23fa5f18e354c131528431ce8b0b182f50c18d0628b9d1008b31d604bbd1801163ec254949e110195251201130c634436e30a0099c218cb9bc585ffd0f3fe28ebc23f0483c228fcc23f4484a8a4b62b9972425c5bd2429292ec94bb283da23168bda23961da307b2e406f9e4eb92768c1ec88b6ecb921be45e57efd0204ff598c5484a0a357a2da246545559cbb2740854c58e28f21b99b94021908f51696be9145ed51d32a3291e5775499a6546533c76ec702f0af78a5cd6885ae9e8a0a528a8f3122d65d19c06b957da71430185603ab0aacb03a10baa2eaa8345a52a776ec7a2ae11c8a343a3ea763e0985595e532d3c7a3ca87ae9e8d14d49c1a252b5a2f90204ba1be62a893ef9fafd499e78258cb106307605bb616301ccaaa60bc4698c7138396e2400e50602aac09918e340c1c971e30049609a23efc457a419dcd10c6183000d206e18c00c76c300617074e4cdc5381c30992c508824ba3030b8431295700ce5d2996048a08bb24021d0c7033938d0f14067031d0e74feb70747e7031d12e8f25c1ff2400e12e8c29143377b3ed0e95c1ff2d1e940073a3a2eab54c1c0c074261d3948a00b47c782622654dd1048042a55505c23aa82095d5135a248a0ab736f87ba3a30303317a8a22a10e504097455a0244e74fe2a914430a12b04e602fdeea874b610ab7ad08728225b88b585dcd08ab585585b88cbf5d6f52e5687c8088b2a3420c65888dd00000a98554d9c1c9c0df0e1461366304e8e0480d80d0e188cb1110f1b3e4e1e610501361a4798c1185b725d79c9cb67296ea5e4c606079d14d2ad9474a8ebad3bc4faf7312a12025d1f415c5ee6bebc456446cfe83b7a17951bcc0837180a9bf158971ebd858945cce8fb44aa97e29574a68fb997d5b19e9846ee95603a13f54aa64d75443aacaf4a9455a240d54d02c4a2aece942314721cd555812ea93355a04beae48091d175dccbdc109e2cd2adaece882ae2438fdee543f88e8ab05c54cb13b1f4e541b5682514908b6a09f23e64320544949778f4e82d491042127e2080e72f6af4082d3ce2028e208123b270c4041cc100c618c724048c75186b04638c1169b0353046448131e2068c314674c118233410c8880e638c1102608c35c14ed69004636c0d3f30c6cc606b14813166cdbc0c18ccb306e908b3d68882596ba030b68601981a6a30c62c35e6c0d430438d26dc8031a6c6c546941a308c710aa0c60318538331c616818545fc804d564a87baf4e56104314e16f1802aacc10f8cfde144115858c2738dde251fb93c2e8f10e8fa28b95841568e58941552e94c303033a4fbd25972451924baa319dd79caba405749274451505c0fdcce97e28b512aa1aa988e252a55d19960604637744b5c644ac9d5a1f468531dd02cd5931e5f2c554ccced745c44f988be3c0ac118db3c8143712ca7471a6330c63a2941568e403124c8ca91211b2b3609d0cf8507388140c30f8c4d3123102904b23a23a0d028833186060b181a58a041d16000638c108320c41b36374e08b10203d3c1c21ab94af2e084403821105000220e8c31269349ff547426aae5fa0d068d8810a1d05d5129e91f15214223a6c0184b4016ec440e0bc84bf270458f227910401e181e00e139c1830ef030d349e9e041e201028c5d25ea0e74608ce9b0aa2925e5e48c4b9cd18833f49022023b18c1aaa6336c30c63ad7edccbc8c1df4c018cbf1a59997c1c1384fb003b6430f3b70ea600936a2407478011d7ed0a131d649e95c77e6654c3873e08292fbb9348832527d287457469f84f278a68ada1d253b042a6118181049c59a4415b5354b163030577442a11b0add95e92a914620987f1f3025d255fadfd41e81a6df9dd0e78aa12e8fe77f8f3e742d2981482ad6b55b665e46f417084452f93c9a725c998be33f2f1e6af4524c20d275e94c1c50722b6be665feba9335f3321688ca8a4be7b23a30745f2598c71794a4a3dfa2484bbe33f3326f85f4bf0522a954216402916ee7652a4a8947c6ba57078abda9ce54513b26e6be4c337f2d1898d1a8ba1fa22852674481965ca0d2050a5d4b445549ae6904f2d199ac49a513040606a6130addeb8e4059c058305774de2a0273dde92b6a6572e980481706347de727db9940a4e956a5ea5ad6e71f755886a874a6edb9a89005b2aeab6326b43bd6b54715b54720cfb55b44f9c8cab5c4deaaa472ab920a09f4b1461488118c6dd65083b1cd2218db28821089606c8308011c82b18d210ac1d8260d3418b366462a2870608c69c260230c016018f4016a8d40886108c6d806123e90e13146166cc007c618c70a121126d061019948c2109430e40617ce3019639a218c6081170c90811ec08031a601810dae1012060c805006634cf3c5212c90a36a220a2630c636402c0806f1c5135f448231c661000ddea0081deca08c3830c600d0031c48423387144c848031b611c4236a50024be82010933166e2039800f0e0a3862dfac018d3cc013b326021032ef6c0ac99a7c22409602802631a2584608c3156011d644c8c6930d3e4d0cc6860341ef8c20fea5383e2a2a8eaa40b77608c45420c275ed020a20b4698554d248a6964ca1004b3aa49001b31b8b40861795969293285e7884a106ae4d15f4d276568b81008d649e9a45c5fe511b562b17001c805197139e1021066555317d7168030f7b6d0612d968a554d230a6479b4a0052020998e0e174b250a8d01341e8686868535a2019a169de2f1b408617121628d78ac142a4484bcb8783c299ac6d2a071e20e1ac646d4fe1e1febfed096daa31135ca011636168074526e270634e8c1152471053d30c62e70016602c6d8cc531153a950c118eb006332ecc40a7fb0aa69e6653a7081eb5d5c585c885cefe2d2434f610f53c8620a1cc6988fc9b29ed8d50f99266bd2a3b7ae699a7999c9092774e03bad7426cbea4c1846b37426ebea0af4b97a5169f44a66a6c9d22c23aac512fdc572512d57f4174b2874452e14f542ba4a5c42d7a5d2201e56539feb835ca31fb1502d1411fd17d5a247f61a8182dcebf670597283cc5c2491bda8961dd65f333ca2007272841b302470ae114854957015025dd5fd50f6783ebf3553c216097479662e897495581efd5f913c36b6906136b62083d9d8e20cccc6168060366436cc868c04980d19223216b3b18505988d2d42c01893116010b800032eb08902149e704284059c0c210b1b233881d404262c61a384240c014727e52a514330618ce981314686196444817552a88f1232704046178c311da39b47ec648c468c91823110c1e2e45383028a31d230c61126fd9d2751f9e64fe291b9af44e67e152ef99378aaa9fa8b74f5f5582929a2bc44085e08a10b2160c63a299d14fd7a04cad613211224e4a2af4b7a22fa2f8b027259a51efa95507a64831004086c0042923d8d96dc2022d0657d67f4232ac9dd9d1e9d941f81421d11755d148fce5495284b6495a899c985ead1e9dc0ea9d4193d894a91b1405567a484ba6e87b23a94fed199f47bbe2ae5b7462098ce57a5eb661d54f523d8c04c2874bfdac0058410b1483a2d2a3f5e3a44ec8b8a252245c7f2e8bcb81409d299464fa274a733b574a6ffeaba5816280403d3b93033d58d1951a10b63ed4dbdbe158847678281295156746060603a26e874ee757b74747474b84421027d5c4837c8073a5f519d4e87049d4e871ac1c0c0d80e063a134512dd25ffba1373595789ea6c6aba40a3520e248083279ee8a468904787b5bfea58230a0464c4126344dfef619d84a1c580c489188e10c30f623481313154c098a53f44225d252e5608742fea45bf5544b39cd46047f115e9b52714ba9eeab78a16ce6c683184d9d0a2063e2cf00463acdac0e26236b0f882d9c0020ecc4616066036b2e000b391451166230b12301b59f480d9c8420acc461667301b5a28607345a7f39b1fd0600963334b948442376f925ca3abe49380624ad6ff664cd3801330b6b918db5815178c6d482fbaa30db519893621c636328c6db660d706c4d8e6538298cddefc466b966c3263cc6473851560144094975c91288348d4bbbca68258ff3eae7779994c01b92a2a74adea93804645aafba195aac4e35dd6050203097821090633052fccb092929292e241c2e60ac6c0c0d8065f0644e5a54508121622427e745819272f28c10666e3610c264891168f9542c5c5c506f1f420a252c590229e232a4160ac6a9a5c00802537c8c816f171d2020a9cb000082ce0c1ac2fc50c420f498430b6d182b14d169b2b365830c624c066d8890afa6055d3e7d15bd67bf4e865f2c52346822a090af8c0183b49c18f920d6670d2450936579c7461e3a40b93cd15272878010a4028c081820b783ed6fdc1281fbba58855a487281f2189f2111797951d1d1d2e5154948f50e8ae84ee4a4a0a0134883292929292e219bdd6a08f0744ba1e929592b2436fed70fe99c5d23b76641249857a494941e958b773ab924a14b72add6b749554d416f735b582676e10cdd22253514aae1f4d11baa27ca4a2462091e7af2baa5e74479e0f953425a2f68865478e921e81643c17688bcba2449eeb7a3e0928e64700b22a0a64fdefeb6532e8b2965c91b63df40844c5922bd2208f282fd9f1924421ca4752525c122d2a8d3c5c887465e3848b334a9d910a176330c6d809174bb8a013498d89440ae37648308c31aa8564833a834dfa4fa82e58a9a21a6321d047890aa5801118a651a9b240980a61d208c6baac91139306b5dcebf61001e144d485284768112120847e30c63affb93a9ffca45149874c7efd3970743ee68a40327c90c9810c9609c1166a9c6cf106d601812a5249abc85049a6932da82d560e01bac109680620c9d8e4d299a8ea93e8d1b59eb8a17d3b3030fa41a3dbf958f7f391c4f4bf3d1ffadfa52df4fddf3b7e7b965c911e51f8e4d38979039bae1c1847478f3afa93dc4f8e8b2326099d18109cec34b611f610c62618980e75c18c4041604854358160a611d5f924df633f804d5645e1e89cbc0e4e9e6a473036cd5c258fbffa18fd9debaa4c5f956262409feaa2463842f73548833c7a44619d84d74b18d34718b3343dd10f60cc7ac969e43730c67212586794bbc857584594fc083be204f3e1042f610c86ba3a3a445df8c63de3e45e30209188baa2eb966e0360cc603024eab23a273053c04c80313675ae12d581519e5e54b29e88070b271e209c782863304a3ee692b480841645602b4fe818a28504b1932cd89045092c57e94c230a64dd5dbd122ad451993ef992ac2c0a808522b0602758d8008b1f6c8299ee757bc028f9114c070a22dfd13357444d573433ba576ce10a2a9c5cc1c5151d2468402206242840320592089c583109c6d824a49302a3a72b82e9401112e52313e98a60605260f4e531bdccbd82ac1c8179184bbf55daf1b1ee8f8f753f5658f180932392b8a28ada47c0305923953118634796c8509d202b478ec01ca1ec48873176445f1e2755a4a12f8f2ac818a2594046d2507262640c66e407639305322280a9c8198c154101a808089254eff2228897193036cd601dffbb0322812e98ce278374c0fc6f0fcc0ecd42025d2d3b5ea2d861a4a3838a285cb2c04625172fa6202b475cde0552d7e845549094941725d488080988c810b921023388c00622508108dc904b0c49c2c910312698eb2d4fa72373af94141c9d0947a724f20e1439acd01d0981c1d18181e93c868199c19ddffa8a19818634f6434a3142dec0c65011429a847418c39deb8991073a2d6930d61246470b6919c2a6d0e7510bd512040c2741bc604a7e9f0439c2262bc7579408476766748330c6548064e12aa95e53231d2a40c260534575dc0b90232cf40244003b764431c5195360610a1e4cc1630a05b09431e90e1423aaa32366046261c00f347e20e1c791931f266c5a418243c8440241713b2c275208c007174e7c78c12c017a60a1c70f7a84d183480f0aac2862e5082b455600c0430e273cbec0c30b1e4f650f2a3a501972a2328560122130420884840082401020a00108540082b76347193b4eb0234767031cd8d46f50a8635da0948fb9504c30a1d08562facbbd9322a4032c1d28a61c2e3154088710181c9dabfa37d2ffb8dbfd4d3f9fc16e19f796f14ae8f275309bc14efd28a4b5c95a71bb7b97c16afce2bbeaa4635de1f3cff4aa4981860cd6fdab8f6ebf924e7c2f7623d09861318e916a775f679ce1476b0cd633d575c757f7b7079b1683cd5a66eae6f4e6f2f1f4305829a99c785f795bc639c3cf749fd180c1668c679631e31af7cd557ea63f1268be605b96d729d5fbfd9657e2cff4f69668bc609bce4eabfb8def0eb3fc4c774769ba60a97ce75af6e857d297f1677a1996629cef8d77c35c757e383a4bc3052baf86f41dd674575aeffe4cbf286a0b565228f574e9cfbddfcf7da6d390460bf63efaa26b3fdbd4b83efd4caf224d16ac7eb4ddf859ebedbb661a8305fbaad6d0498aa3de2deffc997ebd25b7d05cc16eec52d3ffeab2fac6f72b8bfa4c97d158c1e2bfffa8a60ddef7daa69fe90e3a747f2d483c95a60af677d319b73775a5cec5f69a6a99a669a22d483c493454b02fca191f428d75db3b36cd142c961b7ed37e33fd4cff1180dcdda7142cd4f54ab9df6dec3cdf3aa1083451b034c6e7f2bdbaddfffd9f7c571e54bd4cd30aca8b14334fc5c7ba3f5650a6c99a261e3d824c13d69e998a12bd08e505088ae5f1a0ea659aa6185f75911ec7051a28d84d737bfbdefceadb3864d54ac5791ecd583045f384169b7b91ca58dd73cf41e772963bdfeba27cbae97c284388a0f04059415141190204c50a9144a038038d13acaed4a5acbf75735bb7fc992e35d5529b633d9b603786327e8c59ea5c5dd69fe9a30f79f227a91ea933c16659617d39be2d7d421797602f7ecd5f3b75154a1aa512ac839edb6347bf6e98694c829593c2ea22f5a66b84b221c162b9fd3a87b9d5baff08f6458df3cb75bfb661fefb993e0279feba6e84183dbad6a273ee5e9d4fba0a37c4126e3cb36c0e672c82bd9366fa18eeb99da3efadf24043044b9b839ff3c4114b38339e26ea71e85a90783439d00cc14adaa4cb2de79a5b6dce334df9f50b1094fd44a669f27860034343869df0cdff6f72d6199fef31ac86bbd5a7b13e7a67744f08f67e8bfbe5f9768c7837f7994e5d759176688260b1739fb86d29e18e0f660d106c7cff4fdeab776efa46acf9816d7867b9b357ec28def8697c509bacb0512aa1bbfa5c7487517e95b8be5aafbbd1353db01fa17e54d6eb1cdc393a0fecc412ea8a25a4efc5fa36942d483c59687660756ccef57e0f6fbfae691db4e70ed69c9d6e3bc6fb3439b0f2f773916a58eb530cb50607d6a957f7ee3f77f53d8f506a6e602584d0bd4958ab4b87eff16ac4c8576397dfd6333af9da673a94d306b6cdc71262bcdf7cb5d54a414bfe47f54a28205a686a6077de9b66b9affe0bdbdf22d3881a4d93a834922d483c581386a591c639ffdeebaef97e116a6860f36739678cfba393fae1aa998185f3c1f8b48415fb963b7ea62bf9d1abdea5ba5cdc677559af6a6460b774ee6b8c53c35ba7479167f4a1e933242a47d7b184f282826309c5bd060c0be56c733a3c219cd4551857345f588de9833f9bc357b176cd85401303eb28dd5b46a835844fb7383530b071ff934e3bfa70be54be1e3de4a1f1c2627db79cd8dda62d3e793d550acd0b2cd4b5ea4ab773f456ecd1e3620f8d0bac473de1fb165b7c3f342db019637761bb2ee5ae55c6fbd1b0c05608a3a414d32af373d2abcb7a9a15d889abd3b56a28338418e7cf7490f3aa51816dd76f7c9fa3f3eaf7c9cf74a93db78a89ee7c4ea149818df43e8cceb59c69de515f96a60beb3b3a4ce9ad3a53593d86280aff4081751d71ad52d72d6bdefac61358a8b3ceeebc55dae6dd199ac03e9db05d9d5feb7bf1852fda1258bdb1d72b31cc1f6bc3cd7d8854baa225819518d3eb70fe3767fb7bad11d82c2fdd994ef80efad4f833ddf33c1ee9666c7e1e1b74953e98db76ed335d5fd29b9658ea646e9fab9eb0b947fd33ddf95062e76eb2bd19256e2ee7bb9fe99687d2d06375593289fdd776bd1b4ae7b5e97c8b94eccd4e6794d039faeee5d9aab82c8d5756aae3ce2faf9ecdb20eff366f1b4fb86b75b38acac6d9e6ab10ce5c5d3a282317d63dfaeebd4f657b2f866e15f397e4bcfaab52926e1fb3dfe8f4d689e7673a755ef25ca24da5a0e4ae526bdfb33e7937fc4c87b2e479abba24e8a2d2e8b58ca8960fba57f184f151fd991eab1f8d588a886ca559eb1be9c3faa8cef743a4d24b0b59edd03d7e3c5b7e7869fd4c8f2eeef412ed17858ccdbe9d4b0a7f3b75b9c2cf74fd1ee832d62dacfb7b8e3e9eb7e1978faae83b649aaceaa96c41e2890159ecead6b4dea95bae32d7e7f22b2abb8f751a370d1fbcfe60adee6a6c41e2391203f153886b8bee30bcdbc116249ebbedcfec60d4ffb4d4b866573df05acf49f3762ee6986715a2ed9bdab5f69e9be3e7ce3fd3ad6c1bbcd2a3db75badc1ec3cff4991b7a1eb1d5b341ea0fd38799cefb9fe951c7b5b33da61bea97b8496f5a8f404a46256ae4a10e63a36e334a4da7d49542f8cd632b7d5da5a3eed646ef945ad827e1fb3d9db6a65fa68e24023d9c85adbf719e344678f59d3a6261ef9573ce371bf50add83b0058987e50a8b2f8cf7b617bffd6e34cfdc10c902f5c8a0d11d325b907836128bf596faea8a6b768d6e193302c9d466858d53c28750524df7cb2e9f4b77c466ea3b377ffef08631de275b9078b0a8c256e95ade11c3da707e554f5328f4b9c916241e2b8cd877e7307e97ee7adf9df7335d4f9417295066462ad33433ba2ff2b520f1d822166fa9698d71c6ea628bed333d5ea2ad8197b66ffc295f7eb3467ff75a1217db9eded327cc3bcf27db2fd12c31a34b64aaa6c922511e9f2d483c9a4873ce1b9dd2451addd1199f8e19c60b73ded1f7a59fe92bd3a41f44a2f234593e88a0b8e6d545d210410456c64d737b2769ddadbdfe4cff3ca2eae79190910f492d129532c4badbae797cf355397774fb894c1953c2afe325da8f24c4bebcab572c35fcd8f0a39fe95745e18b81968aef75f9dcdd31c387d2a70812f3b78cb7f37d7593f033fd391020d5cd86f76b8fd62877764e05150e525af793b942eda8fe4a640afaba3e7e757b861147f799aea91796ff88e374d1c557b59cae35f543e319e16b78d3e7debdcd450a3bdfd65462ade3cdf0c6ff4c0f9144208ffeea7e2c3a7d5c09e7be6d6be95eb7ed5c116b257518f5a32de32a35fd58d3650f79f1c3fb67ded965dc97125115c9ddcb8a7511e7ed74ae9adeda5afc4c275195f6c6c3be9bf7c6ede07e187b8e9f495dcf556c7418c629dfacf8b679e167fa8fa850f664d0752fcbb987c07e6d7436e81a7d33ee563fd34554b521ca8b1428b424a22a1fd374f198a6da82c433021058baa163196384583a4779ec80afb99bee218cafc53a3f739a5eac2e12171f8010ca286b9d53c208ab73533ad677527ff4cdd74debf8624af87bb8282a8dde25da4ba2b0bbbd971b95b04e3931a49fe915e5450aea1ab14c53f517a8fa8b4544553ef2935ca609e5458a69ca9f649aa6133c60f595cfb9fdbe559a698ad530eea971743543ec5002a9d361ad5552296b83d29d830dd3fb76c44fd21635a582da772b862debba6b8652bbcf74d2cc55515ea44099a6a9a236b547d3e4099144a05a3d0e225df7ab05894753846971f60be78bd2dd0a698b7b531588b47556acf56eb061ba71f6681dc60eef97e5bcae6af73ed33dcfa5f4fc4958a07d3abb5babf60765ddee7ea67b9ccb4bb49f7c228af28474e5f10cc0d977e375f0b5effa6f66f7997e5534046e3175586e1ceb4b171f9c9fe99e499bd5fe9aac723e8cf1bea93fd3a9cf0ed81877fd593796713ef9f3335d0955c5b89b6ca5bad2e734a4b14a97ef677a755950d80961750e36e76e9c6fe3cff44bb46b111d56ea1bdff49aeb539a61fe4cd7a3eba1b4bb6905072cd655fe9dd59fccd9e5f999feb14a244f456da982b282f282f2020425bf11259748f5fc493c4a6e35630b120f900d58f933b6f859caaaeb73ee33bdba9fec6917a8aa629a2ed19ea6afa86cdd2a509a2ba15c25118a0f2228282f40503c2db620f1549103be369d843256285f8350d2378992844293d5f342fc78cf2d5fadf83fd3515ea440f91180ac12ca0b1014cf08e421519596aeba48ef2b2ac316249e2238ac52e5f144e089255764010db8870a796246a098182a04810cbc86f20406703881e3023a3a4f586003156814c831818e049c8880a80210704f455d77020fd0ef91d16dc0014f34c08200031c1581056ce0c6fd2a02270a70cfc740c0864f2001209188c28100e7c4012460009f40011c0608e0c9c0009c88ca9eaf3cd7d2f93d2e23a323e5b7a600663cfa3d9e6bc96320000e250500b084527255a009195dc7c4b9c71333027dacfbe9709cd8b887eeab44018d7bfee3f1640b24f25c4b7e024c035f3861072b685e7447264ed8e110cc0005606b28441936607260716070606f606e606d606c606b606a606960686067606560646066b0313031b0303030b02f302fb02eb032981658161816d815981558151815d814981458141814d8139813581318138230bb60366438372800639c1de4803176c30db9aee5095dab9a2c1d30c604c0c9c10d376308ee180e04c011c373033118aba8e90d531532706342b9b181336e6ca0083772a471234715d5e4b981a30a1c3d18db4242214bb33848a5bb87a9100fc79101188c71be88010c18e378f102c6382217b8bce44f7203c50fd6cc48c589f2112c5e053a04aa2205faab608cd3c58c7e21a1900a631c14588c714e302a6dc63826d02096eaca2a8c714a30a25a40fb758831c6d990048c8e403ae767987b33b7aa546e7470dce8148031cb628c93a454626cc4525da322a150c94708df8b6ad121eac78f00e4c218e72251d5a888087443d40bd56229ea8574ab97b742d7c78b6e8b0e95a82a96d8d0fd1e548b4ca6808c8a7c086bf1217c572eaa4526dfcbc5b2d9faeac305a70a1263cc7ac40d09c48031e68c88f212cdf28c0c710f4a6ae66518e38c5a73334f054714bad7d58c7164d8cc5311d221d06785b3c5555d12631c1063cc4483aa526808639ccf671065c40a52f9b8ac593ff8d87dadf46362282024aa659a62a8100709f6b597658ed9699a7f57c939827d0e2394126ee831e018c14e096176f7ddf735bbeb6fddcf90e9f1194e116c8e73efbaa3ab0f7a73fc991e2a89de0b10945029c8140a81b0e5a99e344d2d483c158708365e9a73f4eade349eb20ace10ec96be35be2e7396d0c14a86ddaf417f8e677bd0eb6d44e08c612ffca75f6e5adf376f8d42b030d7dd286e50e6a7f5bd20587c618d38c329a1a3010708f65f67daa6ebdda07bdb511b707e60f1ac39be9c2ebe5df7f316249e0fc70796c2fcf4a6ee9fb3de2e520fa707d6c57d2bfe56f3c52f3ff7a98e0776570ce3c3b8bd6e5ca973e9383b70d145275da412b6571d758e5297723b6f55bbf41b3fd3276b9aaa0e6c75f8ddebf966fc1546597360ef84dbef6747616df4561c583a21cd5eb5fbddce75e3dcc05eb737f6771f2b94ba6a8e18566baa5f6b53ca96f17be7d8c03e9c2f6e8f7f37c4346f4e0d2cae35eb0721d44e6ebd61186fce27299c8e7aadb4ba7cd4a584ad3a9dee71b294d971c64e9fa4d334793c5b201194720656c2f9a8fcc719c6da646d014706d635be7fb17ef1fd690c8685cdff21c455e3e8e6cc9c2f2cc6f2e6ddf2ebf877baff4caf28cff531d0e98c817d8fd1b93d71cc30a82e5f84efb9acb14a585f742aefc7a665f6af587a385ed8f65ebc114679ddc7a61fe705d6b3c63ac33c69fdbbe305ddcfca34c180e3028bddfdcf3aa3cb4d7ea694d302abb1c319e3bcbfe680c3021bdf5707e5fce9d34187ad993b649aa6c9e3f4c980b302eb33efdf0f4ee960c5faa7c9aa1c1558871d6d33eeb89bfb9df767ba470251e121048814d223dd07270533ced75e9d4ea957f7391f7cfdaf5dbddf8c5746ec46385d582c65bbd2c9a7db413831a42d483c5c6c3261f56e18b7df9bce29f1aed23798b04f7f9611bb77eaedd5bf84ad383b0debf57ca56c2f5bc2c257f17becf2c3b5e2182b61b1bfaa67ccf5dfd3345f4a58f7bbf7ccd75d7312cb3709775adf93343e0aab57d8a85f8fffb8e91c71a4b34949d8fb194b3a218cdeb693fb335d826013099be5ac1ae60c7584d0e9f6a499971a6c2061a7cef875bcf44637ddc727cdbccc5cee1136cae9d1d1d8f29338d67784751ca1946dca8f92caaa1b613574b5d21625c58dbefc1861e5738de39e7abeb75f736b38ded927d6d05d352c8d745fbf715787df6e0e575058dc22ec743f5f47071fd4f9c50ca122ac4b5cffeb6bfbf5f4e610c244d8b75bf5bb75a3984a171d22c2428ce1ae59ba7be194be213c84dd1157adafcc184e09f33684dd5ad7870e1b8434630d6121ec73fa3679e7d6ed5df7308469d8a6bd6278fd35d7a673114234acd75df55348ab83f35b8d4e080bab765e1dac8edb75cf4537f21a199b41d826e93fc5f8ff394871dd08c23eecf2d637bf8b707e08bf8a1ad202611d74753b7a1fa6b9c938a31b59d900c2ea7c31841aee7bff60f76bed4129abdfea767c7eb04db7d9dc8bd35ddebaa9ec838555bbcf786973adc2fa900f76b69c216eff516e4a2388a4123d49f4242f0a9b3dd88da18bf9ef83f8c15661947ab08f5d46f95c7fcb10cfcf83d5ee67a6ba7abcede6c7d7060f96ca1ae9de8fe6daae76d73f649a9c43256cee609d379cf5c5b5b93b1fd633ecccf1bed357d387d03967073b639555b6776785ed466f9b3ad8481dad75e79cdf3ff8900ef625be2fc6fd74ce0ba763f1346133077baf375cdd8bcecdadbf1d892d483c483672b0d1fd1dbcb8e9ab430ff2314d4e0e611307fbffb04207e1eb7669be70b0d3e5d99c7f74eeb9ba1f8ccd1b2c7d4e9f83ee9e84ee5b9ca6699ab9a6a9b967e67247d8b8c136a72bd6d81bd61147fcdea60dd63db7658c8fc2cfd849ffcc6b74f335421de98a9e67c3067b698c11c64cf19ef7bddfacc1d6872f6d0f4b195fcc72c2296cd46029c6304a2869ad4e7ecb282f4050a85f619306eb32a43852d922dc2f6f385b163668b04d6a2ce995cdfd379f26778fcf57c6e60c56435d5d93dafda519fb35838dcec588a5abf04ec730089b3258ec70a5903e86f2a3ab940c166b5a339633e7f8cf55eaa4b531c3be482b8ed1a5ded4e5d72d282bd304934c93bbc7611257fd099b3158f91ac5db1fbebf8473bb602306ebfe1f84f85eb9b1d3e7320c565ef71d1f3e3937865282c13abdb8d2f7bfbf3907bf605b7ed0b77c51ee2a7d632fd8fa64c4d3ddacf3cbf66417ecf3addd7df8a64baf739661dfb97aeb7359b787d833179aac6dfa6b3ea1a46fc6f6eaab746e485fcef1e1087d0b166239e17b94d2d7a2dcdbe3d02d61a3053bf783ed564ca9c42f566fc1260b76d396e1d3bfa3cb32d2384dd4e35f51d9f39c0e3658b04f36f735caf95c7e2dd62b58bc5fce33b728e7dcf0b7156cdd8fcee668a613e296ab60a16eff5ea5145e77777b2ad8eba08bae5d6627f1d73d055bb1eb76a7ccfaa184aea560f5dd5b3ef79fdd9e2e7314ecce3a66a9f767d862a60d0a566ef7b8e247a79333d227d8dc1c661d65a654c2afce09b14e3963a4b5453aa3734edd737539c6495fff76f25d13ac3f9637c2a7ef73fe9eaf0261c3047bb53f6d0dba5ca93bf7cd9760a3dbff1e6dd95bc35157226c94601f7e956e4cf7cd55d788dd079b245888a774d0e5961bd2f8ea3445d7830d12ec6ecf9dd6d83d77f57e7a7304eb666d99ce789f4698b53682bdf4ee4b9dcc157b9cff3645b0d335e8793bfab06cd3e10d11ac3bbd5d74299faefbce21588f51bf0d5dc6eebd2b211976670addc6106fddb4dc730c5b278534bbf7307cfc7a238456297432ca48ddbdb9f9a6e749ebccb0ddbfdb692a3741b0f9efd334e3f86f52df739aa6897a5c8af2128a830d10ec751a47d9a8a45e1f27f1718d2ea8ba46a6f7d8fcc0ee992b760de65767d6ff363eb032d2983deb9f145ff9d88a4d0f6cbe3bc60731f6e8b055cc039be796f93d9e0e5f46d97630a5bf389dcb0921ac933eea1edf9d4f737bafc6ebdcdbe8c0d2f934c5554f7ae59e8fe6c0ced7f6e329f3eba9f79e38b0734aec50d65d7596fab5b8b9c1d6b1bebc70e7267fcbf7993eabd88861f3cd5ac77d2176f1e93fc8b2b1816d52d2baf3a5b7c2e724ae418bd5e19caf4959a3531873f6d95ceef79cfe7d3761586f7456972e627cddefa581cdd427f67a71ded3efd3cd0c2c9e593fc750572703076dc0f069b4f9c2caa7fb52fd91d2872b0c51db33535d9408ca0a0acba8f471c920d964b08981fdbaa594f4697d7fbe6b6af4fc35d502e90606d661faea47ad298efb46ea8acd55365ed82cf3953f29a4f866bff3331dced0e60516bf7cdd75343efcdbddfb99d2f12ba14cd388025dedb911362eb05ea3febd318514baf93089aa7a4c1389aa34e906d9b4c04e2a5b73f836dd71bba33fa689fad31862c302fb74bbefeedc536fefee33dd237d04ba958e71b3022b5fbabbaed1eb3f5c7c5081b282f2a3422a6c5460f7ff74ed51dcb69ceee9cff46bc98fa6172028d6cc53314da42ba2ed8b4d0aecd4906239f7743731cdb4b620f19c60d3857552d2af6e67d724c451bb4cd3e47bd064c2d6772dd2ba1f75be717d7f2dd060c2e697f3e98f58cfcf748f878aa1ee12d6e7bb7ad75ab38313d218b2461a4b58d70ec6fab2d3cd6ba4f733dd73ddab84adf037fdabe58c6e4aac3fb3ba15682861a18ef1be7d71f5edfa65731fcd246cab325e8de1c7ebe8a655121642b961ae52eb2b618eb1797391b0ee0f35c4b7ba1cdb5faa59a6c99a2648d83d1fcc7a3b751566d7ff082b21764e37750ddb9eb37da63bc2e67bb7bb12d23cb56bdad1465897a7d7fb686baedbefca08ab778e2e6e39ef7e4eeffb992e3d88660d0b2b6caf35de94520823ae1a35ecb737e79ebb667ff85c7ea66b7da9d0345d1f334d282f5280aa17941537324dd384b59c3ed02cc27ec37ad67f57f1fe09f557258b0a650f751e52c9234351a1e9505ea4401101ca0a0a1094172028551176365ae77473bebae1763a11f629ce2fe78a33de7adffc998ea267094504fa585312143c4b280d22d120c256dc6ebeb2e6ed5adb957ea6874237c3910fe79a43786c323aaf50cae91e2bac52ef483dc35b6b7678ff673a8cd118c2e6da6a74cdb51d6375df85b0b2e5f96ee78777dbb3ed34ac7caefe7b155634ec6719df8e2ebf2d6f9b84b04e37ad73c6f8ff4e2a7f10f6c9c753bbf836bdfeaa20ecac587b73f25dde9d6120aca49bde9ff7c1fa3e9d0684ad52cbf6b6abb2360df11fac5feabf77753853ed37fbc136e772e797b339af15f7c1d2865fdfd81c6deedbac7cb00eee16e776a8db8db9cd3d583d6bacd0319eb5fed67ab04fd7e9aed2f9ee7d2fca3cd8e97253f81bb7c7efee88075bddcfa7ae46d81c9d0ddec1c25cebab8ef18e58c7f9ceb00dd7ed6885fe133b766e07dbb4c4f4efbbe72fbb6c9fe99f0ba4044eafcfeb605b9c0fdf4b9d9c4f660ae960a1fc9fb3b9bab7ab38c73958ec9ec75db1ce7a3ffd1767171a3958e9609d0dcaab318cb8e1cff49811c8aa4af92b8f7f8cb6fb3e1023282b2854a0aca05ca520d354bd4c7545d3d4dcbda64630ba6f7ea0898384b3ca3a3d3ed8fcd27ff3fac43ff7f6cf7419dd8f69b2505e80f85481060e16d756b57cadab3b771bfc4cb7e816346fb0d22b8ef76aedf06d11de87f222c50f1e3d3ed6fdf1b1ee67056505e507ca0a8a4bb482c60df6b3cb49698d6feaf63c96346db06e7bc618ea49337e4c331bacabed5e5dafc31d5fb6e62ed0acc142d8dcc62dba9b90c2a6d560e16f5933d5dad5f7dc735c68d26025bc5a62a7a76c72c6499f093468b08e0662d897da499c218d70d62b5b1336b09edbdd19ee27659e4f710de29bcd5f7dd2e3a4f7efc30ed3faae63af3a0c5b75f55cdd8e18ea56f1d32668609ffb75b4eee84eb37f0db94c93c7290b9a98817535caad1b94cfc117a99481bd91eedc5af32ac1b0f5cdf85cfd9c6583ffda17b155391dbef6a874fee89cf16b8d6fe2a66b8bf2bf361103db5ea532d34b73dd53ba699aa669aa1e779fb72add0985266060a99cbf9f8eefb1d6d5dd0b4b7dbe8d35d51acbfb32b4a2891758b9ddbb9ab6d72fef875d132e88aeba5757eb8b7236e7e094b2ee4c6393fe6a85f15d132db0b166382f74fdf68e1a66165887af7e8f4feeec6e7bd7c40aacfcfbbcee5d5da4f5fe6d41132ab0323a8c6fd2599dfe0c5b91699aa6265260a36bf80ed256a98eaeeecff42420fd7a54eabcc29626bab0536a182fc6d5bde61b67262cfecfd76ba6515fdc1c62c23a79ef73153f58e557a82f61316d19ee79a7ac5a4e6709585d736d46e7a48cb3b54a1f8e9bbefcff39be39e9ab848d2f53f83b6a77e5d378b530a184cd103b3c23def2f3a32e9a4cc2eadcda8330c6475fa52f4bc26e08e37c5df5ffc5d40251b742a0ab143f601209fb103ae9a2bb7f6f7d1beeef412724acfe4b5bfd2d276d41e24181c9236c76fb2bcd0ea5a39e9fcf54943ac23e476b836eff7514622c1b615f3f9d3fc21c1f851abb0923ecd6b2e29addfdbef7e78e8e7445cf88c91af6e537a7bed09dc3bbc66da2868d1b3bd4f5cdfacfe75e84ad0f66dd76acf12fd6ad2ac24afdeed2597fbb44c456e18cf4c1575b6c3ea37c77e6cf19b79ae9d337041422b95497551161e186346318e9c387b03aee9b9dd38d33f4d79c212c7ccfdde67474dfbb4d8f282f52a0cc42d8cfaebe935e2ba6cde33449c3beedd961faf453f8384a13346c763a699337673cf3744e08fb57cbfaf4c67ace58ff7eaeeb9a0cc2eee8e4d4cfa3a33e3d3eca10202895642208fb6db6165dbed5ebcbfa036165a55bfb7cf2d587de2220accbfac95cddcbaaa37bf9074b9fdecd699d17cb77e33b9431f1837d52ee3b63ab9ef3ef9bdf16267db06fc7e73beffdb27e7537864cf8605dc3ebbea1d7e8b237580a7d459ad4640f16e39de5ab1f73850fb6fab9ee10f8b92e09ce5c0e9ae8c13ad4f8ce9f38bb2969d63e885417e999e4c146ec2aa6d8b77c2df3cd2678b06d43fa70c75b257d73e63bd85df5bbb33dad3f6ba8ef67ba25025517bd2c575dd619763f58e7ddd2c11c278ed70eb655afadc52ce7deb24d5907ab33de17ca99f7763ddd9ea669b2e8603184f0650867751721ae15e519553f9a5e3d6d319943a4aeb917a573d7bc39a7cf29fec7f9afc39c1f749fe9a28af2314da28adaf30b1339581cdbc7fdf423dc78531d078ba96b1fb5a3b3b58b778583bded69d7729c1f6bf63cdf60dd7575c737ddbbf13e08dd602fcd133e97d76d57217c6db05acb7ab3373d73feb81d1bacdbee93665765c317c76ab286382794af35589b74af7a8d34530af7751bebb761744d0d56d68c23852f3bdcf8e59d062b218c0dd298e57ccf2e9aa0c1d287334ff9a0abf30c76dedc248ed4cd0b5b6e750a133358fd31c2f872ad74ffbbae0c36dedcb4966dbb889d636942060bf7a47fa9cc71d27c6533e349585d95b17a954e424add6b95af69d353ca76f78e63b0afc1aab1a6eee2abf14f8f471783d5bee9750f638e31bb285b9078909884c155ea2aa4ef39a50d56f86495fba76bd46bc43a7f8bb00589279b80c142c779577dddabf241e8ae11881a22dddd63f205bbe175ede8fce6f0a51af4f1fc084056a5422bd3a4bd609fe67b63de37eb28a98e265db0edc916e385d125cdda6b19d667c498beaa6fc5eedbe482c534c2db3e5e79df830ffac0640b76dff8dc848e462ce5a6aefec0440b36e2361bac1b4b59e13b37c9827d9d719b18eeebd8c94ab1609d731443ac778d353748af60df8354be5b697b2dd6e756b05a6a7f32ca2a698639e62ad8e71762e874ef26f73ba7c2a3d335395dacaeb9571f7d71c38cabe77869f3fce49d82c5d1a9a32fb6ddb274d8440a56bb59dd86d53f6be9ea8c828d7ace5de7bfba27d63142c14e3cf175de1afdea74734fc84ed6f8227ef0b5259838c1e2eab145fa9cf68a2fae3d30698275baeeb8e58b2f6a1626d8faa4ccb255b8b59b2d4e4dadcc23982cc14629e1ebdde2952fab83cde918264ab018d2f75a9f8610bed69c4912ece3faef41ed0ee9ccaf9a20c1be94f56b8b976ad75b4b9323d8481fa4ae9b4f8df3aeaf0b1323d8af9152fa74e35c217e6752044b677b9ae775b33a4c27a4461e13225889b18c156afd24becfe3106cce8f636cfbc617abbc900cdb668d2fbbc33bfda5dc6358d7b6dbbe679dbbb9d75908eeb0f96cb4c1eace7993da51f8d32b76ae9904c15277bbeaf7313af92d5eab2aadb420f1cc9800a1d259ddf317a99cd3c9282b5462f2032be1c68ff7dfb7f1c48f9af8c0c679effc6f0fe9d4f84e931ed81cab96eee9a63fcb4ddf0a131ed4da22a48dd6e975428751ceac3dbeeb727bd51bccd26407f63994337f7e79babfda440716fb8310ea0b77cbf27535c981cd8f5229b5d34d1bdc90e2c0d6db6a9dfebee3737b6f88f22245893462b981ad0e9fce74befb78bea773b6c0440c7be56e946ab7f38c0dba1b98d8c05e5c5dcb5bc7086b9d71571e98d4c0ee8735eb9fb2d5779b7c81491856ff6d35bfe8ad3e5cb7bb1398d0c0de9de3942dee99216d3b5ed51d62029319d8a6a767099ddc91badc6f7a48602203dbf66ef73689f7fc4b617ceec83001c3ba7497ebccade278b77eeed5c0e40bebd4b39b73e64c738d71bf1786490c2c6e55eb08a9a3d8b9b96b7581090c2cf4fdb1de9fb4c17dfd52e73230f1c2ba28f3ccfadd1c658c4e5f60dd9bfbfd8c3237e970846098b8c06e5d2fad5fe1746fb3c516931658ecdae9085d7c9b0516ef3bbdd1abdd5c81d5ae9f6e992bf488f7862ab0bf1bad31e6f7306daf3a9314d8b96f8bef5adee8b5456ad28585ef3e371f6d4fffffbf4cd8eacde9c57aeffa2a7e8d8309eb3cba8c1ba6f4efbd385ec2c6cf9936bc6bc35bd6893996b07e5dbc5a3fad5b9c32cb4ad8dfcde5f6f976bb7b63c8a1849dfa3a4d25752927cdf2722661719bd5c9daeac617c2481d7592b04f1dad37e35d29c5f3bd354dd3f4924c93282fb1ea4be24479c98b847532e6d6de6bd5a36ef09ac19e8f5522551e1c48d8a9f77dede1d7fce627f323acef2a1b7cfa50baf8f0e538c246a8277e87df5cbb31e32e388db0f8b5abf1ddfaa2965a5f4658192f9db8debfb27d94740dbbeb755db5a374ebf7d5d5b0cfc57875c51bcfeb54cb45581d6b95ae42eaff9f735444ab55d25a69731456e80ed24a3dcb5aff52fce4a53127c2cefbb2eff78fbfc99bdd4e13222c94bf9f7c8c2174f2cd79082b35c63b4249b7a4dea43684bd9ff3db4e3e9413beef42d8ecee74d2e7f6e6b24ec3ce878eb3f60c6bde556b346c741e698c54ba5b5f9b84b0ba36aa27c6987ac652d241d827b5df981fdeeb768659105657e9b5b66fcec9e964206c7cade9fc154fb937ac11100fbe29df8374361abd51d89edb19fec7bc5f73db6bfd83ad523f47b394af7dc61b5331387eb04fd39d5fcc1a4a771fbf0ff6dd777e5aefd66ee3bcf960e76b71e686218c6dca47f760e55309a5d3fcadc6e8a01e6293d21fa5b43539e79b95420c5d7d94621c739bb97d1e2cd4bafaa39fdbefad69c5837ded9a9b8ea17bed4d77efe0688baf5658237c74ce37697408a7d6ae793bc3528cab47e87a66ec5bba1decf526e3f60df586776347649ae40e3875b0574bbfeefd7b72e2e7688374b0cdc9a63fbb07637d6ffb1cecde8fd2ea79527797779383f5e66fb6b7f33b7659df38d8e66ebd59be67a76fcb0e0e56cef6f4bc106677106f7f83cb2921a41e23755929a4713af8f5c9d9627683f58f9eef83dbd54c617b6db0fb5da4f5b9ffe6748ece06bba5746f4ffc367c11e6bb064b9d7458d2981d86b1d63a5b9078288e1aac6cdb5179e5850dde7d7588229206db5eb6cf52c748dd517c471c34d8eaf4b5434ae59313b60cc1e09cc15e88f557e8f1cae8f04f6a069bf59c0de35bdb671af1873865b09effbaea70fc47f7758d92c1d2d7e27eed10c7e84fbe103866d8ff38b78332ebf7f2cebbce3158faaf4df7ea1f6fab37533158fc78bead3dd61a6f6d2f0c16c28de76d5bbac66dbfbd470e06bb23ccf8be8eb04e2df77fc1de5621cdd2b99d9bd3b77ac1bebb0e3a9f30675c77dc5db0baded930843ade325a930e657bce9fcb291fd4d4a7c479c2a7d071c14e8cf7add5619d1d94bac65803ce16accfe7fb6e0ca7bb8f3ee8a216ecadf1b7c3b6776defda65c1ca2863947f6f7599ba372cd85a31adf8db7ca7f01bbe82a5ad5eecf1e5a43f21a656b0f9ea7739ebce34cff96e156ccd0d6efc1c9d0e3e7f8d0a933ae9e0ac4dc6e89e6b9236dabcd67773eafc8dfe9c829dd139fd965f571906470a76ca4a6773d0b1bbf7613d4dd3f4a4992bbe28d8fa3ec34c1d529c5bc60f0af6351d238495eae6b83ae73ce1cdea0ebdbe28bd3dd7a0bbdadc7d3a3bdc79d75a27c709f66177216cf2ce173174b809f6b796583b7a9fa62d4fe8b96e125045f2785a9078260e13acf3addf6b75ce5a1f6c1eda710ace12ac6f4da7dff94ef1de6f7ea6c750a1c75182bd33efdaf4dc4ed7b6a1fe4c8fa1802421c2d7ea84f03975d0b90add7c9d639c25a8241207769196a32888210000c3588c0804a311003038281c0d06c3e1b064b2abe50014000352924c904e3c968984c120c9411845410cc318030000c418038c4150497305f0d1bfe603d604a4f012e1209d3435ae1d291771876f13df77e6bd7b88d22fd3b5e8b4e48f5e4cca2120e22b85a41c82ba63eb89244972ae1107cc6c7ee3d8c655e52130d8893f2e6813040c12bc92551e5514a6a0fe1a73d55d07210c07363d8bab49102383f42b0aad2a0dc77537b5e209586269c714135daab0f0d2122dc238b4d223370685dbdd96ad1e209de7fd9900f2fe59ad442199f4ee2410dd4bd59a6d3925dc4376f3b371d0346eef7a2679b8b58f9d4915a09d175571c142b3e389b9b799c95192ec11e31b52c9cd8a0aa2a2c56366b2077ae6ad659cd9dc703901507790a39142937be068e0f6c0ef992a6b40e554fbc7ebf4abdaefdb54e40be2836ef40f9948707eca6c5327d919cbc51b7e990cbd407e366fdedd0172fb8004042ce280236917fe1e35023ca12bb678969ee47769d23e9b0722a2d49f6e2ab1ea500f72ac54c8597e0333a83e262ba4b6948e43abd660155c4219db02ed93ff44f34a8b7eb3ada302a4ee91a350859426dc997f6a4c1b579e561a103a34f49852633141896d80be0c023b3e68c884e60a1f21614a47585e933d5de3e6979ee3b0b76f22fad20a525901659bd76bb56d81e93447951e040649f4ba460a0258a5f06c4353a609436b38ee718f593cc787595907a002acf76ad5eab7e7818022323dc005ebbbea1683865f34f13ebe20d6ee6024462fc52ec7c4f069fcb216eec3adeb49d886d322d8a918eb49b9921a85a3aad397ef5eeb561e80d7714d7a97b061e5dcad4d70a660e80cfbf0eb8b925fc5dd3e70031c759576017e0efbd3399c0e084f160949a62986c0b891d5eb5c7a5ce0d88fa3f8d3e6a683427d273f2f22d80e62ede834bb95bf15442e52284bf7c5abce5cd68637e8000de790b9ef40ca5195d0224903980aa2009109c4f853a9885d0662666184fa0f0a1576aa427938720fd40203ab9038d44e1750a1e266cf4b86e30deec26123ee6e381c5939003496051c1af50b1a1e2d8aadee9d8a5ef3ecf4d0b863938767b05ee463eac5ad20f76c14a7368413aa6a14d47adf46b7223edc2b2665e0933bf1c2602e0b32a871a43b6c67f4b98c89ebeedea02917a9c630231f5056ba2730aec5b4038f85c21591765c6ca00ba4fa90ee99d46bfdf2047b1cbd7168710be46866f644fc050d6500fa88ae7ac052fd113509d2f95280b4b3abe6c1192fb1c76be7b6343e78220f7cd18fc7a23b484f2b5d1ce6f7060eb23bb71a41da1d725768240d547a32caf4942d9cc622aedaea2bd3bddacca005034be5f5c549a810506915eaa12a9aa4eade516b377d0018071b03a8171f573aa8a922e05fe770b03809b01bf2fac9f0a5118c297e8602a48b1657647ed89257b509bcb3389cb927d87ccceda7fba540a732bb83cc992c134f3b375a6ad22ca67a1dfed124544fcd895a09bb95669d798ec5954e070f86fe220e4b799ad977f01bf4f48de91d834932b1821c6a7dd48a54f7ae85d3c395de9647de772dca2bf71154ce214331491f087446342edd6aa7c525cdd9a8b43e4f382254413500f0d5cd9b9cd9083f62b956f5556cba6aa339c842cd984480660855c9463d9a2150f853b1ac7b2176847c0394a604bb15316697cf6fb25a97cd3ef70702b2caf53420632badb2aaeebb3918bca64d14df2d563a5a4171b70a137bc5886d8a1a1b008dd320db180c143fef4ddc774335402e5d1cd098c6585423dd012e62f6dbcc1e3c986d901371f919861b371491b7bcf31a7f2b8dedcb62c8097a592a896953185ac6019e701bf13572408336c303697df33925809dd6062d2ff8169813129d148472846eb8ad6d2b69be6af5930f7bcc32efd087bfdf4ccc557b1a6422a65fd57d9336ea9c565a8b33f6ebba914830e483fba65da913be01cedd4c1afa94614052b4efb848ea7ef1145b101b037e148df209ca2ad064d7f3c97fac91134470de5dc66a40564f0732ef5653ef741716b455647a14cbee59caf726254c1be4500d3eb3b34c024c77286b95c559b185d296c6e2d468d545dc72f0cc764002e309dd5d2e37602af6c80088d1c6de5d4c1f3948cc680d5e9da1920715e42617c73754306fe1b1ed48059f9b3f11172ff0dcc2add19128b0f5873834dec68df59e0d413e0948c370475358ee606967ab93f8228a1b3cbb58b3a1f7509b37f0c08b03b042bc9556bd06e59f8958d82f47cf7ac46db96988c016ad7b5b7926bc2111edd43835fbf6696a84615965729902b844454793f24ef8a19b6e197429a1c5192dd1444fe08d8bf07e37230756d47c3be75ed3d9a3dea3cbcc5cc6891b438000fa73d4d028b498de4705c6657139b90237212c8a91446019e12f891fc26947ba70c538277a5b344994f75ec610db5833a8cb241bcd8287650d1907e71854d3b6025c898ae874d3aa8efb6a07e3866cc808b2a5ae70fab32f97062623e63f4689a466e6dfe97b4e621f16c732c2e2770987e66e844327f41e025576bd1b7047c1b6025f831d5ed91fc1d93ccf64bdcc10732477138cc13d076b81efa45aced6670098694190faef8044183ab286bcc940279d7d751af9e8b54835e618806e0724113c3d586bfb51328b691d1bb01d371c1f71d6ee9f93790a2fb13940b3c847064ff362b0f668c768e849075cacf264eb35ef73dc6de48bfbb1874bbcdb61019966c7e2b4035cbca753294d191f1c48dc4eb9af7bffa738e2f9fea8c9b036d9729fe7b0fb34b99424ca0691100c869394be3f98cf23d855f71750ea22a67bec763da9ace2ac19db65c80c062cab2d671a0c170f39493278d520822adf6048ddc3a1ed4de3e041bd3b34d1b95154224eca92d7b3ee0b02a81f8346592aeb34b8b53b1b6255f1188b3e43925ffc7394a62693f078a998c898dd89ae94784977e61c89c919da893cae50070b384e776c05ec21a8ec77ef6ec8c940316ebbce8c97dccd2514820495a8ebcd00dfd16011f6c3d2253877d31a94ea27860f19ad1512c7955fcbe5ef8048fdc4bfe01d0d7f4c78bb31dac63501da0664ce4f24bc283ed13f071a1815e5d977349b82c62d209ea836268bccccfb1616d043854f48cd8572b0549a39b8b9eda2d91025e0b4cf4854d17ba7d8f414b3cbb177ed03106f6c8cbdbb88bd6d1c9af552aa2a36d857952698b9731930377d3042a9298007c3838a2e3cbb4dcd919eb54291c6066073cb0ad537992e1934419018dbe8c9f94a8d3e448f2d93e3a4cc8cf7e03d0759914ee48b7cb345fd9628411aa54868343bd04917bb1a0dfb35c03132c3efacf5f90fccc99d53ffea39117b8106afe0c6a04aa507389b10ce93c370ad0bfd1b93bb0eed19a1a073965a0a06dedb632f41ffc339424eca6a3d9e3c596f686273a3a68949974d3148a2ea3dc83fcedb4d9722946c04ccfc6cfb15b41d4cbbf184b9c7304f981933925f5c9094387d64100054cf4a33aedd8ece34cf37ba1c0dc61e1c6232eeec6552a27978b763c22a74979a90defc06d7a1494fc386e3dc6eaccdc8fecd6bb6059888abf41a1f2dcab983f6a5801af059e5069ce5e94e1f69aa99d1a79e6dd5390b8fd4ffb9ca206546c70748b691e203c47c17b78ec7c0d69693644a54c797141ad4cc10a1ca2ac5679b03cda31ba061cfa4ea7c3abe2f76101e13fb33f1dfdbdec5c008bee6ee9d192b443fe876930ad16b5dca9ef51b3cfc76f0b0b961a04b3f3151b4d5068e0ddb9658bf81e5651e1d41bc0269effceb3adb29750e27b0369ec11a3fcac51ce794fbc8957b14e462d249472408f4b1191290c036d8c5805276b7c862d678d5fe4d27475e695591d71cd18c4d1fab72cccf054716c876c040d1a151968e6a2ca25f96321472174d36adca202e5ae654d5d23ec454be4f367c3156762a8fa76519e665092685c5b2276619b42031c6a7eb571525fc232859437e203041fb6b2ca2524a1962964b400ca1eb43c48a4fe1761df4839c7c75fc203f185c40a62db1cf88e530c9a72bb3c058c1c909bd583fc37a310eb48a3170629faf5806d75be2554c7c1e6ba2fe4d74b0e76cc758907f95a1b52e4ee6cd25aa50fd7551084651adf113ca0144692a36ae68eb606b05e7f28682fbc00810c163cbbb61b30232200323398ac9d2a97502cb89aaaf19bfeea204d59696f295fa4a8b069adb703ed4321c004067253701a05f07ddf1c06d82f784295f2d070683de1b50e7c4438a98b15954bed0fdbab1aa50e09cb9bfc25ee4af02af069b0c1fa1335aeac504140d44b9a4c0dec0b1e40967aacb3e13ab6f9232bffa0bc5701832ab4852ef9d2c2536bcb3fbd211a6f32048e6c7202abfa89b50afeff47086755a0c9b99efb198f2722c92628d34798de4f997c554fae88e532035e8ce3ed05c714b9acf070b54182c3120aaf6e47e240a37ba14d1ffd3edcc633854e3eec662b76672f930c18e7bb52d76ad7d7c392b0b5527e6864d8e736a462095b0e874f3bfc100938fd95aa94c4d5de17f5a5c46967224b4db84209f2874a27cac73312378bb33a210fd1140b280a7da4188063f6720947bce629a0c79387a12614e5141d67afd72cde3cb91f6e6f67a26f00c806e3ed7f6227fcdd4d08a61d83f04cc6ff1c67e1b9c533ed043d3d88942220f624f0f34cf893d5afc6464dc2b44415d08a254c82ebe39403763bf4da2f1971abb8c92899b58845c436edb30e3f01ad7641a01945a80e3557dafb78f140504351c01502459d158944d815712cdaa2fa361de301218c37687c9e14bd29ac4e3d02f9d683b727defdb4413474aa6d701ebc63b010d957358633b727e23b068e6057d9e95da0d480d4fa015335b7cd70ae37212f10463838c3faffba48dfcf110bf7d43b654800fbb210a3a8e1e3378ab264caef21ebc8afbf492e7977a6386ab2af304a0015ecdcc86e7770c84bc6c00854ca9f52387b63da4b338d533ef50ee79b87ba5bde1baa33956f4ddbaac7d344852bcd258e2a0440a4b4193b835a62c52a48374e804e7b36023cf8d468943d3c8d3c7dc0bee556f3188d37ced74cb43154d3fe672d0abc62784a2a14955524a1eeef8411732e1288e75636e2f4cb864cdc72393f950900290d40d5e4c1514eb38b4def48df7ce61ffc6117e9a95e39633b8921810ca612982bf11d34f8f7658e3ac8286ffe45765daea317fd09bee29b4b2cc041bf725b974a1c90813262bfcdcd2c1d5dacd80ddcd064c042c6569248938b12951ac126971106c0224fbff5a5eb265ec8e439cc86c7c6115d970dec37b1942e4fe57a28319b152f8b04b500a3665208d51eecb5bf8ed1158ae8d6407cad0c0cc9eae158f4b34cba5c6f5220bc34e879b27c14766bac95c47d6d60d1f4d5914ea76dde5f621197b608dfaa7505e02067055c889a50548ad879e7e9432962f4c8f6a48693aadbb165aa169e7815c234c7e73089a8f458a22953a807142055cc59dad09d0fc459962cb2b76912380973ba25ec737843ada56d3c3f05521229b085885b8b04c0b87a4953b1ea1c47a4e39ff55019fc67a1bf3322e699af85ec14cf07f99f61c8c193b4e328cf2c6bc5abf1e1684708af0061257fb538aa7caa0ec0287c012f1111674ac37446b0ee4c0f6e1fbba4baacfe8852ccba18214161c3163b374282b32a9dd9f741e8743af8ea20d2fb9c4ff78309ac74529f31a2db9032678ad0f35b408934152e94839d08d97df25a32bfe52093ff2872de1ce01271e01654336b1c847e9fa1efb27883a5a4bf65720946161308aef9854f5046a8715e9596f4f49f0d48ede46ea6f98b98018944724520fc398ed732fa3a71104501e75ad55a51c2b56222c86162f08e975151fd3053fa2fe36a61462827c15fd96e9d84946f0594bb99563dd1004bfa616bcfb1a72625366f1d3da405318342770bdf9849a43bcaf5635d1b95afb54c744c340c242e02eadaae187ef00e44292cdcce7e24014f78c134c333c217f52085325de9d88c765d7ac4596eebb1abcefc5c2f30c8eb54ea9a7b16107b624d58f67491652d602fc1c2f79fb1c6860032d1982d731e5178694a26492cfed9cb1ded6acc9bfbb4d11d7807ecdda1c674bc97649efb39fac5975a7b7935950db420f6e19791f3eefa9eccef83d1852ce2882621be19a106f5c846737b8c706ab5db8af00019c36a86d3a1aa2e3fbef32807277f855e1781fb03ff6d2901ee1f1166ed3efaa111f56b865bf8b43ae26ab5f97974d2c32a5832bb799b86ae2b08f2d6f6e9c493bd65274a15355c33c5a759793af7b33110a1f49ddf2d208e73c73336255756b3c1972a20390e6d40dfc36e512da31bc131a5ebcb01acb5801d5bed286404efc987670b8771a1a4d230d28821e85b7c8739c6a447c257cbed362031e6699caa0c615a852ea6f51a66e134357b2c944774f00b0e574da4d90bec886579d2a7b9bc8dc584a51095b7c2d77abce8eee5720f1c0fbcff7f6bd820244e1fb15dd581b551a3ce84233a4f8435300a311cbadf75883a23a6c71ff7dc792a4b7f335185d09b36052ae78b59eed97511309dbb03462c2bf2fa37b2a100c70f91478f813536a5c5bc19a9a055b2cfc8596c82d802db1b25d06e82bb8e7b43e4c96ea6b3031cfee8c9258f1ca39d9b40d95ab04a6145363491285e2cb32456357a4eea8752419fea58a8d59224d63ba12e37e10e40697b269895194fa5161099b890ed87d01afd460902a26c443bbfc10d844b39f491cf35451cb82a8f47ae0ae3d721cbef5601eab344f0501bef8fc62b4c8d97c88594460f308f5a8709d3a3dfe001b6e3ff7dfa880a39bb936fe93388d3eecbfb738ecefe3698f1e8506efecb8818e79ccdb33bc085fb986420ba4f852b7b829317ea30af255e92fa49ddd49186c92f1c0bf0245bafb0da7f02c72229cc07742e2dbd4973d0f7c4e69f9f07a10288ef30a87c3fe87389cb95ec3adbb4009b51a708d8d30213fe730fa893bbcd09915a2dee61d2bdb849c652f015f993244107ec1e1d6dc3e7a7cbed8e611fe1984c2a37575f18a707592ef8c05c7cb94597015f67ca3ea0cc9a5f5c623236e923ed1a4659240fc8de602d1eb10228622363916370002a48664d452303531ea906f14b22904673ee93064a9403c81e50a6e733271eaf7ffd5035f630d5630154036eb0d98753a7991c4452c12e32d31335e254dc50c85573a79539c6ccd9a673c1e5dc0b101eaa6bbc70deaea08daab836e669bfd6e0d421b66629bb472b31c6cd3a2d99166f4c046a8712022f66d550c2acbb6ee7bacd042c6991a706576f53ec4b80fe978cef057f7928ce7a3acbd7134438b0f0e9a80ce9aed9e1aef06525d374cd864c3d321bf9d71742de3751a43d101c36d6585766d3332cd0c4071600845bbbf8664e8b840467cbdd7e262de437418e76f90a91c609df65c69cc5243d4449ee0befdfd09fea50a18b32282cc18a8e19cf20c4307fef4a3df78f527d31c525744e0f841d9326b5ae2d6ab1c8a02104190ff9672c413e39ddadf26616970e71a6aab1f7fa23c8183b5ef838286812b0af3bc4c927fc32e08ef05eeea13515ded9721f27ca08533a16db6a194526873096e50ba84acac458998f128b12afa0efb34d7b07fa817e88756892e4a9ece229fc0b661fb807ce7c8c1640459aedf0294a46f0fab1156895f1f962c1785dff574d5c35eb27d606f4ae6156501d1d1727b5c387af24037b607c676aadc2c22e6ac49977c123f73dd855387fe5f770986ae09b75585c380930c1fa3c5a622fa45650148e2b7a236fc5112ec9843518cd78733202666e7dcb0609176997dd5a38bb8470b03c65e46732f8ca7cc8d9dbbae66cf82d5883bf079b9fa331de720a8cb3c0075df83f3e70946d1c65064bcee6222e57eaeaef8d723748adc2d2e8c510b6f3feb3abe53109fa54cf4482d070c16340447a01c09111b70b841fd20f3efc6f773e655c377a321fe1eedcdc73b1dcfe213b19f49a6a7c539c92827befc238a126ecc6f13e0f284f2b79eb52b7b89c8d516f96c8435a4d879ccac49bcfdf083842faa4f2a7da69a1354650c4cadf6dadce21cb00370ee5bdd900959ee2aeada93a79307e1d970f08c81d2f6eec0e13945b5e007ec9fe336a190cc935455173c757e94139ec7f088107515e8ba75964720efc80ab11476559fe6ddf24a9820e4fd1fea945478a28aceca0083048dc161f5752833a040bdf3071096ee6d4ff384ab271356864f162663e0b7df4c3d7f328d9b00035f5afac843a1e1e1545d7dd45b5808533378770b4b564efad222d39322ee0c134c31df9cf7e359bf0c14956a7b317403435f45b1423c0f8fbff6db92232a134b7c288be9170fe836f4ff31e5cf7a5d4cf55e9338e498153cb7fb069b10c8130e878d82445f290c3810787db32d61e53ecf0ddc9b2e636a9cba1caaee693336ff3245b60882cb95c7235d3a64ff167d5d7979962c00c05f453a2aff10e4bcd32c998300c819d11d25573f22e6af99ac4a6ad5d406a8fa1fca73636b696de636f73f1be2d00b9910f0441cc468a39bbf07a359ee3df95e60d006fe139f25af31b3a592f8d5149b333681936e23c7b281f06e90dc113fc3156e0747ff89aebaea458d0b1d99ebe8a7410a7c7cbcb59cf9ca0992f12f79ca8cf48834a71b8cf22ff4b0ddde883bdfb22dfdf3465d1af206cba8a0310a8856bc78ea60f272c9c65bfea72b7f939e6e8e1837c622fd6f0062684fdbec5440b3db1c39ebb080ce212ae0dcad2fe98f45fce431334cc468cfcc76885118a93aa95294feec6aee14da4eb3e13497fe4f0fb2fedefe07b272dbb41f2e1db785524a764119c8779fb306766e42d7c3ee99632b1cfb0c7166d6d4246f2cbd068840e4b33125acf57f640fe8414da804574760c1364d7a8258017da068dee8c8e5c95d6c16d3ef753dc4e0de5b536a05b586eb94259f97bb73f214761d3aedbe8180027b5a215c6eb80ec13f2ee7a712ff166ac7b5bb410c3460931c8699ec4a462d158c2189cd8f63d8923bf2343b232ab5288c4f0fb78c0f58abf5d864ec98b52ef6184c886abaae534c1ea6b8c93570aa33c904974a75ccb92133495ab1bf762e04888323fe78a7b73a66e57b83f93ad857abb774f58a5a3d03c1d188e744ffb048f853fc5097debea9d699f057113c2a604c7af6bd36e7e370aabfeb7a52462a87bfa4abfbce03472994bfa094472500f9c6972410a86715bfc292d1600b4d2dbe9c20dc14cc510a1de1467d3c2ae54964bd4f662a28f87e952fe04d00f434dff42817749a45e7d922307c6f8872d01b7366281540863226298bbcc325c78e9f26cf1f8c33bfe43d068b229d4a183d1810d90ede1645a5b4ed61058a5236866f156537a75c3b078a1221abdd3202f6d2b63aaffa481e92fb8a5218d648f773caa513c0a812b2daada1e097e557a0322ca27d917cfc4ce2ec7bc653b5434c02b0bb7abf0a0abc519082ce9f81f7a22d77bf0d8b4b03337a0cc4a227a7263b75715378d0da2175686f0507dce30ff54392cddff78f603ffb16534f00b7998bdd686e66fdb2507e4eafce53b7deb8cdcd62aee51df8b3d61b7f6e786f3a41fc5d7ecbc33dfe674f05468def77ce66303f5a13c6a405cd3799c12e94d6bc17aa0655f20fdbf07c80ab2006bdf3239851d2c7344a7e4927bacad326a78384d766d52066756f056f632300ed96020fcdada7c1e93eff11633e61f54cd88077375a36eb6048ec25586c576689478ab27761ace68a1a13dbddf2c9f655c84cc6474ba7999c8623750e57d54ae018ec3ed1d512fa77667ca8e2d3caa4bfb8610f605f9511b12b8eda1462f6a5a43c0b080504e15d5bac7d1ea1f567b1969398bec2d647fe3202511ad0eb32f3d753461a9e60205432034facfbe4ca5172af741c734de82c98e54ebc692a43e4f9f7ba6fca27232ea832287edd40ecf3f3074031123d9fe424c098e86645e8d801fff74f7fcb7cd3bf6dfeed00a758bcbe003f5709b5dde564eecc61331970d60472c384334a8e922ea8325682185b6afce8a476f214007c55c33f0182de659969480773f4d100a1da8953c754526da94927cccacb7a275a533f0dfcb6887001ec1c34d614c4122d50e64a6c22a8678445d3b4cfda7d48dd6b28ca7a764b71adba8ff5f976f80facad854d9d26c79acc48d5c1317a6bb32a8fa8e587f3b6e0cc5e4d1c7f5f3d1b67891f25c69be615bf99aea536160397e85548ff16810fba74cc51f9fc0b62ad0c3980dd8774c6295b55c820f085ab0680ec5520d2d562550cb5c3d90adca7544d69215fbbf9206bfffe61be4664e3bbcc5abed44fa0eb9375a093409b721397610eb6e706046e3916cc96f4e995a2ad0b94222b4148156a2ccf461eeef4ae4f6c13c6db9aba52c10235d8b9f73f7d528ab15414386342410a1b91c97362c0e395efa5d63994c41cd6a117d059d9b342cf504115ddca423894926d99270f833a8e282060b52eb90e94ef53cc23b0affab46beb7937ce1dd929daa4f7ed7072b1388d58901b99649b92b68a23687f9d8f12a175573e7933ade9908d51eecef7b70c7c266a24fd4597009164949f105b43511265f96cf7ef1a38a3d04718df71e4aa9854f6b0f8dd01e8d3793321fb6074e12bc935ba76565a82f301e9672e57e0928be850937ef0813f433cb6ac39002c16749474d4ef994f36741daafce52d684056db67cd91f9abdb6c88dd2b21053539d02fe6b71f6edc392f17fd7a383c0d9996a0e29bedda0f28eea1bc56710f3164f74649bd95e3f43beba7c64914b57d7d19f0cd64f657c4540005e6884ee32a5ee72e17da5e3ea31eccb63b4ac9dd7d2d957b529a33449dc74a646ff8c2e37b447fbb6d58928000b2e1a03ebce81ef2f1c512cd16826aa04975a4b3b155f37a31c98c72f329260b05473ed1a729e16be59bffb86aaf1c9c7389d098ac6bed31b3072c99fbfa21ed616f04db5e059f6756c0c6deba8ad6f1aa4e89c49eee2f48e7ef946f11abe0868678d384654c6492956a5b73b4269cd8846fff97bec1675ecd296adcc474a43e177d1ecbd70f373a13cb0ab024bb40c2924e1e0bd26a0a6a5a1afe3ed1523d43d34c6a24046661c8bfa92fdf9c6b957b56f182bb5adc32d5c188b8b09624df88d4bd1ea7359bcd6c17b6e70458e1de61d0b58cb96426630562a01f09852ed3c55a951604a78a55d10f4dfbe263119eae9244d3184ddf7d8280421a7730e92bbc2f317542c4a86d426ffa877e83d232559319c7e2f2eed9217667cef0273c8fefa33319723aeb6043337a398d8a867e30b9fa901bf0275a2a3ad285581885f4ec1d2d15c4124f7ab98216441c97de5c7df659875dca860207f968018343afdb0391a9b556f825245753321d4913f6a2d0809dfcb09181be95a2d4e50ae92309aa5dae217b94cc8aa51c2ced83c86713f31a378d525050ea30df2cbc3bf90277ecbb12c91ec886596dbe494d638c5aced7e97ceefd77215fbee1e91e073a26e0b4058734f57391868224b332e2419cb7a558f179a550fc015e81f01fb4511c60b2acb7aa2e7251a0b9127210147f7cf67fc0f535d2a8805b684ac1daf23d61509052f1051495e5fa3a725174291cdc3aa648751f8ddb1e740887bcb1009e52dd9ab2bcff81f2f5f42a6f5135e4ae6eff60ba73014ac464adebba7257d7ae61f3cd0a9b8385578edcc1449e1e03d582309e141e04a2e673c23bc5393ce47f176fe4e7b6385b1e6fe6a0836546d23f4feb63d8d5d6d8cf3ad11d2b2076acdd995e0eb71f0ac203a446d5694de43ebf1d33b53806c98a18fb24d4993023bdca8d1334ad8d1bd4a34b332ada83b6eb9a99a2e3408e1654cc9cac0347673de1940b07e601628cf1be01c4ce72193dfa51f4abec73ef1ea7fa1fe0ec5c63345584fdb5edffb6258061485ae9bd5319d77c964d8192df39c32630473fb57505e681c0a4d5ebe5ba05ed00cc4e66cc8535c1cc37ecbaf62b50dc6de0389f3e7e8735f34956ca680689ed480c92ecd0d2c9c12384ee20a3409a3535039c8e57033823fa7e08f9bab6b8d025ed5a5657901531a3c715f7539a01f507ffb7e30db1a566dee04fd25a8cc078ac82114e077dcd7e0054f9786c64afe39e5cf3a29f2400646bbdf0cb12a783d58b7d694b42014d1d02245c85467e7bd1dba924e08957981390079225361d5a591c7af17b58b2d1949b3e9ef6803c615934bad5ab000545882bcb748a9a0d89f77630d5f34ed31bd4a1d062dabe3db8882b2c2244996cca7ef1dbcd6a98ed2100fcda521324f2832eff6b07ec3910e3fa862b6f20956f1b766c3e98338f0b62faef8c7519fef2b54c872ed028dce011af2698b52b0ab4f156158bfabcd88f048e114c588ef80aea226659d9a1b3029af1ab3c2ebbbbcd1a3602ac106559ab91d9745576d4ed82f1ff3b0f15258f6cefb6f17310f42bc0c648712ee56ab8e2855b6b27384c8414fdc26733490663bc9b308558c477296d5e0bb5c4c0c62476f24fad1a9d1e21ec4b635a849b6a9c8d33272e5ff56f0836fd9f52d79cb78aec5cf84eb2f6865656adbb628b27901fdee94d7e46d71849bf619d325f574c1911c7e2084d5c688a722f09db81a64ebab7f6f73baabf9db5cc103c4812f00b017b07236c8c28ea8676479d8080585ec52514075c195c728ef175747a07bf7ae5d3223cbe7de42c3b4c22b1e222ef132630aec3346c7a27510436d5dcaa6b44fab97f359f33b7f2aec63797aa5089ea4772e43e576c55dc5d49bf75d9597f46a2ba89b49957df7bc48b76531db39d3436b7fb0503d4b7d089914af4ffd7daca7615d53f9b1148fd3ce6880a6e2f85ba0dd64e5c58bd12068e2ae9eb53444f518dfe01b19f4e8464c43b9e2d04555cfebf46c606d5b9021137819c2517b90f085c7c3d56370c17639710f0f6c0fe1c910a4645bb28410824212309244ff6fe07ab020407375df30d184e6b4fac297f7193aeda6ac1a9318d3255a44a92ab1cb4d2e190bdd3734c72478f11519bcab5a89e80414a1de8b46840337335d263c4f9239a1877d2935b26d2584a87d11068116dcbde4f40cc57993f3c6e23ed5603705ed5c0bd2162a162361740423c0b8ea24d50d7da39ba2b061029c7cc7a80cf9d2973db97c393564d449dd1902e86513c7e20fe6e11b4a576bb3a360b9cdde43fe776246d11152344eddbf2f0397835ee655b118b1628da7147edb2b3b17e31f80d44711dd5c517f53c4d34579230f67d9558822f1c4401d9716d9f8b1eb91856844464119027d81db648e48aeba955e6b1a636d25682379ecf841f4f7b2158194b11bf53b7f8e4f1c531abf5416096a65729c7800cfd03e63a031bbd5e8b5c5d41470509379d75206638167722a1ef4a226f1fc15db28cbe23192088b4f7626795dc9c74fcb13eb9fde87e21e160846e2cfe48e4ddce83d75decabcc335142a4dbcef092642a032f16987e4ea378b19778236a0290918ec54247ed27d3e1a5cdee50cf1bd1429f9a98942b42a95e6df82b33c84e538e3942f8181ad48cbf078455bf9545121ddc8cc9c1d9304ca4676ea0bc7b5af2e6f54a4ad41b0dcb00ba1cbc2170a5233de32f13ddefc1e7b197f4d367d0187920065172e05e311cddaae32d4b3c5529f8f11dd42736867fcfb7b02a77acb885970fe8d3785f8b3dff65cb66c1ea3d96d4f837257b9ab2b095673461c1dad326ca4151bbaf2c59c1b271da9553b1eac7e3e2c58c0868c4a2893983a03f48878c1c9a18a7fb2d35984902bd23590195e17c4e14bccf6ce8ca7546bf3f8758d3f36cfd0f52ee03c13f2094eb26d31054fe543719e22169c550eb1ec13624f765a47da426f5054e36b51e62d7a56d0ee4b211943b2a3107ab9b6d47ee8669972e26f2a21639c890235fa904bee8df725b3597336b3cc6dabb94b71b4f27f88318b435e557344f9bba294be93202a93ed2d6d77e487ed37fe554d96183b10ca60a42d7125a7f67087b5fab4705be593ba29fac55653baf18cee14f315460f163adf52f10cb33848bdaa75e49e1554b63674f4aca286599208e0e40b6059d5a700b393ca11e91062db084fabd33d1d2fc07d2bfe82e6220687635c39e567684aa1d8c6b02566b690ddefd2e0616f831c7ffb5ec8c7cdc6a8cb06024888dcb99228dfda4a316234da9af7ad9d3d34b7c26b10cb61753636d0cf45ea4e6803cee90e4278177c8d3f022e2eb4b283f482b9fd88b0e82d17422b87ba6c0454e70df46210595dba6d139192e4a69986782b3feb3f7acf1437c6415b6a43e8431cdd762b47da76841354e4d1a7da2a8461f0d8bed2e21a422832f82f8a3b6d985c587c9bb889812706e784485b5c8dcb597ee593b4592ad9b2159a000d89eb364f464fa20e0a7aabd56f5ab8a33f32471923a315a516e24552866a8b00b42530ac3175fa6a41e1b4e8cb0210bb1e16b095290f8347fa78169072586299af207264901b76946e3c6c22eb8ecb8d881407d1ea6a4060aa6343f5950c0b136360c9a29b17d3ae49f7be9afd9198b7283c4807fdf42380c1231780d9a3ac32640f90d4270ba8ec3f839301d37ca68e57e3c75493577cb09d5a8444937f56508f4e573cfc863be445799ce1ad53b2af00c34478364861eae1b0c373db4ff245ee148889a304af35618b4bf8c520267fdf9742a2a3419e7c08b4177a6f8278486e15c348808252d0a1ddb124886e4d918fcf9abe1aec0e446e3d60ac52ddf4bbaf560e76425da0db1c2ca35ab224f8977b05085cbf13cd36b6b709ee0243c016c4fbeaf8d06484d4c158056d6e067ab0dc565351703c61c5fa44db99eeecc9ee09bd3776257f7b1b34f579b9080bb63dea5de8f1b6d9583eaab2516b3a1ff10a2409c9325df21f8c5041f42ba1c704aef0b01ab1d1514e819e1f7bcdc638772197ce547e09125cee9ddcbe85708d12dec9f42c97c850da3d213fab473e59de8139e694ae78cfbed69c8557e153ad19822b97c1a450d6c429064d40961e87099ee8074dffd96f9c267b84d060d8edae56a85fa888efced3fbcd2ece47e3361c53cfd14e7a266ce739c85a87d272ba653d169a76981a0ca030238010335070e1d2ae940d2308bc7126b8c7f59183442d394a2a8793dd2344ba80adceeae12b35cb52bc42f32ff483c4e8dd834e9fc31a62ad171aada48f77005d9219b704c826f42fd34fe6a2b7eab23f76d7330410279b3ed970bbaa2bf6d2cdf53710b01c6268a1cdc6c9d8c14194b97c3ba2c77ceb329d6007c3a3700b7ac505285945f46c5ed27bf18dd8e1c3b377b5b2cb80e2f32ba825a855454c79dae680e573087db4b43bd9c8df30cd185960679dfa5a8915f842eee97eedd5e3e6d95a08f794690f9fed9eef9d7b176f2be9e69b0cb3b3a18fafdb3def49f87f9ebdc55e91071c80aab8739b2dc14ef28d06cc5ba2e387eea45134a97c84d329301c66c06cf8cdf9a8c49ce92c3a5213a674f9f3ee7af207d1f8faa85c649f93ac085da92339c0e74eafb3a61adaa48bc2f896514afe2c24b3e50b311a7d9cd0678383c9d995d24a3987d61ddfbfc5691524d87f349cdbcb921d919c553d9c4f935b4cf7a7f0546f4d03554055d161cc5fff20d2c57a7e5e1758709f35dd2b4e68ac22fb5edb7d3d0ba5d2e45c549098d6b15f4e05bdfdb83ee930acb1f17eab0b9b6ebdc33bac712476ad169e2e9dc5ea99ddce8eebba5fd02c5cd7fdeb663810cc40af836ccfa118c868c2926903a2a92ce16c56deaf9c8651e6f743b1bd83988b048ffb9e13fa3f6b64c1f9713d45eb59c853f26ae6194889b21eb365ec8621ac3b1a3aef58392a136ddf912016269b4c6986f94cc77003ce0444657d011b059ee07ce2966e1b808252acf87d46bbf09dcf5ecbb8382aa4ebd293bd0a64e246c42cad730e9780e5e4249567d6e0530cec2789180c1a050bebafd0dae34d4d4e6db483c59e4d381e21f23098e493823252a4d4d10ee7139330a7fcd97469f2901f5f1c4c7aca43623559561849f37b39a669c0fc62bc8f800f08a1abdd4117f12205d14ce3cbf6a2a008f1e926cf7675fb7602c1dc1220520f9ecf1e033610cdfe973a45b9261a1ff5c9950569a6e306831a25df0297ee7b47779f4afb7488822fe89da236c8151bf5dc5e6f9104915a3b0327729f673843e20d77003a7bf2035c5baab53500c7f4a2a56a82991070c17d79e848d861938b44e433455941fdec5540e381968c5075110a528d2a87b80847413f0e3cf15b5ba35fc9870744e595965f89fcf3445ac0ad65cfb104a85d29d9665f43e3e9b2cb01892e4a2f6a20b0ed62843a68b0590bea684774e3374ce08090c204b0f047fd4f4bd2c3a4cbb40285c5f023a311ac2b92f1482600b346a1c72da7f2b2ad7dc87c94984a8d002d159b13923a3cdf2df26546ec8f5bb634730bc25250e543e6040e5aed74fb5aee83484977f70cac089c596f956b3500c9b8e4260b84afe85348e2a292009aa99b358cec4c2a9c512b77f4e47a501ff184ca34e0012ca278f5a946111616b3d7479257281d8b08ee93799639a3522119d9a4600e144b792113f36090fd03fc6b35736fd096900f5025fb233cdbcf227c8d1cbc4034f746704a4a476b9a5ec840e3fffd0d6ef595845ecbd6470fb5578fd8c1e4cf43e0fa02c6b5811e9c9baa18210dca48520594ac15d6e5500c1c7c920aafcc46ac00e611facb157e6cc65d6b834e057a4246aeee6acf295a39d4d526caa5f6c3ca8aca44040ddf88dfd61aa53d8b351bba5ee2c203b482e389fcd781560ca761e1d99ef0a320fe6b1b631dc1e608674ff259a81bb8db022e573545c13a2ded2d8e1bc99261834a1413869e9986064d6f2a8d1edbd25f8a0c33ccac6d77e903f71cee4fdb702aafca2800d61093ca4125ef11949d4e233dd0e237055c035e80d74d7c7d87401958b97f054ebecdd998e728c4f7901e0e34d3183265bd1ffdd6d41fb54926ee76363d121ecc9f4c014bf6cec434b8dca75ea9e8fe4780e2b78aa36dd8dca280f714808b3b49a0a97f81f5393e13632b5e612cfff16de46bd53a678b784b9fccbd4c5c082f6ca6a2f6f5066f64897b1b43f5504eb30a7c769d020e0c064f7808b0aa60b9cf1540eb4589900b078101856f0e22a9c56ff7c79f78d1379d8deb06cae6466f611a1640ef9569b361eff452b0a66409d6190eddf6f68b4251774f7a1d5aa3ba312873aec7dc5a412a70adefa001a060d829d639dcb95c10f5f31eb5b91b9d507fb62ae0041d448ba157698dc6157a4dc62230389200e3fea0ec905ac00853ede5b6b10c96a18e9110e29ee43673cb07340ca0482e4f0ae1ba36f20142836ee30e1147b02e5546b26ff65ce46752170aafab3e06207fb5f6e4cddffc7f97f2a90743ee137d537653997ed3edb0da61c0c72c66f5f37d05e2f9417f26cbedb1c46e37db51d1f0bf60998fceb2d2af98f6e2190551adb6564750078e325ea32aa427675461ebd060adfe7c10a771b0ebef69938c5e74c4bf00f510325b2f6377fafeff694ad122757ec0582a5533a2655b597d670ede106073051d32f1ccfe97ba8de890ac76d002566f8862cc4ac34e71c46397a90c4dc69bfb9cdf5a2996bfc39cb9dff51fccc645f65a0c6e65d4a10e5d22810f4c6d54b674781378d210524fbddacb7b160b3d2354e6a60438a051fd95f7eaedabc5ef678e8739216ba10ec7df47cc525f530d5331d9fab4f36d22fb5ab1c1f86971f6ac76733a5a7f08dff64f0124772cdf6651669a93499b03c612e3fc5c392dc903357ca717ac81f6b9cd5e33bbfaa9a64f3154797549cd8d6c3a084e84247367bd2408894118b0dd0f3e5718ac0a2895069ebd6dea20aaca7e6f9da85ec512c8a2a045a25d837980ad3cbdd1fd5f1919b1ef042543c758dd03aebc94e1ac27cb7903cd7ffb422a6620e974e5067e9817bec7696fd82286e27afe868f86a93212fec9c12f643062b4500acf407954126404b6f90aaa1f9948fb5b1e14025a1f054e1ca9fdc7e0973393493f9749e600dc0f2bf0599b2960f4097a60bfb3bee53f84eb45490b3d5f86f9939545e00a58bff16a11bf77a800784f5510de25c20fd02624579a4c7b9a1cf773e2d8abf4bd383f86e154a7c4373d4973f47f9942456f26f1dc79c7ce36c47e6d34880e1477c9bfe77c1972e5fd983d11c820337bb720cea252cb3a38502ae34e5eb97306116cd8365a59c9e89d2c80c70d0730473b2428120210341a8973bc3486d9ed961421d18dd9a8554c5f7f6ac2726ac190d2b867194c362ebe2f05cda103ba0ec97a89d1351becaa7114e5d489d4c752b096b34695c57db5b4d9a472416d278b02c5d2e72ce184f8892ee08f318e007ecca4201167b59248b72d6ae36ee2e003fd9246c6f75af7e8ac6110b5e3db805e0b685c79d6193b9ec50f1931a3f696ab7a725e65eca58ba15edfb02263e4dac457d0984045d1e6bce4b780c3bea6cb11c8174da3df624d1da90f49f4b37486e42f75678fcdaf44768f1c812ea8cd0cdf03afab5750f52a4a963090134bf57061001437e2a21791d4fc165680b0adb03049551d4f21921d2440179d1943e2b132c46fbf517778b53a2b866e0cfcfc715247e88837376efcafdd0cc6adef66c3b42c863116cd43b3fe612d4c76d0117a2eb5d0290f2c5d40ccc4cba13ca9e19e305b65d71aacaa149c52ae49807e908948ad91acaaaae6d781029d3eaa78550992c3b7c57e7faa83d7f61fce1988ab862bfe2f167832cf30122dcf4c6e3f26f417c483f80c2f9485480437ebb240e148edbf503508d3bd7320ef0a3967b6b0f9891d4fd238c7d95641d001b2a1ce826ba14ed16c4630f498b0f4b77917ebbadab28433b9925cd47216455fd497e6529dc6e8207529ddf7bef9ca266c67ba30276c02780a078fb4780480dcade0a8dc7ec5c54e50385d914d38519f46e107ae81f009a6d52b1b5e8a12cb1ff1f9478cb802addca34313d5fab61ff174044df246da4d488849ac4d8818d9ad73c298aa623cc53966de2871bef44146df8cf69c20a62ed5046e9cb3559195419b9c89ab22159f451837da165db8d700fa328042617fbccc54d92e29514683cee795043ae7002e4ddc799ec28e74ac7eb4b1c5228f19095caec781c1d533bc3dbd101056f24e7246e179bfedb07952a07fe09001a1ced1f751bdeabf4efc75a79ae7085845c548930d77c19bcc9635af481ddf6959a5c73119f82d9804c6e81c94fcd921b6c1674fdbe2c85140f22d30a71eb521543b0ebbf4f65d7c48189b6bf28bc80dc370a369291316f6a871a019690843b28704c957338fe03183586444e1904d2809f7b2e2e1c70f05c187708967f52e7584bd4d7a079156af89b45fbafc8e0356d6bfdfcd6f2631ed99c624ab17612fcf95e301a42e5626ef646c0f388cffa22414bd4293577b655a1b8944223dd75bfa419dc9e74c15b54c944f1bd936945b6292a5f7acc9e783396bc0f4e8d46f7a99a669dcdadd864ff0ccf086bc2f555082ba92117d5e04a291cc35963d63d4abf89b8d2eb133170f561a80b23d8b839ef56020a82bd99d100073ef96a4b03cd226fa8e7a3155d8c122f94bc6cf9c2c0583d0df27a1d5c09310035c79b3686d71a7d6aea10b497aaf641103c5a3eb88d68e3fbc0b76c377eede7b9bb288ff6ce2251706cfd43fdc5696003ee8a046e96050fb03cd97a2adf6f07668104edcbc48df43bc963680f5b391942e35c74edb0d1326a789f6703d283fda21ad4f2d9dda3b1e984c580ce1aed4bc9701dde029bad9ed70df7883a58ceaa093aff04d404a130a9b15be8173ffbe15fec0cb126df6142a216dded295dc4fc8541c29426d77f542266530c1058dc728eaea0c26761c8ba75c43c1be0fa320aebcd607c5781a2b55e325ce8c4a290fb2abffc20ecb6d6113d28923d21dc33728e3e87b55829efbdfe42fc02b667da3cb08b5ba8a3bfd95cdaffe7dff19f8a75a257a1d282b0f6317c0e5a26bca8e906a40d948823c808af339a8abf887e37daae812bc491fd3369e6be74e397b60cec62ce5e66b9c0b05ff0c95915daf4290971964a66daad3d93b4264cf4b6db5745a92ad9489a11f96b819c2e5a4b2bd9ae9401a6cbc033028154c2360af64d067b817e7c5f4a65720a7f0e1ce032500d9d0d54b86fa0349c0c7f2d73d970675fb7761bdeb4011533fd70a8e19ba6b35a5320af6b9900063c1329f4f95bfc8f8aa1cb3eb8de1c2e74b4c13962809e55c77365c27481d367c88013c203e70801f40379674123f65e9d1f90f68566e723a2a4c458a34db3629e04d1680c0cd404a11fd85a7c9e5842edb08ed82a9cc3e8a9c50fdba86942418924c117cadde6da4e8885859ac13b267c3836124379a3394e7fcb576b52bf790f107ebb76078e4f99e7190914f387593412059cf91041cdd99044f9de3ebfa579f91fc6f87e9c1cba61d43add01ea45a445688257ce5ad6c5f5f11e98ea1bf66e256919baa60528e2acce2b7caeec5a83539ed1eb3716519a1ad11343ad1361ad2eab4885f4e898b896116c75bea37c17db8d9527430029ceebd84b7925a14a27d4bda55bb00c7df1bdd086ec41d1a2a61523b9c2306c5b9ba212efcd9d4a7ef3603d3e6332e065e761ecc6051f2e887015dccbfa3bbd75f8455a2a52d7970235db7fbeeb673af566e5830d5292ea48753aef4047c9e3d680e745ae48590aa3ca4027f8fcfdd4237ab436783302b0d1e9dababf401f283ad9c5dea6bbeee390910d6fc743be2dd3a220e2648bc0f78f6f756a817105156626bb5aa1c9d0e1bb46f2d4ca683b506348514819c5340adada12ab6853a8a4739463922583e36f7cdef241dec5255c30f8bae4667d8f853eb156e155ffcd0bdfca7c97cb7700afc3fbaf977655a2a7c73bb9c9a5f5f8b5fcc5c02ecc68b112c7f098d0b746d05d40a65bd75668f118b30d0b2edf3d5850ec07e20c45f0f4283b660ce2b76b6f6dd7670d98ecf7fbd127ad8a47d77373d7dc20b834944d801fe00015280188db9ca62642292a81bdd78f0060c7d7a041e14500000fd00a078631a97f01ac6fc015549953cd5576ff0c9afd0056e9ac727cb88c1aed1e72d408267db9d9077050693e00cdb0c58b9176e568496df354ca532131d381b3f975b92ef872094481d240bbd558823d1946e3a97a0a230d53abaa38f87a3352b302fc27761b0d130301b4312d450d348d348a5ee6abd53622bf54991a51446cb0dad83a030901da0aa44076c7a52f498258697d7ee0be177b15c21fe3dacbfadb037aec27a2bf4606e3ed4c80a807dd2493b71c9a42dd5048328d810d5b1f9e23f7ee040b4bc881cefff9bf6ef358e15b6afa508dbd75baef5593be50c1da3a8cdfe1efc3f2aa917cbbae5918f34f36f5b7826f7756faacfbd12e188bbe3f27fb07dbe0f5d79cbed5e9f317b327fb908d4e332b20e60a9c7f1c9beb49a02be6b44e969e8529667e80f6f53c6324eb668a4339d50fdec0c19a2c5c3445cac83593183b9364071e766f9c4c2fa536e890ff995590b5e9754497ec3e1906a21d1d7ffc0f5ef9d2dfaaf2ec7a1edb8893bf7d2992ff15c289cced795dae3942814ad7892324a1477a82073803a3134f28a8b088297a7a36011125926d21962ad9903b826c20cb367b1569d6f90e61eade2f09b3c36528b872a3c64e10bbb503d5504350c3259e02269d0009318c30ddc3c66ee675f1d24d2de1265da091d1e91ed91235e1460071571946f19bdfcc1d0a7760e9fc7f735473824bd0176b1ebfd8ad637917c2adb2cdba8113f8b01509201ec2150db17b7a6089eae2854600fa33d97fb5ffe1cfd85bdf0ad521d5b718d7d95bc4d9dcbe6ba3082addc4e565c8c17d122d8a678d4da216d0b32e072aeb2bc94bf0a93c1df3315c8dd60f25275a50a63a19c3efc275d38aec50fede42b520b40de6ab6953d9f895b0544cdcb248136cb738914d93345bbe59ca3651ee29964c467a9d42f1bb632038291441920706892b2007a18e2b3499ea5a8f35802f87aeb3e351b252bab23a769410cb155485afa21814213489601182c16d22752dc95467958e1cd4efe69b5f2ee88cb465bd7de2ff84835ca0206519f8f62def59d396faa5a9dd169b56c6f4993ce07f4db2c2a83f3bdcf44f7556d5d1b4abdb506289ff59a2db2114e212de0bacbcec63decb83e5857d50f30d483cddeb85a127efd0c57559936959c2781573d0d2184f9ddae24fc80fb461610048cbf178502df12906e3ff2ba6705a352f887b2236e1a37bf36709375d9e4ec4fe89f99fd9d2ff25b3cdac4e0fdec15fc62fd9bdd8d8f4ffc06c50b3208881de23ecefe2d693b4a62fced8a3e9a9b10041669d332d01fa38a2873a7a4f9c046f1014e92ae7e8c3090ea586ac5db211c122ba780949d7f3162e280c399368d5461e61c419cde1b3e036ee1ca5a412785c24663f69548f8805f5ee6a01de795caa65153990be88f3acfc41c03fd446784519f1d4a160748b87162938ba7c261e911ecc80b69a8183b32123edd17c1a5b0deff0b2108a5a2075d0f73977d2a18396412110ba109e0c50cefbcc351c20ff0d1564be07fedfb89295ed87ca158735b583cc50605c542ffedaabb8cc7d3f155392468d12ae70ece273f62fd367a8a95b9830f8352064707240f08020eb8b83bfb0998bddcb9bfb37257dd3b7f86e8326df43e100178bd491eee84ab9970678dc9e4dda4a39262351451f80bd21873520606bdbe99257ba482bef0b98cb648e83b0c4f118964137a64634a272c4b56c41de797332c4d40feedac721bb043af1d0c7c059ea8064610228afd0cff06779091197f7ddf1628f161d2ba281b672d35994181a56d4b7890b21628ccea135a5021316c795001ad3c488fe6e6f3b3524efd96900ed3885383545fb990bc39519ebb6c04e132a741b3238020ea94d10b2335b983441d4c8a47ab4a47044df53b77adb8afcf8eca74f0544264211bc72ee111f76004405d8fcd899cce0415a1cc9a6fcdd7a5f42b83b52eb240a0ea78d69d1913f21b883958f362e9b0e5578bb5b0916a52e0366d6ae8ba286a2d3084b8ec5f03bb931afbb5f8f505dd9782c6de70057c9243ad34c593f097bf865cbcf1442bd332506136b24c34e6d07f52fb1df3ea7cce475eaff3094e880569830c7fa4c3fc411c935185eb99359e75c566fad11771eb9c110e065d8e2d1de1ff041d1306f98da61955b359ff88ac73051da97d3877e379782bc755c883419ba01b821a19c101905becc646d9b39dbea14c4fb11509d42eea5c6fd8b525b59ebeaae91c5dda08e5d11c763d329b1c27a627d6ba7c68adf9aca03875c9a60d34c233e24208d873a84baf4509ad188715ff78709c7ce2b26ceb9b9db833e249b477a62c2ef4aa367e11cf9e688612740bacbfadf91ccc84e382471833a2e7102433a4b24bebc77cdc19e215b26680ebc9086d3bf58a300f8568986a098a2fcaea52cceb0becc11a7105fcf05630ff50900765bb525072bd1a131bd572d1646ba724d90045fe9f0a54a070e9cd6a1ddb604a70fdb0d9815fe6480c401794fec03421e823a8679b0a02a391903351fbbc2dc90c8fd2816b31ab8be9bd4e94443e26716b48a5ccab1ba720ce71e99dbf4cae247274c2c1803c1beb94acd884f677b7d557ca842ee3050a1ec504763464a77d4e1ed49fda4e9d60638d3707253a5bcc77c3632f736f8814d1119bcec5a4070173534531c0878f51d3ab0d5a7aab17d465674ac0e4dc9a8487a76a44b76e3784e1086712669e3c462178f881d8e1c328a197478b82a3089996b1535fa2d452dc2a82ed2bd8aa1170ccf795101f23add34d64df1c827f656c14a9bb07b7ea5206d5820e851074109508171a8c3c6a8c5727f98c60a8bd152fd9feb17948bf62d0604a1805b0e4116c8ae0975a05c755045c37fb4b687fa8582fa144ec53b94632ef5b622c2815b93423d67251a7e1fe4ac7f2f9fb87da3cfc711a1b2343a406caa309ad99b564d9a3f7222272ab3cb8a47532665dac86832794b4f8353d85dbabe31efa7e9c9984c2eb9fe4cf3d9e154ecf171bddb7d664ed4203becd45c9cf36c479cb4c3b7fd1e2a87da0c16770e95ea315a1a19e4929f51e5930914a99ba7a03ddcdb786b821acb6cbdadd292ccde29fd9edaf60b7e81a2904b7602d20bdd5617ac0ce6026d81627609dfdac5c52c919f0bd2370f9b2f9fdb6d76b102c3b2aa1f18c5fbf072becdd42b8eae3d05c7c40e9d61b4705c34d916d88ef633bc7db81178175da698c34282305070f028d83f983f32e23e12ed28291cf1006b8a23b029662fb1c35b74c92b82da6cbdbeb7da9e5e6258f99c2c5bb69dbba6bd9483a3bdd37564335343b4870fa1aa20e2b944cbfa507936db182416149d4555d9943d5584b3eb70f45c42bd3c6c9ced794ddbbefeb7342966e6aad286f6b969d471b5fdf00cba62aa54c305ead8c94c999db02a9efda07aee0e1438a0551c0ce0468e1c0e6b50ddbb4f9cfc005838d4de8f200872b0307bf5da179758d95687b317208fcf10da8fd70b06640bcd01633cb739efd13dcd485b83e91a8bc57dd8f186169a243f5d71383b058cf4878b3be2dbe2dedec8803d795e95768eeb9b83b36c70ade7408632f869ed301bf080d08f2979b4605b3e70f1f8159924ae2b3b1ac573fb5fa5964f08b3f18c941b9fa5ce9e1e9797c87f8fa67cd2697889db0068388c54af762471ef9099d318c31e2f98ecb360922ddebef709ea405a9cb5523bdcb7dab3c29cfd52da26de236b3d3aa4608340c8accc2d78388a6977bae1177664bf9f093288a8680a0e3e452e5173adfff7cff47d7aaf88d7c0fcc24a9bab326bc38c719159f282b2fc9c77606d060522c5238e5ef682b058a5c973fe6d60ebc31ee197d730b1b65fadb84ceb1658ecd5f17fee6d8b56e9383edc50f472554c9df94812d711cbe07335aaeef5c93f1ec79ea39000411d9e7a10be5c337d1ebbed5872b34c6bd2539c99393fbac7854403d939b8da396607ad9e2c71070d5eb4c8f0caaaf2fe66966b2b5e34ddcc31db902cbb30dc255eb6f172e11ce43a96da9a257d935598f29a8133a12bf54095c2d78e2e1117472f63df738a51938d6f71966a9687c4918ea2554c5c1d675c75876aabf48b77d1035ade1430155978758ea346278040219fe886efc2933878a254a5ceb9ba5890a6d1c6beeb22a7423db6cf447420f1c8843d493c5b3fc321d0e9695c17443cb7582c6cba496eea9739c0b9ccdb63a4ac186b95a7c002a696f9c6971fde0b2da55ba4971f167c4cd69fc49d2b2308ca0542a47a148b9e373026a2017fd2087e43d6d7211795f5a60693ef8e729df46c4b54ccf8ca7e917dc222853fda00906b6cb165c4f8035d92d0df283688e8cf6bd4d794b1b94c680b8547d04c0bba23af9dae32cdeac0e2f3ccdc403ece44a98f6bfdb77ade593b6a0daf3a513982ecd019a88c506de3758b55d92362e96fbcd71a21607191ff2b12b35794e6b7c0a7a730dc60247e019d5675b034fc87e785b2736f5e5da0170c62f42e52abe48248acc1044cb94a5ff10f1390c5c160a1911e6d093558e96a1bb3c1f9ea1edcee68fb56676125a0be90e96864e64d6e0904ad704d95cdb07fa631d35b7639cb047fb3e2354ccc001d6c4bf990e5a6d89ed90130839bb83badabb8e2aa4cbfc38353fa6c3b1201c1fc99449c482c9214b16c74adc8cd4316a61c7a0ed8c98d6c6c39d2a1fe03c287f8a9b7b07f58bd37189c5edc8aa77a87206f88c44e3fb05d0c6ee3c7c7ee5c236d010d441b5ced5d7ceca2e4ae784253cdbf1c3f8a4b99ae0cae46fc0b6c14ecee2bc3396d4bff8950f995f9b25dab9744ce21002ac80c8b83251f67dbbe2224035712a430471bdafb448a6ade8ea2cad1a6f7e59ff35d0eebc3095d509c7152cd5ddbe4ce45e264cc40dce7f9a2f50844b5d7799baee23f94700e5f88a4ca63857043e5b223a1713e5a89132fd009ea5eea9fc9dec3ccf80f6780e875a3b2d4caa0c87ca563791d8c73fa685f49b107f2782307435ede416e76d061f662d74cf510c3ab9ea5d06359a3e90f07cb637e8ca983186e9ef0327db3c31c040742848127485368853fcdb741858b969c38b8c6612d9d0ac65b298328c58790f048116c55cc85f5a6802b64ca597bf12d9b264d39d32f026e92668cb8035b5234862a428c2ed9214aecfe675fecc2cc22b02bc1cca5c134aa2b301bd2e19b2778da3b7e5fa93aa73dc4920f0341e98b4e3e2397249ffc5ff04e80fa4eac2e2478be9b2b0dec4f4809d331720adcd12fc4f8155939fb3e7d9838cb9c6b75ec69fa89c5a399c5d322a15f6a34cf80dff87f455f4e7d794ed509432f29186d4cbd4d851d01687c52dd766993aed930d928fd709599c43d044228f61036f401788af78d27998f5df50beb5a3d92b7bb4e04c1cd1a24bce48289021397dc97600fe2e4061627c2d683256bb7d1ed4cb2ce2c6398fc67f6b7ec6a7246fab61967ca763ab15faa51bf9727ead601b83fda63d182f5cdcb0c6665126c46d2df6d3cc402475a420245c8cca41851073aaefbc49532fec9c6cc8c5f9d4e169ad3953a052ffcad407b8b34f78b100dd927cd63442d3708235d7a3b895841a39cfb814ca1010ad853c16bbf87b244828e7d35042e02586430106733f8af527b97cfac27b4ea256f466591df2491fc8e9b3370c396adb940430246491ac69786d626bddfaa21072649d0c8b4695e2f96f2fb25f391d6f3ea9e554cb9bbef410a75aeaf75d7f01ee5e07f109de34ded162524025bd292d06413cae012ed6826d7bde0f7082ebd4c0208c75675e133526dbe17af65a0e7b728b35070645842b59965d397ccad664fc660c1d61a3068f9974caf5b6c38c04dd801b5eaf0d97e9bdb1feff468a7afa72117a658cb6dc55fd8618614433eb3c9fe0b40d18625f648997f6dd8ba064ba801d62d4b8513b40387abc128babd357ce09806352bc43afb0e258dcd2a6a6548cc8c327e68915510cf4f7154bca2f5df8ff700d43afeeae561f087e504d1f663e29f4f4e3761a5ab7d5b62741cb6cb5b99689bd5148d9706d6ca4dadecf19fd810d783639e9ddce917a3eff4d94242c182b31653ac37eb9ed0ee7a31b0dbf06a6d31a309bfd9d41a6eb9c6b30f5cf37ea81f46d1d88430677e423cd473fee2f253c9e7ee156f2d4d609d59ce057f3f7def16039f9f6f3b13ad3d3cf27f5c12f2ef146e67aaf753cac9a37b9022b02ed3c7cbbc0fbc7db207c4fda93e746a6448ebafa72673e87cce05a5d72cd275eb542b394007a6d5ea870adca409dec187dccbedb0ce43c0153467017552ef22e3a63ea9b902a4de02ae6d05bc0d3450b4960efcffffffffffffff2f42847a93ff5bc924a5ac7e543e0215c894524a29254b000000200000000000005e4f849a5838084d0dc40dbe0d072143c8eaa110f547e948a23678340f05eddfed20d44a89281e4a2393ded2eb314d56ef50128939769b6bcc93c60e25932071235b2415a7a9435943084a68d0497a474387620c49d3ec833413393387b275fcdf6f37d950924341a49399738f2ae914879279ae3c17f5373a824359d544be97937957f486f26753a5fd396a5a8f1b8a69a3c627712ac13c6d28e6dc1549aeb98876d850f0912ed6b9e4f5d36b28678d9dd6ffba1a0a2aa6439ea0c2cb5c4f43b1e4efe624bb49e6d2d15038f3204c5728cb0bfd0cc54cf99227e7cc50929eda4bcd7a33776528ede69cb82e2b1a634e86926c089d2f6304ffcac750ceb17df2b3e976d28ba168ea416eec541a3f280ca599a49336d7dc9f466028a7b7bb11ab0aad9f2f14de22fc483db177325e28a95909f1ba71a3ed5d28fe061f59491be5d45c28ce8bea64428830566fa1785a73c874db2cb9d542d9e38e88f115fbaa66a1f81d3256472d55e2c442f1343d04b18d4193f00a25f1a7841eed31fbe4ac5090bf95498cfbafc7aa50f8119d37db756dc608150a313f6b8764722a52640ac528db3b22eea51e299442368d0639a3f399a2500ed12b21c9320d57824251447ed0b17f4231e897f7f3a02592ef84e2660de9b27d138a266e1ba35ae7781f269446986a121ace33c82ca1a46ea783ce361ec346098511fe6f779efe452409653325a2a66c8cf9264828a7dc67894d1f2ed44728f94c3471f55b1f6923943796d0c983cd88d945288fea08ca47982e216e22144eb53a88a0a346e61e42217e0e498ffc9159e51642317be4d01b4488d9c90e4249bc77e9dfa093e8d94028c8cf07ad21f983e26e67f04eb63bdbf9a0e8f93765732e55a6ea1e943d93a8bcf124d28b7850d290b4ffb4678c79b383428e72bb9aaed963745012f6e1d443baffcf7350ce506be23aa6e9131c944ee686176513e006455193e3c613fab5b304b04139c7d71c1693d2b5ad4539a49d98f7254c8bb28de64d3a3288d126cfa230e712a2e5c6b06bb22c4aaaf6e1ffd65b54c8b128979e949aa4ca7d790c8b82d48ce1b3048b9d14bfa21c92f2dd11b1939162571454646bd29b56148287cd794f17264eac28dd88b49da0a51bfc5594f74ec6eb89ba2239aa28ce9b8c6791d7f9f75414924e7da673e2c6144145e13a289fdd18adcd3e45594537bc996cea90294ae9b9438e9b318dc9a5282489d5e2a7aa37b4a428875051235963d4b08ea2ac993e06cd2227c988a21c24e5be69f3f33b43514cdd303f11cdb408415132391327748e91befb44a94b2e837f124944cd13a5d33127fb5f7fbb6b3b51aa5c17a576f4091dc4899292fdebba79c46713c59162efbda75e274613e5a4c5622e628c9fc944f94349daf0a76d754c946c346e50b6499fc42f519ab99314da3db28758a220c39a3825b256a2f441d689922c250a4235565374c78c27519e245f8407b5295a491434ec44d3e98458979128c91c99e8663261048952967eee78f5a6367c44593ffa4611d276948e289c4afbd031c7b6ec1a517ecf1972de7b2b09234aef315e6e987abfb688e2e9d6489a73c87f2a554431dd8346ccacbfa64f44a922970e2173940911e58c3f6a653a6225748842f860a7dd3d8ad23986280811366f8e6bc68f17a2b423737ff608662b218a414c85c4f0887ef5204ab6d7152a5bbf0aa22849e32921a221b51b88921c654257c66b0d2b204a3ae967cd663d4188f987f2760e62dee325bcc90f055d319f3a92c997fa50d0f2267ed4e4d8e4f3a1a4c14fa24491c9cb3d14d4c67a880e333a437a28b777073731d38cf350aad856c9273b62f6e0a1202bdc438eacd49cdfa1a0ff45a44852c4e9ac1d8a794d662f6577995487723c55193d2e362b1d8a417f903863b6fae11cca71d487d41fcef37572284911ff796747df1f878298fc6ca3cf644e834369c784cc1b4ae2acc244787f4ddc50ce1095af914cad59d886b29eaa241d34272194d850ae9d1cc4e4bd50ebafa1209fe62a4b4668570dc5ad1139435545284f1a8a398664aa3b7f4dd4a0a1907437479db0cbcacf50cc62af3d7e75324371ce2569f198d1345d8672b6879acbbc2262c850cc29749d7c17e9168fa1984adc6e1e53e79b154331c792d15c44af951a86b2c6a4256cb2c946130c05d3999a6c8357aef885924893adaaaaf375bc508e1ffd754a2f56bb2e945fffe622ac97b4b950de7ca74e8e444f5bdd160a972bca22db7ae7b5504c3199637b0a55e167a1e47d5522440a7942060b25a527a989c927e6cfaf505033d15934dbf66f85d208f921f2dcf23f5d8582d42852d3733899672a9436b76d1e7f1b65e5140a9a37c6bc5c91f84ba110a28c5ad7949d3b1785529b4c92b3d5673c1914ca713c8758fa6e277a42e1dd63276691631e27947f4c8d467091299635a168c274e3b37f7c3f31a16891cfdd4ac4c7660965d99e6c9e3d4452be120aa683e8912f11af4249286b0435a73b5b6e90503695603aa9fa349323145c7d46291d5e7f1ba11ce35e68f268aa6f110a495b4cb2dc69da0e118a1a564f4848aff1344328080fe1eaa9f6721242f1b6b38e58ed9edf8350ca90b3a11f772c04424968faaf6cea4a8efca078ba3cf43c58f7c407e51cc327c82449727a50f03839afb1f25a9b07e5d11b5dcbf9d09b1d14cdf37f503521725e1d9445937f79af659a9a8362d098f564d09ef48383d2a85027f24f290bf106056f8f5a6d4a2bfcab01d8a01cb174e70db1b5f20a462dca26eb63437f577dea306851dacc3b216fdc302bcda2f8b3b9a2aa22894a8f2c4a22e4a4638fd0c28845513c54c6cb060da74718b0284eae78d17beb17de81f18ab28a10127bc7b183878e3f410f1ee9d08eb8a224c72cf4d896e7dfb86a2b0a2f71e54a7d56a86e55595112233d083d29d7fc84aaba8a726e1f51cf396b97896c1c60a8a29ca6dffce46e8b4cebda00231505eb7c8b9073d0151643002d8d05080315e560b2f487f989c9a23deba103c7173e14c629cad9c3b55f36292143f407304c5108abae1fe7f4d44b34071b294569939a1cd73f5dbd9414c5ca0f73e5dee163ed3a52308a8290d4e6579fb4be763e4451be4e77b7d624d44e3e14e5f413275cbefc7d9d4051fe34e1318b6ab208ff270a962f4944fb469031e389d2671c19167b1e94da74a21479f643dce831ba69096070a29422e409a5d7f74256bb0ec0d844a93c28f169630c3972d444d1c574735263fbb2a64c14dd56425c99d9aad23051f224e4f446114f9b45302e5192092ad944ec24ad2e0c4b942cd5d227dbe7102208a312850d79966f3bb21731302851d07f9e6fa337fec79f44715fb422a848326532d32451cea62ee4a3ec7e92388c48943ecfdf59eebfe66620a169991df5bd0fbb2381f1884c2c33de4cbde5aada843231db9c77d6aa81e188e265bbd5d5e4cdc6061cd0c5175e40e0050bf0e1c32f70c31a51c8564a9fe74a474a0981063480c78f1d10b871e3068e82c188726612e7313eb987989483cd8b5d443993a6a947cd7542f93d521245e030008c4418a7bc3e4d3573d647178fa3070f1c5fb817660a031178cdd6786a79dd5daa049d3ba46fbecd13737cf8c23884b1eac4a35e09bbe84287046edc3044e13c27317b9f9af4a873b0e1e9e8f1230b5192359f797334493649b60dc02044a9730e3dd1dffa9cd43e065148b73e2fd97365ba978720caa779a54cdff5ae237581286a502632673c99f3640151d8a0b3860c41e9369dea3f94c347d6c7cb8e25e35f60f8a1a0197c259f361b4df23e14b3eb9ef6689945a44d1b1f0a9bfbd487a43735c9a4b33d94c3dff9e5c7897d0caa1e8ab29b6e3d77d49876db3c94b448efa03b42d06e0b0f0549a6223b1fef2ab51c6c7b8752e6b09676d222e812cac1968860d8a1a85a1fa2edcc83d25b0eb6bc008c3a94b3099d237a467649324300061dca39bd7f7def7e2c21ccc1d67328b5c41094fc06f550a61c6cab278792ae867c727292cfd4e6602b630ac088434129a5eb552695834d2d00030ee51cdbdf234ebcd389008c37943574c79c1d63861a6d0e36e263eff8d0e145f2d081e30b1f5a8110e078000c371446475d512b2d226417461bcaba6d9147d3f96f0ec9c1c6c600186c2826dd0ed164768fa4733e7c00630da590713bcab6488a14dfb8716a280921630e22e9f2add37d681a8ae195f9642a13caf6ee5e41670660a0a15492d353bf3547f8188c331477eb5ff26e7cdd5032433906b1376a9d9a7eb230ca60c89aca5a6504ab5b19d9a2b14588dc1abec2204349c6bf7990335bbb750c458d1a7d667d82525d0c6519d16ddf220c4339c9dfa0a226d86bc66080a118ff7ee65e7196affa80f18582a692e13f467a99dabc508cf13a7a9499a4de325d402ce5b5e2364526bcbce4b35751779d359ecf85628a8e1a4a9629d9b3d0a3e1f8c2c73600c6160a1e4d997f880f934ddf5a2898587badc8493edaf43dbe47164a759f1697c13ad8887800030be51ceda23c950a99ebd2c1a303eb058c2b94ed42cb5886084a468671580b6058a138b76219e66bd6940da30a65b9b41331e6f42ebaa44239b4db083d7fe619da140a418c6e8fad0f1a746503185228049d44f56e6d8418996f00230ae58d3cc934758820c0e3c7172fe871160a05611dd4ae8cdc24e4ff84e25ee6670c6a9183ea38a1b4a1e33108bd4d9349134a16613b83527973b284c184c244afd2234af3e8e93ed0611ca63580b184f27c7fa452b33af7f11b376edcf81e8cc3f87bf0383980a184e209f1f9d6a3fa018928a55b6b9c30baa98495e3ce8e17f8484414cc7fb5d6967188ee3e52ca874cd6ecb6ae8ee5d6a931b4d5a46518a2e439c34ab3aabd29a5d98f1578415646210ad697733242d2bef75572718109d86840066ca8214206218a892e31259d9595b766da6789ff12467dd3320651b45f5bf3b9990dfeca1044d9d6354fa6e852a2ad8c401465b73c9b7899830d07dae15df821200a1baa4d4fdf4c9bb643c61fcaa231e49cca428212412fc30fa5c836f274ba5853a61f3ebc8bb23e944d07d118738f5669ff05f2a1381afbfa4974c7247ee67916903d94b49e04b33755de25c9c1f6851707510f0be040861e4aa5329f34f53359a282bff31d5041b9c08d1b5f7871cc64e4a1687b5def93559492bf0f3c145647b9068f742e222773c8b843b92a35f4ad448a209f1c6c653c64d8a194ea29f38898f6f6a53a64d4a170dd57baae62ec21830ea5cdbf8c781f239370a01f32e6509c9c93942e53132b9372b0a1e7a1a30b0d54e0c68dfca3a30b1f6632e4509020773b4dfc8d626a0d64c4a1a44ed6ca726664081b0e859b8f9e44ccfa18d51b4ad246b7c3efec862e26a999cb9c7b95e4d6ec4e1993501b84e7581b780d330f2f174ff32e4d51fa2de2def96422830de5a01df3fa7838a1f1b46b28ee682a6d3db9da35e9c68d63010ee2230832d4504cd3695a648e4943497bb6b91ecd153b970c3494644e27836f1cd399bc3394848fce3aef1f7be2a6053cb8b871a3cd508e2192274b2df5269234905186f27bd2575a44543dad642886cd1e2b4484c650fab83fba561b537d2a86c2c5c5d2d465ce3379cc6619de0943a9424e0ac939d2fdf43f5e033fde07146480a1a02187d0f459bc5b00f440c617ca5ef215fa4e32bc50de9c5396a847973b5b4617ca3168b111735d3e3bcae04231c6c49d939bfd1ae3c483478f0b2441c6160ad692a4c6d8b81e5329430bc594629e31d2468925ff2e7ef0e0d1c30bff1e3f34508110e0f8424616ca6b62d4e66334a798b150948f9837290d32e6d52b1435c9111349e907b95aa1685d1a8288572da94c552067574d8eb18ee0dd555e1d83d2b6a90c2a94f4484fedab6aa12d9c8269b26a9e1576a21142ccf6d3239ad9e3f6470ae5246467ded12df10b1e7d1d8fb40302840b1951287a0c9916933287281d14f65aad08e9ed2c2dcd46e34ef8b2ce1376efaaaedbd09674933ce5736be3e8fda04086130a1e7bd2f6e810be8489a3078fb46b42a97282cbc40addbbbd0f2614d6447bc83189e4605b42d952764d75d291d7a512ca3e2275ee94d8a4d2413292509096b1b955c48b080905899974bd490ca2d56b23e30885b8397f8298747d272726c30865650c22e8d18d5d46118aeaad5954f6e89c50521c3288504eb97aebb33e079b8940c6108a9bb9f4f5f3c9390fe6604323810c219423a99ae4b81e553f95836dad033282b099d679bc4a4ead176d7309b339c614d235cfdde3c7174690018472f07072aaf7fa6fab8c1f943be7d3a594cefba05ca3b29d27a465f4202bab8ea8da95f6b6999d237c7e6cdc373b2320830705fdaad79093be34b43990b183a2fbc96bfd15cd64d78d1b376edcb8a103193a28ea4af8a4646d7250d248f2d388f04fd9f5f81c2d302e6c58591c64e080bbbe4d4a84873cde80589f62624672dc37071b0e36956183f20871d93259ef9f5a9433a875ad9d889793a4b4b0ed2db6eb43ebdb24966589ae8f13ea2366f0c82c0ac2eef62584511685fc7e993e68bd08da8b453983cc924bf327994e8545d15cb404a1c93d3cb33788f18a2d5299976bb658dc8c6bcc694d1cf1b876053e70a01762b8a210f386bfc95a7573a65614ae6a33fc7e54c8595190eb1261745c47d1d22acaff41872475a38a52d65b9ba749c648453124e531d5a94f51d941454183f6fb1c5c43bf494f5188f16365925b1142bc3609314c510a197a9ebd92f7455f8a62ee1233f95466d4a0a3408fff018118a428c91e2583989e1c74e4bc661b4531297deedf5a8ba2141f2b4abd8547333f07db0e6fc1efc0d183070e43b45014d6ae5a5736ed659c2688018ac2eda6addfa06122ef376ec4f8444982dcb899bc467787a0fd70c1e3c6f044c1e4e7ec6371ee216f8c4e149336e125747ec89d6e4e148412b172d487c9dfcf26ca496e4dd229839a20c78acac8756bbd58ad6c50ea23b69b3d7e7c819928ed9e761dbd321e933207db0b7afc3063eb0a840047026260a21867e3a7d44e1f940673b0a11dff03023bfe870bca8d1b0ac0189728fec4e82babb91c6c3828d08577e01411c312c5fc233646ac462491e660c3715e7801016b59c4a844b15be2ac799e1ff9d1175fac8012c54fcd2793c998cf1edf021e5de0403b74a43b31c4984461b2abfc5a7e08a69644f974f00feb1115cd9a48943ae7a73f3bd1e9290289b2e8c8eca2269b975e3f70fc172fd8401a001611e311650f7b565f9a64e2898e28b7d6a95afb97838d0529e81e16d848c46844d9b54e36a9ff18516cdb8f233d8cba8fb11c6c2c48018ee4df058e3409e80d22c6224a4a62fed0a0b7e7e3a788d256b6a9f18d9944d4858e1c472f1185fc991372061f1145d71b9994d814e9e38eefe15fe038c10e1d38fe043dde0bbb431463069d182297881e3c8628be76c78932ba4cb82946210ae174a3893829e3cb861085ebb43967ed70634a9583ad0bff1f3e70a4a08b1518328882323de13b3177be2d09a2744aa4695a8751b2358128cb8a648d1363654e3a204a7b226f8aa45a7d7d63fca114e29d1e93a72769b2187e286f0ec9da7cad9404f50221461f4ad61beb796d5a5eb76526c4e043319d0e19f4b8a96dceaf40056fe62b5041a93d943afdbe6386dcd166e9a12464bcff2d6ca4a73503240fe5a41173f89360fadfbd71a362e0a1a0279b5efdf410920e230c62dca1f84969d2398b6e0631ec506ccf6b25c32bc6fedc3a94cfb4a74afeb192b1c3410c3a94ef25874893d2d274276310630e25d1a1a2236b33343339144de48ebcf7c799fe7afcf822461c4aeae29551923c4510030ea5d8cfdd7d562b32c9bca1a0ef314daacf6b4a876e2884adec24d3c6f731591b8a416bb492f9435a66b047b971c37bfc8d1b47878e186c288b2c6df9e9237d8f740dc5e83a62dbef3c3fe4d550169d61299a8a9186a28a10b5f35126259531d050ec543a7e9455e6cea63394f368299d3e73d02e62339494bca67508fdcadb6294a1103de41f3b91c3cd1c830c883862b7e14c660cf9abc98e12edae331e31c4c02853771b71ca73460531c25036958927c943eb450c30106553bb422356c8755ac6b664d10c9ddf454d9482185f286848e2738d0c9f24e385c2af5c6a79f09e349a1c6c4b2a10021c3588d185d295eeeccea6a9c1aa400870ac2006170a221be4089dbfda211e630b855dbff06c35f1aefe8fa185c29fceef8e772fb99e1859287e7bd239b3e78418b3278881056d34c286b6b9c6856d889f9b75ed067b91a52e6e5f10e30a6553b3bab2f8e0697436861856288cb24c9b5ac7536788068e0c31aa50485b1bf472dccc52256b88418592c8d195a1749c30313e85e2c84dd13332f65cc9dcb85186185228e6adc7c99fc3cffbfc3b74182f789cb3b517fcd0817474e1751685ba4b34430e2562866340a110a345dc6c499b3a931088f184d27cf2cc92fb83c88d3821db72b1fad20a4ff718d1bf21643c751b36ed319a50fcd0fc69ed74b895178309c5cf7d1eeed1b3de4ca4418c2594257ac89598e974869212ca195abd5378fbeba69350dab48cd9eefb21562414c3bfada9dc0c04318e505242f78570cb0d3a838c50084af32659b5294221ea66afe62775b6931844b84d245ccaa8a4f1b68d7b4f5f8c2114e43c9288991ac3f92986100a5e3a9fc4cd924a09a5184128846c9fb1c4e8700d4a318050ca2f155f9f94e79e14e307e5ea6492336d48724f8ae18342ae11415a6f8daa4c8ad18362109326c68ff693ef148307655335f9c84194f839c5d84139b857b709cf91574e317450f6cb993da9bdb1e214230705b115f1e78399b036c5c04129ddb64de9b49ea6a618372804cf6a8d0f61ed698a61839295cc78b12f13f39908508b427e5b116a7a46893311801645313549b745c610ce44805914676210ed33a5376722802c0a1a236b52fc8de42f112016a5ddc9b23fb163672d11001665d1d22175967e0e5722c02b0a2f4126a5e93c21f54f0057143ef465c82629547f9e00ad2846cf1e7fc2e8a09bf304604541a699fed5c81f49e609b08a3fa9c7a8fcad8ab2a98d91a5318dd2762a4a3a7ddc28b22cb3675494654bc6db67b0ceef29caaf6a623c4618f5ac29ca39ed468b0be5afd952943a2e4eadaaa8cff74951b6d698302e3fa9a43f8af29d672625624e4acc1745f14e6952238265e4f143510e9b53aac43d0f617c5094fc4c64cbccdcb4ff44d1d4b744dc188d25e489e2ef28899ee51b2eea44e94a7e1e25b499ac8813052122d6c49c4d945cd52644dd0f9b6ba224b3b3d9a677b53713c5f4ac39356c459235268a19928fd29cf7dd5fa2243f9b0c4a9f8c258a39bccaddb4a812c5248428a52bf4ca6711250a26417792fea42308d1248a59b26ece9f7f6c84481225edaeb2ae4c9e73882251cabe3521c3ff655b08120539912cf2a504bdff11c5199137514b7df4de110593f47276e79a547f1a514ca7f38b758418f3e830a298766c3c8565129a398b2847ddcef6b71451fa301df3f01c153111a5abcea3f1b45de920a2ec9944ee9cf341da778892ce3bd7d946c847ea18a274eff7135d64943b15a21842a4efb425d3584f88423c25ad34c72ca2438328bca9989819326a7f0c01045196b04f204a26a225f68e26993b802826394d4f32d48469fe508c9ee7ccaf2cc7763f1434880489113bc7d87d28e6c810d2e8104588cc8742780d2939d79a3aed1ecae9c3e88b037faf288695eba064d8e8e93c734539a8dd47f86beb2b4f07335a517e8fb0213fc929a1735851d2a1fe84fd975ee6ee0633565132e1194686dcce1055828319aa280413395fa7c6a4a2a09fee42c8a057ed252aca5d1f92e7aa5c49211fe414a58f626abd9ab4470c9ba2201f43c9f7fea4a4586d8319a5288bedc4244184a89d7293a2907e449aaa5232097d1425a523355a693813b18ba29ce1d3c7f1b4bd2744a128e9e460e3e93e274b0d280a22e8b462eacd7d4e9f2824dd93e3226dfe9c124f945325861397a9740c63270a2974c74da3b7269f753ee0c5710172a2b83af1df6e5482e698190e93c38c4d944f9d7a8ab2cef6a9b7c185260a396e0c218ede488cf666a2a41b5c3b7d922cadb66106264ae762224c507a347c108e37ccb844396378d554a653291dbb6186254a6b7f5a3664478d9557a2dcf953e86787081e51a544417c7eae6dcd8eef6c1225d33eeba23af4f8f89228c78b59d6898e186644a26c3a6a301321ed370641a2a0d5f7c54d7b92f6f88872f27653e91996a12237c311a55d9b1311cffd7777086634a2d8494e8d509e4dcae73560838b1b376edc38c3616ce40f3318516a3d999373f02af3d0224a62c3467fba1e0fd15611059dcfa4a79969bf3b8928c6a0a6cf3724c8ec1211c53613a79fbf3da9c40e517019eb34de551afb42c00c4394544de90cbe26f29da7100599ed93a46910327b8410e5ce3c213cf34244cd0ea290ac32c9f9af6608a2dca9f1b5d7f24094ff6e84edaf44af17a9300310651de913440715b1d7ffa1d85e7dda1efc7d5ff783df9d3f7b087a715cd033fa50981c93ce257b37e2113e143c76885f7eba1ebcac0cd8e0c24661c0c6052660a38b2fbcd0b133f63082197a2879db9bde04a51c6c2df81d66881979286ce458663aca555e28071bda1a2266e0a1b8d9b3c88a6a445f09d28163070fbe716303df0582c01e66dca1b449a6fac9be93a2673b144c6392a4a36fecf7ad0ee59b755bf90e32648ae8502c31cb1c3d47bfc76e0e25339d94d04da127892be5504eef1d92d40c21bb51128772e87fc55e88a0c93ead8419702826ddfc7e3a7ff214d58c3794d2c4881c84102b9e560e2eddc00c3714259e98db1419648e2907db0bbaf8e20bd71774f1c5170ab0c145231866b4a1e8312223a28450a2640d7cb102f40cb87103c7172b408fbc30830d059163b8e6ef38fe5a4e61c61a8a1b2322fe3c7b9216a9a178aaa1b9d2f36faa9e86a2247daa56479ed0198286725a89b719f4676edfcf50d211828d4552a7465a3b8a198a1bd2fca9ccf9caf8f8c08c3214c2e8901fef93fd65c40233c850189d3507fdb137971ef90833c650d236cb5177b2bab19d2186b2f58670753ab46a751ea84f30230ca5891bf7b235cbc1865ecc0043d9cb94d021f63d56f0c57531e30be51019a331687e91312607db0b5cc0a3c71738d00e1de96678a1bcdb1a92efd3e665d38592aa4ea12ef38bbe190b33b850ea31957f42cdb54fdf03c78f1bd75d98b185424e69b2b5848a137ae760e3a20a33b450166ff79e733fcb60df136664010f4d21e6316ff8ba38f06a30030bb87d494af594b1c9a5d47e4fccdce9f04247174c011b5c78c0c606266003716dc615ca499ca5e9ac9b89f833ac509063b161bbf65aa396836d4dbdc70abc0233aa400e9b4c971072a642e15b4497d6cbabf69c1f2e781c640a65eff0b11d93ba33716748a11ce3fa1b37d49030230ac5eba0fba5eb73894dca6640a13c5f122bd384059f5db80e2f78ec7841173c2ef0df80be06cc7842397aba0d61d3a70831c809a5485552e49cba0cd9d38462ce8dd13f3a32c4d830a11c72769ef2d8d121cf4b98b184824bfecd22fffe324e4a28e764113bf3af37463f09e5a49ade4ad8066529120aa2939a8d73429edef207338e50126a6d7212a7bc57332314335bb2d65e45284749d2e43ee4139f241b1b98c0ff38d3ab40087000800f338850104a3be3477da7d477c6104a2533687bdf1c74fee0f8f12760fbf10ee8a20be7628610ca132c724ebab10e42217a9212643b27cd8647daa1c37bcc0042c9a3c9d9cdd9cff84149c73276d2bbf979a2337c50901923f78bdef658fd8c1e14337846ec7c4ae879078fb6c08d1b3e52c083470e26cce041e1423fdc86ccb8b0d1a3083376508c2f59a512e23a28d5445132a3eb4a6dcc4149b976ae10f2f560060ecaa32dc369eda0b39afe06e5fe0e21872c425d43ce0c1b14d3db2915135f8f65d5a2108250123eab2fdb821685917affffae9343306f075bcca298d4bb7db60cd3eb9145293bf3640f2645a7fdc4a2b826e39bd0e1da21b65bc0a2a44f3c5909a5d7233a39d858a0c3c78e3f3e7478f1832d5e51daa41e71336be6140e610b5714e24ed0764f311ef6e760c3c13e7ce0d8f129e0f1c3061736de0617361004d0ffe0c22970e3860fbf7103af0221c01100256cd18a62ef5ee4aacfd9428839d80a798f0d181736aad8b8c0046c00610b5614439a204a7b6bd221a755144e6e0755f6b93545af8a92ac517a4e66665ff6a9284c1a31227a5891693da828fb69501def3555ba3e45b1aac2943cebc8bd61531472fe0f3f3a796c9bb614e510920ebd2d1dfe9a2545c9346bcad2fce1f3baa328abc5697ad329e393144549ed97120dc9843c0986a2a03741b5982cddfa0f140591103f9cff89420e8dd18ed3241ff4444158a9cf1f26b9c4af1385b4e1d53fc8eb489a71a294e95b3648c92763555b6ca2701ef2a496d2f28c244d14926999084a98f2515326ca31379ee7d637cb09c24441949efe64aa47ab7f2e5148eb6da9aab974de1c4b14533f5ba8094f254a7244ab07f12022678712a5082ab9248d9cb6914fa22054deebfc876fdbb8240aa64e6ee4c9d364aa47a2b8bd49624e52235c26902888eb78a5317394ddf0230adafe375a997fa7b1230ae699aa7b45e7e7c836a29c7983f0387a3afbbb8c28c8ec21e9fa58175110397c96ca4ea1433015512effd31047d76cc85d22cad9b34faf7c6a8d0611a51241ad8d8eaa4dcd0e510c26c2b586495fe331042ade6e53f68528dac8fc7e6f0851b4f3d08e9a1e44397af80f7273bd4e2a886234957752ecbc34ac2d0251107a7942e7d0cf7dd516802885f65696eccd4d956ff18723dc977ba44c9b9bb5ca98cb4db3e8d0f6766fe18762ce56aa5acc358698c7446cd1877226ad5d23d1c487b207d7afb1d910b6ed3d94b2f5373ca74e9269d44359e4cba4adffd0eed7167928463325d366cd3def080f058f35317e26d916772806cfd5c96674ccd6912dec503811659f4ed2470cf2167528858d3cf1aa30b5ada14331a7b9fffb98d944eb39143c989053bdb238f5caa12092854e5d6d924d49e350f2aeec30a1aceb4a7028ccccd5abd89c28b9082bb6784331d8674f0f1a73ec356e286684bdd1fa6d8b3694437aeca44af4489dd3166c28889c1c27e96def3f4bc1166b286f6da7a48eef6432aa86826aecf01926dc220d253bfda6e1d9847b28b740432186c89638c1c5e4c4b63843d947ac68fb58ff9f6386c287e0e1948e3129ada23214f522dc4613d96c74870c45ff10d2444da1676c338662cfa8099234f427118ba13cc99367edf9df78d230942bad4f99975cb73fc1501addea504dd5165f2887d8966fcefa24d3b385170a59444e22e296ba50b2dc9339674f35da743a045b70a1283a849ccc69f484dce46ea1183324a129a36a12723ed44231a7f59f18e1ba93896c050c822db25070cdb93fd64f3e43040b85a8c9deb326e6d0b9af5052e26d54a4d2525b298e2dac5016bd91ff54214a525e85e256e988f96767b3c4ca1654f845939079a2d52da6500e93a53a2ede165228e89c576296a454dde8aa802da250aa5393bcd556e791854249fdad88dc98c9adf384423a11431293b34b98d10905a9b139e8eef091a32614f4d9e5cd09a143739850f04e23eb47eb3d479650969874c4899551112ba11839c75337e354b94928b8874c1141a5484d252494ce6e363527a679f50865bf8e91a4c90a75d61ba1a03446f012efa9da5484b28cbed9dc29b24d2742f95c6eb4e9b6916e0fa110c3c57418d39a9d8550ec48a36f2ac308cda1201444d84af7d39ff4014231eda799f8ecfca0a43531c93d111f14f276cd6f0cbb6fd5f4a09892aea386d2bb111a1e94c634545e897650cadcbb0f4f5d161a1d94354af02c32eb5be4a090fc3a093d9f9eb7f42d7050fa3f0f39bc26abf47c8b1b94d4fc4788fdb31bcbb7b04149c7ef390d6626725f8b82e488275ba664700f2d8ab13da49323d39488cca21c744e1f229e4416a594b429441042c708492c8a199284a765709791c0a2583afc47eddf53da22af2867a7da59932b4acacbeef56fac65ee56144dc444c854623ea9cd8a6296b039e40f265972bd8a62e7b031d74cd8a44baa286471594d11a5b9f4a92875121b739cce26930c2a8a3d212761a293fe203f45d1947ddc53223b94944d5192dbcca4da747b9f5c8a924e59ad9fde415bc5a428abcd9bc81fdc34974651b8eedccededf9341a228e9f607b1b0cff9a25094ecaa2c9486b8d66441518c41c652e23343af643f514e2edfa32945245aeb8982c9bc95171aed44415f5227ed53d393564e944a073dd5ad9eec5add4449556c4952355174112ab2ab57cf43cd44319b8c9fcb6d4e874962a27455552e277762872e51ae932c2aead3cb762c5190d85b31237ee4db95284651a531c9f7581da644394d104a8355fca80c2751b01a29224e3e77764e12e5aeaf88553a84137e248adac17b63c826df33248afd21bbc734b299af3da2e03dd1ebce2209a57744398b4eb3d9202d5dd38862c4f87974e69362926a800c4694c4c79ec7d8d2cbf07421631105915d82e8f3f41de2f423e9c8e183f8f882078e92a18842feaf4f2ac3bef329edf02e2ef08847051a650488753a0d8775a20052282619223c4bc444a1e023946f4b90fc1bb37c722800144a257544ac7c93a8e1f1637f9cffe2c68d0a58800b2e6c34c0c60626c063039a1508010e019ca2004f28e68c5be4db10328609edd0916c878e06105114c00985ac1b5298a9c9263cd984925c0d1aea9ab1ae452694b75db4ee47de1ca92ac0128afd398bdcb8e7dd8f120ab299994292fc3ced020548c2aa79d4a8db1921a1f43fa6738e21e493778e50ee09496886544a544d8c501e7d8dd9422d42e14b065361be12624688509613e9f43e684cbde1219473a6883b393259d5cd1b37b011051042397f786d502ba2e79372b059220a1004bb51002094d4f2fd627470593df1220af08362c428a7e74f2328800fcab945dbf74c46ae4cd240017a50fadf2fdffe1817262f2714800785d8416fdb66638e9d991f0ab08352a9f4747b59914db4696528800e4a9d831825d9f45772a4003928fcaf75dc58e40c6626160a8003fdbf6eed838b5528c00d8ad1e326f9254fc540016c5092755a3a640e3389768d5a14b75349144fa383526e0d5a14fceecd76d27d0961e660c3c103a10a6acca27c4264e41079193ba73c6ac8a22462d42a53b3b2affa10356251083ac713ae914bfbeda546881ab0280495361ea3c80c19947fa8f18a921239b2a9cdef952172420d5714d57ac2bd96d0e945a715e575f536593f994c04e9a1062b0ab17c3b4b5cf10e3556618d5f6fa7f947e4504315241b99bc4752281565fb983af27c2f9248dbe0620313b091d7861aa828679b7978ce12a3ada864a8718ac29e98b051bdb4c1850f53143677a9931f9d4733e2800d302945e15b437dcc666efa775214279ac65016a1418d5194a4d798cede6d7fbf284a3a2679397964f4cc2914a50d2937d375d3e40d8ad28b122aa9e4a3c40e7da264db2334b7e36390278a11ae3c5fd606ad7f278a3935ab6d5e6fdb6638514e9ea34b12f7acddd94469d7acbd73c85cdeae8942f08ea364c79d2811c944c1cc348e90761f748f8942dad1ec8d7b1bacec4b94f5fe93e790d56d7e4b94725fd5c7339b6e6b2b51928d418918c285889f1285fcf1ad54dbd6897c12e53e256edb33870cb6244afe2ec96de36ecc512351ac2c39fa4d969b301d240a41c6cc8d8ef2cfb63da264e232f6c93041ada41c5110137478fdcd7e27f64614e732436d48fbffffe4076a30c26ed4584441681ecf0ab1a9733e7328a2b4f5716fdf26a2f79388a2a41197db3b1b417f4414e4c81cf2f77cd038ff214ade6f623be61e99ac638872fb5e7d121e6ac49a0a5150a63b3322b76b169d10858f39bcb3757fa88e0ca220f26e6a7e84cf190ba2dcbd3d5e1e43ed470e44396e44499f9be385896a00a2246a3244ce7cf1e41f0a6222687d5fcda49e5dc1177e2899c78c6ea6ccf378d00535fa50be0f69213ae44f2e1d3e14d326daf8e41d95dbe078418f1f7b2846bf51253f754e5a94f45096d71a29e1ec57e32607db8eefa2cc0635f2704b8a4fb733253c146389b449ab08d999f21d0a5aad3ced69d16142cf5da8618742d23029dbdaea25ff7528c872b7d09c4ee439d1a16ca263b0aa3ee13963e6508e3511ac66648d7c5c0ee5d651dfbb31b67123712866e7b6b4d0fb4d32c3a1a0a3f5febf8a84bcd937943ee49490f3c7fd2454379423b5e78ff9611b0aa7deca849a8cf765d56043b94fc27dd8e41acad53b7e71561eb495a686c2a524a5c14e6b09b778a41d3ad2501e15f219b2f3af6e56030d85d825c9be5db44fbfc30236b8b08176e890800d2e6c706163556350e30ca5f95fd1d09393ce7e3314c47f3ccfb12b077f814da85186bbacc3ea22a788c43acbd0e23166ced14afee98c5c0b6a90a1e8a3194527b573b07d8f2f6ec7097430a1c6188aed79ec3d4cb7956f7cd8106a88a11c32961aa93357e6d28d1b6a3e76f0d0e1a3c250d898bd391f722fd4004331f77cc6287965225d3ed4f8426153ecba9c9fde339117ca9376c448081a4388de85d25dcec8bc1dfb53c412f350830bc50fbac546e65442f6eb82473bee0d35b6501231ff697c9ad278cdb1a186164a1983449dd12332a307438d2c94ef83f727d531e87837166a60a1a01ac35b49cf1104b450e30ac58e9f39092d5921476785529eab6ed4a011c259a30a65fdde52eb184385a27d8c2c428b0c37699f42d1424b521a1329943f45301184f4afed310a8590a368f24c425ffe0f85c2e76dcd5f5f9f2424376e74106a3ca1b01e5c7552de9d3839a120e63f4b7f3a5b9d51116a34a168ba3736ee9fa7cd792694e43726c6a4e4e8fec812caa53de4d7e7277d389550d2d94e643b0f79eb4d12fe88d5e94b660c120af72534e6ded4118a496dd84e99748749cd08e51c61d3c8384ac65fcd0d6a14a12cd61b35ac3ee789d3052660e387a3d52042616bf28e7e0fdd7b96106a0ca1182e45f9b976bc578f50430805fdff49291d74376634075bdeb8f163c7f7f81c3b7418a14610ca397f6e4e880b8462c77a92ef29455fa63f287bfaecf2b194cc7bf2413146efea18c3e8a0b4fb3a7a6c207fd4e84141cb86eb1121c74e6878500cedb9b7d57693d3db516307c592abd1c34da483b2f5c7e89123c9927f0e8a515527add55847cdd4c041314eac4c6493f8c76f5092b2e94447cc356c50f0486999b3d63af3e660e3095c2d8a7d91836cd5f49d55ba404722b4286c6978aa89bddf26cfa29cf23ee95db6b9af5cd090051f5e23e275f40fe158943a7e7be96a07cd59615138d3f2f9a93d647bcdc1c603075f80c62b30d58f1772e28a9287abd7dcd2c12cc71c6c5ff8d8a1c302345ad1ef98ac184f2a83906185316c73043d07dbd35845d9c54cab9d0eb91b6b1aaa2067e82f91438c2346990ae473d6d9c41b6196a1a2141b773c649550a7a65314ab377d9c3c590e361f4d021aa6287d8e3188acd6f9623fa528a67e3b3bdd26323724071b298a337b599ffe84aaf347517693369a9f2194c690284a2522cab9b768edf084a220d6a5417944b7935b82a2603116cade3697fcfc8933e9fd5332d3c813c59c47241972c8098f93ba0ad0e8c4a9ea9474bff45ac04321408313e568e6259b74d4d6d53651b474d5186b3ddf4869a2f05a273254940ec9d74c94fc7e947e8f9a4e8862a268134b7bde8f21e7732e51fe88b9938c79e2872d211a9628ead6caccfd06133fe760ebc1034756a22c9f6574d424ab3b8367488992e849ad26b636b29b4449a7eedafa7d18fd5412a5db1461723af145a22433f3dfdb8aa5e928701a9028ad7576fa4e3a6810e2008f1f3b20b001a4f188a26d149599bd44fa9d8623ec068d46ac55c2cdc7e3faed341851aa3b9b7cba53b8ff37061a8b28977aeb12419dca4c1185b35219a3ae6ed41373b015e25181321a8928c6b20f66f3f9399b1411254fba33c5fd883ffd1ca2acddde923d68b10c114314dce2744d876527a05188a2af261921f434e6d4d220041e354579abc4902c1a84f94e047997eedf6ead20bad16db11f62a6cf7d20548f9194e2f615d1c44543669bbc372e3b27a4c38b09740e6800a26ca1219bfa661955fa1f0af93c464d0bed9dfba1d8995a84ebc4fb5036714d4fae41ed6b920fa51131f7e7c9663286308d3d1452291d4adce98e2cb91e4a3255e6b8f1c6a389a03c944b5ed2e698ef397c070f05bd9bd46d95c60e79dec1485da926ec4631a0618782cc224c33679de9e95d40a30e25f530d6a367a24349aeee66532f393d436c0ee575cdef9e919443215c7bba88b9c6a1e492b3e48590119e253894436ecb24ecf486727ebc4b5276124db66e28e77ef116c959d4c47c1bca27557da4f69f2475b2a190e54f8e58cf29d38a6b2888d8219e46d5cd2b931a4ad527c53363f84c42c40734d250d458b111e319349493b95cf8da7e865266133be92237829ecd50d8dc20b783d289694528031a652865a7eb7c4af36c4c11194a2221e4e4367a5449790ce5b25a8da99934ac278662cbee9ae9eec9769aa41186827e2fcb5fd113434c1f18ca69edaa5939e13bfec70218d0f842e1f2543fe9ac8e6aea951d818617caeb39fd4fb687e90f223e8046178a73ba71f2a96cd58c698f1f5fd0e0423185509a797b32b95639d8b650f6b43e49c48b0f79f58d1bc702edf1e30b1a5a289dee9c4d46ca12f18e688146164a99ae91de6ce54dc8020fb0b18109d8c800ea42470e2e8e06160a67e1ff31a77dd57473b079c1a3c70b7a6c400934ae501ab1fbe9eb4de4858e15ca614fbdef98127d663a1ed0a84259bb32cd7f841352ae09d0a04201684ca1182fd4f48ec8983d458840430ae5d3dbad79c22a041a5128c6bb87d21e6f23a78663078fb41ad0804221c43439f918e498047fd07842218d7726212c2c62f92cd0d1c523b6b10107ec780df8a0e18442d2d4aca673a76a894d28ecc858698ce9a09365424163628861eddcd754349650c893d5fb93a6049d250d2514f46d4b4d446dd2ccf1b43a008d241474ceb15ff44751cd31349050b0add8929bf9478889c78f1d1048348e50e8a876a277ab9d4f05fc0a54f00c38df01bb5f810a1ee138cf0292838611ca99c388dc24d4bee4bf0885f469b489d3fe27234c830805a9a7ae459265ee54d31842495643ea2bf9263ef31d484308654d423ee4e605064759778fffa1236904a19074733b6b9214cf310d20147ef3a70d41749d0af90f0af2849ae56f77c98e5cc06301376ea07701f7f8f105d2f00126f46592355942b443a3074551b51b4c8b8cfd9eadf5f8f1052068f0a090a4881e3541e6349ad2d841a963e8acaffd3839a1a183528e7899f86a5b516a0eb61f2bf0a26d6cc001361aa0801fd0c841f126ca070d3294836d8d036f5ee014a8fba163878e1d2b38f3e2bde8000d1c143ce57c46e8df3c49e706ba7c68f1f8494ac306a5760f2b9a1d2c5df55a143e07f1523927f1bc9c418bc278e68aec41e29bec67cca2942b423bfd3eb228e8ea6e865c7a27423416e58e90ccdeaa8645497a1c8d32a8742e65a3c138180a8642a150100c0aaaafae00031408001838228ec5a2d168a0cdd307140003493a2e40382c1e221c1614161809c48140280c0685038130180c08044261503820820d84aa3bae99f0d8659316536ff37d6b0355b7686ce8b1682fbb4e94407f76c022a1d570f2e0d1a13c2b59c3b2d45ab24f1ef44db8dce704ff7b2bb292183bb5302096a35dd4f2495c188e8851534859cea4a8c29d3afe3a3657c43952ce405378574c51178009009138d5379d9b927e2563838e35e29c15c05a29edf170a69ad8d3b57fb1a270a6f1683cf5a16dbfe1344047c5c9d619ef142f50be7261ebb25ad78ebd576903eec496f65d527435832f66b7136c83d1fda81a60fcc092deee3cecbbd6381471a50acca6fb7039efeacf18be520854ece86806899194140ec7aeb7cc883ce1a22d5482322a7e05304d9f29e4f452b8559500b0d19f64b6595bc1ea7d140d4f278c33e3288cc49d8c94b0cd33cdb68e8833fa07a37765e34c31c98c75a481b5424671b619d2fd65636a7df6e0c6518b95af529266eb85ffa874bdc46c70528bea47e4fdeda8e9a41ffffd87aba2150ce85a3a0f25d0247b780f95a41074d7524ca4951efe20625b9874f6f2a66a4b4d5342614b5652d0666643b93124fd79ccaaf03b9e855ec1e44ef8e2d1c68009ef606306d456050922d3882237565554638655f1a87ceeb32a0e8541d88993f8088fdb04eed20845db5a2f08336a7ac2aabca125000f9ea9098011eabc27bcb8dbc6dd5eb975a3b72a265eb1e361493ea194f9ac587aefe75a1748a6b44e01dfd12012f4c3a4fea3fcdd423b34f785c6a0994fb058b3c9cd5a3aa4bf60413c7e44a7402245179bf0a50f736dd141e63c1c629470750e76dd5c0684c07371b817d677407401091b81a11a87e8fe49aa1fa0874477cf85f78707aba31df4d03d203ff18f2601a42e4da6896d2143573740df9415d93d273f0e6298d4da987d6e0d516e0eafacc62e993348abfe87e4dd6dcaa83e8d733e142015e43cbe77df9f6bc9b89198bf233e4484245e060bcc8c5813759edf4fab07564309010616333bf1d69359375314de5b6a50e301bff6e19a07065433f93e139c5962868c3b223073da82eb10835399d3014611f6238d8af1292eab11447430205e0c92cba93f334ed747960a08eaa2cd1f89d65619705ced18c26f557f1726ef8ec1802f13df53b0cc40fa6aaa4e03c4e5346ed0b9b5edd2a251d97173c5b6dfb77ba1085e537d5c4c678501b04a318285a60251d3412bf0f0997a7c834e456eca43152c45faca4f153a4548e5920a9d22a3729c0a51119bf2aa0aa822a3f24b0549499202ef7e013a4060ce528a941ebe5ba8b89c114d34cd88edf42af951f9a1505d381129e305ea8486a5048a02feba312b14f903a39de640cd7cd77459c7b726a6e9cfece2994afe4d195e03c0ff155c559d6a091c3de4815800fd7b0720115a08ef40d59bd5fc1656e35584c3e705ef80e1d2b49939171b508524363469d8425149cb6b08d06b46601f52742c1fa8bf40c806ba1038271032812e044e1138c4116866c454d642041af11d1899e00c94302b6a082db464eeb5b50b9c6e6038a6f6b457d6b4498a2285d14b535ff4315b94384954568169da00a89d1119b418bc929f2c3bf03989709a853efc68e7f2b3dc667080636db0c92b0f7e3001292163302802b201668baef910174dd6c379d86c82525588a3599c2515a5a3c105646c6d85cf8d19d3fb17083519bdd1cd0f0b9fe18d7ac3fca672e345a4c35680249ceac0ffbcefce4138c3d2d82f3a53b164c609fcace8551aa5231a1820b4bb4f86b6e70325d789b248b1a9fe7ca6da44e31d5538181829395410bcc7d3c798527c972e80b0133dcfbc621638656dff4e4b87139a1ea6a8f4613d851f73e3fd9ed6aa45a120a159b225eb382fe5e560879154c9a4984c79e2b3d3e39a2485c8e775cfd7740112f01d916adfef07a04cd8178cd0a0e87f6cc0b54778b08b9eb1038c6b1921c526b202d964ac03016de20a68d02f7a038d502f5558b1aeec973c6181cd27c6cc5a741ea80baf7ed6770fa94686c1d6999b94e98166938a44f1563a2f0aadbbef98230c558b3dc085667da1728112fcc64cbdd92851cc37184ded53d24c315dfba125d8ea724e394b5cc221a5c4a56cae765bd392f10fd72c212fef5d97a7bd685d934bcbcaf59478df82be6b44ac9f22fa9e43882a0ad406fd824d12a79fa7734e6e907f991e35c5b1763f757189610675e79acb38dae0cc6ac6ee8855c418eb06c01310a4c92a6a27f669561e1de7ddb812e81af0a879664c275052537de4cdf646e492ea1cbc2d2c27624e7be436e930eb11a2f29686ffbe04797b45b993949b785d2c89664aba476e0e9916fa982b1590559da8d1eb11d0a90a44ca69a01c4049ab5c22f5412fe3adb4f5eacf2d68e8ae8cb54a292b2e73710d2f1e334f4243989e208a5c9c942d35083b75f1ddc4045740cef663ca719f6b70d195a79b809d46a0a82d0a4aeeef5f4de02569a4881788186bf4fbac41ff9831675d86a0a5652cd2848e1f4d65f1a66a348692e8df765bc20fa484812942c174cb30d8ec9e2e10d10c15912898fbe6de0130297dc21b6ccd1246f4268409d517c39442de72437524389558d6eb83561afaaa7f43f651ab608526e299e3529c5f7762338f9951c6fc425d208c65aa4113c6cc495a80616e30777490ef22612284f9beeeb82bfe6263fb87ded9f9d90d2fa720bb689722d026dc63cc535380a25ec28aa6f1e92b244803f5e2d3749040d471adace9afb131e12178853d21794cc9e3c3a6d22b693929e1b0b0cc8f891a7efb619d97fb507248f7fc14d763ebd21563f80196107c6c883889f855155fe1a002fa225b5de64d774ebecb16768e412e3ddbd518d81b3a301e7a4330dec037cb38596f4d0d2080ce9647e112e3f082e6db9db073c01b3d2ccd2eb02ee1f07b53ade1f686e956bbf090c4648e81744a5af356a41620352241cca635b14e609b9099c0dd6151930a40ec86296233c155b5de105c74004b8c19a013ab6f0aed60496b9ac2ba88e876fe61bc3f6a4a5098bacf2080763b9a19592cafb0653ab9c9d52e5ff0dba307e6e4037fb849f2122534aa9555c05f3fcfd38e22ea4dbb0a8b3ad4492f41305441a5a288cae7c76388b8bc15d2b1a2f5e63b1b512b8b5e313c42186fbf7e33108848e295e0e407ed1804985bce07ed5fe7218b8ae51958a7cb6228a69d487ad29278b031968322ff0b736ab5f662006dabd5b16055678802d779568e630401c63bac44a0f7593be6d46f288230eeb59c5c6f94be92c5c742410d013ac309f56200c9055adcdfcb8c28d5b1a6e568e13f93d090dec93e6e893ab5da9cbe9bdfb221bdda83d08bc10642062736e90333c29dd93f1b278b00ddc335c08f243b0510104aeab9f359c190fd6441d3df952f05900b7bca2305048adf25d15e002c8fe33aeeb77c10050a92d66c0827d21319addbac733e2bbac094a6a90d52021bfbd98200a07056569b686cefa5296a26b17067ab115ac50372d0bf50943d8fc24d80c905605c970611f0276d7ac529cbf152ed423c98b708ed1cc0765d540cc6c10b7f67fc7b3864e7d2ef020f7966d762a722e31d1523a4e99938a44dfd71c6279976629c3000cf1d8969fa85713760da876d76c18a67736837db50a6750251fe53b10f8400b9b807e1ba23e6a14f05ecab2e3622c1d255eaa650285e089473e0a20ee49a39891e88c0ad0ce2afd7fb54c9a254914b08480598e4c6e05031ffc6d923c696445dfdaa18deaef60a12923698979af5c1e4589f13f8b0c582d67a1933d5fb5fc2a836aa46ad7fa0a9ac77d5e47cff1b2fec92180dfc2c06e23d280e7e67f1eb913993f710d1e38926a7c5dea247d47a9fce0f98f4cea76b5491e6a427e15a759c540e36951c5f151c676c6422df034adfd82e3d261782ebf4f719ddca7c7c3bf03a8c03ae3257d33319a8e379f8ed79013a586f12fc66a66b8082f318e5536eb53b46b1233f09024f1afaea5d16201f3590dc3d9b9c1b2a1b6c41683ff1119245611e60940408152ba8b3693cab245e0a4cf5ada73bb932d57ed27c2d12a8ee484505ccd4515b22adb5038232df1b65401bf10ef86c390f1f89352bc55fce0d77095097ffcc2cb89f91815ac2f9a827993d04876bcd8f1d9d8e82f50a81e3222d4b8b59837627212030509e1dd34adde64e9e375fd9f6249d4f65f0ba367f90927c40e670dad7c5a953161c8e49415463bbe138b6ffc00a59f123030b3f634c743cd5582d483ed9a91f857870d83c6383ca4939a24b14721384def78c163d6a96b024179949196304cd2c1d5ab8598862997235fba2ef776f5aa25bb0a25ee21ff344724cbd827f1d800d9d3afc6321b70d81debd5049c5f058e84f0cfa410a4fb3dac20d8bf2f0a4a96e824ab3f620f9dc4463ec740a414c22153b36a612c35653b89793e2883b528a57b5e9261460da3ef9d47003bbf171a8ec1c93936b50d641441b25a85c3abaaf21f02b911b4e61335e6785a25004461b0a05e49c12c6b64174a4904a6945702b701fffa44a0fe45e132c4a7f5763567807644a9faceb11463e86a9347975d1311830d19511a296e088b020211359f65abdc9578e8a9d0f5d3de0834f1145f7821640cdc0a64f33062d8191e6500d773578e0306a6c281a58dd3d3a9d9ea39cee01d03ecb4e6740d975ffe713c0e9f30ee02357f49ea97c1b0c53019569a636f8b56e1c9089c83acfb42e5d94d56d4c0c8005285135e484050ce0646534bc840f709c8429d8f4b1fa715172ef35166ec61c29dc80cf7974805152f8d8566e7db0caa31ebfe6a42442635b55db4f24627c2bf0a17612141351506166fd137c277da1bf48bcb6026906fc9c974cec81484feb4c32b39f221c88fe02ea90610e0fb72e01dd48f23760e0f4eda0ab1a74119292cb909d6afe65c118a37826f63f5a5124f384d636a228281326236db3fc94f17427a0bea59348d097ab4207fac7d4e9e26c69499e5d5870ce055f5da4f6aa742351fcf799e1f211d6e62a7317f8b8d6d85663411e563f915eb790807fed36b1eff6eb64b38f879fd2d30376bddbdfed8cd0dc50444214edb7a067c06874e59ce60bcff56b12882fe480e83f04b02ff538babc24fdb4abe15abf2cd5a72ec2ac463dd64f4ba03a4781d2a32e94d028841f2a5642737a373375a14bd4369660c925a2540a7910d8201ca5517265bf5bc7cfc4370d6195424419150fd27a636364f7d33b2dd821d945b2ce76964a1e91294a95b091fd14a268f6f7a3389e37c613eae0374a40177b4c647bca0029b8583dd33a4082289280b0db078e5bb1d50a7de2cfcf42b6cab9b93dd8f53591f86085468386f25a2c5dcec62a9ed7438493729df8348ec9ba8ebe1d84d0ff34b9944d2dddf3c441584c9f84d47e616ab9b3fbce2196498281a9ed42b14e8fab9abe1da46396cb4a162d89303f2448629afa319b10c812451f8d2322858323ab9b1c3ac1616a9085eb11fad265a01685c9397547bb09cf73d018aba4af72f6fb5723c524d5b0b244af2c6a04af5bd72f0cda69a51c29cee116df04c246aa4d4c480243bd6c6d3691362103005a30cb6111feb4cec5723f9c7c6978d0e9a525d6e092c9324f60b5a25796f4f32a04226487fe62ea183355764a66a0a157137f46a5660aef7e704cc1bf6916a78ce1ed79e0cf34a53ea22f209fb5d995424d85281ac2d026f19c12a1654925cdf1b8b3b8155292a0b3e15423306f9f8b0aa4b70bc6e401810e2665122e4d153853daef3cff734bcd080cacdf648e24797bc9767352474911d9a63f1c4b63557c998ffe909952e17df3a3ec70a924a6f28ebc3a058baa7cebc5368108cef5338d139343924c3b07abe265a3d56536b120c8c164a8dd1cc6fd6f935b0859a10c403923cb0e3158c3401744c581bcd4201cf95b2487e11625bd7739a94a9511041a4375c722a92d623419b6c00d822b4ca61840cb045a2b874676960cd833a6e4616aa734638047836afc0226c66038e6fe970ea534a15daf3461dbadc2497af9480bc9dea68ed54c0b95e29cbe379d0c673b3cf9663064ea0a98680ab8a3ad8d41058ce241b623ffd37c7d56393bb973ac45472031483e52aecff51ace192050027b3869f8ccfb3ba2498fc6a451f05a3453de77757c1419838f12f3801887c099e4c891e46423788cca5690d698598547f4ba67701101acaa09a788189b82cef36cb0dbea66664417df06cfc3a97cc9eb9794efbfe906b7ec564439b748345cf920e084c208d8976cce1a29bcff3e92d3e066f7f7a85b8f1f67085dd759e69c71037a371188400c4258227090629b23dda1256c571cd832042378d5dbae73227d89c7ce1667f1c42345ab9458626bb0121366902dcf30142b0cf059ca78952947358114f9772c4e526c4e7e93b48ea2c009b4121f61812832b65f30354f973670cbce0cb2ba8b4250326cf60198a421631b953b224f407931384c5a7d5cd5f35d9eed6387518dcea7ef400c7cc458cef5c36a40e5a74d2c4d39765a40e03005cced7696732f353a992221cdebfa5b67fc6ea8dea69a688d70e44f52679ef960d34ea7b8db356a32b42a8fae7671f44ae9a056b62e61f9bc8bd0f2a871840282b2eec08b7e8227f0bd3b03f9ab80715e7dceaaefa4d73b1aaa2a84778b9e4d2b9de61f017d587d7ceb60d1623040a28afad4d9c7df0c12d4d4d3a881b0c84c7f6c1866874f6907cd1e844b19e8134dcbaa666966009f69f6cd6693daf86951a902419e54f38c860ce50fd8b69b3706d47c2cca170356838a7a28ffe1194785b9d14fa9a61f2c4660983b28b05299c36343587b449cd171125205076fb9c32292439987a21d02f2d30e8c66ff410511ef0f98e6a0f883faf693744288f8e6c7bea8203c9f717a06c67f2bf130ca28fd73c16fa3e670eb6d0e3a4b9d2f1fe2ce6ee95d16893716ab0ac8f0f9fe78337417c4baa470b4e1a8c2e6b0e5aab8da5d44ae3dd7cc25ef5270055cb82ea1bb39bfd0b618c70c77141efac97d68bffd40a82022545f4a9bc20d2a63af059b484b252429a844148d4a4c515109293a458250f751f76eb996d4d32ed6e3f6b3bab193e2682f3292144aa3cbe772552e3472c4fb93876e98246b043e21a4fa8b89dea12298510f8c111c85caa4be54b717624ba118d137e6b13754488df83f2297207257d4e1099a3eab2486cebf619e99540f774e2f7764d5af94e32b9f5d59bbcb4e63eecf8812f30d1a850cac76ce7de7ad28dc44cb77061f82fb1e82d8c2db75e8973d543cb1ed792b5d378910b5ca4446ff6d4d2a9122a644acf7f192c4755e5620925893fd1ec3b75ba0170be86d811b2cd08b05d4930b231d4572d6ebaa60cfc5ad66001b96751d98074313b40a144a6048462ede899f0a148a5119c8011f3fe5a5b5e727ee873082096a0b4e504d409dadba2409dc5158112a732f20dfae1ba4c58fbdeaa1dd4c6de18de8d2d12ab629cd525d7a70a0bbb401b3d26ad14709e78339f385613aa3c41cb9448ee31b1669536f824d6ab24cd6a44cbcc93761d3342586d00c29bfff21975e501cf9b4512131e958393b8b23fd3a196554a3c9bae2fe0fe842166c836d05ba2700e2ca6961188ef0a22337020231d75bf3f88f63736407edb81dea300ee0600ed9e13ba2431cc683d0cbf8878c5d1421039b011c6689b657f3f1430eecd81db7031dc78139b0633a2e07ee82ca18823faff0a5e717d4014ddc0892fe9a20feb93b1747a4da17dabc7187a533d94eff03de0b5387e98a4e502fee00b16dbc290a648e5b6dd4b2ba856eec631ae74ae0feeb741b86ab74d0ad9dff44f29e9cf8449d053e2b5d3d681a8dde00eaf62332b5dc91957dd5b04f6ef8a4283a96808a0d6880c6fbbab5c91f2b5ba0ca1029283c2ffbd31b8be5687cc85da111f0338690a51e29c9b6ccc208afa2b9551a071fc981c0ad074d2d3a3f58b2458cfa0b5603132a4922d59d6ee3ee948e23524f6ac97b8686f9a879cfb5473ee12dfa9fc9efc83cc268f1d455069bbdd0ae3c100b463a107026ab65f470592aa7d0305edc9163be72e1659d404a84044887b694853b047a52a2aeddc70474549309703447395d7a98eba5a3759e601c03bfb74f8c897c8c4be5aba1bec4d5d294d4d18c5a1f05b8eab253d5922131a92066d314cf32cd73beb50e41386fe73f4db294224bfb3e36f0fe93480f3b4f90042251081c23c9057d241b8362db8bdd6e710e72b818983287a1eeba3b7bf251966bd23010eec78a7979bda53f5a8edd0195c2ce229d1c4ebff201ba228f60249779b2385478a8976a16c4ed70d5741e38b204a75c0fe4472a1ceeb93778493ca9c7416b6451f2e5468bfc5a0aa63b1b2424166270ea06c383edbaf51e2a97e52636094017e8f381731a5d09741b72d45fdb11c70692332d1c4f3d4be6962ad86738d65a35014ce6989f049c3f4b92e5a96b82d47f180f12081791f528d8d3a716602d66974c9502df87dee1dddab5d217d3e271cfaf1b2fff75dbbe5b7bcac402258a2b5eaa7eed57bfe0a593743b748326ba30df3a58e0f97bf59c208c3d3f616810cd3f100c7f6dfd0a534fefa05318a1530ad40bae211badd0501801ce001590d798f010369c2378333b64ac386f47c47d38c05b022c268d8910b10284bf1b3c9cfd520a5ea7a0ae470fcf0d94c21173c819a3e81a57c2f8835dcb7b5d6b24a6d7ef906edfe89e3dbf58ab30191c6cde659ef03d2d785c8231a6e59e7121e2773bcd1e411a36dec6b1c3728339a7ed27ae1541ac62daf5c58f49283e85e420f05efa8720662328cd92793fbe4db08cbb54c68333fca80e15c94f6e5b860ec97299a30c6ecd970097a60ff85863f29c775a70396210612e18aadee964012c177014286f429f129ae060118750c602b4665d2a02349cc428dd172f484ff89c04d8493b4dcb185fac284f35ae07cb7cb4b0fc5ddba822e320f9d69a07a0d9ba99db5680036ceeed33491aaea2b55694b4eceebc2f06a3037384c28887480bbb575cb616cf0a8403cdb31a24f655a7e24c90d8d57f3f01cc37aa2c49318a7da53dae128af1e945a36f8bb9d4da10be65b4909167b7bd0f574680cdfc40195af2bc0c3935412b522953d9180af1b6898aa7a6a9bb3aa973be43b5ed82640773f1ea53abf902aae6b689ffb181dd4ae3fc26c9e7da1f31a7c0f74c4b1c18f9df01c68f63f3bc637590da96691105f60eae71cfd422d689b012cb9a7af8729cb4c87dbd55911b997b22dda52158d7762982fbb633bf12b489a2b2eb85075a3839673562c25bfb8dc44f23ba12471a30586624c4a1be691ede9cb21f1304268a40d30f7ac0feeb497d3c3eb446057e740e64a2577224875ee23474beff24168f8e38ba47cf54f37017a3008eff9e91a13bced19444ab91202f7b073cee32df7c432ed879a5137f5eb993f98f75b02e841a13db05432fc49f47d1f549e8f1edb21d6c2d860a2c2a9f02359456bfd364a7348f0b3a24691f2e0cb9ec1a8a10e4420c819921ca7aecc9d15429601c4a0ab26f446ae9564385ea2096e8ae48953ea43d9e091e458cf998f663a06c1c33bec0653f4e7a859de625ccb6286f04cb7364fb79c73ecc255abc19cf97220f3b5274cd5bfb271797433420804244ccaf4e7a39321ebff45c11c678ad4f241deb88280e19649fb16818c94573618d2edae980e20f5ca8628c4316d7abe3ce0021af7f61584ed902ce712108e900b3209b1c73cc33806874c3da69c19de8787eeedfe935456686f364aa2d23b7674bfb17dc1d66f05429d09291091e2927f83c305c97636df631a36abcbc41111188fd87c8dd1a524400cebb19045e3ac928d91b902d2730c3b737cb58e8606e0077590be4fae56225319d27e3c2abbafa3f23afa2a2b5cda46c8b5e044bcaf94d89afb922db41e1f8255c54cd1af0d629261e2aa93f029cf237f81ca3d4409da6fe6673d34fe85e1591e8511ac56aceba26be282cf1652378fee62481ce6885ea57d3550cb966cc4a563b24dc3f3e4d825bbc06c4d2069139fea4e4da1cec433a2b2693661a846042e477974f15455f92391086b501b8d7ec5b284fd89dbc2e31596058a8170baf270b23dfae60bd57e8a2a5de34abe771835b6c5ce963e65d5ff4e25d474d9b005c9af58d1aa6ef614a680d0ac7aac41fde5398f334d2d0544d97ec1ec0f830e2e846348114addd0ef086c145f4cfc0ae95e86f3428b9322c1dd3eb98e913fe81ae09992c58a824d3724079dc990ba4f3314917fb3c7ed5eb059ced65e401320c83ec6864b2997756655ae66173649ccc7611ffdf3bb8f70ce58c07112d2754247c194d81bbffec5a3c210fc75243172cfd5580efb94056e3eda0fa1cad085ccdb18efe95419a2e95cec2ff7393947c8f1f55ff5c2dfa882142860759a30b57c88e95a7769bf67634095c0da04da12a340510ff44b1658931ab288b79df000803af2afe19380eed605a14da37c7ff9b4c08c5de5abcc326711c83302e6ec049d72ad5f4a8c87d2f7945979a0d8264b442727466789fe38c3e5a9a7cedf1ce1caee1f9251002272a9813e5e95a9cc58076faced3f78fe7d32a76fe47450eb93ba9bfb172e9c3c9fcf33152b1b727c79dded14ec0ed3783a2493511935ded8990827abd05d0b9b73d105b24c95d4674130ecc829e3fc462cb8c48299152ca5dde90b57cc694dc60dd6560fb33d6d54fbd96e55815a20147fd04efd0ea6877079683d5877f867ff79cb371576159239a1314969b4f1e23a8a1ba149f8924823189bc4f8f9021234deaf36e253cb06a55cf20b96de33d580159c73102d3324af31fdce5a7638d5562615885ec186b120073c0c0e18126db98a120380c2ae19a08b648639ad93caf79194869482fdfcb4177bca650d757336a7e570196c64e8752780ee334235d093c576d25f5f02d848605c5e75659ad5e566e3b5ed4187e14243010def1ffdad2c349eab5e0b89e48427a46ebc569a1b92b6e5cd6015cb04ff234531e832999a2294ff9944d79caa7608aa630e5533465539af22998c2944ef1944ce144b45689fcd61eba4338b4876e084225644221c44274e80ee150ebcc4a69d5db68c3415f19de0a245031dcb414521035109407420744440422272245704446426484d2d20e0dd907c1b5989b6cb6b66a73ad2e0653ba28c1424d2d31ebd7194105cdafb5b5f7706cafdd355cdb6b770dd7f6da5dc3daa9819aa8b11a5dbb6bbad6d7e09aaef5d5c1fbc0a4a30b49caa429dd9a2253dcd3b11a0ed4c428b4425f800bbad017e0822ef405b8a00b7d012ee8425f8005293c05cf800bac16394616a3e331e7573e0756d5fb6138af137d024ee8449f80139a3009902009c9c1ac1d894aaac06768e48ae48818f124391631c561cb0c0893812e85d13789bd904e8cd4a6066aa1268aad56bde7abf544e23ef9ab73b92ee2ea58aeecb83a97cb3ab2cee5ba8e0bb37015300bd8c0161e4332d015dc530c529b0acc9791002bd633d0ce214196b572596065b39c15649995ac825ec916bbf22b63a5b20056b085afdccaaaac95cb022b9be5ac20cb5eb92bb0b2582e0baeac95cb1ee814cb0bdadc3503a5aace65b398ac74f9ae43ad56e7589fac7619d7f5825d8005cd60168081142cc32fcc85b2800c73a12ec0c264a80bb030188a612bc80265c00b928568300cc73019ea022c4c86b2800c63a1c4e01b64a133c00559e80cc094e0083e0e70d18bcc9e4870c68e3633d1aa44f0d4a8f51a4e5f140f4e3923da2e86646a8b410d969558e8c57d6be6a0a9fbc6a5a2f04ea2d0e6cf39635d9631537ab156464ad5ded44dce06984267babbbd3fab350ffcead9402157e3bba484a2ede48e5578fcfa85b1f101e2b9ead44aaa3cb5b96617406180d2ecb68b875c58ca695fb37758a686bde9666ab3bcde63ea4857e7d0cbd44aee10834a2ebd7b50bb2fc69e20e052f8d6ae2080ff790812173be26278d0d8e82b872c43cde6b65bc0822a070f8cf665e4ae86e2f915cfd16c10f9adb66ef47be801df5961d6145de805da10516b82e6077ba0bb4747ca0982de415e20cb643b8bbec0cd91f869df6fef9a8704ca840b7f43c8dde98527c38b1a47a1543f0bebc83aad71465e84ac25a2270e53fa8a15f9e443d51c01cd5b30ac6263a7554337ac7180e1967aa2fc9c1f48ffae6d21fa9b446f49be132d457471d8b1a510b4c7432a25e3ec565f3463f219f2ad10c2b2d018efa90fb7b4997b259c17aa0cdc03498803ac2bf200630dcf0ec5a67eef8c021a53976dfbe3dac2334663cceddbfa8e8314e965fa7c25dfae450906b7df587ca8bdceb1d695188fd0abdd2a4a82af7b0430cb3010bc4a95178f356375365be131719fa6a46b262be12315f85c4d84a1a3ad425f8cbc1978be590685756ac29552517788d01d97892e70bc0b3a402928838283a9b143979442a16f1eb0855dcc41407e45a3581faa52ace53bc1d53824a64124d9306643002628ae66c965f34242688e6acba748848d7d3325159c0711568444d5799a420c97dc708fb02e14a179a40f18be36093e499d8b764140c308f423ac6775333abea5e9209250570b135bcb7c62dc991f77c92d18366eae30b91699514267a0bdc81896f6a7ed1d6603e95ca00162c26db1a37c12e7b321ca670c81c87b0a8283b22d4d870b43b6e3cd9af2ab90bcb1b3cdd7ef670f91ca4fb7d9e24286f38358f58d7a84dabd534c0a05616c78288881d4122b5293bce9d1dab0afb7457f1e90ff5343aa0824722cd8ea7be75d5f80709b211801b0ad34e7f87264feadcec4858bcdef7026368c6e44afc9db5e5c6083db2c193863d6e6630cd1a168a89823becb87e74dc937902a5bc00049828076f42310fab6fad3a4de681498c1d74c301734d822308f2df2c83f398d0ac49f0a6770a3d6a0cdb1485908057393fad1744e804bca6cb552d20992e26cb2d9e3c93b533153754c81ac065cc48d29c528a00c4f03368db0d1a566cce2588d94bf3fb802324a2f83cff0a0b28e0a4336f6bbe571833a7abb909cae498c753bada770c41a16385db5ae515cbd417a7589353fb8cf0564f6db079163f7958c2dc8e0d06036ceef6f39f5ec2c23d7250dbd5f5ae8a8118840dde3b11a8accbae2f728f270b989b61647412c98d9b3111cb71dcd8540c507d1786c0b16491801d996b420d357f03db44ea3ffd397893bda819f953014dc3755cf2cca86c5c996df49cfa129b192434f1154ed960663c477020e00057e0c3460f054723033333333333333333333ab9adfb73fed29d34e9936b3c45ce42637c98a0800fc45f6f1e9ccd490ed36b783770c0604940c690ccf0cb447477a1fb58701c081a94a36914eb301bcc1a81e6ccf56224daeb881a90ef7683fee51e2da062e76d4859e75e8a0e360031f6f7f9427df6b6035b88445490f35f01d39504de297f6d29a06c643e83052707f0f3f8a06d6766f327f3d5e8f43cf608a39ea43ad8e3dcc0c4ca59cd3879646e3b55a063eee66cad4e9ab93a664e0eda3ac9892a93ab31463006360b543a9cd51d4fc6126062e4f67bc34a12d24d23030ae9273f43079cc910e067eb287b163863649f717beec418edb375ee0eb322d96c66417f8149d52e78e6c2a259f0bdcc55cb35893ae1bcd2df05131d72f43520bdc547687767bbd1dcc2c30b972e82975907a399658603aeb5fc3f4c31c6b790572dc7927b1bf3bdcd00a7cbb868a615915981c688ef7516a9a5d546073b8c5e0f19eb886720afc87571d779a14d77a29f016c9c38d9c52de148f0297336bb2905cb97f28b055bd1ef162c6187902bf19628ef4387e87ed84bfa4a38c8ec3b428ad097cea74e983a4494699308025f017e11d75fecc621e25f0492f96757093c047d6a9237e647b5b410257392ecfa7153a1e47e0ee3a84caf6e1de448dc0de7b55da46fd4a125204ee7fa3c4b5abd8d17e44607a2743eacfd8639221b0398b7b777d58b27839c20084b0b7c58ef552b20a068189ea24b9d171ab8f40e0bd3fb25091922e3cf2837f73e472b3eec0363018800f98fef7ce5439bab8790f38774bd19972340fd8aee01f7c73aae777c07658e659dbdea3a8e860fda81c679074003960b73cfc14fd70003860ac3bc54a9fb427cd5b70659ac43d426cc195e788f97af73db40f31d40a5dd4823b8fb3b4e3ccb12591166ceed83c977d8e8adf2cd8e05be94a63ba493959301d69b41c6b0e52feff0c5dc4829f4ede41c8e5de372cf848b76cacbd3d8cec158c04951c676f08b3942bea1022e34d9eaa155c797fff7f871d8684ac603447e6601b33abe0430e8f5c5d55f09552ba540ffc22e7a9e036cfe204bb891d6551c178e650b6917f53524ec18aa79053db3c537093123d0841d2774c396a0a5d94828d68efd02ef1edef48c1e55787e487aaceff28580dd1a3df95c817bd28b890524d1d35978f27147c68515b630cfe9bc38082c9d2943fa610e9573fc1584d66f1500f52563dc174bee7a8fb3878587682bb24c92d7d94a5f38413ac5fc7dfa7dff1647013fcd88d66324d73d334c147dd663965d18ff94cb01325e97d3d26f8107e1dbca592d6ae4ae8e2128ce69b5dc7231d4f4296e0e30f72c8d7ab7bafac12d80525b8f068f3ab6f9a6bc826c17ddcd1839d8deaf892602732868759ad361d9160d7eb34684b75a614487096c253e5c6ac95e023b8d3104bbf5d5b342b47f09652bbfd7894bfa56a041bbc7d4345f9794edaa2d0052378b30e5d534f5f4a0be96211686e49ebde266d2b829f549a1e7aa80ad12782fdf872752ca9db1f0711fc4bc8ab217a0ec14dc8ef1cdacaf43b0e43701f87ca397aaa1462ebf83b7be42fe92304db7a5696a38e21b6190477e972b24222765504c1d8bf04d5fe8f2a6520f8f078729c1a91969783c1610130c2888153008c30c038c36fa07144a08a19c718f7842e00c1ba97a5ea20a77695fcc18c39fc0ae963ca5b793f3099935a15f1ef3cc708aea18b3eb0ad313da79425c5e9920fbb649f4656750ffcb4a6daa9ddc71dd1039b72e0b127697dc8e4818b2615a279d4eea8e181af4a91e5fb412efd0edcedb6c66fcb0e4cfc4f676dded51bb934745107ae2ba68a308d0ed52362744187737690f28d9f470b39cc38c62833e3a032e318e3ba98039f83a594bc160d7421072652a776dce6b1640cdd4803052fc85ac17185812ee2c0ffe5f03ff372e3a4381cf86fb3201245b42f95f50676f25f5735bf6ee0535089b6b983b45d671b981273af9462bd475d75191d38c00162e02a2836862ed8c07a87b1daf73d3f270fbb42176be0d36df4a69014cac33074a1062b5be674aa33ad0c5da481e9baa893cd3feac769c542176860b7a3510b5d39e694f52374718645b2ba62464b480f75b1a88f172155972a7843cb65800255397461066e35d8d9a775d55ec4d33826d04519b8a97c5f9febfabe7243e8820c4cab6b596ed45e097d0c6c6aca8e36b8e7e588818f21f5313bdad46c192074110636fd6b45678bd21c2142e8020cec67873859923234e5bfc0e4f0303a777853517b2fb0493c08c9ddab93a3bd0bbcbda4f89fc3b6f1beb9c0a76addde982b547e8f2d3026393489a9b22b4f0e2df02ed6398e9497bf42340b5c8efd15a97a62816dbfacdf7c55123979053696e638c7efccfd9a5881cf8e96be3a8ed22e8f55e0c35c1d43fe1c2ab0d6419c5a8b14dada9b02f729b484564c31265b0a6cd4682b7ddd1bb3595160d2cdd3c6ca669aa30a0a7cb49e3978f8dfe7d171001861d4a08b273029a1d3355df9f9770293a34b36758b1d84fac300238c8c41174de02375d0d5aa97171edb051338ad880ccb14eb6209dc679beec7615fe842098c652f9f98a2a75a1483d0451238eb960ea91d479bb132882e90c09a651ccbc8cf213f7204eecef253eac83d8716bba1b530e8c2084c320fea72e7bae820471138310b1a793b47f915a38c2e2fe88208bcd805cf9103cb51f54360634eb52969b49055ad3184c0e49426685effc9b19420b0973eaced88f1ba42c6192d306294d1ea822e80c06a8c481ebf668f7eba1f709136a3dd04cfd1bb8c26e341173ee03aca762b779cd5b6f780498f8e1c73b53e87e886161a3ce0f33a6be8d89a7b97ee80971c67a589d9f1eb7e37b4c428a377075de8804fa27e71cac32bbd740ed81cfaabc573e47bd4ded0ea0207aca9945ae87c6fc1e708cf1e72323131cb0d2d5bb079a2baa6f7af05d341ed5f4aa237b4c428a3092dd88ff1f2c60ec3543b47cf82c96d7f0911737a1c7464c14f3c0b7d911e69c4cd0cb88805ab924b4dc2acbbd2060bf6f7739cdf6621258582ce577092a393e01ffec65b0c33fc0c3348065cb8820ff93c55e4ca41bdf456f093c5535d781056f09e33a64babf1c3ae1cabe0ee762f75fc99933748bac0852a98ca586a792d96399e0a2674478af81379434e72810b5430c93e26f58abccccf29f430a6fbeda66b0ab63de8c93475290513a4e3d8bd625a5aa4a460d5eb573d875993a38d82adb41faac4e31b4ba12c70210aee35d2cc268224e022146c89f8f927cde9ff2628588f2d9d8690d3d17a643bc0c52718dd1a9f1c97e5a027da13b8f0049f73e6a04ef0361d4b96ee78d93d569ce0633d4d1e39081139291a65985155042e36c1e7ef4053444c1652c5052b28030d3f43bb6882ff50eac523e77492f399e03e63ccf02c5af93b4e1a64a0600c4c30621134737ead28255e82b311cf8e5bdc33a26e0926741c76a496ccfcde5a8141c16941fb195a093ec79ec4824d9e8c335a6016045c50828f55c9a351b1f18e4e13b898049be35d4e648769cf726e684982771b110f7939c6f79a065c44824de312d7cc3c8cba0c129447cea2ae29f27b850c143c628d398a395887a89dcb0e90e101b2159831060ab00270062e1ca19b585b9669fa4883af75434b0c328e63054607209068ac13b868045f621a194a53bab8a11b5ac502a9682c23f8502de6dde5f10cf517c1bf7ff0d14aeda75225810b4530ed66a113d3c27e6312c1e7b71ed5b0dbcc1655021788603fdad4aea9bd9ba2e82a38c6580470710846cd73872f5edf6a1a03c2b881c61181b21710811813e0c2104ca6c8fca05d2c23fa8560c2523a7b95288f1f45085e63289d14626f9d3683e02f4e648b629ae287a120d87bfdcf8f2a1d08367e4a314a8eeeacd307106c67d59ec79883fedc1f385bcdfcb175ac1c43ce0f5cc7ed30580e2bc4d4b17de073e4239122049d58133ef011992c721c4f7b60b74f3c2a9d589259d403ff1235788c4831454dc903bb1f53ea73309b4c213ca0751ec5a4d3d577307970cbb96254b99b76a8c3bdfa225eca4a9304031775e0ee75c5f5b55cbd335b032ee8c05bce39d0f5d4e7c0e70cadfdf35b0e4cd034c1a2b7b6fe72c4817b354d39accbbc145b38b09ad2b9697e58fdf702818b37701f3a48c94fe35be50fdc01176e60c5a26eafbce2646e1c328e334c1bb8a01df7c7178d78c9c3063e7918f73bf46e4be671b10656d5e3caef8f3f96a48a0b3530797def472d4fc79371acc00c31b84803efa7d152bb221672430323b93cbeb6fafee4ce19388dddafd1bda35a0bcdc0fa44294b163c2ecac045d37bbf90dfcb62c60519b80a8da52d6ec963d563380a9aa374314522022ec4c064c799a96d374747231761d8bb3eaeaedbc6ca196998c1042ec050dad77a8d764dfd7da1ce9b35e2f98ea7e586de800b2fd401cae0a20b840c2eb890997ad81525670d9e918619cdc516b8bc183cbc35bf8f742db06ae639d49c7d33539a053e73766c8f95a38f3bc50516b848ea71eabca92bcf838b2bb0e1d1af72d6181ed2c78515788d1d45dc0f5f444c3517d85a808b2af0771d59ddd312071199f284468d4cf01915d4f22453ed310e17a3053530c1da5e688694de1b5ac6821a97e05373f48ccc89a55bb104a717a3aec3239560b26ee65072eed0624ca5183528c1c48e28c92c94788ef5d9c0010e70ce38c0014a5063127c4829f2b287762e9a1ee38c32fe0c37830c8c0e40e00faf00b9346a48828f425379fc1771c52a31360444043522c159c7d349493fca81ae90e02e73501e699cb06c5d8d477021b7437eaf3466a08623180d4143c5c57beda404d4680431a30623acc622decb217bbb72108279432bb18622d8f550f3a4ff50f3a34d229816efdcb829bf3cb43510c19df5a79c62ef5f06cd2198f8418e1c85b0fc8bad21b8fa28460fb183942c29049b2fe45cbffb15db0a21f848413f73f41ddb27adc620b81cb865dbf00fb2ab554310ac59c7a994163a7fea080463b182a4dc1f85fc3080607288d1e53172fb9f840935fec00549dea17218a72fe77e603f8f9564ce71f6abac156af481cd17ff5fb2fbaad2e603aba13dfc5772baba8f3db057ffbee933487553a8a107eef62fc73e0dd29d19156ae481d54b1f26d7f5d0151ddb871a78e0735bd5b4338688e5f1a1c61dd88ec97562acf050f5b703177c3465fad862acded481d5c91392fe3651632a0f35e8c044cb5139aa585b69c71c38eb2a0fd389e686961cb80da183468e21cffabea18556230e7c4b4878d20fbba1b55e030eec7ebc4b5b1f07c17cd553e30d6c344b15ba31651bfb4c0d37f0e951ae8b981a75c523e150a30dec25b1cf7cd961ceb17a430d361c9ff7e6ecb59db13d51d45803713fe8491352f6961a6aa8814d7e79bdfbc41b5aa51035d2c0685d5f6f503d1706c7186938040451030dfc8ffbb8777421c5ad6b9c81c98b35a11df76630a4ef38fc98b52f032b112f4ad34a3cc9e1646045e3c4184f4b2f233e06de8396d4ea498b81d12e55db14f1f0201d067e375fb61ff74ce90e18d837e9b20e2d87e9d62fb077b1b27f52ea05c642b2d09eda7b1d325d60ffe258b8c04ef28fcebffc5dea2d70eb77398a19cbd5a3ac16f8c0a32fefc6942c305d936abbb38e055eed52fcad8fbdb3a257e0327fe6b39fecd9816a05d6334648ba5f9137a45560fc3343f4942715f8d3c971554c9fea3e720afce5c941dd47b922e62052e0c3edfca1e4deee0a2151607c6f724a4102052e3ae7a4ef9dd48fc7273029f45f3f4c314888a013f8c8536888a1c1f3c59bb0761aaf9c3e98c0694c511dc7caacdc2e814b21537866a4906f2a8129f798c9a632338e314c5123099c7a4af2cc8157468d2381fd0c1aef6cd247e0da2dc4ae88e894a4c3085cc89cfc3f96a568bd14814ddd19ca3c5b57c81511b810ead723cc05d41802eb67173c5a09113aa68610b877bfe4915fc9d762b678a2461098b8e2df2539f37f64d1440d20f0d13c976aeae40529db50e3074cea8ea3785693d21bc91a3e602ad8c50af669b25bbc460f183fcdf57174785da8c1036e4276bd246567394eb8506307bcd5e4d33eb1a8eb311db0163c8e1ae34751319e03d374cc75294757166ae080fd9d1033558ed69822012f6ec1aa560ae5a715b3eb3e022f6cc1e9d56794cc1d47a3e30dad1283ec035ed4824ff1ce577be34f348fdc8017b46035a55fb4b78fb74183cc828f3ffd73c84821957bb2e04c557434a2a3b2339de0452cd8ea30f24447473143e586d692e1052cb8cd8e2279ae4cb5bd0c5d8117af60938d65daf7c9d48eb33478e10a6ecf3c226a8e5d5d5f319a8cc3cf68011a6438121b78d10a367d278b923e8987e8651bbc60051f72a8f6293f0eb23078b10a36729c62f9dfc62d55b0fdd1cfd4a27b94d5636dc18b54f091e62c261971e2052a6e2dbdfdf3f60f159451c60d5f41a3c08c0f5e9c828d3d7599ac2437b4b8e08529f8088b6e1da7d8b628c82e78510acefd7b54c5235fc8b137b42e8c1b69a000042b580103c008030c30c200230c30380046072a00062315f182144cfef8db7aa2a8e77aed0c5e8c82bb10227baccf83695c145cda458e6a937a94434e28f8756dc95188e37bc103147c8498ea4d25f80936b287a1ec5a63654ee7852798e8f16fc82f2239308f2678d109bd5cfc43cbe340e30479c4a3e41b6e82dd8eb39f84948834594d70ba5e9b3cd2944cf0b162afb9bb8b5fda986043c464913e4b050f249760a3c60dda21fae687a225182fd58fcaba1c87be12ac6707e19adca344ebc97268ce952acf48c30c197831093ebc8a9eefec534a1622092e542ea9fb4fa5e9c748303168eab02f7ffc34e505244c21baa2221e47aaf6232c0f73342ca356c8117b30f53eed3877dbd488434c21a8782421568c6062f87f7650bf1121e3c522dcaa0f63ae8a3954118694b624829cf75d227c44f0a187913a768f43b0176d73ec503dac8eda10e4e67872458ea1b29885c05554757452ee1021d8ca263956e6f7e9a06310ec5aea8875e923278f2288e3af2e104cd0781ef67567c7e50182a49df54224e50f5c78541fb7b4a6185df981f76cd5b4c961ce9aeb3e7063a96b42b0173fb3f0c1f866efb1a7fc1cb487a398ae71d7c3a1322b86d89bd172c08b3c30297e902965873fa9f378e02b89e8675f761c39ce1db889dcbb59d1ac2e69ecc04fb87de689a90317d2515de7f1fbfc1c74e06226e9e8b25c2fe6a0e7684c828a76c71f72e063f9ff6f87e812dc1207269afff487e97f390c1cd8899c7350193b55a8fa06be2be4c949ddff317743975634a44b79d106c62a3d8c6e1ef2476eb281cf9cd329d92b9947d749c18b35709239a4c8101d7454510d8c9fc741374324279306a643cc5168eb5836218706364524dfed4e5e5bec0c6cbd47e9257f3ad45466787b344a3afdcbc05478e9c7893f19d833dff7ffe02e95ff18d8da7c96253197fde6c5c06ab924d12c1e95c67c1878496f1f99788e109a070313a425dbd4ebc7f7fe17d8b89597a522e609ee7b8135afbe2c4997d2f6bac0f68de95be738b0893e17b8e834fb13bbd861f2b7c08494ca95eea05347be16d8fa185a52eb6926f3b3c0c68f1d529490be6cf258e083680892c3e3670dfe15988eedf7a952f2f8b9b702dfb9b375f4079d72ecabc04fd5ef75d2680d1598ac397e578fda687e0a7c8ec3bb4bc9d2b6230536b7e3b4313a0c92f12830296f57eba43ce6a1c04f695bd6603f81cd6929568ae92152da09bc0712f1b5cbbcf3d304ee23b29305dfbd60029f3ba4d08ed376f2cd1278575f2dcfec61160f2590227a1234a72481932ecd49f5a38faf8204de3f8e5ccbe3f8a3338fc0fab798f57694e61123f013e3b44bc498b76211f81c3ba77ddc3911d8aaea38a9d2be7bcc87c07ffb5bd97d87ff9a0ba1cd4d2185b405814d7f7d15fe417ca91c08ecc6dc137f435e477ec077acff9f3c4daed4f1016fd11fd3874a7d740ff828cd9842ac74913978c04fee1cdb755fd344db8b1d702b51a1377b6ecf9ee3850ef80c493e21788eb39d39e0a3c97e79730e59d1798103265d08ad39fe4eabd4b7602b2ca76f8dd882bd8a69ecca3787de6bc197ea564c56d12ff7430b4e72087e39e7ca711c7e66c1d67738bdaa9b99eec9620f73d0165347d160925870d2eb21d385d6e6342c18f11edbe479051b275272a4c6159c07eaf7529556301d87924633a6fd5e59c15e25cd241e49e870b20aeeb39e877d44157ce4b1e5d0c925e9bb54f039c709ea6d1d54b0fba926f6fbae06eb9c82ad08361d85d031051b3dcacd69fbfa942e056f1d44fc1c48f228c749c1f79fe611b19730db8c82cf62f5a29b273ffa8882dd4891b7b335e78809055f29b3a7d4a4a54f41c1e7b9072bcb27781d31bdb63cd1627a82d7deb831487848a9349de034b89fa54c299b7986137cac39a74f10f30e72fc26b894df9a63d6fd8698d104ff9d3b7aa252c8ab4c26f8e8b6c9dd9df743c960820f1229e7e069472f3297603dbacd41c6d01bdfb6049b991f954747f133720650093e8a293565bf0da93f94e046bd2b477b1adfce24b8cd9c73a7e062d31949b05ef1fe35337a183a22c1c7dc6972f616128ca5d9766d66cd5a3e82f398ee2fb7aff6871c0370049fa32bcf1bcf1e57e418402378ffa83922c183118ca5ddb8e1eee3d13b16c17fae6c1bf2052b0f1d8a403ad0e8b1b444b0ea61b4c821da88e711c165989fa5e861aac81f820dfbffea38c490901a8271db901073741ce9a6106c8ab55abdf1dfa20721d8ced7aeef0f82d794793ad444436a05c126af9f28396403c1e78ededddf8398523140b07df55963dffe810fd2bffb6187ed61b67ee0e3f1e8257f3c315585fac0db65afc41c295f86960fdc65a80f75ed81d3d0eb904ca21a7a3df01bd5a2879fa30c200f9c87fbf16e4ab748d931003cf0511d658e24868c60b903131d47b7d349aa13da818ff3ada7c8af0e9c6eee7584c7bef4153af0d1f3446a2a736027a9a7d710d11fbe1c3877db9435771d401cb85495f9137e97a4fd67c127ede0de9e2fd3fbb2e052fecaebec63c188664ba1e3b6901c870597dd534dfc583b73fe0aeefc238debb72b58b7c81e6825fb0fed561c76a4901ba3d6ace072bc1aed68ac26e6ac823dad0f4fabd7dd1d55b0d9ce7e5d554c4227156c1ecf9739ce810ade7b376e54da56ca710aae74f3e31842b5bcc3147cde8d9ac36061121da560a399454a3529f8689f22e6a5c8c1b3a3602c721c59a3b407a9220a2e67144d5d39237b6828f81a9d68f9e2133f40c14b168b7de2e9f2999f60f26be758e3598c38f10413c2364d07f16c219de03dacabb0bb6c352227d88f235dc9b149ebd80493a3a9c78fd64299a7093efc90e3cd891b7526b8ec8f91fa63f38a1826f8aabbb5144f3bca2fc1e859de9483d0cf5a825d09fff5f42125530926e51089a356123928c189b9696d1e4f11ac49f0d943984d95850e95047b17b4929b549138ee4d36cf091524f8285faf4becd8d3ff083e99bbe6f5204dcc38824b3a3988a63e7dd188c36eed19c17f6ead8c0a9a79f922b87bddcd51e6458e7245b0295bb46e5a88bb9d083eb7c38e93a5f8363988604b627775a60ac13b87e0a36d8b8c4942c7e818828ba7b9fb63d7bada14820f7775430e61e2972104f7a75b9a7305cf39c720d88a2e156c7541f0a1155378458ffdde40f095ac3ea524b87416107c84871b3ff4babfcb1f984e2321751053c8aaf881ed209a47a930fb38ed0367b95b975f2b8da57ce07bff522c7d0fece750cc7fb2e328a2074e62921ebd320facc70f54374af060ccf6a1a31defc0a6869059e93b9224da81c97e99bf39fe3a611db8685175ae1e1dd84f11dc5232b4a3d81cd85839c79c3bcc71ef7230674a1b9963445fc4816ff32007759a4324a22fe0c0dd664eda842edfc068740f1db54d28f1d40dad197ce10636c7a1413c0821a66a060a5e806d60255df4f2b30de9ca7a8193e1754df8820dfca6c90faa36566c0dbca7484e79dae348aa2512be5003f76521b286d839e749a681bbf3d0ed26f232e60f0d8ca4f815d4e3766dee90e16990711ce18b33703953fa68a72133301e5f72d01b3532c58a11e18b3230d6a973f2d32303d78174aaaaac9e32a66442f8620cec7da43f12372462fed8207c2106d6d72d4a08a629df266f688581d5a81d8d9d8780818bee27158369d490065f7c81cb1c07499fe338881f452f30de396aacb43c3d552940f8a20b6cccdf3ad5e2f1426ac5f0345070c60bb8c06aae9c38e934ffa42c19e9832fb6c0bf57cc2d9125c6ab530b9cd888a94e0cc942873cf8220b7cc82df172aabcedded0c002231a53a66c9723468c34fc045760cf72540cba9e2facc0fea584749798364b54056ecd628c62f51ef948a8c0bb07d971555f74f0c514d8de4d27b51312b25a6e68110ebe9002af591ee738a48c9873cc1751607fea56f2476546c9110039f8020a9cf4a7d390171d7b64ded0322d061a07151a0c2835f8e2095c0c9a2739cee9fb298dc1174ee02aa489392ca9fce97513b8f8ead97c32654c316202f7a36f75ff9ed17cb204eea26a7e0e43a64cb72b81cd5563525593be2e4b027f112bc7592b3f2bbf48e0f3841852d2be0e327d1c61eb945d512b588ec3be3002abb9e9dd3b6bf030ed22941f0729c5cb96150a038c0638a014e10b22709d1f9d36698e31e5080cbe18021f6db0a81c778538098193d4214974bad41d1d37b4c6707dc11741e043ebee6fd378baeef90208a88566e5dafc3a80c6173f604243a24ddea4f28ee3036e2fa42bc4ce414bbd3d6032b448b6b8f6f1cb03ce4ca2f877ac29c5cbb2fc62078c4aaecf7d99a244a4e880ef09c9ff1c5535448c238d15787f9103262454f95d8aea68cd7e81036eca22dde307dd82b1ca945b257f1ad50f5b70fb8177874739883946366ac16bde14ee9a73aece112df8cf4b6dc1326616dc24bf74629bc9cef2b2e0c6c3e81fedaf587097fd7f1a35d343da61c18468513b994d47957c0597b99dcbf337acd5c3156cc52469d5e314aed96e05af962644f59218de2e2b3809a1eb43320f3f95abe0ded3444b9bab2a8cfc9da624fa24157c6dae4d8aa382dd942996c50f3ffab653b0413d0e2c73d2146c47a1635448068fa42c05775253af19375270faa3f741eed38e4a8941ccc62858b5d8d26943ac0a1add50aa34ce28030df4838215a041ce86282e143640e1fd6a6508b1c374bffd1c667020064e012db4848d4f1499624a7a64cc23166c788237c9b1509f73dc194b09945ad8e804e336a6713d083172b032163638c1c711c3929b5df5c7db12153636c1a6e8f1658fed6daecb2a6c68824f4b316de6989309f66da347d712f5fd281b98e06ad387395967c816b904db8126378bcad209c90d2db3041b54257d4e19e3c0a06a8c13022705d8a80457b5a5214fd69c2bc4d40d1b94603d5c1597902a04290669a0e08c324e6075d73626c176d2ed943bf4d25a02830d49706a91d271505b182100238c0f8c91061a2d484133c0fd01ed940a1b9160238789a54bf9bd031f125c9df78b9f9b857acbc62378d3c9ea6b6eff361d8ee0ff32e7b462221569d208deeeae3cf454ad69af345a800135d86004537b552379beded22d826b4d52b9163c042bc986229872fb894c6f09030cc200186180411700230c30c8026084010655008c30c0a03ad848041b2b33a5d7f7b5d1e00d2d31c448030563a06005c452043610c1a450f696dc2ff2b9c52d60e3105c083963242feb90839002360cc1f69a9aa70dc1330e5fc13908d828049e72f97896576074000260d820047ff7e2bd6be320f88d9665371e85694310bcfe562491fe8f79d733303a0001323010810e54e00007b01108ce530a913abba3f248da0004af95694a3fb6e96d2a0d36fec007754f155ad3a2e7243933d8f003531ff375598e824474ae0c36fac096a63edd8af6ba7819176cf0814f29c5acd48ed8d8437dc0861eb8c87e8dc8152dbb650f7080b5818d3c70f59e9aedca5593e4b8a17e32500130d27034d000230c308800a260030f7ccafb9185fc39d03036ee6036ecc0e8a6c98eaba3a0816ca30e4c8ebd32245ff0d872868b61c63186ae0d3a309d3e3baeac7eb93d626655555555b54593a086c2c61c18c91e3b9a54cb0436e4c0fae698e3f129db90b4e36670a0031cb01107fe83b817242f342265ca30030d3f432360030e6cae7e927cfb9e3fd60336dec0040d92596fd53d4ae486968fd102344c0bd0c00cd87003d37ffef9dc76d357e586965dc0461b588f9243491131ca48830d6c88d92f4247885e726f68f91954c42a60630d7c48d93359c6a89e43ac0a36d4c0b6e8eb64db8d9f9da6819b20529b9a975d5444031f4ad4cd0e3793828d33f0215356be6af4a494ccc0479a23ff58e3b8e6a86560532bddd44ecb63cea2c10619d8903a68738f398ada1f021b63e0e3eed8d1b76bf5c41c18b02106f6c7b33aea685e211a4d828d30f091f757c6e441881e472948b001062e57a8de3c79fadd72021b5fe0edb3b9d877748e35e686161a08031b5eb0d1057e52da7e863c2993f5665cd83f57c8a13474ac17b1b105bedcffaa230ff1315ae02d38e39c430c328e4396860d2db04973d7587b1e8dc90213376dcea4b5b1c515d8c002ef994d7acc62ccf83937b4a85260e30a6c5deaa83264de0a4c3cd1a98f6daf021763a7f438a8ca8ef6a602573f5dae1e8660f51438b39ce33073d2b49649818f2afb73da4b0fd3b451e0ff72fe94af42b6be140a8c5f77e5476a139d3f81ddbacab92faf4be6a41398aa341d39d11f558e6b02173f7660316e5bd27411b400a0c10613d874995c24c4fecf97b9a1456784c1c612981492dea3fd72b9c88103902fd85002bfeaa1a74bd75a4e95810619658471802bd8480212a2684976550e6d8966a1912462ba9c9bb2870c77819721814b3f76215f7710cb7294156c1c81cda9fe28a962887ad008acc7984c2a73ca314d8caedb828d22b0ade297dd62ceee1f24460c8c187646196970a00130f0031c00a1608308ac471f8796cf76b3763b828d21f0d1f18520e1b922c68d10b8f08ffa2da40e3b350b021f85d7a74b7d8e42590904be572c5e87d9ff01db2186b0fc93e303267af6a03bd4cfd841a6075cf4a87354cce67631cb03ee5a74736e7e869cd901df6a493a79e8a103d63dbae7d1490e98bed21ce2784f8e3cb581834bcb27b7e0d324e359c7214f2acf164c1eddd42136e661662ea8510b26879ea34829c67c15f3b460e3858ce5e168f4e8b360d434e7aabe4adfebb260249856fab1eca8e3b1e05e2be859b689ee52c3a26cad99f30a3e58f034be31e30a3e0e6374879bb7829dba1c079992e4482f072bf8e9969042c8d2fbf15d051f72f42124a61c84e451051f744897d263b5e5602af8abd499e320f587afa102cf69f4720acec30a257dbf29d88ce7914fd3928ea5a5e02264f474b49d247a48c1e7649ec3fe899c242547c1aae60e8dec9553ee100597a1729893aa7fc72509051f8be789ef152dfa2228b8f4a1e6703d2acb95f209ee2764f58a9debb73a4fb025d6a9c72dfe76ba134c86b44e4ee6b1a39613dca4aafcb96d827e6513ec464ee2eb1e7fb45b69820d5e21f10c6e3c7fd3e9c079675bb61c4a04730e5c4f94a7b68f8c54ca81b3f27e8b7d126dca3830113447ccf1c6c9291cf838f57fa56b3b929037f03169b20e2362dcc0d427fd7ef3d839144d1bb88e5821f9c7b1810916dc72588c1f676f0dfc640b0da69ef6e398510393f46e5248b2de8b9934f0a2993a859a84a866d0c046acaf09651b2c5ee60c7cee988355a84a39b28c19b8911059ebd21fe4205306fecba7a347d2d12664c8c0da9aa4cd68f9a3109931b03989a964c8fd53793130926eb52d2a1d831f062607f5df1b27a7031f0c7cc72c29a6dcbc16fb2fb09f744ffcf5da3cdc0b4c4c317590425a071dde054625ffa5902da9aa7381f5ad7829ada6af9c6f817b8fa25b878e8ea5b5c0994d457db5b3c065584abaa888fec1021f7e1c5bae9ce287e70aeca75cc1f4572a59c70a4c5f5bb9bf679e74aac05d870e5bcf9fd91a2ab09b6b4d2f74903dd04c818fa3787ac818af230f29b0393f4c49fa2870e6d1d3e70df71d0a4c8ed239a4dc1318bbcbe9ec5d2770a9ef99cbdd26f092ef42faa0b2586599c06812899ab346742c4b6072caf9dd91c53aad4a60739c731c8610d3c77726815f0939979e8904d624ad654e1da4d11c81f5b5ec914fc8ed221a814ddd1aebc82779a6085c480acd106ac78344e0e3e589f2c8defdba21f06107fbebd5ccb685c0697bbabbce41876f10b8b496d929a51d08fc44da1ce47fc0a5cba1a43a63c68d0fd8d835b935d7e610aa1e707f1f965afd4c2b3ce03ac7a09f244332ba036e43324fcdef66191d3a603fc7ab72abfedc7c0ef870fa3b879fd91334000ef8a096da81e4dc828dda29626d8e2d784b79753f3d67b6a7165cd4534b5b0f2db8e859c92f2d2746cf2c98dcc92f36ead7d146165c685c89f93ede1835b1609284ab850eac336960c104bb5c2bfe9fef3b5ec19566da8b17fdde5d57f096c30f99bd6d059326735a6fdd6cd6b2824fe96842eabd102aabe0a4ce63b4ebddf41555f069fdd73d867d6b25156c4a8cbea841052f21c47533f59892a7603f89ba0530059ba3ce1c49f273d0e916a0144cb0f1d55293147c48291aa33544dc1c051f7e740e2397a2e07330d75431bfa996a160bd42e5ac7163881f020a365fed628e73ca98423ec18754ed40caa622053dc1e4e9f25cd5a1adb14e302143a4d81e58468ee2041fa9d221492b6b56bb09367ea7942caf09b624054fa29a663999602378a478a5b2b26831c1fd75901a91fd93d735e5fe7ece2dc1b6eac7f1e82ea85d0936ea474cd9c42a7d28c15a4cd12a4799769d04bbfbaf92390e555224c1fb48a7c89a03532b124c77e0b1e946fd073d24f89ccf254da4ac43fc114c0e5e3fa96e47147e9ff59f6e04ebafed1ff7a5e0513382ef9234d3e0a21b3f8be0a24d06eb287e18f95104779ace5662779af44904977463e46ca9f2a707117cf4e81ce377649f9e43f02977ae55cca62137866063a83cdd7a71fd630ac1474d12e9a1e804ff2004b7db3eb699bef6d941a497f54bb45d4130fee1e5cf31bf4eb281e0725d8e4f2d80e092b7a7e9556acfa17f6072ea964c93e36154fdc0a9c509b92444fa3bfbc09d44bbcf92e403ab1d2be5657f5b96ec81ed38d47b8f454f17a20736e49017625ad18fcf031f5954aef5403c70665f3d2e694326f10e8c7a8e1652e6d795a01dd898c137646a0a92611df88c9a2a469c0e5cdc089e2bad293f73605269ded08ad613ca81cb1399bb83b488dec5818fe361686bacc081cd4bc9e81d48dec0879872b694eadc2454dcc0c66db74a8f107ca2d206aeea53bcea38f5c69e0d4c90984143f66b603f8891564376d89a56035b7f9153aa106208761ab88a1aeba3f6a0818f23478a558e0eca730656936a4aed7e0edb9881eb583ddbc3fc1c749832f0bf39fd7f9023f33564e0cc43675ea50f2fc58c818f3c36bfcaffbd9311039fb6428ddd6160f7fff2a57cf528e600031373ac9ea3bb51ff0b7c1c2b4791a5c2a3b41738dda81de71cdec7b1d205a66e3a7e959ce2a5b9c0c564175a59d5026c816b11b5d2983233aa5a002d70d2d175d9e7d0e3a65a802c9015621baa2916f8e8a90f7dc92b701d86e78e2e23a7495a81cf8fa5234fa8cc29abc05506f374e9265d4aa8c05f1e1dcfd3f941844c818bfa7137e7963419910253218d89c4310a8cd99585d8ed7d1c4281cd10f2e6eabc1a2fa74f603dab9287f8eb71c8d4095c9a48f539fa1cbe296d02a7b13dc4ce4999c085ec71f0eb5043c8ff12d890f7eb62b2d54bbe1278fbd5e429667b243f097cf2cc9dae8304ae82ea6bca1e2577f81198f2d415d72e755437023b91b7abc4bd27e945e09207e92e1d07cf951381af9c76e2ff4afae821f051879d3cecffa0eb23043ec6ecde0e3ccadc9e20b0eefd1d6679b8841c20b0539df595ee2baaf3032e863acb39d2f880cd142a737669ebdd03be528893154a2d373ce0db3a4c0de5d9ea3c76c0696b8a1ba60e380f3d5d95e3901f3b72c077343dd178d143bf05c0012f92a6b1d752eee7166cf9791c74c839e2155bb09ad6711cd9eb99d782ff90e38f8eada39f4a0bce3794e720c9d298b36075a33d4d3e65c1fd57aa1cfa180b3edf7e7869b7e34a212cb83ee9728f34a77c5ebe82c92bd1a1274d8fb395aee034f7fb5a3b568695ad60442fa4b86afa9c53b282a90e9a62f6e8c342ca5578369af3c77f55709643d4d7fbf4def954702a521f599583798e0ac62c3c58720ddd6d9f82dfceaf9362b183d8a66045cd2e0593353f7d341d2dfd0729b8ee8fa26f39cde31ca3602b25bfb86867ccb1a260cd53f4387b0eac2b1b0abe8245abf8b9e6410b0a5e3d74f59095fed9f2093e128d7a8291acbcf2cf0959d34eb0173d3ef3f24ca72627b8d0593acafa328b7213bce4267bad8b4a2ed104db21ba439d9709c6a2e49a85deb4be6182e9703ce4ffb3cd0f914b305aa9b3474e0efd11b104f7f9a562871cf546442ac17e92d6afdc5382ed74b534f5de41f89360cdfc2ad2dd5bfc25c1e7384a59351f09a657b3e5a7d6ce3824f89c73d0e871528fa2fe0826895430dddb116caaf5a61cd58de07d2b6b79f4d47dcd08c6d34f34cf1e1df667114c78a4d590a7fcd3a3082eb26644244f95779308f67a2d66adebdb9003118ca590a3287d0fc16b0e2cf8bb48dac91a82cf39ea4df0a98efb4230695207a51d8fe70f423056631b727497380e82d1d6dfe8b6138f2b82e02f9a84945609041fd46ae26f8e73e4272018e98abd2925ffc086fec075b2a54b9a7e6063bcc910ffeec0257d604aabfa329a9d54081ff874e1769da2752ac91ef84c9a1dd7dbc5f4413d3025b1dc72581d5b8679e0df73b4a61d858e08f1c047bedff4fb67c87177e04bd2c7390a127ddbb30317529d3a7db855395607fed2d7e5df181d98fc71a7cb50766619ce81cf215e9a9727394ca11cf8f7101ad9313a560be3c0c47e6ddd4bc90e420807b6a2698a98f737f0d1ab897e74c8baba1bd84bdf61f4fcd82fdf6d6052c8616f5e9e3c3e1bd6e98e2be67b0d7ca0d321e6ec21a4f4a88155df95964a1af8103f54b1d41c01d0c064d3f1cedf39a432478033f06fbd6f1b398aee8f238019f8e09b72e0517b0ed73d029481d192a8eab82ef9648f0064e0b2d7b967469f66e5083006dec43bf443120f5f8f0062e0a34b13444b428d9f478030701924875dd1417a2f8f00606053b0d0eedacce4418e005f28f5c678bd22392f703df59993630bfd81d7052e475d79f5629f24785c607bc4bcc5d368c8da6d81ed582d73ece0ff71765a68727cb3f1cbb2c08aa5eaa8affb96322cf0151d768cba1a394f770526a5dd5ec99b82aa7456e0242f8ec75b310739ba2a30a9be7f1e8f85fa9f0abcfb864731255d4efe14b8b40e53c9ed4c5f0afc7ac7b69ca9ea633d0abc78d41c68e7dd5d1c0a9cdd45c9f9f30751f427f09242c7358f32627b9cc0e5ab74e9a3c57495d3043eb424aee1d9fba5c304fe337acaec304be0eb4cbf2444e5138d12f830a7e49a755a739e845bdabb225f2470d5775293f6e3931c4760cafc7c3f4e66e70e23f05e6116523d8ac0e9c5942a57a4bc6221027f56167ed1d287ad43e063d248533915029b5b5b322d72a49b0c027f69f33b071d5d3104089c5ac859f92ac71e223fe032b4d244a50b55a30fb8f5a8ad46b3c71d8b3d6052ce9be26d8a47d9f1808f2da282e4b7037e74255fcce0f71a1d30f6976b1d5ed58196036eb27b04c001d3d18a058f216fac6fc17927df3e0fa1ac83cc16ec75903c96ac1d6a85ac169cefe5385e372b4d92d1820f297ea7038a70a894cb25e2a141a14028100783a1502034cbdb00131508001034280fc671248a2361db03148002492814382a20121e140c12120e0e1e0a0a060606060a0c0082c16030180c080302c2d035512d193fa81278749353975ee4b22aad73d3be24a408fa7f9ebccaca6941275a1294584a26a29ccf38e49defe2d6bd1df0e4683b40620e14adb6e3451267bde8594e0f16a92ca3fd5480875c95fa02c8b74be53a5673f1d2cc6c8298a63defadaf39ed693bf80a753e6b2f09cd8f757a6c0e8f22958affddcdd08efc99b25c6a9ae60651093dc5b57f78cad3596d258b007c0bf7e4b72ba3a7468c72119fe0cd773ae0912f8067c6475d44df19ed9880a3f322898a68cb052d43a5149fcf167bac688f01936da3ebf06ca9ed90096d1e71e763bf71b077bd8d4d461f33ffeff3c61066e8e49daf5c84c33a72863c75a034a9fbb1532e6164d413e779ef66551b3afc9937bae28d8f71e0159f712ad33759a7bdabdc87ff6fe6987596b39dc4ed2347846bf3815ec23a173f60aed585b8f433f76ef74193b6779378b034ff6e8ae83e696d1eea03e2725268406495c5b02a8c922eefc4abced1bbf7571c79e09d2e78e2134ec55babdbda2941ceedcd59ef9cdaa0a372ddb17df066cead41c41cb2c0b1d6a9019074bcd455efe6e6cdf6cc063a48e17a24c3d381bea23eac14fa4e55cf11cfbe2f198f0c8090499cf5a6f7622f270f7ffa42e97d70ab1a5cd9dcb193ad1a567752ba08d27a8d9ad2d0c659d9804ae51b81487f183de72760459d698d349014dde82a99a53ae03edcfd4f3aeefdeae88d4e4b41274315fab36776efbbde701871667ffc6b947016473dfecaa739df7266b05b55531e9a319445a44ed8d06c6ae109d824884d2abd4924ae945ba72091b31eb1a3b5ae6e912d3cc59dc774e0c5e65a39691a2bd3195685233ae7932c82c98031b33bac41ec1e079ea4436f7bd4c75feed8933dcbc17bed33237e8ece664612950ca448f4a109cfbd03073ebb7dc008e0e594f849e74c31cadac9c8500937c4e7fc4635c4b18d30eee3cfcc2c672ba340f4529cbc2441084545e5b73a81e2ecee762592a4253f1583f97ae39bdf74a8a7eb619b3736071fb069275643a94e2a739e5039ec9bc162a4839dec8f3e6cf5d118f77ec0b1ee7bd91bf361ad306e3edb9cd2610a046a052d7da0932223916c6494e4397e586da6c0af3df1d5d85a3da287b8f5ac6735345de2f453a6f7215bc33fa1a3ce787fcf903a5d56a3907747bdbb41f9bba17d9269681683d9ab366f235a675ccdd786432a5f6e5f764ed87fb3ff9e496c14522a3530153926c5565ccaeeed47b58e5ef5944fbfd75d6f7a934349a33f44ceb1dedbb9077feb4d376217715fb03d37f6662a7b30d265d353a6de076d38ec48a0fe839cfd307458a5501f5d9e61c8a6c2c280eb1fb3dde327f27088269565ecb5d1a4b366dcab1c5a1141dad9e1fb5631a1c9f3094843fda3f7c09afa698ca5008336797630f529e640e9f04cf35dbcc78d24a52d905e7b508dc9a4e1cdd4c4ef65e1db7b0bbce5f1460965398f8bc722210d26d5754d581281b9f473a5419ec8b666622fd14f7a20734051ef754640e48ec464abd5b5ec0ea4bde51bf27f6c6ae0d42763cd0621e0106985188b9d70c8aa273c6304379434f98f4d5e2133c41257211e52ff31cf92b45c2aa4c058c970b8358048b3e695b2596b082f6ac2007c80f7c6317179cb0ddf801745af7e5a8fe80ca201e6b1abb8fc5025e5865fa88aa93d89e56c8ac5a6a32315f6c84db0feea137da1c8e86aed088b9663fb5928b00719c64e0f93701e38cf0471c542964af85b9e4d8c9cfaea10099e961a1fd4ef5b410cff70abd6167defbc61b9b1d1b9f766218b1bfaefa30a0a33c2557c09fd9f317d6bae3e951952907bbbc6a941007a58058bf34269d08bc53d0c1da4b83486eb4da805e6fc25ceaf1ae2e190f6bcddf16fc4e2b14739f1d22f6f4088870e697ce44eb4932e8ab82c07441bf8fb438a62f0966ac3a0958251a160b63bc13cc8ccc91f5d67c47ba7fa5579c7f5750ec28cdc2a1377e005e1b046a74ce2304e2dafe251cff45aa2cb415e5a455b74ecdacfd74a6f5d209fe479626e3d1f228524f88cc4d08d949abda30d1fb30fa5048549770a941f713c204972223b466f273804f75a82c2b6051ac3070bdf98d71ea247cb42259635adf88ff60c1829e84ba7c3986fddd958b7d1932ed1e435b9831e34e63004f10477a8fefe7d5fe01d16c97ce113ff30f10ffbf17f42a2c19f988ed7a201c52af8225175d2d1ee2bf8ef2d734928e09de19794ec7777509f8e0f5067caff52cb7578c27aedba2dc3c8ca81a15b7fd1ce9ef37bf8fcdb1b94027a95f005c144a583ace5d1f14386e6d340ecf5a7620ac522e1f92db72950975ec867e8b48396404c749dbaeb240d94537dcf82406c4ea07b31fc17c10bab8e3a6568cf530418b76e80eb4a92ce017b340540b0283df6df9f3f7c501460fdc07d15227f302c35b28439d35cd98178c1527dfa7b50f2436fde1320b1075f225174eabd6e6bdfaa32c8589b4cb1dd795cf7d8c7674dbf2a1f41b835129ec14cbfd083cbf4b009b4d219826c88587f72c67b2b2b4260b231696c363e88e2b1a8d4b63cabe8317509d2e40900ee0fca3600d9e2e1e027062aef5e4e9d34a7d10268d1678c087e7ecb740d8548dc5443ba58d0418aedd89f6c357046618288b0a4fcdd8ee958912fb6a5a97998e6beea93e8b8664e219af0c64c154860a4f598562aef26ded2283023e70c4c9bc1809e6e89807412de4208c4040218d8a7cd6c26a28e17cd0663a4d206d61fcf98a96b4517a6b3073228ec8dd42667b4f2bb68b3476862eaaafcf0b24111ea28321b97d1e231d12d61a8eb51235e2df119b16628b0826b9e6a2bb26d3552a3a4fd38184d4cabfbee202701c8050dc210ee1162f5e672ced96a83b2d2a8daab0c0a257ee2aa12745135c7ead674b0724f553c7a08087cbb54133535370f9d72c31311aac3bc28b8e98cb0c66b0e8176bd9f3ea5e0c92c8d5c64f3dc407a0c16460029ac06ed9005344f61430d64d09e6588c4a01dc92c091fcaad808aa450a31a02198cef6c59b3ce69c32245d0428250027e928e43998a6dc4733bf4e0ab4f856918c998ce4d7b55d091c92c8abc74cdaebab4628ae72582577cb7917e99035cbd9743f903a2546e2b5bb1f6a4173c2409f481b63deb02600e0a92164c88f0131c019e3e4a1e2d66539e3166d7efce9350f987016dbf24e4bee83067959f0ce1ad78bd1a26b55b0de7b5e1728e25d71561df1cb0a99f87c8e1481c454c39cb12e8b99c2f5b0742dd77d5711d91027dd84a52a04ccd96a3f57f3c1b4b1071e5e27b4d160e2f29507ab3de6324d4aa1d7d347728009800ac357fcf636b4ec2a4fe6c05f3f4035271aea1886ae03cff45042d479875c857415835c8538d30ea909f82b06990a902427859861d901c7949467e92bfc8c73d3604f3f381406c2b966805db79a8a64d720bf439b86a901896ae760e90cd6451bcd94927e66a62eab37103718a5cbe741cb80168c9c87da0aa6bda28ab2c9f8f5c42d711ad05bce7a43820a1146285ed08f872532c904a5339ffa007aff027c9c652b333e98ff26407ea5eeedd7d524ca2d1b556a1298884d1ad93ce0fa72b8a069b78895da744e8b6d204a7308cdb41bab94e48a8c00cc6226c8a0812c6597130264f70e29ce21bd83725e8cae808d60cb7d8557e22a23c1745175ec7fbc21dc916815af3b38575c191c22db2ec75072551acc155c2b1af3ba8a11d92e516f45f942e23f943e024d378f19763a3f52efffb3d8cbf9b3d7c91d4fa19005329c8a7b01d2adb4ee12eeac4d0ae9ccf6ffb9595aeaeae7cac9f0c30d9cfa81fad316815fcd7dc1ad117871eff0f15e0d3439d6fe0860f107e0913895b78615c3f48b3453767610e4231084085b4010048a57f64e2f0c5e2916eef64590d9f0a6a0ad4e2b3e53d2b6fb4691e71523c1d42958032c2ebeaf51726337f3aa64fa11d938a7a2f3fae35c92a3e7a980f6ef8d0db90f40ac44d3eece5877a07e2868fefddc70de99d941c3f1de16de063921026793b57a377d840d2f3cd7ad1ebecfc9a08a8d7fbc7308266f2996163cb0a0b6cd001cbe8dd932237ff4eaba20ddddb0cb89d4214c507bac38ef9d28ba4828a6eaddebfcb12a0d0c32c217773cd223973db32e0af98422902ee0816a4ae90a7f352a9d58c02722672ffe1b7d3cd1b3c07afa0809ff13e8b03aa6774dbd2936caa23bf91d84791fa23bd9230e05af1ce81c87f074ae3f311596aa675bfd6b5e3e87b6494bc54e23960583ebc5f509a6a8287f79351386f746e7e64774429ea51b8f1834f8d0287705daa6ed00244b5808f2e939140235ae6825d07da958d9954365fba4e22b093c78e4afd6134183a3daf723214899ae5b151225cac6778b34cc1b075e2873ed48e08e4e08c3c0dc223c721e24484658fc2ccd3ef895d403e785a93b5417345ceadb1fd820549418249bec385175958944a485dafd54ff929556cf0a5950ad555bd9d37527a085f87db7b83b1aa871222f3e090f0b79033439ed25cb7178a8b90ff8c562a5009c7306dd5a541f824640e210bab854713391be299fcbe6ac42e170254717137640827c377626e008950168a19d409b506fdc2b41894789e904b347785bcad85b98b5c6f23decf455b724be5c37d7b1c82ea69e004f34313f4296d946585dba5e03275acbd2e113f39463117a305acf2c42cfab064c149e4094058724ebd22ebcb8e3727beae01f8455104dcd09db3fe53a52fb38c1ecad50806e82d3168123248726dcac0b6147f516def92b99285aa85f6835e75cc17b9100a10190bda413db831f620baac16b18bf97b353f4ad059733a05a225965a31b14b843c560ba5ac913eca0281b0008b5d0ac52af111353a8aa6e1131ddb9b829d96fca3a299131606051819d1bd3a3711b384e644656b60d9daa464563a57c875701d848d422d7211b81ede35015acf44029ee463ce1f77ada0847645de8e2ba693f53e20139ae8093b0a248b42e41727d9cfae8b81555172cd8f25ffb588cbc9491e2b29580328c3ceeab0f77c833806fe6138cde364b9ccff6086e871606599d3ab511a9537c1d8e1f24e5cd622caa90006071852132e81a7401144132733eacc5b8686a8ab1966cd758be1fe0f1d2a9c6e8b12cc321cfe190e7dc74f1abf705c80f77dea800dcd9a33d9c99bc7af365917f9d58d88384abd55177ad5eebddc49e8e4571e1b1124e36bc997d46f3fe1665325adf21902f1ea4070e9d7fae3e0c624f435ae7231fa52a445e620a2a83f2e2bcd8a9019dd3f830068af6fff10dc42b9a2c4e70ab3ee9d7109364f24d3c523f807b5de7dda29a2b21db397f7a3e7b8eddc8d117b034e9721f3ba4d2d2062c0c5e874b66705dc4c38277fde07194c2f35e991e37fc5615d79e48470edb2c51477f777a96725b889c2b322b45ca503394e6fee37c982a9633714bad992c257e2946e285d49f5eef249d8b42b53d74657ffee8b86f7dc3ab14a413732bb9bcd5895878e90c7ce7729f24e1fddba911f67ba7ed75daeb222a45857806ee4bafa637739ef60e9e5aafba7eb4297fcd45d6eb1a25f72acbb43d798dde3f666c31be0776cfec2edcd2bbd751dd5cbcd4edeac83489011f2b57c668efceda38ee4c61764b513b0ef308b42a91f9c5a1833043d13aa4195a27a855f2132692afb02d248113fbede4cd56f97c4bba5bd6801264d251b9b0dec4dd2b406a2327f349260d6a554410320412a864f2b9cfb80589e359952dc092aa8fe2594f5740850a1212a3f2c7a39d64237a08d0c9851110d9d5826e6621b1424dc26980957419b920558e331a9098d2f0ff866b2f27886398a391be32094251bc53648a9967d2bf18a47a3fae2b42be8b647e74ab7ee61c842445027339cd5fcc74aa792372849025243dbf74d272ab88f2a615c0068050c4692d5d2cdc9d2287456c8fdd0d99988f315c37813262e1d8812ead9d87844a28c282f8429caea0a004aad4d9cd2b21d4a3bc4802b53647cc79e8ede60e954f9b26db28c71f58bcbdc849958ba63d97d4330a391f6de1dba5f260f4a1a13a9124d5376507f2e265eb15aa772eb095095397626901bdacc330ec46a8cf2c2c54e0f3e2b618c63ae9641dfbbf10227a15bc0c990cd387e37d1b431eba35d2297c06fae167bcb751717b96af1c451b10b3bd3e5fe9385f30b950105eb4b7605c4ec3ef6274a6830982020ad14d34adf1de8031e33ec9a18b3ce3772892e5ef840a30dc9ab434ace751954700e1f270cdda9a26a15f100004a8196b44851b414f0e0cc7de5df82abd5d192e929fbd4a1ca89420167b5f06b000aabc58301bcb2c519185155c03b00f45c453e595d3987ffdbcf8f5a604f0cd107e810cbd9983160050462daccf0466c18e6a1ba41051eab84afcc0f5b0f95705f3b58ff7f79f5fd179a96bc9a0d31ac002f1c40d6a2f5371f9165dd80ec531797f1204b38011b702458e6bc89e331e9d31c1070512c98b2000c81f906feaf2083611868c20177aa0a665d792f90ff2b0868be579865959692f7034a1097d10f74ead14b105a79012f17c8d4838037caa4fe75ea0ff5c1a4dacec79ec56da206e229fa569a7157779428715b6acd2c6b8f5d7e35c47a835f9e5c6b9f5e3ac3150750c14068e13f52e8a96f8e12796f6c5e5df9b55ffd474156b1ba1606568d126c6f80843fef9033479f3535b0b1310dbad81a25de438cc0ce1ca5f76a1410ad4a848063a228c53dd7221c4a6aab428ae043d80fbe9f4960d9af00ab7999d4baaf752d06f7547608fd501ac31e434369c8219acc2759c200303120033296aa7a19d0eb8069e7cda58ed6ea7a3444e4308f064fae1f32b24bae1cf8248b4322ae9cddfc73ce6768082169225083bffec762375074b8c5f0700c1ea032f87cd7f5e1b8329c6f16a56abacc87fb6047ef68dc16f7cf7d948145d1aed8b3f7517c8eaf73685d1b3dfa0b014903e88e5440c68aab6f50e4bb85d56bc8cec1085214d720af0012c081d3e0f425e02d44c263480877e157e0df6baccf2373a95c1bbfff1fb4da088a582e3f4441eba00eb8c876cd6264217430fa23a67e550f0cdf2d5c8f011590ba5a0fd40d84837e27be6ddc6be1aa2147ccf6b37079fd6e01e8027f9bf211fc08aa304e855582fa5a000199c0eaa2c3538e6eb97d55eb643464f6a6d581521e1cde463c7b4809db0b5c019346d784c6b514847ee81cf80c69781e683e559167e990cf1148a7824c6328bac5301b7c06f2c04461f537a98e1fc10de0e2dbfcd129e5af80f2f70eb96727c0c305533f287f179cdbec7a07d790a61d49a2845b802b7652a3dc3b2eaa0090d6ffe5465bce4f684861def4ad9475ca5d2c18dcd3c9be8a69369f8dc8132ed20eb3826cc7514069f901d68055dc6d45fe80f5fbaab7a47596f5d50b70fd60ebdb5823b6301b314ab5b5c5dbe0e6c4311b85ecd9b4c5769543f51f9a0c9f5b81c608d50d190de38eb6455e961ad8c1fafa4ae22d1c3437eb159ed5c030abd7c679ea7aecb530a25b20a40ca67fc040035cfc94844c276868d2bcb864d2ce8b353631497e0d2ed3e3bf3037714a9db58b3c8da03d72b2cd5cb5e00f163d61500585058d71510f48cfde40f430ba896318e93fb95e352cf035c0def0c78571315908012c8ecbdaa0893b57a80dbf788032466112e574f5d2ef531de919c0b7c94c81cb172e35c7961c7f2f60963e343d35144a562334350a0bae3fc2ea19667c9e60fa0641319a6824707984ddda3b403e842c4e14b0c836297461a5111812bcc45de4596789b26b9b5e3456a2c07ad5ca8966322df10a3e0335e1fa09c875c6d9004c665032a8fa1ce6110808bc94c0629921641ee22174113e34f984b4c7a4a73b4715ea86c71a2ed9dc0f2551f7fcfc3ddea4094e9ac70ed469200f82da92fc0fc7ff16061dd43143b9f01c78c36b1edcb942c83032b8a8935067cc6d11b180e14100fd7674c783c00292a00b5ef3af60b4236c3b7582153fea6e6c112696e712a2a12d781aacde78ce3923ac08b4be4a72a9cd65eb8859c7b77704c5a2adc56cf847a27f59725236ce96c6d1540ea6ff014557123c9f687937856a06442284aba801e0300a9a991b41313ecf9e1925336867396ba7f91a5609d776406e17468deb263a1868d0170f3697b179c719a62ab3fdd032d9727edec2412ce699445fce542ee2641e66be9f2269e4a59333d953ca6cee41738a7b921ac1efe70407aa5117c8d5b8bad6bfe909209dfecade2d46ca884878d17c9e8d771cb48edbeb7fa01daf27361e07b97ce8393a0666c6edf85fb483532941b272d157d4ec88ff5ca46ab565394c581914073a9d540800a2de43fc26b5c3ff9e321ed730b1a6ad3a19466753f13e41e0e2b18c018a76ac97c0dffbab4212c624b5c8c8d2584923ebaecfbea5d58e8bf27ea7db324c4121d20922875c7567f142552b5bf11919976e02f39538d8cfe29071a25a07db34564037cb162d810371598667f614606185a0bf7eebeeda18e0f8db0c1f03ca7e45b9244119d85132ac53bba8afd5118c0c1f6c1ae65d37fdc69b4a1c8ff84839114469604d75bd42b02e6dc76e111391746f445ca80316222a89c6fccf9eaaa770d5b3feb959c2dc054746fb619a17e614f5d340b0ddee00a799fa5b9e181945a3696a5ce3def209a3e13ee70a8c7203421125b78111c6d7d072cad145457e682d4aa8564d5e52b589870f677bf8dc5c2489d740dec518d165d57c8b6fdf12365d3c6f622d06708ace44b15063d4675406ea68a127b0d925dd043d4579468d11f574f8446c77d5fc69cd54c601a095f0508d4a06dce94d1d32103e622a4282531feac21a2ff0e92344265aea416eb1ae9e8aa858dd24892013bd10da2f576728fe589d2bba6604ec970e6f06df085a3f67cf05cfd1e1c2e1350867dfd8247041af6422f04cb3effa009bbbe509878f6b9150d9876ce0e95df45e2e0ba0eafa019280e8f5bfe9998dffdd0e58688e56a52f6a457169c343e1f5eb82674a5dbf37bc97ff6ba6203ce7733e1cf34ee29ae6e28b8ca6035048e0172a30ec4a8141878f407514ef7489e44d2fed4b451a9d096552d47123d22106bc15b6c553f60a48ba10a1dcefbede0ae1f768b643c40b140c7fe5fb5b814ec870b1261c9108f4d95d2df39c36500210bbf82e9e5b46591f2910f4ee1ca90919bf40c4f0fa0e401b0e1744b0b81e377d5c599a1feda3fc033e017d7d6270300c773329828c0d1e4c00856ca2d98fd63dc282b1a1b040a58a99e81e5a12ca39e814d74459b53d58ff3d1831acffadd3455046cb967cfb0a222fe59457611082418a040475bfede75fb883780be0c2c16860cc9a754f4d820e615a4b7b794ba2f17b5edddddb2bf7aff3d5e17c43ad1e318eef754ae739af26934640153ab276571cbdc4f851a43e8462d300109e3148493548814ab249af8dae683438445731874e863f2a618a5f863e0dd7c315ccba64e28c9d2f09cfbff1591c44b602612ab1f6cd4f365b1d935c92b5e67c58c380f72e79281533cf99ef4f01e07ff966a217b09a6dd5d00b6624c4d53bd8967771fadaa000a1c4e5cdf37b76b995c870b2633c6522fd64526299c32dd56f861dfa25278798fcff46823c4aac2e95294fbc497f60aa03e98d759dbed15cfc47f6d2f001cbf0b91136f669f9384df63b7b79529a82585c1d6b538908a7dc5b0d42c399ecb9950340520cec23115a139900cc296582cd49db23630c452a9f72d183fd7ea419818cd240005ae9433bb7d772a1e540448ad2969ee2e4282797bf7b01caa08fc487ef9e0b2cc4c2b4534d6cab066f12d5c193c16a83e82a578da4dcce90ff9bfb7b360d60724eab2b3d8a4723d96852f32dfa3b0d00da22106976783f0d4c9f084fe764669c0856a769c45d228857d8213c8755331697bd4ed5797fdd5e784272cc4f5c4ddb44f1187b7b7d33f4f69f9250e21870b02fb1ae57a1c0f7f42db5fec6bbaf5d7cb80f65888e10524153351dc0376fad40b40b0ef1e0070412892966798f4f480f23a1cb38947dcb3beaf0974ed4a88b3d82595dfabf272ad48168b49a8eaabd21efb0a4851a3bf4a7b4cddd647843900e9212bd5bf16c17f6103bc96ff769cf8a393b80863271846976515d2ab0b50557c5c7c80ffe3a578ca02308fd76c07f9416131e539054ac37e1d8cdd7a7e2389292d7174dca7b8680ff8a902aec3d240b6b079d022b0bc47c087a057d454b597014a9deef2cfcbf208cd5ae5ef8273cfa4d28b8e62b3975e46695255d98e57bf7c129c2bf4006b52c7a874179d079e08be38b1e3a2e742d2fd83d72d7682a3cf95230e4fa2f8da49084b051b25bad490800705596d1795e4e1372fe63273c9302fe4e04100b3e50f0a4d001f3ffffff393fc8ffef598d8d43c09e6a6d43db8d529b48522625ddf50b44d6329ab4539229a5c8d16114a028004b0869a4e861144062b7635cc50ad70aeb0a49b51f349d6ced8b8f31b8cfa37145d9491f256bf6d10676bb36734c4a25bac8eb830dbc6895dc1c6447ff6eece871a34c063ed6c0e53a494996a4a02e858a1a78ab4e4a7c479e81a4071f69e04458a9cb490c33d937193ed0c07686261942e8245d32c7f071063628f3ac6032679ffcccc055dad41b5d5236c98f32708210aabffe4d64e0d47d2ce51d2b55f81843f2890e3a45fdc4c028595fadadb269d2e930706ea2e895964ac183c558193ec0c007997216b5a42573ce7d7c81afb11cd5dfcf3756967881ab4d292a8eaad296b2840a1f5de06d534c721461e2786516f0c1054eafc62cc93229352ada02b73e5a45962498c0e0430b7c52ffd2f1638549a9cd021bbb6f4cbaec299e547bf1810556bfdcbcda2fe8b3ccc715d8f3ab77df0bafeda01d7c58816f137352ba25ff937415d8cd12d1f417bc63c97f508153629793095a2a5a563fa65068568a5e9e642a1f3ea4c07ece56d19342283fb90a9a4792c54714d8dc94d43b290fe9df79c10714be5cabf1b14c3f818d25c954ddea5a93a513f8fbeeecba1a26ad4fc90a3e9ac06988f4d30f7a1e2bb3c50713f8f1b6caa4395bb2a0e2f12106a3257bf0d2278486bcb1f85802764aa39865b450ad94b9623af85002174b965062a998bd5145f84802a3638ac14674c60b69d9a20439b640c30712d8b5124f86341db62777850d1b37ca091f476037acaaeebe930ad73fc2603784faa0e277c813937c18814f1a847a0ea24edaba086c49abe939854793d4270263696971fc21f0f6a974ce7194fe581218dc579a27dd2f12029f4fa74f39339568157dc106134fb7e8242699ff2070a9a43ed15c7e7b3f1038ddf13d7b3a3359d3fe03d6326e90d5e93726d77dc09bed9d1091a72d49395e70ea84f493eea5b1f2ee01dfed39c7d7328629691e30626a9268d01b3f74bb0336a6f71ca14da7034e9ec852f1b8b9b26939e05bdb329e1a4f1f4c38e064b5e699eabf94445d08f8e882d73ad31c9dc414475d1f37e04bc949e74b4d3f658208860f1bf0a683bad0769294b5322e7cd4800f0d4fcad33776fa7f850f1a309a9e57d369d30a1f33e0c4b4f5947f756d5b4905f7199a927a7df7f7b70a3350c1ad7657365f95cd4961c629b84df659b35950528a173ecc3085cd28c5d9380acc20059b2ad6a8e8ed07fdb9517039ab496bb24445c149628c3997e99473efe66784824f69b5447d8b0b2db618cf00056fa65495cce9afa4607f820bf578e286ac1394aa9431c3138c0a26d2ffb5d47f86667482dd53b515d457aab550183338c1bda90c7a94f4a7c464c2a1c5021e3d76137cbfbdb748bf24e4da38346b043334c18732b526a51474ac38c2a1b5f5809dc28c4cf023a3af6eff8d27314912871998602d471fa564f2ccf8d53c545a922ab7049f52b399bc1933f5ae64cca804bb319594dcbda307e13e610625782d212b78c97712654312656346241839c204afd5cb14610624b8b2ac51a2a4fd360b3d82d754f17a548e1b4f66cb11e911a582673acd18331ac1e87750b2675d8ce0942c19e25e665a9967c38c4570a2495b3183fd08256814c1c9d51ba377c66c0c3312c1afc9f72fb144cb199c8108ce3f83c64c5a9e6e93e0d0721d5cf0e87163470f1d39ce1604330ec1e9e42742e8ca10a164dc30c3109c68923ee9e2a66ca69e614621f8945dd298b228caf23983109c9c1d6412d44783602bd68952492a4f92585f0a6c862038fdbd96843e29c7d10ac7162d811a56d4c04016660482d74b49c7bca5d4c84c21b0811a99c50c40b03a9e9399fde8d40cdeb86186128fd4038b1cafa303366ca861c61faa0aaec94a4c37cd5e626830c30fbcf8a86d301df57e47096cc18254011ecce803e727ea685eb5ada0e7039f2d43c69c640f7c56f2e47a69b936490facb6e5d19c2ff585290faced9e4a9de2feae080f9cfc9bce848c56a3de81133b49864e794c0e96c1ed59d6965aa81bdd0ebc9ae03b2ab9977e5e07b64be83595a503d7164c9d9879124cce1c383593f1df3b72e0345d4cd693726ba7e3c0e8ae1b0e5c6c916d25a64e654d06e3d1563b67e5d3c9e40dbca8589574c5d7fc256ee03c2f7b8692ce725ac7e074ce2977a9311d6d6d03a7714bd9a7af782e1bd89254e6cf593d1e5c03234c3e952253039b3ffd4d97e6a64ba68111d2456908e1a3818dbd99f93ff86760ebbd2a7b9324491e7c33305a4f499d3b5f588b5f06fe4ea98dd6175349ecc9c08668b494aff2f4ae1f03e7d173f55ded3b2c2f06fe3af6756abe7dd28781bf50a139f64653923c18b8f19464134aff052e896529c8a42f77f65e60549f67935d3d69f55de02a34544cc963273b7181fd189405cda2248f495b60f3d3b9998ef5314c5a60e375da4fd15489a99405be4d36b144647c5f1116b8d041fd530e4a9227ba021fbbdeca3ea64a935b8113fd2e7e72cf79a75781bfdebb24577a988c5381bd93ef2edea7dfd95360b4d62c7fa7a5c057e6df675296c34da2c0fba86865174d5a2a8102ffc9cdf4a593e71d7d025b312c89b7ea044ee365077f13cab4d90446dec993dd4a8c962613f8fc6e3b9523ef34290697828a173dcc7c2db984e437e1e2316a94c027dfd30e3632be8b49e0c44b95c3620a6aa288043e9368825555470d3a0217cbe4d77c2f0cae3a4d2e259486ce9c11d8caa0536b90a522f055927b9ba04ff6cc1211b836619bcfe4c94a591a022788264b93e327b12b81c17d122b51529210f81894a453dea4c4132ce90beef5d3970ad5ebaba420b0993a8f0e2a9fe5551210f8d0f5a372343163fa7fc0491d1d8410fa9de7fb80554d7fa7544c49ea9217ec574c277e4e925c5bea01afb9644e498378c08a4e4286aaa053c5f80e18953ee6684189a73e3ae0fa6d432811fada1cf066a94c78d2f04de3804b9249a9a725856abb60b4e48b926f37e7f1dc80fdebb1ca3dc972ce065c124fa933e9f443a56bc0494264095def0c1af02668ec32fb4c4f92cc98019bdaa4d7c51df51b4d0597348d8c259da8e02a8fcc1294249ae93c055792d0f76c4953301ef72a888afa1332a5e0fc468e7e6bcdc1440ab6b553644c5bf727380a4ebfba626a923283280afeae7ebb379b782714acb5a9206a503066572a8fb068dae4139c0cbd6592c5902778579341a6ae13dc8a05933fb7d64ac609ce43a7548267d224da046f492dbe7d5697249ae0ac8472d591273b9a092eaf093d2958ae6c1626184f9573503b25ecaa4bf0fff972499b29e8c7127ccce9319b5f164b52097eb4ff9492c418fd2c88126cd0d7f7cb96747a054d82d5923b6b4c157e9a9704bb7fb9346391e0d44505a11b93aa2d41828d9b642c79e48f60479518e6e3269edc1dc125f1a083a6d47a8f3782cb5b7f53d319c18a7505f11b7f5517c1982ce994a494ef988ae04789a5bf34c70b90085e4579364b217a83c70b80084eb3943213d4876093e4a12aa4891b561b824b61d2a89c572f0423440932535d4ff6d509c106539d79a10e82df5295c4cdb8fe7941b0b775abd9ef2ba51908beaedb2ea512993503083eee9f94727a6897dc1fd83b55f568fbbeb9f203abda22f4959d695abc0fecef7f12fd724df6f081b3307929a7b72fd93d7049ddbf43f37ae004d1a2b9c73c704ac8bca5c43b8dd9e38135253c7dbc11d3a23bb0757ef2599be69e6530e625c78c59b2032726a8b5a80e5cc9964f4e214407f6ef4f0a9e53e6c0c969e34995f6da3297036762464f39730993f238f09e721e4de627e6948603f72756aed1248ff6970c2e7e3cefeaf70d8c4a1be3eae6bc97ae1b38d94f2bbd848e69ed186cc9366af3a53a15d236b0ba71475f9b7aeeca0636759eb4ccd135f0a69d9436d17715aa1a381ded68fb5bc84fd20646e93cd9c026dd418b2e793574305903ef1d3ad458f2b02852032363f2a47366b9a754d3c005dd644943091394a08191ad3db242478bf979063e253f13f5728c8619d860e27d579a6788d29481b72c312755910cc758323d34c6e018384914a19dc48b96830cc5c08b099d45e524c9398ec2c0c5922e4fd45e98891e18f86bcf6964eb870595fb0217945c3137981cf593173835ba2755e35d604d4b494986ee75ceb9c027336977746c0bac25d150829dd565d7b4c06d694ba19b1f4a6a2d0b7c5052d65cb66281ab2bc947bdfb262fed0a5c681f3d35bda93dc5acc0c6cbf94fb5a71659b12af0bf657a937e31319fa8c0e9a7fe4f82587c514e81d39acbb5941c642eb3a4c027e96f538ad6fbab5514d86fbf93a4bc14143839fb4553929ceae33f817b1b0f95adf35bf038813fab9161573781932bfa6f5f5bc6cd15267069920a0d6242fb949a86185cdad1b491a34d1b36ca0b1a4be02cfedbaec92738544706acb8b8a163c76591c3043494c0a6d42728e99bba21cdbb21d0480227536e316d72bb0a0011682081b52e21b49ff27832fd233096243d4a8c197f64da1240230c4ea69bf43e41c6ef0c251da06104b6c4245e7ffeb0089c5cf2b857c7f4b4408308ac97544abae5b73299d318c2f761f2b6a90e06f7eba2ac948f0caf56085c68be1c33653dc94f3c028d2fd893362aecfc20f0a335a753b27d924609086ce5cd3a3ac84a5246a0f103fee35ff40b9da47909330c1a3ee0bc3227b5683160d0f08291273427cd24fa81460fd8d45437298ac6ff24cf03aefb2e2c28b964c7cf77d0496142a379b7660f3474c0e6ec0ad398add7732d068d1c70d9e4072bfbcacc97c1030d1cf0e9de4e7793be0b367cbdc44a4a7203c6f276bb8dec9446b7030d1bb07d5ffaa14da6e4fb6d6172a05103ce04396a36424b03369e57ded0ededdda5914163068ce67accf6dd49bedec80049059fcdf45e7be816713d0e20a8e032493bed613227f1534ec1b9aa5888b20b15f3ff06105370a71fbf932ccb7939570a46aba97872e5e41527056ffb669ad25855e5541b4046c16f6916a52666969f94e30237068828f8ae4ca52f4bdeebef2b149ccc6ab9fcdb32d9fb630001051b8365f87d671bd1b9f904f725dd965a4d3a16b45d00f104779e7964c9adf89d4e3ac1da281d55da842ed135cb094e2e21b243b3c6d1f1dd04bbf14bc9e0c9a3093e692639c88b262d806482f754e2e597145e59535700c1049bc4f3dadc47089d7b31805c82cf66efe95dc32b2c50632dc1e55add6814402ac1ad7dabf746fdd04a420184128ce60a1655e359969e4a134026c16513cb4e7dfd48df0f0c1049b0be75e67e15d6e90441a08615353ad0a3b180800d1b3a528f1e8dc57f01120946855acb25440912dc08b926989cf308be8366ab24269f1a4d720467295b14f1bbf2f83782fb52cd7c32b7b4760e23f82dcf122a5ffaf4d022d8ce377dab71748e7aa2082ef4a674526e5bed5e13c18fd0e1a696d6ee2f06119ce6ca19a40895533f77083e63a6f0935c932731330457415a08d176e9262a0497444b35ed9e20535584e0d24a3a08b6e2794cf1041d4f1a830022085e932593b1e34949169dd751600009041b7a5463858fa9204c6a58510310fc7f0ad39eaa25b67bfec0b928fdbd1c57c53240fcc0874c1acc357b5ef44f1f18953533d6da997ecd8b0b207c60f5fb627b46f79116b30746ea06d590418ede3ce981bf31ef152567f6461240f2c06992bbe439d6ce008207562f9d3a952d2b899e936e00b903eb67e228515595c1c7186406b1f53b8da11df8cda6b49589b9eaaaaf61450d2b80d4813b49de1284f2121d78d17b93a641690e9cf0bfe4c14e72e0d3ba64f54b91611a8a039bdfef52fff5dc7687032b42dbfd694d06d7a12cc773357a7ee70d9c2531ba852a7153eab881f394d288fe247d58b4c740d9999a0735694367663a9e68276ce092a097b7366d6b89d13570d25264b028aa8133397512553bc9374dd3c0fb7652d9c22aa5ab281a38d35ed983ab97b47e9e81bff12f25cd9466e0e413212f96bc14cb2f0323dada2ee7c890811f259b7d10daa562e6d4b0228bd4630235aca8812690e38a2c528f09981940c6c0c9555236a57290a9752306f6e49a2cae6ad24368c2c078eaa9535210429b8d60603f68cd161a3c752cf10b8cde70ebb8af17782b49fabf3ab113bdeb021774eda8d1316f0a9e71812f796555d96621b4053e89972896b973c5beb4c07d8c2ff2b54c9694240b8c4c0b3a27c174a86e0a1638416c73a7f4d07515bc02a3eb7b5a54eaadc089f98425a9de3eb8ed5f00a9027797e47aa790a6b9252af0a32e69afa47774ff9e02db9b7ddb24ab51c94d29306a4d955cb2e5a7cd7714182bb378616ad7dd4628b0a97582e5b5bf09a1a714784a418501e4097c36d5e57e926532b53981db1c91957ed7198034814ff933c392a0352a55204c60944e5bfdae1a2f09250ed8d80188188cd29dd624df1c9025f09fab7df4ae3d6d4c72024f383c5d00a2044e0e9aaf23be69ad805f0092042e497d4278eef060c9a4821d1f0608125831d9e4e9d4164d9fee087ce63cc93696aa8e7f7917206170da522ec1356ff524b91138c1645072a5cc74419545e004f1ba933b554088c07636116627ebea63508a00162043e094c9a9c4db2eb13389e1500583cb944fdebab6e4492cb0682d125a01881038b192542a897a6ba13e902ff8947b25ddf34c78ec100012046e4cd097afc282326920707249fadd335410981b203fe0bacec22d261d1f3501f1015fba5c7bd4093289a103e2c599c9e465bfc9f68013267a2586dc30b5ee8a02c20346af8df5a993e3e6cab704203bc01d203a4889224a8e8df041722000314070c02951acea62922b1e3db498024817dcbae6a01bf095eb67a9cca46c51b70123eb3b8b5b5d034626715b47896a29894d037e3d2535fd734166c027491056c233abea34a96074e6d6d259222af8ec77318fd096575af93885dd793aeb35054dc10595b7c3bcbc3ef8570a4e9bf8a7ec3323059f2e3273b8e80cca523e46c15f8a652749e226b1f1210ab6047db7b13d549d640a052b2a6d3e5d39283853fbf9e41d0f9564924ff0a7bdc62aafea094ef2d7ab3de529d6063bc18d5de95630bde6c9c209be52dfa26b12f46912da0427dc4726b172dcb3b39a60844c319963a209a5a4c5061f9940fbed53eef018261899349a7b0a3313c5bf041bc7640aa1abb704379e42a9cbb1117da24af0da971fdd8328c1692d4fd119cb4970a92a4cdb73122cc96242830f49b049c96076d27e275d3218092ed5698f9a934c9ff490e0dd469d2479de3426053da21421f4d9f8a6e808562df39e0cefcd67e28de02f9316a5296919c1c77a93fdad5227b193fb580427a593c41c22f2a49894ee64f0a108367ed29c32899a186395087e74ec13ff3f4b0e112182b51cbfaa74da9e857908ae43ad4e57fceec3106c4cb2d3a58f613946573f0ac1e7bf8f9264bececbcb7d1082cf2757a812333d08be774f74ea9c94ccb851c18720b89379a6bc4def5665cf4e011f81487af3dcef4140b06d37a64c97ca1f38c9fb93c9493c75ad257ee0943e9d44d34c74fea30f9c9f580a159ec4071f7c606f2b950c9ab4fd35df03a736fa62050b8be3257ae03a83d0113a6e9707ae5ae3d86514512aee78e0a4598c3d72dd3bb05d723a8d5f29ffa818b38f32f874ab992a9bd00e7c30fd3909e5e1adabd681cd3691154c8ea724f7081f74e0b4984a5a4d4fbd64fb94f03107bea365de4c3a8e27613246f890036f25939b79fe09b272313ee2c0ffda798d9f785fe1c2818b3173ca3f599e794564f096d4e5f2d0cd1b3895d2e6d4dba4b942c70d5c5a75366ff20c9c68199334d7585ed136411031031f3b781a1d3249dbb276f8d90f44cac0c9fad0a94a54cd262d331132f0c994642afa7d4e413776a031b09b6fa77f97ff251d37b6c89188810fb2af943e49dea3d42739100903b73968dab928ff2b1f0c6c2e29eb8efceb9c20f205f65310bdbd1e8a7881df7d537ba2671778dd549abd64b4b4dd7181eb51e959f35f2579b2c28a1a765c245b60a375e7bc4e426fdc9425886881d33aa19f944e8e40240bac7ee7a4b4fdb501112cb0a593a68e7792621c4be40adc2759d4ee5369934cacc0a9ce49b0ae8d9ac75216072255e0f3b8ede74c26076d3121885081df9cb9a4d42c7617551c5a79a374ecd0a101f881c8141821f29424e5dddcf4f1e0a247578d2b2260fe46160e44a4c079aab124bdf58dac1321884481b33231bb728fa614f42b44a0c0899d9dcd4e124f5c4f3a380c9127701a355f3c9df16476ef04de2e2c7454779bc06d12223498b2fa10714598c026296fbc4ee63166e638b47e87e2d8a225802a22061b73089dc5a492fd3f070c912570b14a329d9f43097d2adba20021a2045e74dd9b97e5d1da58046a58c135acf081481238ada05d3bdd9998f44402ef26bd05d96da9978347e0d4a47653a2cf246d1b0634062261f09ad32559b26ec51c7723f029833449a7a8891481937674da6cdab4ffb944d03df9a955b5ca10b8bc39e6118fa241c3e28208185c77d2ddbddc49d411121102aba24a448949892ca1e40b469bef0865a29697524482c005519f546e11ad690381cf34297e7eeaf269fe077c9fc9cf4e3af8799b7cc0dd85e9e439e90febdc0b5e3389d2153f3d60a426b5e7255ea95b9a0766a3447650364474c0a668b22455d2999c4b9b032e273b39a8f86a421c5b70a1c5d5554004075c69b01c4fccf94cdab70b468990a53154540f4a7a03c4832ea9faa94da5a8206283d4af75529115b704d97d215203ae35af634c92b426afa3019bb9cdecc493fd83da44665055a586b5ab9bf7fb9a4e32d8276f3f0949055faf65d2c5d54350c1e598c43d49f32447df2610720a4ed56552f94bb014f293a33570b20831056317fd3695abe6b47f29d8abcdfe9982de917d22059fad797a774aa3e03c06f53afdd952bc932838255a94be998a9f642914dcbdab983c49884a1b0205eb9ea4d199412f3ff44f702393743ac6ecf10477e29a1c5ef6fd55399d6063127547974851236f4ef0a1d2a9ceac2152bbde04975bab74378697b88926386f1b9376cc24136cac4d996f3946df5b31c16db509da338892597309eea4373daeb9d34b9796e07fd46965788e785a092edfb5d9054d978494e0478e54cb93d328297793e07369e7dc67d9d49224c1e947bdcacd562438abdabcdcae20c1b7f7a6a865f9ff4e8f60f4a8789972352599144770c9a424bac7ea34821b19a38566b1245d8430822d5d7eae9fc4ab35e52258b7f4984bd29a10457076173aa9f5d12ccf9141482238d9dd44c91437273106d1052188e04d99d0e9e4fbed4d398760d4e68b2ea9374fb435042f26c518eb5c3735652ca4108ceed8494bdac6efe284e02e65ec3a214768cb9d4170e227f538424905c1650b25ba090f621ed4407041c86c93a644f1d02520d8934c5fbe67faead2fcc1606152ad58c60fbc779e6e4c265f10621f186b51aaae4c4982f22ad111c2075633c9bb17c49250badf03a7f5d36e9e36d1037b3149de292eca03a7ef274b7f3ea5a93178e03fe71ad139de1dd84ce9de849ae7cf65198cce9ee376e044c757f53521e46aba0ebfef287541c4a60397cbc2920c429d3c49cc1c3895740c4f6226f5799f1cf8cdbdd71fcdea20561cf8d8f94c5f8edddb151cd834994ea60c99a22f4543c8281be9216fe053d29d4fd2a1fbfbf26ee093cca239ad9b4c52e663f0769b45ffdde85a4fd20636a9bc26ed84a99841650327ed876daa35afeacc1af81384e9e4a149ba262710a206469afc2588741d7929290d5c584c5e8252c945261d3430dad2eef3d53e039ba4adbe5295433c9e9881cd2283109526e65bc59481cd275f2ec9b364cfb542c8c0c792e29904e5c1c45442c6c0c9d967eaa1c40f259384888151314477d653e2d0423a528f15a42a068484811fe125c6b8f105039bf3cbd364aa2b82902ff04109f11c935399caac173813a427399fba50fd5c1778513146adb54a0817384dee9dbbb34992106b6110b285d38265013f29a5ed202e0e2d353d42b0c05adfdbc9e5974775c9e90942ae6042ce187a177adf902af0252dade54aa224ab0a875201511032852c42a470b0b74a93929cf24b706841600335aab2101285abb5a4ca143544734ebf1ad25db7a33b088102577bdf496fba298fa9902770a16cad82cc98d4d689434bc9418813429ac0b67adc730d2a6d5e0e1338e9f782aed088c1c5d4977ddc84214be0d464dd3bcf3b6982078796df48b7012b369003251e566c60c715366c5c008c10a20446d6c91c428ee6e578594f8424818d2d526409268b78f709003a118204d67f9494446f0c69fa7304eef3d7a46f1311ea94b40e1e29240c4ec91c742c632951626d0436cbcf4bde6c2229428ac0fee751a5db3da8d19e085cc7243ba668d6eaa2e2d01a02a7744f952469abfcbaeeb8518480c1a838aa394b928376867068dda861458d2d6e9c1bc544112204ae32878a1c0d997cd35c683182902ff88caa278ab65c10183b29471dbf6041ab2347f7483c32042140e0c53b0579c172c6f798331284fc80f512a16aa15bd918213e602f33dbd7c925b9e7370c215ef049a7a0c413b3e56c3f3a20a4075cddc9d649c9fb59b4c4033e28fb5bd329ab6e7c07dcf868531ff4a960dad101bf994db26ca14bee2e73c066ff10d3d449bb4b1782033655cc97e4cf77d5f074c199f0524a9ad0266879376054cd4e7fe38fae6e03de339e67508ba2f52b2135e0fed756bd43080dd8ebec1f84a7a6b6a4345608990197d43fb5dc477e364955a0910a2e4f09daf4dfc4fc39a8e0c44afc3b273976f4fe145eceb9313b2f878629589141e47ebc9883fe8fddd328057b59dd820b74a3043a7224870629b8dcc12a63cecf0d1aa3e03a6f124387982c0d51b03196ac4968be24fea8698482b5f737954299d21a1f0048a0010a46c9d94fecf61b875618343ec15b683de17d19bd4cc5a1b548a0e10976f7a4b5589784a764bac1631316a74d0a343ac1be282d31945e1afdaaa079201c3d72a880135c26f3d010d980c626f8fddd9435a6a02f68688213d328d93f5ad0f8e6e3d042a636139c69e72b599692c8d209ba072a800626d84b525e8ee9f4e9ef5f82ebd4f93ea4992cc15a0abf93f9ccf4ad95e02deab92699fa745688126cfa684aca296b537a12bc8919357ab217fdb54b8231e9c737f5f27552d29160cc7210992978dfc940823b31c7dc9ae2951cb547f057315e49496596acd6117cf2d0fd57f6a5d22669046bfe3d4a4b6fb7c92b23d8fa1b9dca4c28f5a745b01643e7519e1d3a0629824f4fafe3c9c46c1acb44b0676df9c5fd24753622828da59f7dd73e5ef577083e9424325606f114be21181b35bab2a5377d625608ee2fdd46e425499f96107cd0174daebf9ba3568348a8ec6f26c5a40882519df3e414534fbf03c1a8f11c642c2920d81a8dd16af4964ee2ff8193548d923c3c284bdffb8135397692a434a53e302aff83e851972cfa7ce0b409426650d31ef85e8de6a52626a104b91e389564cff6a66a82e5781e383993dcffa5391e18d15a71fd53fec8a43b70d24adbd6375b92e3657052a565b01eedc009d2bc4a79f4d4816b4b9ea414bc4307cef7376b9a524289a19e035be7164ca59296037f92d278764a8e9a7f1cf8eb0ab5ecadfbdc81031f640c4ad7a4960c565d3fa88572dfc056926b47dea5ac3dd70d9c97e09db783dd5e6ec7e0e424ae59055744e08617aab8426f52cc0a22bc46450b1caab0826ed9fee49444cf2064be5045150efb585993d4363e151cfd9eb33eede77b7d0aa69ea0ac35585292ca4aa153ea329b30bb1b4b12859424859f14747e0914cc4166e82054d0f59ea02791258daae9e592728222eaa915e3f72669821d42e5cc2f2a9524132cf9d2b2459709aa2562386afd9d25a8a942e8b47e254af03b6452a73b745e9784cec4dda0acc7540509c9b1730f4adff555c38a1a56d4d8628bc403a1069451c5116ef32a2df62fca3c3fa8220c46d578ee744ae59c530c72518511bc1175b2edeb45e0f3581095fb4eb6a8820878fcca8a5d416da343e0def26a5249ee94548bc0e093798e27a7ddaef0a20a2170e6a3844631af54f145d5964c10d5923c337628b31172bccbc48254812a82c04953da7412442cbf630a18550081d3df5c55f94779d2cc0339b4c8a14523013851c50fd8bf2449aac94a4a329aa40778ecf81b5878425a6c51378032aaf001a769b92add7590e1f182cb7d299750a7d52f9755f480cfed0de1a24e1ef0969b4c3b60b4086dfa4b99ee684a74c027fbd1101369623a512154910376338f56796a70c0e85bd0fc39c98c294a95e8a08a2ef8d4398b44a8e206bcbf65d08d637f314569832a6c70b44dea4725c78d2a05411535e04bb4fd4375684ea3245441034e7bedceb2e455cc801bcbb4e06024157c4ab295609a1644d909156ccaff1d3a06373d79740a5ef32915b335e5146f370597a49b249a7412ca3d2f05977425d124ad41c9be3929383db9b2f8974c299bce2838b137d44963d13ca947147c8ab6edfea60d05a7fac352fd9a542ab7a060eb524f5def95bcd127180f51a776af4a0ed2c4136c144b49d08df69aaba4139c995442ed6f30d51be504276ae5d899a64d703a46cb7afae3976552139ce57e921d2c99092e5db5e470ad93da232658cb592d58e7e025f8649df4efc88b6bb62538932471edc3562b8857092e3334881eeb6899152578addb68673a8f10da4970e9a5925fd65eaf4b16ad035b3022094e79c518dff32e1889049bc7a44b6db73976e721c1c97f82ad69afb5d1eac823b6dbcfe52997787fb2648c3882cbfbee2af6592ade3758c0a387e1c148231859a282c79379639b201b61447aba6209612ee2686e29346ed0cba322d8cb2b7d494f468b279a0836585812bba40b5a4d12115ce6b4661e821363b6a7597512bbf45d8c18820f3a7b4c52570e0b460ac1965c42890aa2b7412511823f25b34267e90fa1e4c975ecd09145047a5861050b3e470f1d094d80032cf81c3c92072ed0011b364ee03bb0380370c3c82018cb31d909d2e43aba4739288c0882711159fda62dc9ec245f8c048253c24c2cacfd7d4c690410fa87113fd4481fccb59f046b7168d9b061c346ae05e011237c70b574a649b161238b1d3cde860dcc458cec0113fde4b4de23d4a572348f2b72f5a0e58c257f524aafb69f874dfc2b37b71b55b18487b4247562f49195a9dfc11c647750953479cab02ca6a08250d263c40e6f4e235aba0479d2838b0be8808019a9835995a29945d79c25060b35f192751974cc9ea9c0081df81c66e9fade32658ecd8111f5b94e924b123706f51046e4c05fd45399d77a8ec481d32d326b4992e8d349e5468d2b2c50a3478a40b9c1081cfa9372ac503d7e73c0868d1132189d2d76ee0c4a1c5a1f4809023bae48e96dd81879036f22a3866a878e1b663be2063ee35ad0bf54d2e5ec78f4b851160322b871809131d8edca305127b7401869031754d229e914f7539f88c9081bd82fab7c494ef26915a5913570b14c7d9b668bb16c3c39b0c0a25f478e62801135709d7f355b3acd9572c8d8b0b1828474e44843d94043d94846cec08f9a4e76bae4317d69b46103c7168d63c40c7c4ea206a9f95f4f8ba70c5c3431b608912d1978cf27c9577d5ba783ccc81818995ae2f629a16d298b436ff0e8a145d9c12393e7c0620b332206f6aafc92ca25bb89fc842147c07089f052aafded456a46be70235e281b235d60837accef51d48b5e4d0f46b8c00595743f98ff8609b2912d30eef621733eab13aad702779d4debf34aadf69e054e468f2337e5c9a9da8305b6634ce79aa7a7d92757e08310dda9844e6a92a4c40afc688a194c8a265560b7475ffe0d5d7652814d9fcee27ddaf45ac22970625a6627a53229b01b4f497a7b5251e0d4653cfd5082a899050afc9a08a547a9c62455353e8c3c81aff2dccb1c4cab52880111dc10c018234ee0739253f2e875299a24a509c993f3e98abe2b1338192a4a9e1c6ef2c62206afb963a8ab8bb432cd12182193698330a2042e6ad0f5db52dd8d2481d18d9bd294bc99c00812d824558a2696460df3f54e0068183902a3723ecf565bd2c614bb91242c4e8e0d982746c2e072c9e79f444d525a4d9245f368418e2c72f4d02203366c60dd008023468cc0a5fe0e6ab575f3897911d858c24ce9754ac0a30115a8a10116a40a6cd11230abb2a2141e09c7161d80468c108113b4a8cba1ce1a02270525c9fc38ae79d562c3c62946c0e0bf2fe64f7662666a6f44088c2ed7d75c5d2e36ae15356cd8b06163e40b3ebf2dad95bc83c07d4ecb23479eba6d81c09b94716f349329ed02467ec0c6f453fa930a9d37ed3e6047667f9ee6132d4cda0bee6e6f9399a4e94e4cf680bbd10acfced5080f38c193f274e2586eae233be0742ccd92f2278ce8802ffbd051946e1c1522134672c0586d0add6b77e6258903be2a9734318a109ad23ed2059f62fe3c134afe792a2d61e4069c6449bcce49a7bc8d0df80eea2ab493982ea788911a303a2243099a111a307a235a43a7861017941899017bd9ee3ab7aee41c6c4a88a482934d8c4149a1249d79594888a0824b924ced142c567edf751f21720a2ee813934ccd39a8c6df14ec25294a3c7153de788c1029059bdfb3c63f747e3e33036a081152f02994950725f6683dd18a1a27e0b143053d926a214446c15a8969ed4557cacc5044145c49413ca80cf9b1ad84436b5c11012cb0682b6a5c61011c3d727ce0068682fd6863594c08147ca87c529e20f2093e76dca42c5a7c7153f204f7f9a1b24586f0bc1487168f1b495991245a74163750aa4e70a52de868b79e530e429c60f4522e4baa5d67a5de04a74b2eb1a4fa27255e65c08a03229ae0c4524e25acaf4cb07f22edd33c07139cbe4aaaeae3b8bdb997e0ab2f995ec9b53ab2b3046f6f99c73c55c8f56f0b2295e044e55453fa5ad49b885082cbf9561f63ae92440b9a82c824d8a47faf4fd25f494c6a4b838824f8f6ec9fe99de6a1b930884482f3b2b19c4fff4931e5f0d851a6001148f049eedcd731ab9892c45c61811a1bf042e4119c7567f553493c314ddc9220e208ce4e3e3957498fd559d4084670ff9e363489ee16c1afd899183ff776b457042fa2326b653d25824ddac4e396527a172244b0279e7b4cc9e287e0ddd3938cafa272e51c4370314575f4d8fb275e5d08ce74c949b0cb21445496105c26f792f4c45c27e76b108c8b896152a6e73269b132314404614c0d425dc64fba7eb211a8000bd209c82012084e6c5f514adeaacebb7168d50d410410dc8a28952787e60f6c255b4f2d592f55bd7ee0c34489992d3beced81481ff8cfe95a3eb0998292beb35f6a3b3138b4b6d80363a62ce52a4968ee9324d103b7490c0b3b1d6982d6f2c06d3c51d5542c7d55cd44f0c08b49829fcc550e1e5ad80c44eec09fc69227bac5706895c16eb213634ea5455d2cb0485bdc600117c70ebc89694dacff7c920c0f87d6b9a105173b7894c0ac0e9c1884565790e32954f502113af01f6df335a993e4fcc60e7473e0c43bb9415f92a27354706895c981ef9c832915446dec04be030bac81481cd8b4a25297bc1233598b430b0d0e8c9a258ba192a824469b0c7e542e753b154fb2890e44dec055b2a4233d558f9fb0d8c1e3060b8e61b1838771039b2b69e50f328ecc8d1d28e991b048281983773f29b3a5945eb5811769aad29894defa64900dac89953e9779f0685232105903eb717cf75e53379d458e3e2d5a055bdca82e44d4c0a7faa0ec2b6452fa1d41240d5ccea424a9a42c3de2160ead040d7c6cbd9435ef95048d31a814b0850261281008c4c160301004d1a1f101b3130800182c280dc642a1683494e4f9011480034f2c204430301c28201a1618100a45c25028100c8682613020000a83c2a040881c4e8a93709d009dc01d82b6017c16a71de1694e0710d991596db87f31c102b5f8ebbd8bbea1dbaac3a1f29bff3403f06c8ec0ab005468274d41bbaeb2b095e7e84b088b566d695388402323823a16fd9127f1abf52c7e15198f5f81b9d030f59312bf9bfe312f58145910041319a7a3be85070603c41fc3fa31d603ca3cf14977fdfb5057b17117e5ec392add4aa932cf3bdf4c97fad91fc4b473a8546b08a9c37b9852a99bae4ce60606a40cfee31ad6df03974373f17c748c880d600f8445e1f80adbcd2516120ebbb63f77cebb24f8873ce0d8229fcb2a88c47ead99aabf53580eec6880449401f15279521a4cf012580c04ab3e1b86b605f5c2150d42aa889edc13cbe7e9608850223d39e55af8bde4362db68ab79562be72db747aeb9e32d57bf20796889aff72a50cfec6ee8714784a7dfa503fd568d6e144cb3d35b29fcadb3f32b35abc2dda58498cd4762c4bb9759ce3f4060688aa29977db67f4b6d30fe8ae5bee8c379f0b2d68d380f5ec7f1651824aa7922f3a4d4f2504df5c8632b09522601d09d56a24177fe646fba8a3bf83431a024185c920dd7d24b4c56ba5affdb551ac3f9e383b35de54000dfa0fa59ac7389a9fe511001e654fe83cf93ae5c83dc37595052c16b792a8ef8206039c0c2c6e1a01feae45e3061588dcde01b1540018c12fcff06542f79bcef0ddca11e55f7150fd171d5565e29b0140532e824dda71c3e2b40ba4db5487686cacb28c377f78a4759a7c273273614cb8f275751594736c6255090f682f6a663e8f625a14adb0fd4a332097d38662b0e0ed3564534cbd5922d9557889ebe9c0054a68017bd40f9ce287edd3544143ffbacf11fbd029dded75d27c006512f2a294a8f2d9555f24ac45e021f6a6294cb3665e4bcb451ac823e58104c8b08efc6be735e61c8f0895353a3f3e32189d01a686cd03e680694a48fe2dd3beef9a450dfa25dd21c6871282f83fa12b8349c14d25da13e708395df60c8b281e8484f300a6244cba4efa02ba3273a019ca1ab29f3d903de6f8841cc83bb13744cffbc59057b79a555f0d795ba9639098b27c2e5465ee90876d330abf6de0ace28378504eb6152be2af3cb3b52c1292dc9961d74ec2c8797b884f5a80cdcc5bc7280b905d50fbc2da7d09e5258a7a5fbcabe521d0001a343c0a069dce134c03575403bf8e970fc727ff9040ff9b30353ff2a679cded7a889c733bd87f4f3c7cbb63d62c42a54259d08f397c4f2732bc924c4f231ce1ad749986c3faad99481d75d3b5f398779b76209161806fa5322a319aec6b6e303492f0a57193c8b361781a5300c3abed13285fac2a4e5caaef64be79c50c2bf3707eae1d2db2269811dcea015e4a6ba55f0d2bc4a0382357885c7ea808b1d91577ea03ad5c8984002bb346aa7c7a4bcd2e6fecd4c802efdcf2a5d109c4135fcf74d4af18e74a1435761760e0e5734148f552bd84abe1702b24642ba3431d70dde2952cabb0542845522e34ed22f9a0c6018a91ea9ec8810cacfd298d5fb38d0b03f9dbf18f13dcbe2a78f9b26e9ed66637506cfa9c45484d601f338e2b004b8e4ea468ebbbb55392ba8de8b5c2c747401eb078833ff7089825ed6657820dcc8cd7e5aea2893aec334e6a9a3bfd5472bef2e3555e88ff58b55910c5e92f6e62eb4b63e6a4f8a4ee1d645fbc13a7cacdb2155870f5df25e6ced0e577f19234dfdb7308673875a7e782914632a38556ecea9696a943ed6522227593f387007af50596f8c7b433cf896662f9fe9c77cb687d38a5707dc680f6b4429d41346e61cda975424ee6dc9af3990dc8e604ad30e4c90f4530168347ec1a90e949e67b2b2b18a6b68b86d75be84263f20042e31a256820f277cb326177b0e73eccb31d50a9a31603734110b03afa14e3dcf3cd44ff189946da4264ac35b773c49ed76c542ab5c78a33c692afbd26e24747f0220da493948250da3088345f342ee2a38273790ab291b5ba7d0f5343a27c4ba80fb6980ff14b9ad1b9ba1b23d037a362e41cec898852e8d716c651a517faae58742ffc99eefb41f948a2044e3c6f006910f482787f18e37fb4e89b6b46c4a7090d41a6ca0ae93cde8c040c74a3be8596b74ae340313c4d9f5ec3d01c94edce4771c396a0f911c54bf6569c00273b8ca3979e7231ec28f82c39e3b0cc8f74848fd81367da15c6e449851335f57eec5331497b1384363e4dc38e1d29d49a5859c6880884d9d9eafeefd4aab687794845a441fb30a633c2f8852ab8f5c375dd26d9836584bafbd7a9805be7dc5988432ff45d0aa93dbd5d6d692eb4190ab818f3de899ea4a8f7163ef88214a6d4f10b93ccc8eb7458c69b9d05e96ee869507d6d5658ad9731c88dbe69fc53442f987ee8c882e2d903b1263c42854922458eb11b1abd67a087785d248ccd0e600eb768970db2508cae7135d5dd27619f8042559ff8055e714de7c91d066894e2ac32623ad282d906ecce6100f8a89f02d1c907300f78983b91f48500ffa1d00a658862058c54cbb3164d54cfaa39ed41f6bb8da4c32ee4c1bfd31d586eed7ede3b3dadcd34ceb796d1e4c8be1452ee5f649e72b3604ebd61a5e896be4f50b7a625ece33402cf85e66e668d90884c3ad6013ffff4da73df933442395ff9f69e44092d952bbcbb054142f45b7a15aabdb99e5e2df814c80d8f40d94af5aea3d92879264ccb9f617bbf123c4204bfd08a573f8c41eefadc7d2fdc40b85410c2f9ad4f0f6b76eea36c33a4f980da2fc8604637720c389e9e2d964ce20ae971419b2700789adc8b7eb29339233401fc9f566aad68866ed7343ab95f5e93ab208d1b4987521b9a5d4ecefe71919e8c9146e7a6b5d091b55901ca5704377c44d2e5094873d90787645dbdc54780094ca431c3b416e470603d751eb64a5bc8cf132f8978a03bfd808cdd804ecef3cabc813998a55b282c11f9798a949c858e463130853436a6c2b0741fe8c22d4b1df7b4c7a905592ce4d80fbb749e4743391518833fd0b766abf0ba063fbf9c65dd81fa4c1b6b3c256525f3172f6dfd98a8c6207dfd068b39b8839f0141619040329184b754b4de0daa8ce58d01b9c2159e137c2613f9e0cbde2ed837a2630d12a7b69b478bb77522d53a0d446bb910c1fb9845f904321f89e40aa276e6af92d0a2e3360d7cc298036a74ba3980a226a4a01908c987ba8c08fd50a203795f827df73563fa41113bc2f72811c33c4f3cf8411163d602704bb160f89c41c25c345876c794d8adc896ba094a6dc72166ebe399d32a419ddd129e6e78b9f21f0e5c7c5f7da04095eb345101f9b5ed1b072a8664c5a60e14c9a732f11e6beca2ff050c1c003f9061ea85822407d8728c9f6adcbd470b267690ccede964f23be8458bc7de1b90b4de17310850512c6386ea21ef126584c02dce5e703b59ca9b3b8b4882844db548836e92c348034412d88fe23005d1e00045a71cacc2e03b5495ff9c42a9ee68c9984ac5ce0b1e21aa4ff48043113297fd4205a70d1044eac209e4c3ac878cae57b61d8b4dde773e055ae80be1efaf4f5753fbaa449ab4abf4565604bf92db7c613fb3952f22e7ae55a1595f1c932b0659aa0ba3e92f214bc12a7a0574af4226b516073e3b1e16fbdef358404f47e4fb7fa0a9e7cc018252a7d3654f3d52cce08af376eb17a4506502cce4f63bc9b162057556415b95ee55fc0a4f32422287e01f6d142ad3d5cef6408c1cf11d475bad089e09d1af83e3064eb83a9e6b51bf5f0cf9e703064f39c2543d23919df951da39372bfeea50ec3d0dc6b61fe916803a2084e090516e0c2b810f191f140a6140dbf2d50b995a5709508914cf206180ca326445fdfa20d4bca48b6e275613aa6c4a39733ee0d7eed948e3723de85cc1e9e0853c879e7f13113e3a86e92939cb1c8102e6a71d36a2ac4b71137f217d5b95fde3a30f96fc146fecb7146a092ab8b1948332078f0b78566548404fba9556b65112e8a91c3150e81378148d1abc6ac26d78d6901e47c62575441e82c2240ec92721448b671b45ed5bb8ad610fd345a9a54f44b723444d5888ac6dd3470d52861ec3fc6a007754a06f924361d40ada1e45be61e020d0a54647f4f1fd8fb31075a097733dfd128a69718d76d6f843845ce8b18d643fcbe534ccda9aedcec07717092c16a37a064c821b0ffe2d86fdf2d85d2cfb4d5841893aa3b56c2dd1e6f255fb91225594aa1efeb2aeecd11866914add17c0ed7d3ae3b2a13dd1a328edad151e44ef60b88052a1fbebe947d4487a970a3d9646896bf07199587b3879a48ae68936e311e9ca054e513deac13304a0a71ce5c087d42d7969bcf6257980d827e0c253f895a690225c44f4114a609d96128093712f25fcb8617cf4e5c3b76de07f64d1a96fb250610f8944739d9afe39a38170b39ea76cc0cd7fa0c14a5266b348861d4bf73c2cb91afa5775bc18179e16a624bee59701624a86cf5164e3f4e89c8433ce00c722b79e45ed09cb65b99d16080f70d0ca54545e8781c53ec1144880649c3a8e3200db129eb1d6e76618375d66f76e69663e5e8709dadf06e543a7830fd3e0506c55bef3aaa738144e18d811ade3d34678d89e83507a385a8844ac0a184ecf06bc70cbe0e1863bf0bc9e2aa110975739a89a0ec55290145bae22110109bfc080c2a747099f82cb127fc9ac29707c38bf4bf781636708b801386d4e8bee062a1c6c4395ffc3c06a00473dc37547a7d1376056462db47f2ba731ec1985ce0519e9c5da1d85fe6a4bc83e9a622eea672286e09a8163718906e7ca3446d2eb5a38639fa7e47959d85afa306e4bb7f8b12aaf3f25f9ec868f8a17ceb3c35cc7a51843422f8a3211d008664aeec86c4b1f60e711ac827c2215f7937f168e33c4487706edde9cbbf250a8100d2f56d54956090e64dc64d0ad563a9ab7645cca4f6137ae41df24570a7459b0220dfd9673b9119fa3969a3e426918c1f5171e8b2a1d62e6917818141fa4ded3f4c068299f351400d73980fe556d2f3e9cb340339d6d6f01d1bc28afef0da0a0d6857062e1832c8f7c8aebe8ef3db490ea506d42478d6b874aa58240d66ca9ca1ad8fdffc08ad55378014cb9a2b34ab32aba478c12e4020db72d7db5947f29e7ed4b7ba1b56d8db43816776e3443eabbb448df48bd309bb0532c090cbfcc354f273c111ba39dc7dca546f79df1d882a3cc3d92e33f24337da7127ec379e8d8b14dfaccd4f3a51314166c81ea23b08d46fa4401f2d74e20bce8a3e3042a910385049d99413081cb8bc5404624544bf539e04a60f810f60367585884a4521170d5812a1f05f9bc882e6d62c8595d4e26d9bb5b84f5a5a2ab089ce7cc6ab130c04951edb0a0187d9251bca42ad2ec00bee240413e646b04150d44ad0880d911331169afa61833f81c08977d62bb6d3ffd3d12a734c801841f9f0b9d253cd430539c828d63c157ef3decebf3e004c40a320f1f60d536c44cd668ce1049759398c08a9993d9a9d3235795bb723b4837677563f094d7c09c4c2e1e609971dcf42c6381eaa3e4c04ac2d10c4d6da4e1e72e010ff8398b34827e8dbbeadc7f13b1c150dcce4ef600a3050c790c76c341cac8621ae067e2bee107640921d99f5dd927f06e36203728a749aaf37ded6b3efcacc0082a6b8d12d1b8cf386409eb7e70aad0c9c7069ad372ce6db38be0ee3c4a9d6e57ddb4e34495eb054ca57473924c736c845144c841400e3b79851367c3e0eaed4f630448485900ce1aa7dfe6f08f6e904098defc1dfa84d430d42d3faf4d245fec4669c37a335794dd555d89fe6b5e3ea5986c4714414270fc0a6dc537f1ee2e63a50ac8e72572efde4cff156f7e91941fea4ce5590e35115fcbe1f236ee27ded5178e8254361edc13fbae8687fa038519828c84761d1bb393c88d17ea23851f851104e97989f95cea441fd94ee51e74f97a67ec75e93264195a198e05f5a96a8e725b2c9c6499756e11694f1f4e439a38f3240cd5845559130755a1086f20d4bc1a8ae033912e880f35a473b0edbcfc80ac7c6ab99ce43102094adbd95928273aeca97f78f05e82b718c025bbaba686ca4204e0930080a20ec3fd136ade9c38e5dadd7d2e95fe4b4adb8b6221f3f565a551261cfe24fe13b91cb3c2c0c1b485906959a4e952788d7bac82fea86c89fb3d063896ad8c7775605899645287f8ed45017b5431325c76545057330f182b9be56de9a905a05d4d9c0ef6bb34f7707a80aac1a884a61d40834d5d137ecdc89adca244ad2f2864ca4d7956092c79373724c35588fec0510cc30260dcb273039347d1affe32631263d9427da74d42f0ed5f8f04a84d3fd7a95385b504ea79c2c900da2c36d74b19b09adff8caea44d73db8cce66a6438ddcc7d86c14f7fc1e38b657a8306808ba2a27fcf1430652516e5e6ca2b46796a5684b68624d2338e077a002781098d30b08f10aef551d34c8c8c1ac91eb255bee22a939a94813acab7f250e789191e607e7e2cf2db61fa6ad417236bffacb59f9bb86c875a87ae3420dc73a4c0302eb46bbdc9b8dcb7bb842bd014197514e6e730041dc0cbe913953d28c72b3bc43a3dc8aff888a027862c3c6d0c551a2d86722b1355537b902f7adce54cb1b853c44c66d78cbdfbbc6864d4d089c1f6339c2cb51b6cb591164a464e88d27af0cfec39e300833eb4336e7a284055187c52d2b5e76884d39ca383e9b6d51b63a343580d6d9673eddd532ebaef8f05a29816465296f68a6bbd2dda695645693356c0fbb51863914678080de3d12ce676ca3c53f7942f8da080252f170681816e1f6432b5dda6225526846a693bd81bc6f9dee5b093833e3a2336d44ffe56effdc25e113ecda538dd79913acf0f90bdc446a4c8d9f736a3eae9d6a81b55ff5fa00831ac5fb4bc2e9e62757000b41b33b88f5432c8e0b95c6a6f439a4039e32f76142fe03be635855e5c2a301803db4c465a1040b10b8bed12a0c05ab7fc1d0b1bd26e275876963e9fe2574f566b2ab8ef1d3d8215bd4891fcdeffb225d07fcc47d7c9dc9d9a2b7a7f58a7626593e962511eb0df16bb760e9958ef67b9c0dbf1b9aa5a591267437174830df95a1f5b35acb779fa3dc8d1d2add274dddef80744632e33a82aa8115ac2a60e303cd713bfa91fe8185fc9f8f15ad1cb0ef24ef8ed20201dbbfa34de31332ca3c05a13358db131e9e7eb52a3854245c54a0a077459c2c4417e384d0fa161ec86498348035f7d73737e6b33fa6b3e9718f0e74ba764c6d54f3aa1c0fcf6025fb61c1d34b4aaaca9f75c586868fb0a0668624f3c20ab5ea5f59bda5aba08dc202fe79135501a0025e6bf7a579506e5f7a4f28acbc514b627b1874163078744cb12994d5059fd0d3d97132558e4e221ef94011f30d270555cb6b66ca5ef29fb4a553c671abc9e48901e868ec2a88a876ac9a0ee240e20720e17e8422928d42890366db2485a25d1978379743fd67736d71c72e33c5328a09de410ab686f449ec51e5557e55022b4b565cf2274cffea3399a066570eb01868b8500be7c7f5fd56589389e16222c43baa33beb3023a0baa9aa8216f574f3206c077c61092af4afed558a0150b34e1724942beab3c0dd95fb8288743a4c0652b4e406fa3b79928662d12e569a39af36067df3687b4348508b7249124248c042179482d1244a22429e40e79453a91155242c2904c244f320419a49ffa5304e8c3e9f7463e26774bf66a380c47c0218842f4fb8144ed6bcadee9ce1eaa0d623625dd7465a8c7fa8560ba3c41e907840101834d8c2583ea4889d1c2f797ef941debe50fade73727b3518d54fa1361c0084c5cecb17dcd97c0c053bad66e3a8796135655cc9ffe0556593d431fd125d073f05a0d2c5a79c9ca74226e52fbb7c037599d837e7776ab01afb232dd4143e80bca4374f237eb9a6da8db7bacbd7a4505348c65a43eb2cdafd28c39eb1993622f180d268585b013b0b65e79b95ccac0d96dc14983b4e02adb9c302044e530f8f9a4d592d85047ebcb88684896182dd41efb9cfb2ca0f23bd0928a5fdd2341cff38e614385f1ec5e3bc58f86a488a5e0c764e804b85d71b179dcc210e65b98fb5f02567c958e7ef0fb94c49adbb686e5709b280692d1b7af5dbe87d60fe37cf964c19cbd6bf0bd6108f9867dd826512964933a43df30cd2683143c14109c4c935962d5872007c9c546f9cc619598d31cf6654f6cb0622faef29443f39782d3d8f8de9ae1b75a98d115582a7b072171823e812dcd0277a480cde41750e486e9fe6559deea64a16714c879b1ec1c0f32699da2e985b839d587d84708106a0d3df102ba936d1fa0cf55413682e4e74e99d77a495803ff7bf04832d92e0d4f8198154a11749b23123d339e620dc8bdf5b95d2256e2049f44af6a381c994e0acd3117c3134acbb3ea32a4e66f0b89f0002f1ad6da8467ebf023481c80080bc476670207e9d44bf17979f42eefa94c727bd8ea9419a90467f06b2a956338b3b5d93f34b2cc38a29874b9ac22d20c11fa39da3ae9289806c2f8eba4c26ef93ce184982b563b4405b2b988f288122a1246fcf9da33645fc27155bfcdcaaa25d1a1a5a027f7225f197770017f682da9193c595276d0c8120908651217704637e8985944ce8e986d25258cacf77ef8c10e6b89f8bdb04fec58a71c6177f1d163dbf53370d59eb126cffc1b5fd0d184bb69cd162c83795ad95c5c540e8f55879a41095236780a385f44d5158d780ba58f429f88f20c365c6515c9504ee06a85d81cdab660335c8196a92b83c3421f5e79f75e022814bccb98348e838f3478424c693c3dc4eda3b580399e99d9e7ee2af5e4b40e9ab3e7f80469bce1208cf61ff898c6348d2c4bc859c430b1cc986be089d77900da63f7257739bcae41838fe57f14a611a996ac81f174bb2f345c959ff3bd1a167b15e499187d6c0c7da7653b74829d4ee1d4580771efa73a25578b9eee7df61093c2dd8753b3546cc9ab2002b15f59e25c050eac022d886ab26ecdcf91472d0f781b8958f3ce7a67368b46cc1e5dc6d3f454c8dd29bf5f06314f86b4b58e0160a8a59f3e37d120eea35a0058c0b858791d4eb201a09508005a526fb8aeeb7e669a940e93dd6d96142789ceae42f0b18fdd2afbf38e03bf9c1000c6fdf70310fd8f0f80381f4f1fb68fcce7e6af2200c0f55f34953a66d0175f4101176fc66794a41a7e0eb2c73ce82ba3ff8d3e706abd287612255bc8ef1a6bc7862442136ffbac81f0cc3496b9ad93f770a047f76b678c9bc9527c19df24c3eb55a01ed9139281ccd99926762bb32ea46bf6f78598c607edcde0ef10ddd09e73004b733baf4a23609f9b7216afe9a6c625e2010eac1224463b756ee40dd1989bd022d860208c2d1fc6cf077bc50c8ca875c2373ae91b4e7c56ee15c1f002714bdc08766a2edf0baeda087c73dd12c05e75b0cbdb526c40e9ea15b3d402e4c1cce159f331bf09750f03b2ced70b8eb53bfb8b6e46b1ed2cd6aa205e8d76b09d119aff66182a7dc0ae96d153a79384d8322f57a0bf22eb7c9112754a15d3a01db14124416d55e1efb181f8a33feb11ce4b6bdf84b2aa39b504cbf9bd0602f91dd0307d19f5f216e0a764091cb0ee7813bc7ef53c55709f07c343e9bf8578e0c292fea39fe081c2c564410a1eb4e3e75c168da51b65f28576fc64575f373fdbca57e5455f62a94f706b062a146c767abf7a77bcb496b7f19213e726c1f33d6e7b5444083e8c020fc4d6ab513a8e0261f432b050b111c32ede88fded885cd26ab9a3c7a21faf54f8b692202fa251631b165204c5339dc6de6bc66a51631b23aff6988137292ba849a202e7e7709f9fd070f52e6a3175e2d2a8f557887ea261f28f7fd5f4ca11c40e2ce68e3c628fc8b630276daf359e0a5fb4405cc79ca508eee2e83436f29d2728dfdcec3706eb5d99735f3ddca9457f7271c79a742539841cd5d4ef959a9c86b616c841760444d93dc0eafdc8312179b1883ea04c8bebb0598bdb52730fedc636c2940f75a7fbb0332fdb80012ea30b2a08ea979143ae6dc815eb5a06e15a0745927a546d3f32a20c0a1924a6694085215998acac74878f217b932f4c5c99200539e1693ca8581099b81c698e3b6bdd7ca170e8247ba171c117148ba5061740ccf64a150cf1cff6e47334246348a2df1f2173423bda87a4b1689dd688eb5eef871419bd16e9c911ddc7a8b5624c8d7855497e8f1a93970476b5a9d59d5b99530f262c1153be85989b334e42141b83a71f784401647d80a5427822c653c9c8052c2f7f0255748c5c509af712c159c6af127b9884270975efd59b1d3f91a4646805dfa07c9cc36f762f741d71a5fdf177ac595d10f537099af909ca77666afa9a67177fb1e2099ac4d2801bf852f56941ba2316a46f9c2032d142a94947badbb063eba21e34232eabbe68a984be6b7e741345cf12a12e51496a9ce849348ef9a1760f24cc951f328edce20e0410097070b31a7fda447b6c8601687c0fba55ee05810fe8c7992dd1869a689ffd73b7b601741fdd2e28ffb0d90db73034349f3472a90146faf33ae73a3239a40cdc06e46b6c78d7860a61d4064049b583f184038a55bd3ca6240da372f20fdcb10bca366006fc34c31d2429ac0b0097168614de73e91df71e9f075a7aaf25c49fdf48c12515eff78800bda9ed456c51207e3691cfdcb1e5c9ae279c18d8b0ba73de3ac0b21b44506c0afa05594438f4f99220204236c581b9805140904346840e3c26ac1042431aec92e98f07650f5bef1a88f9c39285a3ddab5a8e4911e57b45ab049377d0bcfe94c024ef05b48df002d013ff8b1514916a61343e0502f554e382061e360828e111ac84171b4882cec64d88afce45d700edb982d7996a48b8d78f31d91c872af5692063341b4976b99c7c03027fa5b774703c01bc6bdc461cd0a7ee203a876842a0779a1149c02a85019783c58102fcf43b1a301b998c64cbcb5559b0a4019ac33825d8e0e95d341317fb0a2fa2c717d181f01e3b54350a567140b468afd5355e511a25af103a433f5d4fa6b6a9567199e868ab66a6a82ac25f73e5c12630ff80a9069bd13b051fd996a98a9712699d863064224044c99642c403383e412219bd9c4e8c9ea970a8ace581b242c8759b3d16030d5704a7496228c4a219abbd71656ad0d646f657454812d300b0fd1437319e694a0b00e3a2b861b62210b0730013030323948820cb527f98aae7001e70b23b54243a9c568c81b5e57a48faa99973574c9e5e31e0698af59118a560fe5983ebe4a863d58fd07053c7e695d2317d92728d3404f0a102433d006a39a8db3178e50262c8e907d6c59064d86f9f5ca12ac68bafffc384f9642a0e2054581241f911bc232c6daa21e46e8c2e4b58333a9122995b3582710f526b2244a349a67ef3dbc5384f4d0b6a7ee80274dc1f07db8344bca64a5dfb096597cf69b7ed57029e4ac799d3cfa90f95b0061fcb735025c7fa4300420dcd73eceea18bf144a57a9e1f4557fca1eb1d04894357cfefdd59ce13c460b58fe97c5c527da7eb2297496f4c6210fa0de5d26836f7b89edecc5955a2fdf0ce30dafc20d21618d7ac26ed6257c01ac7a98fc6400b8b1442cf2ebaece4a0a8b80e208acb05a1de7cca38d5a0f42e5ccfd3838508d8892d91703c486d526d31954ec749fbcc04cd1b353ed3d5db41de033503bd35c00919909cb5c0400045c19ff6f527132228627c4eded867eff3797e84a57b4da583ae68c56d4e392de23825ffe7c1cb89bc9679b72ba2f9760823642940e1352a752bc601a5418e2a3ff2a11bbc3407df6d4428b1cb67b854243e327ea6acc308d6d3b63a2824b36c72c7442125e1c6d594befe5c1a6b624cd62f8dcc1826d50f721760bfc31f8592beb5aeb5b3afc8be8feb82894aed9a2417d856603751d5bfa8d8fa8f1aa49109472cf865e54cf7cfb71ec9b611c7d3db4b1638f1eabef5fff809fccab73880bc9031d619ec35b6939beb5e3bc3f4c58f5893a8c093db37026c9317e58fbf5e4813121baebc5c60c3ed1166892208331418618f8531741e7c6593d0804cc90fe5850b584debf304b9dfe1625abc1a651ca28ae325276b48fc8165c3cd796d1b9ecd63903d4a59adfba33c4ab14ae918a78e4835d19adf564a94f7b3b01af6532d2ca3b57efc2a358b0372bf12a49868f443d808c39783857fd4a0763e9db77041f9d3ab99a39550ddb7c928b7c62cb07e238361d450a9f5c25b23f305af94297c74d39cd9cd0982a9458bbcb06616bfb5213224864e4f525defeabc7da4fa6c19fa68743c6d050602bc2de90dada60cd88769f251d6dedc370d820cc5e4e288084bdee4528103a9603d379ed51c89ded4ae8a0966792b71e2a90db9f6ddda9628382078873cd1e6d01d64bf5c9e1e9995826aac66b0ba544ac83bc43c58bb507634712b83c9781592d2aaeefc26e1a04c66255c95e33ba52becc261a7a4cb0f4eb164b644ca71d6dde274b7cab69e50eac22d9ed343194a9de2ff80d9ca54c412445eda040899e3b29cbc8ec1c2d75757b952a4a00414de9e981a90f4e81257943831776d8fcb77a512e9f9d01cf5f5e8025b1b43384210ddb7e52e8f2e5b6a6c2c2b1f22f0c2e093bd0fc545fccb843e71b15e459686991b9bf68aa465eca19060f19aed5afa91e77ae15320c93a557c2df3c770bb8e6149293eb6ea5b00328a28fec5dc3c3ccef0a92961402ef5ea1f02727a82a93e9581aa12fa153095e0626422c622c1edb6c3a77e4dde6b04157b350846d17106bc35eebf4c93573f64ace446bbaf01dca427ecde6bc1df36f8643a92a73c658692646b7a90c00cf95ae3c5889b851d4c1c104dc704e27badf7811c6a532f5c327825480982fb2a0101e54278962fb6602634b3bc0878a3b7a8d97363858b2a25a0cee7f804c9e0a71854f4122ad9ce0c038cb8d2baee0f97f580aeb533e2738890260f28fcc6a47d6f51f27e490b568ace31d7f356e3a61115eda9a0f3452e0b6fb437c8fbfd8c8ec48d2e0a25b7f04ab2452757c5c3d6618d2189cac8e62dbca2a896dc8de6a979d449a64f1e93d9761ce4a2440429c63db8f0d95791d86472c88a45f040867f92378b98f73109fa3eaf7d3bb072cea895221fe09cfa7bcaed7487dc530b23ee2e8ca8187a52a95a4ef1056388d751ed6859f4902b0a8fce5116ba8f802d240f879fe7ad68fbf3bcce0faec7ed11724c0fc6e3ee083fc64ebcc7c96104f7d1ebc383e5fcbc24decc9999d919efeffb54bb472f3637af7d3867095329cfd61bd0522e4664821beff615bef6111bb712b2904f3a49775f3bc4a74725982005f49aeea900de72c7fca0d909228ac3e804bed82c1b1178451c594d7a0d9044a298c2d567431b323e61dd2262cdbcafb527c05eab1185965d27801197a81ebd262f30c274436dd08d631395c466479a09ba1a15519e0997843140fa10a889ec31315a11004a9018155a5e048150efaf1d8918162d2aa428d7ed81c5b07acf8642bab5c363352a3c7ed7b7c03835a77bf1a4ab9b8d8b9cd082a2ab64e02468c57ec2d668854c020ff9d1f5f819b9c400611f5408e5429000e33e809dfd67ba14d1dee1639f0aaf7842ce3f4ef122ee31275721a6a77d2201338bcfd768b92e6fc17b7ebf6b3303a0e54da88370b1bba11ecab21d7dfc316ff58a9d4d19c8107d2c020a75ab902f30b2a4d001fcf33ccff33ccfa7b0ad756ba9025adf49cae43e6702194899524a29656a4035218d348a0e2cf88531025209c008b108c6cc278f56d7934d37b470b8acef7941338f9cedaec7db11be0e71230b6837b070fed14bafa92a9fef456819ecb5e0c6154ea933093bd7173fdf1fc1d9e186154e67b294c63161bf573c34dca8c2b1a4d8fb3a7eb16e3f154e7d429d9477da299c2c560acbea530a271d1e9b4f9225b7b7a7861b51e872952445955cd348a6841b503888d94d62fd5c64ee35dc78825b6acb36c694fd5fcd09379c70f610ad9cfb7fdda71e034833ce1aeb2e9e20e4a62464cc782c862971d4ce6af0d8e1036419a8ff2bcb5e2a853eb902058832cebef1fe7564d097c48f78f84083870e533e8024e35c427bc8ba9d53f23220c838f9e7b5aa87f9ba28d71220c73889c60c2ef7f725c6a97002c418a774b2d9cd4d526392a82102a418a7b15e0bb7174556766f1120c4386fac4b4df9463447800c63cd93f9e4891b4418a7dfd48cc93e0fc6f9e412f74ca502c6c174fb54a97c9b564320bf389dd021839c53dae484407c71b858a55ad4ea5e9cb57534857a287199dec1a746f7b8418e1c05841707f557bee1a4bd8b93aeb69af0a92e27746700d1c5d14ddc0ecfb3d7d1925c1c3df3c9893cd95fd3081796e30a20b7b01c668b739d05a184cafa4b27653280d4e21c324d9a2efdec12a6c5e9eb4e4c6629df9cec76016416274990a35d7a7a599ce44b9bc9b1296a89a11d406271da79db56af12e36ea60d20b0388e50494f54b86c0079c579ef2c89a74675d8085b00e28ad326ab742a8b2e5172aead3875fc29d96cc46b08292b4e528abaae7d7167254720abb836df063919ca5e2280a8e2607ee93e4689a68205ad91978a835deebce4f2d526473f405071faca19c6534ddee8e3d718c2f7b8eb01728a736dd22564eafc49d348c5504d71ac92c4b852a27c86be76b0291d404ae1aec9d84be1972e298eb7b136e7b5c77635d2f8e123e700328a536fb4e86962cbda62eee400228a530e7d9d5183cdbc404083c70ff46a18e1cb1d4042d120a048904f8078e294bd764483bf9d38104e142f631a5444346cbdc88eb8a9fe0b4a35ba05a6469f51c605904d1ca4be97c9e650e3aae60144139603241307556a43b678bf830d8d17e4c8a1038d34307172937dbba499080d4ea0461a3d4a7089539830d7ec19a194986301c412277153a5ed3129854b2f48250eea79c1577517030825746c0064123a70804822121c008144813c228d040d1e3a1cd1080d80300203208b38020f35440002a2880a8024021187680088217a3c1a454843022085e8b100104258006410364004e1e3081f041d374002a1e3060820746c00e40f3efce00304a40f1400e1838e0a80ecc1c70440f45001903c3400040f1000b9830340eca000903a3c00840e3a78a82182eeb186082000328782125aa298ec769a4603881c4eb66992f8de5f6a7a1287e3c9923658b88bb9440b8753678c5082cf89732ec81bced67971c32928e9322b379fbfb83c761441879d1640da70095126ee6725b1e1f8b6a71994e669d3f84083879aa102c81a96114afe92c9372908ff3e52840627781e3d6800a286e389c512db9ff1019286939820ef47ce9af84a75f448434711ba870a40d070aa2cbf1871cd0ef4e31333b52d01c8190ebef1e4bb783f8f1e34c80c8098e1a8f3fb22742f5b0029c359f48bd43d3fa5849e1416e8192064388c8cb955567367f60b3286f3a93449cc2669bb2823311c644e4a39fdf2309c5f64b7eab7ba34a9c170be24fb5ceba8d824f3855365bfcc5429092576c60ba73db3504a32e12e1c7d2ec9ba982f178e23bba6745dae68a209b28583d8bcb67ef29b50cadac18606a285029285b366fea596ee9e266904c1c28f355ac00972855a7584c6364999a8d9c16b20563885ba922d16f375fe205538564cb972c530b1e13254382899da36cf2c9718cb291c4f928449d3eaa14cb440a4707653512353be832dd95d0c40a2702c5d51e365c5da31415038cdbe09157aeec24afa098bffa5e59031204e38c8a9a8688d7233ce9b941493a692f295a432e324cc4931963cb1628cc1c1c6328e1a1b33566b57c631d9e84611934c34c624e3649a17dfbed4a9b820e394e430f12c7ff30ced216c1ce35cea94ca7149b47496216c18e35836bbf94494cad68b71bce81a72f2d763a212e3d496f55a94565d7c076163182741d34a294d33d3a414c6419ea974f216344f7a3bd88291979849f22b83cca6600318272d6fd144d1b556b12f434ed8f8c559c64a2c19f34cd45bd3d0f141e81f77a8c71a69ccc012367c71eef0924b3c395fb5a41d6cbd385fbab1df2b255e1c9360aae49e0c3a6e41bb38978b9d2549f8124edbba387675268ba62db9389769bf1a132f6c99ca062ecea6fbfbc7f2c92dce99a2d5976d4c2afac816e73d4b739517538b531e4b4ac8cd69715ed19a326973cb26b7b3389bd4166e5452aa5993b238c598c9f4d4ad9854da88c54179faa5496b4a706171982b935752e934cd944361e315878b4928617f5a4fc4c515c70eadca20fa7820a0d13b90dddb68c54126d142dd09ea84d22c0f6cb0e2a034fec89b2c4186fad558230869a4c102111ab0c0c62a8eb65582890a4a8798a88a832af1de4a9b589956a7a26ca0e2a851778450f26be314e74a6117a22b5e8317889040443361c31407d1a42eff5789116c94e22453eaadfb8d79db3c156c90e268ba6aa346cf4cb0318a53a90fcd24995b49dadbc1268a632e5539eca48c2dc14628ce27cf34932449f24e6a0b8a932cd9ad734ae61307f9e967923811b738f2c4a9f4a57cb22aa5b3d03b7192d2a89013a7dc18b1e32146e66f77b06de224c3989754498814d307363471124c9a9832dbec95556a602313a74c9d71fc729e58137f355e20428214e4c871071b9838bdda77a6f4619543b3832d41cb1dd8b8c42955d96732a1b76189f36829d934c59eacd7362a7130e11784d0976ab3c4362871fcdf64ee979b7e426a631207d5ddb5954fe59dd31b6c48e2245b12f9a544ce9ecf6eb01189b39eb8af0d481ce3b848518250f288836f7b6fb03c413ad870c4e1525c89a95b9d838d461c3d6f3cbb33d860c4514c88935cb62b68d1220e422c9f49db258a389e249e746d774ac449b6bcd6692546c5e488389da9203249bbeda2620e713849ea10632ac510075ffb13a3e60aef3e17e2e896c460b2fd498853f7996dc67e1007d3291797ff6ec53b419c92068d1d4acbbbd91688b39e74e25e63e5af70a1f102111200e29834ef44aefa0569a938d8f8c349d9c578ca6c3f9cf4349d6888fdad58dae8031e278d8927afb7c407649c7433a53473b57bc037a8c699ced35efac1861e4adf2696a992b38a9648d8c8434a5231650c3bf11432b02b42f788c549ec64e9eae5ca9ca908d0e0d13d4a508496418e1c45e81e397230210416070d5b5ae1e3c4ca61492c84bce2e4597a439fb4d30fd784b8e2647192b5bb09ea40e377fcd071ad388f1e3dca2fe51d6c67a0cd208415c768f9fda73283a6be56713c13ec24a9bc64a8ed5010a28a63d2396221d5518447e34c0621a938c94deaa4ca90b279d910549ca49a90514adbea9654c8298e562657b2f1b2946d8598e2701a54065152f2bb4f434a71ecdf52debb6931d47f16428a835a926d6369d9542a1fc539c3bc9b12e2b55fa469fcd0718310511c64f6e45e927eb48ede4271f04c726a08a14cdadad8117afc40e30401f58f19f428c1f2200414a793559969bc94b4f9d3719f389f78a19542fc6f08f1c461ef7f3a8474e220dc4be6ddfeb5a9238d1f67491a69f48001092e841317b2890bd1c461f7a434b34d67e25452459f866a4230713069f1e46e6d9716a108444860f7e37d5ce264b696fb22ef4e3e499638956fd6094a49978449432a61393c10428953d834d79621474f8a2671f46f2bb14d9778526b491c4ea3284d4a9956194d244eeb3b22b5f21502091c42c8234c8823421a71caa7e97626c6a433d58c38e5e7a77a659b6fb3421671bcb1f0f59a1b5b390b51c441eb9a5816ac0c49c4f9edc4e45782ca062188388e5fbca09b44841ce2a0bcab049579258852c28710431c4e5cf6525fbb554a250929c4b96f646392f3e5ec643d0821443298bcfd8f35d438230da4730819c4d9e45d33693484929b0d11c4f14c4c66fae5a1624d0271feb92486b8f58038dbc8c99bb604fde1a02568cc5a29b8ebdb7e385e2ca9eb4b4f4a6212eec3292849e9099e7ef2a6960fa7f81573fbfa25e54deee1985dab31e60a792a09eae194d9b26e777af14d300fc7ab2ccf4a19c4c359feae3fdfaf36f63b9c6a747bd6aba2099e1d0e274fb0248af59598d5e1144eb224ddba458783ac6953b3543ae6d75dc81c8e6696d46b76094a924fe67df02442e470ca77354a9cf642e260cdcca7c9c81b0ec734a39209d972c81b4e92d29e69939a84b8e1987a254a5996f64c751b4e73b541494b925bf40d1bea2b5342c8b0f31a8e795bf1fc4aded434d5702ad9e772259fc6fc86462d42d270ee1a0b977c93f4512d1ace6ec97cfc927ca6d2af080d4ec02167387a87c9340831c3297efe29710b7933750c42ca7092e20996634c1893735c2361168490e15879bb23f5b62b7d0e19c3494ba6bc4d75928210319cda04993236facc21613859878a659f22f7230486b309e27ed5548c675e7fe12485b575d5d2ef2742bc706c73d3a9a1dd54336843ba50ce182e68896a4522840b2715475dee08117f92b62822640b27f14af95a9a3eee04f510215a3887b68ba5a27d9f5e4a164e6183a954254c501e732042b07036a157cdb4bac556c9154ef1c4592c694d56c06c5c7d368454e1e8275c304168164285b3f9eea5b8fbeaf5ffe06126042744c8144e722625a88bff91957188144efae76b4e3cdd21d58cc26962bb56598250389eb8614f496b5d51ff138e75b12a69a525c409274129215644689534e2669ce44bcaa4e60d6e41bdcc388ea65fb9cb4d5f8259b8b18cf3893d0fd5274619274157a8f1de332555361987359feb3d3525879022e3f8259f6141cf89497657b8718c9324c97f8929e98d7112e19bd2cba418673519453ff4c43857d0a5d28a8a1eaaea304e42a5d5a04f9dd875b2c20d611c84d2162669996e04e324df8b1669e222c3b57c031859afe5bac8a014861bbfb0653d2d5d99c81787b92a6d1959927d29e9455b267e6f4a15328917a7d817cc3e4bba8bcae46d30d3ebe2f47352525bd1cc44cf5c1c2cfa8736a9cda4f9122e7411731793f405c38d5b9c4e1237699297b6c5b1b35cb36d58d2bbd505c86af41968a4176ed4e2207262d2ab7aa5ba7c5a1ce4b2db995b5c1d13348b93209ba45b2d5d16070fdd26f233dfe46e6271d2272c28cda03553d0c0e254e9cf47b3bee44a525e71906dba5236caae38cc9ea4a342c9a55eb715e71162eef7f4cde9a5ac389fb624b24ebefb92e02a8ea64dce142babe2ec5ab6a9c2f7f2d34cc529fbeff3f4e3eb448d8a73e7996e93336c5ef04f81c77f9cbe0cba4d35c5d12df7f4599c5cbea3529c7449a225f92ee63c43a4388849e28e9059aebbf7519c377b9f2444c7ae942e8af5bc7c4fde7ec5580ac54949799532eef48e4983e2a0426f8d565f9c18f38983e88977eea6f14469be514684c9f89d3866f1b33eadf134c92a270c9d25df2cb5b989933425c8b4394a5241bc1b9a389d52f1e5924a76fac24c9cd24f25d352e1972f878983885219ed9509e2c6254eca4aac5765c954776209544ec7d78b97fedc2bb1f55eba7429f1963a79194d2a999b9ec4b14d287541d445124517fd5bdf24b51589a3092f2644d64a901a43220b2a8a58db3ce2944289f15062d211f8cae60d99744b1a518a71e2ae8c38eae618a5eb6e29e66f115dbaa8d6b8228e26344d6ad996dd9444b8a1b6dd4f5227441c64633a212c4dd4977fb87188e3f58553fabcc279e50d91ce2c21f456e68cc514e220aedfac92ba24fa53429c36bae5f86b133d4fe9c62050d76541566cfa5710f9494a4c5d5256e69205e21c274ddf67ef881b8038c916a3b337691e99f73f9c4ff62d1175b575d6a1226ef8a1a442aa5b6a4cc194781f8efa25779c0cd236dacf8783d67cba4a90d11e102e275e7a38fc78bd8b90e7a15ceb19dd923abfe0e1982ceda4e63af1bde21d90672666d824891dce2a226bd2c8d3a5e25c8783295355a3af4d7e3be97034fd29aae4e289b83107dcf2e81072bd762407c3bbc53c2685ed0ac5218d4bb627c867bd37e050858d2964ba70e30d8596b19464d70dc7344986d0ef8b66e9361c2b46c97cf36e73ba67c369436c7c6c721335095ac341946a8e5aa90dd26f351c3d53c9f85759921ea7e1ace963e469aa123bc91b6838e589280ba3a2f14fce70c8f816b74b34c369d46993617a92d866cb704c937425c3315c8f12b7336338885dab12de1483ed2795831b61b04c6accaf15184e59846c9cb3946fe7174ea2b7f29ac62d450dbd701edba0ecafb4bced2e2484b587b2d35c4a78b9707a936d45b3a285a7b7054e025d9a89b92fc55709450a71921b64186592e075e226c4f944f672851253d83a8863ca2564660c32bb652988e3b8c895138368740e440271dab8498c490a32350521409cf235a58650ee67a6f9c34999bad60fb877bc6c7b5af6acaa28e23f5674913e1c6350ee97ef1b64732cc287636ac8a8b122a36aaf227bc01c227a38a66c19a76be95ac2e4230f8804113c1c83122b492abc47041e3f5a08dfe30e47b3cd21b3f9f7e091180844ec7030d794468dbd5a3073071b1074a0c163c70f1e45a84325a7de61d5d99a29b55bd9fd038d347a243a980e075d5257553c71836ea547ffd0c1034d042273382669d6fda26de7099b5311399c4cd22d253c4cca7eb58550e6c388c4e13cf2e465c6f41acd461481c3c984974c2a6ede7012f3ada633ed5b12a21b4e6df24cb7a47816c4ab4c1069c329bf4766d39941a6faaf5c1444d8701c591f96bd551a54129ec81a8e172b6b6292b52a6f500d6711677165644ae2dd1349c32993f5e8a8269081c87d40040d07a5748bb0cb56e595339c2d992477ca8dcc70124b77a90c070db74a6e764997b893e1286b1964e5aac7700aba23d2d2be990a6a311cb4ba5f965182c5b14c188e6b96c48d5a99bb4b0b866387a5d1572293a0e2fdc279be6eb78493f4c2594e4ed1d393f9623ebb70b84aa2c6bbcb225c38f5492a34f6f244f75b38c8114ab6923c419d20d4c241591635714c58afd3b270d09954c5c9a612c1c2f94e8ca9e684c6093fb9c2c943efaf8d8d5c36d90a0775a69b726fa80b4a6c0a2255388cc7aee6b677fc4922428553122f6618d14e640acb865b4aedec0c6e22633a1e6242dff5a5c70f3544a4704a82aa923daacfd286760d91281cc4c693b446eaa864ba1781c2496acf5182858c99522d451079c231548cb22d93e1c4b00241c409a7cd963d5794ae1947d7d76097db664612702c037128c36042062f55f2bc26e04846009c800319e7d858e1677327efa9578e710a1a430893f5e48590391cc6381cc528e52c45cd22167239362c77a4663e4b416f3bd81207310e26ccfb575e8f29b1328c839a10934a3cd18243180717114a66ed1d255a8c2318a71e15e4c892751cc038c7659a0abaea04d5dcb01470fce254b2a77cff98679b5e0b387cd10b5ee02e7471fe8ed197334bfaf9e7020f11198b9ab3da94f45ea94dde340e5c1c2e6738b92cd9a5d8b207386e71d60aa3652e5d6775638ba32939655363093f61c6518b836be666df924eb2bcd2e218d764dfb02d7949571cb33887969039c24fe603872cce2597fe70c4e254920cfa7362f61fcb1116e7b824369709b5c9fe5203c72bce61b1a2d6237e2b47ea8a931c8daaafb5a93b4d49041cad38c9944ec5988418072b8ea384f9d3ad9ee25805fab253e356b547c0a10a4da8f5de581abb8809642082071ca938e91d25649a20f5649364075b5903b1800315aea7c9bbefd6bce79ee20e0b8729985917cb96e1a2668c29c5f24b7a8e6c6795fc004729ce97af59ef43a4388bd878235cbb64b39975f4c0318ae3c7096ba5ac94030e511cef4ebe979712b3d208041ca13849b2b69ff02e977e3c21a871060f1fe5e000c5412969b2666c75cb11abf13dd43854018e4f1ccdca52deb7a4752b9e38c8aa74ea5b9a4bd4edc44925114a46b7a4293b278e7dcabdad36fffe6b9b3888d520ff54bc7068e21d93549032e2e467164726d6c08189a35b92934f90318e4b1c9416f13fd919a2250ab66bb298cc14324615382ae1e89cb1ec7e6f33234aa052e692f973ac679f84b16bda53f45b734974b2f56dca76a354ef48289f6931cc2da6b9c6c8b6c8fc97523942c566c001899396ba69085df966ed2be078c461b44ead5acaf113f51d6c6a9c81b0c70b72e450e30c1c8e38684d1b4ed8b0740147238e6725282d9ac4e0ef390e469c754c3cd133f469fd1cc7228e515d93542797e050c429ac858ca6fb557349220e27ef82ce3b8b972c88f0c5e7d2d2cbc9210e16e3859aec19e2144fede54cfad1122dc439ac5f94699e4b6612e298e44ba2587ad5ee3119c449d8c89c9855335c5c10e74d11b950b1e25707e25429e6de62a9eeb93c204eaa16f3aff32a7af487f3a9aa10ef1b3f1c74bd6d5b4c497d3889511bb6a7d382b5890f2769ee32fee5d21e4e3a379a2abf5cf55d0fc77b4d72c8b3d03a2ecac341a779a91855576d447838a5578a66262e967be80ea7a0b2c8523adb525cdd0e073321aa794c0e994eafc331ff5628990e27b979ddce7594a899c331e754e99bfd1b3991c3299c8f86b13e0e0795595d96497038283d963567671c697ac3b174e8506b72bbe1ec6373277b32873ebb0da7e0673195246c361cf48e6aeaf034a1d4351c6b4eba35edd56052c3b9bbe4d37092f256060dc72ba1c413a4c89ce16c6a539adc0a1a4b12331c74c52035266d9754a90c279791b5a70491e1d4794ac77d3386835c52cbd418b2f22b86635c573749d44a188ebf9fa643ac0286e3499bad62859aa8a42f1ca3899d2788bca920f7c2f94ad6c414bd25a9982e1ceebf82a9e63d193217ce769aaad452f7eacb5b3898122ef86c689d5fac8593578889d699f5e767e1248abad86fa34ac935168ee6ab267e345b597d8593a6b0146a549c6e8a158ee182a8c952ab70504ad4731d0b7e9951e17841d46543b55338e539154b2821299ccb728a5a51711d321a8593eaf0359954f7c753281c4ff6fe09c713d97549b0186e827038e118622a8e89106167330e62b132496df7d7bcb5642ab98c63ae0cbbb6d9d43b2ae3a02789f252e2cd7e978c936c96c49f51958490713a192b999284f31847b558ba5f4f8c7110231ae635a918a7fd0d594b92c438092ac5fb8db7ad56e2308ee3e6f5b5959a5943611c378e2829c6545ab485c1385ada7e537273c038093f55dbcb2c4288ef17c7cbd17d620cd252f5e68b83c6a960d274ebc5612e25b5ae295892b4c68bb3dadc5f5690eb8dd92ece1596a4f49d41a513d2c551cd92d4d99897b7e7e224be44662fb964181d172761b6512df785c8cd2d0e9b6410a9b52d8ef9c495df121b25965a1c444b6951e9ed25856971d092e76fe555c82cce166f622f9c78a394a42cce5ac28f68f7d0b80dfa1e69342842071a58e3401607f0ea4032005239cce6300ba01f3ed6e85180001cc139a00000fcf0b1860c0810800742af0104345a080810808f1e8fd64869f45023012d5800004018903e78a8d10a08c01d00e0c387e7208000fa47a71c050840ffe894468f1c0700c00022908034d4d01104d43f7ce8d0c1000004c00107401e3f84b0c347ebd091800b60e1c7fb38e3cf30c005ae803a1070012b208f1f44e091aa803c7ea434ce5843878e045c800ac74b2163a67409a93bdac1e56c8db333be870e22f0483ad2238187109a077a357c18fb81068f185c600ac8e3478f1d3e84a04347022e2085539bda741394380a2753b96b631cb10f281c468f6a8b92f7024f38aac5c95b424d94bd67fc19e8030d1e3f7abc1078a871461a482fe00435ce68c613218d33cef8330e30c28c23a881cef8330c30b28cf3599237c83fd15bc552ca38295992e810daa41333cbce48329a53b52134593d820c63499b1a4cf26fe4182749bc7c53c94fe4dc74061ae3e4b723dc4245137b5fc3878560a4187fa9a826f44d4e0723c43826e97c94c64d7b49ba0423c338254b42fec6f477b07107468471cc2822f3a6294b8291609cbc73fe92b47c0418bcc99282925f2061c417d78be345d945d185a69643dc2a7845ad70154d93e0a356ddc1f641681ae4c8e1632417a7f0e732369a293b275c9c35fdb96792a40bd19b2b8cdce27049a48596a4b55e94d8e2943143a8b2cabf39ee5a1ced62480dea848573778416c728a3dc372593599cc4c4870e19234c877c4416567be6ec96b7aca6924ca2f2865871241647bbd8132a4971589c4afe6412c564c97879f38491575ce92b4feedf1566b70c974d8325d19f495f462dd5df482b0ecab46a4c3f62569cad2b498d954d266657713eb9fd34091aebde62551ce5a2574cff5e2a4e19a39dfdc9aa70d951711eef0d4269d3dfdb71e41407f5e626e755ce4c719a13648e4a316fd23d1a29c5498b4c7b617d448a536f8d98cb63a95ddb0dc3c828ce21a36b2daa6d650f45717013d34a214accca2423a14050e0278e5f366266369f92d49e301f8c74e26417f7d449d30c5a5c20f4f816e4c85186114e9c4c8e7d1119d7e7a26e8235c19938563563a6246e260833b194b6689c51e30c84239838c55149ed0515c4d7682397389d9462a8eb16592215632a2969d7e3871a3318a9c40825768d29362b55457e3cde92faa8ac98e446267152415b5a6ed17d33422392b01c915848dc232e8723ce99fad71a7334c5988d386c94d5fc12d72c6cc48863102a4fc5daa9f5928d2c624411074b79ecd2eb6e92351f4984e5c0334610b18738ee284dc2095f19347f65c41096a31096a3072384389c4c31364bee18a5c9914164e16515b2d1cc5d2e65f3367dd29ca8ffd11a2382388db613ba454e3be3378a91401c9467ca2f4a288d00e2a4d5c6b2e838414d9047fe704a4a7e4d568efc70be285fc2696c04b089913e9ce2969c78b2d12b57ce87537e1345359852997233b287e3d86ed8c5972b37570f07e55f9b4a9a685cb9c7ffe0a1238f0a23c933d659c2a492c183997e7349d0241bb9c3417b6de46b9ad51fcb881dce96dbbfaa36dd557247ea70dc98bd4d6496934d49a2c341b3bc45c4d2642ba14d46e670fe1346e39ed8a3f3257238584c7a2d67747f240ea77d39bfca79b99b2b70386b28cd5515c491379c379895ae287b19dd8fb8216526a5788e1cc66031d2066583e21ace7a82c924b5fda693ff881ace714ad227a2c33cd47b3792064bcc96ee46d0702e9fabcd4a97ce76bd1b39c329c4fae4de82cc704a1b57af55db90aeb91b29c3f1dc52b6855372578511329c2be6b7242931b174e9ddc8188e7f27b625f17594246775810e74c460440c960307236138968f09ba1e722b2b6a1801c341989a3329ca5d1e8bdec817ce6ba574f32c5cbe24bd112f1c4dae242c4ddab00b27419e243b671b8e70c172f8c8168e9a35a83cf2bcf2e983879e60440be89ff59f159dde4816ced921ff820cbef35b8f60e1934bde2b71ebc21bb9c2b14a2eb778a9845e0ef146acb08aa5b0164d2936755985ec26e9924c4ba1912a9c56d3ffd4c7bc1ba1c231c653da729a8e4ce1d4a2b7d6479876b0e938428f1f66a5c7ffe0913c228563125fe29a92342351388fc5bac5c82bdfde60300205b6c22c7a6b702fd7245fd2cfe6c959a61a11479e6039f407234e38db5d78695026b20499669cd245f1dcd933e1c463c6494caad76b6fbcd282cb38495eba74a70916d69e204494710aa7e9a4b560569934c938bb880a23e3249772ebcd93e4da8dc8314e1a32c6b2d2e5ce1807551b7d93af29068a10e3a0195452529dc338ad7b256d5f6e264626228c739d60a556a27f9c7407e3a4d7bb84f014b9c1048cf3c5fd51fa549bbc96ffe224992ae1ba2bfc76d822bec834e5c2bc5b65ca15b38af4e22486cab1ee90aa27262f8e493e4105a13cdbddd75d9cdcca45cd3adbfe3a115d9c4384667271123351c5a28a8b53583d917ba25a3aff16e72c8d3b4a124a115b1c7d4bce39cd9d9b24ff4ea41687bd18fde65e448b93e64db6a2c4ed9d379dc82c4ee1ad4bfc4a5913b764d11091c551e349923eb541a7c36e0822b138e5fd69cd93f4d659cc89c0e274e94ddc85e60cd13b1a3c567022af38acaee8171f3b65620a9e882bce1bcf7c439fee08ffbd20d28ac3a6680ca373f75495445871f211f93931a649a7e98aace2a451f4a8c9505aab6d551ca4ddca6c16a1352e1649c549f6339d87590511541c93b89aef5af2c5246191531cb3c50b994be92f6a128b98e2dcb9be55bd6922528a53f69224559924697e4d28428a63bedfa429be8922ef1219458ae2a0f3fc52358985c20a726390d5bd81223f519a149395acc53090c1a18978e22c9aa34995974c5942914e1c3c844a27bb8c12cf2e3bd84a084438710ca3b552066d2132fb4110d9c43953c58c37caeb63fd54ce87d5404413e73d5116379dc69dddcd66e264a3acfd3de6bce2426f0334114c9cf3bcd452f458e412c7a4614c88788925ac6d89f3ed2571b9f46457bf3bd8d29755254ea6eaca526349841247dbddd50d91a9621627714a396de7d2b2ae76c40511499cc29c9da951276810894492be24a3badc9b6c079b11814455494aa6d3e411279175828c96ae885372c471cd2aed7cfc89d66d238ede7fa962b992b5c78cb0bcba3456caee54d718ddd2c975c9cfe438a9c8228e6e6a4379c6c644147192bfc67d9492d5e48c26e294349334bb1743c441cec6d7b4ea6ec29b6041e4100761ba524fbe93d1845ec41027038b2ba8a4ef05737924108903e27028853140343d00e312082020342a0dc522d1803c5385790714000355241c3e363228281a161618188c86e26030100a0404a16040100604c281288e04743088f203cf8c779d834ae1721ee77f3fd2d5f14fdc00d92e08021d97314bb3fc91a3b1a5ccaefe7ba048bccfe9b43998f07fed6f1caa3f28cf44c84500eceb38dd2252dab9a6d5e9ce99de21a459dcc644e2e9ac6c1b2944c1b3155a65b883533c763d39016a974669cc21c9581f86075e196db5d2223d404cd8965bc83ab97cbd70073fdc1b97640760c9959c2c19e069931653329b033ed1d5bc5b60231b818dc6fb86d2d517207e771cddb9db2b4795ee3c76407cfebea84cad6d0a5229973239d923a86f91300df4a978a2b5b59e5e59a9009ee82214c862e062bbeeb00b443ef9c402f6b2503b3842ad88d6896732b8270b753c423826d31653adb55fe0b0be7df214170504780cbf9d48cca96a7013ad2d97192a2b10f147bdea1329b9790612518d612bc8ec8e8ad65f25477f3475df23ee5432dd00e715179b59838f0d89cdf3f15a9b6b06f66dc86899081c2eb17b82d4b3ca83759da93225461440ed6e42251925330e30e4720e2476448d2ebb76ede2ff1f0350584319edc40c73e23fe24af0e93eac6362162498434d4661a25aa4635887a1ddb507aa810c0b22841fb787a92d4359a5a11e3c5ecfc5cc6cc2849767d5fee25ebc31930bf9cd1cb9705fd27debbd825c17e06a4979aa2dd7a0e3d69f5733b23ddafe8d031cdd064dc431e80d40fc96e26032ff0c9ba67c25dc0b06cbe8417350366f6a32e1276b0d0c774b79898b2081665bc0dcd48840348677869011495a4f26dbc616d7e914a2686a9180852d40cae3292475f378531b70833096445b78a8cef9f0f7f7a2f9d2cc52a084651d06942d3b23438ef7722189bf3e3906e46da0578314a0f6cf4c4dcbfd60d471733c84cdd8bf62bbe56c7d85018c034dbd118770d7a37ae646d831961f20774e1c53f9b37a3bc5f5026b4c27962dbaed7044efe88b3c9bfcbfb410ecfcdd99d63f701164d3d6dbe83cf3bcd23c449099076ebf180b61e01e799bd1678b9631db8d29e5b92de47064d6ee4288328134ae5856afa5436ad35f214b3bf0374ebb1ed4360834ca634f64b7e7e1b05333d1bdac2d3da3584d589d068ae81ccb6148fd1bc46c4d80acc93e62c755672c36266761e385fd1b04c7266c31d8e472f31902a163b0fabdf08761876724996c476d7629a1d9ce68ae27b1979324936d34e1d88118701e2ae9597b2e0c3964b2556fc643b99fdce4c4368dedf5060f57863039fe0ef70269f22b3867ed1f059db0a690cbfbd32bd5b41c6921828365f794d0755d8c41a6e0dca911ede2159c6b7ec436a7711a123dc0dae766552e82efa3ac526d76d284d82d6261243bfd7100c32adcbca986643afab9849e5cc216a39f6b7bf99f5f991a09c3cfc2d3105da5c710bda99e8ac7b16c734057457766e80d650eb53fe1f4e9be0787a44005fbbd9f5cfa5909412935db3c6ac47b8f5a267477bbf645c9167504199b028c917caf7c898b39d54daa062ec1ee23536c33fcdde9c04cff304a1eb2473617bf46fb1264e7bc0b57f85493636becaa4c7102e69cce40a814c08e89d5fe91d4d76934db911867fbaf1b3509ff42ea1420efd19c9feee40a16ba79340484907a93214f8bd3ea5d70be63f0a21d5964a1a61d14d6f61ef801354080cd56d3daeff5ee50bb52f060bad6a03d7b50035d8f4d64cf1777956b0fac1613b0d268d03c7ce853c2d26563c0839809d40a08e55408dde0f6afa339a86fd02c1708f090e77b0562ecf5c349573c48016210dc19319d4471a0c4d360471d9b873920e85845bd53199a866879ec3fec33c86be8ec0d491f56e11efed8e442c78948f0c57981082ebc1071aa05a6cc3487bdcedf6a5ed34428c8244d9c7fa299dfaae4a40ab10ec5d5869e6811826cb48377f83306048726b00e37818338f2737d05f00dc2048eb709af7ce3a7ca149f011e1101cb5f84552aa337ef2c00bf2cfc1ff30e5916505328faeb1bac00da791f6d45782b934eeae6e656749639364090220dc166c64c35502fdc8a8c1eab561f2c7038b4a37f73482742afac8a1f1840dffb2d8833a3bbbaf5fb2bf0706117406c44877daa0e4c33fdd4a91e4aded59b9d522fabec09f98d973f7644d8aa510d77c7345cbfc52cdc5edd2d511e05caa96ceed2f1b897ddbeee1c3e3b16614975cd2d1d8e1f34809d7802c99bedd6f8aa29f5d1933fb7b20ccefa611c04907d06b3de9e47be3390f0782ee062593e4d19b20e5e7ea057eef8387e6a40b83750c77c6e02e1639dc9ff7cfdf6b2a527f2db0d296ee92514aeef05efb5464fa66f0a50e2c7b5ad5dc782b1e2112dcc885ccfff8e5cf6cf555298b103c8e5776438690c0e33a4779d9160862879382b705897bf9b616eee582804aac8c39691de76ae1c7d7a8c9a6b93c74402b8dc39183f8dab6b75bd663a84d442b350463a71e858995e20496dff0341674738306b187b8883bb50797f492d9ccde67efb4e12440bbf587fca25213e4bd4bda4c005223150846d5852ee0cd2410dab36ed7347d2c110cd29492a280c4d2b6bfcc3b5e7c74bf03a7f55ccf98ea8bc5f4402a3813c12ad829fe53894b6504770e64e626ae79cfa9643c4bdd22064d11b3310d61f9ced9d45debd20ba56f3adf7ddd5ff3f93f43251d9786aa761cdfa936e2a8c0de8b220ac563ed03b85a9faa58d697fc1173e1e7ef06ab8371ce5515f3871962e3c44a2ae861de3193e0330c8b390b20c362c410c4cf5d8a7f817483d0ca52f367221fa4d433a2e6bc57d9c428b6ac76fbb04bd4850364178a38332e2825f8dfcc095e7aff4ad3394b562b4fb6536f4ab71978aab5e1f59108ce174892222e1194044c75ea78d085ac00cfad4b7d65de8db396e808b026575565464c6bfbea09e2fb4e5270178c056e3056aca3ad3b6aadc6a69f3ad5079c3859a0342a1107f004a71c2b91364028515268bb019beb7bb9c5000c484885f912435fce17a9708a38efb96ba2b691635e7fb813915c0bab2097b3b30ac784cd24f24edf2ee75c63580d72e1ecd094d09cedff01a95533db67e4e3f38a32b1e6e3eda4da6907fa6fef6c344b67fd0e0a37062367ef3dd68cfabce072005e4be5792726295b7419bbd3972890967a786b991da7e98a13021a02159439197518783b5a205ab10121dcb19adbb0a35c02d789fd61e8a9ead63b34fcf1e22f57a7a68489cba4eecc746fe2af6bda19cd5101b4126ac40e220100eacef77b8cbdbe8085b7e504b946d7076247b3f2146d9a60e190aafc1491ef3926e8c4caee66de335d40fb6e489485e763deff958a172e13967f376c46854dd9dc710d60fda4de9a3eac89c12d0598978de943fd9649dcd0184eba12ad507c9f57469956659099a60a3184e0e368bfdba7b5ddf687d417bab008b9b91b0b8ce572ff47d2969dc5a81ad56d0d62d686bbd990eabbe945fcaaf7bc95fd00cd65594aa3ceb41a6edf0cee87041bf2dbc33668480f3c42b3e11bccf2855a1bdcd42704b8239eff863b4d650867758a6b77c2058c0724e69b82c7e3d76c1008905dce58e6164e39e4d71092c24f9be64e5019bafd88a98a528bf834b17c96f78c8af6011486e94b7405b278af5b06ae15327ccfd78519a9be7bef98152465425d94a271938b0e0ebe5f9de11d0be05752a299f40fc2968b12509bfc88cd6e34b78a38c5f126c96c5bd882d4b373b17b10cd94b66bc2123f633274fd880eb25939e0792442f58836816a45c9d266b008e7e430171f6785f8d0599ff825382706cdb647548ec46a49e454ea979c144ed8309096313c9d1fb9778d2877f64a5e13a31318a0b27ac293033aa6764a0a8bf9124ba902b93fce1eaadffe14efd47c7612a34b9c32ab56d29bcca6c34f420eaf65b9401b5f3bc43b1481d0fc8c2a0fa48914a6e92c8f11130a22a63e2bc217857a4bb4d93b223ba345a763a257501b8b5053dd45888ca1d22dcedcdfa163fbed16e946f728c29946476f513cefdaee22c6edab5c20c8e3a6f49f92ffecf1de03b494474ebec473518a9745340d170f18a3c877e622d7433f3ea9c47a6bad245be8bb3d888c690fa299dc9fbf922537e913d260dc79b6df129a96b23dcdb20a13f1816109e021b761ae1417a477658fd027c032a247f462f6dda07b503d218b38e50d1239284ea2202e4f4256a9c238c511fcd013097dec534b61d550ef4e0facbdfa5c8c42985e9d745292313612f3e2d0962f5d86544fcf119393c4dcb342785b9ffd51359026497446710fca243c30df69b200593c1c1f72ce1dfbca73f2d25dd919e7b604636b131c2efddda9088bffcf58b391fa6fc447d5f0b230d830e186eb99b03062adea1d1f402e02820d8a2ea2305fe6350346484cbf22ec20d21d65b0d41f187ffe12bd4a9342f315cda7d9f8bf5a1d0e4b2ff8a0a430a817bbd1c2b5232537f120dbde7030d167bb5356dc01aede8d5b342377211250d2995109f4a80b1acbb72a097e99a56bb21b9b09ec9224acb1237178c50dbed8b3e5961e9f6c281bdbefe65ecac747889d1d371094cd19784b2b8a08e20f418158ef720249967c72c5d3a06ee646edb619da59d95b907f8e283bdebd533e690fac812a42aa860e480881c79d481145434820ca039a0173f85de3e3c15de8302e2d814ac6f697d2f01b48ff5d9798ae2a582542d4ad0061aaf9049e316dc3019826588e67fc05a208c41b940a221fc8afeb5a30c12d63e8daec513a35084fb9ece2abf0f18fc5ee20478cbfae0a1fed5fe1745a7ab3b59b8bff5ce1755dde182671750a5006592888d401c2b8a0c8cc59439bf638e986af8a778cd0b6aa0ce67024e911002d1c61d97b1cd704026d22e857293488c311b783f1a8a2ba6151f8a42f96b233c381e221ebe00dca5ec7115bbd5a8618c46caa3e280c194fb6d232521c9342327a929890192c25b473bfad8a75e5e0f9e0fb3798593abd17456ac8229ed5e2b69a31721e8d29c4692a8153ddf49c77bb7b2dcda24cb8884049b6224617b3db35154f799caa62934ea1fed3abec89107581d79996f9158e1a5620b7595b06b2854b0192e505840a06258aba39616ceee9e9226c418be5524a38484edce27f3352a3e2f6d9a02fbc194ccdd4586a48a53234fe74fd19e49d0dd9e20b28c34df2e54043f013f919f9fc693fab8fc7ffc7ffcbff86f917ebc2052d9896453a2139816766199e6e3291b3b96f24391bc75f39e4b93c83ef7da15bd53596f52d83b0b9c2101898e9b0fc95e51b90ab52d8f683429c8aeda00d314fe6856fe3eee434284df6f804d40e252ce26ac275e5b57253a929a899ead10cee5a282488465f007faebfbd3fb61f31edb88eacb259fd26c0a45dc1ab4e1912d56988c6623693132831dc72c18d4871544909cbff27ceb678dbb82d83ec6e227d70506fa969d03d68c33f71f61869e1f99cc20449fc387212dea988a199723ef3924a69b4036bc611071a63a4535084ec7d5c0225e7276d1ad95825c75e1d0938996f4624261fd8fc4a36736970b6523f0c8989634e216c41c461365181f8a6f048e97a20c21fc910109d62814959363dadfd5d7dd531b297b7e7e5cbcfeb3e4d6f33cc7a9a8685b369600a58b66449c68dacbb459cac6b287c5be5449f12e2e2dcd609fc4d40cc2ba4e1434ae09c5bbb2b85c8d3fd3be89e4ca4668147212364c03bd8a1533f46f81166fdfb5be75a50bc2ffb8a0aa8498c90c2ff4c57e3ee3f036d23542d135eb204a20c02401c1eaa100eacef9194bb149e11ac81944ed02e2034c9baaf01f4de8c574e8511103875d82e9c2f811313a84445ec453edc458a8cf1873e9a1abc5230221189c7cba1c6cb15d33d29c29fcd52a3d3f71b5664438b9def0805cff5852ee07ce027c820033d2c54f77744c9585554b69b387bec29822b2ee39b35255769b85a8443abcdfdd0bb3f03b98466f595347e7fc5224a1b5b988f2550d2784d4d1d3a07968bd665d564fc7ec41f7995851f484f53d61ffdb0518050662e06b57816544054aa101b5aea27d8e05dc260d90d2b2a1f0cfbe2903f86385c1f173e3c3d9d0ead074cf6c146573326e267d1bc31528be1279a531a41545ce8fa505b403b1c45f3af3fe4e1d780f8f927d217d983314d1afc0e4b75bae573ff09ddd6e89563f4bfabdeb8ba86f94beddf561717f09593a5b401ef67bd54f12fd1483fe8aaa30cda6d3af702e155855f4dab12e110763fec3cc36bdc900ea6a5b350a6254ea39dcff6798fe72380eeb067887cb702847060404af53ad5c75c168e7082370fc6f2a0c79a4918c4ac7f41b6933b83d2a0c126c0dc281f7c2ab645cd463651d73add99fe15d6099f5afacee97c73fe991be6c558e1393dc7b7f0002fb270595637a3234aa204cbe671c47ead0ffea2e604656fe3146ae0caf0005a814b96f04baad997bb3f1784097095de8af8233b93f5eacfaceb4e984c44c24c4f4ff3c90d2cccea4499c0bf4222d188b15e06c8d393c6756cb235909a22d7dad8664a29eb14f5489b8a20a3f7f5025b934b62d04c0e40e784b0fb294e87747ade3759c73d7721744d556f298f6fb68a85c909ed3e2d0ce9663ed58681ffff86dcfa95ea01162f1be321c1489400bfff6eacbd2f1a3808d45774d88f1d2536260cf8d78bf007743830c1882f46b2830d46cd1b083c45ec64c005882eb2ff69af6c13bf3c2ea20d83bad2c5029fe8d140b977bee3a43f84b5708f799fe2b81d2734febedeba3a934825d58bf51322e1212fa1b25c17c99f9197e39d252b9ff2a612f5c665edc479353c006281f609f308ee17cc66d849acf95361547bc021d9b6012a5e244695c2517f6d265cd1e0b0e970be2240824b51e73ec85367fc0e6427fb829f273bf6109a63fc89c2b258d1c435834ce5cc632c36071acd092642397b0edcc102ddcd512f5240013b253c561062073983f30f4c190a7b4bbc677f7a87ac67e6db98fce802266f3e00d5fb798660cf83ddf07be3fe2d429e701669814da1a1e151f947d7e83b3d76ba30ae4c9174ed37f114a240cbc47a132d5bc900b0107e099c1f08ddba738a1ea53a10445b8079086cc2f920ccedd4a432dac9d492d47922d9051fef8ff09c3ac21359a90a115db3afccba8bd7462efea01ced9d52f2c20e96154a03f1a671dd65ac58d586084aa51b96d34cef881dd1235da9e916846dc69b4ab7e5c27769dba84059a8ff03dd54fa6c9f8e9a5b81b590f8c5e197587d7a805d78c0c6f8475b40949fa8d5e0873e4b749bb2a4db13b1a35a5747bb2ec2dd90da2c236da9d049e4a4a6254cd62e65c7164a35ad468a7ab10dff8c58505388688a00e1054ad5ca50647c5c7dc5c9b5eb5231cf3a87a54d62162fe883495149eb506aa076149b2f2d9781514fbb31a55f0ba69aa184a4e53abc7b84059c1e7ec0b5f818ccca2527d46a26f07091d4a8d040852e2ae7ac2c37cc6fb30fe0c783cb290d2ac9009cfdf290e05e45be214c7d31822daf057d65cd2118306bcfe0b9845805891d05b7b084e9c64c15ad31cda52017929bbaca6f503a349abe13b0aa846f26f44a1986e0c5c0476b6412554d583972c5519d3575ad058b24b9a1f151544c6c2824bd5aeaa32a600a9aa0d71384853d6a99e7ec320196ca8e412277c9ded81618a525209e5a77c604639c229c2b0572467f956ed5d80023781a1a17ab1a119c7b30c0f58449af65328d476965ba4a135a80036b10e904e06b5442470e09862128e9a88eaf66a8c77cdb7472d20f1c0fe019423a9c280aa0c056a50c05e42f5835ed72d9c229e71e8bc5f8c733d685352d46a0d18b83030ca5b4a28604d761491d45f8f78a180d6849bd9714b011940a0a8e71ee51aef8f02011c8859e29e420f282403d5532cd49240f98039163899a85540da8e0d5c430ae8e7453d5031752031c400002829ba0b1a156c8345286f8202c29b40803af6480b0bbe319bc0bfb38ad55785a40ae60b7c15db2dc7e7447ca2dbec1d0aecb57d394aba805db7cb398de115988f5e597dcfb76adc1ee4185a25f9924d08c81727337776869c0208e8ce89fe5ba2a896009cfcc9ae108b833d30dec727ed8d40b116a3ccbaa10ff462f43cc6dcb6cec508714e3345d8079a2f5f78e96d74c448c4f9cf6a3708ab1cf7edc847cc2eeda18266a495c094ffe403dd159f00db739f00ad65cd97a3861ff574e18375bcf65b51da8ca2e217b8d323831e133fbdd54ff0c0b3a56d4b80c0f12c46bdc100a08c211e190bd42aac3457589a287d26243a076e8522427c3f55c0b2888670eada815bbfaee6b52beed54b8f6481a249cd15c29ebf1209dba244d7c3a4680aef44c48190fb4c91b6645391ae35389eb2c85ed340f7d189e2aa77f1157d431ad35ff2e4e6616f65f73770f3d847e1433dd8a99b716a377136536560f3f53b430f4f245773760f631a3862b328ef38b3374832594db2334edfd1b25ed278d25d533ae0ac705d4a26989b2c9a7e7a58ed6a82e3748619ec157c127bbec8a09a8d426b03d075482177bb7ecc0f4c0f862f2493be32ada08716faf41a4f91a41b75e3c9d4986aba64f0b71f82cc389f2d2d0cfb792366fff00e6bc3e6abc1b6fd5931b1c74ea2d06e4c6dec93be404e77f8004b5512aa208ac92491ed001f44544a6370cabf76fd65a94b52e0e55089b332327af8a8956f3b86465e5f2aba524ffd32e4f15f69a2900798fc20e7a747f04f0dc7e88740638e30094cd5c19c56487a739ea0f09a42e7c817302bbaf540a87d42631a408fbe6303ff690039034eb3a1094c69a454773ba1d3d5b7247bf2966ec0ba17f741226f5b93720acd37ae97d7a907e0d5325e12dbda5e212574976d48f4264cac091f371f2dc5a0ea6ae6c31be805f36808d50884ce6b88a2419374d5cdb1f71c9664132218e5a82047d0067ae641a121ab122a88d12410133d958da615cbf154698a6c38c3876d5e1d3986d0c24182e4ff56f6253108ea4223fe7ceea601089819703b52ccc19a5a3b0e7ab0956c3ce7e5a25075938343b0b71514505b3a4a050e3d3aee30b62ac7bcf86e2162e8d1aa98090f49f9510d75727dc46afee0230df60bff57bfe649850f4f01734bc5c6186688be9b614ca2e22e0b168f33ba89439ed30dc2093560da2c0283857ed6effdb3dbc763789d328d3c315139ee6aac0101bafccf57219b84365c37219409deb8189e1809feac8b76bc0b02531284503b1693a5ecbca7b35da0a87459cb758c10f06ee47587618918567c92007ff1caf8a0ad655f40a933d1c7954fac79ae86229d8d484d3e999c125e43a9bb374a3318629d2f589790de7d1d2f670e6c03f74bbb610006a0fc82983976f494bc95e5ab9196d251e205bfc107d9a71543c92fe5a7ab67a45bf05844ef5f9177ea76d0ea350ed05e736849c26b48ecb058228b0f87084e991534f264430f06c7dbdb98a22b351694f7e915984aba1f9b728d34c2ef78c37c816f008fd07f9502a9d4155536e24fa8a7ed8dbba2bf5b7c07604d3abb7dbbdd93ed6a22badc06c2ce4a275c3ea6610bc0647586f8e0946ead6c5bde5920ee121737aac9dbb326af83f1860cda9a2428cbe78a32bdee0d62ee3e3dac0e7a3c0325558b5180a68d0003aa3e32d3af499d6e69646f9f5ed9d3fc5aaf689ba879df5009c37fe35e4da2efde8c0a1393fb94c7b3056a11020d79a6ebd35a51121eef51cf82a15d3c2c5a4b42affb8aa32c938d08c2f4c78b8c34b83bce504da2e1c7c963ffaa9b5de356d01ce221a22f664bb445e26c5396722fe5a4feab985055b27c6e1677330adafa369308cc3c7a387627115e9980d8a695b3bf5637f886041f0a664fbc4fcb63c366f671f00ebd1734d18c0cee8350598a58b3da35685d3a40526b7a2dada859a4e9f6ff1edb132a9628e42227f48d013ca58550d606668764cb2e124901d2631fc7440358b59d987d841b10eb332fe91c45b54b9e2342bb785753f013258d5c01a01954657f6f0abb6d69dc14157eef931b3f1ccc6f84b0524581a44038d16a98efcbb18ddd0798b5eec576c9f073da5c19e77d08b9ec1582cad4da7c00355116858515f59689e9ce6940a69487450781643c97d89f9ae4c07def6827dce69bbfcfdbe1503371840e36ecea0d9bbe9cb98ad10e2d3c8ee46fb15f478a0663158b5124448a7169773d7aaf3571e00ffc4b45f58b646c31591db14090b170a88e5d23577f535d54100a002e408b11957aeb30cfe28f5835a08916808cc566728780d22f7ba80d6d46ed2ab26d704c28e41483ede85c3d8a7305975b1b3f4c5847606e1301b7deb2f2adedb8b63222ae5cc198ba5817d61cc0a5955c231566d3dfd426a72540a1d61daea3e5261218477252042b79a3d0d715e12091d45abe7aec1886a4403a3888828a3c9191d421f2758190779c36ae1b41b3506be23995db876dbb74012a6a567dade920362313dacea74229595a77f272236ef94a40d63cdcb61de6f5b1e910ba4d62733e6c2a4bd35394cf560913979d194a1fe26478932a5bb9a6330f5b5462f7245e1251693ce66977299216652b1a24a018568b23925cd1a92ae79ab50bb657244f3625de90f8f7a97820c8745ce44bbd997528d78281d31596e640637a17dc61156b80eee939221fb8e256632fdeda7c3c340d5ec39c2807b382e1f1cc127bfdf8e75a8c405daa261619c0e34bf0b5d62d4dd44b98cada201d4dc29a856337cd782d9561d7d78b663a428becd2c6af28681a975d49fcc7bf5ec2126ad984d115c010bd55196b4d0bec22f4e867636ad345a70a94e3699a3554104523a300834262e828a1da66b19d89924cc3b87aacd9365f53d57f1b2acd52a2036237c076793e2fe7aa22c9d8af29322466d696278a259b464b83767dac568df45ac44b6c6618ba21e5c913928d8e230f26068e875a8873b7e0df72241eefa1c8bcad0888ffb8a877e391bfd5115bfa20077c2c5bbe265e72c09876cea2372ccec53b0b653830bb72290cc2d6b393608243eec5b5edb2c229c9305bd017a2aafc3fa9f4a684f8e5933619865847160da53774dfbe33702f2bf915dd59515637363859abf7c2fe2d20fdec3849dea3c7d28989d99e76356857f620c8e8dca43f6d5f9da61e4ebe7df14f7e15a1631e81458a853118a879eeed6b811364d6b7af68dacc15c581d924c2f83034c79c677a08744d4524634e692dcbc6f88b5be13fcd0b955b86a1d82c2afeb63ad876917128181c6cbb521b1715f9eb05ab88ab18022f387d905b4a5203061d3ed1d3a9af029e0ee468257788a661bc45e84029f529cd1b3c45615988e63212c153a2ca02a59824343bfa43aa4a89600ddba21aef9c21ec3cdcc270910684765862542c607d8172f947580eb17ced4a5bd2188aac9d3005f05e936a464bbb6abbf7b894c90ad6ff3d4927f2d8e972d935779bd654d801900c668dcc0453688c9cb4a99ebc47351cdeed77b9f044e749d6d450d01ca7ed8c9d76259e0cf381120e5fc5ac7e5a30fc02d4eb90411fda38d3ea8cc2c91070b7c6b03fabacdfa6faf4858552317266c7be040b60890b3bcc6e7d083ce7f33f2180d16528be26d9243c4998ff66df42eb1efac1a459f07c1fd802560ed19200b11c80092b0016b64333f167ffe88451f9938397994aad8d5ba2c339408c586f5ae914a1c0c926e69b132bc159f93d8a1543b886a0d48950e3035521f923dc25081c536123381404675ff491a315b88260c8081c37c021f5d08402f7530c94ab93b266083ab6c2cace8ecf25f3dab975b6cc58554f86602053eaa6b88bb9a4826aee30b7486cbfc500cd97b2e803c7ccd6a1edb206cae845a09651d7abf4a0a5141a124b3f7cf489840c7c565c0f6d18aa9626cadaac9e1034152c37c8c4d004d7659b2bd5418138d35c519c91dda8bab5c360d96f78d6a6c275f283887e0e9fb92e812abda5225be7c457008011ca5f51678b06c84071728af46dc5c65c82691d186b5565" +expected: + availabilityCores: "0x0c0001e8030000009652f3009c52f30001e803000000440000000200000058b29da15fc66c19ef0ad4e66e95c8e42718d2455001ec6876f4173489a705fbe8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b51453370001e9030000009652f3009c52f30001e90300000044000000000000000571a387d344bef8a77d3a99895958644dee97345861f7747b1cb8186a14089ee9030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083c2d011b32106cc9030d8d22770c632b611c613e4ed351c149ba59e7d61cd6d0055e865b40a8dcf60422987c985c6022231106e0a4556c0270ffcb99e656dd7be25c5eb2a93cbada931620b5bceebe187bf77aa6dde41a6cbb09f0cbbf291faa5a33ec5ebf7fa730c95c84650a68d432ed53fc77b6d8bff3ce50f19d48317c30aca695f1737408d7dd47052c3b3016128ed3d6ac5b3b46be0cc9537919b9f594fa704bc2e304492c6e835db7f2bfe17d5da9102834f5be31865468357055164895cd3b32bdb452067ab9b91421ace94cd58599e42f287eb47663289375824a5f02dbd44d6cb9847e46210a665f28147957290a6d3cf758aa31af9d3ba54c01e670001ea030000009652f3009c52f30001ea0300000044000000010000009b716202cd9fabb93b278a41d9344faecb1300241d255d1de0675a42ebff6b6eea030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083004c606d630584c98d4165a9dfbf9f2a60890f303686b7e5fcb428773a88ad6f08db9fcb4c0e4c4883dee0f9281e1de995b0575ef784a9fda4d58c55cdcc326dc8c838bfbf21eaca646aa5eef8e34d5776310f51b2e1eb1d6b1d86f05739c208efde19b42cad56b4c1e50a381a480ea65aea7c0b0e9f254174bec6f36ebccb70a69233b495ea5a88376ca43e908bbf8b921398dc491d94d1f8ac6f4f4ca6f02d4d71b3b7a81d9a11d797928c060d8c992f908005c1f1460d3ad4fa284dae0b8d9a32fdfc8217ce1456dc87c592ae674614dfadbd1098a3a2889e9c2ada44dac40d6b0154b039a3c7dfead7371f3f1d406f3a7a52307e99588a5d1743cfc52996" + candidateEvents: "0x0c00e8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b514533734cd6976981877d40277ad627ec04b2fc4ba04f558d9e4b41d911a6f41610350e902d91574d9e4897d88a7fb40130cf6c7900b5cb7238036726cd6c07a2255c8ed1c32a018010915879f32707df4a034c9a329ca83a80fab304d1a860690def304379ac236284091930e2b657bf56c4353bdca877b2c8a6bc33ba1611a5d79b2858b00bc707f08066175726120f4635e08000000000561757261010172b799cfe3e2ba2bd80349c7c92d1d84ff01ad6b3d491ff523ee2759e81dc22d58a94cd968ed300dbbc725144a04fa3622a11b2614255b802261d03c53af6f8e000000000200000000e9030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083c2d011b32106cc9030d8d22770c632b611c613e4ed351c149ba59e7d61cd6d0055e865b40a8dcf60422987c985c6022231106e0a4556c0270ffcb99e656dd7be25c5eb2a93cbada931620b5bceebe187bf77aa6dde41a6cbb09f0cbbf291faa5a33ec5ebf7fa730c95c84650a68d432ed53fc77b6d8bff3ce50f19d48317c30aca695f1737408d7dd47052c3b3016128ed3d6ac5b3b46be0cc9537919b9f594fa704bc2e304492c6e835db7f2bfe17d5da9102834f5be31865468357055164895cd3b32bdb452067ab9b91421ace94cd58599e42f287eb47663289375824a5f02dbd44d6cb9847e46210a665f28147957290a6d3cf758aa31af9d3ba54c01e67514f16481063738cae77af97e154a475018c89ac13ca21452572447ae3bea823e90259b97f6f68683641a4b1046440cedddbc2054805e95504e9813b7c352d68ce57ba9357001fb3b6742be5e417621c0fd5f742e9f1d142518605af00a244dc61ebd42d63e07666532e9e2ab468d56fccb902fa801513a4dddc19d9e4b1edaae85ef1d1758908066175726120f4635e080000000005617572610101ba71303b6313ffc193e07bd26aeed8754fd02e86d3fd314074dcb2393a2eae08d32f5ec38737493bbcb09abeb020eb7eadbef00102bd3802b0545d5bca7e6a8d010000000000000000ea030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e14508106083004c606d630584c98d4165a9dfbf9f2a60890f303686b7e5fcb428773a88ad6f08db9fcb4c0e4c4883dee0f9281e1de995b0575ef784a9fda4d58c55cdcc326dc8c838bfbf21eaca646aa5eef8e34d5776310f51b2e1eb1d6b1d86f05739c208efde19b42cad56b4c1e50a381a480ea65aea7c0b0e9f254174bec6f36ebccb70a69233b495ea5a88376ca43e908bbf8b921398dc491d94d1f8ac6f4f4ca6f02d4d71b3b7a81d9a11d797928c060d8c992f908005c1f1460d3ad4fa284dae0b8d9a32fdfc8217ce1456dc87c592ae674614dfadbd1098a3a2889e9c2ada44dac40d6b0154b039a3c7dfead7371f3f1d406f3a7a52307e99588a5d1743cfc5299687f044d87495f20844ff334fe3ff1fb62568167d343d8f39dba5673d75452c06e9023c1bee939fc42a875b2921ecda1e413a5bad4b53bfa7aa314ba9aaf7f66d272cfe563500c93f096e27f359c7834c3b56180169d6055dc7d002bbee58e5b300004a6bf77091facd6381ad90cc2dc3ddab148eede30441cf6767ba2b3ce03b8401a52fdaa208066175726120f4635e080000000005617572610101960963f3ebff0b6b57bf99454febef94f04726a2c94abd401c5d3b568ec3cc384b10dd3dd7d96d456df0b8ed5cc6bacab809e163f85ec0b96529a9f3fa28f5870200000001000000" + candidatePendingAvailability: "0xe8030000b5504a29f00279e92b26d78f1961fea0a0736cfe73c9095c30d3e145081060831aa591a0575f87105ee7feed9a16f697fe2c82ce6759c12c145d429000c20251a2777204e645e33cc3c33bfe795163156f1a8efc379437c040ed8b7184e49dd520d856b61211a44f787be0fa889e056c53ed71c1cb57076c011a5f5a06763293d91329423a01e48b3367edfddbcc708f66fdbc083f646e6407d6d0a685403c3b68aa60144a3d436c639747333586d4f2ca7b434e2cf34880d510be840133df593a63398c0efa2e9cd097176df66954fe7c38634aa7fc8d55e4756210b9c6b084d94221f82a5674177741aa1d30667e25217b14629aa48bf7f9676d19a90c8211cafdc44448ed62f5f67e5c340b420c51fe3bee7c1577f9fa0e819383b5145337000000e902d91574d9e4897d88a7fb40130cf6c7900b5cb7238036726cd6c07a2255c8ed1c32a018010915879f32707df4a034c9a329ca83a80fab304d1a860690def304379ac236284091930e2b657bf56c4353bdca877b2c8a6bc33ba1611a5d79b2858b00bc707f08066175726120f4635e08000000000561757261010172b799cfe3e2ba2bd80349c7c92d1d84ff01ad6b3d491ff523ee2759e81dc22d58a94cd968ed300dbbc725144a04fa3622a11b2614255b802261d03c53af6f8e000000009552f300" + From 077294323afa2c4b8740d80ebbf4109b8ffd3621 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Thu, 24 Aug 2023 21:29:48 +0530 Subject: [PATCH 25/85] feat(lib/erasure): implement Go binding over rust for erasure coding (#3447) --- .github/workflows/unit-tests.yml | 3 + .gitignore | 5 + Makefile | 5 +- go.sum | 1 + lib/erasure/README.md | 6 + lib/erasure/erasure.go | 125 +- lib/erasure/erasure.h | 11 + lib/erasure/erasure_test.go | 320 ++- lib/erasure/rustlib/Cargo.lock | 4037 ++++++++++++++++++++++++++++++ lib/erasure/rustlib/Cargo.toml | 14 + lib/erasure/rustlib/src/lib.rs | 201 ++ 11 files changed, 4584 insertions(+), 144 deletions(-) create mode 100644 lib/erasure/README.md create mode 100644 lib/erasure/erasure.h create mode 100644 lib/erasure/rustlib/Cargo.lock create mode 100644 lib/erasure/rustlib/Cargo.toml create mode 100644 lib/erasure/rustlib/src/lib.rs diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 752948f2bb..fbdc3f72e8 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -77,6 +77,9 @@ jobs: go test -timeout=10m ./... && \ cd .. + - name: generate a shared library file for erasure + run: make compile-erasure + - name: Run unit tests run: go test -coverprofile=coverage.out -covermode=atomic -timeout=45m ./... diff --git a/.gitignore b/.gitignore index d5d719704f..313e462ac4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ tmp # node_modules used by polkadot.js/api tests tests/polkadotjs_test/node_modules !tests/polkadotjs_test/test/*.wasm + +# Ignore rust target dir +lib/erasure/rustlib/target + +*.so diff --git a/Makefile b/Makefile index ee013bdaef..ddae5fb5bc 100644 --- a/Makefile +++ b/Makefile @@ -146,4 +146,7 @@ endif chmod a+x $(GOPATH)/bin/zombienet zombienet-test: install install-zombienet - zombienet test -p native zombienet_tests/functional/0001-basic-network.zndsl \ No newline at end of file + zombienet test -p native zombienet_tests/functional/0001-basic-network.zndsl + +compile-erasure: + cargo build --release --manifest-path=lib/erasure/rustlib/Cargo.toml diff --git a/go.sum b/go.sum index 765ad6150b..4aeab405aa 100644 --- a/go.sum +++ b/go.sum @@ -473,6 +473,7 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= +github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/lib/erasure/README.md b/lib/erasure/README.md new file mode 100644 index 0000000000..e2fbc4f389 --- /dev/null +++ b/lib/erasure/README.md @@ -0,0 +1,6 @@ +# Building Rust code Binary + +- Generate rust binary + ``` + cargo build --release --manifest-path=lib/erasure/rustlib/Cargo.toml + ``` diff --git a/lib/erasure/erasure.go b/lib/erasure/erasure.go index 7a23841259..5935702d4c 100644 --- a/lib/erasure/erasure.go +++ b/lib/erasure/erasure.go @@ -1,70 +1,101 @@ -// Copyright 2021 ChainSafe Systems (ON) +// Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only package erasure +// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/rustlib/target/release -L${SRCDIR}/rustlib/target/release -lerasure +// #include "./erasure.h" +import ( + "C" +) import ( - "bytes" "errors" - "fmt" - - "github.com/klauspost/reedsolomon" + "unsafe" ) -// ErrNotEnoughValidators cannot encode something for zero or one validator -var ErrNotEnoughValidators = errors.New("expected at least 2 validators") +var ( + ErrZeroSizedData = errors.New("data can't be zero sized") + ErrZeroSizedChunks = errors.New("chunks can't be zero sized") +) -// ObtainChunks obtains erasure-coded chunks, divides data into number of validatorsQty chunks and -// creates parity chunks for reconstruction -func ObtainChunks(validatorsQty int, data []byte) ([][]byte, error) { - recoveryThres, err := recoveryThreshold(validatorsQty) - if err != nil { - return nil, err - } - enc, err := reedsolomon.New(validatorsQty, recoveryThres) - if err != nil { - return nil, fmt.Errorf("creating new reed solomon failed: %w", err) +// ObtainChunks obtains erasure-coded chunks, one for each validator. +// This works only up to 65536 validators, and `n_validators` must be non-zero and accepts +// number of validators and scale encoded data. +func ObtainChunks(nValidators uint, data []byte) ([][]byte, error) { + if len(data) == 0 { + return nil, ErrZeroSizedData } - shards, err := enc.Split(data) - if err != nil { - return nil, err + + var cFlattenedChunks *C.uchar + var cFlattenedChunksLen C.size_t + + cnValidators := C.size_t(nValidators) + cData := (*C.uchar)(unsafe.Pointer(&data[0])) + cLen := C.size_t(len(data)) + + cErr := C.obtain_chunks(cnValidators, cData, cLen, &cFlattenedChunks, &cFlattenedChunksLen) + errStr := C.GoString(cErr) + C.free(unsafe.Pointer(cErr)) + + if len(errStr) > 0 { + return nil, errors.New(errStr) } - err = enc.Encode(shards) - if err != nil { - return nil, err + + resData := C.GoBytes(unsafe.Pointer(cFlattenedChunks), C.int(cFlattenedChunksLen)) + C.free(unsafe.Pointer(cFlattenedChunks)) + + chunkSize := uint(len(resData)) / nValidators + chunks := make([][]byte, nValidators) + + start := uint(0) + for i := start; i < nValidators; i++ { + end := start + chunkSize + chunks[i] = resData[start:end] + start = end } - return shards, nil + return chunks, nil } -// Reconstruct the missing data from a set of chunks -func Reconstruct(validatorsQty, originalDataLen int, chunks [][]byte) ([]byte, error) { - recoveryThres, err := recoveryThreshold(validatorsQty) - if err != nil { - return nil, err +// Reconstruct decodable data from a set of chunks. +// +// Provide an iterator containing chunk data and the corresponding index. +// The indices of the present chunks must be indicated. If too few chunks +// are provided, recovery is not possible. +// +// Works only up to 65536 validators, and `n_validators` must be non-zero +func Reconstruct(nValidators uint, chunks [][]byte) ([]byte, error) { + if len(chunks) == 0 { + return nil, ErrZeroSizedChunks } - enc, err := reedsolomon.New(validatorsQty, recoveryThres) - if err != nil { - return nil, err - } - err = enc.Reconstruct(chunks) - if err != nil { - return nil, err + var cReconstructedData *C.uchar + var cReconstructedDataLen C.size_t + var flattenedChunks []byte + + for _, chunk := range chunks { + flattenedChunks = append(flattenedChunks, chunk...) } - buf := new(bytes.Buffer) - err = enc.Join(buf, chunks, originalDataLen) - return buf.Bytes(), err -} -// recoveryThreshold gives the max number of shards/chunks that we can afford to lose and still construct -// the full initial data. Total number of chunks will be validatorQty + recoveryThreshold -func recoveryThreshold(validators int) (int, error) { - if validators <= 1 { - return 0, ErrNotEnoughValidators + cChunkSize := C.size_t(len(chunks[0])) + cFlattenedChunks := (*C.uchar)(unsafe.Pointer(&flattenedChunks[0])) + cFlattenedChunksLen := C.size_t(len(flattenedChunks)) + + cErr := C.reconstruct( + C.size_t(nValidators), + cFlattenedChunks, cFlattenedChunksLen, + cChunkSize, + &cReconstructedData, &cReconstructedDataLen, + ) + errStr := C.GoString(cErr) + C.free(unsafe.Pointer(cErr)) + + if len(errStr) > 0 { + return nil, errors.New(errStr) } - needed := (validators - 1) / 3 + res := C.GoBytes(unsafe.Pointer(cReconstructedData), C.int(cReconstructedDataLen)) + C.free(unsafe.Pointer(cReconstructedData)) - return needed + 1, nil + return res, nil } diff --git a/lib/erasure/erasure.h b/lib/erasure/erasure.h new file mode 100644 index 0000000000..d4fd05289f --- /dev/null +++ b/lib/erasure/erasure.h @@ -0,0 +1,11 @@ +/* + * Copyright 2023 ChainSafe Systems (ON) + * SPDX-License-Identifier: LGPL-3.0-only + */ + +#include +#include + +int32_t add(int32_t a, int32_t b); +const char* obtain_chunks(size_t n_validators, unsigned char *data, size_t len, unsigned char **flattened_chunks, size_t *flattened_chunks_len); +const char* reconstruct(size_t n_validators, unsigned char *flattened_chunks, size_t flattened_chunks_len, size_t chunk_size, unsigned char **res_data, size_t *res_len); \ No newline at end of file diff --git a/lib/erasure/erasure_test.go b/lib/erasure/erasure_test.go index 7a4252ad72..59271027d6 100644 --- a/lib/erasure/erasure_test.go +++ b/lib/erasure/erasure_test.go @@ -1,138 +1,266 @@ -// Copyright 2021 ChainSafe Systems (ON) +// Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package erasure +package erasure_test import ( + "errors" "testing" - "github.com/klauspost/reedsolomon" - "github.com/stretchr/testify/assert" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/erasure" + "github.com/stretchr/testify/require" ) -var testData = []byte("this is a test of the erasure coding") -var expectedChunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {97, 32, 116, 101}, {115, 116, 32, 111}, - {102, 32, 116, 104}, {101, 32, 101, 114}, {97, 115, 117, 114}, {101, 32, 99, 111}, {100, 105, 110, 103}, - {0, 0, 0, 0}, {133, 189, 154, 178}, {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} - -// erasure data missing chunks -var missing2Chunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {}, {115, 116, 32, 111}, - {102, 32, 116, 104}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, - {0, 0, 0, 0}, {133, 189, 154, 178}, {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} -var missing3Chunks = [][]byte{{116, 104, 105, 115}, {32, 105, 115, 32}, {}, {115, 116, 32, 111}, - {}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, {0, 0, 0, 0}, {133, 189, 154, 178}, - {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} -var missing5Chunks = [][]byte{{}, {}, {}, {115, 116, 32, 111}, - {}, {101, 32, 101, 114}, {}, {101, 32, 99, 111}, {100, 105, 110, 103}, {0, 0, 0, 0}, {133, 189, 154, 178}, - {88, 245, 245, 220}, {59, 208, 165, 70}, {127, 213, 208, 179}} - func TestObtainChunks(t *testing.T) { t.Parallel() - type args struct { - validatorsQty int - data []byte - } - tests := map[string]struct { - args args - expectedValue [][]byte - expectedError error + testCases := []struct { + name string + validators uint + dataHex string + expectedChunksHex []string + expectedError error }{ - "happy_path": { - args: args{ - validatorsQty: 10, - data: testData, + { + name: "1_validators", + validators: 1, + dataHex: "0x04020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedChunksHex: []string{}, + expectedError: errors.New("expected at least 2 validators"), + }, + { + name: "2_validators with zero sized data", + validators: 2, + dataHex: "0x", + expectedChunksHex: []string{}, + expectedError: erasure.ErrZeroSizedData, + }, + { + name: "2_validators", + validators: 2, + dataHex: "0x04020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedChunksHex: []string{ + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + }, + { + name: "3_validators", + validators: 3, + dataHex: "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedChunksHex: []string{ + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", }, - expectedValue: expectedChunks, }, - "nil_data": { - args: args{ - validatorsQty: 10, - data: nil, + { + name: "4_validators", + validators: 4, + dataHex: "0x10020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedChunksHex: []string{ + "0x100202000000000000000000000000000000000000000000", + "0x020200000000000000000000000000000000000000000000", + "0x3f60019f0000000000000000000000000000000000000000", + "0x2d60039f0000000000000000000000000000000000000000", }, - expectedError: reedsolomon.ErrShortData, }, - "not_enough_validators": { - args: args{ - validatorsQty: 1, - data: testData, + { + name: "5_validators", + validators: 5, + dataHex: "0x2002020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedChunksHex: []string{ + "0x2002020202000000000000000000000000000000000000000000", + "0x0202020200000000000000000000000000000000000000000000", + "0x1a670202019f0000000000000000000000000000000000000000", + "0x38670202039f0000000000000000000000000000000000000000", + "0x948a020209f70000000000000000000000000000000000000000", + }, + }, + { + name: "6_validators", + validators: 6, + dataHex: "0x40020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + expectedChunksHex: []string{ + "0x400202020202020202000000000000000000000000000000000000000000", + "0x020202020202020200000000000000000000000000000000000000000000", + "0xf069020202020202019f0000000000000000000000000000000000000000", + "0xb269020202020202039f0000000000000000000000000000000000000000", + "0x211702020202020209f70000000000000000000000000000000000000000", + "0x63170202020202020bf70000000000000000000000000000000000000000", + }, + }, + { + name: "7_validators", + validators: 7, + dataHex: "0x8002020202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + expectedChunksHex: []string{ + "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", + "0x0202020202020202020202020202020200000000000000000000000000000000000000000000", + "0x64740202020202020202020202020202019f0000000000000000000000000000000000000000", + "0xe6740202020202020202020202020202039f0000000000000000000000000000000000000000", + "0xf60b020202020202020202020202020209f70000000000000000000000000000000000000000", + "0x740b02020202020202020202020202020bf70000000000000000000000000000000000000000", + "0x127d02020202020202020202020202020a680000000000000000000000000000000000000000", }, - expectedError: ErrNotEnoughValidators, }, } - for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { t.Parallel() - got, err := ObtainChunks(tt.args.validatorsQty, tt.args.data) - expectedThreshold, _ := recoveryThreshold(tt.args.validatorsQty) - if tt.expectedError != nil { - assert.EqualError(t, err, tt.expectedError.Error()) - } else { - assert.NoError(t, err) - assert.Equal(t, tt.args.validatorsQty+expectedThreshold, len(got)) + res, err := erasure.ObtainChunks(c.validators, common.MustHexToBytes(c.dataHex)) + require.Equal(t, c.expectedError, err) + + if err == nil { + var expectedChunks [][]byte + for _, chunk := range c.expectedChunksHex { + expectedChunks = append(expectedChunks, common.MustHexToBytes(chunk)) + } + require.Equal(t, c.validators, uint(len(res))) + require.Equal(t, expectedChunks, res) } - assert.Equal(t, tt.expectedValue, got) }) } + } func TestReconstruct(t *testing.T) { t.Parallel() - type args struct { - validatorsQty int - chunks [][]byte - } - tests := map[string]struct { - args - expectedData []byte - expectedChunks [][]byte - expectedError error + testCases := []struct { + name string + validators uint + chunksHex []string + expectedDataHex string + expectedError error }{ - "missing_2_chunks": { - args: args{ - validatorsQty: 10, - chunks: missing2Chunks, + // generated all these values using `roundtrip_proof_encoding()` function from polkadot. + // https://github.com/paritytech/polkadot/blob/9b1fc27cec47f01a2c229532ee7ab79cc5bb28ef/erasure-coding/src/lib.rs#L413-L418 + { + name: "1_validator_with_zero_sized_chunks", + validators: 1, + expectedDataHex: "0x", + chunksHex: []string{}, + expectedError: erasure.ErrZeroSizedChunks, + }, + { + name: "1_validators", + validators: 1, + expectedDataHex: "0x", + chunksHex: []string{ + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expectedError: errors.New("expected at least 2 validators"), + }, + { + name: "2_validators", + validators: 2, + expectedDataHex: "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + chunksHex: []string{ + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expectedError: nil, + }, + { + name: "3_validators", + validators: 3, + expectedDataHex: "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + chunksHex: []string{ + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expectedError: nil, + }, + { + name: "4_validators", + validators: 4, + expectedDataHex: "0x100202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + chunksHex: []string{ + "0x100202000000000000000000000000000000000000000000", + "0x020200000000000000000000000000000000000000000000", + "0x3f60019f0000000000000000000000000000000000000000", + "0x2d60039f0000000000000000000000000000000000000000", }, - expectedData: testData, - expectedChunks: expectedChunks, + expectedError: nil, }, - "missing_2_chunks,_validator_qty_3": { - args: args{ - validatorsQty: 3, - chunks: missing2Chunks, + { + name: "5_validators", + validators: 5, + expectedDataHex: "0x20020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + chunksHex: []string{ + "0x2002020202000000000000000000000000000000000000000000", + "0x0202020200000000000000000000000000000000000000000000", + "0x1a670202019f0000000000000000000000000000000000000000", + "0x38670202039f0000000000000000000000000000000000000000", + "0x948a020209f70000000000000000000000000000000000000000", }, - expectedError: reedsolomon.ErrTooFewShards, - expectedChunks: expectedChunks, + expectedError: nil, }, - "missing_3_chunks": { - args: args{ - validatorsQty: 10, - chunks: missing3Chunks, + { + name: "6_validators", + validators: 6, + expectedDataHex: "0x400202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + chunksHex: []string{ + "0x400202020202020202000000000000000000000000000000000000000000", + "0x020202020202020200000000000000000000000000000000000000000000", + "0xf069020202020202019f0000000000000000000000000000000000000000", + "0xb269020202020202039f0000000000000000000000000000000000000000", + "0x211702020202020209f70000000000000000000000000000000000000000", + "0x63170202020202020bf70000000000000000000000000000000000000000", }, - expectedData: testData, - expectedChunks: expectedChunks, + expectedError: nil, }, - "missing_5_chunks": { - args: args{ - validatorsQty: 10, - chunks: missing5Chunks, + { + name: "7_validators", + validators: 7, + expectedDataHex: "0x80020202020202020202020202020202020202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + chunksHex: []string{ + "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", + "0x0202020202020202020202020202020200000000000000000000000000000000000000000000", + "0x64740202020202020202020202020202019f0000000000000000000000000000000000000000", + "0xe6740202020202020202020202020202039f0000000000000000000000000000000000000000", + "0xf60b020202020202020202020202020209f70000000000000000000000000000000000000000", + "0x740b02020202020202020202020202020bf70000000000000000000000000000000000000000", + "0x127d02020202020202020202020202020a680000000000000000000000000000000000000000", }, - expectedChunks: missing5Chunks, - expectedError: reedsolomon.ErrTooFewShards, + expectedError: nil, + }, + { + name: "7_validators_with_missing_chunks", + validators: 7, + expectedDataHex: "0x80020202020202020202020202020202020202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + chunksHex: []string{ + "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", + "0x0202020202020202020202020202020200000000000000000000000000000000000000000000", + "0x64740202020202020202020202020202019f0000000000000000000000000000000000000000", + "0xe6740202020202020202020202020202039f0000000000000000000000000000000000000000", + "0xf60b020202020202020202020202020209f70000000000000000000000000000000000000000", + }, + expectedError: nil, }, } - for name, tt := range tests { - tt := tt - t.Run(name, func(t *testing.T) { + + for _, d := range testCases { + d := d + t.Run(d.name, func(t *testing.T) { t.Parallel() - data, err := Reconstruct(tt.args.validatorsQty, len(testData), tt.args.chunks) - if tt.expectedError != nil { - assert.EqualError(t, err, tt.expectedError.Error()) + + var chunks [][]byte + for _, chunk := range d.chunksHex { + chunks = append(chunks, common.MustHexToBytes(chunk)) + } + + actualData, err := erasure.Reconstruct(d.validators, chunks) + require.Equal(t, err, d.expectedError) + + if actualData == nil { + require.Equal(t, common.MustHexToBytes(d.expectedDataHex), []byte{}) } else { - assert.NoError(t, err) - assert.Equal(t, tt.expectedChunks, tt.args.chunks) + require.Equal(t, common.MustHexToBytes(d.expectedDataHex), actualData) } - assert.Equal(t, tt.expectedData, data) }) } } diff --git a/lib/erasure/rustlib/Cargo.lock b/lib/erasure/rustlib/Cargo.lock new file mode 100644 index 0000000000..7e49dec256 --- /dev/null +++ b/lib/erasure/rustlib/Cargo.lock @@ -0,0 +1,4037 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + +[[package]] +name = "addr2line" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8f9420f797f2d9e935edf629310eb938a0d839f984e25327f3c7eed22300c" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "aquamarine" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cde0f2aa063a2a5c28d39b47761aa102bda7c13c84fc118a61b87c7b2f785c" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-secret-scalar" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "ark-transcript", + "digest 0.10.7", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-transcript" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "digest 0.10.7", + "rand_core 0.6.4", + "sha3", +] + +[[package]] +name = "array-bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +dependencies = [ + "addr2line 0.20.0", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object 0.31.1", + "rustc-demangle", +] + +[[package]] +name = "bandersnatch_vrfs" +version = "0.0.1" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-serialize", + "ark-std", + "dleq_vrf", + "fflonk", + "merlin 3.0.0", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "ring", + "sha2 0.10.7", + "zeroize", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" +dependencies = [ + "arrayref", + "arrayvec 0.7.4", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "bounded-collections" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "bounded-vec" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68534a48cbf63a4b1323c433cf21238c9ec23711e0df13b08c33e5c2082663ce" +dependencies = [ + "thiserror", +] + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" +dependencies = [ + "jobserver", + "libc", +] + +[[package]] +name = "cfg-expr" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40ccee03b5175c18cde8f37e7d2a33bcef6f8ec8f7cc0d81090d1bb380949c9" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "winapi", +] + +[[package]] +name = "common" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "fflonk", + "merlin 3.0.0", +] + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "const-random" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" +dependencies = [ + "const-random-macro", + "proc-macro-hack", +] + +[[package]] +name = "const-random-macro" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "proc-macro-hack", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a53c0a4d288377e7415b53dcfc3c04da5cdc2cc95c8d5ac178b58f0b861ad6" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-entity" +version = "0.95.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" +dependencies = [ + "serde", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.7", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.7", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-syn-parse" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dleq_vrf" +version = "0.0.2" +source = "git+https://github.com/w3f/ring-vrf?rev=c86ebd4#c86ebd4114d3165d05f9ce28c1d9e8d7a9a4e801" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-secret-scalar", + "ark-serialize", + "ark-std", + "ark-transcript", + "arrayvec 0.7.4", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dyn-clone" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "304e6508efa593091e97a9abbc10f90aa7ca635b6d2784feff3c89d41dd12272" + +[[package]] +name = "ecdsa" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.1.0", + "spki", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.6.4", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array 0.14.7", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erasure-coding-gorust" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "polkadot-node-primitives", + "reed-solomon-novelpoly", + "thiserror", +] + +[[package]] +name = "errno" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "expander" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7" +dependencies = [ + "blake2", + "fs-err", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fflonk" +version = "0.1.0" +source = "git+https://github.com/w3f/fflonk#26a5045b24e169cffc1f9328ca83d71061145c40" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "merlin 3.0.0", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "frame-metadata" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "aquamarine", + "bitflags", + "environmental", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "macro_magic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "serde_json", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-debug-derive", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-weights", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "Inflector", + "cfg-expr", + "derive-syn-parse", + "expander", + "frame-support-procedural-tools", + "itertools 0.10.5", + "macro_magic", + "proc-macro-warning", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "fs-err" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +dependencies = [ + "fallible-iterator", + "indexmap 1.9.3", + "stable_deref_trait", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + +[[package]] +name = "hermit-abi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.7", + "hmac 0.8.1", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.7", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "macro_magic" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee866bfee30d2d7e83835a4574aad5b45adba4cc807f2a3bbba974e5d4383c9" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "macro_magic_core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e766a20fd9c72bab3e1e64ed63f36bd08410e75803813df210d1ce297d7ad00" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c12469fc165526520dff2807c2975310ab47cf7190a45b99b49a7dc8befab17b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "macro_magic_macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fb85ec1620619edf2984a7693497d4ec88a9665d8b87e942856884c92dbf2a" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memfd" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" +dependencies = [ + "rustix 0.37.23", +] + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory-db" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] + +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec 0.7.4", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "crc32fast", + "hashbrown 0.13.2", + "indexmap 1.9.3", + "memchr", +] + +[[package]] +name = "object" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "parity-scale-codec" +version = "3.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8e946cc0cc711189c0b0249fb8b599cbeeab9784d83c415719368bb8d4ac64" +dependencies = [ + "arrayvec 0.7.4", + "bitvec", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a296c3079b5fefbc499e1de58dc26c09b1b9a5952d26694ee89f04a43ebbb3e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.8", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets 0.48.1", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "polkadot-core-primitives" +version = "0.9.43" +source = "git+https://github.com/paritytech/polkadot?branch=master#c2efce0d60d656cb13f5ade52bf3d866e17085fa" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "polkadot-node-primitives" +version = "0.9.43" +source = "git+https://github.com/paritytech/polkadot?branch=master#c2efce0d60d656cb13f5ade52bf3d866e17085fa" +dependencies = [ + "bounded-vec", + "futures", + "parity-scale-codec", + "polkadot-parachain", + "polkadot-primitives", + "schnorrkel", + "serde", + "sp-application-crypto", + "sp-consensus-babe", + "sp-core", + "sp-keystore", + "sp-maybe-compressed-blob", + "sp-runtime", + "thiserror", + "zstd 0.11.2+zstd.1.5.2", +] + +[[package]] +name = "polkadot-parachain" +version = "0.9.43" +source = "git+https://github.com/paritytech/polkadot?branch=master#c2efce0d60d656cb13f5ade52bf3d866e17085fa" +dependencies = [ + "bounded-collections", + "derive_more", + "frame-support", + "parity-scale-codec", + "polkadot-core-primitives", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "polkadot-primitives" +version = "0.9.43" +source = "git+https://github.com/paritytech/polkadot?branch=master#c2efce0d60d656cb13f5ade52bf3d866e17085fa" +dependencies = [ + "bitvec", + "hex-literal", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-authority-discovery", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-io", + "sp-keystore", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "primitive-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro-warning" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.10", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reed-solomon-novelpoly" +version = "1.0.1-alpha.0" +source = "git+https://github.com/paritytech/reed-solomon-novelpoly?branch=master#17db2a28c0858684388a55c7a4ea7c496602d01e" +dependencies = [ + "derive_more", + "fs-err", + "itertools 0.11.0", + "static_init", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ef7e18e8841942ddb1cf845054f8008410030a3997875d9e49b7a363063df1" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfaf0c85b766276c797f3791f5bc6d5bd116b41d53049af2789666b0c0bc9fa" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.6", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "ring" +version = "0.1.0" +source = "git+https://github.com/w3f/ring-proof#0e948f3c28cbacecdd3020403c4841c0eb339213" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "common", + "fflonk", + "merlin 3.0.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.36.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c37f1bd5ef1b5422177b7646cba67430579cfe2ace80f284fee876bca52ad941" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.37.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "scale-info" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0a159d0c45c12b20c5a844feb1fe4bea86e28f17b92a5f0c42193634d3782" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "912e55f6d20e0e80d63733872b40e1227c0bce1e1ab81ba67d696339bfd7fd29" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "schnellru" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" +dependencies = [ + "ahash 0.8.3", + "cfg-if", + "hashbrown 0.13.2", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin 2.0.1", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.7", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" + +[[package]] +name = "serde" +version = "1.0.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" + +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro", + "sp-core", + "sp-externalities", + "sp-metadata-ir", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "Inflector", + "blake2", + "expander", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "sp-application-crypto" +version = "23.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "16.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-authority-discovery" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-consensus-babe" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-core", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-core" +version = "21.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "array-bytes", + "arrayvec 0.7.4", + "bandersnatch_vrfs", + "bitflags", + "blake2", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "lazy_static", + "libsecp256k1", + "log", + "merlin 2.0.1", + "parity-scale-codec", + "parking_lot 0.12.1", + "paste", + "primitive-types", + "rand 0.8.5", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "tracing", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "9.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.7", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "9.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "quote", + "sp-core-hashing", + "syn 2.0.28", +] + +[[package]] +name = "sp-debug-derive" +version = "8.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "sp-externalities" +version = "0.19.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-genesis-builder" +version = "0.1.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "serde_json", + "sp-api", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "23.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "bytes", + "ed25519", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "rustversion", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-trie", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keystore" +version = "0.27.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "parking_lot 0.12.1", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "4.1.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "thiserror", + "zstd 0.12.4", +] + +[[package]] +name = "sp-metadata-ir" +version = "0.1.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-info", + "sp-std", +] + +[[package]] +name = "sp-panic-handler" +version = "8.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "24.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "paste", + "rand 0.8.5", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", + "sp-weights", +] + +[[package]] +name = "sp-runtime-interface" +version = "17.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "11.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-state-machine" +version = "0.28.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.8.5", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std", + "sp-trie", + "thiserror", + "tracing", + "trie-db", +] + +[[package]] +name = "sp-std" +version = "8.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" + +[[package]] +name = "sp-storage" +version = "13.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", + "thiserror", +] + +[[package]] +name = "sp-tracing" +version = "10.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-trie" +version = "22.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "ahash 0.8.3", + "hash-db", + "hashbrown 0.13.2", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", + "schnellru", + "sp-core", + "sp-std", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "22.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "8.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "sp-wasm-interface" +version = "14.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "anyhow", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std", + "wasmtime", +] + +[[package]] +name = "sp-weights" +version = "20.0.0" +source = "git+https://github.com/paritytech/substrate?branch=master#57d2e53b6afefc079cec3b507710970ccd4a5aae" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ss58-registry" +version = "1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfc443bad666016e012538782d9e3006213a7db43e9fb1dda91657dc06a6fa08" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.6", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "target-lexicon" +version = "0.12.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-bip39" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62cc94d358b5a1e84a5cb9109f559aa3c4d634d2b1b4de3d0fa4adc7c78e2861" +dependencies = [ + "anyhow", + "hmac 0.12.1", + "once_cell", + "pbkdf2 0.11.0", + "rand 0.8.5", + "rustc-hash", + "sha2 0.10.7", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" + +[[package]] +name = "toml_edit" +version = "0.19.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" +dependencies = [ + "indexmap 2.0.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "767abe6ffed88a1889671a102c2861ae742726f52e0a5a425b92c9fbfa7e9c85" +dependencies = [ + "hash-db", + "hashbrown 0.13.2", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.7", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasmparser" +version = "0.102.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" +dependencies = [ + "indexmap 1.9.3", + "url", +] + +[[package]] +name = "wasmtime" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f907fdead3153cb9bfb7a93bbd5b62629472dc06dee83605358c64c52ed3dda9" +dependencies = [ + "anyhow", + "bincode", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "object 0.30.4", + "once_cell", + "paste", + "psm", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-jit", + "wasmtime-runtime", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b9daa7c14cd4fa3edbf69de994408d5f4b7b0959ac13fa69d465f6597f810d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-environ" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a990198cee4197423045235bf89d3359e69bd2ea031005f4c2d901125955c949" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap 1.9.3", + "log", + "object 0.30.4", + "serde", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-jit" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de48df552cfca1c9b750002d3e07b45772dd033b0b206d5c0968496abf31244" +dependencies = [ + "addr2line 0.19.0", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "log", + "object 0.30.4", + "rustc-demangle", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" +dependencies = [ + "once_cell", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecae978b13f7f67efb23bd827373ace4578f2137ec110bbf6a4a7cde4121bbd" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-runtime" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "indexmap 1.9.3", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "paste", + "rand 0.8.5", + "rustix 0.36.15", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-jit-debug", + "windows-sys 0.45.0", +] + +[[package]] +name = "wasmtime-types" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe 6.0.6", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/lib/erasure/rustlib/Cargo.toml b/lib/erasure/rustlib/Cargo.toml new file mode 100644 index 0000000000..9cf63675d0 --- /dev/null +++ b/lib/erasure/rustlib/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "erasure-coding-gorust" +version = "0.1.0" +edition = "2021" + +[lib] +name = "erasure" +crate-type = ["staticlib", "cdylib"] + +[dependencies] +thiserror = "1.0.31" +parity-scale-codec = { version = "3.4.0", default-features = false, features = ["std", "derive"] } +reed-solomon-novelpoly = { package = "reed-solomon-novelpoly", git = "https://github.com/paritytech/reed-solomon-novelpoly", branch = "master" } +polkadot-node-primitives = { package = "polkadot-node-primitives", git = "https://github.com/paritytech/polkadot", branch = "master" } diff --git a/lib/erasure/rustlib/src/lib.rs b/lib/erasure/rustlib/src/lib.rs new file mode 100644 index 0000000000..30ee7ce1ef --- /dev/null +++ b/lib/erasure/rustlib/src/lib.rs @@ -0,0 +1,201 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +extern crate reed_solomon_novelpoly; + +use reed_solomon_novelpoly::{ + f2e16::FIELD_SIZE, + {CodeParams, WrappedShard}, +}; +use std::ffi::CString; +use thiserror::Error; + +/// Errors in erasure coding. +#[derive(Debug, Clone, PartialEq, Error)] +pub enum Error { + /// Returned when there are too many validators. + #[error("there are too many validators")] + TooManyValidators, + /// Cannot encode something for zero or one validator + #[error("expected at least 2 validators")] + NotEnoughValidators, + /// Chunks not of uniform length or the chunks are empty. + #[error("chunks are not uniform, mismatch in length or are zero sized")] + NonUniformChunks, + /// An uneven byte-length of a shard is not valid for `GF(2^16)` encoding. + #[error("uneven length is not valid for field GF(2^16)")] + UnevenLength, + /// Chunk index out of bounds. + #[error("chunk is out of bounds: {chunk_index} not included in 0..{n_validators}")] + ChunkIndexOutOfBounds { + chunk_index: usize, + n_validators: usize, + }, + /// Bad payload in reconstructed bytes. + #[error("reconstructed payload invalid")] + BadPayload, + /// Unknown error + #[error("an unknown error has appeared when deriving code parameters from validator count")] + UnknownCodeParam, +} + +fn str_to_i8ptr(error_message: String) -> *const i8 { + let cstring = CString::new(error_message).unwrap(); + let cstring_ptr = cstring.as_ptr(); + std::mem::forget(cstring); // Prevent Rust from freeing the memory + + cstring_ptr +} + +fn code_params(n_validators: usize) -> Result { + // we need to be able to reconstruct from 1/3 - eps + + let n_wanted = n_validators; + let k_wanted = recovery_threshold(n_wanted)?; + + if n_wanted > FIELD_SIZE as usize { + return Err(Error::TooManyValidators); + } + + CodeParams::derive_parameters(n_wanted, k_wanted).map_err(|e| match e { + reed_solomon_novelpoly::Error::WantedShardCountTooHigh(_) => Error::TooManyValidators, + reed_solomon_novelpoly::Error::WantedShardCountTooLow(_) => Error::NotEnoughValidators, + _ => Error::UnknownCodeParam, + }) +} + +/// Obtain a threshold of chunks that should be enough to recover the data. +pub const fn recovery_threshold(n_validators: usize) -> Result { + if n_validators > FIELD_SIZE { + return Err(Error::TooManyValidators); + } + if n_validators <= 1 { + return Err(Error::NotEnoughValidators); + } + + let needed = n_validators.saturating_sub(1) / 3; + Ok(needed + 1) +} + +// Obtain erasure-coded chunks, one for each validator. +// +// Works only up to 65536 validators, and `n_validators` must be non-zero. +#[no_mangle] +pub extern "C" fn obtain_chunks( + n_validators: usize, + data: *const u8, + len: usize, + flattened_chunks: *mut *mut u8, + flattened_chunks_len: *mut usize, +) -> *const i8 { + let data_slice = unsafe { std::slice::from_raw_parts(data, len) }; + if data_slice.is_empty() { + return str_to_i8ptr(Error::BadPayload.to_string()); + } + + let params_res = code_params(n_validators); + if params_res.is_err() { + return str_to_i8ptr(params_res.unwrap_err().to_string()); + } + let params = params_res.unwrap(); + + let shards_res = params + .make_encoder() + .encode::(&data_slice[..]); + + if shards_res.is_err() { + return str_to_i8ptr(shards_res.unwrap_err().to_string()); + } + + let shards = shards_res.unwrap(); + let chunks: Vec> = shards + .into_iter() + .map(|w: WrappedShard| w.into_inner()) + .collect(); + let mut flattened: Vec = chunks + .iter() + .flat_map(|chunk| chunk.iter().cloned()) + .collect(); + + let result_len = flattened.len(); + let result_data = flattened.as_mut_ptr(); + + unsafe { + *flattened_chunks = result_data; + *flattened_chunks_len = result_len; + } + + std::mem::forget(flattened); + return std::ptr::null(); +} + +// Reconstruct decodable data from a set of chunks. +// +// Provide an iterator containing chunk data and the corresponding index. +// The indices of the present chunks must be indicated. If too few chunks +// are provided, recovery is not possible. +// +// Works only up to 65536 validators, and `n_validators` must be non-zero +#[no_mangle] +pub extern "C" fn reconstruct( + n_validators: usize, + flattened_chunks: *const u8, + flattened_chunks_len: usize, + chunk_size: usize, + res_data: *mut *mut u8, + res_len: *mut usize, +) -> *const i8 { + let flattened_slice: &[u8] = + unsafe { std::slice::from_raw_parts(flattened_chunks, flattened_chunks_len) }; + + let chunks = flattened_slice + .chunks(chunk_size) + .enumerate() + .map(|(index, chunk)| (chunk, index)); + + let params_res = code_params(n_validators); + if params_res.is_err() { + return str_to_i8ptr(params_res.unwrap_err().to_string()); + } + let params = params_res.unwrap(); + + let mut received_shards: Vec> = vec![None; n_validators]; + let mut shard_len = None; + for (chunk_data, chunk_idx) in chunks.into_iter().take(n_validators) { + if chunk_idx >= n_validators { + return str_to_i8ptr( + Error::ChunkIndexOutOfBounds { + chunk_index: chunk_idx, + n_validators, + } + .to_string(), + ); + } + + let shard_len = shard_len.get_or_insert_with(|| chunk_data.len()); + + if *shard_len % 2 != 0 { + return str_to_i8ptr(Error::UnevenLength.to_string()); + } + + if *shard_len != chunk_data.len() || *shard_len == 0 { + return str_to_i8ptr(Error::NonUniformChunks.to_string()); + } + + received_shards[chunk_idx] = Some(WrappedShard::new(chunk_data.to_vec())); + } + + let reconstruct_res = params.make_encoder().reconstruct(received_shards); + if reconstruct_res.is_err() { + return str_to_i8ptr(reconstruct_res.unwrap_err().to_string()); + } + let mut res_vec = reconstruct_res.unwrap(); + + unsafe { + *res_data = res_vec.as_mut_ptr(); + *res_len = res_vec.len(); + } + + std::mem::forget(res_vec); + return std::ptr::null(); +} From 38c0c49e7d06a2c6d6f79d4a20512238f5e97b0a Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Mon, 28 Aug 2023 19:17:06 +0530 Subject: [PATCH 26/85] feat(lib/erasure): construct a trie of chunks of erasure coded values (#3417) --- go.mod | 1 - go.sum | 3 - lib/erasure/erasure.go | 26 ++++++ lib/erasure/erasure_test.go | 165 ++++++++++++++++++++++++++++-------- 4 files changed, 157 insertions(+), 38 deletions(-) diff --git a/go.mod b/go.mod index aa23db609a..cedf4ea205 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,6 @@ require ( github.com/ipfs/go-ds-badger2 v0.1.3 github.com/jpillora/ipfilter v1.2.9 github.com/klauspost/compress v1.16.7 - github.com/klauspost/reedsolomon v1.11.8 github.com/libp2p/go-libp2p v0.31.0 github.com/libp2p/go-libp2p-kad-dht v0.25.0 github.com/minio/sha256-simd v1.0.1 diff --git a/go.sum b/go.sum index 4aeab405aa..b371295068 100644 --- a/go.sum +++ b/go.sum @@ -471,9 +471,6 @@ github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= -github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= -github.com/koron/go-ssdp v0.0.0-20191105050749-2e1c40ed0b5d/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0= github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= diff --git a/lib/erasure/erasure.go b/lib/erasure/erasure.go index 5935702d4c..e67040875f 100644 --- a/lib/erasure/erasure.go +++ b/lib/erasure/erasure.go @@ -10,7 +10,12 @@ import ( ) import ( "errors" + "fmt" "unsafe" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/ChainSafe/gossamer/pkg/scale" ) var ( @@ -99,3 +104,24 @@ func Reconstruct(nValidators uint, chunks [][]byte) ([]byte, error) { return res, nil } + +func ChunksToTrie(chunks [][]byte) (*trie.Trie, error) { + chunkTrie := trie.NewEmptyTrie() + for i, chunk := range chunks { + encodedI, err := scale.Marshal(uint32(i)) + if err != nil { + return nil, fmt.Errorf("marshalling chunk index: %w", err) + } + + chunkHash, err := common.Blake2bHash(chunk) + if err != nil { + return nil, fmt.Errorf("hashing chunk: %w", err) + } + + err = chunkTrie.Put(encodedI, chunkHash[:]) + if err != nil { + return nil, fmt.Errorf("putting chunk into trie: %w", err) + } + } + return chunkTrie, nil +} diff --git a/lib/erasure/erasure_test.go b/lib/erasure/erasure_test.go index 59271027d6..ea54313286 100644 --- a/lib/erasure/erasure_test.go +++ b/lib/erasure/erasure_test.go @@ -16,38 +16,40 @@ func TestObtainChunks(t *testing.T) { t.Parallel() testCases := []struct { name string - validators uint + nValidators uint dataHex string expectedChunksHex []string expectedError error }{ + // generated all these values using `roundtrip_proof_encoding()` function from polkadot. + // https://github.com/paritytech/polkadot/blob/9b1fc27cec47f01a2c229532ee7ab79cc5bb28ef/erasure-coding/src/lib.rs#L413-L418 { name: "1_validators", - validators: 1, + nValidators: 1, dataHex: "0x04020000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedChunksHex: []string{}, expectedError: errors.New("expected at least 2 validators"), }, { name: "2_validators with zero sized data", - validators: 2, + nValidators: 2, dataHex: "0x", expectedChunksHex: []string{}, expectedError: erasure.ErrZeroSizedData, }, { - name: "2_validators", - validators: 2, - dataHex: "0x04020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + name: "2_validators", + nValidators: 2, + dataHex: "0x04020000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedChunksHex: []string{ "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", }, }, { - name: "3_validators", - validators: 3, - dataHex: "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + name: "3_validators", + nValidators: 3, + dataHex: "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedChunksHex: []string{ "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -55,9 +57,9 @@ func TestObtainChunks(t *testing.T) { }, }, { - name: "4_validators", - validators: 4, - dataHex: "0x10020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + name: "4_validators", + nValidators: 4, + dataHex: "0x10020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedChunksHex: []string{ "0x100202000000000000000000000000000000000000000000", "0x020200000000000000000000000000000000000000000000", @@ -66,9 +68,9 @@ func TestObtainChunks(t *testing.T) { }, }, { - name: "5_validators", - validators: 5, - dataHex: "0x2002020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + name: "5_validators", + nValidators: 5, + dataHex: "0x2002020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll expectedChunksHex: []string{ "0x2002020202000000000000000000000000000000000000000000", "0x0202020200000000000000000000000000000000000000000000", @@ -78,9 +80,9 @@ func TestObtainChunks(t *testing.T) { }, }, { - name: "6_validators", - validators: 6, - dataHex: "0x40020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + name: "6_validators", + nValidators: 6, + dataHex: "0x40020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll expectedChunksHex: []string{ "0x400202020202020202000000000000000000000000000000000000000000", "0x020202020202020200000000000000000000000000000000000000000000", @@ -91,9 +93,9 @@ func TestObtainChunks(t *testing.T) { }, }, { - name: "7_validators", - validators: 7, - dataHex: "0x8002020202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll + name: "7_validators", + nValidators: 7, + dataHex: "0x8002020202020202020202020202020202020202020202020202020202020202020000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll expectedChunksHex: []string{ "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", "0x0202020202020202020202020202020200000000000000000000000000000000000000000000", @@ -110,7 +112,7 @@ func TestObtainChunks(t *testing.T) { c := c t.Run(c.name, func(t *testing.T) { t.Parallel() - res, err := erasure.ObtainChunks(c.validators, common.MustHexToBytes(c.dataHex)) + res, err := erasure.ObtainChunks(c.nValidators, common.MustHexToBytes(c.dataHex)) require.Equal(t, c.expectedError, err) if err == nil { @@ -118,7 +120,7 @@ func TestObtainChunks(t *testing.T) { for _, chunk := range c.expectedChunksHex { expectedChunks = append(expectedChunks, common.MustHexToBytes(chunk)) } - require.Equal(t, c.validators, uint(len(res))) + require.Equal(t, c.nValidators, uint(len(res))) require.Equal(t, expectedChunks, res) } }) @@ -130,7 +132,7 @@ func TestReconstruct(t *testing.T) { t.Parallel() testCases := []struct { name string - validators uint + nValidators uint chunksHex []string expectedDataHex string expectedError error @@ -139,14 +141,14 @@ func TestReconstruct(t *testing.T) { // https://github.com/paritytech/polkadot/blob/9b1fc27cec47f01a2c229532ee7ab79cc5bb28ef/erasure-coding/src/lib.rs#L413-L418 { name: "1_validator_with_zero_sized_chunks", - validators: 1, + nValidators: 1, expectedDataHex: "0x", chunksHex: []string{}, expectedError: erasure.ErrZeroSizedChunks, }, { name: "1_validators", - validators: 1, + nValidators: 1, expectedDataHex: "0x", chunksHex: []string{ "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -155,7 +157,7 @@ func TestReconstruct(t *testing.T) { }, { name: "2_validators", - validators: 2, + nValidators: 2, expectedDataHex: "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", chunksHex: []string{ "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -165,7 +167,7 @@ func TestReconstruct(t *testing.T) { }, { name: "3_validators", - validators: 3, + nValidators: 3, expectedDataHex: "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", chunksHex: []string{ "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -176,7 +178,7 @@ func TestReconstruct(t *testing.T) { }, { name: "4_validators", - validators: 4, + nValidators: 4, expectedDataHex: "0x100202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll chunksHex: []string{ "0x100202000000000000000000000000000000000000000000", @@ -188,7 +190,7 @@ func TestReconstruct(t *testing.T) { }, { name: "5_validators", - validators: 5, + nValidators: 5, expectedDataHex: "0x20020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll chunksHex: []string{ "0x2002020202000000000000000000000000000000000000000000", @@ -201,7 +203,7 @@ func TestReconstruct(t *testing.T) { }, { name: "6_validators", - validators: 6, + nValidators: 6, expectedDataHex: "0x400202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll chunksHex: []string{ "0x400202020202020202000000000000000000000000000000000000000000", @@ -215,7 +217,7 @@ func TestReconstruct(t *testing.T) { }, { name: "7_validators", - validators: 7, + nValidators: 7, expectedDataHex: "0x80020202020202020202020202020202020202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll chunksHex: []string{ "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", @@ -230,7 +232,7 @@ func TestReconstruct(t *testing.T) { }, { name: "7_validators_with_missing_chunks", - validators: 7, + nValidators: 7, expectedDataHex: "0x80020202020202020202020202020202020202020202020202020202020202020200000000000000000000000000000000000000000000000000000000000000000000000000000000000000", //nolint:lll chunksHex: []string{ "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", @@ -253,7 +255,7 @@ func TestReconstruct(t *testing.T) { chunks = append(chunks, common.MustHexToBytes(chunk)) } - actualData, err := erasure.Reconstruct(d.validators, chunks) + actualData, err := erasure.Reconstruct(d.nValidators, chunks) require.Equal(t, err, d.expectedError) if actualData == nil { @@ -264,3 +266,98 @@ func TestReconstruct(t *testing.T) { }) } } + +func TestChunksToTrie(t *testing.T) { + t.Parallel() + var testCases = []struct { + name string + chunksHex []string + expectedRootHex string + }{ + // generated all these values using `roundtrip_proof_encoding()` function from polkadot. + // https://github.com/paritytech/polkadot/blob/9b1fc27cec47f01a2c229532ee7ab79cc5bb28ef/erasure-coding/src/lib.rs#L413-L418 + { + name: "2_chunks", + chunksHex: []string{ + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0402000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expectedRootHex: "0x513489282098e960bfd57ed52d62838ce9395f3f59257f1f40fadd02261a7991", + }, + { + name: "3_chunks", + chunksHex: []string{ + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x0802020000000000000000000000000000000000000000000000000000000000000000000000000000000000", + }, + expectedRootHex: "0x57aff6950c28545a43ae9ed83acbc87dd50cf548d8712e3ba9e2f074e333a84c", + }, + { + name: "4_chunks", + chunksHex: []string{ + "0x100202000000000000000000000000000000000000000000", + "0x020200000000000000000000000000000000000000000000", + "0x3f60019f0000000000000000000000000000000000000000", + "0x2d60039f0000000000000000000000000000000000000000", + }, + expectedRootHex: "0x083c17b6cceaf3a5e062bb93ea31a690a218d4ca654f42454b1d639033f1ec9a", + }, + { + name: "5_chunks", + chunksHex: []string{ + "0x2002020202000000000000000000000000000000000000000000", + "0x0202020200000000000000000000000000000000000000000000", + "0x1a670202019f0000000000000000000000000000000000000000", + "0x38670202039f0000000000000000000000000000000000000000", + "0x948a020209f70000000000000000000000000000000000000000", + }, + expectedRootHex: "0x5496b487c0c849eaf194ad8e283eed0fe188e507c62b93f9a2659489f3a12d89", + }, + { + name: "6_chunks", + chunksHex: []string{ + "0x400202020202020202000000000000000000000000000000000000000000", + "0x020202020202020200000000000000000000000000000000000000000000", + "0xf069020202020202019f0000000000000000000000000000000000000000", + "0xb269020202020202039f0000000000000000000000000000000000000000", + "0x211702020202020209f70000000000000000000000000000000000000000", + "0x63170202020202020bf70000000000000000000000000000000000000000", + }, + expectedRootHex: "0xc20501e40e6dd45a9b71a66c9da8bdd3b11ab0722579d26516f7ae1fdb3e3ad2", + }, + { + name: "7_chunks", + chunksHex: []string{ + "0x8002020202020202020202020202020202000000000000000000000000000000000000000000", + "0x0202020202020202020202020202020200000000000000000000000000000000000000000000", + "0x64740202020202020202020202020202019f0000000000000000000000000000000000000000", + "0xe6740202020202020202020202020202039f0000000000000000000000000000000000000000", + "0xf60b020202020202020202020202020209f70000000000000000000000000000000000000000", + "0x740b02020202020202020202020202020bf70000000000000000000000000000000000000000", + "0x127d02020202020202020202020202020a680000000000000000000000000000000000000000", + }, + expectedRootHex: "0xdbc4bde5cf7f7eaa16baec41f9a2c1f800a992b3a4b81c5d1ec66dcf1a4d7f18", + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + var chunks [][]byte + for _, chunk := range c.chunksHex { + chunks = append(chunks, common.MustHexToBytes(chunk)) + } + + trie, err := erasure.ChunksToTrie(chunks) + require.NoError(t, err) + + root, err := trie.Hash() + require.NoError(t, err) + + require.Equal(t, c.expectedRootHex, root.String()) + }) + } +} From 33e9f9a7a54ba1699a84028627d99db6d3c52a4f Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 30 Aug 2023 15:18:45 +0530 Subject: [PATCH 27/85] chore(parachain): use wazero instead of wasmer Use wazero runtime instance instead of wasmer runtime instance --- dot/parachain/runtime/instance.go | 10 +++++----- lib/runtime/constants.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dot/parachain/runtime/instance.go b/dot/parachain/runtime/instance.go index ea15cb4e2e..6e02a5ace2 100644 --- a/dot/parachain/runtime/instance.go +++ b/dot/parachain/runtime/instance.go @@ -10,7 +10,7 @@ import ( parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" - runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wasmer" + runtimewazero "github.com/ChainSafe/gossamer/lib/runtime/wazero" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -49,15 +49,15 @@ type ValidationParameters struct { RelayParentStorageRoot common.Hash } -// Instance is a wrapper around the wasmer runtime instance. +// Instance is a wrapper around the wazero runtime instance. type Instance struct { - *runtimewasmer.Instance + *runtimewazero.Instance } func SetupVM(code []byte) (*Instance, error) { - cfg := runtimewasmer.Config{} + cfg := runtimewazero.Config{} - instance, err := runtimewasmer.NewInstance(code, cfg) + instance, err := runtimewazero.NewInstance(code, cfg) if err != nil { return nil, fmt.Errorf("creating instance: %w", err) } diff --git a/lib/runtime/constants.go b/lib/runtime/constants.go index 30e2148324..494224269a 100644 --- a/lib/runtime/constants.go +++ b/lib/runtime/constants.go @@ -75,7 +75,7 @@ const ( // ParachainHostValidationCodeByHash returns parachain host's validation code by hash ParachainHostValidationCodeByHash = "ParachainHost_validation_code_by_hash" // ParachainHostValidators is the runtime API call ParachainHost_validators - ParachainHostValidators = "ParachainHost_validators" + ParachainHostValidators = "ParachainHost_validators" //nolint // ParachainHostValidatorGroups is the runtime API call ParachainHost_validator_groups ParachainHostValidatorGroups = "ParachainHost_validator_groups" // ParachainHostAvailabilityCores is the runtime API call ParachainHost_availability_cores From 788a1600eddb6b041520f960142458eed388348a Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Sun, 17 Sep 2023 14:47:35 +0530 Subject: [PATCH 28/85] chore(dot/parachain): rename statement (#3470) --- dot/parachain/collation_protocol_test.go | 6 +-- dot/parachain/service.go | 2 +- dot/parachain/statement.go | 20 ++++---- .../statement_distribution_message.go | 16 +++--- .../statement_distribution_message_test.go | 50 ++++++++++++------- dot/parachain/statement_test.go | 37 ++++++++++++-- dot/parachain/testdata/statement.yaml | 10 ++-- dot/parachain/validation_protocol_test.go | 12 ++--- 8 files changed, 97 insertions(+), 56 deletions(-) diff --git a/dot/parachain/collation_protocol_test.go b/dot/parachain/collation_protocol_test.go index 84dbfbd974..4c2908d4d2 100644 --- a/dot/parachain/collation_protocol_test.go +++ b/dot/parachain/collation_protocol_test.go @@ -68,8 +68,8 @@ func TestCollationProtocol(t *testing.T) { }, } - statementWithSeconded := NewStatement() - err := statementWithSeconded.Set(secondedEnumValue) + statementVDTWithSeconded := NewStatementVDT() + err := statementVDTWithSeconded.Set(secondedEnumValue) require.NoError(t, err) testCases := []struct { @@ -96,7 +96,7 @@ func TestCollationProtocol(t *testing.T) { enumValue: CollationSeconded{ Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ - Payload: statementWithSeconded, + Payload: statementVDTWithSeconded, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, diff --git a/dot/parachain/service.go b/dot/parachain/service.go index dfa5294d3a..61d1359268 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -124,7 +124,7 @@ func (s Service) run() { s.Network.GossipMessage(&collationMessage) statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} - err := statementDistributionLargeStatement.Set(SecondedStatementWithLargePayload{ + err := statementDistributionLargeStatement.Set(LargePayload{ RelayParent: common.Hash{}, CandidateHash: CandidateHash{Value: common.Hash{}}, SignedBy: 5, diff --git a/dot/parachain/statement.go b/dot/parachain/statement.go index c5e2633790..bb196510c0 100644 --- a/dot/parachain/statement.go +++ b/dot/parachain/statement.go @@ -11,34 +11,34 @@ import ( "github.com/ChainSafe/gossamer/pkg/scale" ) -// Statement is a result of candidate validation. It could be either `Valid` or `Seconded`. -type Statement scale.VaryingDataType +// StatementVDT is a result of candidate validation. It could be either `Valid` or `Seconded`. +type StatementVDT scale.VaryingDataType -// NewStatement returns a new statement varying data type -func NewStatement() Statement { +// NewStatementVDT returns a new statement varying data type +func NewStatementVDT() StatementVDT { vdt := scale.MustNewVaryingDataType(Seconded{}, Valid{}) - return Statement(vdt) + return StatementVDT(vdt) } // New will enable scale to create new instance when needed -func (Statement) New() Statement { - return NewStatement() +func (StatementVDT) New() StatementVDT { + return NewStatementVDT() } // Set will set a value using the underlying varying data type -func (s *Statement) Set(val scale.VaryingDataTypeValue) (err error) { +func (s *StatementVDT) Set(val scale.VaryingDataTypeValue) (err error) { vdt := scale.VaryingDataType(*s) err = vdt.Set(val) if err != nil { return fmt.Errorf("setting value to varying data type: %w", err) } - *s = Statement(vdt) + *s = StatementVDT(vdt) return nil } // Value returns the value from the underlying varying data type -func (s *Statement) Value() (scale.VaryingDataTypeValue, error) { +func (s *StatementVDT) Value() (scale.VaryingDataTypeValue, error) { vdt := scale.VaryingDataType(*s) return vdt.Value() } diff --git a/dot/parachain/statement_distribution_message.go b/dot/parachain/statement_distribution_message.go index 9e0f3770a7..a17e48accb 100644 --- a/dot/parachain/statement_distribution_message.go +++ b/dot/parachain/statement_distribution_message.go @@ -16,7 +16,7 @@ type StatementDistributionMessage scale.VaryingDataType // NewStatementDistributionMessage returns a new statement distribution message varying data type func NewStatementDistributionMessage() StatementDistributionMessage { - vdt := scale.MustNewVaryingDataType(SignedFullStatement{}, SecondedStatementWithLargePayload{}) + vdt := scale.MustNewVaryingDataType(Statement{}, LargePayload{}) return StatementDistributionMessage(vdt) } @@ -43,26 +43,26 @@ func (sdm *StatementDistributionMessage) Value() (scale.VaryingDataTypeValue, er return vdt.Value() } -// SignedFullStatement represents a signed full statement under a given relay-parent. -type SignedFullStatement struct { +// Statement represents a signed full statement under a given relay-parent. +type Statement struct { Hash common.Hash `scale:"1"` UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` } // Index returns the index of varying data type -func (SignedFullStatement) Index() uint { +func (Statement) Index() uint { return 0 } -// SecondedStatementWithLargePayload represents Seconded statement with large payload +// LargePayload represents Seconded statement with large payload // (e.g. containing a runtime upgrade). // // We only gossip the hash in that case, actual payloads can be fetched from sending node // via request/response. -type SecondedStatementWithLargePayload StatementMetadata +type LargePayload StatementMetadata // Index returns the index of varying data type -func (SecondedStatementWithLargePayload) Index() uint { +func (LargePayload) Index() uint { return 1 } @@ -70,7 +70,7 @@ func (SecondedStatementWithLargePayload) Index() uint { type UncheckedSignedFullStatement struct { // The payload is part of the signed data. The rest is the signing context, // which is known both at signing and at validation. - Payload Statement `scale:"1"` + Payload StatementVDT `scale:"1"` // The index of the validator signing this statement. ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` diff --git a/dot/parachain/statement_distribution_message_test.go b/dot/parachain/statement_distribution_message_test.go index 9a13bace7e..3788783a53 100644 --- a/dot/parachain/statement_distribution_message_test.go +++ b/dot/parachain/statement_distribution_message_test.go @@ -44,8 +44,8 @@ func TestStatementDistributionMessage(t *testing.T) { hash5 := getDummyHash(5) - statementWithValid := NewStatement() - err := statementWithValid.Set(Valid{hash5}) + statementVDTWithValid := NewStatementVDT() + err := statementVDTWithValid.Set(Valid{hash5}) require.NoError(t, err) secondedEnumValue := Seconded{ @@ -71,29 +71,29 @@ func TestStatementDistributionMessage(t *testing.T) { }, } - statementWithSeconded := NewStatement() - err = statementWithSeconded.Set(secondedEnumValue) + statementVDTWithSeconded := NewStatementVDT() + err = statementVDTWithSeconded.Set(secondedEnumValue) require.NoError(t, err) - signedFullStatementWithValid := SignedFullStatement{ + signedFullStatementWithValid := Statement{ Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ - Payload: statementWithValid, + Payload: statementVDTWithValid, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, } - signedFullStatementWithSeconded := SignedFullStatement{ + signedFullStatementWithSeconded := Statement{ Hash: hash5, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ - Payload: statementWithSeconded, + Payload: statementVDTWithSeconded, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, }, } - secondedStatementWithLargePayload := SecondedStatementWithLargePayload{ + largePayload := LargePayload{ RelayParent: hash5, CandidateHash: CandidateHash{hash5}, SignedBy: parachaintypes.ValidatorIndex(5), @@ -104,6 +104,7 @@ func TestStatementDistributionMessage(t *testing.T) { name string enumValue scale.VaryingDataTypeValue encodingValue []byte + expectedErr error }{ // expected encoding is generated by running rust test code: // fn statement_distribution_message_encode() { @@ -119,7 +120,7 @@ func TestStatementDistributionMessage(t *testing.T) { // statement_valid, ValidatorIndex(5), val_sign.clone()); // let sdm_statement_valid = StatementDistributionMessage::Statement( // hash1, unchecked_signed_full_statement_valid); - // println!("encode SignedFullStatement with valid statement => {:?}\n\n", sdm_statement_valid.encode()); + // println!("encode Statement with valid statementVDT => {:?}\n\n", sdm_statement_valid.encode()); // let collator_result = sr25519::Public::from_string( // "0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147"); @@ -157,7 +158,7 @@ func TestStatementDistributionMessage(t *testing.T) { // statement_second, ValidatorIndex(5), val_sign.clone()); // let sdm_statement_second = StatementDistributionMessage::Statement( // hash1, unchecked_signed_full_statement_second); - // println!("encode SignedFullStatement with Seconded statement => {:?}\n\n", sdm_statement_second.encode()); + // println!("encode Statement with Seconded statementVDT => {:?}\n\n", sdm_statement_second.encode()); // let sdm_large_statement = StatementDistributionMessage::LargeStatement(StatementMetadata{ // relay_parent: hash1, @@ -165,24 +166,29 @@ func TestStatementDistributionMessage(t *testing.T) { // signed_by: ValidatorIndex(5_u32), // signature: val_sign.clone(), // }); - // println!("encode SecondedStatementWithLargePayload => {:?}\n\n", sdm_large_statement.encode()); + // println!("encode largePayload => {:?}\n\n", sdm_large_statement.encode()); // } { - name: "SignedFullStatement with valid statement", + name: "Statement with valid statementVDT", enumValue: signedFullStatementWithValid, - encodingValue: common.MustHexToBytes(testDataStatement["sfsValid"]), + encodingValue: common.MustHexToBytes(testDataStatement["statementValid"]), }, { - name: "SignedFullStatement with Seconded statement", + name: "Statement with Seconded statementVDT", enumValue: signedFullStatementWithSeconded, - encodingValue: common.MustHexToBytes(testDataStatement["sfsSeconded"]), + encodingValue: common.MustHexToBytes(testDataStatement["statementSeconded"]), }, { name: "Seconded Statement With LargePayload", - enumValue: secondedStatementWithLargePayload, + enumValue: largePayload, encodingValue: common.MustHexToBytes(testDataStatement["statementWithLargePayload"]), }, + { + name: "invalid struct", + enumValue: invalidVayingDataTypeValue{}, + expectedErr: ErrInvalidVayingDataTypeValue, + }, } for _, c := range testCases { @@ -194,8 +200,13 @@ func TestStatementDistributionMessage(t *testing.T) { vdt := NewStatementDistributionMessage() err := vdt.Set(c.enumValue) - require.NoError(t, err) + if c.expectedErr != nil { + require.EqualError(t, err, c.expectedErr.Error()) + return + } + + require.NoError(t, err) bytes, err := scale.Marshal(vdt) require.NoError(t, err) @@ -204,6 +215,9 @@ func TestStatementDistributionMessage(t *testing.T) { t.Run("unmarshal", func(t *testing.T) { t.Parallel() + if c.expectedErr != nil { + return + } vdt := NewStatementDistributionMessage() err := scale.Unmarshal(c.encodingValue, &vdt) diff --git a/dot/parachain/statement_test.go b/dot/parachain/statement_test.go index 088fb11fd8..7c407ced7b 100644 --- a/dot/parachain/statement_test.go +++ b/dot/parachain/statement_test.go @@ -4,6 +4,8 @@ package parachain import ( + "errors" + "math" "testing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -12,6 +14,15 @@ import ( "github.com/stretchr/testify/require" ) +var ErrInvalidVayingDataTypeValue = errors.New( + "setting value to varying data type: unsupported VaryingDataTypeValue: {} (parachain.invalidVayingDataTypeValue)") + +type invalidVayingDataTypeValue struct{} + +func (invalidVayingDataTypeValue) Index() uint { + return math.MaxUint +} + func getDummyHash(num byte) common.Hash { hash := common.Hash{} for i := 0; i < 32; i++ { @@ -20,7 +31,7 @@ func getDummyHash(num byte) common.Hash { return hash } -func TestStatement(t *testing.T) { +func TestStatementVDT(t *testing.T) { t.Parallel() var collatorID parachaintypes.CollatorID @@ -60,16 +71,24 @@ func TestStatement(t *testing.T) { name string enumValue scale.VaryingDataTypeValue encodingValue []byte + expectedErr error }{ { name: "Seconded", enumValue: secondedEnumValue, - encodingValue: common.MustHexToBytes(testDataStatement["statementSeconded"]), + encodingValue: common.MustHexToBytes(testDataStatement["statementVDTSeconded"]), + expectedErr: nil, }, { name: "Valid", enumValue: Valid{hash5}, encodingValue: common.MustHexToBytes("0x020505050505050505050505050505050505050505050505050505050505050505"), + expectedErr: nil, + }, + { + name: "invalid struct", + enumValue: invalidVayingDataTypeValue{}, + expectedErr: ErrInvalidVayingDataTypeValue, }, } @@ -80,10 +99,15 @@ func TestStatement(t *testing.T) { t.Run("marshal", func(t *testing.T) { t.Parallel() - vdt := NewStatement() + vdt := NewStatementVDT() err := vdt.Set(c.enumValue) - require.NoError(t, err) + if c.expectedErr != nil { + require.EqualError(t, err, c.expectedErr.Error()) + return + } + + require.NoError(t, err) bytes, err := scale.Marshal(vdt) require.NoError(t, err) @@ -92,8 +116,11 @@ func TestStatement(t *testing.T) { t.Run("unmarshal", func(t *testing.T) { t.Parallel() + if c.expectedErr != nil { + return + } - vdt := NewStatement() + vdt := NewStatementVDT() err := scale.Unmarshal(c.encodingValue, &vdt) require.NoError(t, err) diff --git a/dot/parachain/testdata/statement.yaml b/dot/parachain/testdata/statement.yaml index 8da19561cb..84af03fd4d 100644 --- a/dot/parachain/testdata/statement.yaml +++ b/dot/parachain/testdata/statement.yaml @@ -5,13 +5,13 @@ collatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" # Seconded -statementSeconded: "0x0101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c0102030500000000000000" +statementVDTSeconded: "0x0101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c0102030500000000000000" -# SignedFullStatement with valid statement -sfsValid: "0x00050505050505050505050505050505050505050505050505050505050505050502050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" +# Statement with valid statementVDT +statementValid: "0x00050505050505050505050505050505050505050505050505050505050505050502050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" -# SignedFullStatement with Seconded statement -sfsSeconded: "0x0005050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" +# Statement with Seconded statementVDT +statementSeconded: "0x0005050505050505050505050505050505050505050505050505050505050505050101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c010203050000000000000005000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" # Seconded Statement With LargePayload statementWithLargePayload: "0x010505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" diff --git a/dot/parachain/validation_protocol_test.go b/dot/parachain/validation_protocol_test.go index 0a958bfdb2..32e35d5af7 100644 --- a/dot/parachain/validation_protocol_test.go +++ b/dot/parachain/validation_protocol_test.go @@ -166,14 +166,14 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { HrmpWatermark: 0, }, } - statement := NewStatement() - statement.Set(statementSecond) + statementVDT := NewStatementVDT() + statementVDT.Set(statementSecond) statementDistributionStatement := StatementDistribution{NewStatementDistributionMessage()} - statementDistributionStatement.Set(SignedFullStatement{ + statementDistributionStatement.Set(Statement{ Hash: hashA, UncheckedSignedFullStatement: UncheckedSignedFullStatement{ - Payload: statement, + Payload: statementVDT, ValidatorIndex: 5, Signature: validatorSignature, }, @@ -199,11 +199,11 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { signature: val_sign.clone(), }); let validation_sdm_large_statement = protocol_v1::ValidationProtocol::StatementDistribution(sdm_large_statement); - println!("encode validation SecondedStatementWithLargePayload => {:?}\n\n", validation_sdm_large_statement.encode()); + println!("encode validation largePayload => {:?}\n\n", validation_sdm_large_statement.encode()); } */ statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} - statementDistributionLargeStatement.Set(SecondedStatementWithLargePayload{ + statementDistributionLargeStatement.Set(LargePayload{ RelayParent: hashA, CandidateHash: CandidateHash{Value: hashA}, SignedBy: 5, From 12fcb0ff6705448a365b08df25acd299c55938ea Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Wed, 27 Sep 2023 09:08:16 +0200 Subject: [PATCH 29/85] feat(dot/parachain): Implement overseer skeleton (#3460) Co-authored-by: Kishan Mohanbhai Sagathiya --- dot/parachain/overseer/overseer.go | 119 ++++++++++++++++++ dot/parachain/overseer/overseer_test.go | 160 ++++++++++++++++++++++++ dot/parachain/overseer/types.go | 29 +++++ 3 files changed, 308 insertions(+) create mode 100644 dot/parachain/overseer/overseer.go create mode 100644 dot/parachain/overseer/overseer_test.go create mode 100644 dot/parachain/overseer/types.go diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go new file mode 100644 index 0000000000..3f6eea9b94 --- /dev/null +++ b/dot/parachain/overseer/overseer.go @@ -0,0 +1,119 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package overseer + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/ChainSafe/gossamer/internal/log" +) + +var ( + logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-overseer")) +) + +type Overseer struct { + ctx context.Context + cancel context.CancelFunc + errChan chan error // channel for overseer to send errors to service that started it + SubsystemsToOverseer chan any + subsystems map[Subsystem]chan any // map[Subsystem]OverseerToSubSystem channel + wg sync.WaitGroup +} + +func NewOverseer() *Overseer { + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + return &Overseer{ + ctx: ctx, + cancel: cancel, + errChan: make(chan error), + SubsystemsToOverseer: make(chan any), + subsystems: make(map[Subsystem]chan any), + } +} + +// RegisterSubsystem registers a subsystem with the overseer, +// Add OverseerToSubSystem channel to subsystem, which will be passed to subsystem's Run method. +func (o *Overseer) RegisterSubsystem(subsystem Subsystem) { + o.subsystems[subsystem] = make(chan any) +} + +func (o *Overseer) Start() error { + // start subsystems + for subsystem, overseerToSubSystem := range o.subsystems { + o.wg.Add(1) + go func(sub Subsystem, overseerToSubSystem chan any) { + err := sub.Run(o.ctx, overseerToSubSystem, o.SubsystemsToOverseer) + if err != nil { + logger.Errorf("running subsystem %v failed: %v", sub, err) + } + logger.Infof("subsystem %v stopped", sub) + o.wg.Done() + }(subsystem, overseerToSubSystem) + } + + go o.processMessages() + + // TODO: add logic to start listening for Block Imported events and Finalisation events + return nil +} + +func (o *Overseer) processMessages() { + for { + select { + case msg := <-o.SubsystemsToOverseer: + switch msg.(type) { + default: + logger.Error("unknown message type") + } + case <-o.ctx.Done(): + if err := o.ctx.Err(); err != nil { + logger.Errorf("ctx error: %v\n", err) + } + logger.Info("overseer stopping") + return + } + } +} + +func (o *Overseer) Stop() error { + o.cancel() + + // close the errorChan to unblock any listeners on the errChan + close(o.errChan) + + // wait for subsystems to stop + // TODO: determine reasonable timeout duration for production, currently this is just for testing + timedOut := waitTimeout(&o.wg, 500*time.Millisecond) + fmt.Printf("timedOut: %v\n", timedOut) + + return nil +} + +// sendActiveLeavesUpdate sends an ActiveLeavesUpdate to the subsystem +func (o *Overseer) sendActiveLeavesUpdate(update ActiveLeavesUpdate, subsystem Subsystem) { + o.subsystems[subsystem] <- update +} + +func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) (timeouted bool) { + c := make(chan struct{}) + go func() { + defer close(c) + wg.Wait() + }() + timeoutTimer := time.NewTimer(timeout) + select { + case <-c: + if !timeoutTimer.Stop() { + <-timeoutTimer.C + } + return false // completed normally + case <-timeoutTimer.C: + return true // timed out + } +} diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go new file mode 100644 index 0000000000..33e3494a0d --- /dev/null +++ b/dot/parachain/overseer/overseer_test.go @@ -0,0 +1,160 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package overseer + +import ( + "context" + "fmt" + "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type TestSubsystem struct { + name string +} + +func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error { + fmt.Printf("%s run\n", s.name) + counter := 0 + for { + select { + case <-ctx.Done(): + if err := ctx.Err(); err != nil { + fmt.Printf("%s ctx error: %v\n", s.name, err) + } + fmt.Printf("%s overseer stopping\n", s.name) + return nil + case overseerSignal := <-OverseerToSubSystem: + fmt.Printf("%s received from overseer %v\n", s.name, overseerSignal) + default: + // simulate work, and sending messages to overseer + r := rand.Intn(1000) + time.Sleep(time.Duration(r) * time.Millisecond) + SubSystemToOverseer <- fmt.Sprintf("hello from %v, i: %d", s.name, counter) + counter++ + } + } +} + +func TestStart2SubsytemsActivate1(t *testing.T) { + overseer := NewOverseer() + require.NotNil(t, overseer) + + subSystem1 := &TestSubsystem{name: "subSystem1"} + subSystem2 := &TestSubsystem{name: "subSystem2"} + + overseer.RegisterSubsystem(subSystem1) + overseer.RegisterSubsystem(subSystem2) + + err := overseer.Start() + require.NoError(t, err) + + done := make(chan struct{}) + // listen for errors from overseer + go func() { + for errC := range overseer.errChan { + fmt.Printf("overseer start error: %v\n", errC) + } + close(done) + }() + + time.Sleep(1000 * time.Millisecond) + activedLeaf := ActivatedLeaf{ + Hash: [32]byte{1}, + Number: 1, + } + overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem1) + + // let subsystems run for a bit + time.Sleep(4000 * time.Millisecond) + + err = overseer.Stop() + require.NoError(t, err) + + fmt.Printf("overseer stopped\n") + <-done +} + +func TestStart2SubsytemsActivate2Different(t *testing.T) { + overseer := NewOverseer() + require.NotNil(t, overseer) + + subSystem1 := &TestSubsystem{name: "subSystem1"} + subSystem2 := &TestSubsystem{name: "subSystem2"} + + overseer.RegisterSubsystem(subSystem1) + overseer.RegisterSubsystem(subSystem2) + + err := overseer.Start() + require.NoError(t, err) + done := make(chan struct{}) + go func() { + for errC := range overseer.errChan { + fmt.Printf("overseer start error: %v\n", errC) + } + close(done) + }() + + activedLeaf1 := ActivatedLeaf{ + Hash: [32]byte{1}, + Number: 1, + } + activedLeaf2 := ActivatedLeaf{ + Hash: [32]byte{2}, + Number: 2, + } + time.Sleep(250 * time.Millisecond) + overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf1}, subSystem1) + time.Sleep(400 * time.Millisecond) + overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf2}, subSystem2) + // let subsystems run for a bit + time.Sleep(3000 * time.Millisecond) + + err = overseer.Stop() + require.NoError(t, err) + + fmt.Printf("overseer stopped\n") + <-done +} + +func TestStart2SubsytemsActivate2Same(t *testing.T) { + overseer := NewOverseer() + require.NotNil(t, overseer) + + subSystem1 := &TestSubsystem{name: "subSystem1"} + subSystem2 := &TestSubsystem{name: "subSystem2"} + + overseer.RegisterSubsystem(subSystem1) + overseer.RegisterSubsystem(subSystem2) + + err := overseer.Start() + require.NoError(t, err) + done := make(chan struct{}) + go func() { + for errC := range overseer.errChan { + fmt.Printf("overseer start error: %v\n", errC) + } + close(done) + }() + + activedLeaf := ActivatedLeaf{ + Hash: [32]byte{1}, + Number: 1, + } + time.Sleep(300 * time.Millisecond) + overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem1) + time.Sleep(400 * time.Millisecond) + overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem2) + // let subsystems run for a bit + time.Sleep(2000 * time.Millisecond) + + err = overseer.Stop() + require.NoError(t, err) + + fmt.Printf("overseer stopped\n") + <-done +} diff --git a/dot/parachain/overseer/types.go b/dot/parachain/overseer/types.go new file mode 100644 index 0000000000..ece7c767bb --- /dev/null +++ b/dot/parachain/overseer/types.go @@ -0,0 +1,29 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package overseer + +import ( + "context" + + "github.com/ChainSafe/gossamer/lib/common" +) + +// ActivatedLeaf is a parachain head which we care to work on. +type ActivatedLeaf struct { + Hash common.Hash + Number uint32 +} + +// ActiveLeavesUpdate changes in the set of active leaves: the parachain heads which we care to work on. +// +// note: activated field indicates deltas, not complete sets. +type ActiveLeavesUpdate struct { + Activated ActivatedLeaf +} + +// Subsystem is an interface for subsystems to be registered with the overseer. +type Subsystem interface { + // Run runs the subsystem. + Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error +} From 703c2114d51c6030383b895f545114ce87798514 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Mon, 2 Oct 2023 17:20:07 +0530 Subject: [PATCH 30/85] chore(dot/peerset): added parachain related reputation values and reasons (#3498) Also, added enums for common reputation values --- dot/parachain/service.go | 2 ++ dot/peerset/constants.go | 56 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/dot/parachain/service.go b/dot/parachain/service.go index 61d1359268..ca8477f9ef 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/libp2p/go-libp2p/core/peer" @@ -157,4 +158,5 @@ type Network interface { batchHandler network.NotificationsMessageBatchHandler, maxSize uint64, ) error + ReportPeer(change peerset.ReputationChange, p peer.ID) } diff --git a/dot/peerset/constants.go b/dot/peerset/constants.go index c4011d534d..e5fc5be478 100644 --- a/dot/peerset/constants.go +++ b/dot/peerset/constants.go @@ -5,6 +5,18 @@ package peerset import "math" +const ( + CostMinor Reputation = -100000 + CostMajor Reputation = -300000 + CostMinorRepeated Reputation = -200000 + CostMajorRepeated Reputation = -600000 + Malicious Reputation = math.MinInt32 + BenefitMajorFirst Reputation = 300000 + BenefitMajor Reputation = 200000 + BenefitMinorFirst Reputation = 15000 + BenefitMinor Reputation = 10000 +) + // ReputationChange value and reason const ( @@ -14,7 +26,7 @@ const ( BadMessageReason = "Bad message" // BadProtocolValue used when a peer is on unsupported protocol version. - BadProtocolValue Reputation = math.MinInt32 + BadProtocolValue Reputation = Malicious // BadProtocolReason used when a peer is on unsupported protocol version. BadProtocolReason = "Unsupported protocol" @@ -64,7 +76,47 @@ const ( BadJustificationReason = "Bad justification" // GenesisMismatch is used when peer has a different genesis - GenesisMismatch Reputation = math.MinInt32 + GenesisMismatch Reputation = Malicious // GenesisMismatchReason used when a peer has a different genesis GenesisMismatchReason = "Genesis mismatch" + + // BenefitNotifyGoodValue is used when a collator was noted good by another subsystem + BenefitNotifyGoodValue Reputation = BenefitMinor + // BenefitNotifyGoodReason is used when a collator was noted good by another subsystem + BenefitNotifyGoodReason = "A collator was noted good by another subsystem" + + // UnexpectedMessageValue is used when validator side of the collator protocol receives an unexpected message + UnexpectedMessageValue Reputation = CostMinor + // UnexpectedMessageReason is used when validator side of the collator protocol receives an unexpected message + UnexpectedMessageReason = "An unexpected message" + + // CurruptedMessageValue is used when message could not be decoded properly. + CurruptedMessageValue = CostMinor + // CurruptedMessageReason is used when message could not be decoded properly. + CurruptedMessageReason = "Message was corrupt" + + // NetworkErrorValue is used when network errors that originated at the remote host should have same cost as timeout. + NetworkErrorValue = CostMinor + // NetworkErrorReason is used when network errors that originated at the remote host should have same cost as timeout. + NetworkErrorReason = "Some network error" + + // InvalidSignatureValue is used when signature of the network message is invalid. + InvalidSignatureValue Reputation = Malicious + // InvalidSignatureReason is used when signature of the network message is invalid. + InvalidSignatureReason = "Invalid network message signature" + + // ReportBadCollatorValue is used when a collator was reported to be bad by another subsystem + ReportBadCollatorValue Reputation = Malicious + // ReportBadCollatorReason is used when a collator was reported to be bad by another subsystem + ReportBadCollatorReason = "A collator was reported by another subsystem" + + // WrongParaValue is used when a collator provided a collation for the wrong para + WrongParaValue Reputation = Malicious + // WrongParaReason is used when a collator provided a collation for the wrong para + WrongParaReason = "A collator provided a collation for the wrong para" + + // UnneededCollatorValue is used when an unneeded collator connected + UnneededCollatorValue = CostMinor + // UnneededCollatorReason is used when an unneeded collator connected + UnneededCollatorReason = "An unneeded collator connected" ) From 36d91ce2bc1c950475ac4b4e377da3f6b4310b52 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Mon, 2 Oct 2023 18:12:49 +0530 Subject: [PATCH 31/85] feat(dot/parachain): Implement candidate backing skeleton (#3497) --- .../approval_distribution_message.go | 2 +- .../approval_distribution_message_test.go | 4 +- dot/parachain/available_data_fetching.go | 14 +- dot/parachain/available_data_fetching_test.go | 6 +- dot/parachain/backing/candidate_backing.go | 121 ++++++++++++++++++ dot/parachain/candidate_validation.go | 2 +- dot/parachain/candidate_validation_test.go | 2 +- dot/parachain/chunk_fetching.go | 2 +- dot/parachain/chunk_fetching_test.go | 4 +- dot/parachain/collation_fetching.go | 2 +- dot/parachain/collation_fetching_test.go | 2 +- dot/parachain/collation_protocol.go | 4 +- dot/parachain/collation_protocol_test.go | 8 +- dot/parachain/mocks_test.go | 5 +- dot/parachain/overseer/overseer.go | 7 +- dot/parachain/pov_fetching.go | 12 +- dot/parachain/pov_fetching_test.go | 7 +- dot/parachain/service.go | 13 +- .../statement_distribution_message.go | 27 +--- .../statement_distribution_message_test.go | 16 +-- dot/parachain/statement_fetching.go | 2 +- dot/parachain/statement_fetching_test.go | 6 +- dot/parachain/statement_test.go | 8 +- dot/parachain/{ => types}/statement.go | 11 +- dot/parachain/types/types.go | 39 ++++++ dot/parachain/validation_protocol.go | 2 +- dot/parachain/validation_protocol_test.go | 10 +- 27 files changed, 236 insertions(+), 102 deletions(-) create mode 100644 dot/parachain/backing/candidate_backing.go rename dot/parachain/{ => types}/statement.go (81%) diff --git a/dot/parachain/approval_distribution_message.go b/dot/parachain/approval_distribution_message.go index 84cdfb9320..63807e443e 100644 --- a/dot/parachain/approval_distribution_message.go +++ b/dot/parachain/approval_distribution_message.go @@ -134,7 +134,7 @@ type IndirectSignedApprovalVote struct { // ValidatorIndex the validator index. ValidatorIndex parachaintypes.ValidatorIndex `scale:"3"` // Signature the signature of the validator. - Signature ValidatorSignature `scale:"4"` + Signature parachaintypes.ValidatorSignature `scale:"4"` } // Approvals for candidates in some recent, unfinalized block. diff --git a/dot/parachain/approval_distribution_message_test.go b/dot/parachain/approval_distribution_message_test.go index e4d9982d4a..59da7a4f94 100644 --- a/dot/parachain/approval_distribution_message_test.go +++ b/dot/parachain/approval_distribution_message_test.go @@ -116,7 +116,7 @@ func TestEncodeApprovalDistributionMessageApprovals(t *testing.T) { BlockHash: hashA, CandidateIndex: CandidateIndex(2), ValidatorIndex: parachaintypes.ValidatorIndex(3), - Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + Signature: parachaintypes.ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, }, @@ -164,7 +164,7 @@ func TestDecodeApprovalDistributionMessageApprovals(t *testing.T) { BlockHash: hashA, CandidateIndex: CandidateIndex(2), ValidatorIndex: parachaintypes.ValidatorIndex(3), - Signature: ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + Signature: parachaintypes.ValidatorSignature{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, }, diff --git a/dot/parachain/available_data_fetching.go b/dot/parachain/available_data_fetching.go index 8e34648ded..36773048f7 100644 --- a/dot/parachain/available_data_fetching.go +++ b/dot/parachain/available_data_fetching.go @@ -14,7 +14,7 @@ import ( // AvailableDataFetchingRequest represents a request to retrieve all available data for a specific candidate. type AvailableDataFetchingRequest struct { // Hash of the candidate for which the available data is requested. - CandidateHash CandidateHash + CandidateHash parachaintypes.CandidateHash } // Encode returns the SCALE encoding of the AvailableDataFetchingRequest @@ -51,22 +51,12 @@ func (a *AvailableDataFetchingResponse) Value() (val scale.VaryingDataTypeValue, // AvailableData represents the data that is kept available for each candidate included in the relay chain. type AvailableData struct { // The Proof-of-Validation (PoV) of the candidate - PoV PoV `scale:"1"` + PoV parachaintypes.PoV `scale:"1"` // The persisted validation data needed for approval checks ValidationData PersistedValidationData `scale:"2"` } -// PoV represents a Proof-of-Validity block (PoV block) or a parachain block. -// It contains the necessary data for the parachain specific state transition logic. -type PoV struct { - BlockData BlockData `scale:"1"` -} - -// BlockData represents parachain block data. -// It contains everything required to validate para-block, may contain block and witness data. -type BlockData []byte - // Index returns the index of varying data type func (AvailableData) Index() uint { return 0 diff --git a/dot/parachain/available_data_fetching_test.go b/dot/parachain/available_data_fetching_test.go index 72bce779cb..8c1ff96015 100644 --- a/dot/parachain/available_data_fetching_test.go +++ b/dot/parachain/available_data_fetching_test.go @@ -14,8 +14,8 @@ import ( func TestEncodeAvailableDataFetchingRequest(t *testing.T) { availableDataFetchingRequest := AvailableDataFetchingRequest{ - CandidateHash: CandidateHash{ - common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + CandidateHash: parachaintypes.CandidateHash{ + Value: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, } @@ -32,7 +32,7 @@ func TestAvailableDataFetchingResponse(t *testing.T) { testHash := common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19") testBytes := testHash.ToBytes() availableData := AvailableData{ - PoV: PoV{BlockData: testBytes}, + PoV: parachaintypes.PoV{BlockData: testBytes}, ValidationData: PersistedValidationData{ ParentHead: testBytes, RelayParentNumber: parachaintypes.BlockNumber(4), diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go new file mode 100644 index 0000000000..6941a2a080 --- /dev/null +++ b/dot/parachain/backing/candidate_backing.go @@ -0,0 +1,121 @@ +package backing + +import ( + "context" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/internal/log" + "github.com/ChainSafe/gossamer/lib/common" +) + +var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-candidate-backing")) + +type CandidateBacking struct { + SubSystemToOverseer chan<- any + OverseerToSubSystem <-chan any +} + +// ActiveLeavesUpdate is a messages from overseer +type ActiveLeavesUpdate struct { + // TODO: Complete this struct #3503 +} + +// GetBackedCandidates is a message received from overseer that requests a set of backable +// candidates that could be backed in a child of the given relay-parent. +type GetBackedCandidates []struct { + CandidateHash parachaintypes.CandidateHash + CandidateRelayParent common.Hash +} + +// CanSecond is a request made to the candidate backing subsystem to determine whether it is permissible +// to second a given candidate. +// The rule for seconding candidates is: Collations must either be built on top of the root of a fragment tree +// or have a parent node that represents the backed candidate. +type CanSecond struct { + CandidateParaID parachaintypes.ParaID + CandidateRelayParent common.Hash + CandidateHash parachaintypes.CandidateHash + ParentHeadDataHash common.Hash +} + +// Second is a message received from overseer. Candidate Backing subsystem should second the given +// candidate in the context of the given relay parent. This candidate must be validated. +type Second struct { + RelayParent common.Hash + CandidateReceipt parachaintypes.CandidateReceipt + PersistedValidationData parachaintypes.PersistedValidationData + PoV parachaintypes.PoV +} + +// Statement represents a validator's assessment of a specific candidate. If there are disagreements +// regarding the validity of this assessment, they should be addressed through the Disputes Subsystem, +// with the actual escalation deferred until the approval voting stage to ensure its availability. +// Meanwhile, agreements are straightforwardly counted until a quorum is achieved. +type Statement struct { + RelayParent common.Hash + SignedFullStatement SignedFullStatementWithPVD +} + +func New(overseerChan chan<- any) *CandidateBacking { + return &CandidateBacking{ + SubSystemToOverseer: overseerChan, + } +} + +func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan any, subSystemToOverseer chan any) error { + // TODO: handle_validated_candidate_command + // There is one more case where we handle results of candidate validation. + // My feeling is that instead of doing it here, we would be able to do that along with processing + // other backing related overseer message. + // This would become more clear after we complete processMessages function. It would give us clarity + // if we need background_validation_rx or background_validation_tx, as done in rust. + cb.processMessages() + return nil +} + +func (cb *CandidateBacking) processMessages() { + for msg := range cb.OverseerToSubSystem { + // process these received messages by referencing + // https://github.com/paritytech/polkadot-sdk/blob/769bdd3ff33a291cbc70a800a3830638467e42a2/polkadot/node/core/backing/src/lib.rs#L741 + switch msg.(type) { + case ActiveLeavesUpdate: + cb.handleActiveLeavesUpdate() + case GetBackedCandidates: + cb.handleGetBackedCandidates() + case CanSecond: + cb.handleCanSecond() + case Second: + cb.handleSecond() + case Statement: + cb.handleStatement() + default: + logger.Error("unknown message type") + } + } +} + +func (cb *CandidateBacking) handleActiveLeavesUpdate() { + // TODO: Implement this #3503 +} + +func (cb *CandidateBacking) handleGetBackedCandidates() { + // TODO: Implement this #3504 +} + +func (cb *CandidateBacking) handleCanSecond() { + // TODO: Implement this #3505 +} + +func (cb *CandidateBacking) handleSecond() { + // TODO: Implement this #3506 +} + +func (cb *CandidateBacking) handleStatement() { + // TODO: Implement this #3507 +} + +// SignedFullStatementWithPVD represents a signed full statement along with associated Persisted Validation Data (PVD). +type SignedFullStatementWithPVD struct { + SignedFullStatement parachaintypes.UncheckedSignedFullStatement + PersistedValidationData *parachaintypes.PersistedValidationData +} diff --git a/dot/parachain/candidate_validation.go b/dot/parachain/candidate_validation.go index fff3f7938f..43afabaa9b 100644 --- a/dot/parachain/candidate_validation.go +++ b/dot/parachain/candidate_validation.go @@ -23,7 +23,7 @@ var ( // PoVRequestor gets proof of validity by issuing network requests to validators of the current backing group. // TODO: Implement PoV requestor type PoVRequestor interface { - RequestPoV(povHash common.Hash) PoV + RequestPoV(povHash common.Hash) parachaintypes.PoV } func getValidationData(runtimeInstance parachainruntime.RuntimeInstance, paraID uint32, diff --git a/dot/parachain/candidate_validation_test.go b/dot/parachain/candidate_validation_test.go index 004a48a530..85be2678a0 100644 --- a/dot/parachain/candidate_validation_test.go +++ b/dot/parachain/candidate_validation_test.go @@ -70,7 +70,7 @@ func TestValidateFromChainState(t *testing.T) { }) require.NoError(t, err) - pov := PoV{ + pov := parachaintypes.PoV{ BlockData: bd, } diff --git a/dot/parachain/chunk_fetching.go b/dot/parachain/chunk_fetching.go index a6ef9eea21..8669c7cb06 100644 --- a/dot/parachain/chunk_fetching.go +++ b/dot/parachain/chunk_fetching.go @@ -13,7 +13,7 @@ import ( // ChunkFetchingRequest represents a request to retrieve chunks of a parachain candidate type ChunkFetchingRequest struct { // Hash of candidate we want a chunk for. - CandidateHash CandidateHash `scale:"1"` + CandidateHash parachaintypes.CandidateHash `scale:"1"` // The index of the chunk to fetch. Index parachaintypes.ValidatorIndex `scale:"2"` diff --git a/dot/parachain/chunk_fetching_test.go b/dot/parachain/chunk_fetching_test.go index 5fcbcc04d2..19257e7e48 100644 --- a/dot/parachain/chunk_fetching_test.go +++ b/dot/parachain/chunk_fetching_test.go @@ -14,8 +14,8 @@ import ( func TestEncodeChunkFetchingRequest(t *testing.T) { chunkFetchingRequest := ChunkFetchingRequest{ - CandidateHash: CandidateHash{ - common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + CandidateHash: parachaintypes.CandidateHash{ + Value: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, Index: parachaintypes.ValidatorIndex(8), } diff --git a/dot/parachain/collation_fetching.go b/dot/parachain/collation_fetching.go index d46e655934..ef53ed323e 100644 --- a/dot/parachain/collation_fetching.go +++ b/dot/parachain/collation_fetching.go @@ -32,7 +32,7 @@ type CollationFetchingResponse scale.VaryingDataType // Collation represents a requested collation to be delivered type Collation struct { CandidateReceipt parachaintypes.CandidateReceipt `scale:"1"` - PoV PoV `scale:"2"` + PoV parachaintypes.PoV `scale:"2"` } // Index returns the index of varying data type diff --git a/dot/parachain/collation_fetching_test.go b/dot/parachain/collation_fetching_test.go index 148d7ae93b..2e58389b85 100644 --- a/dot/parachain/collation_fetching_test.go +++ b/dot/parachain/collation_fetching_test.go @@ -52,7 +52,7 @@ func TestCollationFetchingResponse(t *testing.T) { }, CommitmentsHash: testHash, }, - PoV: PoV{BlockData{1, 2, 3}}, + PoV: parachaintypes.PoV{BlockData: parachaintypes.BlockData{1, 2, 3}}, } // expected encoding is generated by running rust test code: diff --git a/dot/parachain/collation_protocol.go b/dot/parachain/collation_protocol.go index 4978327fe9..12e45fd431 100644 --- a/dot/parachain/collation_protocol.go +++ b/dot/parachain/collation_protocol.go @@ -106,8 +106,8 @@ func (AdvertiseCollation) Index() uint { // CollationSeconded represents that a collation sent to a validator was seconded. type CollationSeconded struct { - Hash common.Hash `scale:"1"` - UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` + Hash common.Hash `scale:"1"` + UncheckedSignedFullStatement parachaintypes.UncheckedSignedFullStatement `scale:"2"` } // Index returns the index of varying data type diff --git a/dot/parachain/collation_protocol_test.go b/dot/parachain/collation_protocol_test.go index 4c2908d4d2..a4efe9f870 100644 --- a/dot/parachain/collation_protocol_test.go +++ b/dot/parachain/collation_protocol_test.go @@ -40,12 +40,12 @@ func TestCollationProtocol(t *testing.T) { tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) - var validatorSignature ValidatorSignature + var validatorSignature parachaintypes.ValidatorSignature copy(validatorSignature[:], tempSignature) hash5 := getDummyHash(5) - secondedEnumValue := Seconded{ + secondedEnumValue := parachaintypes.Seconded{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, @@ -68,7 +68,7 @@ func TestCollationProtocol(t *testing.T) { }, } - statementVDTWithSeconded := NewStatementVDT() + statementVDTWithSeconded := parachaintypes.NewStatementVDT() err := statementVDTWithSeconded.Set(secondedEnumValue) require.NoError(t, err) @@ -95,7 +95,7 @@ func TestCollationProtocol(t *testing.T) { name: "CollationSeconded", enumValue: CollationSeconded{ Hash: hash5, - UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + UncheckedSignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ Payload: statementVDTWithSeconded, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, diff --git a/dot/parachain/mocks_test.go b/dot/parachain/mocks_test.go index 022531504b..cabf50f6f3 100644 --- a/dot/parachain/mocks_test.go +++ b/dot/parachain/mocks_test.go @@ -7,6 +7,7 @@ package parachain import ( reflect "reflect" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" common "github.com/ChainSafe/gossamer/lib/common" gomock "github.com/golang/mock/gomock" ) @@ -35,10 +36,10 @@ func (m *MockPoVRequestor) EXPECT() *MockPoVRequestorMockRecorder { } // RequestPoV mocks base method. -func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) PoV { +func (m *MockPoVRequestor) RequestPoV(arg0 common.Hash) parachaintypes.PoV { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "RequestPoV", arg0) - ret0, _ := ret[0].(PoV) + ret0, _ := ret[0].(parachaintypes.PoV) return ret0 } diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 3f6eea9b94..7d7b1cec1a 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -39,8 +39,11 @@ func NewOverseer() *Overseer { // RegisterSubsystem registers a subsystem with the overseer, // Add OverseerToSubSystem channel to subsystem, which will be passed to subsystem's Run method. -func (o *Overseer) RegisterSubsystem(subsystem Subsystem) { - o.subsystems[subsystem] = make(chan any) +func (o *Overseer) RegisterSubsystem(subsystem Subsystem) chan any { + OverseerToSubSystem := make(chan any) + o.subsystems[subsystem] = OverseerToSubSystem + + return OverseerToSubSystem } func (o *Overseer) Start() error { diff --git a/dot/parachain/pov_fetching.go b/dot/parachain/pov_fetching.go index e840d1348c..426f1b3b63 100644 --- a/dot/parachain/pov_fetching.go +++ b/dot/parachain/pov_fetching.go @@ -6,13 +6,14 @@ package parachain import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/pkg/scale" ) // PoVFetchingRequest represents a request to fetch the advertised collation at the relay-parent. type PoVFetchingRequest struct { // Hash of the candidate for which we want to retrieve a Proof-of-Validity (PoV). - CandidateHash CandidateHash + CandidateHash parachaintypes.CandidateHash } // Encode returns the SCALE encoding of the PoVFetchingRequest @@ -25,7 +26,7 @@ type PoVFetchingResponse scale.VaryingDataType // NewPoVFetchingResponse returns a new PoV fetching response varying data type func NewPoVFetchingResponse() PoVFetchingResponse { - vdt := scale.MustNewVaryingDataType(PoV{}, NoSuchPoV{}) + vdt := scale.MustNewVaryingDataType(parachaintypes.PoV{}, NoSuchPoV{}) return PoVFetchingResponse(vdt) } @@ -46,11 +47,6 @@ func (p *PoVFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error return vdt.Value() } -// Index returns the index of varying data type -func (PoV) Index() uint { - return 0 -} - // NoSuchPoV indicates that the requested PoV was not found in the store. type NoSuchPoV struct{} @@ -76,7 +72,7 @@ func (p *PoVFetchingResponse) String() string { } v, _ := p.Value() - pov, ok := v.(PoV) + pov, ok := v.(parachaintypes.PoV) if !ok { return "PoVFetchingResponse=NoSuchPoV" } diff --git a/dot/parachain/pov_fetching_test.go b/dot/parachain/pov_fetching_test.go index 75c8089ea4..283ceef7b8 100644 --- a/dot/parachain/pov_fetching_test.go +++ b/dot/parachain/pov_fetching_test.go @@ -6,6 +6,7 @@ package parachain import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" @@ -13,8 +14,8 @@ import ( func TestEncodePoVFetchingRequest(t *testing.T) { poVFetchingRequest := PoVFetchingRequest{ - CandidateHash: CandidateHash{ - common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), + CandidateHash: parachaintypes.CandidateHash{ + Value: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, } @@ -37,7 +38,7 @@ func TestPoVFetchingResponse(t *testing.T) { }{ { name: "PoV", - value: PoV{BlockData: testBytes}, + value: parachaintypes.PoV{BlockData: testBytes}, encodeValue: common.MustHexToBytes("0x0080677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, { diff --git a/dot/parachain/service.go b/dot/parachain/service.go index ca8477f9ef..d592c0e0ea 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -8,6 +8,9 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain/backing" + "github.com/ChainSafe/gossamer/dot/parachain/overseer" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" @@ -116,6 +119,12 @@ func (Service) Stop() error { // main loop of parachain service func (s Service) run() { + overseer := overseer.NewOverseer() + + candidateBacking := backing.New(overseer.SubsystemsToOverseer) + candidateBacking.OverseerToSubSystem = overseer.RegisterSubsystem(candidateBacking) + + // TODO: Add `Prospective Parachains` Subsystem. create an issue. // NOTE: this is a temporary test, just to show that we can send messages to peers // @@ -127,9 +136,9 @@ func (s Service) run() { statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} err := statementDistributionLargeStatement.Set(LargePayload{ RelayParent: common.Hash{}, - CandidateHash: CandidateHash{Value: common.Hash{}}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{}}, SignedBy: 5, - Signature: ValidatorSignature{}, + Signature: parachaintypes.ValidatorSignature{}, }) if err != nil { logger.Errorf("creating test statement message: %w\n", err) diff --git a/dot/parachain/statement_distribution_message.go b/dot/parachain/statement_distribution_message.go index a17e48accb..612952727d 100644 --- a/dot/parachain/statement_distribution_message.go +++ b/dot/parachain/statement_distribution_message.go @@ -45,8 +45,8 @@ func (sdm *StatementDistributionMessage) Value() (scale.VaryingDataTypeValue, er // Statement represents a signed full statement under a given relay-parent. type Statement struct { - Hash common.Hash `scale:"1"` - UncheckedSignedFullStatement UncheckedSignedFullStatement `scale:"2"` + Hash common.Hash `scale:"1"` + UncheckedSignedFullStatement parachaintypes.UncheckedSignedFullStatement `scale:"2"` } // Index returns the index of varying data type @@ -66,36 +66,17 @@ func (LargePayload) Index() uint { return 1 } -// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified. -type UncheckedSignedFullStatement struct { - // The payload is part of the signed data. The rest is the signing context, - // which is known both at signing and at validation. - Payload StatementVDT `scale:"1"` - - // The index of the validator signing this statement. - ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` - - // The signature by the validator of the signed payload. - Signature ValidatorSignature `scale:"3"` -} - // StatementMetadata represents the data that makes a statement unique. type StatementMetadata struct { // Relay parent this statement is relevant under. RelayParent common.Hash `scale:"1"` // Hash of the candidate that got validated. - CandidateHash CandidateHash `scale:"2"` + CandidateHash parachaintypes.CandidateHash `scale:"2"` // Validator that attested the validity. SignedBy parachaintypes.ValidatorIndex `scale:"3"` // Signature of seconding validator. - Signature ValidatorSignature `scale:"4"` + Signature parachaintypes.ValidatorSignature `scale:"4"` } - -// ValidatorSignature represents the signature with which parachain validators sign blocks. -type ValidatorSignature Signature - -// Signature represents a cryptographic signature. -type Signature [64]byte diff --git a/dot/parachain/statement_distribution_message_test.go b/dot/parachain/statement_distribution_message_test.go index 3788783a53..34fa3c8c60 100644 --- a/dot/parachain/statement_distribution_message_test.go +++ b/dot/parachain/statement_distribution_message_test.go @@ -35,7 +35,7 @@ func TestStatementDistributionMessage(t *testing.T) { tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) - var validatorSignature ValidatorSignature + var validatorSignature parachaintypes.ValidatorSignature copy(validatorSignature[:], tempSignature) var collatorID parachaintypes.CollatorID @@ -44,11 +44,11 @@ func TestStatementDistributionMessage(t *testing.T) { hash5 := getDummyHash(5) - statementVDTWithValid := NewStatementVDT() - err := statementVDTWithValid.Set(Valid{hash5}) + statementVDTWithValid := parachaintypes.NewStatementVDT() + err := statementVDTWithValid.Set(parachaintypes.Valid{Value: hash5}) require.NoError(t, err) - secondedEnumValue := Seconded{ + secondedEnumValue := parachaintypes.Seconded{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, @@ -71,13 +71,13 @@ func TestStatementDistributionMessage(t *testing.T) { }, } - statementVDTWithSeconded := NewStatementVDT() + statementVDTWithSeconded := parachaintypes.NewStatementVDT() err = statementVDTWithSeconded.Set(secondedEnumValue) require.NoError(t, err) signedFullStatementWithValid := Statement{ Hash: hash5, - UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + UncheckedSignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ Payload: statementVDTWithValid, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, @@ -86,7 +86,7 @@ func TestStatementDistributionMessage(t *testing.T) { signedFullStatementWithSeconded := Statement{ Hash: hash5, - UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + UncheckedSignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ Payload: statementVDTWithSeconded, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, @@ -95,7 +95,7 @@ func TestStatementDistributionMessage(t *testing.T) { largePayload := LargePayload{ RelayParent: hash5, - CandidateHash: CandidateHash{hash5}, + CandidateHash: parachaintypes.CandidateHash{Value: hash5}, SignedBy: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, } diff --git a/dot/parachain/statement_fetching.go b/dot/parachain/statement_fetching.go index f78537be84..f1c40e3417 100644 --- a/dot/parachain/statement_fetching.go +++ b/dot/parachain/statement_fetching.go @@ -17,7 +17,7 @@ type StatementFetchingRequest struct { RelayParent common.Hash `scale:"1"` // Hash of candidate that was used create the `CommitedCandidateRecept`. - CandidateHash CandidateHash `scale:"2"` + CandidateHash parachaintypes.CandidateHash `scale:"2"` } // Encode returns the SCALE encoding of the StatementFetchingRequest. diff --git a/dot/parachain/statement_fetching_test.go b/dot/parachain/statement_fetching_test.go index b2a2b48af5..3b099bec08 100644 --- a/dot/parachain/statement_fetching_test.go +++ b/dot/parachain/statement_fetching_test.go @@ -35,7 +35,7 @@ func TestEncodeStatementFetchingRequest(t *testing.T) { name: "all_4_in_hash", request: StatementFetchingRequest{ RelayParent: getDummyHash(4), - CandidateHash: CandidateHash{Value: getDummyHash(4)}, + CandidateHash: parachaintypes.CandidateHash{Value: getDummyHash(4)}, }, expectedEncode: common.MustHexToBytes(testDataStatement["all4InCommonHash"]), }, @@ -43,7 +43,7 @@ func TestEncodeStatementFetchingRequest(t *testing.T) { name: "all_7_in_hash", request: StatementFetchingRequest{ RelayParent: getDummyHash(7), - CandidateHash: CandidateHash{Value: getDummyHash(7)}, + CandidateHash: parachaintypes.CandidateHash{Value: getDummyHash(7)}, }, expectedEncode: common.MustHexToBytes(testDataStatement["all7InCommonHash"]), }, @@ -51,7 +51,7 @@ func TestEncodeStatementFetchingRequest(t *testing.T) { name: "random_hash", request: StatementFetchingRequest{ RelayParent: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), - CandidateHash: CandidateHash{ + CandidateHash: parachaintypes.CandidateHash{ Value: common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), }, }, diff --git a/dot/parachain/statement_test.go b/dot/parachain/statement_test.go index 7c407ced7b..7b850afee6 100644 --- a/dot/parachain/statement_test.go +++ b/dot/parachain/statement_test.go @@ -44,7 +44,7 @@ func TestStatementVDT(t *testing.T) { hash5 := getDummyHash(5) - secondedEnumValue := Seconded{ + secondedEnumValue := parachaintypes.Seconded{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, @@ -81,7 +81,7 @@ func TestStatementVDT(t *testing.T) { }, { name: "Valid", - enumValue: Valid{hash5}, + enumValue: parachaintypes.Valid{Value: hash5}, encodingValue: common.MustHexToBytes("0x020505050505050505050505050505050505050505050505050505050505050505"), expectedErr: nil, }, @@ -99,7 +99,7 @@ func TestStatementVDT(t *testing.T) { t.Run("marshal", func(t *testing.T) { t.Parallel() - vdt := NewStatementVDT() + vdt := parachaintypes.NewStatementVDT() err := vdt.Set(c.enumValue) if c.expectedErr != nil { @@ -120,7 +120,7 @@ func TestStatementVDT(t *testing.T) { return } - vdt := NewStatementVDT() + vdt := parachaintypes.NewStatementVDT() err := scale.Unmarshal(c.encodingValue, &vdt) require.NoError(t, err) diff --git a/dot/parachain/statement.go b/dot/parachain/types/statement.go similarity index 81% rename from dot/parachain/statement.go rename to dot/parachain/types/statement.go index bb196510c0..45f49b2ca3 100644 --- a/dot/parachain/statement.go +++ b/dot/parachain/types/statement.go @@ -1,13 +1,11 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package parachaintypes import ( "fmt" - parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" - "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" ) @@ -44,7 +42,7 @@ func (s *StatementVDT) Value() (scale.VaryingDataTypeValue, error) { } // Seconded represents a statement that a validator seconds a candidate. -type Seconded parachaintypes.CommittedCandidateReceipt +type Seconded CommittedCandidateReceipt // Index returns the index of varying data type func (Seconded) Index() uint { @@ -58,8 +56,3 @@ type Valid CandidateHash func (Valid) Index() uint { return 2 } - -// CandidateHash makes it easy to enforce that a hash is a candidate hash on the type level. -type CandidateHash struct { - Value common.Hash `scale:"1"` -} diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index e43225897c..e4b00f6355 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -496,3 +496,42 @@ func NewOccupiedCoreAssumption() OccupiedCoreAssumption { return OccupiedCoreAssumption(vdt) } + +// CandidateHash makes it easy to enforce that a hash is a candidate hash on the type level. +type CandidateHash struct { + Value common.Hash `scale:"1"` +} + +// PoV represents a Proof-of-Validity block (PoV block) or a parachain block. +// It contains the necessary data for the parachain specific state transition logic. +type PoV struct { + BlockData BlockData `scale:"1"` +} + +// Index returns the index of varying data type +func (PoV) Index() uint { + return 0 +} + +// BlockData represents parachain block data. +// It contains everything required to validate para-block, may contain block and witness data. +type BlockData []byte + +// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified. +type UncheckedSignedFullStatement struct { + // The payload is part of the signed data. The rest is the signing context, + // which is known both at signing and at validation. + Payload StatementVDT `scale:"1"` + + // The index of the validator signing this statement. + ValidatorIndex ValidatorIndex `scale:"2"` + + // The signature by the validator of the signed payload. + Signature ValidatorSignature `scale:"3"` +} + +// ValidatorSignature represents the signature with which parachain validators sign blocks. +type ValidatorSignature Signature + +// Signature represents a cryptographic signature. +type Signature [64]byte diff --git a/dot/parachain/validation_protocol.go b/dot/parachain/validation_protocol.go index 4dc800dd79..960830669b 100644 --- a/dot/parachain/validation_protocol.go +++ b/dot/parachain/validation_protocol.go @@ -25,7 +25,7 @@ type UncheckedSignedAvailabilityBitfield struct { ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` // The signature by the validator of the signed payload. - Signature ValidatorSignature `scale:"3"` + Signature parachaintypes.ValidatorSignature `scale:"3"` } // Bitfield avalibility bitfield for given relay-parent hash diff --git a/dot/parachain/validation_protocol_test.go b/dot/parachain/validation_protocol_test.go index 32e35d5af7..23b3eb809f 100644 --- a/dot/parachain/validation_protocol_test.go +++ b/dot/parachain/validation_protocol_test.go @@ -70,7 +70,7 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { println!("encode validation proto => {:?}\n\n", val_proto.encode()); } */ - var validatorSignature ValidatorSignature + var validatorSignature parachaintypes.ValidatorSignature tempSignature := common.MustHexToBytes(testValidationProtocolHex["validatorSignature"]) copy(validatorSignature[:], tempSignature) @@ -145,7 +145,7 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { var collatorSignature parachaintypes.CollatorSignature copy(collatorSignature[:], tempSignature) - statementSecond := Seconded{ + statementSecond := parachaintypes.Seconded{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: 1, RelayParent: hashA, @@ -166,13 +166,13 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { HrmpWatermark: 0, }, } - statementVDT := NewStatementVDT() + statementVDT := parachaintypes.NewStatementVDT() statementVDT.Set(statementSecond) statementDistributionStatement := StatementDistribution{NewStatementDistributionMessage()} statementDistributionStatement.Set(Statement{ Hash: hashA, - UncheckedSignedFullStatement: UncheckedSignedFullStatement{ + UncheckedSignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ Payload: statementVDT, ValidatorIndex: 5, Signature: validatorSignature, @@ -205,7 +205,7 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} statementDistributionLargeStatement.Set(LargePayload{ RelayParent: hashA, - CandidateHash: CandidateHash{Value: hashA}, + CandidateHash: parachaintypes.CandidateHash{Value: hashA}, SignedBy: 5, Signature: validatorSignature, }) From 56496bc8bc074792e9252b1cad7384c15ced1740 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Thu, 5 Oct 2023 21:47:22 +0530 Subject: [PATCH 32/85] collator protocol skeleton (#3512) --- dot/parachain/backing/candidate_backing.go | 3 + .../collation_fetching.go | 26 +- .../collation_fetching_test.go | 6 +- .../message.go} | 79 ++++- .../message_test.go} | 16 +- dot/parachain/collator-protocol/register.go | 54 +++ .../testdata/collation_protocol.yaml | 1 + .../collator-protocol/validator_side.go | 307 ++++++++++++++++++ dot/parachain/network_protocols.go | 3 +- dot/parachain/pov_fetching.go | 10 +- dot/parachain/pov_fetching_test.go | 2 +- dot/parachain/service.go | 50 ++- .../statement_distribution_message_test.go | 19 ++ dot/parachain/types/statement.go | 13 + dot/parachain/{ => types}/statement_test.go | 45 ++- dot/parachain/types/testdata/statement.yaml | 3 + dot/parachain/types/types.go | 23 +- dot/parachain/validation_protocol.go | 2 +- 18 files changed, 561 insertions(+), 101 deletions(-) rename dot/parachain/{ => collator-protocol}/collation_fetching.go (80%) rename dot/parachain/{ => collator-protocol}/collation_fetching_test.go (96%) rename dot/parachain/{collation_protocol.go => collator-protocol/message.go} (68%) rename dot/parachain/{collation_protocol_test.go => collator-protocol/message_test.go} (93%) create mode 100644 dot/parachain/collator-protocol/register.go rename dot/parachain/{ => collator-protocol}/testdata/collation_protocol.yaml (92%) create mode 100644 dot/parachain/collator-protocol/validator_side.go rename dot/parachain/{ => types}/statement_test.go (76%) create mode 100644 dot/parachain/types/testdata/statement.yaml diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 6941a2a080..839bf8a36a 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package backing import ( diff --git a/dot/parachain/collation_fetching.go b/dot/parachain/collator-protocol/collation_fetching.go similarity index 80% rename from dot/parachain/collation_fetching.go rename to dot/parachain/collator-protocol/collation_fetching.go index ef53ed323e..5ba432b491 100644 --- a/dot/parachain/collation_fetching.go +++ b/dot/parachain/collator-protocol/collation_fetching.go @@ -1,16 +1,24 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package collatorprotocol import ( "fmt" + "time" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" ) +const ( + /// Maximum PoV size we support right now. + maxPoVSize = 5 * 1024 * 1024 + collationFetchingRequestTimeout = time.Millisecond * 1200 + collationFetchingMaxResponseSize = maxPoVSize + 10000 // 10MB +) + // CollationFetchingRequest represents a request to retrieve // the advertised collation at the specified relay chain block. type CollationFetchingRequest struct { @@ -22,27 +30,23 @@ type CollationFetchingRequest struct { } // Encode returns the SCALE encoding of the CollationFetchingRequest -func (c *CollationFetchingRequest) Encode() ([]byte, error) { - return scale.Marshal(*c) +func (c CollationFetchingRequest) Encode() ([]byte, error) { + return scale.Marshal(c) } // CollationFetchingResponse represents a response sent by collator type CollationFetchingResponse scale.VaryingDataType -// Collation represents a requested collation to be delivered -type Collation struct { - CandidateReceipt parachaintypes.CandidateReceipt `scale:"1"` - PoV parachaintypes.PoV `scale:"2"` -} +type CollationVDT parachaintypes.Collation // Index returns the index of varying data type -func (Collation) Index() uint { +func (CollationVDT) Index() uint { return 0 } // NewCollationFetchingResponse returns a new collation fetching response varying data type func NewCollationFetchingResponse() CollationFetchingResponse { - vdt := scale.MustNewVaryingDataType(Collation{}) + vdt := scale.MustNewVaryingDataType(CollationVDT{}) return CollationFetchingResponse(vdt) } @@ -80,6 +84,6 @@ func (c *CollationFetchingResponse) String() string { } v, _ := c.Value() - collation := v.(Collation) + collation := v.(CollationVDT) return fmt.Sprintf("CollationFetchingResponse Collation=%+v", collation) } diff --git a/dot/parachain/collation_fetching_test.go b/dot/parachain/collator-protocol/collation_fetching_test.go similarity index 96% rename from dot/parachain/collation_fetching_test.go rename to dot/parachain/collator-protocol/collation_fetching_test.go index 2e58389b85..a31da3f5ee 100644 --- a/dot/parachain/collation_fetching_test.go +++ b/dot/parachain/collator-protocol/collation_fetching_test.go @@ -1,7 +1,7 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package collatorprotocol import ( "testing" @@ -34,10 +34,10 @@ func TestCollationFetchingResponse(t *testing.T) { copy(collatorID[:], tempCollatID) var collatorSignature parachaintypes.CollatorSignature - tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) + tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) copy(collatorSignature[:], tempSignature) - collation := Collation{ + collation := CollationVDT{ CandidateReceipt: parachaintypes.CandidateReceipt{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1), diff --git a/dot/parachain/collation_protocol.go b/dot/parachain/collator-protocol/message.go similarity index 68% rename from dot/parachain/collation_protocol.go rename to dot/parachain/collator-protocol/message.go index 12e45fd431..f914316125 100644 --- a/dot/parachain/collation_protocol.go +++ b/dot/parachain/collator-protocol/message.go @@ -1,18 +1,22 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package collatorprotocol import ( + "errors" "fmt" "github.com/ChainSafe/gossamer/dot/network" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/libp2p/go-libp2p/core/peer" ) +const legacyCollationProtocolV1 = "/polkadot/collation/1" + // CollationProtocol represents all network messages on the collation peer-set. type CollationProtocol scale.VaryingDataType @@ -106,8 +110,8 @@ func (AdvertiseCollation) Index() uint { // CollationSeconded represents that a collation sent to a validator was seconded. type CollationSeconded struct { - Hash common.Hash `scale:"1"` - UncheckedSignedFullStatement parachaintypes.UncheckedSignedFullStatement `scale:"2"` + RelayParent common.Hash `scale:"1"` + Statement parachaintypes.UncheckedSignedFullStatement `scale:"2"` } // Index returns the index of varying data type @@ -117,15 +121,13 @@ func (CollationSeconded) Index() uint { const MaxCollationMessageSize uint64 = 100 * 1024 -type CollationProtocolV1 struct{} - // Type returns CollationMsgType -func (*CollationProtocolV1) Type() network.MessageType { +func (CollationProtocol) Type() network.MessageType { return network.CollationMsgType } // Hash returns the hash of the CollationProtocolV1 -func (cp *CollationProtocolV1) Hash() (common.Hash, error) { +func (cp CollationProtocol) Hash() (common.Hash, error) { // scale encode each extrinsic encMsg, err := cp.Encode() if err != nil { @@ -136,8 +138,8 @@ func (cp *CollationProtocolV1) Hash() (common.Hash, error) { } // Encode a collator protocol message using scale encode -func (cp *CollationProtocolV1) Encode() ([]byte, error) { - enc, err := scale.Marshal(*cp) +func (cp CollationProtocol) Encode() ([]byte, error) { + enc, err := scale.Marshal(cp) if err != nil { return nil, err } @@ -145,7 +147,7 @@ func (cp *CollationProtocolV1) Encode() ([]byte, error) { } func decodeCollationMessage(in []byte) (network.NotificationsMessage, error) { - collationMessage := CollationProtocolV1{} + collationMessage := CollationProtocol{} err := scale.Unmarshal(in, &collationMessage) if err != nil { @@ -155,9 +157,60 @@ func decodeCollationMessage(in []byte) (network.NotificationsMessage, error) { return &collationMessage, nil } -func handleCollationMessage(_ peer.ID, msg network.NotificationsMessage) (bool, error) { - // TODO: Add things - fmt.Println("We got a collation message", msg) +type ProspectiveCandidate struct { + CandidateHash parachaintypes.CandidateHash + ParentHeadDataHash common.Hash +} + +type CollationStatus int + +const ( + // We are waiting for a collation to be advertised to us. + Waiting CollationStatus = iota + // We are currently fetching a collation. + Fetching + // We are waiting that a collation is being validated. + WaitingOnValidation + // We have seconded a collation. + Seconded +) + +func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( + sender peer.ID, msg network.NotificationsMessage) (bool, error) { + if msg.Type() != network.CollationMsgType { + return false, fmt.Errorf("unexpected message type, expected: %d, found:%d", + network.CollationMsgType, msg.Type()) + } + + collatorProtocol, ok := msg.(*CollationProtocol) + if !ok { + return false, fmt.Errorf("failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", msg) + } + + collatorProtocolV, err := collatorProtocol.Value() + if err != nil { + return false, fmt.Errorf("getting collator protocol value: %w", err) + } + collatorProtocolMessage, ok := collatorProtocolV.(CollatorProtocolMessage) + if !ok { + return false, errors.New("expected value to be collator protocol message") + } + + switch collatorProtocolMessage.Index() { + // TODO: Make sure that V1 and VStaging both types are covered + // All the types covered currently are V1. + case 0: // Declare + // TODO: handle collator declaration https://github.com/ChainSafe/gossamer/issues/3513 + case 1: // AdvertiseCollation + // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 + case 2: // CollationSeconded + logger.Errorf("unexpected collation seconded message from peer %s, decreasing its reputation", sender) + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + } + return false, nil } diff --git a/dot/parachain/collation_protocol_test.go b/dot/parachain/collator-protocol/message_test.go similarity index 93% rename from dot/parachain/collation_protocol_test.go rename to dot/parachain/collator-protocol/message_test.go index a4efe9f870..513739fbf6 100644 --- a/dot/parachain/collation_protocol_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -1,7 +1,7 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package collatorprotocol import ( _ "embed" @@ -29,6 +29,14 @@ func init() { } } +func getDummyHash(num byte) common.Hash { + hash := common.Hash{} + for i := 0; i < 32; i++ { + hash[i] = num + } + return hash +} + func TestCollationProtocol(t *testing.T) { t.Parallel() @@ -37,7 +45,7 @@ func TestCollationProtocol(t *testing.T) { copy(collatorID[:], tempCollatID) var collatorSignature parachaintypes.CollatorSignature - tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) + tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) copy(collatorSignature[:], tempSignature) var validatorSignature parachaintypes.ValidatorSignature @@ -94,8 +102,8 @@ func TestCollationProtocol(t *testing.T) { { name: "CollationSeconded", enumValue: CollationSeconded{ - Hash: hash5, - UncheckedSignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + RelayParent: hash5, + Statement: parachaintypes.UncheckedSignedFullStatement{ Payload: statementVDTWithSeconded, ValidatorIndex: parachaintypes.ValidatorIndex(5), Signature: validatorSignature, diff --git a/dot/parachain/collator-protocol/register.go b/dot/parachain/collator-protocol/register.go new file mode 100644 index 0000000000..0dfa8b4ff7 --- /dev/null +++ b/dot/parachain/collator-protocol/register.go @@ -0,0 +1,54 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/libp2p/go-libp2p/core/protocol" +) + +func Register(net Network, protocolID protocol.ID, overseerChan chan<- any) (*CollatorProtocolValidatorSide, error) { + collationFetchingReqResProtocol := net.GetRequestResponseProtocol( + string(protocolID), collationFetchingRequestTimeout, collationFetchingMaxResponseSize) + + cpvs := CollatorProtocolValidatorSide{ + SubSystemToOverseer: overseerChan, + collationFetchingReqResProtocol: collationFetchingReqResProtocol, + } + + // register collation protocol + err := net.RegisterNotificationsProtocol( + protocolID, + network.CollationMsgType, + getCollatorHandshake, + decodeCollatorHandshake, + validateCollatorHandshake, + decodeCollationMessage, + cpvs.handleCollationMessage, + nil, + MaxCollationMessageSize, + ) + if err != nil { + // try with legacy protocol id + err1 := net.RegisterNotificationsProtocol( + protocol.ID(legacyCollationProtocolV1), + network.CollationMsgType, + getCollatorHandshake, + decodeCollatorHandshake, + validateCollatorHandshake, + decodeCollationMessage, + cpvs.handleCollationMessage, + nil, + MaxCollationMessageSize, + ) + + if err1 != nil { + return nil, fmt.Errorf("registering collation protocol, new: %w, legacy:%w", err, err1) + } + } + + return &cpvs, nil +} diff --git a/dot/parachain/testdata/collation_protocol.yaml b/dot/parachain/collator-protocol/testdata/collation_protocol.yaml similarity index 92% rename from dot/parachain/testdata/collation_protocol.yaml rename to dot/parachain/collator-protocol/testdata/collation_protocol.yaml index f5acb9fc2c..796314e80a 100644 --- a/dot/parachain/testdata/collation_protocol.yaml +++ b/dot/parachain/collator-protocol/testdata/collation_protocol.yaml @@ -1,3 +1,4 @@ +collatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" declare: "0x000048215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b14705000000c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go new file mode 100644 index 0000000000..0e0fd97378 --- /dev/null +++ b/dot/parachain/collator-protocol/validator_side.go @@ -0,0 +1,307 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/ChainSafe/gossamer/dot/network" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/internal/log" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "golang.org/x/exp/slices" +) + +var logger = log.NewFromGlobal(log.AddContext("pkg", "collator-protocol")) + +const ( + activityPoll = 10 * time.Millisecond + maxUnsharedDownloadTime = 100 * time.Millisecond +) + +var ( + ErrUnknownOverseerMessage = errors.New("unknown overseer message type") + ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") + ErrCollationNotInView = errors.New("collation is not in our view") + ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") +) + +func (cpvs CollatorProtocolValidatorSide) Run( + ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error { + inactivityTicker := time.NewTicker(activityPoll) + + for { + select { + // TODO: polkadot-rust changes reputation in batches, so we do the same? + case msg, ok := <-cpvs.OverseerToSubSystem: + if !ok { + return nil + } + + err := cpvs.processMessage(msg) + if err != nil { + logger.Errorf("processing overseer message: %w", err) + } + case <-inactivityTicker.C: + // TODO: disconnect inactive peers + // https://github.com/paritytech/polkadot/blob/8f05479e4bd61341af69f0721e617f01cbad8bb2/node/network/collator-protocol/src/validator_side/mod.rs#L1301 + + case unfetchedCollation := <-cpvs.unfetchedCollation: + // TODO: If we can't get the collation from given collator within MAX_UNSHARED_DOWNLOAD_TIME, + // we will start another one from the next collator. + + // check if this peer id has advertised this relay parent + peerData := cpvs.peerData[unfetchedCollation.PendingCollation.PeerID] + if peerData.HasAdvertisedRelayParent(unfetchedCollation.PendingCollation.RelayParent) { + // if so request collation from this peer id + collation, err := cpvs.requestCollation(unfetchedCollation.PendingCollation.RelayParent, + unfetchedCollation.PendingCollation.ParaID, unfetchedCollation.PendingCollation.PeerID) + if err != nil { + logger.Errorf("fetching collation: %w", err) + } + cpvs.fetchedCollations = append(cpvs.fetchedCollations, *collation) + } + + } + } +} + +// requestCollation requests a collation from the network. +// This function will +// - check for duplicate requests +// - check if the requested collation is in our view +func (cpvs CollatorProtocolValidatorSide) requestCollation(relayParent common.Hash, + paraID parachaintypes.ParaID, peerID peer.ID) (*parachaintypes.Collation, error) { + if !slices.Contains[[]common.Hash](cpvs.ourView.heads, relayParent) { + return nil, ErrCollationNotInView + } + + // make collation fetching request + collationFetchingRequest := CollationFetchingRequest{ + RelayParent: relayParent, + ParaID: paraID, + } + + collationFetchingResponse := NewCollationFetchingResponse() + err := cpvs.collationFetchingReqResProtocol.Do(peerID, collationFetchingRequest, &collationFetchingResponse) + if err != nil { + return nil, fmt.Errorf("collation fetching request failed: %w", err) + } + + v, err := collationFetchingResponse.Value() + if err != nil { + return nil, fmt.Errorf("getting value of collation fetching response: %w", err) + } + collationVDT, ok := v.(CollationVDT) + if !ok { + return nil, fmt.Errorf("collation fetching response value expected: CollationVDT, got: %T", v) + } + collation := parachaintypes.Collation(collationVDT) + + return &collation, nil +} + +type UnfetchedCollation struct { + CollatorID parachaintypes.CollatorID + PendingCollation PendingCollation +} + +type PendingCollation struct { + RelayParent common.Hash + ParaID parachaintypes.ParaID + PeerID peer.ID + CommitmentHash *common.Hash +} + +type PeerData struct { + view View + state PeerStateInfo +} + +func (peerData PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { + if peerData.state.PeerState == Connected { + return false + } + + return slices.Contains(peerData.view.heads, relayParent) +} + +func (peerData PeerData) InsertAdvertisement() error { + // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 + return nil +} + +type PeerStateInfo struct { + PeerState PeerState + // instant at which peer got connected + Instant time.Time + CollatingPeerState CollatingPeerState +} + +type CollatingPeerState struct { + CollatorID parachaintypes.CollatorID + ParaID parachaintypes.ParaID + advertisements []common.Hash //nolint + lastActive time.Time //nolint +} + +type PeerState uint + +const ( + Connected PeerState = iota + Collating +) + +type View struct { + // a bounded amount of chain heads + heads []common.Hash + // the highest known finalized number + finalizedNumber uint32 //nolint +} + +// Network is the interface required by parachain service for the network +type Network interface { + GossipMessage(msg network.NotificationsMessage) + SendMessage(to peer.ID, msg network.NotificationsMessage) error + RegisterNotificationsProtocol(sub protocol.ID, + messageID network.MessageType, + handshakeGetter network.HandshakeGetter, + handshakeDecoder network.HandshakeDecoder, + handshakeValidator network.HandshakeValidator, + messageDecoder network.MessageDecoder, + messageHandler network.NotificationsMessageHandler, + batchHandler network.NotificationsMessageBatchHandler, + maxSize uint64, + ) error + GetRequestResponseProtocol(subprotocol string, requestTimeout time.Duration, + maxResponseSize uint64) *network.RequestResponseProtocol + ReportPeer(change peerset.ReputationChange, p peer.ID) +} + +type CollationEvent struct { + CollatorId parachaintypes.CollatorID + PendingCollation PendingCollation +} + +type CollatorProtocolValidatorSide struct { + net Network + + SubSystemToOverseer chan<- any + OverseerToSubSystem <-chan any + + unfetchedCollation chan UnfetchedCollation + + collationFetchingReqResProtocol *network.RequestResponseProtocol + + fetchedCollations []parachaintypes.Collation + // track all active collators and their data + peerData map[peer.ID]PeerData + + ourView View + + // Keep track of all pending candidate collations + pendingCandidates map[common.Hash]CollationEvent +} + +func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, +) (peer.ID, bool) { + for peerID, peerData := range cpvs.peerData { + if peerData.state.PeerState == Collating && peerData.state.CollatingPeerState.CollatorID == collatorID { + return peerID, true + } + } + + return "", false +} + +type CollateOn parachaintypes.ParaID + +type DistributeCollation struct { + CandidateReceipt parachaintypes.CandidateReceipt + PoV parachaintypes.PoV +} + +type ReportCollator parachaintypes.CollatorID + +type NetworkBridgeUpdate struct { + // TODO: not quite sure if we would need this or something similar to this +} + +type SecondedOverseerMsg struct { + Parent common.Hash + Stmt parachaintypes.StatementVDT +} + +type Backed struct { + ParaID parachaintypes.ParaID + // Hash of the para head generated by candidate + ParaHead common.Hash +} + +type InvalidOverseeMsg struct { + Parent common.Hash + CandidateReceipt parachaintypes.CandidateReceipt +} + +func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error { + // run this function as a goroutine, ideally + + switch msg := msg.(type) { + case CollateOn: + return fmt.Errorf("CollateOn %w", ErrNotExpectedOnValidatorSide) + case DistributeCollation: + return fmt.Errorf("DistributeCollation %w", ErrNotExpectedOnValidatorSide) + case ReportCollator: + peerID, ok := cpvs.getPeerIDFromCollatorID(parachaintypes.CollatorID(msg)) + if !ok { + return ErrPeerIDNotFoundForCollator + } + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + case NetworkBridgeUpdate: + // TODO: handle network message https://github.com/ChainSafe/gossamer/issues/3515 + // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1457 //nolint + case SecondedOverseerMsg: + // TODO: handle seconded message https://github.com/ChainSafe/gossamer/issues/3516 + // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1466 //nolint + + case Backed: + // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 + case InvalidOverseeMsg: + invalidOverseerMsg := msg + + collationEvent, ok := cpvs.pendingCandidates[invalidOverseerMsg.Parent] + if !ok { + return nil + } + + if *collationEvent.PendingCollation.CommitmentHash == (invalidOverseerMsg.CandidateReceipt.CommitmentsHash) { + delete(cpvs.pendingCandidates, invalidOverseerMsg.Parent) + } else { + logger.Error("reported invalid candidate for unknown `pending_candidate`") + return nil + } + + peerID, ok := cpvs.getPeerIDFromCollatorID(collationEvent.CollatorId) + if !ok { + return ErrPeerIDNotFoundForCollator + } + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + default: + return ErrUnknownOverseerMessage + } + + return nil +} diff --git a/dot/parachain/network_protocols.go b/dot/parachain/network_protocols.go index b84941a39e..36fc167091 100644 --- a/dot/parachain/network_protocols.go +++ b/dot/parachain/network_protocols.go @@ -11,8 +11,7 @@ import ( ) const ( - LEGACY_VALIDATION_PROTOCOL_V1 = "/polkadot/validation/1" - LEGACY_COLLATION_PROTOCOL_V1 = "/polkadot/collation/1" + legacyValidationProtocolV1 = "/polkadot/validation/1" ) type ReqProtocolName uint diff --git a/dot/parachain/pov_fetching.go b/dot/parachain/pov_fetching.go index 426f1b3b63..3b0063c90f 100644 --- a/dot/parachain/pov_fetching.go +++ b/dot/parachain/pov_fetching.go @@ -26,7 +26,7 @@ type PoVFetchingResponse scale.VaryingDataType // NewPoVFetchingResponse returns a new PoV fetching response varying data type func NewPoVFetchingResponse() PoVFetchingResponse { - vdt := scale.MustNewVaryingDataType(parachaintypes.PoV{}, NoSuchPoV{}) + vdt := scale.MustNewVaryingDataType(parachaintypes.PoV{}, parachaintypes.NoSuchPoV{}) return PoVFetchingResponse(vdt) } @@ -47,14 +47,6 @@ func (p *PoVFetchingResponse) Value() (val scale.VaryingDataTypeValue, err error return vdt.Value() } -// NoSuchPoV indicates that the requested PoV was not found in the store. -type NoSuchPoV struct{} - -// Index returns the index of varying data type -func (NoSuchPoV) Index() uint { - return 1 -} - // Encode returns the SCALE encoding of the PoVFetchingResponse func (p *PoVFetchingResponse) Encode() ([]byte, error) { return scale.Marshal(*p) diff --git a/dot/parachain/pov_fetching_test.go b/dot/parachain/pov_fetching_test.go index 283ceef7b8..5e7b37fe87 100644 --- a/dot/parachain/pov_fetching_test.go +++ b/dot/parachain/pov_fetching_test.go @@ -43,7 +43,7 @@ func TestPoVFetchingResponse(t *testing.T) { }, { name: "NoSuchPoV", - value: NoSuchPoV{}, + value: parachaintypes.NoSuchPoV{}, encodeValue: []byte{1}, }, } diff --git a/dot/parachain/service.go b/dot/parachain/service.go index d592c0e0ea..58b26ba0e4 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -9,6 +9,7 @@ import ( "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/parachain/backing" + collatorprotocol "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol" "github.com/ChainSafe/gossamer/dot/parachain/overseer" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" @@ -24,12 +25,15 @@ const ( ) type Service struct { - Network Network + Network Network + overseer *overseer.Overseer } var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, error) { + overseer := overseer.NewOverseer() + validationProtocolID := GeneratePeersetProtocolName( ValidationProtocolName, forkID, genesisHash, ValidationProtocolVersion) @@ -48,7 +52,7 @@ func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, if err != nil { // try with legacy protocol id err1 := net.RegisterNotificationsProtocol( - protocol.ID(LEGACY_VALIDATION_PROTOCOL_V1), + protocol.ID(legacyValidationProtocolV1), network.ValidationMsgType, getValidationHandshake, decodeValidationHandshake, @@ -68,38 +72,15 @@ func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, CollationProtocolName, forkID, genesisHash, CollationProtocolVersion) // register collation protocol - err = net.RegisterNotificationsProtocol( - protocol.ID(collationProtocolID), - network.CollationMsgType, - getCollatorHandshake, - decodeCollatorHandshake, - validateCollatorHandshake, - decodeCollationMessage, - handleCollationMessage, - nil, - MaxCollationMessageSize, - ) + cpvs, err := collatorprotocol.Register(net, protocol.ID(collationProtocolID), overseer.SubsystemsToOverseer) if err != nil { - // try with legacy protocol id - err1 := net.RegisterNotificationsProtocol( - protocol.ID(LEGACY_COLLATION_PROTOCOL_V1), - network.CollationMsgType, - getCollatorHandshake, - decodeCollatorHandshake, - validateCollatorHandshake, - decodeCollationMessage, - handleCollationMessage, - nil, - MaxCollationMessageSize, - ) - - if err1 != nil { - return nil, fmt.Errorf("registering collation protocol, new: %w, legacy:%w", err, err1) - } + return nil, err } + cpvs.OverseerToSubSystem = overseer.RegisterSubsystem(cpvs) parachainService := &Service{ - Network: net, + Network: net, + overseer: overseer, } go parachainService.run() @@ -130,7 +111,12 @@ func (s Service) run() { // time.Sleep(time.Second * 15) // let's try sending a collation message and validation message to a peer and see what happens - collationMessage := CollationProtocolV1{} + collatorProtocolMessage := collatorprotocol.NewCollatorProtocolMessage() + // NOTE: This is just to test. We should not be sending declare messages, since we are not a collator, just a validator + _ = collatorProtocolMessage.Set(collatorprotocol.Declare{}) + collationMessage := collatorprotocol.NewCollationProtocol() + + _ = collationMessage.Set(collatorProtocolMessage) s.Network.GossipMessage(&collationMessage) statementDistributionLargeStatement := StatementDistribution{NewStatementDistributionMessage()} @@ -167,5 +153,7 @@ type Network interface { batchHandler network.NotificationsMessageBatchHandler, maxSize uint64, ) error + GetRequestResponseProtocol(subprotocol string, requestTimeout time.Duration, + maxResponseSize uint64) *network.RequestResponseProtocol ReportPeer(change peerset.ReputationChange, p peer.ID) } diff --git a/dot/parachain/statement_distribution_message_test.go b/dot/parachain/statement_distribution_message_test.go index 34fa3c8c60..ba873ec3f3 100644 --- a/dot/parachain/statement_distribution_message_test.go +++ b/dot/parachain/statement_distribution_message_test.go @@ -5,7 +5,9 @@ package parachain import ( _ "embed" + "errors" "fmt" + "math" "testing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -28,6 +30,23 @@ func init() { } } +var ErrInvalidVayingDataTypeValue = errors.New( + "setting value to varying data type: unsupported VaryingDataTypeValue: {} (parachain.invalidVayingDataTypeValue)") + +type invalidVayingDataTypeValue struct{} + +func (invalidVayingDataTypeValue) Index() uint { + return math.MaxUint +} + +func getDummyHash(num byte) common.Hash { + hash := common.Hash{} + for i := 0; i < 32; i++ { + hash[i] = num + } + return hash +} + func TestStatementDistributionMessage(t *testing.T) { t.Parallel() diff --git a/dot/parachain/types/statement.go b/dot/parachain/types/statement.go index 45f49b2ca3..b8fb69a70f 100644 --- a/dot/parachain/types/statement.go +++ b/dot/parachain/types/statement.go @@ -56,3 +56,16 @@ type Valid CandidateHash func (Valid) Index() uint { return 2 } + +// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified. +type UncheckedSignedFullStatement struct { + // The payload is part of the signed data. The rest is the signing context, + // which is known both at signing and at validation. + Payload StatementVDT `scale:"1"` + + // The index of the validator signing this statement. + ValidatorIndex ValidatorIndex `scale:"2"` + + // The signature by the validator of the signed payload. + Signature ValidatorSignature `scale:"3"` +} diff --git a/dot/parachain/statement_test.go b/dot/parachain/types/statement_test.go similarity index 76% rename from dot/parachain/statement_test.go rename to dot/parachain/types/statement_test.go index 7b850afee6..4fcd230b47 100644 --- a/dot/parachain/statement_test.go +++ b/dot/parachain/types/statement_test.go @@ -1,21 +1,36 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package parachain +package parachaintypes import ( + _ "embed" "errors" + "fmt" "math" "testing" - parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" ) +//go:embed testdata/statement.yaml +var testDataStatementRaw []byte + +var testDataStatement map[string]string + +func init() { + err := yaml.Unmarshal(testDataStatementRaw, &testDataStatement) + if err != nil { + fmt.Printf("Error unmarshaling test data: %s\n", err) + return + } +} + var ErrInvalidVayingDataTypeValue = errors.New( - "setting value to varying data type: unsupported VaryingDataTypeValue: {} (parachain.invalidVayingDataTypeValue)") + "setting value to varying data type: unsupported VaryingDataTypeValue: {} (parachaintypes.invalidVayingDataTypeValue)") type invalidVayingDataTypeValue struct{} @@ -34,18 +49,18 @@ func getDummyHash(num byte) common.Hash { func TestStatementVDT(t *testing.T) { t.Parallel() - var collatorID parachaintypes.CollatorID + var collatorID CollatorID tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") copy(collatorID[:], tempCollatID) - var collatorSignature parachaintypes.CollatorSignature + var collatorSignature CollatorSignature tempSignature := common.MustHexToBytes(testDataStatement["collatorSignature"]) copy(collatorSignature[:], tempSignature) hash5 := getDummyHash(5) - secondedEnumValue := parachaintypes.Seconded{ - Descriptor: parachaintypes.CandidateDescriptor{ + secondedEnumValue := Seconded{ + Descriptor: CandidateDescriptor{ ParaID: uint32(1), RelayParent: hash5, Collator: collatorID, @@ -54,12 +69,12 @@ func TestStatementVDT(t *testing.T) { ErasureRoot: hash5, Signature: collatorSignature, ParaHead: hash5, - ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), + ValidationCodeHash: ValidationCodeHash(hash5), }, - Commitments: parachaintypes.CandidateCommitments{ - UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, - NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, - HeadData: parachaintypes.HeadData{ + Commitments: CandidateCommitments{ + UpwardMessages: []UpwardMessage{{1, 2, 3}}, + NewValidationCode: &ValidationCode{1, 2, 3}, + HeadData: HeadData{ Data: []byte{1, 2, 3}, }, ProcessedDownwardMessages: uint32(5), @@ -81,7 +96,7 @@ func TestStatementVDT(t *testing.T) { }, { name: "Valid", - enumValue: parachaintypes.Valid{Value: hash5}, + enumValue: Valid{Value: hash5}, encodingValue: common.MustHexToBytes("0x020505050505050505050505050505050505050505050505050505050505050505"), expectedErr: nil, }, @@ -99,7 +114,7 @@ func TestStatementVDT(t *testing.T) { t.Run("marshal", func(t *testing.T) { t.Parallel() - vdt := parachaintypes.NewStatementVDT() + vdt := NewStatementVDT() err := vdt.Set(c.enumValue) if c.expectedErr != nil { @@ -120,7 +135,7 @@ func TestStatementVDT(t *testing.T) { return } - vdt := parachaintypes.NewStatementVDT() + vdt := NewStatementVDT() err := scale.Unmarshal(c.encodingValue, &vdt) require.NoError(t, err) diff --git a/dot/parachain/types/testdata/statement.yaml b/dot/parachain/types/testdata/statement.yaml new file mode 100644 index 0000000000..0a42f207b5 --- /dev/null +++ b/dot/parachain/types/testdata/statement.yaml @@ -0,0 +1,3 @@ +collatorSignature: "0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86" + +statementVDTSeconded: "0x0101000000050505050505050505050505050505050505050505050505050505050505050548215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505c67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b8605050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505040c01020300010c0102030c0102030500000000000000" \ No newline at end of file diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index e4b00f6355..52978023c8 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -513,21 +513,22 @@ func (PoV) Index() uint { return 0 } +// NoSuchPoV indicates that the requested PoV was not found in the store. +type NoSuchPoV struct{} + +// Index returns the index of varying data type +func (NoSuchPoV) Index() uint { + return 1 +} + // BlockData represents parachain block data. // It contains everything required to validate para-block, may contain block and witness data. type BlockData []byte -// UncheckedSignedFullStatement is a Variant of `SignedFullStatement` where the signature has not yet been verified. -type UncheckedSignedFullStatement struct { - // The payload is part of the signed data. The rest is the signing context, - // which is known both at signing and at validation. - Payload StatementVDT `scale:"1"` - - // The index of the validator signing this statement. - ValidatorIndex ValidatorIndex `scale:"2"` - - // The signature by the validator of the signed payload. - Signature ValidatorSignature `scale:"3"` +// Collation represents a requested collation to be delivered +type Collation struct { + CandidateReceipt CandidateReceipt `scale:"1"` + PoV PoV `scale:"2"` } // ValidatorSignature represents the signature with which parachain validators sign blocks. diff --git a/dot/parachain/validation_protocol.go b/dot/parachain/validation_protocol.go index 960830669b..74d10ce5bb 100644 --- a/dot/parachain/validation_protocol.go +++ b/dot/parachain/validation_protocol.go @@ -179,7 +179,7 @@ func handleValidationMessage(_ peer.ID, msg network.NotificationsMessage) (bool, } func getValidationHandshake() (network.Handshake, error) { - return &collatorHandshake{}, nil + return &validationHandshake{}, nil } func decodeValidationHandshake(_ []byte) (network.Handshake, error) { From 807c8766931d147f53c9268845608e195fc4a985 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 9 Oct 2023 11:25:32 +0530 Subject: [PATCH 33/85] Handle Declare message received by a collator - Verify the collator by its signature and - Save its collator id, peer id and Para id it collates for in our memory --- dot/parachain/collator-protocol/message.go | 81 ++++++++++++++++++- .../collator-protocol/validator_side.go | 18 ++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index f914316125..2f730b001b 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -11,6 +11,8 @@ import ( parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/libp2p/go-libp2p/core/peer" ) @@ -175,6 +177,16 @@ const ( Seconded ) +// getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. +// The payload is a the local peed id of the node, which serves to prove that it controls the +// collator key it is declaring and intends to collate under. +func getDeclareSignaturePayload(peerID peer.ID) []byte { + payload := []byte("COLL") + payload = append(payload, peerID...) + + return payload +} + func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( sender peer.ID, msg network.NotificationsMessage) (bool, error) { if msg.Type() != network.CollationMsgType { @@ -196,11 +208,74 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( return false, errors.New("expected value to be collator protocol message") } + collatorProtocolMessageV, err := collatorProtocolMessage.Value() + if err != nil { + return false, fmt.Errorf("getting collator protocol message value: %w", err) + } + switch collatorProtocolMessage.Index() { - // TODO: Make sure that V1 and VStaging both types are covered - // All the types covered currently are V1. + // TODO: Make sure that V1 types are covered. + // TODO: Create an issue to cover v2 types. case 0: // Declare - // TODO: handle collator declaration https://github.com/ChainSafe/gossamer/issues/3513 + declareMessage, ok := collatorProtocolMessageV.(Declare) + if !ok { + return false, errors.New("expected message to be declare") + } + + // check if we already have the collator id declared in this message. If so, punish the + // peer who sent us this message by reducing its reputation + peerID, ok := cpvs.getPeerIDFromCollatorID(declareMessage.CollatorId) + if ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return true, nil + } + + peerData := cpvs.peerData[sender] + if peerData.state.PeerState == Collating { + logger.Error("peer is already in the collating state") + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return true, nil + } + + // check signature declareMessage.CollatorSignature + err = sr25519.VerifySignature(declareMessage.CollatorId[:], declareMessage.CollatorSignature[:], + getDeclareSignaturePayload(peerID)) + if errors.Is(err, crypto.ErrSignatureVerificationFailed) { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.InvalidSignatureValue, + Reason: peerset.InvalidSignatureReason, + }, sender) + return true, fmt.Errorf("invalid signature: %w", err) + } + if err != nil { + return false, fmt.Errorf("verifying signature: %w", err) + } + + _, ok = cpvs.currentAssignments[parachaintypes.ParaID(declareMessage.ParaID)] + if ok { + logger.Errorf("declared as collator for current para: %d", declareMessage.ParaID) + + peerData.SetCollating(declareMessage.CollatorId, parachaintypes.ParaID(declareMessage.ParaID)) + cpvs.peerData[sender] = peerData + } else { + logger.Errorf("declared as collator for unneeded para: %d", declareMessage.ParaID) + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnneededCollatorValue, + Reason: peerset.UnneededCollatorReason, + }, sender) + + // TODO: Disconnect peer. + // Do a thorough review of substrate/client/network/src/ + // check how are they managing peerset of different protocol. + // Currently we have a Handler in dot/peerset, but it does not get used anywhere. + delete(cpvs.peerData, sender) + } case 1: // AdvertiseCollation // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 case 2: // CollationSeconded diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 0e0fd97378..cfeb12631e 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -125,7 +125,7 @@ type PeerData struct { state PeerStateInfo } -func (peerData PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { +func (peerData *PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { if peerData.state.PeerState == Connected { return false } @@ -133,7 +133,17 @@ func (peerData PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool return slices.Contains(peerData.view.heads, relayParent) } -func (peerData PeerData) InsertAdvertisement() error { +func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, paraID parachaintypes.ParaID) { + peerData.state = PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: collatorID, + ParaID: paraID, + }, + } +} + +func (peerData *PeerData) InsertAdvertisement() error { // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 return nil } @@ -208,6 +218,10 @@ type CollatorProtocolValidatorSide struct { // Keep track of all pending candidate collations pendingCandidates map[common.Hash]CollationEvent + + // Parachains we're currently assigned to. With async backing enabled + // this includes assignments from the implicit view. + currentAssignments map[parachaintypes.ParaID]uint } func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, From 5d1f2c02282843740f9085eb4a1da0d0cf827939 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 10 Oct 2023 15:58:56 +0530 Subject: [PATCH 34/85] temp --- dot/parachain/collator-protocol/message.go | 41 ++++++++++++------- .../collator-protocol/message_test.go | 13 ++++++ .../collator-protocol/validator_side.go | 10 +++-- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 2f730b001b..f345e47a64 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -189,28 +189,32 @@ func getDeclareSignaturePayload(peerID peer.ID) []byte { func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( sender peer.ID, msg network.NotificationsMessage) (bool, error) { + + // we don't propagate collation messages, so it will always be false + propagate := false + if msg.Type() != network.CollationMsgType { - return false, fmt.Errorf("unexpected message type, expected: %d, found:%d", + return propagate, fmt.Errorf("%w, expected: %d, found:%d", ErrUnexpectedMessageOnCollationProtocol, network.CollationMsgType, msg.Type()) } collatorProtocol, ok := msg.(*CollationProtocol) if !ok { - return false, fmt.Errorf("failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", msg) + return propagate, fmt.Errorf("failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", msg) } collatorProtocolV, err := collatorProtocol.Value() if err != nil { - return false, fmt.Errorf("getting collator protocol value: %w", err) + return propagate, fmt.Errorf("getting collator protocol value: %w", err) } collatorProtocolMessage, ok := collatorProtocolV.(CollatorProtocolMessage) if !ok { - return false, errors.New("expected value to be collator protocol message") + return propagate, errors.New("expected value to be collator protocol message") } collatorProtocolMessageV, err := collatorProtocolMessage.Value() if err != nil { - return false, fmt.Errorf("getting collator protocol message value: %w", err) + return propagate, fmt.Errorf("getting collator protocol message value: %w", err) } switch collatorProtocolMessage.Index() { @@ -219,7 +223,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( case 0: // Declare declareMessage, ok := collatorProtocolMessageV.(Declare) if !ok { - return false, errors.New("expected message to be declare") + return propagate, errors.New("expected message to be declare") } // check if we already have the collator id declared in this message. If so, punish the @@ -230,17 +234,26 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, }, peerID) - return true, nil + return propagate, nil + } + + // NOTE: peerData for sender will be filled when it gets connected to us + peerData, ok := cpvs.peerData[sender] + if !ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return propagate, fmt.Errorf("%w: %s", ErrUnknownPeer, sender) } - peerData := cpvs.peerData[sender] if peerData.state.PeerState == Collating { logger.Error("peer is already in the collating state") cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, }, sender) - return true, nil + return propagate, nil } // check signature declareMessage.CollatorSignature @@ -251,12 +264,13 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( Value: peerset.InvalidSignatureValue, Reason: peerset.InvalidSignatureReason, }, sender) - return true, fmt.Errorf("invalid signature: %w", err) + return propagate, fmt.Errorf("invalid signature: %w", err) } if err != nil { - return false, fmt.Errorf("verifying signature: %w", err) + return propagate, fmt.Errorf("verifying signature: %w", err) } + // NOTE: assingments are setting when we handle view changes _, ok = cpvs.currentAssignments[parachaintypes.ParaID(declareMessage.ParaID)] if ok { logger.Errorf("declared as collator for current para: %d", declareMessage.ParaID) @@ -270,11 +284,10 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( Reason: peerset.UnneededCollatorReason, }, sender) - // TODO: Disconnect peer. + // TODO: Disconnect peer. #3530 // Do a thorough review of substrate/client/network/src/ // check how are they managing peerset of different protocol. // Currently we have a Handler in dot/peerset, but it does not get used anywhere. - delete(cpvs.peerData, sender) } case 1: // AdvertiseCollation // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 @@ -286,7 +299,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( }, sender) } - return false, nil + return propagate, nil } func getCollatorHandshake() (network.Handshake, error) { diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 513739fbf6..c63a01f35d 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -10,9 +10,11 @@ import ( "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + "github.com/ChainSafe/gossamer/dot/network" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" ) @@ -172,3 +174,14 @@ func TestDecodeCollationHandshake(t *testing.T) { require.NoError(t, err) require.Equal(t, testHandshake, msg) } + +func TestHandleCollationMessage(t *testing.T) { + cpvs := CollatorProtocolValidatorSide{} + + // test with wrong message type + msg1 := &network.BlockAnnounceMessage{} + peerID1 := peer.ID("testPeerID1") + propagate, err := cpvs.handleCollationMessage(peerID1, msg1) + require.False(t, propagate) + require.ErrorIs(t, err, ErrUnexpectedMessageOnCollationProtocol) +} diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index cfeb12631e..acecf8f4a9 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -27,10 +27,12 @@ const ( ) var ( - ErrUnknownOverseerMessage = errors.New("unknown overseer message type") - ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") - ErrCollationNotInView = errors.New("collation is not in our view") - ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") + ErrUnexpectedMessageOnCollationProtocol = errors.New("unexpected message on collation protocol") + ErrUnknownPeer = errors.New("unknown peer") + ErrUnknownOverseerMessage = errors.New("unknown overseer message type") + ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") + ErrCollationNotInView = errors.New("collation is not in our view") + ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") ) func (cpvs CollatorProtocolValidatorSide) Run( From 2242509c8bc3bbe722927f371063b46710636afb Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 11 Oct 2023 18:31:32 +0530 Subject: [PATCH 35/85] added tests, need to prettify a bit though --- dot/parachain/collator-protocol/message.go | 6 +- .../collator-protocol/message_test.go | 216 +++++++++++++++++- .../collator-protocol/mocks_generate_test.go | 6 + dot/parachain/collator-protocol/mocks_test.go | 105 +++++++++ .../collator-protocol/validator_side.go | 2 +- 5 files changed, 330 insertions(+), 5 deletions(-) create mode 100644 dot/parachain/collator-protocol/mocks_generate_test.go create mode 100644 dot/parachain/collator-protocol/mocks_test.go diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index f345e47a64..03b41af75b 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -228,12 +228,12 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( // check if we already have the collator id declared in this message. If so, punish the // peer who sent us this message by reducing its reputation - peerID, ok := cpvs.getPeerIDFromCollatorID(declareMessage.CollatorId) + _, ok = cpvs.getPeerIDFromCollatorID(declareMessage.CollatorId) if ok { cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, - }, peerID) + }, sender) return propagate, nil } @@ -258,7 +258,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( // check signature declareMessage.CollatorSignature err = sr25519.VerifySignature(declareMessage.CollatorId[:], declareMessage.CollatorSignature[:], - getDeclareSignaturePayload(peerID)) + getDeclareSignaturePayload(sender)) if errors.Is(err, crypto.ErrSignatureVerificationFailed) { cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.InvalidSignatureValue, diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index c63a01f35d..4e2e89e1d5 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -9,13 +9,17 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" + gomock "github.com/golang/mock/gomock" "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" "github.com/ChainSafe/gossamer/dot/network" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" ) //go:embed testdata/collation_protocol.yaml @@ -178,10 +182,220 @@ func TestDecodeCollationHandshake(t *testing.T) { func TestHandleCollationMessage(t *testing.T) { cpvs := CollatorProtocolValidatorSide{} - // test with wrong message type + // fail with wrong message type msg1 := &network.BlockAnnounceMessage{} peerID1 := peer.ID("testPeerID1") propagate, err := cpvs.handleCollationMessage(peerID1, msg1) require.False(t, propagate) require.ErrorIs(t, err, ErrUnexpectedMessageOnCollationProtocol) + + // fail if we can't cast the message to type `*CollationProtocol` + msg2 := NewCollationProtocol() + peerID2 := peer.ID("testPeerID2") + propagate, err = cpvs.handleCollationMessage(peerID2, msg2) + require.False(t, propagate) + require.ErrorContains(t, err, "failed to cast into collator protocol message, expected: *CollationProtocol, got: collatorprotocol.CollationProtocol") + + // fail if no value set in the collator protocol message + msg3 := NewCollationProtocol() + peerID3 := peer.ID("testPeerID3") + propagate, err = cpvs.handleCollationMessage(peerID3, &msg3) + require.False(t, propagate) + require.ErrorContains(t, err, "getting collator protocol value: varying data type not set") + + // fail with unknown peer and report the sender if sender is not stored in our peerdata + msg4 := NewCollationProtocol() + vdt_child := NewCollatorProtocolMessage() + + err = vdt_child.Set(Declare{}) + require.NoError(t, err) + + err = msg4.Set(vdt_child) + require.NoError(t, err) + + peerID4 := peer.ID("testPeerID4") + + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + cpvs.net = net + + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID4) + propagate, err = cpvs.handleCollationMessage(peerID4, &msg4) + require.False(t, propagate) + require.ErrorIs(t, err, ErrUnknownPeer) + + // report the sender if the collatorId in the Declare message belongs to any peer stored in our peer data + var collatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + msg5 := NewCollationProtocol() + vdt_child = NewCollatorProtocolMessage() + + err = vdt_child.Set(Declare{ + CollatorId: collatorID, + }) + require.NoError(t, err) + + err = msg5.Set(vdt_child) + require.NoError(t, err) + + peerID5 := peer.ID("testPeerID5") + + cpvs.peerData = map[peer.ID]PeerData{ + peerID5: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: collatorID, + }, + }, + }, + } + + ctrl = gomock.NewController(t) + net = NewMockNetwork(ctrl) + cpvs.net = net + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID5) + + propagate, err = cpvs.handleCollationMessage(peerID5, &msg5) + require.False(t, propagate) + require.NoError(t, err) + + // fail if collator signature is invalid and report the sender + var collatorSignature parachaintypes.CollatorSignature + tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) + copy(collatorSignature[:], tempSignature) + + msg6 := NewCollationProtocol() + vdt_child = NewCollatorProtocolMessage() + + err = vdt_child.Set(Declare{ + CollatorId: collatorID, + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }) + require.NoError(t, err) + + err = msg6.Set(vdt_child) + require.NoError(t, err) + + peerID6 := peer.ID("testPeerID6") + + cpvs.peerData = map[peer.ID]PeerData{ + peerID6: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + } + + ctrl = gomock.NewController(t) + net = NewMockNetwork(ctrl) + cpvs.net = net + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.InvalidSignatureValue, + Reason: peerset.InvalidSignatureReason, + }, peerID6) + + propagate, err = cpvs.handleCollationMessage(peerID6, &msg6) + require.False(t, propagate) + require.ErrorIs(t, err, crypto.ErrSignatureVerificationFailed) + + // fail if paraID in Declare message is not assigned to our peer and report the sender + peerID7 := peer.ID("testPeerID7") + + collatorKeypair, err := sr25519.GenerateKeypair() + require.NoError(t, err) + collatorID1, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) + require.NoError(t, err) + + payload := getDeclareSignaturePayload(peerID7) + signatureBytes, err := collatorKeypair.Sign(payload) + require.NoError(t, err) + collatorSignature1 := [sr25519.SignatureLength]byte{} + copy(collatorSignature1[:], signatureBytes) + + msg7 := NewCollationProtocol() + vdt_child = NewCollatorProtocolMessage() + + err = vdt_child.Set(Declare{ + CollatorId: collatorID1.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature1, + }) + require.NoError(t, err) + + err = msg7.Set(vdt_child) + require.NoError(t, err) + + cpvs.peerData = map[peer.ID]PeerData{ + peerID7: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + } + + ctrl = gomock.NewController(t) + net = NewMockNetwork(ctrl) + cpvs.net = net + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnneededCollatorValue, + Reason: peerset.UnneededCollatorReason, + }, peerID7) + + propagate, err = cpvs.handleCollationMessage(peerID7, &msg7) + require.False(t, propagate) + require.NoError(t, err) + + // success case: check if PeerState of the sender has changed to Collating from Connected + peerID8 := peer.ID("testPeerID7") + + msg8 := NewCollationProtocol() + vdt_child = NewCollatorProtocolMessage() + + err = vdt_child.Set(Declare{ + CollatorId: collatorID1.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature1, + }) + require.NoError(t, err) + + err = msg8.Set(vdt_child) + require.NoError(t, err) + + cpvs.peerData = map[peer.ID]PeerData{ + peerID8: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + } + + // ctrl = gomock.NewController(t) + // net = NewMockNetwork(ctrl) + // cpvs.net = net + // net.EXPECT().ReportPeer(peerset.ReputationChange{ + // Value: peerset.UnneededCollatorValue, + // Reason: peerset.UnneededCollatorReason, + // }, peerID7) + + cpvs.currentAssignments = map[parachaintypes.ParaID]uint{ + parachaintypes.ParaID(5): 1, + } + + propagate, err = cpvs.handleCollationMessage(peerID8, &msg8) + require.False(t, propagate) + require.NoError(t, err) } diff --git a/dot/parachain/collator-protocol/mocks_generate_test.go b/dot/parachain/collator-protocol/mocks_generate_test.go new file mode 100644 index 0000000000..88c5538252 --- /dev/null +++ b/dot/parachain/collator-protocol/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Network diff --git a/dot/parachain/collator-protocol/mocks_test.go b/dot/parachain/collator-protocol/mocks_test.go new file mode 100644 index 0000000000..3452cabb6e --- /dev/null +++ b/dot/parachain/collator-protocol/mocks_test.go @@ -0,0 +1,105 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/collator-protocol (interfaces: Network) + +// Package collatorprotocol is a generated GoMock package. +package collatorprotocol + +import ( + reflect "reflect" + time "time" + + network "github.com/ChainSafe/gossamer/dot/network" + peerset "github.com/ChainSafe/gossamer/dot/peerset" + gomock "github.com/golang/mock/gomock" + peer "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" +) + +// MockNetwork is a mock of Network interface. +type MockNetwork struct { + ctrl *gomock.Controller + recorder *MockNetworkMockRecorder +} + +// MockNetworkMockRecorder is the mock recorder for MockNetwork. +type MockNetworkMockRecorder struct { + mock *MockNetwork +} + +// NewMockNetwork creates a new mock instance. +func NewMockNetwork(ctrl *gomock.Controller) *MockNetwork { + mock := &MockNetwork{ctrl: ctrl} + mock.recorder = &MockNetworkMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { + return m.recorder +} + +// GetRequestResponseProtocol mocks base method. +func (m *MockNetwork) GetRequestResponseProtocol(arg0 string, arg1 time.Duration, arg2 uint64) *network.RequestResponseProtocol { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRequestResponseProtocol", arg0, arg1, arg2) + ret0, _ := ret[0].(*network.RequestResponseProtocol) + return ret0 +} + +// GetRequestResponseProtocol indicates an expected call of GetRequestResponseProtocol. +func (mr *MockNetworkMockRecorder) GetRequestResponseProtocol(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestResponseProtocol", reflect.TypeOf((*MockNetwork)(nil).GetRequestResponseProtocol), arg0, arg1, arg2) +} + +// GossipMessage mocks base method. +func (m *MockNetwork) GossipMessage(arg0 network.NotificationsMessage) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GossipMessage", arg0) +} + +// GossipMessage indicates an expected call of GossipMessage. +func (mr *MockNetworkMockRecorder) GossipMessage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GossipMessage", reflect.TypeOf((*MockNetwork)(nil).GossipMessage), arg0) +} + +// RegisterNotificationsProtocol mocks base method. +func (m *MockNetwork) RegisterNotificationsProtocol(arg0 protocol.ID, arg1 network.MessageType, arg2 func() (network.Handshake, error), arg3 func([]byte) (network.Handshake, error), arg4 func(peer.ID, network.Handshake) error, arg5 func([]byte) (network.NotificationsMessage, error), arg6 func(peer.ID, network.NotificationsMessage) (bool, error), arg7 func(peer.ID, network.NotificationsMessage), arg8 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterNotificationsProtocol", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + ret0, _ := ret[0].(error) + return ret0 +} + +// RegisterNotificationsProtocol indicates an expected call of RegisterNotificationsProtocol. +func (mr *MockNetworkMockRecorder) RegisterNotificationsProtocol(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterNotificationsProtocol", reflect.TypeOf((*MockNetwork)(nil).RegisterNotificationsProtocol), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +// ReportPeer mocks base method. +func (m *MockNetwork) ReportPeer(arg0 peerset.ReputationChange, arg1 peer.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReportPeer", arg0, arg1) +} + +// ReportPeer indicates an expected call of ReportPeer. +func (mr *MockNetworkMockRecorder) ReportPeer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportPeer", reflect.TypeOf((*MockNetwork)(nil).ReportPeer), arg0, arg1) +} + +// SendMessage mocks base method. +func (m *MockNetwork) SendMessage(arg0 peer.ID, arg1 network.NotificationsMessage) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMessage", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMessage indicates an expected call of SendMessage. +func (mr *MockNetworkMockRecorder) SendMessage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockNetwork)(nil).SendMessage), arg0, arg1) +} diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index acecf8f4a9..0664cb3d32 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -229,7 +229,7 @@ type CollatorProtocolValidatorSide struct { func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, ) (peer.ID, bool) { for peerID, peerData := range cpvs.peerData { - if peerData.state.PeerState == Collating && peerData.state.CollatingPeerState.CollatorID == collatorID { + if /*peerData.state.PeerState == Collating &&*/ peerData.state.CollatingPeerState.CollatorID == collatorID { return peerID, true } } From 526c6572b37c4565e3a9337ad34cdc66f7c3fd7c Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 11 Oct 2023 21:44:51 +0530 Subject: [PATCH 36/85] prettified tests --- .../collator-protocol/message_test.go | 355 +++++++++--------- .../collator-protocol/validator_side.go | 2 +- 2 files changed, 173 insertions(+), 184 deletions(-) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 4e2e89e1d5..dd7e2a1c27 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -179,223 +179,212 @@ func TestDecodeCollationHandshake(t *testing.T) { require.Equal(t, testHandshake, msg) } -func TestHandleCollationMessage(t *testing.T) { +func TestHandleCollationMessageCommon(t *testing.T) { cpvs := CollatorProtocolValidatorSide{} + peerID := peer.ID("testPeerID") + // fail with wrong message type msg1 := &network.BlockAnnounceMessage{} - peerID1 := peer.ID("testPeerID1") - propagate, err := cpvs.handleCollationMessage(peerID1, msg1) + propagate, err := cpvs.handleCollationMessage(peerID, msg1) require.False(t, propagate) require.ErrorIs(t, err, ErrUnexpectedMessageOnCollationProtocol) // fail if we can't cast the message to type `*CollationProtocol` msg2 := NewCollationProtocol() - peerID2 := peer.ID("testPeerID2") - propagate, err = cpvs.handleCollationMessage(peerID2, msg2) + propagate, err = cpvs.handleCollationMessage(peerID, msg2) require.False(t, propagate) require.ErrorContains(t, err, "failed to cast into collator protocol message, expected: *CollationProtocol, got: collatorprotocol.CollationProtocol") // fail if no value set in the collator protocol message msg3 := NewCollationProtocol() - peerID3 := peer.ID("testPeerID3") - propagate, err = cpvs.handleCollationMessage(peerID3, &msg3) + propagate, err = cpvs.handleCollationMessage(peerID, &msg3) require.False(t, propagate) require.ErrorContains(t, err, "getting collator protocol value: varying data type not set") +} - // fail with unknown peer and report the sender if sender is not stored in our peerdata - msg4 := NewCollationProtocol() - vdt_child := NewCollatorProtocolMessage() +func TestHandleCollationMessageDeclare(t *testing.T) { + peerID := peer.ID("testPeerID") - err = vdt_child.Set(Declare{}) + collatorKeypair, err := sr25519.GenerateKeypair() require.NoError(t, err) - - err = msg4.Set(vdt_child) + collatorID, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) require.NoError(t, err) - peerID4 := peer.ID("testPeerID4") - - ctrl := gomock.NewController(t) - net := NewMockNetwork(ctrl) - cpvs.net = net - - net.EXPECT().ReportPeer(peerset.ReputationChange{ - Value: peerset.UnexpectedMessageValue, - Reason: peerset.UnexpectedMessageReason, - }, peerID4) - propagate, err = cpvs.handleCollationMessage(peerID4, &msg4) - require.False(t, propagate) - require.ErrorIs(t, err, ErrUnknownPeer) - - // report the sender if the collatorId in the Declare message belongs to any peer stored in our peer data - var collatorID parachaintypes.CollatorID - tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") - copy(collatorID[:], tempCollatID) - - msg5 := NewCollationProtocol() - vdt_child = NewCollatorProtocolMessage() - - err = vdt_child.Set(Declare{ - CollatorId: collatorID, - }) - require.NoError(t, err) - - err = msg5.Set(vdt_child) + payload := getDeclareSignaturePayload(peerID) + signatureBytes, err := collatorKeypair.Sign(payload) require.NoError(t, err) + collatorSignature := [sr25519.SignatureLength]byte{} + copy(collatorSignature[:], signatureBytes) - peerID5 := peer.ID("testPeerID5") + var invalidCollatorSignature parachaintypes.CollatorSignature + tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) + copy(invalidCollatorSignature[:], tempSignature) - cpvs.peerData = map[peer.ID]PeerData{ - peerID5: { - view: View{}, - state: PeerStateInfo{ - PeerState: Collating, - CollatingPeerState: CollatingPeerState{ - CollatorID: collatorID, + testCases := []struct { + description string + declareMsg Declare + peerData map[peer.ID]PeerData + currentAssignments map[parachaintypes.ParaID]uint + net Network + errString string + }{ + { + description: " fail with unknown peer and report the sender if sender is not stored in our peerdata", + declareMsg: Declare{}, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + errString: ErrUnknownPeer.Error(), + }, + { + description: "report the sender if the collatorId in the Declare message belongs to any peer stored in our peer data", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: collatorID.AsBytes(), + }, + }, }, }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), }, - } - - ctrl = gomock.NewController(t) - net = NewMockNetwork(ctrl) - cpvs.net = net - net.EXPECT().ReportPeer(peerset.ReputationChange{ - Value: peerset.UnexpectedMessageValue, - Reason: peerset.UnexpectedMessageReason, - }, peerID5) - - propagate, err = cpvs.handleCollationMessage(peerID5, &msg5) - require.False(t, propagate) - require.NoError(t, err) - - // fail if collator signature is invalid and report the sender - var collatorSignature parachaintypes.CollatorSignature - tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) - copy(collatorSignature[:], tempSignature) - - msg6 := NewCollationProtocol() - vdt_child = NewCollatorProtocolMessage() - - err = vdt_child.Set(Declare{ - CollatorId: collatorID, - ParaID: uint32(5), - CollatorSignature: collatorSignature, - }) - require.NoError(t, err) - - err = msg6.Set(vdt_child) - require.NoError(t, err) - - peerID6 := peer.ID("testPeerID6") - - cpvs.peerData = map[peer.ID]PeerData{ - peerID6: { - view: View{}, - state: PeerStateInfo{ - PeerState: Connected, + { + description: "fail if collator signature could not be verified", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: parachaintypes.CollatorSignature{}, }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + errString: "verifying signature", }, - } - - ctrl = gomock.NewController(t) - net = NewMockNetwork(ctrl) - cpvs.net = net - net.EXPECT().ReportPeer(peerset.ReputationChange{ - Value: peerset.InvalidSignatureValue, - Reason: peerset.InvalidSignatureReason, - }, peerID6) - - propagate, err = cpvs.handleCollationMessage(peerID6, &msg6) - require.False(t, propagate) - require.ErrorIs(t, err, crypto.ErrSignatureVerificationFailed) - - // fail if paraID in Declare message is not assigned to our peer and report the sender - peerID7 := peer.ID("testPeerID7") - - collatorKeypair, err := sr25519.GenerateKeypair() - require.NoError(t, err) - collatorID1, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) - require.NoError(t, err) - - payload := getDeclareSignaturePayload(peerID7) - signatureBytes, err := collatorKeypair.Sign(payload) - require.NoError(t, err) - collatorSignature1 := [sr25519.SignatureLength]byte{} - copy(collatorSignature1[:], signatureBytes) - - msg7 := NewCollationProtocol() - vdt_child = NewCollatorProtocolMessage() - - err = vdt_child.Set(Declare{ - CollatorId: collatorID1.AsBytes(), - ParaID: uint32(5), - CollatorSignature: collatorSignature1, - }) - require.NoError(t, err) - - err = msg7.Set(vdt_child) - require.NoError(t, err) - - cpvs.peerData = map[peer.ID]PeerData{ - peerID7: { - view: View{}, - state: PeerStateInfo{ - PeerState: Connected, + { + description: "fail if collator signature is invalid and report the sender", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: invalidCollatorSignature, }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.InvalidSignatureValue, + Reason: peerset.InvalidSignatureReason, + }, peerID) + return net + }(), + errString: crypto.ErrSignatureVerificationFailed.Error(), }, - } - - ctrl = gomock.NewController(t) - net = NewMockNetwork(ctrl) - cpvs.net = net - net.EXPECT().ReportPeer(peerset.ReputationChange{ - Value: peerset.UnneededCollatorValue, - Reason: peerset.UnneededCollatorReason, - }, peerID7) - - propagate, err = cpvs.handleCollationMessage(peerID7, &msg7) - require.False(t, propagate) - require.NoError(t, err) - - // success case: check if PeerState of the sender has changed to Collating from Connected - peerID8 := peer.ID("testPeerID7") - - msg8 := NewCollationProtocol() - vdt_child = NewCollatorProtocolMessage() - - err = vdt_child.Set(Declare{ - CollatorId: collatorID1.AsBytes(), - ParaID: uint32(5), - CollatorSignature: collatorSignature1, - }) - require.NoError(t, err) - - err = msg8.Set(vdt_child) - require.NoError(t, err) - - cpvs.peerData = map[peer.ID]PeerData{ - peerID8: { - view: View{}, - state: PeerStateInfo{ - PeerState: Connected, + { + // TODO: test that we disconnect sender in this case, after we add that functionality + description: "fail if paraID in Declare message is not assigned to our peer and report the sender", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + currentAssignments: make(map[parachaintypes.ParaID]uint), + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnneededCollatorValue, + Reason: peerset.UnneededCollatorReason, + }, peerID) + return net + }(), + }, + { + description: "success case: check if PeerState of the sender has changed to Collating from Connected", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + currentAssignments: map[parachaintypes.ParaID]uint{ + parachaintypes.ParaID(5): 1, }, }, } - // ctrl = gomock.NewController(t) - // net = NewMockNetwork(ctrl) - // cpvs.net = net - // net.EXPECT().ReportPeer(peerset.ReputationChange{ - // Value: peerset.UnneededCollatorValue, - // Reason: peerset.UnneededCollatorReason, - // }, peerID7) - - cpvs.currentAssignments = map[parachaintypes.ParaID]uint{ - parachaintypes.ParaID(5): 1, + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + peerData: c.peerData, + currentAssignments: c.currentAssignments, + } + msg := NewCollationProtocol() + vdt_child := NewCollatorProtocolMessage() + + err = vdt_child.Set(c.declareMsg) + require.NoError(t, err) + + err = msg.Set(vdt_child) + require.NoError(t, err) + + propagate, err := cpvs.handleCollationMessage(peerID, &msg) + require.False(t, propagate) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + }) } - - propagate, err = cpvs.handleCollationMessage(peerID8, &msg8) - require.False(t, propagate) - require.NoError(t, err) } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 0664cb3d32..77c1ff1153 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -229,7 +229,7 @@ type CollatorProtocolValidatorSide struct { func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, ) (peer.ID, bool) { for peerID, peerData := range cpvs.peerData { - if /*peerData.state.PeerState == Collating &&*/ peerData.state.CollatingPeerState.CollatorID == collatorID { + if peerData.state.CollatingPeerState.CollatorID == collatorID { return peerID, true } } From cf842899c6a45e555cb382043ab356e8b4c06d06 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 11 Oct 2023 21:49:25 +0530 Subject: [PATCH 37/85] added one more check --- dot/parachain/collator-protocol/message_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index dd7e2a1c27..274b1a7f9c 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -227,6 +227,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) { peerData map[peer.ID]PeerData currentAssignments map[parachaintypes.ParaID]uint net Network + success bool errString string }{ { @@ -357,6 +358,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) { currentAssignments: map[parachaintypes.ParaID]uint{ parachaintypes.ParaID(5): 1, }, + success: true, }, } @@ -385,6 +387,13 @@ func TestHandleCollationMessageDeclare(t *testing.T) { } else { require.ErrorContains(t, err, c.errString) } + + if c.success { + peerData, ok := cpvs.peerData[peerID] + require.True(t, ok) + require.Equal(t, Collating, peerData.state.PeerState) + require.Equal(t, c.declareMsg.CollatorId, peerData.state.CollatingPeerState.CollatorID) + } }) } } From 5051079c16cf5fc79a2fbad465a8e5d8f74066ef Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Thu, 12 Oct 2023 09:28:08 +0530 Subject: [PATCH 38/85] Update dot/parachain/collator-protocol/message.go Co-authored-by: JimboJ <40345116+jimjbrettj@users.noreply.github.com> --- dot/parachain/collator-protocol/message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 03b41af75b..2a70c5cf83 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -178,7 +178,7 @@ const ( ) // getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. -// The payload is a the local peed id of the node, which serves to prove that it controls the +// The payload is a the local peer id of the node, which serves to prove that it controls the // collator key it is declaring and intends to collate under. func getDeclareSignaturePayload(peerID peer.ID) []byte { payload := []byte("COLL") From 9b5a96904b62fd023895941f715a97e6ffb9fab5 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Thu, 12 Oct 2023 09:34:18 +0530 Subject: [PATCH 39/85] Update dot/parachain/collator-protocol/message.go --- dot/parachain/collator-protocol/message.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 2a70c5cf83..d37fc2d8e2 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -218,8 +218,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( } switch collatorProtocolMessage.Index() { - // TODO: Make sure that V1 types are covered. - // TODO: Create an issue to cover v2 types. + // TODO: Create an issue to cover v2 types. #3534 case 0: // Declare declareMessage, ok := collatorProtocolMessageV.(Declare) if !ok { From f43579424fb65032739864eefdf939caeafac702 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 12 Oct 2023 09:42:15 +0530 Subject: [PATCH 40/85] fixing lint --- dot/parachain/collator-protocol/message.go | 6 ++++-- dot/parachain/collator-protocol/message_test.go | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 03b41af75b..69574b2049 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -200,7 +200,9 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( collatorProtocol, ok := msg.(*CollationProtocol) if !ok { - return propagate, fmt.Errorf("failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", msg) + return propagate, fmt.Errorf( + "failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", + msg) } collatorProtocolV, err := collatorProtocol.Value() @@ -270,7 +272,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( return propagate, fmt.Errorf("verifying signature: %w", err) } - // NOTE: assingments are setting when we handle view changes + // NOTE: assignments are setting when we handle view changes _, ok = cpvs.currentAssignments[parachaintypes.ParaID(declareMessage.ParaID)] if ok { logger.Errorf("declared as collator for current para: %d", declareMessage.ParaID) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 274b1a7f9c..f586fe86f0 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -194,7 +194,8 @@ func TestHandleCollationMessageCommon(t *testing.T) { msg2 := NewCollationProtocol() propagate, err = cpvs.handleCollationMessage(peerID, msg2) require.False(t, propagate) - require.ErrorContains(t, err, "failed to cast into collator protocol message, expected: *CollationProtocol, got: collatorprotocol.CollationProtocol") + require.ErrorContains(t, err, "failed to cast into collator protocol message, "+ + "expected: *CollationProtocol, got: collatorprotocol.CollationProtocol") // fail if no value set in the collator protocol message msg3 := NewCollationProtocol() @@ -204,6 +205,8 @@ func TestHandleCollationMessageCommon(t *testing.T) { } func TestHandleCollationMessageDeclare(t *testing.T) { + t.Parallel() + peerID := peer.ID("testPeerID") collatorKeypair, err := sr25519.GenerateKeypair() @@ -231,7 +234,7 @@ func TestHandleCollationMessageDeclare(t *testing.T) { errString string }{ { - description: " fail with unknown peer and report the sender if sender is not stored in our peerdata", + description: "fail with unknown peer and report the sender if sender is not stored in our peerdata", declareMsg: Declare{}, net: func() Network { ctrl := gomock.NewController(t) @@ -245,7 +248,8 @@ func TestHandleCollationMessageDeclare(t *testing.T) { errString: ErrUnknownPeer.Error(), }, { - description: "report the sender if the collatorId in the Declare message belongs to any peer stored in our peer data", + description: "report the sender if the collatorId in the Declare message belongs to " + + "any peer stored in our peer data", declareMsg: Declare{ CollatorId: collatorID.AsBytes(), }, From fafbb5eb26d0a34896a47b5f596f78a7755a1654 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 12 Oct 2023 14:45:51 +0530 Subject: [PATCH 41/85] underscore to camelcase --- .../collator-protocol/message_test.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index f586fe86f0..61977f6ea3 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -126,16 +126,16 @@ func TestCollationProtocol(t *testing.T) { t.Run("marshal", func(t *testing.T) { t.Parallel() - vdt_parent := NewCollationProtocol() - vdt_child := NewCollatorProtocolMessage() + vdtParent := NewCollationProtocol() + vdtChild := NewCollatorProtocolMessage() - err := vdt_child.Set(c.enumValue) + err := vdtChild.Set(c.enumValue) require.NoError(t, err) - err = vdt_parent.Set(vdt_child) + err = vdtParent.Set(vdtChild) require.NoError(t, err) - bytes, err := scale.Marshal(vdt_parent) + bytes, err := scale.Marshal(vdtParent) require.NoError(t, err) require.Equal(t, c.encodingValue, bytes) @@ -144,18 +144,18 @@ func TestCollationProtocol(t *testing.T) { t.Run("unmarshal", func(t *testing.T) { t.Parallel() - vdt_parent := NewCollationProtocol() - err := scale.Unmarshal(c.encodingValue, &vdt_parent) + vdtParent := NewCollationProtocol() + err := scale.Unmarshal(c.encodingValue, &vdtParent) require.NoError(t, err) - vdt_child_temp, err := vdt_parent.Value() + vdtChildTemp, err := vdtParent.Value() require.NoError(t, err) - require.Equal(t, uint(0), vdt_child_temp.Index()) + require.Equal(t, uint(0), vdtChildTemp.Index()) - vdt_child := vdt_child_temp.(CollatorProtocolMessage) + vdtChild := vdtChildTemp.(CollatorProtocolMessage) require.NoError(t, err) - actualData, err := vdt_child.Value() + actualData, err := vdtChild.Value() require.NoError(t, err) require.Equal(t, c.enumValue.Index(), actualData.Index()) @@ -376,12 +376,12 @@ func TestHandleCollationMessageDeclare(t *testing.T) { currentAssignments: c.currentAssignments, } msg := NewCollationProtocol() - vdt_child := NewCollatorProtocolMessage() + vdtChild := NewCollatorProtocolMessage() - err = vdt_child.Set(c.declareMsg) + err = vdtChild.Set(c.declareMsg) require.NoError(t, err) - err = msg.Set(vdt_child) + err = msg.Set(vdtChild) require.NoError(t, err) propagate, err := cpvs.handleCollationMessage(peerID, &msg) From c9055ddc318355ed91d10257368bd04c8ab045bb Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Thu, 12 Oct 2023 10:41:40 -0400 Subject: [PATCH 42/85] feat(parachain): availability store skeleton (#3521) --- .../availability-store/availabilitystore.go | 84 +++++++++++++++++++ dot/parachain/availability-store/messages.go | 57 +++++++++++++ dot/parachain/availability-store/register.go | 12 +++ dot/parachain/service.go | 9 +- 4 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 dot/parachain/availability-store/availabilitystore.go create mode 100644 dot/parachain/availability-store/messages.go create mode 100644 dot/parachain/availability-store/register.go diff --git a/dot/parachain/availability-store/availabilitystore.go b/dot/parachain/availability-store/availabilitystore.go new file mode 100644 index 0000000000..91cfc1a7d9 --- /dev/null +++ b/dot/parachain/availability-store/availabilitystore.go @@ -0,0 +1,84 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package availability_store + +import ( + "context" + + "github.com/ChainSafe/gossamer/internal/log" +) + +var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-availability-store")) + +type AvailabilityStoreSubsystem struct { + SubSystemToOverseer chan<- any + OverseerToSubSystem <-chan any + //db interface{} // Define the actual database type + //config Config // Define the actual config type + //pruningConfig PruningConfig + //clock Clock + //metrics Metrics +} + +func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, + SubsystemToOverseer chan any) error { + av.processMessages() + return nil +} + +func (av *AvailabilityStoreSubsystem) processMessages() { + for msg := range av.OverseerToSubSystem { + logger.Debugf("received message %v", msg) + switch msg := msg.(type) { + case QueryAvailableData: + av.handleQueryAvailableData(msg) + case QueryDataAvailability: + av.handleQueryDataAvailability(msg) + case QueryChunk: + av.handleQueryChunk(msg) + case QueryChunkSize: + av.handleQueryChunkSize(msg) + case QueryAllChunks: + av.handleQueryAllChunks(msg) + case QueryChunkAvailability: + av.handleQueryChunkAvailability(msg) + case StoreChunk: + av.handleStoreChunk(msg) + case StoreAvailableData: + av.handleStoreAvailableData(msg) + } + } +} + +func (av *AvailabilityStoreSubsystem) handleQueryAvailableData(msg QueryAvailableData) { + // TODO: handle query available data +} + +func (av *AvailabilityStoreSubsystem) handleQueryDataAvailability(msg QueryDataAvailability) { + // TODO: handle query data availability +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunk(msg QueryChunk) { + // TODO: handle query chunk +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunkSize(msg QueryChunkSize) { + // TODO: handle query chunk size +} + +func (av *AvailabilityStoreSubsystem) handleQueryAllChunks(msg QueryAllChunks) { + // TODO: handle query all chunks +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunkAvailability(msg QueryChunkAvailability) { + // TODO: handle query chunk availability +} + +func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) { + // TODO: handle store chunk +} + +func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailableData) { + // TODO: handle store available data +} diff --git a/dot/parachain/availability-store/messages.go b/dot/parachain/availability-store/messages.go new file mode 100644 index 0000000000..8524e4fd9c --- /dev/null +++ b/dot/parachain/availability-store/messages.go @@ -0,0 +1,57 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package availability_store + +import ( + "github.com/ChainSafe/gossamer/lib/common" +) + +// QueryAvailableData query a AvailableData from the AV store +type QueryAvailableData struct { + CandidateHash common.Hash + AvailableData AvailableData +} + +type QueryDataAvailability struct { + CandidateHash common.Hash + Sender chan AvailableData +} + +type QueryChunk struct { + CandidateHash common.Hash + ValidatorIndex uint32 + Sender chan []byte +} + +type QueryChunkSize struct { + CandidateHash common.Hash + Sender chan uint32 +} + +type QueryAllChunks struct { + CandidateHash common.Hash + Sender chan []byte +} + +type QueryChunkAvailability struct { + CandidateHash common.Hash + ValidatorIndex uint32 + Sender chan bool +} + +type StoreChunk struct { + CandidateHash common.Hash + Chunk []byte + Sender chan any +} + +type StoreAvailableData struct { + CandidateHash common.Hash + NValidators uint32 + AvailableData AvailableData + ExpectedErasureRoot common.Hash + Sender chan any +} + +type AvailableData struct{} // Define your AvailableData type diff --git a/dot/parachain/availability-store/register.go b/dot/parachain/availability-store/register.go new file mode 100644 index 0000000000..c247a77786 --- /dev/null +++ b/dot/parachain/availability-store/register.go @@ -0,0 +1,12 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package availability_store + +func Register(overseerChan chan<- any) (*AvailabilityStoreSubsystem, error) { + availabilityStore := AvailabilityStoreSubsystem{ + SubSystemToOverseer: overseerChan, + } + + return &availabilityStore, nil +} diff --git a/dot/parachain/service.go b/dot/parachain/service.go index 58b26ba0e4..e1d4d21494 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -8,6 +8,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" + availability_store "github.com/ChainSafe/gossamer/dot/parachain/availability-store" "github.com/ChainSafe/gossamer/dot/parachain/backing" collatorprotocol "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol" "github.com/ChainSafe/gossamer/dot/parachain/overseer" @@ -34,11 +35,17 @@ var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, error) { overseer := overseer.NewOverseer() + availabilityStore, err := availability_store.Register(overseer.SubsystemsToOverseer) + if err != nil { + return nil, fmt.Errorf("registering availability store: %w", err) + } + availabilityStore.OverseerToSubSystem = overseer.RegisterSubsystem(availabilityStore) + validationProtocolID := GeneratePeersetProtocolName( ValidationProtocolName, forkID, genesisHash, ValidationProtocolVersion) // register validation protocol - err := net.RegisterNotificationsProtocol( + err = net.RegisterNotificationsProtocol( protocol.ID(validationProtocolID), network.ValidationMsgType, getValidationHandshake, From 6a300dc309ffe19867d09d04f9d20472ba54c4e7 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 12 Oct 2023 20:13:10 +0530 Subject: [PATCH 43/85] temp --- dot/parachain/collator-protocol/message.go | 117 +++++++++++++++++- .../collator-protocol/validator_side.go | 61 ++++++++- dot/peerset/constants.go | 12 +- 3 files changed, 181 insertions(+), 9 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 80e3dd4937..3c8135b9d5 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -177,6 +177,108 @@ const ( Seconded ) +// BlockedAdvertisement is vstaging advertisement that was rejected by the backing +// subsystem. Validator may fetch it later if its fragment +// membership gets recognized before relay parent goes out of view. +type BlockedAdvertisement struct { + // peer that advertised the collation + peerID peer.ID + collatorID parachaintypes.CollatorID + candidateRelayParent common.Hash + candidateHash parachaintypes.CandidateHash +} + +func canSecond() bool { + // TODO + // https://github.com/paritytech/polkadot-sdk/blob/6079b6dd3aaba56ef257111fda74a57a800f16d0/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L955 + return false +} + +func (cpvs CollatorProtocolValidatorSide) enqueueCollation(collations Collations) { + switch collations.status { + case Fetching, WaitingOnValidation: + + } + +} +func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, peerID peer.ID, prospectiveCandidate *ProspectiveCandidate) error { + // TODO: + // - tracks advertisements received and the source (peer id) of the advertisement + + // - accept one advertisement per collator per source per relay-parent + + perRelayParent, ok := cpvs.perRelayParent[relayParent] + if !ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return ErrRelayParentUnknown + } + + peerData, ok := cpvs.peerData[peerID] + if !ok { + return ErrUnknownPeer + } + + if peerData.state.PeerState != Collating { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return ErrUndeclaredPara + } + + collatorParaID := peerData.state.CollatingPeerState.ParaID + + if perRelayParent.assignment == nil || *perRelayParent.assignment != collatorParaID { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.WrongParaValue, + Reason: peerset.WrongParaReason, + }, peerID) + return ErrInvalidAssignment + } + + if perRelayParent.prospectiveParachainMode.isEnabled && prospectiveCandidate == nil { + return ErrProtocolMismatch + } + + isAdvertisementInvalid, err := peerData.InsertAdvertisement() + if isAdvertisementInvalid { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + logger.Errorf(ErrInvalidAdvertisement.Error()) + } + if err != nil { + return fmt.Errorf("inserting advertisement: %w", err) + } + + if perRelayParent.collations.IsSecondedLimitReached(perRelayParent.prospectiveParachainMode) { + return ErrSecondedLimitReached + } + + isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || canSecond() + + if !isSecondingAllowed { + logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", + relayParent, collatorParaID, prospectiveCandidate.CandidateHash) + + cpvs.BlockedAdvertisements = append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + peerID: peerID, + collatorID: peerData.state.CollatingPeerState.CollatorID, + candidateRelayParent: relayParent, + candidateHash: prospectiveCandidate.CandidateHash, + }) + return nil + } + + cpvs.enqueueCollation(perRelayParent.collations) + + return nil +} + // getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. // The payload is a the local peer id of the node, which serves to prove that it controls the // collator key it is declaring and intends to collate under. @@ -291,7 +393,20 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( // Currently we have a Handler in dot/peerset, but it does not get used anywhere. } case 1: // AdvertiseCollation - // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 + advertiseCollationMessage, ok := collatorProtocolMessageV.(AdvertiseCollation) + if !ok { + return propagate, errors.New("expected message to be advertise collation") + } + + err := cpvs.handleAdvertisement(common.Hash(advertiseCollationMessage)) + if err != nil { + return propagate, fmt.Errorf("handling v1 advertisement: %w", err) + } + // TODO: + // - tracks advertisements received and the source (peer id) of the advertisement + + // - accept one advertisement per collator per source per relay-parent + case 2: // CollationSeconded logger.Errorf("unexpected collation seconded message from peer %s, decreasing its reputation", sender) cpvs.net.ReportPeer(peerset.ReputationChange{ diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 77c1ff1153..fda9356b20 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -33,6 +33,12 @@ var ( ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") ErrCollationNotInView = errors.New("collation is not in our view") ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") + ErrProtocolMismatch = errors.New("An advertisement format doesn't match the relay parent") + ErrSecondedLimitReached = errors.New("Para reached a limit of seconded candidates for this relay parent.") + ErrRelayParentUnknown = errors.New("relay parent is unknown") + ErrUndeclaredPara = errors.New("peer has not declared its para id") + ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") + ErrInvalidAdvertisement = errors.New("advertisement is invalid") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -145,9 +151,9 @@ func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, par } } -func (peerData *PeerData) InsertAdvertisement() error { +func (peerData *PeerData) InsertAdvertisement() (isAdvertisementInvalid bool, err error) { // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 - return nil + return false, nil } type PeerStateInfo struct { @@ -224,6 +230,57 @@ type CollatorProtocolValidatorSide struct { // Parachains we're currently assigned to. With async backing enabled // this includes assignments from the implicit view. currentAssignments map[parachaintypes.ParaID]uint + + // state tracked per relay parent + perRelayParent map[common.Hash]PerRelayParent // map[replay parent]PerRelayParent + + // TODO: In rust this is a map, let's see if we can get away with a map + // blocked_advertisements: HashMap<(ParaId, Hash), Vec>, + BlockedAdvertisements []BlockedAdvertisement +} + +// struct PerRelayParent { +// prospective_parachains_mode: ProspectiveParachainsMode, +// assignment: GroupAssignments, +// collations: Collations, +// } +type ProspectiveParachainsMode struct { + // if disabled, there are no prospective parachains. Runtime API does not have support for `async_backing_params` + isEnabled bool + + // these values would be present only if `isEnabled` is true + + // The maximum number of para blocks between the para head in a relay parent and a new candidate. + // Restricts nodes from building arbitrary long chains and spamming other validators. + maxCandidateDepth uint + + // How many ancestors of a relay parent are allowed to build candidates on top of. + allowedAncestryLen uint +} + +type PerRelayParent struct { + prospectiveParachainMode ProspectiveParachainsMode + assignment *parachaintypes.ParaID + collations Collations +} + +type Collations struct { + // What is the current status in regards to a collation for this relay parent? + status CollationStatus + // how many collations have been seconded + secondedCount uint +} + +// IsSecondedLimitReached check the limit of seconded candidates for a given para has been reached. +func (collations Collations) IsSecondedLimitReached(relayParentMode ProspectiveParachainsMode) bool { + var secondedLimit uint + if relayParentMode.isEnabled { + secondedLimit = relayParentMode.maxCandidateDepth + 1 + } else { + secondedLimit = 1 + } + + return collations.secondedCount >= secondedLimit } func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, diff --git a/dot/peerset/constants.go b/dot/peerset/constants.go index e5fc5be478..dbd93d7552 100644 --- a/dot/peerset/constants.go +++ b/dot/peerset/constants.go @@ -76,17 +76,17 @@ const ( BadJustificationReason = "Bad justification" // GenesisMismatch is used when peer has a different genesis - GenesisMismatch Reputation = Malicious + GenesisMismatch = Malicious // GenesisMismatchReason used when a peer has a different genesis GenesisMismatchReason = "Genesis mismatch" // BenefitNotifyGoodValue is used when a collator was noted good by another subsystem - BenefitNotifyGoodValue Reputation = BenefitMinor + BenefitNotifyGoodValue = BenefitMinor // BenefitNotifyGoodReason is used when a collator was noted good by another subsystem BenefitNotifyGoodReason = "A collator was noted good by another subsystem" // UnexpectedMessageValue is used when validator side of the collator protocol receives an unexpected message - UnexpectedMessageValue Reputation = CostMinor + UnexpectedMessageValue = CostMinor // UnexpectedMessageReason is used when validator side of the collator protocol receives an unexpected message UnexpectedMessageReason = "An unexpected message" @@ -101,17 +101,17 @@ const ( NetworkErrorReason = "Some network error" // InvalidSignatureValue is used when signature of the network message is invalid. - InvalidSignatureValue Reputation = Malicious + InvalidSignatureValue = Malicious // InvalidSignatureReason is used when signature of the network message is invalid. InvalidSignatureReason = "Invalid network message signature" // ReportBadCollatorValue is used when a collator was reported to be bad by another subsystem - ReportBadCollatorValue Reputation = Malicious + ReportBadCollatorValue = Malicious // ReportBadCollatorReason is used when a collator was reported to be bad by another subsystem ReportBadCollatorReason = "A collator was reported by another subsystem" // WrongParaValue is used when a collator provided a collation for the wrong para - WrongParaValue Reputation = Malicious + WrongParaValue = Malicious // WrongParaReason is used when a collator provided a collation for the wrong para WrongParaReason = "A collator provided a collation for the wrong para" From c9588eb914dbb598d47ed33612cbc95349e9ed0a Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Fri, 13 Oct 2023 20:53:04 +0530 Subject: [PATCH 44/85] some progress --- dot/parachain/collator-protocol/message.go | 20 ++++--- .../collator-protocol/validator_side.go | 56 ++++++++++++++++--- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 3c8135b9d5..5e7c054dbc 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -201,7 +201,9 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation(collations Collations } } -func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, peerID peer.ID, prospectiveCandidate *ProspectiveCandidate) error { + +func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, + prospectiveCandidate *ProspectiveCandidate) error { // TODO: // - tracks advertisements received and the source (peer id) of the advertisement @@ -212,11 +214,11 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, - }, peerID) + }, sender) return ErrRelayParentUnknown } - peerData, ok := cpvs.peerData[peerID] + peerData, ok := cpvs.peerData[sender] if !ok { return ErrUnknownPeer } @@ -225,7 +227,7 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, - }, peerID) + }, sender) return ErrUndeclaredPara } @@ -235,11 +237,13 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.WrongParaValue, Reason: peerset.WrongParaReason, - }, peerID) + }, sender) return ErrInvalidAssignment } + // Note: Prospective Parachain mode would be set or edited when the view gets updated. if perRelayParent.prospectiveParachainMode.isEnabled && prospectiveCandidate == nil { + // Expected v2 advertisement. return ErrProtocolMismatch } @@ -248,7 +252,7 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, - }, peerID) + }, sender) logger.Errorf(ErrInvalidAdvertisement.Error()) } if err != nil { @@ -266,7 +270,7 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common relayParent, collatorParaID, prospectiveCandidate.CandidateHash) cpvs.BlockedAdvertisements = append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ - peerID: peerID, + peerID: sender, collatorID: peerData.state.CollatingPeerState.CollatorID, candidateRelayParent: relayParent, candidateHash: prospectiveCandidate.CandidateHash, @@ -398,7 +402,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( return propagate, errors.New("expected message to be advertise collation") } - err := cpvs.handleAdvertisement(common.Hash(advertiseCollationMessage)) + err := cpvs.handleAdvertisement(common.Hash(advertiseCollationMessage), sender, nil) if err != nil { return propagate, fmt.Errorf("handling v1 advertisement: %w", err) } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index fda9356b20..e50d374b1c 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -33,12 +33,14 @@ var ( ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") ErrCollationNotInView = errors.New("collation is not in our view") ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") - ErrProtocolMismatch = errors.New("An advertisement format doesn't match the relay parent") - ErrSecondedLimitReached = errors.New("Para reached a limit of seconded candidates for this relay parent.") + ErrProtocolMismatch = errors.New("an advertisement format doesn't match the relay parent") + ErrSecondedLimitReached = errors.New("para reached a limit of seconded candidates for this relay parent") ErrRelayParentUnknown = errors.New("relay parent is unknown") ErrUndeclaredPara = errors.New("peer has not declared its para id") ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") ErrInvalidAdvertisement = errors.New("advertisement is invalid") + ErrUndeclaredCollator = errors.New("no prior declare message received for this collator ") + ErrOutOfView = errors.New("collation relay parent is out of our view") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -151,8 +153,47 @@ func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, par } } -func (peerData *PeerData) InsertAdvertisement() (isAdvertisementInvalid bool, err error) { +func IsRelayParentInImplicitView( + relayParent common.Hash, + relayParentMode ProspectiveParachainsMode, + implicitView ImplicitView, + activeLeaves map[common.Hash]ProspectiveParachainsMode, + paraID parachaintypes.ParaID, +) bool { + if !relayParentMode.isEnabled { + _, ok := activeLeaves[relayParent] + return ok + } + + for hash, mode := range activeLeaves { + knownAllowedRelayParent := implicitView.KnownAllowedRelayParentsUnder(hash, paraID) + if mode.isEnabled && knownAllowedRelayParent.String() == relayParent.String() { + return true + } + } + + return false +} + +func (peerData *PeerData) InsertAdvertisement( + onRelayParent common.Hash, + relayParentMode ProspectiveParachainsMode, + candidateHash *common.Hash, + implicitView ImplicitView, + activeLeaves map[common.Hash]ProspectiveParachainsMode, +) (isAdvertisementInvalid bool, err error) { // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 + + switch peerData.state.PeerState { + case Connected: + return true, ErrUndeclaredCollator + case Collating: + if !IsRelayParentInImplicitView(onRelayParent, relayParentMode, implicitView, + activeLeaves, peerData.state.CollatingPeerState.ParaID) { + return true, ErrOutOfView + } + + } return false, nil } @@ -239,11 +280,10 @@ type CollatorProtocolValidatorSide struct { BlockedAdvertisements []BlockedAdvertisement } -// struct PerRelayParent { -// prospective_parachains_mode: ProspectiveParachainsMode, -// assignment: GroupAssignments, -// collations: Collations, -// } +// Prospective parachains mode of a relay parent. Defined by +// the Runtime API version. +// +// Needed for the period of transition to asynchronous backing. type ProspectiveParachainsMode struct { // if disabled, there are no prospective parachains. Runtime API does not have support for `async_backing_params` isEnabled bool From a9c55faf007fa1f7cc62cfe0a5853352f10ec5c3 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Fri, 13 Oct 2023 21:32:12 +0530 Subject: [PATCH 45/85] temp --- .../backing_implicit_view.go | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 dot/parachain/collator-protocol/backing_implicit_view.go diff --git a/dot/parachain/collator-protocol/backing_implicit_view.go b/dot/parachain/collator-protocol/backing_implicit_view.go new file mode 100644 index 0000000000..69b19190f4 --- /dev/null +++ b/dot/parachain/collator-protocol/backing_implicit_view.go @@ -0,0 +1,33 @@ +package collatorprotocol + +import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +// https://github.com/paritytech/polkadot-sdk/blob/d2fc1d7c91971e6e630a9db8cb627f8fdc91e8a4/polkadot/node/subsystem-util/src/backing_implicit_view.rs#L102 +type ImplicitView struct { +} + +// / Get the known, allowed relay-parents that are valid for parachain candidates +// / which could be backed in a child of a given block for a given para ID. +// / +// / This is expressed as a contiguous slice of relay-chain block hashes which may +// / include the provided block hash itself. +// / +// / If `para_id` is `None`, this returns all valid relay-parents across all paras +// / for the leaf. +// / +// / `None` indicates that the block hash isn't part of the implicit view or that +// / there are no known allowed relay parents. +// / +// / This always returns `Some` for active leaves or for blocks that previously +// / were active leaves. +// / +// / This can return the empty slice, which indicates that no relay-parents are allowed +// / for the para, e.g. if the para is not scheduled at the given block hash. +func (iview ImplicitView) KnownAllowedRelayParentsUnder(hash common.Hash, + paraID parachaintypes.ParaID) common.Hash { + + return common.Hash{} +} From 23dccd0476f934769965a72ca9073e2edbd30769 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Tue, 17 Oct 2023 00:20:24 +0530 Subject: [PATCH 46/85] feat(parachain/collator): handle Declare message received by a collator (#3529) - Verify the collator by its signature and - Save its collator id, peer id and Para id that it collates for in our memory --- dot/parachain/collator-protocol/message.go | 105 +++++++- .../collator-protocol/message_test.go | 251 +++++++++++++++++- .../collator-protocol/mocks_generate_test.go | 6 + dot/parachain/collator-protocol/mocks_test.go | 105 ++++++++ dot/parachain/collator-protocol/register.go | 1 + .../collator-protocol/validator_side.go | 30 ++- 6 files changed, 472 insertions(+), 26 deletions(-) create mode 100644 dot/parachain/collator-protocol/mocks_generate_test.go create mode 100644 dot/parachain/collator-protocol/mocks_test.go diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index f914316125..80e3dd4937 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -11,6 +11,8 @@ import ( parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" "github.com/libp2p/go-libp2p/core/peer" ) @@ -175,32 +177,119 @@ const ( Seconded ) +// getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. +// The payload is a the local peer id of the node, which serves to prove that it controls the +// collator key it is declaring and intends to collate under. +func getDeclareSignaturePayload(peerID peer.ID) []byte { + payload := []byte("COLL") + payload = append(payload, peerID...) + + return payload +} + func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( sender peer.ID, msg network.NotificationsMessage) (bool, error) { + + // we don't propagate collation messages, so it will always be false + propagate := false + if msg.Type() != network.CollationMsgType { - return false, fmt.Errorf("unexpected message type, expected: %d, found:%d", + return propagate, fmt.Errorf("%w, expected: %d, found:%d", ErrUnexpectedMessageOnCollationProtocol, network.CollationMsgType, msg.Type()) } collatorProtocol, ok := msg.(*CollationProtocol) if !ok { - return false, fmt.Errorf("failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", msg) + return propagate, fmt.Errorf( + "failed to cast into collator protocol message, expected: *CollationProtocol, got: %T", + msg) } collatorProtocolV, err := collatorProtocol.Value() if err != nil { - return false, fmt.Errorf("getting collator protocol value: %w", err) + return propagate, fmt.Errorf("getting collator protocol value: %w", err) } collatorProtocolMessage, ok := collatorProtocolV.(CollatorProtocolMessage) if !ok { - return false, errors.New("expected value to be collator protocol message") + return propagate, errors.New("expected value to be collator protocol message") + } + + collatorProtocolMessageV, err := collatorProtocolMessage.Value() + if err != nil { + return propagate, fmt.Errorf("getting collator protocol message value: %w", err) } switch collatorProtocolMessage.Index() { - // TODO: Make sure that V1 and VStaging both types are covered - // All the types covered currently are V1. + // TODO: Create an issue to cover v2 types. #3534 case 0: // Declare - // TODO: handle collator declaration https://github.com/ChainSafe/gossamer/issues/3513 + declareMessage, ok := collatorProtocolMessageV.(Declare) + if !ok { + return propagate, errors.New("expected message to be declare") + } + + // check if we already have the collator id declared in this message. If so, punish the + // peer who sent us this message by reducing its reputation + _, ok = cpvs.getPeerIDFromCollatorID(declareMessage.CollatorId) + if ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return propagate, nil + } + + // NOTE: peerData for sender will be filled when it gets connected to us + peerData, ok := cpvs.peerData[sender] + if !ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return propagate, fmt.Errorf("%w: %s", ErrUnknownPeer, sender) + } + + if peerData.state.PeerState == Collating { + logger.Error("peer is already in the collating state") + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return propagate, nil + } + + // check signature declareMessage.CollatorSignature + err = sr25519.VerifySignature(declareMessage.CollatorId[:], declareMessage.CollatorSignature[:], + getDeclareSignaturePayload(sender)) + if errors.Is(err, crypto.ErrSignatureVerificationFailed) { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.InvalidSignatureValue, + Reason: peerset.InvalidSignatureReason, + }, sender) + return propagate, fmt.Errorf("invalid signature: %w", err) + } + if err != nil { + return propagate, fmt.Errorf("verifying signature: %w", err) + } + + // NOTE: assignments are setting when we handle view changes + _, ok = cpvs.currentAssignments[parachaintypes.ParaID(declareMessage.ParaID)] + if ok { + logger.Errorf("declared as collator for current para: %d", declareMessage.ParaID) + + peerData.SetCollating(declareMessage.CollatorId, parachaintypes.ParaID(declareMessage.ParaID)) + cpvs.peerData[sender] = peerData + } else { + logger.Errorf("declared as collator for unneeded para: %d", declareMessage.ParaID) + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnneededCollatorValue, + Reason: peerset.UnneededCollatorReason, + }, sender) + + // TODO: Disconnect peer. #3530 + // Do a thorough review of substrate/client/network/src/ + // check how are they managing peerset of different protocol. + // Currently we have a Handler in dot/peerset, but it does not get used anywhere. + } case 1: // AdvertiseCollation // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 case 2: // CollationSeconded @@ -211,7 +300,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( }, sender) } - return false, nil + return propagate, nil } func getCollatorHandshake() (network.Handshake, error) { diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 513739fbf6..61977f6ea3 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -9,11 +9,17 @@ import ( "testing" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" + gomock "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p/core/peer" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" + "github.com/ChainSafe/gossamer/dot/network" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" ) //go:embed testdata/collation_protocol.yaml @@ -120,16 +126,16 @@ func TestCollationProtocol(t *testing.T) { t.Run("marshal", func(t *testing.T) { t.Parallel() - vdt_parent := NewCollationProtocol() - vdt_child := NewCollatorProtocolMessage() + vdtParent := NewCollationProtocol() + vdtChild := NewCollatorProtocolMessage() - err := vdt_child.Set(c.enumValue) + err := vdtChild.Set(c.enumValue) require.NoError(t, err) - err = vdt_parent.Set(vdt_child) + err = vdtParent.Set(vdtChild) require.NoError(t, err) - bytes, err := scale.Marshal(vdt_parent) + bytes, err := scale.Marshal(vdtParent) require.NoError(t, err) require.Equal(t, c.encodingValue, bytes) @@ -138,18 +144,18 @@ func TestCollationProtocol(t *testing.T) { t.Run("unmarshal", func(t *testing.T) { t.Parallel() - vdt_parent := NewCollationProtocol() - err := scale.Unmarshal(c.encodingValue, &vdt_parent) + vdtParent := NewCollationProtocol() + err := scale.Unmarshal(c.encodingValue, &vdtParent) require.NoError(t, err) - vdt_child_temp, err := vdt_parent.Value() + vdtChildTemp, err := vdtParent.Value() require.NoError(t, err) - require.Equal(t, uint(0), vdt_child_temp.Index()) + require.Equal(t, uint(0), vdtChildTemp.Index()) - vdt_child := vdt_child_temp.(CollatorProtocolMessage) + vdtChild := vdtChildTemp.(CollatorProtocolMessage) require.NoError(t, err) - actualData, err := vdt_child.Value() + actualData, err := vdtChild.Value() require.NoError(t, err) require.Equal(t, c.enumValue.Index(), actualData.Index()) @@ -172,3 +178,226 @@ func TestDecodeCollationHandshake(t *testing.T) { require.NoError(t, err) require.Equal(t, testHandshake, msg) } + +func TestHandleCollationMessageCommon(t *testing.T) { + cpvs := CollatorProtocolValidatorSide{} + + peerID := peer.ID("testPeerID") + + // fail with wrong message type + msg1 := &network.BlockAnnounceMessage{} + propagate, err := cpvs.handleCollationMessage(peerID, msg1) + require.False(t, propagate) + require.ErrorIs(t, err, ErrUnexpectedMessageOnCollationProtocol) + + // fail if we can't cast the message to type `*CollationProtocol` + msg2 := NewCollationProtocol() + propagate, err = cpvs.handleCollationMessage(peerID, msg2) + require.False(t, propagate) + require.ErrorContains(t, err, "failed to cast into collator protocol message, "+ + "expected: *CollationProtocol, got: collatorprotocol.CollationProtocol") + + // fail if no value set in the collator protocol message + msg3 := NewCollationProtocol() + propagate, err = cpvs.handleCollationMessage(peerID, &msg3) + require.False(t, propagate) + require.ErrorContains(t, err, "getting collator protocol value: varying data type not set") +} + +func TestHandleCollationMessageDeclare(t *testing.T) { + t.Parallel() + + peerID := peer.ID("testPeerID") + + collatorKeypair, err := sr25519.GenerateKeypair() + require.NoError(t, err) + collatorID, err := sr25519.NewPublicKey(collatorKeypair.Public().Encode()) + require.NoError(t, err) + + payload := getDeclareSignaturePayload(peerID) + signatureBytes, err := collatorKeypair.Sign(payload) + require.NoError(t, err) + collatorSignature := [sr25519.SignatureLength]byte{} + copy(collatorSignature[:], signatureBytes) + + var invalidCollatorSignature parachaintypes.CollatorSignature + tempSignature := common.MustHexToBytes(testDataCollationProtocol["collatorSignature"]) + copy(invalidCollatorSignature[:], tempSignature) + + testCases := []struct { + description string + declareMsg Declare + peerData map[peer.ID]PeerData + currentAssignments map[parachaintypes.ParaID]uint + net Network + success bool + errString string + }{ + { + description: "fail with unknown peer and report the sender if sender is not stored in our peerdata", + declareMsg: Declare{}, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + errString: ErrUnknownPeer.Error(), + }, + { + description: "report the sender if the collatorId in the Declare message belongs to " + + "any peer stored in our peer data", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: collatorID.AsBytes(), + }, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + }, + { + description: "fail if collator signature could not be verified", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: parachaintypes.CollatorSignature{}, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + errString: "verifying signature", + }, + { + description: "fail if collator signature is invalid and report the sender", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: invalidCollatorSignature, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.InvalidSignatureValue, + Reason: peerset.InvalidSignatureReason, + }, peerID) + return net + }(), + errString: crypto.ErrSignatureVerificationFailed.Error(), + }, + { + // TODO: test that we disconnect sender in this case, after we add that functionality + description: "fail if paraID in Declare message is not assigned to our peer and report the sender", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + currentAssignments: make(map[parachaintypes.ParaID]uint), + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnneededCollatorValue, + Reason: peerset.UnneededCollatorReason, + }, peerID) + return net + }(), + }, + { + description: "success case: check if PeerState of the sender has changed to Collating from Connected", + declareMsg: Declare{ + CollatorId: collatorID.AsBytes(), + ParaID: uint32(5), + CollatorSignature: collatorSignature, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + currentAssignments: map[parachaintypes.ParaID]uint{ + parachaintypes.ParaID(5): 1, + }, + success: true, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + peerData: c.peerData, + currentAssignments: c.currentAssignments, + } + msg := NewCollationProtocol() + vdtChild := NewCollatorProtocolMessage() + + err = vdtChild.Set(c.declareMsg) + require.NoError(t, err) + + err = msg.Set(vdtChild) + require.NoError(t, err) + + propagate, err := cpvs.handleCollationMessage(peerID, &msg) + require.False(t, propagate) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + if c.success { + peerData, ok := cpvs.peerData[peerID] + require.True(t, ok) + require.Equal(t, Collating, peerData.state.PeerState) + require.Equal(t, c.declareMsg.CollatorId, peerData.state.CollatingPeerState.CollatorID) + } + }) + } +} diff --git a/dot/parachain/collator-protocol/mocks_generate_test.go b/dot/parachain/collator-protocol/mocks_generate_test.go new file mode 100644 index 0000000000..88c5538252 --- /dev/null +++ b/dot/parachain/collator-protocol/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Network diff --git a/dot/parachain/collator-protocol/mocks_test.go b/dot/parachain/collator-protocol/mocks_test.go new file mode 100644 index 0000000000..3452cabb6e --- /dev/null +++ b/dot/parachain/collator-protocol/mocks_test.go @@ -0,0 +1,105 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/collator-protocol (interfaces: Network) + +// Package collatorprotocol is a generated GoMock package. +package collatorprotocol + +import ( + reflect "reflect" + time "time" + + network "github.com/ChainSafe/gossamer/dot/network" + peerset "github.com/ChainSafe/gossamer/dot/peerset" + gomock "github.com/golang/mock/gomock" + peer "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" +) + +// MockNetwork is a mock of Network interface. +type MockNetwork struct { + ctrl *gomock.Controller + recorder *MockNetworkMockRecorder +} + +// MockNetworkMockRecorder is the mock recorder for MockNetwork. +type MockNetworkMockRecorder struct { + mock *MockNetwork +} + +// NewMockNetwork creates a new mock instance. +func NewMockNetwork(ctrl *gomock.Controller) *MockNetwork { + mock := &MockNetwork{ctrl: ctrl} + mock.recorder = &MockNetworkMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNetwork) EXPECT() *MockNetworkMockRecorder { + return m.recorder +} + +// GetRequestResponseProtocol mocks base method. +func (m *MockNetwork) GetRequestResponseProtocol(arg0 string, arg1 time.Duration, arg2 uint64) *network.RequestResponseProtocol { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetRequestResponseProtocol", arg0, arg1, arg2) + ret0, _ := ret[0].(*network.RequestResponseProtocol) + return ret0 +} + +// GetRequestResponseProtocol indicates an expected call of GetRequestResponseProtocol. +func (mr *MockNetworkMockRecorder) GetRequestResponseProtocol(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestResponseProtocol", reflect.TypeOf((*MockNetwork)(nil).GetRequestResponseProtocol), arg0, arg1, arg2) +} + +// GossipMessage mocks base method. +func (m *MockNetwork) GossipMessage(arg0 network.NotificationsMessage) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "GossipMessage", arg0) +} + +// GossipMessage indicates an expected call of GossipMessage. +func (mr *MockNetworkMockRecorder) GossipMessage(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GossipMessage", reflect.TypeOf((*MockNetwork)(nil).GossipMessage), arg0) +} + +// RegisterNotificationsProtocol mocks base method. +func (m *MockNetwork) RegisterNotificationsProtocol(arg0 protocol.ID, arg1 network.MessageType, arg2 func() (network.Handshake, error), arg3 func([]byte) (network.Handshake, error), arg4 func(peer.ID, network.Handshake) error, arg5 func([]byte) (network.NotificationsMessage, error), arg6 func(peer.ID, network.NotificationsMessage) (bool, error), arg7 func(peer.ID, network.NotificationsMessage), arg8 uint64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegisterNotificationsProtocol", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) + ret0, _ := ret[0].(error) + return ret0 +} + +// RegisterNotificationsProtocol indicates an expected call of RegisterNotificationsProtocol. +func (mr *MockNetworkMockRecorder) RegisterNotificationsProtocol(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterNotificationsProtocol", reflect.TypeOf((*MockNetwork)(nil).RegisterNotificationsProtocol), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) +} + +// ReportPeer mocks base method. +func (m *MockNetwork) ReportPeer(arg0 peerset.ReputationChange, arg1 peer.ID) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ReportPeer", arg0, arg1) +} + +// ReportPeer indicates an expected call of ReportPeer. +func (mr *MockNetworkMockRecorder) ReportPeer(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportPeer", reflect.TypeOf((*MockNetwork)(nil).ReportPeer), arg0, arg1) +} + +// SendMessage mocks base method. +func (m *MockNetwork) SendMessage(arg0 peer.ID, arg1 network.NotificationsMessage) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMessage", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMessage indicates an expected call of SendMessage. +func (mr *MockNetworkMockRecorder) SendMessage(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockNetwork)(nil).SendMessage), arg0, arg1) +} diff --git a/dot/parachain/collator-protocol/register.go b/dot/parachain/collator-protocol/register.go index 0dfa8b4ff7..96d54ee25f 100644 --- a/dot/parachain/collator-protocol/register.go +++ b/dot/parachain/collator-protocol/register.go @@ -15,6 +15,7 @@ func Register(net Network, protocolID protocol.ID, overseerChan chan<- any) (*Co string(protocolID), collationFetchingRequestTimeout, collationFetchingMaxResponseSize) cpvs := CollatorProtocolValidatorSide{ + net: net, SubSystemToOverseer: overseerChan, collationFetchingReqResProtocol: collationFetchingReqResProtocol, } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 0e0fd97378..77c1ff1153 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -27,10 +27,12 @@ const ( ) var ( - ErrUnknownOverseerMessage = errors.New("unknown overseer message type") - ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") - ErrCollationNotInView = errors.New("collation is not in our view") - ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") + ErrUnexpectedMessageOnCollationProtocol = errors.New("unexpected message on collation protocol") + ErrUnknownPeer = errors.New("unknown peer") + ErrUnknownOverseerMessage = errors.New("unknown overseer message type") + ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") + ErrCollationNotInView = errors.New("collation is not in our view") + ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -125,7 +127,7 @@ type PeerData struct { state PeerStateInfo } -func (peerData PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { +func (peerData *PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { if peerData.state.PeerState == Connected { return false } @@ -133,7 +135,17 @@ func (peerData PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool return slices.Contains(peerData.view.heads, relayParent) } -func (peerData PeerData) InsertAdvertisement() error { +func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, paraID parachaintypes.ParaID) { + peerData.state = PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: collatorID, + ParaID: paraID, + }, + } +} + +func (peerData *PeerData) InsertAdvertisement() error { // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 return nil } @@ -208,12 +220,16 @@ type CollatorProtocolValidatorSide struct { // Keep track of all pending candidate collations pendingCandidates map[common.Hash]CollationEvent + + // Parachains we're currently assigned to. With async backing enabled + // this includes assignments from the implicit view. + currentAssignments map[parachaintypes.ParaID]uint } func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, ) (peer.ID, bool) { for peerID, peerData := range cpvs.peerData { - if peerData.state.PeerState == Collating && peerData.state.CollatingPeerState.CollatorID == collatorID { + if peerData.state.CollatingPeerState.CollatorID == collatorID { return peerID, true } } From 72a14536acff549ec4aa9cb163397606bf2d18f3 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 18 Oct 2023 17:06:15 +0530 Subject: [PATCH 47/85] more changes, some improvements in overseer --- dot/parachain/backing/candidate_backing.go | 4 ++ dot/parachain/collator-protocol/message.go | 53 ++++++++++++++--- .../collator-protocol/validator_side.go | 59 ++++++++++++++++--- dot/parachain/overseer/overseer.go | 13 ++++ dot/parachain/overseer/overseer_test.go | 5 ++ dot/parachain/overseer/types.go | 2 + 6 files changed, 121 insertions(+), 15 deletions(-) diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 839bf8a36a..8275e3c38c 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -76,6 +76,10 @@ func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan an return nil } +func (cb *CandidateBacking) String() parachaintypes.SubSystemName { + return parachaintypes.CandidateBacking +} + func (cb *CandidateBacking) processMessages() { for msg := range cb.OverseerToSubSystem { // process these received messages by referencing diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 5e7c054dbc..b1026b2908 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain/backing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" @@ -188,12 +189,35 @@ type BlockedAdvertisement struct { candidateHash parachaintypes.CandidateHash } -func canSecond() bool { - // TODO - // https://github.com/paritytech/polkadot-sdk/blob/6079b6dd3aaba56ef257111fda74a57a800f16d0/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L955 - return false +func (cpvs CollatorProtocolValidatorSide) canSecond( + candidateParaID parachaintypes.ParaID, + candidateRelayParent common.Hash, + candidateHash parachaintypes.CandidateHash, + parentHeadDataHash common.Hash, +) bool { + canSecondRequest := backing.CanSecond{ + CandidateParaID: candidateParaID, + CandidateRelayParent: candidateRelayParent, + CandidateHash: candidateHash, + ParentHeadDataHash: parentHeadDataHash, + } + + responseChan := make(chan bool) + + cpvs.SubSystemToOverseer <- struct { + responseChan chan bool + canSecondRequest backing.CanSecond + }{ + responseChan: responseChan, + canSecondRequest: canSecondRequest, + } + + // TODO: Add timeout + return <-responseChan } +// Enqueue collation for fetching. The advertisement is expected to be +// validated. func (cpvs CollatorProtocolValidatorSide) enqueueCollation(collations Collations) { switch collations.status { case Fetching, WaitingOnValidation: @@ -202,7 +226,7 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation(collations Collations } -func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, +func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, prospectiveCandidate *ProspectiveCandidate) error { // TODO: // - tracks advertisements received and the source (peer id) of the advertisement @@ -247,7 +271,13 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common return ErrProtocolMismatch } - isAdvertisementInvalid, err := peerData.InsertAdvertisement() + isAdvertisementInvalid, err := peerData.InsertAdvertisement( + relayParent, + perRelayParent.prospectiveParachainMode, + &prospectiveCandidate.CandidateHash, + cpvs.implicitView, + cpvs.activeLeaves, + ) if isAdvertisementInvalid { cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, @@ -263,18 +293,25 @@ func (cpvs CollatorProtocolValidatorSide) handleAdvertisement(relayParent common return ErrSecondedLimitReached } - isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || canSecond() + isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( + collatorParaID, + relayParent, + prospectiveCandidate.CandidateHash, + prospectiveCandidate.ParentHeadDataHash, + ) if !isSecondingAllowed { logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", relayParent, collatorParaID, prospectiveCandidate.CandidateHash) - cpvs.BlockedAdvertisements = append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ peerID: sender, collatorID: peerData.state.CollatingPeerState.CollatorID, candidateRelayParent: relayParent, candidateHash: prospectiveCandidate.CandidateHash, }) + + cpvs.BlockedAdvertisements = blockedAdvertisements return nil } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index e50d374b1c..1ce82ef8b1 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -41,6 +41,8 @@ var ( ErrInvalidAdvertisement = errors.New("advertisement is invalid") ErrUndeclaredCollator = errors.New("no prior declare message received for this collator ") ErrOutOfView = errors.New("collation relay parent is out of our view") + ErrDuplicateAdvertisement = errors.New("advertisement is already known") + ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -83,6 +85,10 @@ func (cpvs CollatorProtocolValidatorSide) Run( } } +func (cpvs CollatorProtocolValidatorSide) String() parachaintypes.SubSystemName { + return parachaintypes.CollationProtocol +} + // requestCollation requests a collation from the network. // This function will // - check for duplicate requests @@ -175,15 +181,16 @@ func IsRelayParentInImplicitView( return false } +// Note an advertisement by the collator. Returns `true` if the advertisement was imported +// successfully. Fails if the advertisement is duplicate, out of view, or the peer has not +// declared itself a collator. func (peerData *PeerData) InsertAdvertisement( onRelayParent common.Hash, relayParentMode ProspectiveParachainsMode, - candidateHash *common.Hash, + candidateHash *parachaintypes.CandidateHash, implicitView ImplicitView, activeLeaves map[common.Hash]ProspectiveParachainsMode, ) (isAdvertisementInvalid bool, err error) { - // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 - switch peerData.state.PeerState { case Connected: return true, ErrUndeclaredCollator @@ -193,6 +200,27 @@ func (peerData *PeerData) InsertAdvertisement( return true, ErrOutOfView } + if relayParentMode.isEnabled { + // relayParentMode.maxCandidateDepth + candidates, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] + if ok && slices.Contains[[]parachaintypes.CandidateHash](candidates, *candidateHash) { + return true, ErrDuplicateAdvertisement + } + + if len(candidates) > int(relayParentMode.maxCandidateDepth) { + return true, ErrPeerLimitReached + } + candidates = append(candidates, *candidateHash) + peerData.state.CollatingPeerState.advertisements[onRelayParent] = candidates + } else { + _, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] + if ok { + return true, ErrDuplicateAdvertisement + } + peerData.state.CollatingPeerState.advertisements[onRelayParent] = []parachaintypes.CandidateHash{*candidateHash} + } + + peerData.state.CollatingPeerState.lastActive = time.Now() } return false, nil } @@ -205,10 +233,11 @@ type PeerStateInfo struct { } type CollatingPeerState struct { - CollatorID parachaintypes.CollatorID - ParaID parachaintypes.ParaID - advertisements []common.Hash //nolint - lastActive time.Time //nolint + CollatorID parachaintypes.CollatorID + ParaID parachaintypes.ParaID + // collations advertised by peer per relay parent + advertisements map[common.Hash][]parachaintypes.CandidateHash + lastActive time.Time //nolint } type PeerState uint @@ -278,6 +307,22 @@ type CollatorProtocolValidatorSide struct { // TODO: In rust this is a map, let's see if we can get away with a map // blocked_advertisements: HashMap<(ParaId, Hash), Vec>, BlockedAdvertisements []BlockedAdvertisement + + // Leaves that do support asynchronous backing along with + // implicit ancestry. Leaves from the implicit view are present in + // `active_leaves`, the opposite doesn't hold true. + // + // Relay-chain blocks which don't support prospective parachains are + // never included in the fragment trees of active leaves which do. In + // particular, this means that if a given relay parent belongs to implicit + // ancestry of some active leaf, then it does support prospective parachains. + implicitView ImplicitView + + /// All active leaves observed by us, including both that do and do not + /// support prospective parachains. This mapping works as a replacement for + /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition + /// to asynchronous backing is done. + activeLeaves map[common.Hash]ProspectiveParachainsMode } // Prospective parachains mode of a relay parent. Defined by diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 7d7b1cec1a..4714d17b26 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -9,6 +9,8 @@ import ( "sync" "time" + "github.com/ChainSafe/gossamer/dot/parachain/backing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" ) @@ -22,6 +24,7 @@ type Overseer struct { errChan chan error // channel for overseer to send errors to service that started it SubsystemsToOverseer chan any subsystems map[Subsystem]chan any // map[Subsystem]OverseerToSubSystem channel + nameToSubsystem map[parachaintypes.SubSystemName]Subsystem wg sync.WaitGroup } @@ -34,6 +37,7 @@ func NewOverseer() *Overseer { errChan: make(chan error), SubsystemsToOverseer: make(chan any), subsystems: make(map[Subsystem]chan any), + nameToSubsystem: make(map[parachaintypes.SubSystemName]Subsystem), } } @@ -42,6 +46,7 @@ func NewOverseer() *Overseer { func (o *Overseer) RegisterSubsystem(subsystem Subsystem) chan any { OverseerToSubSystem := make(chan any) o.subsystems[subsystem] = OverseerToSubSystem + o.nameToSubsystem[subsystem.String()] = subsystem return OverseerToSubSystem } @@ -71,6 +76,10 @@ func (o *Overseer) processMessages() { select { case msg := <-o.SubsystemsToOverseer: switch msg.(type) { + case backing.CanSecond: + subsystem := o.nameToSubsystem[parachaintypes.CandidateBacking] + overseerToSubsystem := o.subsystems[subsystem] + overseerToSubsystem <- msg default: logger.Error("unknown message type") } @@ -90,6 +99,10 @@ func (o *Overseer) Stop() error { // close the errorChan to unblock any listeners on the errChan close(o.errChan) + for _, sub := range o.subsystems { + close(sub) + } + // wait for subsystems to stop // TODO: determine reasonable timeout duration for production, currently this is just for testing timedOut := waitTimeout(&o.wg, 500*time.Millisecond) diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index 33e3494a0d..e8f1222508 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/stretchr/testify/require" ) @@ -40,6 +41,10 @@ func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, S } } +func (s *TestSubsystem) String() parachaintypes.SubSystemName { + return parachaintypes.SubSystemName(s.name) +} + func TestStart2SubsytemsActivate1(t *testing.T) { overseer := NewOverseer() require.NotNil(t, overseer) diff --git a/dot/parachain/overseer/types.go b/dot/parachain/overseer/types.go index ece7c767bb..d14beb2597 100644 --- a/dot/parachain/overseer/types.go +++ b/dot/parachain/overseer/types.go @@ -6,6 +6,7 @@ package overseer import ( "context" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" ) @@ -26,4 +27,5 @@ type ActiveLeavesUpdate struct { type Subsystem interface { // Run runs the subsystem. Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error + String() parachaintypes.SubSystemName } From 4d4c049a2233f880f420d6d931898b0d4e937392 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 18 Oct 2023 21:00:54 +0530 Subject: [PATCH 48/85] more changes --- dot/parachain/collator-protocol/message.go | 32 ++++++++++++++++++- .../collator-protocol/validator_side.go | 11 ++++--- dot/parachain/types/subsystem_names.go | 8 +++++ 3 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 dot/parachain/types/subsystem_names.go diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index b1026b2908..752ec24205 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -218,14 +218,44 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( // Enqueue collation for fetching. The advertisement is expected to be // validated. -func (cpvs CollatorProtocolValidatorSide) enqueueCollation(collations Collations) { +func (cpvs CollatorProtocolValidatorSide) enqueueCollation( + collations Collations, + relayParent common.Hash, + paraID parachaintypes.ParaID, + peerID peer.ID, + collatorID parachaintypes.CollatorID, + prospectiveCandidate *ProspectiveCandidate) { switch collations.status { + // TODO: In rust code, a lot of thing that are being done in handle_advertisement + // are being repeated here. + // Currently enqueueCollation is being called from handle_advertisement only, so we might not need to + // repeat that here. + // If enqueueCollation gets used somewhere else, we would need to repeat those things here. + case Fetching, WaitingOnValidation: + logger.Debug("added collation to unfetched list") + collations.waitingQueue = append(collations.waitingQueue, UnfetchedCollation{ + CollatorID: collatorID, + PendingCollation: PendingCollation{ + RelayParent: relayParent, + ParaID: paraID, + PeerID: peerID, + ProspectiveCandidate: prospectiveCandidate, + }, + }) + case Waiting: + + case Seconded: } } +func (cpvs *CollatorProtocolValidatorSide) fetchedCollation(pendingCollation PendingCollation, + collatorID parachaintypes.CollatorID) { + +} + func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, prospectiveCandidate *ProspectiveCandidate) error { // TODO: diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 1ce82ef8b1..c37dca91a7 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -130,10 +130,11 @@ type UnfetchedCollation struct { } type PendingCollation struct { - RelayParent common.Hash - ParaID parachaintypes.ParaID - PeerID peer.ID - CommitmentHash *common.Hash + RelayParent common.Hash + ParaID parachaintypes.ParaID + PeerID peer.ID + CommitmentHash *common.Hash + ProspectiveCandidate *ProspectiveCandidate } type PeerData struct { @@ -354,6 +355,8 @@ type Collations struct { status CollationStatus // how many collations have been seconded secondedCount uint + // Collation that were advertised to us, but we did not yet fetch. + waitingQueue []UnfetchedCollation // : VecDeque<(PendingCollation, CollatorId)>, } // IsSecondedLimitReached check the limit of seconded candidates for a given para has been reached. diff --git a/dot/parachain/types/subsystem_names.go b/dot/parachain/types/subsystem_names.go new file mode 100644 index 0000000000..ec99741fd4 --- /dev/null +++ b/dot/parachain/types/subsystem_names.go @@ -0,0 +1,8 @@ +package parachaintypes + +type SubSystemName string + +const ( + CandidateBacking SubSystemName = "CandidateBacking" + CollationProtocol SubSystemName = "CollationProtocol" +) From 8b77ca9b8efb76579ccd763efd262df4fae537a9 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 19 Oct 2023 12:27:53 +0530 Subject: [PATCH 49/85] more addition to collator protocol --- dot/parachain/collator-protocol/message.go | 61 +++++++++++++++---- .../collator-protocol/validator_side.go | 16 ++++- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 752ec24205..f57519da10 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -225,6 +225,14 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( peerID peer.ID, collatorID parachaintypes.CollatorID, prospectiveCandidate *ProspectiveCandidate) { + + pendingCollation := PendingCollation{ + RelayParent: relayParent, + ParaID: paraID, + PeerID: peerID, + ProspectiveCandidate: prospectiveCandidate, + } + switch collations.status { // TODO: In rust code, a lot of thing that are being done in handle_advertisement // are being repeated here. @@ -235,25 +243,49 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( case Fetching, WaitingOnValidation: logger.Debug("added collation to unfetched list") collations.waitingQueue = append(collations.waitingQueue, UnfetchedCollation{ - CollatorID: collatorID, - PendingCollation: PendingCollation{ - RelayParent: relayParent, - ParaID: paraID, - PeerID: peerID, - ProspectiveCandidate: prospectiveCandidate, - }, + CollatorID: collatorID, + PendingCollation: pendingCollation, }) case Waiting: - + // limit is not reached, it's allowed to second another collation + cpvs.fetchCollation(pendingCollation, collatorID) case Seconded: - + perRelayParent := cpvs.perRelayParent[relayParent] + if perRelayParent.prospectiveParachainMode.isEnabled { + cpvs.fetchCollation(pendingCollation, collatorID) + } else { + logger.Debug("a collation has already been seconded") + } } } -func (cpvs *CollatorProtocolValidatorSide) fetchedCollation(pendingCollation PendingCollation, - collatorID parachaintypes.CollatorID) { +func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation, + collatorID parachaintypes.CollatorID) error { + + candidateHash := pendingCollation.ProspectiveCandidate.CandidateHash + peerData, ok := cpvs.peerData[pendingCollation.PeerID] + if !ok { + return ErrUnknownPeer + } + + if !peerData.HasAdvertised(pendingCollation.RelayParent, &candidateHash) { + return ErrNotAdvertised + } + + // TODO: Add it to collation_fetch_timeouts if we can't process this in timeout time. + // state + // .collation_fetch_timeouts + // .push(timeout(id.clone(), candidate_hash, relay_parent).boxed()); + collation, err := cpvs.requestCollation(pendingCollation.RelayParent, pendingCollation.ParaID, + pendingCollation.PeerID) + if err != nil { + return fmt.Errorf("requesting collation: %w", err) + } + + cpvs.fetchedCollations = append(cpvs.fetchedCollations, *collation) + return nil } func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, @@ -345,7 +377,12 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo return nil } - cpvs.enqueueCollation(perRelayParent.collations) + cpvs.enqueueCollation(perRelayParent.collations, + relayParent, + collatorParaID, + sender, + peerData.state.CollatingPeerState.CollatorID, + prospectiveCandidate) return nil } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index c37dca91a7..d567372766 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -43,6 +43,7 @@ var ( ErrOutOfView = errors.New("collation relay parent is out of our view") ErrDuplicateAdvertisement = errors.New("advertisement is already known") ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") + ErrNotAdvertised = errors.New("collation was not previously advertised") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -71,7 +72,7 @@ func (cpvs CollatorProtocolValidatorSide) Run( // check if this peer id has advertised this relay parent peerData := cpvs.peerData[unfetchedCollation.PendingCollation.PeerID] - if peerData.HasAdvertisedRelayParent(unfetchedCollation.PendingCollation.RelayParent) { + if peerData.HasAdvertised(unfetchedCollation.PendingCollation.RelayParent, nil) { // if so request collation from this peer id collation, err := cpvs.requestCollation(unfetchedCollation.PendingCollation.RelayParent, unfetchedCollation.PendingCollation.ParaID, unfetchedCollation.PendingCollation.PeerID) @@ -95,6 +96,8 @@ func (cpvs CollatorProtocolValidatorSide) String() parachaintypes.SubSystemName // - check if the requested collation is in our view func (cpvs CollatorProtocolValidatorSide) requestCollation(relayParent common.Hash, paraID parachaintypes.ParaID, peerID peer.ID) (*parachaintypes.Collation, error) { + + // TODO: Make sure that the request can be done in MAX_UNSHARED_DOWNLOAD_TIME timeout if !slices.Contains[[]common.Hash](cpvs.ourView.heads, relayParent) { return nil, ErrCollationNotInView } @@ -142,12 +145,19 @@ type PeerData struct { state PeerStateInfo } -func (peerData *PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { +func (peerData *PeerData) HasAdvertised( + relayParent common.Hash, + mayBeCandidateHash *parachaintypes.CandidateHash) bool { if peerData.state.PeerState == Connected { return false } - return slices.Contains(peerData.view.heads, relayParent) + candidates, ok := peerData.state.CollatingPeerState.advertisements[relayParent] + if mayBeCandidateHash == nil { + return ok + } + + return slices.Contains(candidates, *mayBeCandidateHash) } func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, paraID parachaintypes.ParaID) { From d1c5de88d50d87fe35d78f02c167e5cfceac8991 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 19 Oct 2023 20:27:27 +0530 Subject: [PATCH 50/85] added some tests for advertise collation --- .../collator-protocol/message_test.go | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 61977f6ea3..c176062271 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -401,3 +401,103 @@ func TestHandleCollationMessageDeclare(t *testing.T) { }) } } + +func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { + t.Parallel() + + peerID := peer.ID("testPeerID") + + testCases := []struct { + description string + advertiseCollation AdvertiseCollation + // peerData map[peer.ID]PeerData + // currentAssignments map[parachaintypes.ParaID]uint + net Network + // success bool + errString string + }{ + { + description: "fail with relay parent is unknown if we don't have the relay parent tracked and report the peer", + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + errString: ErrRelayParentUnknown.Error(), + }, + { + description: "fail with unknown peer if peer is not tracked in our list of active collators", + errString: ErrUnknownPeer.Error(), + }, + { + description: "fail with undeclared para if peer has not declared its para id and report the peer", + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + errString: ErrUndeclaredPara.Error(), + }, + { + description: "fail with invalid assignment if para id is not currently assigned to us for this relay parent and report the peer", + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.WrongParaValue, + Reason: peerset.WrongParaReason, + }, peerID) + return net + }(), + errString: ErrInvalidAssignment.Error(), + }, + { + // NOTE: prospective parachain mode and prospective candidates were added in V2, + // In V1, prospective parachain mode is disabled by and prospective candidates is nil + // In V2, prospective parachain mode is enabled by and prospective candidates is not nil + description: "fail with protocol mismatch is prospective parachain mode in enable but with got a nil value for prospective candidate", + errString: ErrProtocolMismatch.Error(), + }, + { + description: "fail if para reached a limit of seconded candidates for this relay parent", + errString: ErrSecondedLimitReached.Error(), + }, + {}, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + // peerData: c.peerData, + // currentAssignments: c.currentAssignments, + } + msg := NewCollationProtocol() + vdtChild := NewCollatorProtocolMessage() + + err := vdtChild.Set(c.advertiseCollation) + require.NoError(t, err) + + err = msg.Set(vdtChild) + require.NoError(t, err) + + propagate, err := cpvs.handleCollationMessage(peerID, &msg) + require.False(t, propagate) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + }) + } +} From 4fb70f3ebee6721fa00745edd7ef0dc912bd8cf9 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Fri, 20 Oct 2023 22:49:40 +0530 Subject: [PATCH 51/85] feat(parachain/overseer): added message forwarding in overseer (#3546) - Made changes to overseer to support processing messages coming from subsystems - Added logic to process those messages --- .../availability-store/availabilitystore.go | 5 +++ dot/parachain/backing/candidate_backing.go | 4 +++ .../collator-protocol/validator_side.go | 8 +++-- dot/parachain/overseer/overseer.go | 35 +++++++++++++++++++ dot/parachain/overseer/overseer_test.go | 32 +++++++++++++---- dot/parachain/overseer/types.go | 2 ++ dot/parachain/types/subsystem_names.go | 9 +++++ 7 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 dot/parachain/types/subsystem_names.go diff --git a/dot/parachain/availability-store/availabilitystore.go b/dot/parachain/availability-store/availabilitystore.go index 91cfc1a7d9..baa228638f 100644 --- a/dot/parachain/availability-store/availabilitystore.go +++ b/dot/parachain/availability-store/availabilitystore.go @@ -6,6 +6,7 @@ package availability_store import ( "context" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" ) @@ -27,6 +28,10 @@ func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsyst return nil } +func (*AvailabilityStoreSubsystem) Name() parachaintypes.SubSystemName { + return parachaintypes.AvailabilityStore +} + func (av *AvailabilityStoreSubsystem) processMessages() { for msg := range av.OverseerToSubSystem { logger.Debugf("received message %v", msg) diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 839bf8a36a..f18dcb67f1 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -76,6 +76,10 @@ func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan an return nil } +func (*CandidateBacking) Name() parachaintypes.SubSystemName { + return parachaintypes.CandidateBacking +} + func (cb *CandidateBacking) processMessages() { for msg := range cb.OverseerToSubSystem { // process these received messages by referencing diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 77c1ff1153..1e5caba257 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -75,6 +75,10 @@ func (cpvs CollatorProtocolValidatorSide) Run( } } +func (CollatorProtocolValidatorSide) Name() parachaintypes.SubSystemName { + return parachaintypes.CollationProtocol +} + // requestCollation requests a collation from the network. // This function will // - check for duplicate requests @@ -261,7 +265,7 @@ type Backed struct { ParaHead common.Hash } -type InvalidOverseeMsg struct { +type InvalidOverseerMsg struct { Parent common.Hash CandidateReceipt parachaintypes.CandidateReceipt } @@ -292,7 +296,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error case Backed: // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 - case InvalidOverseeMsg: + case InvalidOverseerMsg: invalidOverseerMsg := msg collationEvent, ok := cpvs.pendingCandidates[invalidOverseerMsg.Parent] diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 7d7b1cec1a..dd112c6868 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -9,6 +9,11 @@ import ( "sync" "time" + availability_store "github.com/ChainSafe/gossamer/dot/parachain/availability-store" + "github.com/ChainSafe/gossamer/dot/parachain/backing" + collatorprotocol "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" ) @@ -22,6 +27,7 @@ type Overseer struct { errChan chan error // channel for overseer to send errors to service that started it SubsystemsToOverseer chan any subsystems map[Subsystem]chan any // map[Subsystem]OverseerToSubSystem channel + nameToSubsystem map[parachaintypes.SubSystemName]Subsystem wg sync.WaitGroup } @@ -34,6 +40,7 @@ func NewOverseer() *Overseer { errChan: make(chan error), SubsystemsToOverseer: make(chan any), subsystems: make(map[Subsystem]chan any), + nameToSubsystem: make(map[parachaintypes.SubSystemName]Subsystem), } } @@ -42,6 +49,7 @@ func NewOverseer() *Overseer { func (o *Overseer) RegisterSubsystem(subsystem Subsystem) chan any { OverseerToSubSystem := make(chan any) o.subsystems[subsystem] = OverseerToSubSystem + o.nameToSubsystem[subsystem.Name()] = subsystem return OverseerToSubSystem } @@ -60,6 +68,7 @@ func (o *Overseer) Start() error { }(subsystem, overseerToSubSystem) } + o.wg.Add(1) go o.processMessages() // TODO: add logic to start listening for Block Imported events and Finalisation events @@ -70,10 +79,32 @@ func (o *Overseer) processMessages() { for { select { case msg := <-o.SubsystemsToOverseer: + var subsystem Subsystem + switch msg.(type) { + case backing.GetBackedCandidates, backing.CanSecond, backing.Second, backing.Statement: + subsystem = o.nameToSubsystem[parachaintypes.CandidateBacking] + + case collatorprotocol.CollateOn, collatorprotocol.DistributeCollation, collatorprotocol.ReportCollator, + collatorprotocol.Backed, collatorprotocol.AdvertiseCollation, collatorprotocol.InvalidOverseerMsg, + collatorprotocol.SecondedOverseerMsg: + + subsystem = o.nameToSubsystem[parachaintypes.CollationProtocol] + + case availability_store.QueryAvailableData, availability_store.QueryDataAvailability, + availability_store.QueryChunk, availability_store.QueryChunkSize, availability_store.QueryAllChunks, + availability_store.QueryChunkAvailability, availability_store.StoreChunk, + availability_store.StoreAvailableData: + + subsystem = o.nameToSubsystem[parachaintypes.AvailabilityStore] + default: logger.Error("unknown message type") } + + overseerToSubsystem := o.subsystems[subsystem] + overseerToSubsystem <- msg + case <-o.ctx.Done(): if err := o.ctx.Err(); err != nil { logger.Errorf("ctx error: %v\n", err) @@ -90,6 +121,10 @@ func (o *Overseer) Stop() error { // close the errorChan to unblock any listeners on the errChan close(o.errChan) + for _, sub := range o.subsystems { + close(sub) + } + // wait for subsystems to stop // TODO: determine reasonable timeout duration for production, currently this is just for testing timedOut := waitTimeout(&o.wg, 500*time.Millisecond) diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index 33e3494a0d..d3c03e5bbf 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/stretchr/testify/require" ) @@ -17,6 +18,10 @@ type TestSubsystem struct { name string } +func (s *TestSubsystem) Name() parachaintypes.SubSystemName { + return parachaintypes.SubSystemName(s.name) +} + func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error { fmt.Printf("%s run\n", s.name) counter := 0 @@ -47,8 +52,13 @@ func TestStart2SubsytemsActivate1(t *testing.T) { subSystem1 := &TestSubsystem{name: "subSystem1"} subSystem2 := &TestSubsystem{name: "subSystem2"} - overseer.RegisterSubsystem(subSystem1) - overseer.RegisterSubsystem(subSystem2) + overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) + overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) + + go func() { + <-overseerToSubSystem1 + <-overseerToSubSystem2 + }() err := overseer.Start() require.NoError(t, err) @@ -86,8 +96,13 @@ func TestStart2SubsytemsActivate2Different(t *testing.T) { subSystem1 := &TestSubsystem{name: "subSystem1"} subSystem2 := &TestSubsystem{name: "subSystem2"} - overseer.RegisterSubsystem(subSystem1) - overseer.RegisterSubsystem(subSystem2) + overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) + overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) + + go func() { + <-overseerToSubSystem1 + <-overseerToSubSystem2 + }() err := overseer.Start() require.NoError(t, err) @@ -128,8 +143,13 @@ func TestStart2SubsytemsActivate2Same(t *testing.T) { subSystem1 := &TestSubsystem{name: "subSystem1"} subSystem2 := &TestSubsystem{name: "subSystem2"} - overseer.RegisterSubsystem(subSystem1) - overseer.RegisterSubsystem(subSystem2) + overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) + overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) + + go func() { + <-overseerToSubSystem1 + <-overseerToSubSystem2 + }() err := overseer.Start() require.NoError(t, err) diff --git a/dot/parachain/overseer/types.go b/dot/parachain/overseer/types.go index ece7c767bb..ccc3fdb0fb 100644 --- a/dot/parachain/overseer/types.go +++ b/dot/parachain/overseer/types.go @@ -6,6 +6,7 @@ package overseer import ( "context" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" ) @@ -26,4 +27,5 @@ type ActiveLeavesUpdate struct { type Subsystem interface { // Run runs the subsystem. Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error + Name() parachaintypes.SubSystemName } diff --git a/dot/parachain/types/subsystem_names.go b/dot/parachain/types/subsystem_names.go new file mode 100644 index 0000000000..e9d1414f49 --- /dev/null +++ b/dot/parachain/types/subsystem_names.go @@ -0,0 +1,9 @@ +package parachaintypes + +type SubSystemName string + +const ( + CandidateBacking SubSystemName = "CandidateBacking" + CollationProtocol SubSystemName = "CollationProtocol" + AvailabilityStore SubSystemName = "AvailabilityStore" +) From db47168c233f746742dcaf8aad51ddb8afe91be7 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 23 Oct 2023 20:24:40 +0530 Subject: [PATCH 52/85] wrote some tests --- dot/parachain/collator-protocol/message.go | 67 ++++--- .../collator-protocol/message_test.go | 188 ++++++++++++++++-- .../collator-protocol/validator_side.go | 12 +- 3 files changed, 218 insertions(+), 49 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index f57519da10..825d9300e2 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -226,6 +226,7 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( collatorID parachaintypes.CollatorID, prospectiveCandidate *ProspectiveCandidate) { + // TODO: return errors pendingCollation := PendingCollation{ RelayParent: relayParent, ParaID: paraID, @@ -263,13 +264,16 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation, collatorID parachaintypes.CollatorID) error { - candidateHash := pendingCollation.ProspectiveCandidate.CandidateHash + var candidateHash *parachaintypes.CandidateHash + if pendingCollation.ProspectiveCandidate != nil { + candidateHash = &pendingCollation.ProspectiveCandidate.CandidateHash + } peerData, ok := cpvs.peerData[pendingCollation.PeerID] if !ok { return ErrUnknownPeer } - if !peerData.HasAdvertised(pendingCollation.RelayParent, &candidateHash) { + if !peerData.HasAdvertised(pendingCollation.RelayParent, candidateHash) { return ErrNotAdvertised } @@ -333,21 +337,25 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo return ErrProtocolMismatch } - isAdvertisementInvalid, err := peerData.InsertAdvertisement( + var prospectiveCandidateHash *parachaintypes.CandidateHash + if prospectiveCandidate != nil { + prospectiveCandidateHash = &prospectiveCandidate.CandidateHash + } + + isAdvertisementValid, err := peerData.InsertAdvertisement( relayParent, perRelayParent.prospectiveParachainMode, - &prospectiveCandidate.CandidateHash, + prospectiveCandidateHash, cpvs.implicitView, cpvs.activeLeaves, ) - if isAdvertisementInvalid { + if !isAdvertisementValid { cpvs.net.ReportPeer(peerset.ReputationChange{ Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, }, sender) logger.Errorf(ErrInvalidAdvertisement.Error()) - } - if err != nil { + } else if err != nil { return fmt.Errorf("inserting advertisement: %w", err) } @@ -355,27 +363,28 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo return ErrSecondedLimitReached } - isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( - collatorParaID, - relayParent, - prospectiveCandidate.CandidateHash, - prospectiveCandidate.ParentHeadDataHash, - ) - - if !isSecondingAllowed { - logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", - relayParent, collatorParaID, prospectiveCandidate.CandidateHash) - - blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ - peerID: sender, - collatorID: peerData.state.CollatingPeerState.CollatorID, - candidateRelayParent: relayParent, - candidateHash: prospectiveCandidate.CandidateHash, - }) - - cpvs.BlockedAdvertisements = blockedAdvertisements - return nil - } + // NOTE: Matters only in V2 + // isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( + // collatorParaID, + // relayParent, + // prospectiveCandidate.CandidateHash, + // prospectiveCandidate.ParentHeadDataHash, + // ) + + // if !isSecondingAllowed { + // logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", + // relayParent, collatorParaID, prospectiveCandidate.CandidateHash) + + // blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + // peerID: sender, + // collatorID: peerData.state.CollatingPeerState.CollatorID, + // candidateRelayParent: relayParent, + // candidateHash: prospectiveCandidate.CandidateHash, + // }) + + // cpvs.BlockedAdvertisements = blockedAdvertisements + // return nil + // } cpvs.enqueueCollation(perRelayParent.collations, relayParent, @@ -429,7 +438,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( return propagate, fmt.Errorf("getting collator protocol message value: %w", err) } - switch collatorProtocolMessage.Index() { + switch collatorProtocolMessageV.Index() { // TODO: Create an issue to cover v2 types. #3534 case 0: // Declare declareMessage, ok := collatorProtocolMessageV.(Declare) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index c176062271..e3bb1554ca 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -406,18 +406,23 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { t.Parallel() peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + testParaID := parachaintypes.ParaID(5) testCases := []struct { description string advertiseCollation AdvertiseCollation - // peerData map[peer.ID]PeerData + peerData map[peer.ID]PeerData // currentAssignments map[parachaintypes.ParaID]uint - net Network + perRelayParent map[common.Hash]PerRelayParent + net Network + activeLeaves map[common.Hash]ProspectiveParachainsMode // success bool errString string }{ { - description: "fail with relay parent is unknown if we don't have the relay parent tracked and report the peer", + description: "fail with relay parent is unknown if we don't have the relay parent tracked and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), net: func() Network { ctrl := gomock.NewController(t) net := NewMockNetwork(ctrl) @@ -425,16 +430,33 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { Value: peerset.UnexpectedMessageValue, Reason: peerset.UnexpectedMessageReason, }, peerID) + return net }(), errString: ErrRelayParentUnknown.Error(), }, { - description: "fail with unknown peer if peer is not tracked in our list of active collators", - errString: ErrUnknownPeer.Error(), + description: "fail with unknown peer if peer is not tracked in our list of active collators", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: {}, + }, + errString: ErrUnknownPeer.Error(), }, { - description: "fail with undeclared para if peer has not declared its para id and report the peer", + description: "fail with undeclared para if peer has not declared its para id and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: {}, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, net: func() Network { ctrl := gomock.NewController(t) net := NewMockNetwork(ctrl) @@ -447,7 +469,24 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { errString: ErrUndeclaredPara.Error(), }, { - description: "fail with invalid assignment if para id is not currently assigned to us for this relay parent and report the peer", + description: "fail with invalid assignment if para id is not currently assigned to us for this relay parent and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, net: func() Network { ctrl := gomock.NewController(t) net := NewMockNetwork(ctrl) @@ -463,22 +502,141 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { // NOTE: prospective parachain mode and prospective candidates were added in V2, // In V1, prospective parachain mode is disabled by and prospective candidates is nil // In V2, prospective parachain mode is enabled by and prospective candidates is not nil - description: "fail with protocol mismatch is prospective parachain mode in enable but with got a nil value for prospective candidate", - errString: ErrProtocolMismatch.Error(), + description: "fail with protocol mismatch is prospective parachain mode in enable but with got a nil value for prospective candidate", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + prospectiveParachainMode: ProspectiveParachainsMode{ + isEnabled: true, + }, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: testParaID, + }, + }, + }, + }, + errString: ErrProtocolMismatch.Error(), }, + // { + // description: "fail with error out of view and report the peer", + // advertiseCollation: AdvertiseCollation(testRelayParent), + // perRelayParent: map[common.Hash]PerRelayParent{ + // testRelayParent: { + // assignment: &testParaID, + // }, + // }, + // peerData: map[peer.ID]PeerData{ + // peerID: { + // view: View{}, + // state: PeerStateInfo{ + // PeerState: Collating, + // CollatingPeerState: CollatingPeerState{ + // ParaID: testParaID, + // }, + // }, + // }, + // }, + // net: func() Network { + // ctrl := gomock.NewController(t) + // net := NewMockNetwork(ctrl) + // net.EXPECT().ReportPeer(peerset.ReputationChange{ + // Value: peerset.UnexpectedMessageValue, + // Reason: peerset.UnexpectedMessageReason, + // }, peerID) + // return net + // }(), + // activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + // }, { - description: "fail if para reached a limit of seconded candidates for this relay parent", - errString: ErrSecondedLimitReached.Error(), + description: "fail if para reached a limit of seconded candidates for this relay parent", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + collations: Collations{ + // For Collator Protocol v1, we can only second one candidate + // at a time, so seconded limit would be 1 + secondedCount: 1, + }, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: testParaID, + }, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + // reporting for error out of view + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + errString: ErrSecondedLimitReached.Error(), + }, + { + description: "fail if para reached a limit of seconded candidates for this relay parent", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + collations: Collations{ + secondedCount: 0, + }, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: testParaID, + }, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + // reporting for error out of view + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + errString: "", }, - {}, } for _, c := range testCases { c := c t.Run(c.description, func(t *testing.T) { t.Parallel() cpvs := CollatorProtocolValidatorSide{ - net: c.net, - // peerData: c.peerData, + net: c.net, + perRelayParent: c.perRelayParent, + peerData: c.peerData, + activeLeaves: c.activeLeaves, // currentAssignments: c.currentAssignments, } msg := NewCollationProtocol() @@ -501,3 +659,5 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { }) } } + +// TODO: test InsertAdvertisement seperately diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 7010afd947..942467dfd9 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -204,36 +204,36 @@ func (peerData *PeerData) InsertAdvertisement( ) (isAdvertisementInvalid bool, err error) { switch peerData.state.PeerState { case Connected: - return true, ErrUndeclaredCollator + return false, ErrUndeclaredCollator case Collating: if !IsRelayParentInImplicitView(onRelayParent, relayParentMode, implicitView, activeLeaves, peerData.state.CollatingPeerState.ParaID) { - return true, ErrOutOfView + return false, ErrOutOfView } if relayParentMode.isEnabled { // relayParentMode.maxCandidateDepth candidates, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] if ok && slices.Contains[[]parachaintypes.CandidateHash](candidates, *candidateHash) { - return true, ErrDuplicateAdvertisement + return false, ErrDuplicateAdvertisement } if len(candidates) > int(relayParentMode.maxCandidateDepth) { - return true, ErrPeerLimitReached + return false, ErrPeerLimitReached } candidates = append(candidates, *candidateHash) peerData.state.CollatingPeerState.advertisements[onRelayParent] = candidates } else { _, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] if ok { - return true, ErrDuplicateAdvertisement + return false, ErrDuplicateAdvertisement } peerData.state.CollatingPeerState.advertisements[onRelayParent] = []parachaintypes.CandidateHash{*candidateHash} } peerData.state.CollatingPeerState.lastActive = time.Now() } - return false, nil + return true, nil } type PeerStateInfo struct { From 3d0e7828287f3e56b07ad97500d53262d81db404 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 25 Oct 2023 11:40:06 +0530 Subject: [PATCH 53/85] TestInsertAdvertisement --- dot/parachain/collator-protocol/message.go | 57 ++++----- .../collator-protocol/message_test.go | 115 ++++++++++++++++++ .../collator-protocol/validator_side.go | 2 +- 3 files changed, 145 insertions(+), 29 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 825d9300e2..2bd8ea0bc8 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -224,7 +224,7 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( paraID parachaintypes.ParaID, peerID peer.ID, collatorID parachaintypes.CollatorID, - prospectiveCandidate *ProspectiveCandidate) { + prospectiveCandidate *ProspectiveCandidate) error { // TODO: return errors pendingCollation := PendingCollation{ @@ -249,16 +249,17 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( }) case Waiting: // limit is not reached, it's allowed to second another collation - cpvs.fetchCollation(pendingCollation, collatorID) + return cpvs.fetchCollation(pendingCollation, collatorID) case Seconded: perRelayParent := cpvs.perRelayParent[relayParent] if perRelayParent.prospectiveParachainMode.isEnabled { - cpvs.fetchCollation(pendingCollation, collatorID) + return cpvs.fetchCollation(pendingCollation, collatorID) } else { logger.Debug("a collation has already been seconded") } } + return nil } func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation, @@ -363,37 +364,37 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo return ErrSecondedLimitReached } - // NOTE: Matters only in V2 - // isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( - // collatorParaID, - // relayParent, - // prospectiveCandidate.CandidateHash, - // prospectiveCandidate.ParentHeadDataHash, - // ) - - // if !isSecondingAllowed { - // logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", - // relayParent, collatorParaID, prospectiveCandidate.CandidateHash) - - // blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ - // peerID: sender, - // collatorID: peerData.state.CollatingPeerState.CollatorID, - // candidateRelayParent: relayParent, - // candidateHash: prospectiveCandidate.CandidateHash, - // }) - - // cpvs.BlockedAdvertisements = blockedAdvertisements - // return nil - // } - - cpvs.enqueueCollation(perRelayParent.collations, + /*NOTE:---------------------------------------Matters only in V2----------------------------------------------*/ + isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( + collatorParaID, + relayParent, + prospectiveCandidate.CandidateHash, + prospectiveCandidate.ParentHeadDataHash, + ) + + if !isSecondingAllowed { + logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", + relayParent, collatorParaID, prospectiveCandidate.CandidateHash) + + blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + peerID: sender, + collatorID: peerData.state.CollatingPeerState.CollatorID, + candidateRelayParent: relayParent, + candidateHash: prospectiveCandidate.CandidateHash, + }) + + cpvs.BlockedAdvertisements = blockedAdvertisements + return nil + } + /*--------------------------------------------END----------------------------------------------------------*/ + + return cpvs.enqueueCollation(perRelayParent.collations, relayParent, collatorParaID, sender, peerData.state.CollatingPeerState.CollatorID, prospectiveCandidate) - return nil } // getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index e3bb1554ca..ddac56d6fa 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -661,3 +661,118 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { } // TODO: test InsertAdvertisement seperately + +func TestInsertAdvertisement(t *testing.T) { + relayParent := getDummyHash(5) + + candidateHash := parachaintypes.CandidateHash{ + Value: getDummyHash(9), + } + + testCases := []struct { + description string + peerData PeerData + relayParentMode ProspectiveParachainsMode + candidateHash *parachaintypes.CandidateHash + implicitView ImplicitView + activeLeaves map[common.Hash]ProspectiveParachainsMode + err error + }{ + { + description: "fail with undeclared collator", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + err: ErrUndeclaredCollator, + }, + { + description: "fail with error out of view", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: nil, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + err: ErrOutOfView, + }, + { + description: "fail with error duplicate advertisement", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(5), + advertisements: map[common.Hash][]parachaintypes.CandidateHash{ + relayParent: {}, + }, + }, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: nil, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + relayParent: { + isEnabled: false, + }, + }, + err: ErrDuplicateAdvertisement, + }, + // { + // description: "fail with error peer limit reached", + // }, + { + description: "success case", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(5), + advertisements: map[common.Hash][]parachaintypes.CandidateHash{}, + }, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: &candidateHash, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + relayParent: { + isEnabled: false, + }, + }, + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + _, err := c.peerData.InsertAdvertisement(relayParent, c.relayParentMode, c.candidateHash, c.implicitView, c.activeLeaves) + require.ErrorIs(t, err, c.err) + }, + ) + } + +} + +func TestEnqueueCollation(t *testing.T) { + + testCases := []struct { + description string + }{ + {}, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + }, + ) + } +} diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 942467dfd9..0d03f02ab6 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -39,7 +39,7 @@ var ( ErrUndeclaredPara = errors.New("peer has not declared its para id") ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") ErrInvalidAdvertisement = errors.New("advertisement is invalid") - ErrUndeclaredCollator = errors.New("no prior declare message received for this collator ") + ErrUndeclaredCollator = errors.New("no prior declare message received for this collator") ErrOutOfView = errors.New("collation relay parent is out of our view") ErrDuplicateAdvertisement = errors.New("advertisement is already known") ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") From 64846c882bb26e6ce799c9318b6df4da83c0f228 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 25 Oct 2023 11:57:27 +0530 Subject: [PATCH 54/85] added TestFetchCollation --- .../collator-protocol/message_test.go | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index ddac56d6fa..256d9bcd4a 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -761,18 +761,46 @@ func TestInsertAdvertisement(t *testing.T) { } -func TestEnqueueCollation(t *testing.T) { - +func TestFetchCollation(t *testing.T) { + cpvs := CollatorProtocolValidatorSide{} + collatorID := parachaintypes.CollatorID{} + testPeerID := peer.ID("testPeerID") + pendingCollation := PendingCollation{ + PeerID: testPeerID, + } testCases := []struct { description string + peerData map[peer.ID]PeerData + err error }{ - {}, + { + description: "fail with unknown peer", + peerData: map[peer.ID]PeerData{}, + err: ErrUnknownPeer, + }, + { + description: "fail with collation not previously advertised", + peerData: map[peer.ID]PeerData{ + testPeerID: { + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + advertisements: make(map[common.Hash][]parachaintypes.CandidateHash), + }, + }, + }, + }, + err: ErrNotAdvertised, + }, } for _, c := range testCases { c := c t.Run(c.description, func(t *testing.T) { t.Parallel() - }, - ) + + cpvs.peerData = c.peerData + err := cpvs.fetchCollation(pendingCollation, collatorID) + require.ErrorIs(t, err, c.err) + }) } } From 4ba1cf29de56acfeeb6c2c79757624a8fa7384f5 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 25 Oct 2023 12:31:32 +0530 Subject: [PATCH 55/85] fixing lint --- dot/parachain/collator-protocol/message.go | 12 +- .../collator-protocol/message_test.go | 103 ++++-------------- .../collator-protocol/validator_side.go | 26 ++--- 3 files changed, 37 insertions(+), 104 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 2bd8ea0bc8..22dd6f9e72 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -180,7 +180,7 @@ const ( // BlockedAdvertisement is vstaging advertisement that was rejected by the backing // subsystem. Validator may fetch it later if its fragment -// membership gets recognized before relay parent goes out of view. +// membership gets recognised before relay parent goes out of view. type BlockedAdvertisement struct { // peer that advertised the collation peerID peer.ID @@ -249,11 +249,11 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( }) case Waiting: // limit is not reached, it's allowed to second another collation - return cpvs.fetchCollation(pendingCollation, collatorID) + return cpvs.fetchCollation(pendingCollation) case Seconded: perRelayParent := cpvs.perRelayParent[relayParent] if perRelayParent.prospectiveParachainMode.isEnabled { - return cpvs.fetchCollation(pendingCollation, collatorID) + return cpvs.fetchCollation(pendingCollation) } else { logger.Debug("a collation has already been seconded") } @@ -262,8 +262,7 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( return nil } -func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation, - collatorID parachaintypes.CollatorID) error { +func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation) error { var candidateHash *parachaintypes.CandidateHash if pendingCollation.ProspectiveCandidate != nil { @@ -373,7 +372,8 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo ) if !isSecondingAllowed { - logger.Infof("Seconding is not allowed by backing, queueing advertisement, relay parent: %s, para id: %d, candidate hash: %s", + logger.Infof("Seconding is not allowed by backing, queueing advertisement,"+ + " relay parent: %s, para id: %d, candidate hash: %s", relayParent, collatorParaID, prospectiveCandidate.CandidateHash) blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 256d9bcd4a..82db19de53 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -413,15 +413,14 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { description string advertiseCollation AdvertiseCollation peerData map[peer.ID]PeerData - // currentAssignments map[parachaintypes.ParaID]uint - perRelayParent map[common.Hash]PerRelayParent - net Network - activeLeaves map[common.Hash]ProspectiveParachainsMode - // success bool - errString string + perRelayParent map[common.Hash]PerRelayParent + net Network + activeLeaves map[common.Hash]ProspectiveParachainsMode + errString string }{ { - description: "fail with relay parent is unknown if we don't have the relay parent tracked and report the peer", + description: "fail with relay parent is unknown if we don't have the relay" + + " parent tracked and report the peer", advertiseCollation: AdvertiseCollation(testRelayParent), net: func() Network { ctrl := gomock.NewController(t) @@ -444,7 +443,8 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { errString: ErrUnknownPeer.Error(), }, { - description: "fail with undeclared para if peer has not declared its para id and report the peer", + description: "fail with undeclared para if peer has not declared its para id" + + " and report the peer", advertiseCollation: AdvertiseCollation(testRelayParent), perRelayParent: map[common.Hash]PerRelayParent{ testRelayParent: {}, @@ -469,7 +469,8 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { errString: ErrUndeclaredPara.Error(), }, { - description: "fail with invalid assignment if para id is not currently assigned to us for this relay parent and report the peer", + description: "fail with invalid assignment if para id is not currently" + + " assigned to us for this relay parent and report the peer", advertiseCollation: AdvertiseCollation(testRelayParent), perRelayParent: map[common.Hash]PerRelayParent{ testRelayParent: { @@ -502,7 +503,8 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { // NOTE: prospective parachain mode and prospective candidates were added in V2, // In V1, prospective parachain mode is disabled by and prospective candidates is nil // In V2, prospective parachain mode is enabled by and prospective candidates is not nil - description: "fail with protocol mismatch is prospective parachain mode in enable but with got a nil value for prospective candidate", + description: "fail with protocol mismatch is prospective parachain mode in" + + " enable but with got a nil value for prospective candidate", advertiseCollation: AdvertiseCollation(testRelayParent), perRelayParent: map[common.Hash]PerRelayParent{ testRelayParent: { @@ -525,36 +527,6 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { }, errString: ErrProtocolMismatch.Error(), }, - // { - // description: "fail with error out of view and report the peer", - // advertiseCollation: AdvertiseCollation(testRelayParent), - // perRelayParent: map[common.Hash]PerRelayParent{ - // testRelayParent: { - // assignment: &testParaID, - // }, - // }, - // peerData: map[peer.ID]PeerData{ - // peerID: { - // view: View{}, - // state: PeerStateInfo{ - // PeerState: Collating, - // CollatingPeerState: CollatingPeerState{ - // ParaID: testParaID, - // }, - // }, - // }, - // }, - // net: func() Network { - // ctrl := gomock.NewController(t) - // net := NewMockNetwork(ctrl) - // net.EXPECT().ReportPeer(peerset.ReputationChange{ - // Value: peerset.UnexpectedMessageValue, - // Reason: peerset.UnexpectedMessageReason, - // }, peerID) - // return net - // }(), - // activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, - // }, { description: "fail if para reached a limit of seconded candidates for this relay parent", advertiseCollation: AdvertiseCollation(testRelayParent), @@ -592,41 +564,6 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, errString: ErrSecondedLimitReached.Error(), }, - { - description: "fail if para reached a limit of seconded candidates for this relay parent", - advertiseCollation: AdvertiseCollation(testRelayParent), - perRelayParent: map[common.Hash]PerRelayParent{ - testRelayParent: { - assignment: &testParaID, - collations: Collations{ - secondedCount: 0, - }, - }, - }, - peerData: map[peer.ID]PeerData{ - peerID: { - view: View{}, - state: PeerStateInfo{ - PeerState: Collating, - CollatingPeerState: CollatingPeerState{ - ParaID: testParaID, - }, - }, - }, - }, - net: func() Network { - ctrl := gomock.NewController(t) - net := NewMockNetwork(ctrl) - // reporting for error out of view - net.EXPECT().ReportPeer(peerset.ReputationChange{ - Value: peerset.UnexpectedMessageValue, - Reason: peerset.UnexpectedMessageReason, - }, peerID) - return net - }(), - activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, - errString: "", - }, } for _, c := range testCases { c := c @@ -637,7 +574,6 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { perRelayParent: c.perRelayParent, peerData: c.peerData, activeLeaves: c.activeLeaves, - // currentAssignments: c.currentAssignments, } msg := NewCollationProtocol() vdtChild := NewCollatorProtocolMessage() @@ -660,9 +596,9 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { } } -// TODO: test InsertAdvertisement seperately - func TestInsertAdvertisement(t *testing.T) { + t.Parallel() + relayParent := getDummyHash(5) candidateHash := parachaintypes.CandidateHash{ @@ -724,9 +660,6 @@ func TestInsertAdvertisement(t *testing.T) { }, err: ErrDuplicateAdvertisement, }, - // { - // description: "fail with error peer limit reached", - // }, { description: "success case", peerData: PeerData{ @@ -753,7 +686,8 @@ func TestInsertAdvertisement(t *testing.T) { t.Run(c.description, func(t *testing.T) { t.Parallel() - _, err := c.peerData.InsertAdvertisement(relayParent, c.relayParentMode, c.candidateHash, c.implicitView, c.activeLeaves) + _, err := c.peerData.InsertAdvertisement( + relayParent, c.relayParentMode, c.candidateHash, c.implicitView, c.activeLeaves) require.ErrorIs(t, err, c.err) }, ) @@ -762,8 +696,9 @@ func TestInsertAdvertisement(t *testing.T) { } func TestFetchCollation(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{} - collatorID := parachaintypes.CollatorID{} testPeerID := peer.ID("testPeerID") pendingCollation := PendingCollation{ PeerID: testPeerID, @@ -799,7 +734,7 @@ func TestFetchCollation(t *testing.T) { t.Parallel() cpvs.peerData = c.peerData - err := cpvs.fetchCollation(pendingCollation, collatorID) + err := cpvs.fetchCollation(pendingCollation) require.ErrorIs(t, err, c.err) }) } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 0d03f02ab6..0a9f31b5ba 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -34,16 +34,17 @@ var ( ErrCollationNotInView = errors.New("collation is not in our view") ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") ErrProtocolMismatch = errors.New("an advertisement format doesn't match the relay parent") - ErrSecondedLimitReached = errors.New("para reached a limit of seconded candidates for this relay parent") - ErrRelayParentUnknown = errors.New("relay parent is unknown") - ErrUndeclaredPara = errors.New("peer has not declared its para id") - ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") - ErrInvalidAdvertisement = errors.New("advertisement is invalid") - ErrUndeclaredCollator = errors.New("no prior declare message received for this collator") - ErrOutOfView = errors.New("collation relay parent is out of our view") - ErrDuplicateAdvertisement = errors.New("advertisement is already known") - ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") - ErrNotAdvertised = errors.New("collation was not previously advertised") + ErrSecondedLimitReached = errors.New("para reached a limit of seconded" + + " candidates for this relay parent") + ErrRelayParentUnknown = errors.New("relay parent is unknown") + ErrUndeclaredPara = errors.New("peer has not declared its para id") + ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") + ErrInvalidAdvertisement = errors.New("advertisement is invalid") + ErrUndeclaredCollator = errors.New("no prior declare message received for this collator") + ErrOutOfView = errors.New("collation relay parent is out of our view") + ErrDuplicateAdvertisement = errors.New("advertisement is already known") + ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") + ErrNotAdvertised = errors.New("collation was not previously advertised") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -248,7 +249,7 @@ type CollatingPeerState struct { ParaID parachaintypes.ParaID // collations advertised by peer per relay parent advertisements map[common.Hash][]parachaintypes.CandidateHash - lastActive time.Time //nolint + lastActive time.Time } type PeerState uint @@ -349,9 +350,6 @@ type ProspectiveParachainsMode struct { // The maximum number of para blocks between the para head in a relay parent and a new candidate. // Restricts nodes from building arbitrary long chains and spamming other validators. maxCandidateDepth uint - - // How many ancestors of a relay parent are allowed to build candidates on top of. - allowedAncestryLen uint } type PerRelayParent struct { From afcc501dffa327acb2a2e27498dfd7760d72abc9 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 25 Oct 2023 12:34:56 +0530 Subject: [PATCH 56/85] small fix --- .../backing_implicit_view.go | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/dot/parachain/collator-protocol/backing_implicit_view.go b/dot/parachain/collator-protocol/backing_implicit_view.go index 69b19190f4..80f4cfbc5e 100644 --- a/dot/parachain/collator-protocol/backing_implicit_view.go +++ b/dot/parachain/collator-protocol/backing_implicit_view.go @@ -9,23 +9,23 @@ import ( type ImplicitView struct { } -// / Get the known, allowed relay-parents that are valid for parachain candidates -// / which could be backed in a child of a given block for a given para ID. -// / -// / This is expressed as a contiguous slice of relay-chain block hashes which may -// / include the provided block hash itself. -// / -// / If `para_id` is `None`, this returns all valid relay-parents across all paras -// / for the leaf. -// / -// / `None` indicates that the block hash isn't part of the implicit view or that -// / there are no known allowed relay parents. -// / -// / This always returns `Some` for active leaves or for blocks that previously -// / were active leaves. -// / -// / This can return the empty slice, which indicates that no relay-parents are allowed -// / for the para, e.g. if the para is not scheduled at the given block hash. +// Get the known, allowed relay-parents that are valid for parachain candidates +// which could be backed in a child of a given block for a given para ID. +// +// This is expressed as a contiguous slice of relay-chain block hashes which may +// include the provided block hash itself. +// +// If `para_id` is `None`, this returns all valid relay-parents across all paras +// for the leaf. +// +// `None` indicates that the block hash isn't part of the implicit view or that +// there are no known allowed relay parents. +// +// This always returns `Some` for active leaves or for blocks that previously +// were active leaves. +// +// This can return the empty slice, which indicates that no relay-parents are allowed +// for the para, e.g. if the para is not scheduled at the given block hash. func (iview ImplicitView) KnownAllowedRelayParentsUnder(hash common.Hash, paraID parachaintypes.ParaID) common.Hash { From c90d6c8061dcf83643f0c55cb520cac6287db05c Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 30 Oct 2023 13:07:47 +0530 Subject: [PATCH 57/85] more progress --- .../collator-protocol/validator_side.go | 99 +++++++++++++++++++ dot/parachain/types/types.go | 9 ++ 2 files changed, 108 insertions(+) diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 0a9f31b5ba..fcabb8895d 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -335,6 +335,23 @@ type CollatorProtocolValidatorSide struct { /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. activeLeaves map[common.Hash]ProspectiveParachainsMode + + fetchedCandidates map[string]CollationEvent +} + +// Identifier of a fetched collation +type fetchedCollation struct { + // Candidate's relay parent + relayParent common.Hash + paraID parachaintypes.ParaID + candidateHash parachaintypes.CandidateHash + // Id of the collator the collation was fetched from + collatorID parachaintypes.CollatorID +} + +func (f fetchedCollation) String() string { + return fmt.Sprintf("relay parent: %s, para id: %d, candidate hash: %s, collator id: %+v", + f.relayParent.String(), f.paraID, f.candidateHash.Value.String(), f.collatorID) } // Prospective parachains mode of a relay parent. Defined by @@ -403,6 +420,7 @@ type NetworkBridgeUpdate struct { // TODO: not quite sure if we would need this or something similar to this } +// SecondedOverseerMsg represents that the candidate we recommended to be seconded was validated successfully. type SecondedOverseerMsg struct { Parent common.Hash Stmt parachaintypes.StatementVDT @@ -414,6 +432,8 @@ type Backed struct { ParaHead common.Hash } +// InvalidOverseerMsg represents an invalid candidata. +// We recommended a particular candidate to be seconded, but it was invalid; penalize the collator. type InvalidOverseerMsg struct { Parent common.Hash CandidateReceipt parachaintypes.CandidateReceipt @@ -443,6 +463,85 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error // TODO: handle seconded message https://github.com/ChainSafe/gossamer/issues/3516 // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1466 //nolint + statementV, err := msg.Stmt.Value() + if err != nil { + return fmt.Errorf("getting value of statement: %w", err) + } + if statementV.Index() != 1 { + logger.Error("expected a seconded statement") + } + + receipt, ok := statementV.(parachaintypes.Seconded) + if !ok { + return fmt.Errorf("statement value expected: Seconded, got: %T", statementV) + } + candidateHashV, err := parachaintypes.CommittedCandidateReceipt(receipt).Hash() + if err != nil { + return fmt.Errorf("getting candidate hash from receipt: %w", err) + } + fetchedCollation := fetchedCollation{ + relayParent: receipt.Descriptor.RelayParent, + paraID: parachaintypes.ParaID(receipt.Descriptor.ParaID), + candidateHash: parachaintypes.CandidateHash{Value: candidateHashV}, + collatorID: receipt.Descriptor.Collator, + } + // remove the candidate from the list of fetched candidates + collationEvent, ok := cpvs.fetchedCandidates[fetchedCollation.String()] + if !ok { + logger.Error("collation has been seconded, but the relay parent is deactivated") + return nil + } + + delete(cpvs.fetchedCandidates, fetchedCollation.String()) + + // notify good collation + peerID, ok := cpvs.getPeerIDFromCollatorID(collationEvent.CollatorId) + if !ok { + return ErrPeerIDNotFoundForCollator + } + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.BenefitNotifyGoodValue, + Reason: peerset.BenefitNotifyGoodReason, + }, peerID) + + // notify candidate seconded + _, ok = cpvs.peerData[peerID] + if ok { + collatorProtocolMessage := NewCollatorProtocolMessage() + err = collatorProtocolMessage.Set(CollationSeconded{ + RelayParent: msg.Parent, + Statement: parachaintypes.UncheckedSignedFullStatement{ + Payload: msg.Stmt, + // TODO: + // ValidatorIndex: , + // Signature: , + }, + }) + if err != nil { + return fmt.Errorf("setting collation seconded: %w", err) + } + collationMessage := NewCollationProtocol() + + err = collationMessage.Set(collatorProtocolMessage) + if err != nil { + return fmt.Errorf("setting collation message: %w", err) + } + + err = cpvs.net.SendMessage(peerID, &collationMessage) + if err != nil { + return fmt.Errorf("sending collation message: %w", err) + } + + perRelayParent, ok := cpvs.perRelayParent[msg.Parent] + if ok { + perRelayParent.collations.status = Seconded + perRelayParent.collations.secondedCount++ + cpvs.perRelayParent[msg.Parent] = perRelayParent + } + + // TODO: Few more things for async backing, but we don't have async backing yet + // https://github.com/paritytech/polkadot-sdk/blob/7035034710ecb9c6a786284e5f771364c520598d/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1531-L1532 + } case Backed: // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 case InvalidOverseerMsg: diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index 52978023c8..aecf854bab 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -252,6 +252,15 @@ type CommittedCandidateReceipt struct { Commitments CandidateCommitments `scale:"2"` } +func (ccr CommittedCandidateReceipt) Hash() (common.Hash, error) { + bytes, err := scale.Marshal(ccr) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to marshal CommittedCandidateReceipt: %w", err) + } + + return common.Blake2bHash(bytes) +} + // AssignmentID The public key of a keypair used by a validator for determining assignments // to approve included parachain candidates. type AssignmentID [sr25519.PublicKeyLength]byte From f56db9cecfd644a08c874e8c443d88364fce4d24 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 30 Oct 2023 16:48:47 +0530 Subject: [PATCH 58/85] more progress --- .../collator-protocol/validator_side.go | 32 ++- .../collator-protocol/validator_side_test.go | 187 ++++++++++++++++++ dot/parachain/statement_fetching.go | 2 +- dot/parachain/types/types.go | 23 ++- 4 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 dot/parachain/collator-protocol/validator_side_test.go diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index fcabb8895d..51550cd4f1 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -306,9 +306,6 @@ type CollatorProtocolValidatorSide struct { ourView View - // Keep track of all pending candidate collations - pendingCandidates map[common.Hash]CollationEvent - // Parachains we're currently assigned to. With async backing enabled // this includes assignments from the implicit view. currentAssignments map[parachaintypes.ParaID]uint @@ -439,7 +436,7 @@ type InvalidOverseerMsg struct { CandidateReceipt parachaintypes.CandidateReceipt } -func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error { +func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // run this function as a goroutine, ideally switch msg := msg.(type) { @@ -460,9 +457,6 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error // TODO: handle network message https://github.com/ChainSafe/gossamer/issues/3515 // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1457 //nolint case SecondedOverseerMsg: - // TODO: handle seconded message https://github.com/ChainSafe/gossamer/issues/3516 - // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1466 //nolint - statementV, err := msg.Stmt.Value() if err != nil { return fmt.Errorf("getting value of statement: %w", err) @@ -475,7 +469,10 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error if !ok { return fmt.Errorf("statement value expected: Seconded, got: %T", statementV) } - candidateHashV, err := parachaintypes.CommittedCandidateReceipt(receipt).Hash() + + candidateReceipt := parachaintypes.CommittedCandidateReceipt(receipt) + + candidateHashV, err := candidateReceipt.ToPlain().Hash() if err != nil { return fmt.Errorf("getting candidate hash from receipt: %w", err) } @@ -547,13 +544,28 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error case InvalidOverseerMsg: invalidOverseerMsg := msg - collationEvent, ok := cpvs.pendingCandidates[invalidOverseerMsg.Parent] + candidateHashV, err := msg.CandidateReceipt.Hash() + if err != nil { + return fmt.Errorf("getting candidate hash from receipt: %w", err) + } + fetchedCollation := fetchedCollation{ + relayParent: msg.CandidateReceipt.Descriptor.RelayParent, + paraID: parachaintypes.ParaID(msg.CandidateReceipt.Descriptor.ParaID), + candidateHash: parachaintypes.CandidateHash{Value: candidateHashV}, + collatorID: msg.CandidateReceipt.Descriptor.Collator, + } + fmt.Println("cpvs.fetchedCandidates") + fmt.Println(cpvs.fetchedCandidates) + fmt.Println("fetchedCollation.String()") + fmt.Println(fetchedCollation.String()) + + collationEvent, ok := cpvs.fetchedCandidates[fetchedCollation.String()] if !ok { return nil } if *collationEvent.PendingCollation.CommitmentHash == (invalidOverseerMsg.CandidateReceipt.CommitmentsHash) { - delete(cpvs.pendingCandidates, invalidOverseerMsg.Parent) + delete(cpvs.fetchedCandidates, fetchedCollation.String()) } else { logger.Error("reported invalid candidate for unknown `pending_candidate`") return nil diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go new file mode 100644 index 0000000000..614ce8d2ad --- /dev/null +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -0,0 +1,187 @@ +package collatorprotocol + +import ( + "testing" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +func TestProcessOverseerMessage(t *testing.T) { + t.Parallel() + + var testCollatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(testCollatorID[:], tempCollatID) + peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + // testParaID := parachaintypes.ParaID(5) + + testCandidateReceipt := parachaintypes.CandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: uint32(1000), + RelayParent: common.MustHexToHash("0xded542bacb3ca6c033a57676f94ae7c8f36834511deb44e3164256fd3b1c0de0"), //nolint:lll + Collator: testCollatorID, + PersistedValidationDataHash: common.MustHexToHash("0x690d8f252ef66ab0f969c3f518f90012b849aa5ac94e1752c5e5ae5a8996de37"), //nolint:lll + PovHash: common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274"), //nolint:lll + ErasureRoot: common.MustHexToHash("0xc07f658163e93c45a6f0288d229698f09c1252e41076f4caa71c8cbc12f118a1"), //nolint:lll + ParaHead: common.MustHexToHash("0x9a8a7107426ef873ab89fc8af390ec36bdb2f744a9ff71ad7f18a12d55a7f4f5"), //nolint:lll + // ValidationCodeHash: parachaintypes.ValidationCodeHash(validationCodeHashV), + }, + + CommitmentsHash: common.MustHexToHash("0xa54a8dce5fd2a27e3715f99e4241f674a48f4529f77949a4474f5b283b823535"), + } + testCases := []struct { + description string + msg any + peerData map[peer.ID]PeerData + // perRelayParent map[common.Hash]PerRelayParent + net Network + fetchedCandidates map[string]CollationEvent + // activeLeaves map[common.Hash]ProspectiveParachainsMode + errString string + }{ + { + description: "CollateOn message fails with message not expected", + msg: CollateOn(2), + errString: ErrNotExpectedOnValidatorSide.Error(), + }, + { + description: "DistributeCollation message fails with message not expected", + msg: DistributeCollation{}, + errString: ErrNotExpectedOnValidatorSide.Error(), + }, + { + description: "ReportCollator message fails with peer not found for collator", + msg: ReportCollator(testCollatorID), + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "ReportCollator message succeeds and reports a bad collator", + msg: ReportCollator(testCollatorID), + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + + return net + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + errString: "", + }, + { + description: "InvalidOverseerMsg message fails with peer not found for collator", + msg: InvalidOverseerMsg{ + Parent: testRelayParent, + CandidateReceipt: testCandidateReceipt, + }, + fetchedCandidates: func() map[string]CollationEvent { + candidateHash, _ := testCandidateReceipt.Hash() + fetchedCollation := fetchedCollation{ + paraID: parachaintypes.ParaID(testCandidateReceipt.Descriptor.ParaID), + relayParent: testCandidateReceipt.Descriptor.RelayParent, + collatorID: testCandidateReceipt.Descriptor.Collator, + candidateHash: parachaintypes.CandidateHash{ + Value: candidateHash, + }, + } + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "InvalidOverseerMsg message succeeds, reports a bad collator and removes fetchedCandidate", + msg: InvalidOverseerMsg{ + Parent: testRelayParent, + CandidateReceipt: testCandidateReceipt, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + + return net + }(), + fetchedCandidates: func() map[string]CollationEvent { + candidateHash, _ := testCandidateReceipt.Hash() + fetchedCollation := fetchedCollation{ + paraID: parachaintypes.ParaID(testCandidateReceipt.Descriptor.ParaID), + relayParent: testCandidateReceipt.Descriptor.RelayParent, + collatorID: testCandidateReceipt.Descriptor.Collator, + candidateHash: parachaintypes.CandidateHash{ + Value: candidateHash, + }, + } + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + errString: "", + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + // perRelayParent: c.perRelayParent, + fetchedCandidates: c.fetchedCandidates, + peerData: c.peerData, + // activeLeaves: c.activeLeaves, + } + + err := cpvs.processMessage(c.msg) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + }) + } +} diff --git a/dot/parachain/statement_fetching.go b/dot/parachain/statement_fetching.go index f1c40e3417..5c8fb80c4e 100644 --- a/dot/parachain/statement_fetching.go +++ b/dot/parachain/statement_fetching.go @@ -16,7 +16,7 @@ type StatementFetchingRequest struct { // Data needed to locate and identify the needed statement. RelayParent common.Hash `scale:"1"` - // Hash of candidate that was used create the `CommitedCandidateRecept`. + // Hash of candidate that was used create the `CommitedCandidateReceipt`. CandidateHash parachaintypes.CandidateHash `scale:"2"` } diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index aecf854bab..df1e06ca76 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -241,6 +241,10 @@ type CandidateCommitments struct { HrmpWatermark uint32 `scale:"6"` } +func (cc CandidateCommitments) Hash() common.Hash { + return common.MustBlake2bHash(scale.MustMarshal(cc)) +} + // SessionIndex is a session index. type SessionIndex uint32 @@ -252,13 +256,11 @@ type CommittedCandidateReceipt struct { Commitments CandidateCommitments `scale:"2"` } -func (ccr CommittedCandidateReceipt) Hash() (common.Hash, error) { - bytes, err := scale.Marshal(ccr) - if err != nil { - return common.Hash{}, fmt.Errorf("failed to marshal CommittedCandidateReceipt: %w", err) +func (ccr CommittedCandidateReceipt) ToPlain() CandidateReceipt { + return CandidateReceipt{ + Descriptor: ccr.Descriptor, + CommitmentsHash: ccr.Commitments.Hash(), } - - return common.Blake2bHash(bytes) } // AssignmentID The public key of a keypair used by a validator for determining assignments @@ -332,6 +334,15 @@ type CandidateReceipt struct { CommitmentsHash common.Hash `scale:"2"` } +func (cr CandidateReceipt) Hash() (common.Hash, error) { + bytes, err := scale.Marshal(cr) + if err != nil { + return common.Hash{}, fmt.Errorf("failed to marshal CommittedCandidateReceipt: %w", err) + } + + return common.Blake2bHash(bytes) +} + // HeadData Parachain head data included in the chain. type HeadData struct { Data []byte `scale:"1"` From 0d9c1bd0344a3865d1a0d2eb088548976bb82df0 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 31 Oct 2023 11:08:04 +0530 Subject: [PATCH 59/85] some more tests --- .../collator-protocol/validator_side.go | 6 +----- .../collator-protocol/validator_side_test.go | 20 +++++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 51550cd4f1..380e60540b 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -462,7 +462,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { return fmt.Errorf("getting value of statement: %w", err) } if statementV.Index() != 1 { - logger.Error("expected a seconded statement") + return fmt.Errorf("expected a seconded statement") } receipt, ok := statementV.(parachaintypes.Seconded) @@ -554,10 +554,6 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { candidateHash: parachaintypes.CandidateHash{Value: candidateHashV}, collatorID: msg.CandidateReceipt.Descriptor.Collator, } - fmt.Println("cpvs.fetchedCandidates") - fmt.Println(cpvs.fetchedCandidates) - fmt.Println("fetchedCollation.String()") - fmt.Println(fetchedCollation.String()) collationEvent, ok := cpvs.fetchedCandidates[fetchedCollation.String()] if !ok { diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 614ce8d2ad..e402df5796 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -19,6 +19,7 @@ func TestProcessOverseerMessage(t *testing.T) { copy(testCollatorID[:], tempCollatID) peerID := peer.ID("testPeerID") testRelayParent := getDummyHash(5) + // testParaID := parachaintypes.ParaID(5) testCandidateReceipt := parachaintypes.CandidateReceipt{ @@ -40,8 +41,9 @@ func TestProcessOverseerMessage(t *testing.T) { msg any peerData map[peer.ID]PeerData // perRelayParent map[common.Hash]PerRelayParent - net Network - fetchedCandidates map[string]CollationEvent + net Network + fetchedCandidates map[string]CollationEvent + deletesFetchCandidate bool // activeLeaves map[common.Hash]ProspectiveParachainsMode errString string }{ @@ -112,7 +114,8 @@ func TestProcessOverseerMessage(t *testing.T) { }, } }(), - errString: ErrPeerIDNotFoundForCollator.Error(), + deletesFetchCandidate: true, + errString: ErrPeerIDNotFoundForCollator.Error(), }, { description: "InvalidOverseerMsg message succeeds, reports a bad collator and removes fetchedCandidate", @@ -161,7 +164,8 @@ func TestProcessOverseerMessage(t *testing.T) { }, }, }, - errString: "", + deletesFetchCandidate: true, + errString: "", }, } for _, c := range testCases { @@ -176,12 +180,20 @@ func TestProcessOverseerMessage(t *testing.T) { // activeLeaves: c.activeLeaves, } + lenFetchedCandidatesBefore := len(cpvs.fetchedCandidates) + err := cpvs.processMessage(c.msg) if c.errString == "" { require.NoError(t, err) } else { require.ErrorContains(t, err, c.errString) } + + if c.deletesFetchCandidate { + require.Equal(t, lenFetchedCandidatesBefore-1, len(cpvs.fetchedCandidates)) + } else { + require.Equal(t, lenFetchedCandidatesBefore, len(cpvs.fetchedCandidates)) + } }) } } From 0bd5c40148964b2335c8bd4dc581c46bc82a0a54 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 31 Oct 2023 14:03:22 +0530 Subject: [PATCH 60/85] more tests --- .../collator-protocol/validator_side_test.go | 111 +++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index e402df5796..458f2d7f2f 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -22,6 +22,16 @@ func TestProcessOverseerMessage(t *testing.T) { // testParaID := parachaintypes.ParaID(5) + commitments := parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + } + testCandidateReceipt := parachaintypes.CandidateReceipt{ Descriptor: parachaintypes.CandidateDescriptor{ ParaID: uint32(1000), @@ -34,8 +44,9 @@ func TestProcessOverseerMessage(t *testing.T) { // ValidationCodeHash: parachaintypes.ValidationCodeHash(validationCodeHashV), }, - CommitmentsHash: common.MustHexToHash("0xa54a8dce5fd2a27e3715f99e4241f674a48f4529f77949a4474f5b283b823535"), + CommitmentsHash: commitments.Hash(), } + testCases := []struct { description string msg any @@ -167,6 +178,104 @@ func TestProcessOverseerMessage(t *testing.T) { deletesFetchCandidate: true, errString: "", }, + { + description: "SecondedOverseerMsg message fails with peer not found for collator and removes fetchedCandidate", + msg: SecondedOverseerMsg{ + Parent: testRelayParent, + Stmt: func() parachaintypes.StatementVDT { + vdt := parachaintypes.NewStatementVDT() + vdt.Set(parachaintypes.Seconded( + parachaintypes.CommittedCandidateReceipt{ + Descriptor: testCandidateReceipt.Descriptor, + Commitments: commitments, + }, + )) + return vdt + }(), + }, + fetchedCandidates: func() map[string]CollationEvent { + candidateHash, _ := testCandidateReceipt.Hash() + fetchedCollation := fetchedCollation{ + paraID: parachaintypes.ParaID(testCandidateReceipt.Descriptor.ParaID), + relayParent: testCandidateReceipt.Descriptor.RelayParent, + collatorID: testCandidateReceipt.Descriptor.Collator, + candidateHash: parachaintypes.CandidateHash{ + Value: candidateHash, + }, + } + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + deletesFetchCandidate: true, + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "SecondedOverseerMsg message succceds, reports a good collator and removes fetchedCandidate", + msg: SecondedOverseerMsg{ + Parent: testRelayParent, + Stmt: func() parachaintypes.StatementVDT { + vdt := parachaintypes.NewStatementVDT() + vdt.Set(parachaintypes.Seconded( + parachaintypes.CommittedCandidateReceipt{ + Descriptor: testCandidateReceipt.Descriptor, + Commitments: commitments, + }, + )) + return vdt + }(), + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.BenefitNotifyGoodValue, + Reason: peerset.BenefitNotifyGoodReason, + }, peerID) + + net.EXPECT().SendMessage(peerID, gomock.AssignableToTypeOf(&CollationProtocol{})) + + return net + }(), + fetchedCandidates: func() map[string]CollationEvent { + candidateHash, _ := testCandidateReceipt.Hash() + fetchedCollation := fetchedCollation{ + paraID: parachaintypes.ParaID(testCandidateReceipt.Descriptor.ParaID), + relayParent: testCandidateReceipt.Descriptor.RelayParent, + collatorID: testCandidateReceipt.Descriptor.Collator, + candidateHash: parachaintypes.CandidateHash{ + Value: candidateHash, + }, + } + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + deletesFetchCandidate: true, + errString: "", + }, } for _, c := range testCases { c := c From 368cc7d7c02e0d2b90eb0426255c0e86a8a24217 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 31 Oct 2023 19:29:19 +0530 Subject: [PATCH 61/85] handling backed message --- dot/parachain/collator-protocol/message.go | 11 ++++++++--- .../collator-protocol/validator_side.go | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 22dd6f9e72..e3f7255bc6 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -376,14 +376,19 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo " relay parent: %s, para id: %d, candidate hash: %s", relayParent, collatorParaID, prospectiveCandidate.CandidateHash) - blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + backed := Backed{ + ParaID: collatorParaID, + ParaHead: prospectiveCandidate.ParentHeadDataHash, + } + + blockedAdvertisement := BlockedAdvertisement{ peerID: sender, collatorID: peerData.state.CollatingPeerState.CollatorID, candidateRelayParent: relayParent, candidateHash: prospectiveCandidate.CandidateHash, - }) + } - cpvs.BlockedAdvertisements = blockedAdvertisements + cpvs.BlockedAdvertisements[backed.String()] = blockedAdvertisement return nil } /*--------------------------------------------END----------------------------------------------------------*/ diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 380e60540b..b7d9f20396 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -315,7 +315,8 @@ type CollatorProtocolValidatorSide struct { // TODO: In rust this is a map, let's see if we can get away with a map // blocked_advertisements: HashMap<(ParaId, Hash), Vec>, - BlockedAdvertisements []BlockedAdvertisement + // BlockedAdvertisements []BlockedAdvertisement + BlockedAdvertisements map[string]BlockedAdvertisement // Leaves that do support asynchronous backing along with // implicit ancestry. Leaves from the implicit view are present in @@ -429,6 +430,10 @@ type Backed struct { ParaHead common.Hash } +func (b Backed) String() string { + return fmt.Sprintf("para id: %d, para head: %s", b.ParaID, b.ParaHead.String()) +} + // InvalidOverseerMsg represents an invalid candidata. // We recommended a particular candidate to be seconded, but it was invalid; penalize the collator. type InvalidOverseerMsg struct { @@ -541,6 +546,12 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { } case Backed: // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 + backed := msg + _, ok := cpvs.BlockedAdvertisements[backed.String()] + if ok { + delete(cpvs.BlockedAdvertisements, backed.String()) + requestUnblockedCollations() + } case InvalidOverseerMsg: invalidOverseerMsg := msg @@ -581,3 +592,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { return nil } + +func requestUnblockedCollations() { + +} From 90c24f888f9f6b99509ad63403c2989beaa3d0fc Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 31 Oct 2023 21:03:43 +0530 Subject: [PATCH 62/85] more progress --- dot/parachain/collator-protocol/message.go | 2 +- .../collator-protocol/validator_side.go | 32 +++++++++++++++---- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index e3f7255bc6..9a7cd250b6 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -388,7 +388,7 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo candidateHash: prospectiveCandidate.CandidateHash, } - cpvs.BlockedAdvertisements[backed.String()] = blockedAdvertisement + cpvs.BlockedAdvertisements[backed.String()] = []BlockedAdvertisement{blockedAdvertisement} return nil } /*--------------------------------------------END----------------------------------------------------------*/ diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index b7d9f20396..bb299d7813 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -313,10 +313,13 @@ type CollatorProtocolValidatorSide struct { // state tracked per relay parent perRelayParent map[common.Hash]PerRelayParent // map[replay parent]PerRelayParent - // TODO: In rust this is a map, let's see if we can get away with a map - // blocked_advertisements: HashMap<(ParaId, Hash), Vec>, - // BlockedAdvertisements []BlockedAdvertisement - BlockedAdvertisements map[string]BlockedAdvertisement + // Advertisements that were accepted as valid by collator protocol but rejected by backing. + // + // It's only legal to fetch collations that are either built on top of the root + // of some fragment tree or have a parent node which represents backed candidate. + // Otherwise, a validator will keep such advertisement in the memory and re-trigger + // requests to backing on new backed candidates and activations. + BlockedAdvertisements map[string][]BlockedAdvertisement // Leaves that do support asynchronous backing along with // implicit ancestry. Leaves from the implicit view are present in @@ -550,7 +553,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { _, ok := cpvs.BlockedAdvertisements[backed.String()] if ok { delete(cpvs.BlockedAdvertisements, backed.String()) - requestUnblockedCollations() + cpvs.requestUnblockedCollations(backed) } case InvalidOverseerMsg: invalidOverseerMsg := msg @@ -593,6 +596,23 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { return nil } -func requestUnblockedCollations() { +// requestUnblockedCollations Checks whether any of the advertisements are unblocked and attempts to fetch them. +func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed Backed) { + + for _, blockedAdvertisements := range cpvs.BlockedAdvertisements { + for _, blockedAdvertisement := range blockedAdvertisements { + isSecondingAllowed := cpvs.canSecond( + backed.ParaID, blockedAdvertisement.candidateRelayParent, blockedAdvertisement.candidateHash, backed.ParaHead) + + if isSecondingAllowed { + cpvs.enqueueCollation( + blockedAdvertisement.candidateRelayParent, + backed.ParaID, + blockedAdvertisement.peerID, + blockedAdvertisement.collatorID, + ) + } + } + } } From e1925b67dd8c9c1b6ac79d854aaff73be1217d9f Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 31 Oct 2023 21:22:01 +0530 Subject: [PATCH 63/85] more stuff --- .../collator-protocol/validator_side.go | 39 +++++++++++++++---- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index bb299d7813..49074fda74 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -597,22 +597,45 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { } // requestUnblockedCollations Checks whether any of the advertisements are unblocked and attempts to fetch them. -func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed Backed) { +func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed Backed) error { for _, blockedAdvertisements := range cpvs.BlockedAdvertisements { + + newBlockedAdvertisements := []BlockedAdvertisement{} + for _, blockedAdvertisement := range blockedAdvertisements { isSecondingAllowed := cpvs.canSecond( backed.ParaID, blockedAdvertisement.candidateRelayParent, blockedAdvertisement.candidateHash, backed.ParaHead) - if isSecondingAllowed { - cpvs.enqueueCollation( - blockedAdvertisement.candidateRelayParent, - backed.ParaID, - blockedAdvertisement.peerID, - blockedAdvertisement.collatorID, - ) + if !isSecondingAllowed { + newBlockedAdvertisements = append(newBlockedAdvertisements, blockedAdvertisement) + continue } + + perRelayParent, ok := cpvs.perRelayParent[blockedAdvertisement.candidateRelayParent] + if !ok { + return ErrRelayParentUnknown + } + + err := cpvs.enqueueCollation( + perRelayParent.collations, + blockedAdvertisement.candidateRelayParent, + backed.ParaID, + blockedAdvertisement.peerID, + blockedAdvertisement.collatorID, + nil, // nil for now until we have prospective parachain + ) + if err != nil { + return fmt.Errorf("enqueueing collation: %w", err) + } + } + + if len(newBlockedAdvertisements) == 0 { + return nil } + cpvs.BlockedAdvertisements[backed.String()] = newBlockedAdvertisements } + + return nil } From 6440a1dd7ed49cac0249d109aadbbc7f05eba1c4 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Fri, 10 Nov 2023 14:19:56 +0530 Subject: [PATCH 64/85] feat(parachain/collator): handle AdvertiseCollation message received by a collator (#3535) Fixes #3514 --- .../backing_implicit_view.go | 37 ++ dot/parachain/collator-protocol/message.go | 238 +++++++++++- .../collator-protocol/message_test.go | 338 ++++++++++++++++++ .../collator-protocol/validator_side.go | 177 ++++++++- dot/parachain/overseer/overseer_test.go | 4 + dot/parachain/types/subsystem_names.go | 3 + dot/peerset/constants.go | 12 +- 7 files changed, 787 insertions(+), 22 deletions(-) create mode 100644 dot/parachain/collator-protocol/backing_implicit_view.go diff --git a/dot/parachain/collator-protocol/backing_implicit_view.go b/dot/parachain/collator-protocol/backing_implicit_view.go new file mode 100644 index 0000000000..4d7c6b20db --- /dev/null +++ b/dot/parachain/collator-protocol/backing_implicit_view.go @@ -0,0 +1,37 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +// TODO: https://github.com/ChainSafe/gossamer/issues/3537 +// https://github.com/paritytech/polkadot-sdk/blob/d2fc1d7c91971e6e630a9db8cb627f8fdc91e8a4/polkadot/node/subsystem-util/src/backing_implicit_view.rs#L102 +type ImplicitView struct { +} + +// Get the known, allowed relay-parents that are valid for parachain candidates +// which could be backed in a child of a given block for a given para ID. +// +// This is expressed as a contiguous slice of relay-chain block hashes which may +// include the provided block hash itself. +// +// If `para_id` is `None`, this returns all valid relay-parents across all paras +// for the leaf. +// +// `None` indicates that the block hash isn't part of the implicit view or that +// there are no known allowed relay parents. +// +// This always returns `Some` for active leaves or for blocks that previously +// were active leaves. +// +// This can return the empty slice, which indicates that no relay-parents are allowed +// for the para, e.g. if the para is not scheduled at the given block hash. +func (iview ImplicitView) KnownAllowedRelayParentsUnder(hash common.Hash, + paraID parachaintypes.ParaID) common.Hash { + + return common.Hash{} +} diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 80e3dd4937..921518ac11 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain/backing" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" @@ -177,6 +178,227 @@ const ( Seconded ) +// BlockedAdvertisement is vstaging advertisement that was rejected by the backing +// subsystem. Validator may fetch it later if its fragment +// membership gets recognised before relay parent goes out of view. +type BlockedAdvertisement struct { + // peer that advertised the collation + peerID peer.ID + collatorID parachaintypes.CollatorID + candidateRelayParent common.Hash + candidateHash parachaintypes.CandidateHash +} + +func (cpvs CollatorProtocolValidatorSide) canSecond( + candidateParaID parachaintypes.ParaID, + candidateRelayParent common.Hash, + candidateHash parachaintypes.CandidateHash, + parentHeadDataHash common.Hash, +) bool { + canSecondRequest := backing.CanSecond{ + CandidateParaID: candidateParaID, + CandidateRelayParent: candidateRelayParent, + CandidateHash: candidateHash, + ParentHeadDataHash: parentHeadDataHash, + } + + responseChan := make(chan bool) + + cpvs.SubSystemToOverseer <- struct { + responseChan chan bool + canSecondRequest backing.CanSecond + }{ + responseChan: responseChan, + canSecondRequest: canSecondRequest, + } + + // TODO: Add timeout + return <-responseChan +} + +// Enqueue collation for fetching. The advertisement is expected to be +// validated. +func (cpvs CollatorProtocolValidatorSide) enqueueCollation( + collations Collations, + relayParent common.Hash, + paraID parachaintypes.ParaID, + peerID peer.ID, + collatorID parachaintypes.CollatorID, + prospectiveCandidate *ProspectiveCandidate) error { + + // TODO: return errors + pendingCollation := PendingCollation{ + RelayParent: relayParent, + ParaID: paraID, + PeerID: peerID, + ProspectiveCandidate: prospectiveCandidate, + } + + switch collations.status { + // TODO: In rust code, a lot of thing that are being done in handle_advertisement + // are being repeated here. + // Currently enqueueCollation is being called from handle_advertisement only, so we might not need to + // repeat that here. + // If enqueueCollation gets used somewhere else, we would need to repeat those things here. + + case Fetching, WaitingOnValidation: + logger.Debug("added collation to unfetched list") + collations.waitingQueue = append(collations.waitingQueue, UnfetchedCollation{ + CollatorID: collatorID, + PendingCollation: pendingCollation, + }) + case Waiting: + // limit is not reached, it's allowed to second another collation + return cpvs.fetchCollation(pendingCollation) + case Seconded: + perRelayParent, ok := cpvs.perRelayParent[relayParent] + if !ok { + logger.Error("candidate relay parent went out of view for valid advertisement") + return ErrRelayParentUnknown + } + if perRelayParent.prospectiveParachainMode.isEnabled { + return cpvs.fetchCollation(pendingCollation) + } else { + logger.Debug("a collation has already been seconded") + } + } + + return nil +} + +func (cpvs *CollatorProtocolValidatorSide) fetchCollation(pendingCollation PendingCollation) error { + var candidateHash *parachaintypes.CandidateHash + if pendingCollation.ProspectiveCandidate != nil { + candidateHash = &pendingCollation.ProspectiveCandidate.CandidateHash + } + peerData, ok := cpvs.peerData[pendingCollation.PeerID] + if !ok { + return ErrUnknownPeer + } + + if !peerData.HasAdvertised(pendingCollation.RelayParent, candidateHash) { + return ErrNotAdvertised + } + + // TODO: Add it to collation_fetch_timeouts if we can't process this in timeout time. + // state + // .collation_fetch_timeouts + // .push(timeout(id.clone(), candidate_hash, relay_parent).boxed()); + collation, err := cpvs.requestCollation(pendingCollation.RelayParent, pendingCollation.ParaID, + pendingCollation.PeerID) + if err != nil { + return fmt.Errorf("requesting collation: %w", err) + } + + cpvs.fetchedCollations = append(cpvs.fetchedCollations, *collation) + + return nil +} + +func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent common.Hash, sender peer.ID, + prospectiveCandidate *ProspectiveCandidate) error { + // TODO: + // - tracks advertisements received and the source (peer id) of the advertisement + // - accept one advertisement per collator per source per relay-parent + + perRelayParent, ok := cpvs.perRelayParent[relayParent] + if !ok { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return ErrRelayParentUnknown + } + + peerData, ok := cpvs.peerData[sender] + if !ok { + return ErrUnknownPeer + } + + if peerData.state.PeerState != Collating { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + return ErrUndeclaredPara + } + + collatorParaID := peerData.state.CollatingPeerState.ParaID + + if perRelayParent.assignment == nil || *perRelayParent.assignment != collatorParaID { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.WrongParaValue, + Reason: peerset.WrongParaReason, + }, sender) + return ErrInvalidAssignment + } + + // Note: Prospective Parachain mode would be set or edited when the view gets updated. + if perRelayParent.prospectiveParachainMode.isEnabled && prospectiveCandidate == nil { + // Expected v2 advertisement. + return ErrProtocolMismatch + } + + var prospectiveCandidateHash *parachaintypes.CandidateHash + if prospectiveCandidate != nil { + prospectiveCandidateHash = &prospectiveCandidate.CandidateHash + } + + isAdvertisementValid, err := peerData.InsertAdvertisement( + relayParent, + perRelayParent.prospectiveParachainMode, + prospectiveCandidateHash, + cpvs.implicitView, + cpvs.activeLeaves, + ) + if !isAdvertisementValid { + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, sender) + logger.Errorf(ErrInvalidAdvertisement.Error()) + } else if err != nil { + return fmt.Errorf("inserting advertisement: %w", err) + } + + if perRelayParent.collations.IsSecondedLimitReached(perRelayParent.prospectiveParachainMode) { + return ErrSecondedLimitReached + } + + /*NOTE:---------------------------------------Matters only in V2----------------------------------------------*/ + isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( + collatorParaID, + relayParent, + prospectiveCandidate.CandidateHash, + prospectiveCandidate.ParentHeadDataHash, + ) + + if !isSecondingAllowed { + logger.Infof("Seconding is not allowed by backing, queueing advertisement,"+ + " relay parent: %s, para id: %d, candidate hash: %s", + relayParent, collatorParaID, prospectiveCandidate.CandidateHash) + + blockedAdvertisements := append(cpvs.BlockedAdvertisements, BlockedAdvertisement{ + peerID: sender, + collatorID: peerData.state.CollatingPeerState.CollatorID, + candidateRelayParent: relayParent, + candidateHash: prospectiveCandidate.CandidateHash, + }) + + cpvs.BlockedAdvertisements = blockedAdvertisements + return nil + } + /*--------------------------------------------END----------------------------------------------------------*/ + + return cpvs.enqueueCollation(perRelayParent.collations, + relayParent, + collatorParaID, + sender, + peerData.state.CollatingPeerState.CollatorID, + prospectiveCandidate) + +} + // getDeclareSignaturePayload gives the payload that should be signed and included in a Declare message. // The payload is a the local peer id of the node, which serves to prove that it controls the // collator key it is declaring and intends to collate under. @@ -219,7 +441,7 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( return propagate, fmt.Errorf("getting collator protocol message value: %w", err) } - switch collatorProtocolMessage.Index() { + switch collatorProtocolMessageV.Index() { // TODO: Create an issue to cover v2 types. #3534 case 0: // Declare declareMessage, ok := collatorProtocolMessageV.(Declare) @@ -291,7 +513,19 @@ func (cpvs CollatorProtocolValidatorSide) handleCollationMessage( // Currently we have a Handler in dot/peerset, but it does not get used anywhere. } case 1: // AdvertiseCollation - // TODO: handle collation advertisement https://github.com/ChainSafe/gossamer/issues/3514 + advertiseCollationMessage, ok := collatorProtocolMessageV.(AdvertiseCollation) + if !ok { + return propagate, errors.New("expected message to be advertise collation") + } + + err := cpvs.handleAdvertisement(common.Hash(advertiseCollationMessage), sender, nil) + if err != nil { + return propagate, fmt.Errorf("handling v1 advertisement: %w", err) + } + // TODO: + // - tracks advertisements received and the source (peer id) of the advertisement + // - accept one advertisement per collator per source per relay-parent + case 2: // CollationSeconded logger.Errorf("unexpected collation seconded message from peer %s, decreasing its reputation", sender) cpvs.net.ReportPeer(peerset.ReputationChange{ diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 61977f6ea3..82db19de53 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -401,3 +401,341 @@ func TestHandleCollationMessageDeclare(t *testing.T) { }) } } + +func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { + t.Parallel() + + peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + testParaID := parachaintypes.ParaID(5) + + testCases := []struct { + description string + advertiseCollation AdvertiseCollation + peerData map[peer.ID]PeerData + perRelayParent map[common.Hash]PerRelayParent + net Network + activeLeaves map[common.Hash]ProspectiveParachainsMode + errString string + }{ + { + description: "fail with relay parent is unknown if we don't have the relay" + + " parent tracked and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + + return net + }(), + errString: ErrRelayParentUnknown.Error(), + }, + { + description: "fail with unknown peer if peer is not tracked in our list of active collators", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: {}, + }, + errString: ErrUnknownPeer.Error(), + }, + { + description: "fail with undeclared para if peer has not declared its para id" + + " and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: {}, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + errString: ErrUndeclaredPara.Error(), + }, + { + description: "fail with invalid assignment if para id is not currently" + + " assigned to us for this relay parent and report the peer", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.WrongParaValue, + Reason: peerset.WrongParaReason, + }, peerID) + return net + }(), + errString: ErrInvalidAssignment.Error(), + }, + { + // NOTE: prospective parachain mode and prospective candidates were added in V2, + // In V1, prospective parachain mode is disabled by and prospective candidates is nil + // In V2, prospective parachain mode is enabled by and prospective candidates is not nil + description: "fail with protocol mismatch is prospective parachain mode in" + + " enable but with got a nil value for prospective candidate", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + prospectiveParachainMode: ProspectiveParachainsMode{ + isEnabled: true, + }, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: testParaID, + }, + }, + }, + }, + errString: ErrProtocolMismatch.Error(), + }, + { + description: "fail if para reached a limit of seconded candidates for this relay parent", + advertiseCollation: AdvertiseCollation(testRelayParent), + perRelayParent: map[common.Hash]PerRelayParent{ + testRelayParent: { + assignment: &testParaID, + collations: Collations{ + // For Collator Protocol v1, we can only second one candidate + // at a time, so seconded limit would be 1 + secondedCount: 1, + }, + }, + }, + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: testParaID, + }, + }, + }, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + // reporting for error out of view + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.UnexpectedMessageValue, + Reason: peerset.UnexpectedMessageReason, + }, peerID) + return net + }(), + activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + errString: ErrSecondedLimitReached.Error(), + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + perRelayParent: c.perRelayParent, + peerData: c.peerData, + activeLeaves: c.activeLeaves, + } + msg := NewCollationProtocol() + vdtChild := NewCollatorProtocolMessage() + + err := vdtChild.Set(c.advertiseCollation) + require.NoError(t, err) + + err = msg.Set(vdtChild) + require.NoError(t, err) + + propagate, err := cpvs.handleCollationMessage(peerID, &msg) + require.False(t, propagate) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + }) + } +} + +func TestInsertAdvertisement(t *testing.T) { + t.Parallel() + + relayParent := getDummyHash(5) + + candidateHash := parachaintypes.CandidateHash{ + Value: getDummyHash(9), + } + + testCases := []struct { + description string + peerData PeerData + relayParentMode ProspectiveParachainsMode + candidateHash *parachaintypes.CandidateHash + implicitView ImplicitView + activeLeaves map[common.Hash]ProspectiveParachainsMode + err error + }{ + { + description: "fail with undeclared collator", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Connected, + }, + }, + err: ErrUndeclaredCollator, + }, + { + description: "fail with error out of view", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: nil, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + err: ErrOutOfView, + }, + { + description: "fail with error duplicate advertisement", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(5), + advertisements: map[common.Hash][]parachaintypes.CandidateHash{ + relayParent: {}, + }, + }, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: nil, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + relayParent: { + isEnabled: false, + }, + }, + err: ErrDuplicateAdvertisement, + }, + { + description: "success case", + peerData: PeerData{ + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + ParaID: parachaintypes.ParaID(5), + advertisements: map[common.Hash][]parachaintypes.CandidateHash{}, + }, + }, + }, + relayParentMode: ProspectiveParachainsMode{}, + candidateHash: &candidateHash, + activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + relayParent: { + isEnabled: false, + }, + }, + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + _, err := c.peerData.InsertAdvertisement( + relayParent, c.relayParentMode, c.candidateHash, c.implicitView, c.activeLeaves) + require.ErrorIs(t, err, c.err) + }, + ) + } + +} + +func TestFetchCollation(t *testing.T) { + t.Parallel() + + cpvs := CollatorProtocolValidatorSide{} + testPeerID := peer.ID("testPeerID") + pendingCollation := PendingCollation{ + PeerID: testPeerID, + } + testCases := []struct { + description string + peerData map[peer.ID]PeerData + err error + }{ + { + description: "fail with unknown peer", + peerData: map[peer.ID]PeerData{}, + err: ErrUnknownPeer, + }, + { + description: "fail with collation not previously advertised", + peerData: map[peer.ID]PeerData{ + testPeerID: { + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + advertisements: make(map[common.Hash][]parachaintypes.CandidateHash), + }, + }, + }, + }, + err: ErrNotAdvertised, + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + cpvs.peerData = c.peerData + err := cpvs.fetchCollation(pendingCollation) + require.ErrorIs(t, err, c.err) + }) + } +} diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 1e5caba257..f9d33ccfc7 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -33,6 +33,18 @@ var ( ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") ErrCollationNotInView = errors.New("collation is not in our view") ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") + ErrProtocolMismatch = errors.New("an advertisement format doesn't match the relay parent") + ErrSecondedLimitReached = errors.New("para reached a limit of seconded" + + " candidates for this relay parent") + ErrRelayParentUnknown = errors.New("relay parent is unknown") + ErrUndeclaredPara = errors.New("peer has not declared its para id") + ErrInvalidAssignment = errors.New("we're assigned to a different para at the given relay parent") + ErrInvalidAdvertisement = errors.New("advertisement is invalid") + ErrUndeclaredCollator = errors.New("no prior declare message received for this collator") + ErrOutOfView = errors.New("collation relay parent is out of our view") + ErrDuplicateAdvertisement = errors.New("advertisement is already known") + ErrPeerLimitReached = errors.New("limit for announcements per peer is reached") + ErrNotAdvertised = errors.New("collation was not previously advertised") ) func (cpvs CollatorProtocolValidatorSide) Run( @@ -61,7 +73,7 @@ func (cpvs CollatorProtocolValidatorSide) Run( // check if this peer id has advertised this relay parent peerData := cpvs.peerData[unfetchedCollation.PendingCollation.PeerID] - if peerData.HasAdvertisedRelayParent(unfetchedCollation.PendingCollation.RelayParent) { + if peerData.HasAdvertised(unfetchedCollation.PendingCollation.RelayParent, nil) { // if so request collation from this peer id collation, err := cpvs.requestCollation(unfetchedCollation.PendingCollation.RelayParent, unfetchedCollation.PendingCollation.ParaID, unfetchedCollation.PendingCollation.PeerID) @@ -85,6 +97,8 @@ func (CollatorProtocolValidatorSide) Name() parachaintypes.SubSystemName { // - check if the requested collation is in our view func (cpvs CollatorProtocolValidatorSide) requestCollation(relayParent common.Hash, paraID parachaintypes.ParaID, peerID peer.ID) (*parachaintypes.Collation, error) { + + // TODO: Make sure that the request can be done in MAX_UNSHARED_DOWNLOAD_TIME timeout if !slices.Contains[[]common.Hash](cpvs.ourView.heads, relayParent) { return nil, ErrCollationNotInView } @@ -120,10 +134,11 @@ type UnfetchedCollation struct { } type PendingCollation struct { - RelayParent common.Hash - ParaID parachaintypes.ParaID - PeerID peer.ID - CommitmentHash *common.Hash + RelayParent common.Hash + ParaID parachaintypes.ParaID + PeerID peer.ID + CommitmentHash *common.Hash + ProspectiveCandidate *ProspectiveCandidate } type PeerData struct { @@ -131,12 +146,19 @@ type PeerData struct { state PeerStateInfo } -func (peerData *PeerData) HasAdvertisedRelayParent(relayParent common.Hash) bool { +func (peerData *PeerData) HasAdvertised( + relayParent common.Hash, + mayBeCandidateHash *parachaintypes.CandidateHash) bool { if peerData.state.PeerState == Connected { return false } - return slices.Contains(peerData.view.heads, relayParent) + candidates, ok := peerData.state.CollatingPeerState.advertisements[relayParent] + if mayBeCandidateHash == nil { + return ok + } + + return slices.Contains(candidates, *mayBeCandidateHash) } func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, paraID parachaintypes.ParaID) { @@ -149,9 +171,70 @@ func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, par } } -func (peerData *PeerData) InsertAdvertisement() error { - // TODO: part of https://github.com/ChainSafe/gossamer/issues/3514 - return nil +func IsRelayParentInImplicitView( + relayParent common.Hash, + relayParentMode ProspectiveParachainsMode, + implicitView ImplicitView, + activeLeaves map[common.Hash]ProspectiveParachainsMode, + paraID parachaintypes.ParaID, +) bool { + if !relayParentMode.isEnabled { + _, ok := activeLeaves[relayParent] + return ok + } + + for hash, mode := range activeLeaves { + knownAllowedRelayParent := implicitView.KnownAllowedRelayParentsUnder(hash, paraID) + if mode.isEnabled && knownAllowedRelayParent.String() == relayParent.String() { + return true + } + } + + return false +} + +// Note an advertisement by the collator. Returns `true` if the advertisement was imported +// successfully. Fails if the advertisement is duplicate, out of view, or the peer has not +// declared itself a collator. +func (peerData *PeerData) InsertAdvertisement( + onRelayParent common.Hash, + relayParentMode ProspectiveParachainsMode, + candidateHash *parachaintypes.CandidateHash, + implicitView ImplicitView, + activeLeaves map[common.Hash]ProspectiveParachainsMode, +) (isAdvertisementInvalid bool, err error) { + switch peerData.state.PeerState { + case Connected: + return false, ErrUndeclaredCollator + case Collating: + if !IsRelayParentInImplicitView(onRelayParent, relayParentMode, implicitView, + activeLeaves, peerData.state.CollatingPeerState.ParaID) { + return false, ErrOutOfView + } + + if relayParentMode.isEnabled { + // relayParentMode.maxCandidateDepth + candidates, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] + if ok && slices.Contains[[]parachaintypes.CandidateHash](candidates, *candidateHash) { + return false, ErrDuplicateAdvertisement + } + + if len(candidates) > int(relayParentMode.maxCandidateDepth) { + return false, ErrPeerLimitReached + } + candidates = append(candidates, *candidateHash) + peerData.state.CollatingPeerState.advertisements[onRelayParent] = candidates + } else { + _, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] + if ok { + return false, ErrDuplicateAdvertisement + } + peerData.state.CollatingPeerState.advertisements[onRelayParent] = []parachaintypes.CandidateHash{*candidateHash} + } + + peerData.state.CollatingPeerState.lastActive = time.Now() + } + return true, nil } type PeerStateInfo struct { @@ -162,10 +245,11 @@ type PeerStateInfo struct { } type CollatingPeerState struct { - CollatorID parachaintypes.CollatorID - ParaID parachaintypes.ParaID - advertisements []common.Hash //nolint - lastActive time.Time //nolint + CollatorID parachaintypes.CollatorID + ParaID parachaintypes.ParaID + // collations advertised by peer per relay parent + advertisements map[common.Hash][]parachaintypes.CandidateHash + lastActive time.Time } type PeerState uint @@ -228,6 +312,71 @@ type CollatorProtocolValidatorSide struct { // Parachains we're currently assigned to. With async backing enabled // this includes assignments from the implicit view. currentAssignments map[parachaintypes.ParaID]uint + + // state tracked per relay parent + perRelayParent map[common.Hash]PerRelayParent + + // TODO: In rust this is a map, let's see if we can get away with a map + // blocked_advertisements: HashMap<(ParaId, Hash), Vec>, + BlockedAdvertisements []BlockedAdvertisement + + // Leaves that do support asynchronous backing along with + // implicit ancestry. Leaves from the implicit view are present in + // `active_leaves`, the opposite doesn't hold true. + // + // Relay-chain blocks which don't support prospective parachains are + // never included in the fragment trees of active leaves which do. In + // particular, this means that if a given relay parent belongs to implicit + // ancestry of some active leaf, then it does support prospective parachains. + implicitView ImplicitView + + /// All active leaves observed by us, including both that do and do not + /// support prospective parachains. This mapping works as a replacement for + /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition + /// to asynchronous backing is done. + activeLeaves map[common.Hash]ProspectiveParachainsMode +} + +// Prospective parachains mode of a relay parent. Defined by +// the Runtime API version. +// +// Needed for the period of transition to asynchronous backing. +type ProspectiveParachainsMode struct { + // if disabled, there are no prospective parachains. Runtime API does not have support for `async_backing_params` + isEnabled bool + + // these values would be present only if `isEnabled` is true + + // The maximum number of para blocks between the para head in a relay parent and a new candidate. + // Restricts nodes from building arbitrary long chains and spamming other validators. + maxCandidateDepth uint +} + +type PerRelayParent struct { + prospectiveParachainMode ProspectiveParachainsMode + assignment *parachaintypes.ParaID + collations Collations +} + +type Collations struct { + // What is the current status in regards to a collation for this relay parent? + status CollationStatus + // how many collations have been seconded + secondedCount uint + // Collation that were advertised to us, but we did not yet fetch. + waitingQueue []UnfetchedCollation // : VecDeque<(PendingCollation, CollatorId)>, +} + +// IsSecondedLimitReached check the limit of seconded candidates for a given para has been reached. +func (collations Collations) IsSecondedLimitReached(relayParentMode ProspectiveParachainsMode) bool { + var secondedLimit uint + if relayParentMode.isEnabled { + secondedLimit = relayParentMode.maxCandidateDepth + 1 + } else { + secondedLimit = 1 + } + + return collations.secondedCount >= secondedLimit } func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID parachaintypes.CollatorID, diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index d3c03e5bbf..81ccb745d0 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -45,6 +45,10 @@ func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, S } } +func (s *TestSubsystem) String() parachaintypes.SubSystemName { + return parachaintypes.SubSystemName(s.name) +} + func TestStart2SubsytemsActivate1(t *testing.T) { overseer := NewOverseer() require.NotNil(t, overseer) diff --git a/dot/parachain/types/subsystem_names.go b/dot/parachain/types/subsystem_names.go index e9d1414f49..5e40a1d420 100644 --- a/dot/parachain/types/subsystem_names.go +++ b/dot/parachain/types/subsystem_names.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachaintypes type SubSystemName string diff --git a/dot/peerset/constants.go b/dot/peerset/constants.go index e5fc5be478..dbd93d7552 100644 --- a/dot/peerset/constants.go +++ b/dot/peerset/constants.go @@ -76,17 +76,17 @@ const ( BadJustificationReason = "Bad justification" // GenesisMismatch is used when peer has a different genesis - GenesisMismatch Reputation = Malicious + GenesisMismatch = Malicious // GenesisMismatchReason used when a peer has a different genesis GenesisMismatchReason = "Genesis mismatch" // BenefitNotifyGoodValue is used when a collator was noted good by another subsystem - BenefitNotifyGoodValue Reputation = BenefitMinor + BenefitNotifyGoodValue = BenefitMinor // BenefitNotifyGoodReason is used when a collator was noted good by another subsystem BenefitNotifyGoodReason = "A collator was noted good by another subsystem" // UnexpectedMessageValue is used when validator side of the collator protocol receives an unexpected message - UnexpectedMessageValue Reputation = CostMinor + UnexpectedMessageValue = CostMinor // UnexpectedMessageReason is used when validator side of the collator protocol receives an unexpected message UnexpectedMessageReason = "An unexpected message" @@ -101,17 +101,17 @@ const ( NetworkErrorReason = "Some network error" // InvalidSignatureValue is used when signature of the network message is invalid. - InvalidSignatureValue Reputation = Malicious + InvalidSignatureValue = Malicious // InvalidSignatureReason is used when signature of the network message is invalid. InvalidSignatureReason = "Invalid network message signature" // ReportBadCollatorValue is used when a collator was reported to be bad by another subsystem - ReportBadCollatorValue Reputation = Malicious + ReportBadCollatorValue = Malicious // ReportBadCollatorReason is used when a collator was reported to be bad by another subsystem ReportBadCollatorReason = "A collator was reported by another subsystem" // WrongParaValue is used when a collator provided a collation for the wrong para - WrongParaValue Reputation = Malicious + WrongParaValue = Malicious // WrongParaReason is used when a collator provided a collation for the wrong para WrongParaReason = "A collator provided a collation for the wrong para" From bca22c9cbc850a895c424ba453acc73a5e79d4e5 Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Mon, 20 Nov 2023 18:17:31 -0500 Subject: [PATCH 65/85] feat(parachain/availabilitystore): implement database store/retrieve (#3560) --- dot/mock_node_builder_test.go | 9 +- dot/node.go | 4 +- dot/node_integration_test.go | 8 +- .../availability-store/availability_store.go | 335 +++++++++++++++ .../availability_store_test.go | 394 ++++++++++++++++++ .../availability-store/availabilitystore.go | 89 ---- dot/parachain/availability-store/messages.go | 101 ++++- dot/parachain/availability-store/register.go | 13 +- dot/parachain/service.go | 6 +- dot/services.go | 4 +- 10 files changed, 850 insertions(+), 113 deletions(-) create mode 100644 dot/parachain/availability-store/availability_store.go create mode 100644 dot/parachain/availability-store/availability_store_test.go delete mode 100644 dot/parachain/availability-store/availabilitystore.go diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index 2a1b2a5480..452dd51af6 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -18,7 +18,6 @@ import ( system "github.com/ChainSafe/gossamer/dot/system" types "github.com/ChainSafe/gossamer/dot/types" babe "github.com/ChainSafe/gossamer/lib/babe" - common "github.com/ChainSafe/gossamer/lib/common" grandpa "github.com/ChainSafe/gossamer/lib/grandpa" keystore "github.com/ChainSafe/gossamer/lib/keystore" runtime "github.com/ChainSafe/gossamer/lib/runtime" @@ -138,18 +137,18 @@ func (mr *MocknodeBuilderIfaceMockRecorder) createNetworkService(config, stateSr } // createParachainHostService mocks base method. -func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, forkID string, genesishHash common.Hash) (*parachain.Service, error) { +func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, forkID string, st *state.Service) (*parachain.Service, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "createParachainHostService", net, forkID, genesishHash) + ret := m.ctrl.Call(m, "createParachainHostService", net, forkID, st) ret0, _ := ret[0].(*parachain.Service) ret1, _ := ret[1].(error) return ret0, ret1 } // createParachainHostService indicates an expected call of createParachainHostService. -func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, forkID, genesishHash interface{}) *gomock.Call { +func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, forkID, st interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, forkID, genesishHash) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, forkID, st) } // createRPCService mocks base method. diff --git a/dot/node.go b/dot/node.go index f4d5d2a482..35d34904dd 100644 --- a/dot/node.go +++ b/dot/node.go @@ -64,7 +64,7 @@ type nodeBuilderIface interface { dh *digest.Handler) (*core.Service, error) createGRANDPAService(config *cfg.Config, st *state.Service, ks KeyStore, net *network.Service, telemetryMailer Telemetry) (*grandpa.Service, error) - createParachainHostService(net *network.Service, forkID string, genesishHash common.Hash) (*parachain.Service, error) + createParachainHostService(net *network.Service, forkID string, st *state.Service) (*parachain.Service, error) newSyncService(config *cfg.Config, st *state.Service, finalityGadget BlockJustificationVerifier, verifier *babe.VerificationManager, cs *core.Service, net *network.Service, telemetryMailer Telemetry) (*dotsync.Service, error) @@ -381,7 +381,7 @@ func newNode(config *cfg.Config, } nodeSrvcs = append(nodeSrvcs, fg) - phs, err := builder.createParachainHostService(networkSrvc, gd.ForkID, stateSrvc.Block.GenesisHash()) + phs, err := builder.createParachainHostService(networkSrvc, gd.ForkID, stateSrvc) if err != nil { return nil, err } diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index e42f2d6eb3..93918e8acb 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -120,7 +120,11 @@ func TestNewNode(t *testing.T) { return stateSrvc, nil }) - phs, err := parachain.NewService(testNetworkService, "random_fork_id", common.Hash{}) + mockBlockState := &state.BlockState{} + mockStateService := &state.Service{ + Block: mockBlockState, + } + phs, err := parachain.NewService(testNetworkService, "random_fork_id", mockStateService) require.NoError(t, err) m.EXPECT().createRuntimeStorage(gomock.AssignableToTypeOf(&state.Service{})).Return(&runtime. @@ -156,7 +160,7 @@ func TestNewNode(t *testing.T) { m.EXPECT().createParachainHostService( gomock.AssignableToTypeOf(&network.Service{}), gomock.AssignableToTypeOf("random_fork_id"), - gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf(mockStateService), ).Return(phs, nil) got, err := newNode(initConfig, ks, m, mockServiceRegistry) diff --git a/dot/parachain/availability-store/availability_store.go b/dot/parachain/availability-store/availability_store.go new file mode 100644 index 0000000000..d19e31f230 --- /dev/null +++ b/dot/parachain/availability-store/availability_store.go @@ -0,0 +1,335 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package availabilitystore + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/internal/database" + "github.com/ChainSafe/gossamer/internal/log" + "github.com/ChainSafe/gossamer/lib/common" +) + +var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-availability-store")) + +const ( + avaliableDataPrefix = "available" + chunkPrefix = "chunk" + metaPrefix = "meta" + unfinalizedPrefix = "unfinalized" + pruneByTimePrefix = "prune_by_time" +) + +// AvailabilityStoreSubsystem is the struct that holds subsystem data for the availability store +type AvailabilityStoreSubsystem struct { + SubSystemToOverseer chan<- any + OverseerToSubSystem <-chan any + availabilityStore AvailabilityStore + //TODO: pruningConfig PruningConfig + //TODO: clock Clock + //TODO: metrics Metrics +} + +// AvailabilityStore is the struct that holds data for the availability store +type AvailabilityStore struct { + availableTable database.Table + chunkTable database.Table + metaTable database.Table + //TODO: unfinalizedTable database.Table + //TODO: pruneByTimeTable database.Table +} + +// NewAvailabilityStore creates a new instance of AvailabilityStore +func NewAvailabilityStore(db database.Database) *AvailabilityStore { + return &AvailabilityStore{ + availableTable: database.NewTable(db, avaliableDataPrefix), + chunkTable: database.NewTable(db, chunkPrefix), + metaTable: database.NewTable(db, metaPrefix), + } +} + +// loadAvailableData loads available data from the availability store +func (as *AvailabilityStore) loadAvailableData(candidate common.Hash) (*AvailableData, error) { + resultBytes, err := as.availableTable.Get(candidate[:]) + if err != nil { + return nil, fmt.Errorf("getting candidate %v from available table: %w", candidate, err) + } + result := AvailableData{} + err = json.Unmarshal(resultBytes, &result) + if err != nil { + return nil, fmt.Errorf("unmarshalling available data: %w", err) + } + return &result, nil +} + +// loadMetaData loads metadata from the availability store +func (as *AvailabilityStore) loadMetaData(candidate common.Hash) (*CandidateMeta, error) { + resultBytes, err := as.metaTable.Get(candidate[:]) + if err != nil { + return nil, fmt.Errorf("getting candidate %v from available table: %w", candidate, err) + } + result := CandidateMeta{} + err = json.Unmarshal(resultBytes, &result) + if err != nil { + return nil, fmt.Errorf("unmarshalling candidate meta: %w", err) + } + return &result, nil +} + +// storeMetaData stores metadata in the availability store +func (as *AvailabilityStore) storeMetaData(candidate common.Hash, meta CandidateMeta) error { + dataBytes, err := json.Marshal(meta) + if err != nil { + return fmt.Errorf("marshalling meta for candidate: %w", err) + } + err = as.metaTable.Put(candidate[:], dataBytes) + if err != nil { + return fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) + } + return nil +} + +// loadChunk loads a chunk from the availability store +func (as *AvailabilityStore) loadChunk(candidate common.Hash, validatorIndex uint32) (*ErasureChunk, error) { + resultBytes, err := as.chunkTable.Get(append(candidate[:], uint32ToBytes(validatorIndex)...)) + if err != nil { + return nil, fmt.Errorf("getting candidate %v, index %d from chunk table: %w", candidate, validatorIndex, err) + } + result := ErasureChunk{} + err = json.Unmarshal(resultBytes, &result) + if err != nil { + return nil, fmt.Errorf("unmarshalling chunk: %w", err) + } + return &result, nil +} + +// storeChunk stores a chunk in the availability store +func (as *AvailabilityStore) storeChunk(candidate common.Hash, chunk ErasureChunk) error { + meta, err := as.loadMetaData(candidate) + if err != nil { + + if errors.Is(err, database.ErrNotFound) { + // TODO: were creating metadata here, but we should be doing it in the parachain block import? + // TODO: also we need to determine how many chunks we need to store + meta = &CandidateMeta{ + ChunksStored: make([]bool, 16), + } + } else { + return fmt.Errorf("load metadata: %w", err) + } + } + + if meta.ChunksStored[chunk.Index] { + logger.Debugf("Chunk %d already stored", chunk.Index) + return nil // already stored + } else { + dataBytes, err := json.Marshal(chunk) + if err != nil { + return fmt.Errorf("marshalling chunk: %w", err) + } + err = as.chunkTable.Put(append(candidate[:], uint32ToBytes(chunk.Index)...), dataBytes) + if err != nil { + return fmt.Errorf("storing chunk for candidate %v, index %d: %w", candidate, chunk.Index, err) + } + + meta.ChunksStored[chunk.Index] = true + err = as.storeMetaData(candidate, *meta) + if err != nil { + return fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) + } + } + logger.Debugf("stored chuck %d for %v", chunk.Index, candidate) + return nil +} + +// storeAvailableData stores available data in the availability store +func (as *AvailabilityStore) storeAvailableData(candidate common.Hash, data AvailableData) error { + dataBytes, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("marshalling available data: %w", err) + } + err = as.availableTable.Put(candidate[:], dataBytes) + if err != nil { + return fmt.Errorf("storing available data for candidate %v: %w", candidate, err) + } + return nil +} + +func uint32ToBytes(value uint32) []byte { + result := make([]byte, 4) + binary.LittleEndian.PutUint32(result, value) + return result +} + +// Run runs the availability store subsystem +func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, + SubsystemToOverseer chan any) error { + av.processMessages() + return nil +} + +// Name returns the name of the availability store subsystem +func (*AvailabilityStoreSubsystem) Name() parachaintypes.SubSystemName { + return parachaintypes.AvailabilityStore +} + +func (av *AvailabilityStoreSubsystem) processMessages() { + for msg := range av.OverseerToSubSystem { + logger.Debugf("received message %v", msg) + switch msg := msg.(type) { + case QueryAvailableData: + err := av.handleQueryAvailableData(msg) + if err != nil { + logger.Errorf("failed to handle available data: %w", err) + } + case QueryDataAvailability: + err := av.handleQueryDataAvailability(msg) + if err != nil { + logger.Errorf("failed to handle query data availability: %w", err) + } + case QueryChunk: + err := av.handleQueryChunk(msg) + if err != nil { + logger.Errorf("failed to handle query chunk: %w", err) + } + case QueryChunkSize: + err := av.handleQueryChunkSize(msg) + if err != nil { + logger.Errorf("failed to handle query chunk size: %w", err) + } + case QueryAllChunks: + err := av.handleQueryAllChunks(msg) + if err != nil { + logger.Errorf("failed to handle query all chunks: %w", err) + } + case QueryChunkAvailability: + err := av.handleQueryChunkAvailability(msg) + if err != nil { + logger.Errorf("failed to handle query chunk availability: %w", err) + } + case StoreChunk: + err := av.handleStoreChunk(msg) + if err != nil { + logger.Errorf("failed to handle store chunk: %w", err) + } + case StoreAvailableData: + err := av.handleStoreAvailableData(msg) + if err != nil { + logger.Errorf("failed to handle store available data: %w", err) + } + } + } +} + +func (av *AvailabilityStoreSubsystem) handleQueryAvailableData(msg QueryAvailableData) error { + result, err := av.availabilityStore.loadAvailableData(msg.CandidateHash) + if err != nil { + msg.Sender <- AvailableData{} + return fmt.Errorf("load available data: %w", err) + } + msg.Sender <- *result + return nil +} + +func (av *AvailabilityStoreSubsystem) handleQueryDataAvailability(msg QueryDataAvailability) error { + _, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + msg.Sender <- false + return nil + } else { + return fmt.Errorf("load metadata: %w", err) + } + } + msg.Sender <- err == nil + return nil +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunk(msg QueryChunk) error { + result, err := av.availabilityStore.loadChunk(msg.CandidateHash, msg.ValidatorIndex) + if err != nil { + msg.Sender <- ErasureChunk{} + return fmt.Errorf("load chunk: %w", err) + } + msg.Sender <- *result + return nil +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunkSize(msg QueryChunkSize) error { + meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + if err != nil { + return fmt.Errorf("load metadata: %w", err) + } + var validatorIndex uint32 + for i, v := range meta.ChunksStored { + if v { + validatorIndex = uint32(i) + break + } + } + + chunk, err := av.availabilityStore.loadChunk(msg.CandidateHash, validatorIndex) + if err != nil { + return fmt.Errorf("load chunk: %w", err) + } + msg.Sender <- uint32(len(chunk.Chunk)) + return nil +} + +func (av *AvailabilityStoreSubsystem) handleQueryAllChunks(msg QueryAllChunks) error { + meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + if err != nil { + msg.Sender <- []ErasureChunk{} + return fmt.Errorf("load metadata: %w", err) + } + chunks := []ErasureChunk{} + for i, v := range meta.ChunksStored { + if v { + chunk, err := av.availabilityStore.loadChunk(msg.CandidateHash, uint32(i)) + if err != nil { + logger.Errorf("load chunk: %w", err) + } + chunks = append(chunks, *chunk) + } else { + logger.Warnf("chunk %d not stored for %v", i, msg.CandidateHash) + } + } + msg.Sender <- chunks + return nil +} + +func (av *AvailabilityStoreSubsystem) handleQueryChunkAvailability(msg QueryChunkAvailability) error { + meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + if err != nil { + msg.Sender <- false + return fmt.Errorf("load metadata: %w", err) + } + msg.Sender <- meta.ChunksStored[msg.ValidatorIndex] + return nil +} + +func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) error { + err := av.availabilityStore.storeChunk(msg.CandidateHash, msg.Chunk) + if err != nil { + msg.Sender <- err + return fmt.Errorf("store chunk: %w", err) + } + msg.Sender <- nil + return nil +} + +func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailableData) error { + err := av.availabilityStore.storeAvailableData(msg.CandidateHash, msg.AvailableData) + if err != nil { + msg.Sender <- err + return fmt.Errorf("store available data: %w", err) + } + msg.Sender <- err // TODO: determine how to replicate Rust's Result type + return nil +} diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go new file mode 100644 index 0000000000..385ad5bb08 --- /dev/null +++ b/dot/parachain/availability-store/availability_store_test.go @@ -0,0 +1,394 @@ +package availabilitystore + +import ( + "errors" + "testing" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/state" + "github.com/ChainSafe/gossamer/internal/database" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/stretchr/testify/require" +) + +var ( + testChunk1 = ErasureChunk{ + Chunk: []byte("chunk1"), + Index: 0, + Proof: []byte("proof1"), + } + testChunk2 = ErasureChunk{ + Chunk: []byte("chunk2"), + Index: 1, + Proof: []byte("proof2"), + } + testavailableData1 = AvailableData{ + PoV: parachaintypes.PoV{BlockData: []byte("blockdata")}, + ValidationData: parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{Data: []byte("parentHead")}, + }, + } +) + +func setupTestDB(t *testing.T) database.Database { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + + err := as.storeChunk(common.Hash{0x01}, testChunk1) + require.NoError(t, err) + err = as.storeChunk(common.Hash{0x01}, testChunk2) + require.NoError(t, err) + + err = as.storeAvailableData(common.Hash{0x01}, testavailableData1) + require.NoError(t, err) + + return inmemoryDB +} +func TestAvailabilityStore_StoreLoadAvailableData(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + + err := as.storeAvailableData(common.Hash{0x01}, testavailableData1) + require.NoError(t, err) + + got, err := as.loadAvailableData(common.Hash{0x01}) + require.NoError(t, err) + require.Equal(t, &testavailableData1, got) + + got, err = as.loadAvailableData(common.Hash{0x02}) + require.EqualError(t, err, "getting candidate 0x0200000000000000000000000000000000000000000000000000000000000000"+ + " from available table: pebble: not found") + var ExpectedAvailableData *AvailableData = nil + require.Equal(t, ExpectedAvailableData, got) +} + +func TestAvailabilityStore_StoreLoadChuckData(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + + err := as.storeChunk(common.Hash{0x01}, testChunk1) + require.NoError(t, err) + err = as.storeChunk(common.Hash{0x01}, testChunk2) + require.NoError(t, err) + + resultChunk, err := as.loadChunk(common.Hash{0x01}, 0) + require.NoError(t, err) + require.Equal(t, &testChunk1, resultChunk) +} + +func TestAvailabilityStoreSubsystem_handleQueryAvailableData(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + availabilityStore := NewAvailabilityStore(inmemoryDB) + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + availabilityStore: *availabilityStore, + } + + tests := map[string]struct { + msg QueryAvailableData + msgSenderChan chan AvailableData + expectedResult AvailableData + err error + }{ + "available_data_found": { + msg: QueryAvailableData{ + CandidateHash: common.Hash{0x01}, + }, + msgSenderChan: make(chan AvailableData), + expectedResult: testavailableData1, + err: nil, + }, + "available_data_not_found": { + msg: QueryAvailableData{ + CandidateHash: common.Hash{0x07}, + }, + msgSenderChan: make(chan AvailableData), + expectedResult: AvailableData{}, + err: errors.New("load available data: getting candidate" + + " 0x0700000000000000000000000000000000000000000000000000000000000000 from available table: pebble" + + ": not found"), + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt.msg.Sender = tt.msgSenderChan + + go func() { + err := availabilityStoreSubsystem.handleQueryAvailableData(tt.msg) + if tt.err == nil { + require.NoError(t, err) + } else { + require.EqualError(t, err, tt.err.Error()) + } + }() + + msgSenderChanResult := <-tt.msgSenderChan + require.Equal(t, tt.expectedResult, msgSenderChanResult) + }) + } +} + +func TestAvailabilityStoreSubsystem_handleQueryDataAvailability(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + availabilityStore := NewAvailabilityStore(inmemoryDB) + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + availabilityStore: *availabilityStore, + } + + tests := map[string]struct { + msg QueryDataAvailability + msgSenderChan chan bool + expectedResult bool + wantErr bool + }{ + "data_available_true": { + msg: QueryDataAvailability{ + CandidateHash: common.Hash{0x01}, + }, + msgSenderChan: make(chan bool), + expectedResult: true, + wantErr: false, + }, + "data_available_false": { + msg: QueryDataAvailability{ + CandidateHash: common.Hash{0x07}, + }, + msgSenderChan: make(chan bool), + expectedResult: false, + wantErr: false, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt.msg.Sender = tt.msgSenderChan + + go func() { + if err := availabilityStoreSubsystem.handleQueryDataAvailability(tt.msg); (err != nil) != tt.wantErr { + t.Errorf("handleQueryDataAvailability() error = %v, wantErr %v", err, tt.wantErr) + } + }() + + msgSenderChanResult := <-tt.msgSenderChan + require.Equal(t, tt.expectedResult, msgSenderChanResult) + }) + } +} + +func TestAvailabilityStoreSubsystem_handleQueryChunk(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + availabilityStore := NewAvailabilityStore(inmemoryDB) + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + availabilityStore: *availabilityStore, + } + + tests := map[string]struct { + msg QueryChunk + msgSenderChan chan ErasureChunk + expectedResult ErasureChunk + err error + }{ + "chunk_found": { + msg: QueryChunk{ + CandidateHash: common.Hash{0x01}, + }, + msgSenderChan: make(chan ErasureChunk), + expectedResult: testChunk1, + err: nil, + }, + "query_chunk_not_found": { + msg: QueryChunk{ + CandidateHash: common.Hash{0x07}, + }, + msgSenderChan: make(chan ErasureChunk), + expectedResult: ErasureChunk{}, + err: errors.New("load chunk: getting candidate " + + "0x0700000000000000000000000000000000000000000000000000000000000000, " + + "index 0 from chunk table: pebble: not found"), + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt.msg.Sender = tt.msgSenderChan + + go func() { + err := availabilityStoreSubsystem.handleQueryChunk(tt.msg) + if tt.err == nil { + require.NoError(t, err) + } else { + require.EqualError(t, err, tt.err.Error()) + } + }() + + msgSenderChanResult := <-tt.msgSenderChan + require.Equal(t, tt.expectedResult, msgSenderChanResult) + }) + } +} + +func TestAvailabilityStoreSubsystem_handleQueryAllChunks(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + availabilityStore := NewAvailabilityStore(inmemoryDB) + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + availabilityStore: *availabilityStore, + } + + tests := map[string]struct { + msg QueryAllChunks + msgSenderChan chan []ErasureChunk + expectedResult []ErasureChunk + err error + }{ + "chunks_found": { + msg: QueryAllChunks{ + CandidateHash: common.Hash{0x01}, + }, + msgSenderChan: make(chan []ErasureChunk), + expectedResult: []ErasureChunk{testChunk1, testChunk2}, + err: nil, + }, + "query_chunks_not_found": { + msg: QueryAllChunks{ + CandidateHash: common.Hash{0x07}, + }, + msgSenderChan: make(chan []ErasureChunk), + expectedResult: []ErasureChunk{}, + err: errors.New( + "load metadata: getting candidate 0x0700000000000000000000000000000000000000000000000000000000000000" + + " from available table: pebble: not found"), + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt.msg.Sender = tt.msgSenderChan + + go func() { + err := availabilityStoreSubsystem.handleQueryAllChunks(tt.msg) + if tt.err == nil { + require.NoError(t, err) + } else { + require.EqualError(t, err, tt.err.Error()) + } + }() + + msgSenderChanResult := <-tt.msgSenderChan + require.Equal(t, tt.expectedResult, msgSenderChanResult) + }) + } +} + +func TestAvailabilityStoreSubsystem_handleQueryChunkAvailability(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + availabilityStore := NewAvailabilityStore(inmemoryDB) + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + availabilityStore: *availabilityStore, + } + + tests := map[string]struct { + msg QueryChunkAvailability + msgSenderChan chan bool + expectedResult bool + err error + }{ + "query_chuck_availability_true": { + msg: QueryChunkAvailability{ + CandidateHash: common.Hash{0x01}, + ValidatorIndex: 0, + }, + msgSenderChan: make(chan bool), + expectedResult: true, + }, + "query_chuck_availability_false": { + msg: QueryChunkAvailability{ + CandidateHash: common.Hash{0x01}, + ValidatorIndex: 2, + }, + msgSenderChan: make(chan bool), + expectedResult: false, + }, + "query_chuck_availability_candidate_not_found_false": { + msg: QueryChunkAvailability{ + CandidateHash: common.Hash{0x07}, + ValidatorIndex: 0, + }, + msgSenderChan: make(chan bool), + expectedResult: false, + err: errors.New( + "load metadata: getting candidate 0x0700000000000000000000000000000000000000000000000000000000000000" + + " from available table: pebble: not found"), + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + + tt.msg.Sender = tt.msgSenderChan + + go func() { + err := availabilityStoreSubsystem.handleQueryChunkAvailability(tt.msg) + if tt.err == nil { + require.NoError(t, err) + } else { + require.EqualError(t, err, tt.err.Error()) + } + }() + + msgSenderChanResult := <-tt.msgSenderChan + require.Equal(t, tt.expectedResult, msgSenderChanResult) + }) + } +} + +func TestAvailabilityStore_handleStoreChunk(t *testing.T) { + inmemoryDB := setupTestDB(t) + as := NewAvailabilityStore(inmemoryDB) + asSub := AvailabilityStoreSubsystem{ + availabilityStore: *as, + } + msgSenderChan := make(chan any) + msg := StoreChunk{ + CandidateHash: common.Hash{0x01}, + Chunk: testChunk1, + Sender: msgSenderChan, + } + + go asSub.handleStoreChunk(msg) + msgSenderChanResult := <-msg.Sender + require.Equal(t, nil, msgSenderChanResult) +} + +func TestAvailabilityStore_handleStoreAvailableData(t *testing.T) { + inmemoryDB := setupTestDB(t) + as := NewAvailabilityStore(inmemoryDB) + asSub := AvailabilityStoreSubsystem{ + availabilityStore: *as, + } + msgSenderChan := make(chan any) + msg := StoreAvailableData{ + CandidateHash: common.Hash{0x01}, + NValidators: 0, + AvailableData: AvailableData{}, + ExpectedErasureRoot: common.Hash{}, + Sender: msgSenderChan, + } + + go asSub.handleStoreAvailableData(msg) + msgSenderChanResult := <-msg.Sender + require.Equal(t, nil, msgSenderChanResult) +} diff --git a/dot/parachain/availability-store/availabilitystore.go b/dot/parachain/availability-store/availabilitystore.go deleted file mode 100644 index baa228638f..0000000000 --- a/dot/parachain/availability-store/availabilitystore.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2023 ChainSafe Systems (ON) -// SPDX-License-Identifier: LGPL-3.0-only - -package availability_store - -import ( - "context" - - parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" - "github.com/ChainSafe/gossamer/internal/log" -) - -var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-availability-store")) - -type AvailabilityStoreSubsystem struct { - SubSystemToOverseer chan<- any - OverseerToSubSystem <-chan any - //db interface{} // Define the actual database type - //config Config // Define the actual config type - //pruningConfig PruningConfig - //clock Clock - //metrics Metrics -} - -func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, - SubsystemToOverseer chan any) error { - av.processMessages() - return nil -} - -func (*AvailabilityStoreSubsystem) Name() parachaintypes.SubSystemName { - return parachaintypes.AvailabilityStore -} - -func (av *AvailabilityStoreSubsystem) processMessages() { - for msg := range av.OverseerToSubSystem { - logger.Debugf("received message %v", msg) - switch msg := msg.(type) { - case QueryAvailableData: - av.handleQueryAvailableData(msg) - case QueryDataAvailability: - av.handleQueryDataAvailability(msg) - case QueryChunk: - av.handleQueryChunk(msg) - case QueryChunkSize: - av.handleQueryChunkSize(msg) - case QueryAllChunks: - av.handleQueryAllChunks(msg) - case QueryChunkAvailability: - av.handleQueryChunkAvailability(msg) - case StoreChunk: - av.handleStoreChunk(msg) - case StoreAvailableData: - av.handleStoreAvailableData(msg) - } - } -} - -func (av *AvailabilityStoreSubsystem) handleQueryAvailableData(msg QueryAvailableData) { - // TODO: handle query available data -} - -func (av *AvailabilityStoreSubsystem) handleQueryDataAvailability(msg QueryDataAvailability) { - // TODO: handle query data availability -} - -func (av *AvailabilityStoreSubsystem) handleQueryChunk(msg QueryChunk) { - // TODO: handle query chunk -} - -func (av *AvailabilityStoreSubsystem) handleQueryChunkSize(msg QueryChunkSize) { - // TODO: handle query chunk size -} - -func (av *AvailabilityStoreSubsystem) handleQueryAllChunks(msg QueryAllChunks) { - // TODO: handle query all chunks -} - -func (av *AvailabilityStoreSubsystem) handleQueryChunkAvailability(msg QueryChunkAvailability) { - // TODO: handle query chunk availability -} - -func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) { - // TODO: handle store chunk -} - -func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailableData) { - // TODO: handle store available data -} diff --git a/dot/parachain/availability-store/messages.go b/dot/parachain/availability-store/messages.go index 8524e4fd9c..e1bbab319e 100644 --- a/dot/parachain/availability-store/messages.go +++ b/dot/parachain/availability-store/messages.go @@ -1,51 +1,77 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package availability_store +package availabilitystore import ( + "time" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" ) // QueryAvailableData query a AvailableData from the AV store type QueryAvailableData struct { CandidateHash common.Hash - AvailableData AvailableData + Sender chan AvailableData } +// QueryDataAvailability query wether a `AvailableData` exists within the AV store +// +// This is useful in cases when existence +// matters, but we don't want to necessarily pass around multiple +// megabytes of data to get a single bit of information. type QueryDataAvailability struct { CandidateHash common.Hash - Sender chan AvailableData + Sender chan bool } +// ErasureChunk a chunk of erasure-encoded block data +type ErasureChunk struct { + Chunk []byte + Index uint32 + Proof []byte +} + +// QueryChunk query an `ErasureChunk` from the AV store by candidate hash and validator index type QueryChunk struct { CandidateHash common.Hash ValidatorIndex uint32 - Sender chan []byte + Sender chan ErasureChunk } +// QueryChunkSize get the size of an `ErasureChunk` from the AV store by candidate hash type QueryChunkSize struct { CandidateHash common.Hash Sender chan uint32 } +// QueryAllChunks query all chunks that we have for the given candidate hash type QueryAllChunks struct { CandidateHash common.Hash - Sender chan []byte + Sender chan []ErasureChunk } +// QueryChunkAvailability query wether a `ErasureChunk` exists within the AV store +// +// This is useful in cases when existence +// matters, but we don't want to necessarily pass around multiple +// megabytes of data to get a single bit of information. type QueryChunkAvailability struct { CandidateHash common.Hash ValidatorIndex uint32 Sender chan bool } +// StoreChunk store an `ErasureChunk` in the AV store type StoreChunk struct { CandidateHash common.Hash - Chunk []byte + Chunk ErasureChunk Sender chan any } +// StoreAvailableData store an `AvailableData` in the AV store type StoreAvailableData struct { CandidateHash common.Hash NValidators uint32 @@ -54,4 +80,65 @@ type StoreAvailableData struct { Sender chan any } -type AvailableData struct{} // Define your AvailableData type +// AvailableData is the data we keep available for each candidate included in the relay chain +type AvailableData struct { + PoV parachaintypes.PoV + ValidationData parachaintypes.PersistedValidationData +} + +// CanidataMeta information about a candidate +type CandidateMeta struct { + State State + DataAvailable bool + ChunksStored []bool +} + +// State is the state of candidate data +type State scale.VaryingDataType + +// NewState creates a new State +func NewState() State { + vdt := scale.MustNewVaryingDataType(Unavailable{}, Unfinalized{}, Finalized{}) + return State(vdt) +} + +// Unavailable candidate data was first observed at the given time but in not available in any black +type Unavailable struct { + Timestamp time.Time +} + +// Index returns the index of the varying data type +func (Unavailable) Index() uint { + return 0 +} + +// Unfinalized the candidate was first observed at the given time and was included in the given list of +// unfinalized blocks, which may be empty. The timestamp here is not used for pruning. Either +// one of these blocks will be finalized or the state will regress to `State::Unavailable`, in +// which case the same timestamp will be reused. Blocks are sorted ascending first by block +// number and then hash. candidate data was first observed at the given time and is available in at least one block +type Unfinalized struct { + Timestamp time.Time + BlockNumberHash []BlockNumberHash +} + +// Index returns the index of the varying data type +func (Unfinalized) Index() uint { + return 1 +} + +// Finalized candidate data has appeared in a finalized block and did so at the given time +type Finalized struct { + Timestamp time.Time +} + +// Index returns the index of the varying data type +func (Finalized) Index() uint { + return 2 +} + +// BlockNumberHash is a block number and hash +type BlockNumberHash struct { + blockNumber parachaintypes.BlockNumber //nolint:unused,structcheck + blockHash common.Hash //nolint:unused,structcheck +} diff --git a/dot/parachain/availability-store/register.go b/dot/parachain/availability-store/register.go index c247a77786..932945b405 100644 --- a/dot/parachain/availability-store/register.go +++ b/dot/parachain/availability-store/register.go @@ -1,12 +1,17 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only -package availability_store +package availabilitystore -func Register(overseerChan chan<- any) (*AvailabilityStoreSubsystem, error) { - availabilityStore := AvailabilityStoreSubsystem{ +import "github.com/ChainSafe/gossamer/dot/state" + +func Register(overseerChan chan<- any, st *state.Service) (*AvailabilityStoreSubsystem, error) { + availabilityStore := NewAvailabilityStore(st.DB()) + + availabilityStoreSubsystem := AvailabilityStoreSubsystem{ SubSystemToOverseer: overseerChan, + availabilityStore: *availabilityStore, } - return &availabilityStore, nil + return &availabilityStoreSubsystem, nil } diff --git a/dot/parachain/service.go b/dot/parachain/service.go index e1d4d21494..798d11999a 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -14,6 +14,7 @@ import ( "github.com/ChainSafe/gossamer/dot/parachain/overseer" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" "github.com/libp2p/go-libp2p/core/peer" @@ -32,10 +33,11 @@ type Service struct { var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) -func NewService(net Network, forkID string, genesisHash common.Hash) (*Service, error) { +func NewService(net Network, forkID string, st *state.Service) (*Service, error) { overseer := overseer.NewOverseer() + genesisHash := st.Block.GenesisHash() - availabilityStore, err := availability_store.Register(overseer.SubsystemsToOverseer) + availabilityStore, err := availability_store.Register(overseer.SubsystemsToOverseer, st) if err != nil { return nil, fmt.Errorf("registering availability store: %w", err) } diff --git a/dot/services.go b/dot/services.go index 171f81b9b6..e9633b9c06 100644 --- a/dot/services.go +++ b/dot/services.go @@ -457,9 +457,9 @@ func (nodeBuilder) createGRANDPAService(config *cfg.Config, st *state.Service, k return grandpa.NewService(gsCfg) } -func (nodeBuilder) createParachainHostService(net *network.Service, forkID string, genesisHash common.Hash) ( +func (nodeBuilder) createParachainHostService(net *network.Service, forkID string, st *state.Service) ( *parachain.Service, error) { - return parachain.NewService(net, forkID, genesisHash) + return parachain.NewService(net, forkID, st) } func (nodeBuilder) createBlockVerifier(st *state.Service) *babe.VerificationManager { From fedb9108907df03937206a9078e7873e1b2f23df Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Tue, 21 Nov 2023 13:52:45 +0530 Subject: [PATCH 66/85] feat(parachain/collator): handle Seconded overseer message (#3557) `Seconded` is a type of statement for a candidate. Seconded implies `Valid` (also a type of statement). Collator protocol could receive `Seconded` message from backing subsystem for a particular candidate. On receiving this message we would - stop tracking its candidate for backing - reward the collator that sent us this collation - sent out a gossip message in the collator protocol peerset that this candidate was seconded --- .../collator-protocol/validator_side.go | 126 +++++++- .../collator-protocol/validator_side_test.go | 284 ++++++++++++++++++ dot/parachain/statement_fetching.go | 2 +- dot/parachain/types/types.go | 20 ++ 4 files changed, 422 insertions(+), 10 deletions(-) create mode 100644 dot/parachain/collator-protocol/validator_side_test.go diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index f9d33ccfc7..e0fcb4d28d 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -306,9 +306,6 @@ type CollatorProtocolValidatorSide struct { ourView View - // Keep track of all pending candidate collations - pendingCandidates map[common.Hash]CollationEvent - // Parachains we're currently assigned to. With async backing enabled // this includes assignments from the implicit view. currentAssignments map[parachaintypes.ParaID]uint @@ -335,6 +332,25 @@ type CollatorProtocolValidatorSide struct { /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. activeLeaves map[common.Hash]ProspectiveParachainsMode + + // Collations that we have successfully requested from peers and waiting + // on validation. + fetchedCandidates map[string]CollationEvent +} + +// Identifier of a fetched collation +type fetchedCollationInfo struct { + // Candidate's relay parent + relayParent common.Hash + paraID parachaintypes.ParaID + candidateHash parachaintypes.CandidateHash + // Id of the collator the collation was fetched from + collatorID parachaintypes.CollatorID +} + +func (f fetchedCollationInfo) String() string { + return fmt.Sprintf("relay parent: %s, para id: %d, candidate hash: %s, collator id: %+v", + f.relayParent.String(), f.paraID, f.candidateHash.Value.String(), f.collatorID) } // Prospective parachains mode of a relay parent. Defined by @@ -403,9 +419,10 @@ type NetworkBridgeUpdate struct { // TODO: not quite sure if we would need this or something similar to this } +// SecondedOverseerMsg represents that the candidate we recommended to be seconded was validated successfully. type SecondedOverseerMsg struct { Parent common.Hash - Stmt parachaintypes.StatementVDT + Stmt parachaintypes.UncheckedSignedFullStatement } type Backed struct { @@ -414,12 +431,14 @@ type Backed struct { ParaHead common.Hash } +// InvalidOverseerMsg represents an invalid candidata. +// We recommended a particular candidate to be seconded, but it was invalid; penalise the collator. type InvalidOverseerMsg struct { Parent common.Hash CandidateReceipt parachaintypes.CandidateReceipt } -func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error { +func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // run this function as a goroutine, ideally switch msg := msg.(type) { @@ -440,21 +459,95 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error // TODO: handle network message https://github.com/ChainSafe/gossamer/issues/3515 // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1457 //nolint case SecondedOverseerMsg: - // TODO: handle seconded message https://github.com/ChainSafe/gossamer/issues/3516 - // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1466 //nolint + statementV, err := msg.Stmt.Payload.Value() + if err != nil { + return fmt.Errorf("getting value of statement: %w", err) + } + if statementV.Index() != 1 { + return fmt.Errorf("expected a seconded statement") + } + + receipt, ok := statementV.(parachaintypes.Seconded) + if !ok { + return fmt.Errorf("statement value expected: Seconded, got: %T", statementV) + } + + candidateReceipt := parachaintypes.CommittedCandidateReceipt(receipt) + + fetchedCollation, err := newFetchedCollationInfo(candidateReceipt.ToPlain()) + if err != nil { + return fmt.Errorf("getting fetched collation info: %w", err) + } + + // remove the candidate from the list of fetched candidates + collationEvent, ok := cpvs.fetchedCandidates[fetchedCollation.String()] + if !ok { + logger.Error("collation has been seconded, but the relay parent is deactivated") + return nil + } + + delete(cpvs.fetchedCandidates, fetchedCollation.String()) + + // notify good collation + peerID, ok := cpvs.getPeerIDFromCollatorID(collationEvent.CollatorId) + if !ok { + return ErrPeerIDNotFoundForCollator + } + cpvs.net.ReportPeer(peerset.ReputationChange{ + Value: peerset.BenefitNotifyGoodValue, + Reason: peerset.BenefitNotifyGoodReason, + }, peerID) + + // notify collation seconded + _, ok = cpvs.peerData[peerID] + if ok { + collatorProtocolMessage := NewCollatorProtocolMessage() + err = collatorProtocolMessage.Set(CollationSeconded{ + RelayParent: msg.Parent, + Statement: msg.Stmt, + }) + if err != nil { + return fmt.Errorf("setting collation seconded: %w", err) + } + collationMessage := NewCollationProtocol() + + err = collationMessage.Set(collatorProtocolMessage) + if err != nil { + return fmt.Errorf("setting collation message: %w", err) + } + + err = cpvs.net.SendMessage(peerID, &collationMessage) + if err != nil { + return fmt.Errorf("sending collation message: %w", err) + } + perRelayParent, ok := cpvs.perRelayParent[msg.Parent] + if ok { + perRelayParent.collations.status = Seconded + perRelayParent.collations.secondedCount++ + cpvs.perRelayParent[msg.Parent] = perRelayParent + } + + // TODO: Few more things for async backing, but we don't have async backing yet + // https://github.com/paritytech/polkadot-sdk/blob/7035034710ecb9c6a786284e5f771364c520598d/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1531-L1532 + } case Backed: // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 case InvalidOverseerMsg: invalidOverseerMsg := msg - collationEvent, ok := cpvs.pendingCandidates[invalidOverseerMsg.Parent] + fetchedCollation, err := newFetchedCollationInfo(msg.CandidateReceipt) + if err != nil { + return fmt.Errorf("getting fetched collation info: %w", err) + } + + collationEvent, ok := cpvs.fetchedCandidates[fetchedCollation.String()] if !ok { return nil } if *collationEvent.PendingCollation.CommitmentHash == (invalidOverseerMsg.CandidateReceipt.CommitmentsHash) { - delete(cpvs.pendingCandidates, invalidOverseerMsg.Parent) + delete(cpvs.fetchedCandidates, fetchedCollation.String()) } else { logger.Error("reported invalid candidate for unknown `pending_candidate`") return nil @@ -474,3 +567,18 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg interface{}) error return nil } + +func newFetchedCollationInfo(candidateReceipt parachaintypes.CandidateReceipt) (*fetchedCollationInfo, error) { + candidateHash, err := candidateReceipt.Hash() + if err != nil { + return nil, fmt.Errorf("getting candidate hash: %w", err) + } + return &fetchedCollationInfo{ + paraID: parachaintypes.ParaID(candidateReceipt.Descriptor.ParaID), + relayParent: candidateReceipt.Descriptor.RelayParent, + collatorID: candidateReceipt.Descriptor.Collator, + candidateHash: parachaintypes.CandidateHash{ + Value: candidateHash, + }, + }, nil +} diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go new file mode 100644 index 0000000000..eb75df028a --- /dev/null +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -0,0 +1,284 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +import ( + "testing" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/dot/peerset" + "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/require" +) + +func TestProcessOverseerMessage(t *testing.T) { + t.Parallel() + + var testCollatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(testCollatorID[:], tempCollatID) + peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + + commitments := parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + } + + testCandidateReceipt := parachaintypes.CandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: uint32(1000), + RelayParent: common.MustHexToHash("0xded542bacb3ca6c033a57676f94ae7c8f36834511deb44e3164256fd3b1c0de0"), //nolint:lll + Collator: testCollatorID, + PersistedValidationDataHash: common.MustHexToHash("0x690d8f252ef66ab0f969c3f518f90012b849aa5ac94e1752c5e5ae5a8996de37"), //nolint:lll + PovHash: common.MustHexToHash("0xe7df1126ac4b4f0fb1bc00367a12ec26ca7c51256735a5e11beecdc1e3eca274"), //nolint:lll + ErasureRoot: common.MustHexToHash("0xc07f658163e93c45a6f0288d229698f09c1252e41076f4caa71c8cbc12f118a1"), //nolint:lll + ParaHead: common.MustHexToHash("0x9a8a7107426ef873ab89fc8af390ec36bdb2f744a9ff71ad7f18a12d55a7f4f5"), //nolint:lll + }, + + CommitmentsHash: commitments.Hash(), + } + + testCases := []struct { + description string + msg any + peerData map[peer.ID]PeerData + net Network + fetchedCandidates map[string]CollationEvent + deletesFetchCandidate bool + errString string + }{ + { + description: "CollateOn message fails with message not expected", + msg: CollateOn(2), + errString: ErrNotExpectedOnValidatorSide.Error(), + }, + { + description: "DistributeCollation message fails with message not expected", + msg: DistributeCollation{}, + errString: ErrNotExpectedOnValidatorSide.Error(), + }, + { + description: "ReportCollator message fails with peer not found for collator", + msg: ReportCollator(testCollatorID), + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "ReportCollator message succeeds and reports a bad collator", + msg: ReportCollator(testCollatorID), + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + + return net + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + errString: "", + }, + { + description: "InvalidOverseerMsg message fails with peer not found for collator", + msg: InvalidOverseerMsg{ + Parent: testRelayParent, + CandidateReceipt: testCandidateReceipt, + }, + fetchedCandidates: func() map[string]CollationEvent { + fetchedCollation, err := newFetchedCollationInfo(testCandidateReceipt) + require.NoError(t, err) + + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + deletesFetchCandidate: true, + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "InvalidOverseerMsg message succeeds, reports a bad collator and removes fetchedCandidate", + msg: InvalidOverseerMsg{ + Parent: testRelayParent, + CandidateReceipt: testCandidateReceipt, + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.ReportBadCollatorValue, + Reason: peerset.ReportBadCollatorReason, + }, peerID) + + return net + }(), + fetchedCandidates: func() map[string]CollationEvent { + fetchedCollation, err := newFetchedCollationInfo(testCandidateReceipt) + require.NoError(t, err) + + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + deletesFetchCandidate: true, + errString: "", + }, + { + description: "SecondedOverseerMsg message fails with peer not found for collator and removes fetchedCandidate", + msg: SecondedOverseerMsg{ + Parent: testRelayParent, + Stmt: func() parachaintypes.UncheckedSignedFullStatement { + vdt := parachaintypes.NewStatementVDT() + vdt.Set(parachaintypes.Seconded( + parachaintypes.CommittedCandidateReceipt{ + Descriptor: testCandidateReceipt.Descriptor, + Commitments: commitments, + }, + )) + return parachaintypes.UncheckedSignedFullStatement{ + Payload: vdt, + } + }(), + }, + fetchedCandidates: func() map[string]CollationEvent { + fetchedCollation, err := newFetchedCollationInfo(testCandidateReceipt) + require.NoError(t, err) + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + deletesFetchCandidate: true, + errString: ErrPeerIDNotFoundForCollator.Error(), + }, + { + description: "SecondedOverseerMsg message succceds, reports a good collator and removes fetchedCandidate", + msg: SecondedOverseerMsg{ + Parent: testRelayParent, + Stmt: func() parachaintypes.UncheckedSignedFullStatement { + vdt := parachaintypes.NewStatementVDT() + vdt.Set(parachaintypes.Seconded( + parachaintypes.CommittedCandidateReceipt{ + Descriptor: testCandidateReceipt.Descriptor, + Commitments: commitments, + }, + )) + return parachaintypes.UncheckedSignedFullStatement{ + Payload: vdt, + } + }(), + }, + net: func() Network { + ctrl := gomock.NewController(t) + net := NewMockNetwork(ctrl) + net.EXPECT().ReportPeer(peerset.ReputationChange{ + Value: peerset.BenefitNotifyGoodValue, + Reason: peerset.BenefitNotifyGoodReason, + }, peerID) + + net.EXPECT().SendMessage(peerID, gomock.AssignableToTypeOf(&CollationProtocol{})) + + return net + }(), + fetchedCandidates: func() map[string]CollationEvent { + fetchedCollation, err := newFetchedCollationInfo(testCandidateReceipt) + require.NoError(t, err) + return map[string]CollationEvent{ + fetchedCollation.String(): { + CollatorId: testCandidateReceipt.Descriptor.Collator, + PendingCollation: PendingCollation{ + CommitmentHash: &testCandidateReceipt.CommitmentsHash, + }, + }, + } + }(), + peerData: map[peer.ID]PeerData{ + peerID: { + view: View{}, + state: PeerStateInfo{ + PeerState: Collating, + CollatingPeerState: CollatingPeerState{ + CollatorID: testCollatorID, + ParaID: parachaintypes.ParaID(6), + }, + }, + }, + }, + deletesFetchCandidate: true, + errString: "", + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + cpvs := CollatorProtocolValidatorSide{ + net: c.net, + // perRelayParent: c.perRelayParent, + fetchedCandidates: c.fetchedCandidates, + peerData: c.peerData, + // activeLeaves: c.activeLeaves, + } + + lenFetchedCandidatesBefore := len(cpvs.fetchedCandidates) + + err := cpvs.processMessage(c.msg) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + if c.deletesFetchCandidate { + require.Equal(t, lenFetchedCandidatesBefore-1, len(cpvs.fetchedCandidates)) + } else { + require.Equal(t, lenFetchedCandidatesBefore, len(cpvs.fetchedCandidates)) + } + }) + } +} diff --git a/dot/parachain/statement_fetching.go b/dot/parachain/statement_fetching.go index f1c40e3417..5c8fb80c4e 100644 --- a/dot/parachain/statement_fetching.go +++ b/dot/parachain/statement_fetching.go @@ -16,7 +16,7 @@ type StatementFetchingRequest struct { // Data needed to locate and identify the needed statement. RelayParent common.Hash `scale:"1"` - // Hash of candidate that was used create the `CommitedCandidateRecept`. + // Hash of candidate that was used create the `CommitedCandidateReceipt`. CandidateHash parachaintypes.CandidateHash `scale:"2"` } diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index 52978023c8..e95bd9c273 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -241,6 +241,10 @@ type CandidateCommitments struct { HrmpWatermark uint32 `scale:"6"` } +func (cc CandidateCommitments) Hash() common.Hash { + return common.MustBlake2bHash(scale.MustMarshal(cc)) +} + // SessionIndex is a session index. type SessionIndex uint32 @@ -252,6 +256,13 @@ type CommittedCandidateReceipt struct { Commitments CandidateCommitments `scale:"2"` } +func (ccr CommittedCandidateReceipt) ToPlain() CandidateReceipt { + return CandidateReceipt{ + Descriptor: ccr.Descriptor, + CommitmentsHash: ccr.Commitments.Hash(), + } +} + // AssignmentID The public key of a keypair used by a validator for determining assignments // to approve included parachain candidates. type AssignmentID [sr25519.PublicKeyLength]byte @@ -323,6 +334,15 @@ type CandidateReceipt struct { CommitmentsHash common.Hash `scale:"2"` } +func (cr CandidateReceipt) Hash() (common.Hash, error) { + bytes, err := scale.Marshal(cr) + if err != nil { + return common.Hash{}, fmt.Errorf("marshalling CommittedCandidateReceipt: %w", err) + } + + return common.Blake2bHash(bytes) +} + // HeadData Parachain head data included in the chain. type HeadData struct { Data []byte `scale:"1"` From a0dcc9149dab1720afc98331c54354aafd1d0302 Mon Sep 17 00:00:00 2001 From: Kishan Sagathiya Date: Tue, 9 Jan 2024 18:52:40 +0530 Subject: [PATCH 67/85] feat(dot/parachain): added overseer signals (#3638) - Added ActiveLeavesUpdate and BlockFinalized overseer signals. - Connected parachain service with a block state. Using this block state parachain service would be notified when a new relay chain block gets imported or finalized. - On receiving these signals overseer would update its active leaves and broadcast an ActiveLeavesUpdate or/and BlockFinalized signal to all subsystems - Added `ProcessActiveLeavesUpdateSignal` and `ProcessBlockFinalizedSignal` method to the subsystem interface, all subsystems will have to implement this method based on their requirements to react to these signals --- .../availability-store/availability_store.go | 125 ++++++++----- .../availability_store_test.go | 3 + dot/parachain/backing/candidate_backing.go | 72 +++++--- .../collator-protocol/validator_side.go | 29 ++- dot/parachain/overseer/mocks_generate_test.go | 6 + dot/parachain/overseer/mocks_test.go | 87 +++++++++ dot/parachain/overseer/overseer.go | 133 ++++++++++++-- dot/parachain/overseer/overseer_test.go | 167 +++++++----------- dot/parachain/overseer/types.go | 17 +- dot/parachain/service.go | 4 +- dot/parachain/types/overseer_signals.go | 30 ++++ 11 files changed, 474 insertions(+), 199 deletions(-) create mode 100644 dot/parachain/overseer/mocks_generate_test.go create mode 100644 dot/parachain/overseer/mocks_test.go create mode 100644 dot/parachain/types/overseer_signals.go diff --git a/dot/parachain/availability-store/availability_store.go b/dot/parachain/availability-store/availability_store.go index d19e31f230..0cb7f70315 100644 --- a/dot/parachain/availability-store/availability_store.go +++ b/dot/parachain/availability-store/availability_store.go @@ -9,6 +9,7 @@ import ( "encoding/json" "errors" "fmt" + "sync" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/database" @@ -28,6 +29,10 @@ const ( // AvailabilityStoreSubsystem is the struct that holds subsystem data for the availability store type AvailabilityStoreSubsystem struct { + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + SubSystemToOverseer chan<- any OverseerToSubSystem <-chan any availabilityStore AvailabilityStore @@ -170,7 +175,10 @@ func uint32ToBytes(value uint32) []byte { // Run runs the availability store subsystem func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, SubsystemToOverseer chan any) error { - av.processMessages() + + av.wg.Add(1) + go av.processMessages() + return nil } @@ -180,53 +188,81 @@ func (*AvailabilityStoreSubsystem) Name() parachaintypes.SubSystemName { } func (av *AvailabilityStoreSubsystem) processMessages() { - for msg := range av.OverseerToSubSystem { - logger.Debugf("received message %v", msg) - switch msg := msg.(type) { - case QueryAvailableData: - err := av.handleQueryAvailableData(msg) - if err != nil { - logger.Errorf("failed to handle available data: %w", err) - } - case QueryDataAvailability: - err := av.handleQueryDataAvailability(msg) - if err != nil { - logger.Errorf("failed to handle query data availability: %w", err) - } - case QueryChunk: - err := av.handleQueryChunk(msg) - if err != nil { - logger.Errorf("failed to handle query chunk: %w", err) - } - case QueryChunkSize: - err := av.handleQueryChunkSize(msg) - if err != nil { - logger.Errorf("failed to handle query chunk size: %w", err) - } - case QueryAllChunks: - err := av.handleQueryAllChunks(msg) - if err != nil { - logger.Errorf("failed to handle query all chunks: %w", err) - } - case QueryChunkAvailability: - err := av.handleQueryChunkAvailability(msg) - if err != nil { - logger.Errorf("failed to handle query chunk availability: %w", err) - } - case StoreChunk: - err := av.handleStoreChunk(msg) - if err != nil { - logger.Errorf("failed to handle store chunk: %w", err) + for { + select { + case msg := <-av.OverseerToSubSystem: + logger.Debugf("received message %v", msg) + switch msg := msg.(type) { + case QueryAvailableData: + err := av.handleQueryAvailableData(msg) + if err != nil { + logger.Errorf("failed to handle available data: %w", err) + } + case QueryDataAvailability: + err := av.handleQueryDataAvailability(msg) + if err != nil { + logger.Errorf("failed to handle query data availability: %w", err) + } + case QueryChunk: + err := av.handleQueryChunk(msg) + if err != nil { + logger.Errorf("failed to handle query chunk: %w", err) + } + case QueryChunkSize: + err := av.handleQueryChunkSize(msg) + if err != nil { + logger.Errorf("failed to handle query chunk size: %w", err) + } + case QueryAllChunks: + err := av.handleQueryAllChunks(msg) + if err != nil { + logger.Errorf("failed to handle query all chunks: %w", err) + } + case QueryChunkAvailability: + err := av.handleQueryChunkAvailability(msg) + if err != nil { + logger.Errorf("failed to handle query chunk availability: %w", err) + } + case StoreChunk: + err := av.handleStoreChunk(msg) + if err != nil { + logger.Errorf("failed to handle store chunk: %w", err) + } + case StoreAvailableData: + err := av.handleStoreAvailableData(msg) + if err != nil { + logger.Errorf("failed to handle store available data: %w", err) + } + + case parachaintypes.ActiveLeavesUpdateSignal: + av.ProcessActiveLeavesUpdateSignal() + + case parachaintypes.BlockFinalizedSignal: + av.ProcessBlockFinalizedSignal() + + default: + logger.Error(parachaintypes.ErrUnknownOverseerMessage.Error()) } - case StoreAvailableData: - err := av.handleStoreAvailableData(msg) - if err != nil { - logger.Errorf("failed to handle store available data: %w", err) + + case <-av.ctx.Done(): + if err := av.ctx.Err(); err != nil { + logger.Errorf("ctx error: %v\n", err) } + av.wg.Done() + return } + } } +func (av *AvailabilityStoreSubsystem) ProcessActiveLeavesUpdateSignal() { + // TODO: #3630 +} + +func (av *AvailabilityStoreSubsystem) ProcessBlockFinalizedSignal() { + // TODO: #3630 +} + func (av *AvailabilityStoreSubsystem) handleQueryAvailableData(msg QueryAvailableData) error { result, err := av.availabilityStore.loadAvailableData(msg.CandidateHash) if err != nil { @@ -333,3 +369,8 @@ func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailabl msg.Sender <- err // TODO: determine how to replicate Rust's Result type return nil } + +func (av *AvailabilityStoreSubsystem) Stop() { + av.cancel() + av.wg.Wait() +} diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go index 385ad5bb08..f0b85625cf 100644 --- a/dot/parachain/availability-store/availability_store_test.go +++ b/dot/parachain/availability-store/availability_store_test.go @@ -1,3 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package availabilitystore import ( diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index f18dcb67f1..dcf5e4febd 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -5,6 +5,7 @@ package backing import ( "context" + "sync" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" @@ -14,6 +15,10 @@ import ( var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-candidate-backing")) type CandidateBacking struct { + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + SubSystemToOverseer chan<- any OverseerToSubSystem <-chan any } @@ -72,7 +77,10 @@ func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan an // other backing related overseer message. // This would become more clear after we complete processMessages function. It would give us clarity // if we need background_validation_rx or background_validation_tx, as done in rust. - cb.processMessages() + + cb.wg.Add(1) + go cb.processMessages() + return nil } @@ -80,31 +88,48 @@ func (*CandidateBacking) Name() parachaintypes.SubSystemName { return parachaintypes.CandidateBacking } +func (cb *CandidateBacking) ProcessActiveLeavesUpdateSignal() { + // TODO #3644 +} + +func (cb *CandidateBacking) ProcessBlockFinalizedSignal() { + // TODO #3644 +} + func (cb *CandidateBacking) processMessages() { - for msg := range cb.OverseerToSubSystem { - // process these received messages by referencing - // https://github.com/paritytech/polkadot-sdk/blob/769bdd3ff33a291cbc70a800a3830638467e42a2/polkadot/node/core/backing/src/lib.rs#L741 - switch msg.(type) { - case ActiveLeavesUpdate: - cb.handleActiveLeavesUpdate() - case GetBackedCandidates: - cb.handleGetBackedCandidates() - case CanSecond: - cb.handleCanSecond() - case Second: - cb.handleSecond() - case Statement: - cb.handleStatement() - default: - logger.Error("unknown message type") + for { + select { + case msg := <-cb.OverseerToSubSystem: + // process these received messages by referencing + // https://github.com/paritytech/polkadot-sdk/blob/769bdd3ff33a291cbc70a800a3830638467e42a2/polkadot/node/core/backing/src/lib.rs#L741 + switch msg.(type) { + case GetBackedCandidates: + cb.handleGetBackedCandidates() + case CanSecond: + cb.handleCanSecond() + case Second: + cb.handleSecond() + case Statement: + cb.handleStatement() + case parachaintypes.ActiveLeavesUpdateSignal: + cb.ProcessActiveLeavesUpdateSignal() + case parachaintypes.BlockFinalizedSignal: + cb.ProcessBlockFinalizedSignal() + default: + logger.Error(parachaintypes.ErrUnknownOverseerMessage.Error()) + return + } + + case <-cb.ctx.Done(): + if err := cb.ctx.Err(); err != nil { + logger.Errorf("ctx error: %v\n", err) + } + cb.wg.Done() + return } } } -func (cb *CandidateBacking) handleActiveLeavesUpdate() { - // TODO: Implement this #3503 -} - func (cb *CandidateBacking) handleGetBackedCandidates() { // TODO: Implement this #3504 } @@ -121,6 +146,11 @@ func (cb *CandidateBacking) handleStatement() { // TODO: Implement this #3507 } +func (cb *CandidateBacking) Stop() { + cb.cancel() + cb.wg.Wait() +} + // SignedFullStatementWithPVD represents a signed full statement along with associated Persisted Validation Data (PVD). type SignedFullStatementWithPVD struct { SignedFullStatement parachaintypes.UncheckedSignedFullStatement diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index e0fcb4d28d..d1feb167f8 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -29,7 +29,6 @@ const ( var ( ErrUnexpectedMessageOnCollationProtocol = errors.New("unexpected message on collation protocol") ErrUnknownPeer = errors.New("unknown peer") - ErrUnknownOverseerMessage = errors.New("unknown overseer message type") ErrNotExpectedOnValidatorSide = errors.New("message is not expected on the validator side of the protocol") ErrCollationNotInView = errors.New("collation is not in our view") ErrPeerIDNotFoundForCollator = errors.New("peer id not found for collator") @@ -83,6 +82,11 @@ func (cpvs CollatorProtocolValidatorSide) Run( cpvs.fetchedCollations = append(cpvs.fetchedCollations, *collation) } + case <-cpvs.ctx.Done(): + if err := cpvs.ctx.Err(); err != nil { + logger.Errorf("ctx error: %v\n", err) + } + return nil } } } @@ -91,6 +95,18 @@ func (CollatorProtocolValidatorSide) Name() parachaintypes.SubSystemName { return parachaintypes.CollationProtocol } +func (cpvs CollatorProtocolValidatorSide) ProcessActiveLeavesUpdateSignal() { + // NOTE: nothing to do here +} + +func (cpvs CollatorProtocolValidatorSide) ProcessBlockFinalizedSignal() { + // NOTE: nothing to do here +} + +func (cpvs CollatorProtocolValidatorSide) Stop() { + cpvs.cancel() +} + // requestCollation requests a collation from the network. // This function will // - check for duplicate requests @@ -291,6 +307,9 @@ type CollationEvent struct { } type CollatorProtocolValidatorSide struct { + ctx context.Context + cancel context.CancelFunc + net Network SubSystemToOverseer chan<- any @@ -561,8 +580,14 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { Value: peerset.ReportBadCollatorValue, Reason: peerset.ReportBadCollatorReason, }, peerID) + + case parachaintypes.ActiveLeavesUpdateSignal: + cpvs.ProcessActiveLeavesUpdateSignal() + case parachaintypes.BlockFinalizedSignal: + cpvs.ProcessBlockFinalizedSignal() + default: - return ErrUnknownOverseerMessage + return parachaintypes.ErrUnknownOverseerMessage } return nil diff --git a/dot/parachain/overseer/mocks_generate_test.go b/dot/parachain/overseer/mocks_generate_test.go new file mode 100644 index 0000000000..38ea06d27e --- /dev/null +++ b/dot/parachain/overseer/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package overseer + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . BlockState diff --git a/dot/parachain/overseer/mocks_test.go b/dot/parachain/overseer/mocks_test.go new file mode 100644 index 0000000000..e6e7b08d83 --- /dev/null +++ b/dot/parachain/overseer/mocks_test.go @@ -0,0 +1,87 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/overseer (interfaces: BlockState) + +// Package overseer is a generated GoMock package. +package overseer + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + gomock "github.com/golang/mock/gomock" +) + +// MockBlockState is a mock of BlockState interface. +type MockBlockState struct { + ctrl *gomock.Controller + recorder *MockBlockStateMockRecorder +} + +// MockBlockStateMockRecorder is the mock recorder for MockBlockState. +type MockBlockStateMockRecorder struct { + mock *MockBlockState +} + +// NewMockBlockState creates a new mock instance. +func NewMockBlockState(ctrl *gomock.Controller) *MockBlockState { + mock := &MockBlockState{ctrl: ctrl} + mock.recorder = &MockBlockStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockState) EXPECT() *MockBlockStateMockRecorder { + return m.recorder +} + +// FreeFinalisedNotifierChannel mocks base method. +func (m *MockBlockState) FreeFinalisedNotifierChannel(arg0 chan *types.FinalisationInfo) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeFinalisedNotifierChannel", arg0) +} + +// FreeFinalisedNotifierChannel indicates an expected call of FreeFinalisedNotifierChannel. +func (mr *MockBlockStateMockRecorder) FreeFinalisedNotifierChannel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeFinalisedNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).FreeFinalisedNotifierChannel), arg0) +} + +// FreeImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) FreeImportedBlockNotifierChannel(arg0 chan *types.Block) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeImportedBlockNotifierChannel", arg0) +} + +// FreeImportedBlockNotifierChannel indicates an expected call of FreeImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) FreeImportedBlockNotifierChannel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).FreeImportedBlockNotifierChannel), arg0) +} + +// GetFinalisedNotifierChannel mocks base method. +func (m *MockBlockState) GetFinalisedNotifierChannel() chan *types.FinalisationInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFinalisedNotifierChannel") + ret0, _ := ret[0].(chan *types.FinalisationInfo) + return ret0 +} + +// GetFinalisedNotifierChannel indicates an expected call of GetFinalisedNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetFinalisedNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalisedNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetFinalisedNotifierChannel)) +} + +// GetImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) GetImportedBlockNotifierChannel() chan *types.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImportedBlockNotifierChannel") + ret0, _ := ret[0].(chan *types.Block) + return ret0 +} + +// GetImportedBlockNotifierChannel indicates an expected call of GetImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetImportedBlockNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetImportedBlockNotifierChannel)) +} diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index dd112c6868..9d24727028 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -12,6 +12,8 @@ import ( availability_store "github.com/ChainSafe/gossamer/dot/parachain/availability-store" "github.com/ChainSafe/gossamer/dot/parachain/backing" collatorprotocol "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol" + "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" @@ -22,22 +24,41 @@ var ( ) type Overseer struct { - ctx context.Context - cancel context.CancelFunc - errChan chan error // channel for overseer to send errors to service that started it + ctx context.Context + cancel context.CancelFunc + errChan chan error // channel for overseer to send errors to service that started it + + blockState BlockState + activeLeaves map[common.Hash]uint32 + + // block notification channels + imported chan *types.Block + finalised chan *types.FinalisationInfo + SubsystemsToOverseer chan any subsystems map[Subsystem]chan any // map[Subsystem]OverseerToSubSystem channel nameToSubsystem map[parachaintypes.SubSystemName]Subsystem wg sync.WaitGroup } -func NewOverseer() *Overseer { +// BlockState interface for block state methods +type BlockState interface { + GetImportedBlockNotifierChannel() chan *types.Block + FreeImportedBlockNotifierChannel(ch chan *types.Block) + GetFinalisedNotifierChannel() chan *types.FinalisationInfo + FreeFinalisedNotifierChannel(ch chan *types.FinalisationInfo) +} + +func NewOverseer(blockState BlockState) *Overseer { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) + return &Overseer{ ctx: ctx, cancel: cancel, errChan: make(chan error), + blockState: blockState, + activeLeaves: make(map[common.Hash]uint32), SubsystemsToOverseer: make(chan any), subsystems: make(map[Subsystem]chan any), nameToSubsystem: make(map[parachaintypes.SubSystemName]Subsystem), @@ -55,6 +76,13 @@ func (o *Overseer) RegisterSubsystem(subsystem Subsystem) chan any { } func (o *Overseer) Start() error { + + imported := o.blockState.GetImportedBlockNotifierChannel() + finalised := o.blockState.GetFinalisedNotifierChannel() + + o.imported = imported + o.finalised = finalised + // start subsystems for subsystem, overseerToSubSystem := range o.subsystems { o.wg.Add(1) @@ -68,10 +96,10 @@ func (o *Overseer) Start() error { }(subsystem, overseerToSubSystem) } - o.wg.Add(1) + o.wg.Add(2) go o.processMessages() + go o.handleBlockEvents() - // TODO: add logic to start listening for Block Imported events and Finalisation events return nil } @@ -109,15 +137,101 @@ func (o *Overseer) processMessages() { if err := o.ctx.Err(); err != nil { logger.Errorf("ctx error: %v\n", err) } - logger.Info("overseer stopping") + o.wg.Done() return } } } +func (o *Overseer) handleBlockEvents() { + for { + select { + case <-o.ctx.Done(): + if err := o.ctx.Err(); err != nil { + logger.Errorf("ctx error: %v\n", err) + } + o.wg.Done() + return + case imported := <-o.imported: + blockNumber, ok := o.activeLeaves[imported.Header.Hash()] + if ok { + if blockNumber != uint32(imported.Header.Number) { + panic("block number mismatch") + } + return + } + + o.activeLeaves[imported.Header.Hash()] = uint32(imported.Header.Number) + delete(o.activeLeaves, imported.Header.ParentHash) + + // TODO: + /* + - Add active leaf only if given head supports parachain consensus. + - You do that by checking the parachain host runtime api version. + - If the parachain host runtime api version is at least 1, then the parachain consensus is supported. + + #[async_trait::async_trait] + impl HeadSupportsParachains for Arc + where + Client: RuntimeApiSubsystemClient + Sync + Send, + { + async fn head_supports_parachains(&self, head: &Hash) -> bool { + // Check that the `ParachainHost` runtime api is at least with version 1 present on chain. + self.api_version_parachain_host(*head).await.ok().flatten().unwrap_or(0) >= 1 + } + } + + */ + activeLeavesUpdate := parachaintypes.ActiveLeavesUpdateSignal{ + Activated: ¶chaintypes.ActivatedLeaf{ + Hash: imported.Header.Hash(), + Number: uint32(imported.Header.Number), + }, + Deactivated: []common.Hash{imported.Header.ParentHash}, + } + + o.broadcast(activeLeavesUpdate) + + case finalised := <-o.finalised: + deactivated := make([]common.Hash, 0) + + for hash, blockNumber := range o.activeLeaves { + if blockNumber <= uint32(finalised.Header.Number) && hash != finalised.Header.Hash() { + deactivated = append(deactivated, hash) + delete(o.activeLeaves, hash) + } + } + + o.broadcast(parachaintypes.BlockFinalizedSignal{ + Hash: finalised.Header.Hash(), + BlockNumber: uint32(finalised.Header.Number), + }) + + // If there are no leaves being deactivated, we don't need to send an update. + // + // Our peers will be informed about our finalized block the next time we + // activating/deactivating some leaf. + if len(deactivated) > 0 { + o.broadcast(parachaintypes.ActiveLeavesUpdateSignal{ + Deactivated: deactivated, + }) + } + } + } +} + +func (o *Overseer) broadcast(msg any) { + for _, overseerToSubSystem := range o.subsystems { + overseerToSubSystem <- msg + } +} + func (o *Overseer) Stop() error { o.cancel() + o.blockState.FreeImportedBlockNotifierChannel(o.imported) + o.blockState.FreeFinalisedNotifierChannel(o.finalised) + // close the errorChan to unblock any listeners on the errChan close(o.errChan) @@ -133,11 +247,6 @@ func (o *Overseer) Stop() error { return nil } -// sendActiveLeavesUpdate sends an ActiveLeavesUpdate to the subsystem -func (o *Overseer) sendActiveLeavesUpdate(update ActiveLeavesUpdate, subsystem Subsystem) { - o.subsystems[subsystem] <- update -} - func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) (timeouted bool) { c := make(chan struct{}) go func() { diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index 81ccb745d0..acc1788d58 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -7,10 +7,13 @@ import ( "context" "fmt" "math/rand" + "sync/atomic" "testing" "time" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + types "github.com/ChainSafe/gossamer/dot/types" + gomock "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" ) @@ -45,56 +48,35 @@ func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, S } } -func (s *TestSubsystem) String() parachaintypes.SubSystemName { - return parachaintypes.SubSystemName(s.name) +func (s *TestSubsystem) ProcessActiveLeavesUpdateSignal() { + fmt.Printf("%s ProcessActiveLeavesUpdateSignal\n", s.name) } -func TestStart2SubsytemsActivate1(t *testing.T) { - overseer := NewOverseer() - require.NotNil(t, overseer) - - subSystem1 := &TestSubsystem{name: "subSystem1"} - subSystem2 := &TestSubsystem{name: "subSystem2"} - - overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) - overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) +func (s *TestSubsystem) ProcessBlockFinalizedSignal() { + fmt.Printf("%s ProcessActiveLeavesUpdateSignal\n", s.name) +} - go func() { - <-overseerToSubSystem1 - <-overseerToSubSystem2 - }() +func (s *TestSubsystem) String() parachaintypes.SubSystemName { + return parachaintypes.SubSystemName(s.name) +} - err := overseer.Start() - require.NoError(t, err) +func (s *TestSubsystem) Stop() {} - done := make(chan struct{}) - // listen for errors from overseer - go func() { - for errC := range overseer.errChan { - fmt.Printf("overseer start error: %v\n", errC) - } - close(done) - }() +func TestHandleBlockEvents(t *testing.T) { + ctrl := gomock.NewController(t) - time.Sleep(1000 * time.Millisecond) - activedLeaf := ActivatedLeaf{ - Hash: [32]byte{1}, - Number: 1, - } - overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem1) + blockState := NewMockBlockState(ctrl) - // let subsystems run for a bit - time.Sleep(4000 * time.Millisecond) + finalizedNotifierChan := make(chan *types.FinalisationInfo) + importedBlockNotiferChan := make(chan *types.Block) - err = overseer.Stop() - require.NoError(t, err) + blockState.EXPECT().GetFinalisedNotifierChannel().Return(finalizedNotifierChan) + blockState.EXPECT().GetImportedBlockNotifierChannel().Return(importedBlockNotiferChan) + blockState.EXPECT().FreeFinalisedNotifierChannel(finalizedNotifierChan) + blockState.EXPECT().FreeImportedBlockNotifierChannel(importedBlockNotiferChan) - fmt.Printf("overseer stopped\n") - <-done -} + overseer := NewOverseer(blockState) -func TestStart2SubsytemsActivate2Different(t *testing.T) { - overseer := NewOverseer() require.NotNil(t, overseer) subSystem1 := &TestSubsystem{name: "subSystem1"} @@ -103,82 +85,55 @@ func TestStart2SubsytemsActivate2Different(t *testing.T) { overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) - go func() { - <-overseerToSubSystem1 - <-overseerToSubSystem2 - }() + var finalizedCounter atomic.Int32 + var importedCounter atomic.Int32 - err := overseer.Start() - require.NoError(t, err) - done := make(chan struct{}) go func() { - for errC := range overseer.errChan { - fmt.Printf("overseer start error: %v\n", errC) - } - close(done) - }() - - activedLeaf1 := ActivatedLeaf{ - Hash: [32]byte{1}, - Number: 1, - } - activedLeaf2 := ActivatedLeaf{ - Hash: [32]byte{2}, - Number: 2, - } - time.Sleep(250 * time.Millisecond) - overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf1}, subSystem1) - time.Sleep(400 * time.Millisecond) - overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf2}, subSystem2) - // let subsystems run for a bit - time.Sleep(3000 * time.Millisecond) - - err = overseer.Stop() - require.NoError(t, err) - - fmt.Printf("overseer stopped\n") - <-done -} - -func TestStart2SubsytemsActivate2Same(t *testing.T) { - overseer := NewOverseer() - require.NotNil(t, overseer) - - subSystem1 := &TestSubsystem{name: "subSystem1"} - subSystem2 := &TestSubsystem{name: "subSystem2"} - - overseerToSubSystem1 := overseer.RegisterSubsystem(subSystem1) - overseerToSubSystem2 := overseer.RegisterSubsystem(subSystem2) + for { + select { + case msg := <-overseerToSubSystem1: + if msg == nil { + continue + } + + _, ok := msg.(parachaintypes.BlockFinalizedSignal) + if ok { + finalizedCounter.Add(1) + } + + _, ok = msg.(parachaintypes.ActiveLeavesUpdateSignal) + if ok { + importedCounter.Add(1) + } + case msg := <-overseerToSubSystem2: + if msg == nil { + continue + } + + _, ok := msg.(parachaintypes.BlockFinalizedSignal) + if ok { + finalizedCounter.Add(1) + } + + _, ok = msg.(parachaintypes.ActiveLeavesUpdateSignal) + if ok { + importedCounter.Add(1) + } + } - go func() { - <-overseerToSubSystem1 - <-overseerToSubSystem2 + } }() err := overseer.Start() require.NoError(t, err) - done := make(chan struct{}) - go func() { - for errC := range overseer.errChan { - fmt.Printf("overseer start error: %v\n", errC) - } - close(done) - }() + finalizedNotifierChan <- &types.FinalisationInfo{} + importedBlockNotiferChan <- &types.Block{} - activedLeaf := ActivatedLeaf{ - Hash: [32]byte{1}, - Number: 1, - } - time.Sleep(300 * time.Millisecond) - overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem1) - time.Sleep(400 * time.Millisecond) - overseer.sendActiveLeavesUpdate(ActiveLeavesUpdate{Activated: activedLeaf}, subSystem2) - // let subsystems run for a bit - time.Sleep(2000 * time.Millisecond) + time.Sleep(1000 * time.Millisecond) err = overseer.Stop() require.NoError(t, err) - fmt.Printf("overseer stopped\n") - <-done + require.Equal(t, int32(2), finalizedCounter.Load()) + require.Equal(t, int32(2), importedCounter.Load()) } diff --git a/dot/parachain/overseer/types.go b/dot/parachain/overseer/types.go index ccc3fdb0fb..3e2d1d6150 100644 --- a/dot/parachain/overseer/types.go +++ b/dot/parachain/overseer/types.go @@ -7,25 +7,14 @@ import ( "context" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" - "github.com/ChainSafe/gossamer/lib/common" ) -// ActivatedLeaf is a parachain head which we care to work on. -type ActivatedLeaf struct { - Hash common.Hash - Number uint32 -} - -// ActiveLeavesUpdate changes in the set of active leaves: the parachain heads which we care to work on. -// -// note: activated field indicates deltas, not complete sets. -type ActiveLeavesUpdate struct { - Activated ActivatedLeaf -} - // Subsystem is an interface for subsystems to be registered with the overseer. type Subsystem interface { // Run runs the subsystem. Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error Name() parachaintypes.SubSystemName + ProcessActiveLeavesUpdateSignal() + ProcessBlockFinalizedSignal() + Stop() } diff --git a/dot/parachain/service.go b/dot/parachain/service.go index 798d11999a..bbeeafdf00 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -34,7 +34,7 @@ type Service struct { var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) func NewService(net Network, forkID string, st *state.Service) (*Service, error) { - overseer := overseer.NewOverseer() + overseer := overseer.NewOverseer(st.Block) genesisHash := st.Block.GenesisHash() availabilityStore, err := availability_store.Register(overseer.SubsystemsToOverseer, st) @@ -109,7 +109,7 @@ func (Service) Stop() error { // main loop of parachain service func (s Service) run() { - overseer := overseer.NewOverseer() + overseer := s.overseer candidateBacking := backing.New(overseer.SubsystemsToOverseer) candidateBacking.OverseerToSubSystem = overseer.RegisterSubsystem(candidateBacking) diff --git a/dot/parachain/types/overseer_signals.go b/dot/parachain/types/overseer_signals.go new file mode 100644 index 0000000000..2c5d143570 --- /dev/null +++ b/dot/parachain/types/overseer_signals.go @@ -0,0 +1,30 @@ +package parachaintypes + +import ( + "errors" + + "github.com/ChainSafe/gossamer/lib/common" +) + +var ErrUnknownOverseerMessage = errors.New("unknown overseer message type") + +// ActivatedLeaf is a parachain head which we care to work on. +type ActivatedLeaf struct { + Hash common.Hash + Number uint32 +} + +// ActiveLeavesUpdateSignal changes in the set of active leaves: the parachain heads which we care to work on. +// +// note: activated field indicates deltas, not complete sets. +type ActiveLeavesUpdateSignal struct { + Activated *ActivatedLeaf + // Relay chain block hashes no longer of interest. + Deactivated []common.Hash +} + +// BlockFinalized signal is used to inform subsystems of a finalized block. +type BlockFinalizedSignal struct { + Hash common.Hash + BlockNumber uint32 +} From f7bc903c326dacb36f9bae2a5318ff6ad1419d31 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Tue, 16 Jan 2024 20:45:04 +0530 Subject: [PATCH 68/85] feat(dot/parachain/backing): handle `statement` message coming from overseer (#3532) - Implemented functionality to handle a statement message coming to the candidate backing subsystem - StatementMessage represents a validator's assessment of a specific candidate. - on receiving a statement message, we import a statement into the statement table and dispatch `Backed` notifications and misbehaviours as a result of importing a statement. - if the statement is seconded, the committed candidate receipt will be fetched from the statement table. Attesting data will be generated and stored to retry validation with other backing validators if a validator does not provide a PoV. - if the statement is valid, the validator index will be stored in existing attesting data. It will be checked whether the backing job is already running with the current validator. If not, the backing job will be started. - and then validation work will be kicked off. - added some overseer messages for other subsystems as well, such as provisioner, candidate validation, and statement distribution in `dot/parachain/types/overseer_messages.go` - implemented `Misbehaviour` enum using the interface (#3601) - removed duplicate parachain types from `lib/babe/inherents` (#3668) - implemented ExecutorParams varying datatype slice (partially implements #3544) --- .../availability-store/availability_store.go | 6 +- .../availability_store_test.go | 8 +- dot/parachain/availability-store/messages.go | 16 +- dot/parachain/backing/candidate_backing.go | 314 ++++- .../backing/candidate_backing_test.go | 1176 +++++++++++++++++ dot/parachain/backing/mocks_generate_test.go | 6 + dot/parachain/backing/mocks_test.go | 94 ++ .../backing/per_relay_parent_state.go | 408 ++++++ dot/parachain/backing/statement_table.go | 39 + .../backing/validated_candidate_command.go | 44 + dot/parachain/collator-protocol/message.go | 10 +- .../collator-protocol/message_test.go | 28 +- .../messages/overseer_messages.go | 43 + .../collator-protocol/validator_side.go | 93 +- .../collator-protocol/validator_side_test.go | 17 +- dot/parachain/overseer/overseer.go | 15 +- dot/parachain/overseer/overseer_test.go | 4 +- dot/parachain/overseer/types.go | 2 +- dot/parachain/types/executor_params.go | 239 ++++ dot/parachain/types/misbehavior.go | 95 ++ dot/parachain/types/overseer_message.go | 126 ++ dot/parachain/types/overseer_signals.go | 3 + dot/parachain/types/statement_test.go | 8 +- .../{subsystem_names.go => subsystems.go} | 0 dot/parachain/types/subsystems_test.go | 145 ++ dot/parachain/types/types.go | 45 + dot/parachain/types/validity_attestation.go | 63 + .../types/validity_attestation_test.go | 54 + dot/parachain/validation_protocol.go | 17 +- dot/parachain/validation_protocol_test.go | 2 +- lib/babe/inherents/parachain_inherents.go | 184 +-- .../inherents/parachain_inherents_test.go | 9 +- 32 files changed, 2921 insertions(+), 392 deletions(-) create mode 100644 dot/parachain/backing/candidate_backing_test.go create mode 100644 dot/parachain/backing/mocks_generate_test.go create mode 100644 dot/parachain/backing/mocks_test.go create mode 100644 dot/parachain/backing/per_relay_parent_state.go create mode 100644 dot/parachain/backing/statement_table.go create mode 100644 dot/parachain/backing/validated_candidate_command.go create mode 100644 dot/parachain/collator-protocol/messages/overseer_messages.go create mode 100644 dot/parachain/types/executor_params.go create mode 100644 dot/parachain/types/misbehavior.go create mode 100644 dot/parachain/types/overseer_message.go rename dot/parachain/types/{subsystem_names.go => subsystems.go} (100%) create mode 100644 dot/parachain/types/subsystems_test.go create mode 100644 dot/parachain/types/validity_attestation.go create mode 100644 dot/parachain/types/validity_attestation_test.go diff --git a/dot/parachain/availability-store/availability_store.go b/dot/parachain/availability-store/availability_store.go index 0cb7f70315..4717cdd7cf 100644 --- a/dot/parachain/availability-store/availability_store.go +++ b/dot/parachain/availability-store/availability_store.go @@ -174,12 +174,10 @@ func uint32ToBytes(value uint32) []byte { // Run runs the availability store subsystem func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, - SubsystemToOverseer chan any) error { + SubsystemToOverseer chan any) { av.wg.Add(1) go av.processMessages() - - return nil } // Name returns the name of the availability store subsystem @@ -361,7 +359,7 @@ func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) error { } func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailableData) error { - err := av.availabilityStore.storeAvailableData(msg.CandidateHash, msg.AvailableData) + err := av.availabilityStore.storeAvailableData(msg.CandidateHash.Value, msg.AvailableData) if err != nil { msg.Sender <- err return fmt.Errorf("store available data: %w", err) diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go index f0b85625cf..a0bc7017a3 100644 --- a/dot/parachain/availability-store/availability_store_test.go +++ b/dot/parachain/availability-store/availability_store_test.go @@ -382,10 +382,12 @@ func TestAvailabilityStore_handleStoreAvailableData(t *testing.T) { asSub := AvailabilityStoreSubsystem{ availabilityStore: *as, } - msgSenderChan := make(chan any) + msgSenderChan := make(chan error) msg := StoreAvailableData{ - CandidateHash: common.Hash{0x01}, - NValidators: 0, + CandidateHash: parachaintypes.CandidateHash{ + Value: common.Hash{0x01}, + }, + NumValidators: 0, AvailableData: AvailableData{}, ExpectedErasureRoot: common.Hash{}, Sender: msgSenderChan, diff --git a/dot/parachain/availability-store/messages.go b/dot/parachain/availability-store/messages.go index e1bbab319e..1486f4fc37 100644 --- a/dot/parachain/availability-store/messages.go +++ b/dot/parachain/availability-store/messages.go @@ -71,13 +71,19 @@ type StoreChunk struct { Sender chan any } -// StoreAvailableData store an `AvailableData` in the AV store +// StoreAvailableData computes and checks the erasure root of `AvailableData` +// before storing its chunks in the AV store. type StoreAvailableData struct { - CandidateHash common.Hash - NValidators uint32 - AvailableData AvailableData + // A hash of the candidate this `ASMStoreAvailableData` belongs to. + CandidateHash parachaintypes.CandidateHash + // The number of validators in the session. + NumValidators uint32 + // The `AvailableData` itself. + AvailableData AvailableData + // Erasure root we expect to get after chunking. ExpectedErasureRoot common.Hash - Sender chan any + // channel to send result to. + Sender chan error } // AvailableData is the data we keep available for each candidate included in the relay chain diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index dcf5e4febd..5f4baab606 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -1,10 +1,30 @@ // Copyright 2023 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only +// This package implements the Candidate Backing subsystem. +// It ensures every parablock considered for relay block inclusion has been seconded by at least +// one validator, and approved by a quorum. Parablocks for which not enough validators will assert +// correctness are discarded. If the block later proves invalid, the initial backers are slashable; +// this gives Polkadot a rational threat model during subsequent stages. + +// Its role is to produce backable candidates for inclusion in new relay-chain blocks. It does so +// by issuing signed Statements and tracking received statements signed by other validators. Once +// enough statements are received, they can be combined into backing for specific candidates. + +// Note that though the candidate backing subsystem attempts to produce as many backable candidates +// as possible, it does not attempt to choose a single authoritative one. The choice of which +// actually gets included is ultimately up to the block author, by whatever metrics it may use; +// those are opaque to this subsystem. + +// Once a sufficient quorum has agreed that a candidate is valid, this subsystem notifies the +// Provisioner, which in turn engages block production mechanisms to include the parablock. + package backing import ( "context" + "errors" + "fmt" "sync" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" @@ -14,6 +34,16 @@ import ( var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-candidate-backing")) +var ( + errRejectedByProspectiveParachains = errors.New("candidate rejected by prospective parachains subsystem") + errInvalidErasureRoot = errors.New("erasure root doesn't match the announced by the candidate receipt") + errStatementForUnknownRelayParent = errors.New("received statement for unknown relay parent") + errNilRelayParentState = errors.New("relay parent state is nil") + errCandidateStateNotFound = errors.New("candidate state not found") + errAttestingDataNotFound = errors.New("attesting data not found") +) + +// CandidateBacking represents the state of the subsystem responsible for managing candidate backing. type CandidateBacking struct { ctx context.Context cancel context.CancelFunc @@ -21,108 +51,137 @@ type CandidateBacking struct { SubSystemToOverseer chan<- any OverseerToSubSystem <-chan any + // State tracked for all relay-parents backing work is ongoing for. This includes + // all active leaves. + // + // relay-parents fall into one of 3 categories. + // 1. active leaves which do support prospective parachains + // 2. active leaves which do not support prospective parachains + // 3. relay-chain blocks which are ancestors of an active leaf and do support prospective + // parachains. + // + // Relay-chain blocks which don't support prospective parachains are + // never included in the fragment trees of active leaves which do. + // + // While it would be technically possible to support such leaves in + // fragment trees, it only benefits the transition period when asynchronous + // backing is being enabled and complicates code complexity. + perRelayParent map[common.Hash]*perRelayParentState + // State tracked for all candidates relevant to the implicit view. + // + // This is guaranteed to have an entry for each candidate with a relay parent in the implicit + // or explicit view for which a `Seconded` statement has been successfully imported. + perCandidate map[parachaintypes.CandidateHash]*perCandidateState +} + +// perCandidateState represents the state information for a candidate in the subsystem. +type perCandidateState struct { + persistedValidationData parachaintypes.PersistedValidationData + secondedLocally bool + paraID parachaintypes.ParaID + relayParent common.Hash +} + +// attestingData contains the data needed to retry validation with other backing validators +// in case a validator does not provide a PoV. +type attestingData struct { + // The candidate to attest. + candidate parachaintypes.CandidateReceipt + // Hash of the PoV we need to fetch. + povHash common.Hash + // Validator we are currently trying to get the PoV from. + fromValidator parachaintypes.ValidatorIndex + // Other backing validators we can try in case `from_validator` failed. + backing []parachaintypes.ValidatorIndex +} + +// TableContext represents the contextual information associated with a validator and groups +// for a table under a relay-parent. +type TableContext struct { + validator *validator + groups map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex + validators []parachaintypes.ValidatorID } -// ActiveLeavesUpdate is a messages from overseer -type ActiveLeavesUpdate struct { - // TODO: Complete this struct #3503 +// validator represents local validator information. +// It can be created if the local node is a validator in the context of a particular relay chain block. +type validator struct { + index parachaintypes.ValidatorIndex } -// GetBackedCandidates is a message received from overseer that requests a set of backable +// GetBackedCandidatesMessage is a message received from overseer that requests a set of backable // candidates that could be backed in a child of the given relay-parent. -type GetBackedCandidates []struct { +type GetBackedCandidatesMessage []struct { CandidateHash parachaintypes.CandidateHash CandidateRelayParent common.Hash } -// CanSecond is a request made to the candidate backing subsystem to determine whether it is permissible +// CanSecondMessage is a request made to the candidate backing subsystem to determine whether it is permissible // to second a given candidate. // The rule for seconding candidates is: Collations must either be built on top of the root of a fragment tree // or have a parent node that represents the backed candidate. -type CanSecond struct { +type CanSecondMessage struct { CandidateParaID parachaintypes.ParaID CandidateRelayParent common.Hash CandidateHash parachaintypes.CandidateHash ParentHeadDataHash common.Hash } -// Second is a message received from overseer. Candidate Backing subsystem should second the given +// SecondMessage is a message received from overseer. Candidate Backing subsystem should second the given // candidate in the context of the given relay parent. This candidate must be validated. -type Second struct { +type SecondMessage struct { RelayParent common.Hash CandidateReceipt parachaintypes.CandidateReceipt PersistedValidationData parachaintypes.PersistedValidationData PoV parachaintypes.PoV } -// Statement represents a validator's assessment of a specific candidate. If there are disagreements +// StatementMessage represents a validator's assessment of a specific candidate. If there are disagreements // regarding the validity of this assessment, they should be addressed through the Disputes Subsystem, // with the actual escalation deferred until the approval voting stage to ensure its availability. // Meanwhile, agreements are straightforwardly counted until a quorum is achieved. -type Statement struct { +type StatementMessage struct { RelayParent common.Hash SignedFullStatement SignedFullStatementWithPVD } +// SignedFullStatementWithPVD represents a signed full statement along with associated Persisted Validation Data (PVD). +type SignedFullStatementWithPVD struct { + SignedFullStatement parachaintypes.UncheckedSignedFullStatement + PersistedValidationData *parachaintypes.PersistedValidationData +} + +// New creates a new CandidateBacking instance and initialises it with the provided overseer channel. func New(overseerChan chan<- any) *CandidateBacking { return &CandidateBacking{ SubSystemToOverseer: overseerChan, + perRelayParent: map[common.Hash]*perRelayParentState{}, + perCandidate: map[parachaintypes.CandidateHash]*perCandidateState{}, } } -func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan any, subSystemToOverseer chan any) error { - // TODO: handle_validated_candidate_command - // There is one more case where we handle results of candidate validation. - // My feeling is that instead of doing it here, we would be able to do that along with processing - // other backing related overseer message. - // This would become more clear after we complete processMessages function. It would give us clarity - // if we need background_validation_rx or background_validation_tx, as done in rust. - +func (cb *CandidateBacking) Run(ctx context.Context, overseerToSubSystem chan any, subSystemToOverseer chan any) { cb.wg.Add(1) - go cb.processMessages() - - return nil -} - -func (*CandidateBacking) Name() parachaintypes.SubSystemName { - return parachaintypes.CandidateBacking + go cb.runUtil() } -func (cb *CandidateBacking) ProcessActiveLeavesUpdateSignal() { - // TODO #3644 -} +func (cb *CandidateBacking) runUtil() { + chRelayParentAndCommand := make(chan relayParentAndCommand) -func (cb *CandidateBacking) ProcessBlockFinalizedSignal() { - // TODO #3644 -} - -func (cb *CandidateBacking) processMessages() { for { select { + case rpAndCmd := <-chRelayParentAndCommand: + if err := cb.processValidatedCandidateCommand(rpAndCmd); err != nil { + logger.Error(err.Error()) + } case msg := <-cb.OverseerToSubSystem: - // process these received messages by referencing - // https://github.com/paritytech/polkadot-sdk/blob/769bdd3ff33a291cbc70a800a3830638467e42a2/polkadot/node/core/backing/src/lib.rs#L741 - switch msg.(type) { - case GetBackedCandidates: - cb.handleGetBackedCandidates() - case CanSecond: - cb.handleCanSecond() - case Second: - cb.handleSecond() - case Statement: - cb.handleStatement() - case parachaintypes.ActiveLeavesUpdateSignal: - cb.ProcessActiveLeavesUpdateSignal() - case parachaintypes.BlockFinalizedSignal: - cb.ProcessBlockFinalizedSignal() - default: - logger.Error(parachaintypes.ErrUnknownOverseerMessage.Error()) - return + if err := cb.processMessage(msg, chRelayParentAndCommand); err != nil { + logger.Errorf("processing message %s", err.Error()) } - case <-cb.ctx.Done(): + close(chRelayParentAndCommand) if err := cb.ctx.Err(); err != nil { - logger.Errorf("ctx error: %v\n", err) + logger.Errorf("ctx error: %s\n", err) } cb.wg.Done() return @@ -130,29 +189,150 @@ func (cb *CandidateBacking) processMessages() { } } -func (cb *CandidateBacking) handleGetBackedCandidates() { +func (cb *CandidateBacking) Stop() { + cb.cancel() + cb.wg.Wait() +} + +func (*CandidateBacking) Name() parachaintypes.SubSystemName { + return parachaintypes.CandidateBacking +} + +// processMessage processes incoming messages from overseer +func (cb *CandidateBacking) processMessage(msg any, chRelayParentAndCommand chan relayParentAndCommand) error { + // process these received messages by referencing + // https://github.com/paritytech/polkadot-sdk/blob/769bdd3ff33a291cbc70a800a3830638467e42a2/polkadot/node/core/backing/src/lib.rs#L741 + switch msg := msg.(type) { + case GetBackedCandidatesMessage: + cb.handleGetBackedCandidatesMessage() + case CanSecondMessage: + cb.handleCanSecondMessage() + case SecondMessage: + cb.handleSecondMessage() + case StatementMessage: + return cb.handleStatementMessage(msg.RelayParent, msg.SignedFullStatement, chRelayParentAndCommand) + case parachaintypes.ActiveLeavesUpdateSignal: + cb.ProcessActiveLeavesUpdateSignal() + case parachaintypes.BlockFinalizedSignal: + cb.ProcessBlockFinalizedSignal() + default: + return fmt.Errorf("%w: %T", parachaintypes.ErrUnknownOverseerMessage, msg) + } + return nil +} + +func (cb *CandidateBacking) ProcessActiveLeavesUpdateSignal() { + // TODO #3503 +} + +func (cb *CandidateBacking) ProcessBlockFinalizedSignal() { + // TODO #3644 +} + +func (cb *CandidateBacking) handleGetBackedCandidatesMessage() { // TODO: Implement this #3504 } -func (cb *CandidateBacking) handleCanSecond() { +func (cb *CandidateBacking) handleCanSecondMessage() { // TODO: Implement this #3505 } -func (cb *CandidateBacking) handleSecond() { +func (cb *CandidateBacking) handleSecondMessage() { // TODO: Implement this #3506 } -func (cb *CandidateBacking) handleStatement() { - // TODO: Implement this #3507 -} +// Import the statement and kick off validation work if it is a part of our assignment. +func (cb *CandidateBacking) handleStatementMessage( + relayParent common.Hash, + signedStatementWithPVD SignedFullStatementWithPVD, + chRelayParentAndCommand chan relayParentAndCommand, +) error { + rpState, ok := cb.perRelayParent[relayParent] + if !ok { + return fmt.Errorf("%w: %s", errStatementForUnknownRelayParent, relayParent) + } -func (cb *CandidateBacking) Stop() { - cb.cancel() - cb.wg.Wait() + if rpState == nil { + return errNilRelayParentState + } + + summary, err := rpState.importStatement(cb.SubSystemToOverseer, signedStatementWithPVD, cb.perCandidate) + if err != nil { + return fmt.Errorf("importing statement: %w", err) + } + + rpState.postImportStatement(cb.SubSystemToOverseer, summary) + + if summary == nil { + logger.Debug("summary is nil") + return nil + } + + if summary.GroupID != rpState.assignment { + logger.Debugf("The ParaId: %d is not assigned to the local validator at relay parent: %s", + summary.GroupID, relayParent) + return nil + } + + // already ensured in importStatement that the value of the statementVDT has been set. + // that is why there is no chance we can get an error here. + statementVDT, _ := signedStatementWithPVD.SignedFullStatement.Payload.Value() + + var attesting attestingData + switch statementVDT := statementVDT.(type) { + case parachaintypes.Seconded: + commitedCandidateReceipt, err := rpState.table.getCandidate(summary.Candidate) + if err != nil { + return fmt.Errorf("getting candidate: %w", err) + } + + attesting = attestingData{ + candidate: commitedCandidateReceipt.ToPlain(), + povHash: statementVDT.Descriptor.PovHash, + fromValidator: signedStatementWithPVD.SignedFullStatement.ValidatorIndex, + backing: []parachaintypes.ValidatorIndex{}, + } + case parachaintypes.Valid: + candidateHash := parachaintypes.CandidateHash(statementVDT) + attesting, ok = rpState.fallbacks[candidateHash] + if !ok { + // polkadot-sdk returs nil error here + return errAttestingDataNotFound + } + + ourIndex := rpState.tableContext.validator.index + if signedStatementWithPVD.SignedFullStatement.ValidatorIndex == ourIndex { + return nil + } + + if rpState.awaitingValidation[candidateHash] { + logger.Debug("Job already running") + attesting.backing = append(attesting.backing, signedStatementWithPVD.SignedFullStatement.ValidatorIndex) + return nil + } + + logger.Debug("No job, so start another with current validator") + attesting.fromValidator = signedStatementWithPVD.SignedFullStatement.ValidatorIndex + } + + rpState.fallbacks[summary.Candidate] = attesting + + // After `import_statement` succeeds, the candidate entry is guaranteed to exist. + pc, ok := cb.perCandidate[summary.Candidate] + if !ok { + return errCandidateStateNotFound + } + + return rpState.kickOffValidationWork( + cb.SubSystemToOverseer, + chRelayParentAndCommand, + pc.persistedValidationData, + attesting, + ) } -// SignedFullStatementWithPVD represents a signed full statement along with associated Persisted Validation Data (PVD). -type SignedFullStatementWithPVD struct { - SignedFullStatement parachaintypes.UncheckedSignedFullStatement - PersistedValidationData *parachaintypes.PersistedValidationData +func getPovFromValidator() parachaintypes.PoV { + // TODO: Implement this #3545 + // https://github.com/paritytech/polkadot-sdk/blob/7ca0d65f19497ac1c3c7ad6315f1a0acb2ca32f8/polkadot/node/core/backing/src/lib.rs#L1744 //nolint:lll + return parachaintypes.PoV{} } diff --git a/dot/parachain/backing/candidate_backing_test.go b/dot/parachain/backing/candidate_backing_test.go new file mode 100644 index 0000000000..6ee090419c --- /dev/null +++ b/dot/parachain/backing/candidate_backing_test.go @@ -0,0 +1,1176 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import ( + "errors" + "testing" + + availabilitystore "github.com/ChainSafe/gossamer/dot/parachain/availability-store" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/crypto/sr25519" + "github.com/ChainSafe/gossamer/pkg/scale" + gomock "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" +) + +var tempSignature = common.MustHexToBytes("0xc67cb93bf0a36fcee3d29de8a6a69a759659680acf486475e0a2552a5fbed87e45adce5f290698d8596095722b33599227f7461f51af8617c8be74b894cf1b86") //nolint:lll + +func getDummyHash(t *testing.T, num byte) common.Hash { + t.Helper() + hash := common.Hash{} + for i := 0; i < 32; i++ { + hash[i] = num + } + return hash +} + +func getDummyCommittedCandidateReceipt(t *testing.T) parachaintypes.CommittedCandidateReceipt { + t.Helper() + hash5 := getDummyHash(t, 6) + + var collatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(collatorID[:], tempCollatID) + + var collatorSignature parachaintypes.CollatorSignature + copy(collatorSignature[:], tempSignature) + + ccr := parachaintypes.CommittedCandidateReceipt{ + Descriptor: parachaintypes.CandidateDescriptor{ + ParaID: uint32(1), + RelayParent: hash5, + Collator: collatorID, + PersistedValidationDataHash: hash5, + PovHash: hash5, + ErasureRoot: hash5, + Signature: collatorSignature, + ParaHead: hash5, + ValidationCodeHash: parachaintypes.ValidationCodeHash(hash5), + }, + Commitments: parachaintypes.CandidateCommitments{ + UpwardMessages: []parachaintypes.UpwardMessage{{1, 2, 3}}, + NewValidationCode: ¶chaintypes.ValidationCode{1, 2, 3}, + HeadData: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, + ProcessedDownwardMessages: uint32(5), + HrmpWatermark: uint32(0), + }, + } + + return ccr +} + +func mockOverseer(t *testing.T, subsystemToOverseer chan any) { + t.Helper() + for data := range subsystemToOverseer { + switch data := data.(type) { + case parachaintypes.ProspectiveParachainsMessageIntroduceCandidate: + data.Ch <- nil + case parachaintypes.ProspectiveParachainsMessageCandidateSeconded, + parachaintypes.ProvisionerMessageProvisionableData, + parachaintypes.ProspectiveParachainsMessageCandidateBacked, + collatorprotocolmessages.Backed, + parachaintypes.StatementDistributionMessageBacked: + continue + default: + t.Errorf("unknown type: %T\n", data) + } + } +} + +func secondedSignedFullStatementWithPVD( + t *testing.T, + statementVDTSeconded parachaintypes.StatementVDT, +) SignedFullStatementWithPVD { + t.Helper() + return SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTSeconded, + }, + PersistedValidationData: ¶chaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, + RelayParentNumber: 5, + RelayParentStorageRoot: getDummyHash(t, 5), + MaxPovSize: 3, + }, + } +} + +func TestImportStatement(t *testing.T) { + t.Parallel() + + dummyCCR := getDummyCommittedCandidateReceipt(t) + seconded := parachaintypes.Seconded(dummyCCR) + + statementVDTSeconded := parachaintypes.NewStatementVDT() + err := statementVDTSeconded.Set(seconded) + require.NoError(t, err) + + hash, err := dummyCCR.Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + statementVDTValid := parachaintypes.NewStatementVDT() + err = statementVDTValid.Set(parachaintypes.Valid{}) + require.NoError(t, err) + + testCases := []struct { + description string + rpState func() perRelayParentState + perCandidate map[parachaintypes.CandidateHash]*perCandidateState + signedStatementWithPVD SignedFullStatementWithPVD + summary *Summary + err string + }{ + { + description: "statementVDT_not_set", + rpState: func() perRelayParentState { + return perRelayParentState{} + }, + signedStatementWithPVD: SignedFullStatementWithPVD{}, + summary: nil, + err: "getting value from statementVDT:", + }, + { + description: "statementVDT_in_not_seconded", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(new(Summary), nil) + + return perRelayParentState{ + table: mockTable, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + }, + }, + summary: new(Summary), + err: "", + }, + { + description: "invalid_persisted_validation_data", + rpState: func() perRelayParentState { + return perRelayParentState{} + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTSeconded, + }, + }, + summary: nil, + err: "persisted validation data is nil", + }, + { + description: "statement_is_seconded_and_candidate_is_known", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(new(Summary), nil) + + return perRelayParentState{ + table: mockTable, + } + }, + perCandidate: map[parachaintypes.CandidateHash]*perCandidateState{ + candidateHash: { + persistedValidationData: parachaintypes.PersistedValidationData{ + ParentHead: parachaintypes.HeadData{ + Data: []byte{1, 2, 3}, + }, + }, + secondedLocally: false, + paraID: 1, + relayParent: getDummyHash(t, 5), + }, + }, + signedStatementWithPVD: secondedSignedFullStatementWithPVD(t, statementVDTSeconded), + summary: new(Summary), + err: "", + }, + { + description: "statement_is_seconded_and_candidate_is_unknown", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(new(Summary), nil) + + return perRelayParentState{ + table: mockTable, + } + }, + perCandidate: map[parachaintypes.CandidateHash]*perCandidateState{}, + signedStatementWithPVD: secondedSignedFullStatementWithPVD(t, statementVDTSeconded), + summary: new(Summary), + err: "", + }, + { + description: "statement_is_seconded_and_candidate_is_unknown_with_prospective_parachain_mode_enabled", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(new(Summary), nil) + + return perRelayParentState{ + table: mockTable, + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + } + }, + perCandidate: map[parachaintypes.CandidateHash]*perCandidateState{}, + signedStatementWithPVD: secondedSignedFullStatementWithPVD(t, statementVDTSeconded), + summary: new(Summary), + err: "", + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + subSystemToOverseer := make(chan any) + defer close(subSystemToOverseer) + + rpState := c.rpState() + if rpState.prospectiveParachainsMode.IsEnabled { + go mockOverseer(t, subSystemToOverseer) + } + + summary, err := rpState.importStatement(subSystemToOverseer, c.signedStatementWithPVD, c.perCandidate) + if c.summary == nil { + require.Nil(t, summary) + } else { + require.Equal(t, c.summary, summary) + } + + if c.err == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.err) + } + }) + } +} + +func mustHexTo32BArray(t *testing.T, inputHex string) (outputArray [sr25519.PublicKeyLength]byte) { + t.Helper() + copy(outputArray[:], common.MustHexToBytes(inputHex)) + return outputArray +} + +func dummySummary(t *testing.T) *Summary { + t.Helper() + + return &Summary{ + Candidate: parachaintypes.CandidateHash{ + Value: getDummyHash(t, 5), + }, + GroupID: 3, + ValidityVotes: 5, + } +} + +func dummyValidityAttestation(t *testing.T, value string) parachaintypes.ValidityAttestation { + t.Helper() + + var validatorSignature parachaintypes.ValidatorSignature + copy(validatorSignature[:], tempSignature) + + vdt := parachaintypes.NewValidityAttestation() + switch value { + case "implicit": + err := vdt.Set(parachaintypes.Implicit(validatorSignature)) + require.NoError(t, err) + case "explicit": + err := vdt.Set(parachaintypes.Explicit(validatorSignature)) + require.NoError(t, err) + default: + require.Fail(t, "invalid value") + } + return vdt +} + +func dummyTableContext(t *testing.T) TableContext { + t.Helper() + + return TableContext{ + validator: &validator{ + index: 1, + }, + groups: map[parachaintypes.ParaID][]parachaintypes.ValidatorIndex{ + 1: {1, 2, 3}, + 2: {4, 5, 6}, + 3: {7, 8, 9}, + }, + validators: []parachaintypes.ValidatorID{ + mustHexTo32BArray(t, "0xa262f83b46310770ae8d092147176b8b25e8855bcfbbe701d346b10db0c5385d"), + mustHexTo32BArray(t, "0x804b9df571e2b744d65eca2d4c59eb8e4345286c00389d97bfc1d8d13aa6e57e"), + mustHexTo32BArray(t, "0x4eb63e4aad805c06dc924e2f19b1dde7faf507e5bb3c1838d6a3cfc10e84fe72"), + mustHexTo32BArray(t, "0x74c337d57035cd6b7718e92a0d8ea6ef710da8ab1215a057c40c4ef792155a68"), + }, + } +} + +func rpStateWhenPpmDisabled(t *testing.T) perRelayParentState { + t.Helper() + + attestedToReturn := AttestedCandidate{ + GroupID: 3, + Candidate: getDummyCommittedCandidateReceipt(t), + ValidityVotes: []validityVote{ + { + ValidatorIndex: 7, + ValidityAttestation: dummyValidityAttestation(t, "implicit"), + }, + { + ValidatorIndex: 8, + ValidityAttestation: dummyValidityAttestation(t, "explicit"), + }, + { + ValidatorIndex: 9, + ValidityAttestation: dummyValidityAttestation(t, "implicit"), + }, + }, + } + + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(&attestedToReturn, nil) + + return perRelayParentState{ + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: false, + }, + table: mockTable, + tableContext: dummyTableContext(t), + backed: map[parachaintypes.CandidateHash]bool{}, + } +} + +func TestPostImportStatement(t *testing.T) { + t.Parallel() + + testCases := []struct { + description string + rpState func() perRelayParentState + summary *Summary + }{ + { + description: "summary_is_nil", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().drainMisbehaviors().Return([]parachaintypes.ProvisionableDataMisbehaviorReport{ + { + ValidatorIndex: 1, + Misbehaviour: parachaintypes.MultipleCandidates{}, + }, + }) + + return perRelayParentState{ + table: mockTable, + } + }, + summary: nil, + }, + { + description: "failed_to_get_attested_candidate_from_table", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(nil, errors.New("could not get attested candidate from table")) + + return perRelayParentState{ + table: mockTable, + } + }, + summary: dummySummary(t), + }, + { + description: "candidate_is_already_backed", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + candidate := getDummyCommittedCandidateReceipt(t) + hash, err := candidate.Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(&AttestedCandidate{ + GroupID: 4, + Candidate: candidate, + }, nil) + + return perRelayParentState{ + table: mockTable, + backed: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + } + }, + summary: dummySummary(t), + }, + { + description: "Validity_vote_from_unknown_validator", + rpState: func() perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(&AttestedCandidate{ + GroupID: 3, + Candidate: getDummyCommittedCandidateReceipt(t), + }, nil) + + return perRelayParentState{ + table: mockTable, + backed: map[parachaintypes.CandidateHash]bool{}, + tableContext: dummyTableContext(t), + } + }, + summary: dummySummary(t), + }, + { + description: "prospective_parachain_mode_is_disabled", + rpState: func() perRelayParentState { + return rpStateWhenPpmDisabled(t) + }, + summary: dummySummary(t), + }, + { + description: "prospective_parachain_mode_is_enabled", + rpState: func() perRelayParentState { + state := rpStateWhenPpmDisabled(t) + state.prospectiveParachainsMode = parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + } + return state + }, + summary: dummySummary(t), + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + subSystemToOverseer := make(chan any) + defer close(subSystemToOverseer) + + go mockOverseer(t, subSystemToOverseer) + + rpState := c.rpState() + rpState.postImportStatement(subSystemToOverseer, c.summary) + }) + } +} + +func TestKickOffValidationWork(t *testing.T) { + t.Parallel() + + attesting := attestingData{ + candidate: getDummyCommittedCandidateReceipt(t).ToPlain(), + } + + hash, err := attesting.candidate.Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + testCases := []struct { + description string + rpState perRelayParentState + }{ + { + description: "already_issued_statement_for_candidate", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + }, + { + description: "not_issued_statement_but_waiting_for_validation", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + subSystemToOverseer := make(chan any) + chRelayParentAndCommand := make(chan relayParentAndCommand) + pvd := parachaintypes.PersistedValidationData{} + + err := c.rpState.kickOffValidationWork(subSystemToOverseer, chRelayParentAndCommand, pvd, attesting) + require.NoError(t, err) + }) + } +} + +func TestBackgroundValidateAndMakeAvailable(t *testing.T) { + t.Parallel() + + var pvd parachaintypes.PersistedValidationData + candidateReceipt := getDummyCommittedCandidateReceipt(t).ToPlain() + + hash, err := candidateReceipt.Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + relayParent := getDummyHash(t, 5) + + testCases := []struct { + description string + rpState perRelayParentState + expectedErr string + mockOverseer func(ch chan any) + mockExecutorParamsGetter executorParamsGetter + }{ + { + description: "validation_process_already_started_for_candidate", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + expectedErr: "", + mockOverseer: func(ch chan any) {}, + mockExecutorParamsGetter: executorParamsAtRelayParent, + }, + { + description: "unable_to_get_validation_code", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + }, + expectedErr: "getting validation code by hash: ", + mockOverseer: func(ch chan any) { + data := <-ch + req, ok := data.(parachaintypes.RuntimeApiMessageRequest) + if !ok { + t.Errorf("invalid overseer message type: %T\n", data) + } + + req.RuntimeApiRequest.(parachaintypes.RuntimeApiRequestValidationCodeByHash). + Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]{ + Err: errors.New("mock error getting validation code"), + } + }, + mockExecutorParamsGetter: executorParamsAtRelayParent, + }, + { + description: "unable_to_get_executor_params", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + }, + expectedErr: "getting executor params at relay parent: ", + mockOverseer: func(ch chan any) { + data := <-ch + req, ok := data.(parachaintypes.RuntimeApiMessageRequest) + if !ok { + t.Errorf("invalid overseer message type: %T\n", data) + } + + req.RuntimeApiRequest.(parachaintypes.RuntimeApiRequestValidationCodeByHash). + Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]{ + Data: parachaintypes.ValidationCode{1, 2, 3}, + } + }, + mockExecutorParamsGetter: func(h common.Hash, c chan<- any) (parachaintypes.ExecutorParams, error) { + return parachaintypes.NewExecutorParams(), errors.New("mock error getting executor params") + }, + }, + { + description: "unable_to_get_validation_result", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + }, + expectedErr: "getting validation result: ", + mockOverseer: func(ch chan any) { + for data := range ch { + switch data := data.(type) { + case parachaintypes.RuntimeApiMessageRequest: + data.RuntimeApiRequest.(parachaintypes.RuntimeApiRequestValidationCodeByHash). + Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]{ + Data: parachaintypes.ValidationCode{1, 2, 3}, + } + case parachaintypes.CandidateValidationMessageValidateFromExhaustive: + data.Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationResult]{ + Err: errors.New("mock error getting validation result"), + } + default: + t.Errorf("invalid overseer message type: %T\n", data) + } + } + }, + mockExecutorParamsGetter: executorParamsAtRelayParent, + }, + { + description: "validation_result_is_invalid", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + }, + expectedErr: "", + mockOverseer: func(ch chan any) { + for data := range ch { + switch data := data.(type) { + case parachaintypes.RuntimeApiMessageRequest: + data.RuntimeApiRequest.(parachaintypes.RuntimeApiRequestValidationCodeByHash). + Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]{ + Data: parachaintypes.ValidationCode{1, 2, 3}, + } + case parachaintypes.CandidateValidationMessageValidateFromExhaustive: + data.Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationResult]{ + Data: parachaintypes.ValidationResult{ + IsValid: false, + Err: errors.New("mock error validating candidate"), + }, + } + default: + t.Errorf("invalid overseer message type: %T\n", data) + } + } + }, + mockExecutorParamsGetter: executorParamsAtRelayParent, + }, + { + description: "validation_result_is_valid", + rpState: perRelayParentState{ + issuedStatements: map[parachaintypes.CandidateHash]bool{}, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + }, + expectedErr: "", + mockOverseer: func(ch chan any) { + for data := range ch { + switch data := data.(type) { + case parachaintypes.RuntimeApiMessageRequest: + data.RuntimeApiRequest.(parachaintypes.RuntimeApiRequestValidationCodeByHash). + Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]{ + Data: parachaintypes.ValidationCode{1, 2, 3}, + } + case parachaintypes.CandidateValidationMessageValidateFromExhaustive: + data.Ch <- parachaintypes.OverseerFuncRes[parachaintypes.ValidationResult]{ + Data: parachaintypes.ValidationResult{ + IsValid: true, + }, + } + case availabilitystore.StoreAvailableData: + data.Sender <- errInvalidErasureRoot + default: + t.Errorf("invalid overseer message type: %T\n", data) + } + } + }, + mockExecutorParamsGetter: executorParamsAtRelayParent, + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + subSystemToOverseer := make(chan any) + chRelayParentAndCommand := make(chan relayParentAndCommand) + + go c.mockOverseer(subSystemToOverseer) + go func(chRelayParentAndCommand chan relayParentAndCommand) { + <-chRelayParentAndCommand + }(chRelayParentAndCommand) + + err := c.rpState.validateAndMakeAvailable( + c.mockExecutorParamsGetter, + subSystemToOverseer, + chRelayParentAndCommand, + candidateReceipt, + relayParent, + pvd, + parachaintypes.PoV{}, + 2, + attest, + candidateHash, + ) + + if c.expectedErr != "" { + require.ErrorContains(t, err, c.expectedErr) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestHandleStatementMessage(t *testing.T) { + t.Parallel() + + relayParent := getDummyHash(t, 5) + chRelayParentAndCommand := make(chan relayParentAndCommand) + + dummyCCR := getDummyCommittedCandidateReceipt(t) + seconded := parachaintypes.Seconded(dummyCCR) + + statementVDTSeconded := parachaintypes.NewStatementVDT() + err := statementVDTSeconded.Set(seconded) + require.NoError(t, err) + + hash, err := dummyCCR.Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + statementVDTValid := parachaintypes.NewStatementVDT() + err = statementVDTValid.Set(parachaintypes.Valid(candidateHash)) + require.NoError(t, err) + + testCases := []struct { + description string + perRelayParent func() map[common.Hash]*perRelayParentState + perCandidate map[parachaintypes.CandidateHash]*perCandidateState + signedStatementWithPVD SignedFullStatementWithPVD + err string + }{ + + { + description: "unknown_relay_parent", + perRelayParent: func() map[common.Hash]*perRelayParentState { + return map[common.Hash]*perRelayParentState{} + }, + signedStatementWithPVD: SignedFullStatementWithPVD{}, + err: errStatementForUnknownRelayParent.Error(), + }, + { + description: "nil_relay_parent", + perRelayParent: func() map[common.Hash]*perRelayParentState { + return map[common.Hash]*perRelayParentState{ + relayParent: nil, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{}, + err: errNilRelayParentState.Error(), + }, + { + description: "getting_error_importing_statement", + perRelayParent: func() map[common.Hash]*perRelayParentState { + return map[common.Hash]*perRelayParentState{ + relayParent: {}, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{}, + err: scale.ErrVaryingDataTypeNotSet.Error(), + }, + { + description: "getting_nil_summary_of_import_statement", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(nil, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + }, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + }, + }, + err: "", + }, + + { + description: "paraId_is_not_assigned_to_the_local_validator", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(nil, errors.New("could not get attested candidate from table")) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + assignment: 5, + }, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + }, + }, + err: "", + }, + + { + description: "statementVDT_set_to_valid_and_candidate_not_in_fallbacks", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{}, + fallbacks: map[parachaintypes.CandidateHash]attestingData{}, + }, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + }, + }, + err: errAttestingDataNotFound.Error(), + }, + + { + description: "statementVDT_set_to_valid_also_same_validatorIndex_in_tableContext_and_signedStatement", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + tableContext: dummyTableContext(t), + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{}, + fallbacks: map[parachaintypes.CandidateHash]attestingData{ + candidateHash: {}, + }, + }, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + ValidatorIndex: 1, + }, + }, + err: "", + }, + { + description: "statementVDT_set_to_valid_and_validation_job_already_running_for_candidate", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + tableContext: dummyTableContext(t), + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{}, + fallbacks: map[parachaintypes.CandidateHash]attestingData{ + candidateHash: {}, + }, + awaitingValidation: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + } + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + ValidatorIndex: 2, + }, + }, + err: "", + }, + { + description: "statementVDT_set_to_valid_and_start_validation_job_for_candidate", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + tableContext: dummyTableContext(t), + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{}, + fallbacks: map[parachaintypes.CandidateHash]attestingData{ + candidateHash: { + candidate: getDummyCommittedCandidateReceipt(t).ToPlain(), + }, + }, + awaitingValidation: map[parachaintypes.CandidateHash]bool{}, + issuedStatements: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + } + }, + + perCandidate: map[parachaintypes.CandidateHash]*perCandidateState{ + candidateHash: {}, + }, + signedStatementWithPVD: SignedFullStatementWithPVD{ + SignedFullStatement: parachaintypes.UncheckedSignedFullStatement{ + Payload: statementVDTValid, + ValidatorIndex: 2, + }, + }, + err: "", + }, + { + description: "statementVDT_set_to_seconded_and_error_getting_candidate_from_table", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + mockTable.EXPECT().getCandidate( + gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), + ).Return( + new(parachaintypes.CommittedCandidateReceipt), + errors.New("could not get candidate from table"), + ) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + fallbacks: map[parachaintypes.CandidateHash]attestingData{}, + issuedStatements: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + } + }, + signedStatementWithPVD: secondedSignedFullStatementWithPVD(t, statementVDTSeconded), + err: "could not get candidate from table", + }, + { + description: "statementVDT_set_to_seconded_and_successfully_get_candidate_from_table", + perRelayParent: func() map[common.Hash]*perRelayParentState { + ctrl := gomock.NewController(t) + mockTable := NewMockTable(ctrl) + + mockTable.EXPECT().importStatement( + gomock.AssignableToTypeOf(new(TableContext)), + gomock.AssignableToTypeOf(SignedFullStatementWithPVD{}), + ).Return(&Summary{ + Candidate: candidateHash, + GroupID: 4, + }, nil) + mockTable.EXPECT().drainMisbehaviors(). + Return([]parachaintypes.ProvisionableDataMisbehaviorReport{}) + mockTable.EXPECT().attestedCandidate( + gomock.AssignableToTypeOf(new(parachaintypes.CandidateHash)), + gomock.AssignableToTypeOf(new(TableContext)), + ).Return(new(AttestedCandidate), nil) + mockTable.EXPECT().getCandidate( + gomock.AssignableToTypeOf(parachaintypes.CandidateHash{}), + ).Return(&dummyCCR, nil) + + return map[common.Hash]*perRelayParentState{ + relayParent: { + table: mockTable, + assignment: 4, + backed: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + fallbacks: map[parachaintypes.CandidateHash]attestingData{}, + issuedStatements: map[parachaintypes.CandidateHash]bool{ + candidateHash: true, + }, + }, + } + + }, + signedStatementWithPVD: secondedSignedFullStatementWithPVD(t, statementVDTSeconded), + err: "", + }, + } + + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + subSystemToOverseer := make(chan any) + + backing := CandidateBacking{ + SubSystemToOverseer: subSystemToOverseer, + perRelayParent: c.perRelayParent(), + perCandidate: func() map[parachaintypes.CandidateHash]*perCandidateState { + if c.perCandidate == nil { + return map[parachaintypes.CandidateHash]*perCandidateState{} + } + return c.perCandidate + }(), + } + + defer close(subSystemToOverseer) + go mockOverseer(t, subSystemToOverseer) + + err := backing.handleStatementMessage(relayParent, c.signedStatementWithPVD, chRelayParentAndCommand) + if c.err == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.err) + } + }) + } +} diff --git a/dot/parachain/backing/mocks_generate_test.go b/dot/parachain/backing/mocks_generate_test.go new file mode 100644 index 0000000000..97b30c4324 --- /dev/null +++ b/dot/parachain/backing/mocks_generate_test.go @@ -0,0 +1,6 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Table diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go new file mode 100644 index 0000000000..c1930ef798 --- /dev/null +++ b/dot/parachain/backing/mocks_test.go @@ -0,0 +1,94 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/backing (interfaces: Table) + +// Package backing is a generated GoMock package. +package backing + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/parachain/types" + gomock "github.com/golang/mock/gomock" +) + +// MockTable is a mock of Table interface. +type MockTable struct { + ctrl *gomock.Controller + recorder *MockTableMockRecorder +} + +// MockTableMockRecorder is the mock recorder for MockTable. +type MockTableMockRecorder struct { + mock *MockTable +} + +// NewMockTable creates a new mock instance. +func NewMockTable(ctrl *gomock.Controller) *MockTable { + mock := &MockTable{ctrl: ctrl} + mock.recorder = &MockTableMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTable) EXPECT() *MockTableMockRecorder { + return m.recorder +} + +// attestedCandidate mocks base method. +func (m *MockTable) attestedCandidate(arg0 *types.CandidateHash, arg1 *TableContext) (*AttestedCandidate, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "attestedCandidate", arg0, arg1) + ret0, _ := ret[0].(*AttestedCandidate) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// attestedCandidate indicates an expected call of attestedCandidate. +func (mr *MockTableMockRecorder) attestedCandidate(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "attestedCandidate", reflect.TypeOf((*MockTable)(nil).attestedCandidate), arg0, arg1) +} + +// drainMisbehaviors mocks base method. +func (m *MockTable) drainMisbehaviors() []types.ProvisionableDataMisbehaviorReport { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "drainMisbehaviors") + ret0, _ := ret[0].([]types.ProvisionableDataMisbehaviorReport) + return ret0 +} + +// drainMisbehaviors indicates an expected call of drainMisbehaviors. +func (mr *MockTableMockRecorder) drainMisbehaviors() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "drainMisbehaviors", reflect.TypeOf((*MockTable)(nil).drainMisbehaviors)) +} + +// getCandidate mocks base method. +func (m *MockTable) getCandidate(arg0 types.CandidateHash) (*types.CommittedCandidateReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "getCandidate", arg0) + ret0, _ := ret[0].(*types.CommittedCandidateReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// getCandidate indicates an expected call of getCandidate. +func (mr *MockTableMockRecorder) getCandidate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getCandidate", reflect.TypeOf((*MockTable)(nil).getCandidate), arg0) +} + +// importStatement mocks base method. +func (m *MockTable) importStatement(arg0 *TableContext, arg1 SignedFullStatementWithPVD) (*Summary, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "importStatement", arg0, arg1) + ret0, _ := ret[0].(*Summary) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// importStatement indicates an expected call of importStatement. +func (mr *MockTableMockRecorder) importStatement(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "importStatement", reflect.TypeOf((*MockTable)(nil).importStatement), arg0, arg1) +} diff --git a/dot/parachain/backing/per_relay_parent_state.go b/dot/parachain/backing/per_relay_parent_state.go new file mode 100644 index 0000000000..e0e292e3ab --- /dev/null +++ b/dot/parachain/backing/per_relay_parent_state.go @@ -0,0 +1,408 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import ( + "errors" + "fmt" + + availabilitystore "github.com/ChainSafe/gossamer/dot/parachain/availability-store" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + + "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// PerRelayParentState represents the state information for a relay-parent in the subsystem. +type perRelayParentState struct { + prospectiveParachainsMode parachaintypes.ProspectiveParachainsMode + // The hash of the relay parent on top of which this job is doing it's work. + relayParent common.Hash + // The `ParaId` assigned to the local validator at this relay parent. + assignment parachaintypes.ParaID + // The table of candidates and statements under this relay-parent. + table Table + // The table context, including groups. + tableContext TableContext + // Data needed for retrying in case of `ValidatedCandidateCommand::AttestNoPoV`. + fallbacks map[parachaintypes.CandidateHash]attestingData + // These candidates are undergoing validation in the background. + awaitingValidation map[parachaintypes.CandidateHash]bool + // We issued `Seconded` or `Valid` statements on about these candidates. + issuedStatements map[parachaintypes.CandidateHash]bool + // The candidates that are backed by enough validators in their group, by hash. + backed map[parachaintypes.CandidateHash]bool +} + +// importStatement imports a statement into the statement table and returns the summary of the import. +func (rpState *perRelayParentState) importStatement( + subSystemToOverseer chan<- any, + signedStatementWithPVD SignedFullStatementWithPVD, + perCandidate map[parachaintypes.CandidateHash]*perCandidateState, +) (*Summary, error) { + statementVDT, err := signedStatementWithPVD.SignedFullStatement.Payload.Value() + if err != nil { + return nil, fmt.Errorf("getting value from statementVDT: %w", err) + } + + if statementVDT.Index() == 2 { // Valid + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) + } + + // PersistedValidationData should not be nil if the statementVDT is Seconded. + if signedStatementWithPVD.PersistedValidationData == nil { + return nil, fmt.Errorf("persisted validation data is nil") + } + + statementVDTSeconded := statementVDT.(parachaintypes.Seconded) + hash, err := parachaintypes.CommittedCandidateReceipt(statementVDTSeconded).Hash() + if err != nil { + return nil, fmt.Errorf("getting candidate hash: %w", err) + } + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + if _, ok := perCandidate[candidateHash]; ok { + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) + } + + if rpState.prospectiveParachainsMode.IsEnabled { + chIntroduceCandidate := make(chan error) + subSystemToOverseer <- parachaintypes.ProspectiveParachainsMessageIntroduceCandidate{ + IntroduceCandidateRequest: parachaintypes.IntroduceCandidateRequest{ + CandidateParaID: parachaintypes.ParaID(statementVDTSeconded.Descriptor.ParaID), + CommittedCandidateReceipt: parachaintypes.CommittedCandidateReceipt(statementVDTSeconded), + PersistedValidationData: *signedStatementWithPVD.PersistedValidationData, + }, + Ch: chIntroduceCandidate, + } + + introduceCandidateErr, ok := <-chIntroduceCandidate + if !ok { + return nil, fmt.Errorf("%w: %s", + errRejectedByProspectiveParachains, + "Could not reach the Prospective Parachains subsystem.", + ) + } + if introduceCandidateErr != nil { + return nil, fmt.Errorf("%w: %w", errRejectedByProspectiveParachains, introduceCandidateErr) + } + + subSystemToOverseer <- parachaintypes.ProspectiveParachainsMessageCandidateSeconded{ + ParaID: parachaintypes.ParaID(statementVDTSeconded.Descriptor.ParaID), + CandidateHash: candidateHash, + } + } + + // Only save the candidate if it was approved by prospective parachains. + perCandidate[candidateHash] = &perCandidateState{ + persistedValidationData: *signedStatementWithPVD.PersistedValidationData, + secondedLocally: false, // This is set after importing when seconding locally. + paraID: parachaintypes.ParaID(statementVDTSeconded.Descriptor.ParaID), + relayParent: statementVDTSeconded.Descriptor.RelayParent, + } + + return rpState.table.importStatement(&rpState.tableContext, signedStatementWithPVD) +} + +// postImportStatement handles a summary received from importStatement func and dispatches `Backed` notifications and +// misbehaviors as a result of importing a statement. +func (rpState *perRelayParentState) postImportStatement(subSystemToOverseer chan<- any, summary *Summary) { + // If the summary is nil, issue new misbehaviors and return. + if summary == nil { + issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) + return + } + + attested, err := rpState.table.attestedCandidate(&summary.Candidate, &rpState.tableContext) + if err != nil { + logger.Error(err.Error()) + } + + // If the candidate is not attested, issue new misbehaviors and return. + if attested == nil { + issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) + return + } + + hash, err := attested.Candidate.Hash() + if err != nil { + logger.Error(err.Error()) + return + } + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + // If the candidate is already backed, issue new misbehaviors and return. + if rpState.backed[candidateHash] { + issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) + return + } + + // Mark the candidate as backed. + rpState.backed[candidateHash] = true + + // Convert the attested candidate to a backed candidate. + backedCandidate := attestedToBackedCandidate(*attested, &rpState.tableContext) + if backedCandidate == nil { + issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) + return + } + + paraID := backedCandidate.Candidate.Descriptor.ParaID + + if rpState.prospectiveParachainsMode.IsEnabled { + + // Inform the prospective parachains subsystem that the candidate is now backed. + subSystemToOverseer <- parachaintypes.ProspectiveParachainsMessageCandidateBacked{ + ParaID: parachaintypes.ParaID(paraID), + CandidateHash: candidateHash, + } + + // Backed candidate potentially unblocks new advertisements, notify collator protocol. + subSystemToOverseer <- collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(paraID), + ParaHead: backedCandidate.Candidate.Descriptor.ParaHead, + } + + // Notify statement distribution of backed candidate. + subSystemToOverseer <- parachaintypes.StatementDistributionMessageBacked(candidateHash) + + } else { + // TODO: figure out what this comment means by 'avoid cycles'. + // + // The provisioner waits on candidate-backing, which means + // that we need to send unbounded messages to avoid cycles. + // + // Backed candidates are bounded by the number of validators, + // parachains, and the block production rate of the relay chain. + subSystemToOverseer <- parachaintypes.ProvisionerMessageProvisionableData{ + RelayParent: rpState.relayParent, + ProvisionableData: parachaintypes.ProvisionableDataBackedCandidate(backedCandidate.Candidate.ToPlain()), + } + } + + issueNewMisbehaviors(subSystemToOverseer, rpState.relayParent, rpState.table) +} + +// issueNewMisbehaviors checks for new misbehaviors and sends necessary messages to the Overseer subsystem. +func issueNewMisbehaviors(subSystemToOverseer chan<- any, relayParent common.Hash, table Table) { + // collect the misbehaviors to avoid double mutable self borrow issues + misbehaviors := table.drainMisbehaviors() + + for _, m := range misbehaviors { + // TODO: figure out what this comment means by 'avoid cycles'. + // + // The provisioner waits on candidate-backing, which means + // that we need to send unbounded messages to avoid cycles. + // + // Misbehaviors are bounded by the number of validators and + // the block production protocol. + subSystemToOverseer <- parachaintypes.ProvisionerMessageProvisionableData{ + RelayParent: relayParent, + ProvisionableData: parachaintypes.ProvisionableDataMisbehaviorReport{ + ValidatorIndex: m.ValidatorIndex, + Misbehaviour: m.Misbehaviour, + }, + } + } +} + +func attestedToBackedCandidate( + attested AttestedCandidate, + tableContext *TableContext, +) *parachaintypes.BackedCandidate { + group := tableContext.groups[attested.GroupID] + validatorIndices := make([]bool, len(group)) + var validityAttestations []parachaintypes.ValidityAttestation + + // The order of the validity votes in the backed candidate must match + // the order of bits set in the bitfield, which is not necessarily + // the order of the `validity_votes` we got from the table. + for positionInGroup, validatorIndex := range group { + for _, validityVote := range attested.ValidityVotes { + if validityVote.ValidatorIndex == validatorIndex { + validatorIndices[positionInGroup] = true + validityAttestations = append(validityAttestations, validityVote.ValidityAttestation) + } + } + + if !validatorIndices[positionInGroup] { + logger.Error("validity vote from unknown validator") + return nil + } + } + + return ¶chaintypes.BackedCandidate{ + Candidate: attested.Candidate, + ValidityVotes: validityAttestations, + ValidatorIndices: scale.NewBitVec(validatorIndices), + } +} + +// Kick off validation work and distribute the result as a signed statement. +func (rpState *perRelayParentState) kickOffValidationWork( + subSystemToOverseer chan<- any, + chRelayParentAndCommand chan relayParentAndCommand, + pvd parachaintypes.PersistedValidationData, + attesting attestingData, +) error { + hash, err := attesting.candidate.Hash() + if err != nil { + return fmt.Errorf("getting candidate hash: %w", err) + } + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + if rpState.issuedStatements[candidateHash] { + return nil + } + + pov := getPovFromValidator() + + return rpState.validateAndMakeAvailable( + executorParamsAtRelayParent, + subSystemToOverseer, + chRelayParentAndCommand, + attesting.candidate, + rpState.relayParent, + pvd, + pov, + uint32(len(rpState.tableContext.validators)), + attest, + candidateHash, + ) +} + +// this is temporary until we implement executorParamsAtRelayParent #3544 +type executorParamsGetter func(common.Hash, chan<- any) (parachaintypes.ExecutorParams, error) + +func (rpState *perRelayParentState) validateAndMakeAvailable( + executorParamsAtRelayParentFunc executorParamsGetter, // remove after executorParamsAtRelayParent is implemented #3544 + subSystemToOverseer chan<- any, + chRelayParentAndCommand chan relayParentAndCommand, + candidateReceipt parachaintypes.CandidateReceipt, + relayParent common.Hash, + pvd parachaintypes.PersistedValidationData, + pov parachaintypes.PoV, + numValidator uint32, + makeCommand validatedCandidateCommand, + candidateHash parachaintypes.CandidateHash, +) error { + if rpState.awaitingValidation[candidateHash] { + return nil + } + + rpState.awaitingValidation[candidateHash] = true + validationCodeHash := candidateReceipt.Descriptor.ValidationCodeHash + + chValidationCodeByHashRes := make(chan parachaintypes.OverseerFuncRes[parachaintypes.ValidationCode]) + subSystemToOverseer <- parachaintypes.RuntimeApiMessageRequest{ + RelayParent: relayParent, + RuntimeApiRequest: parachaintypes.RuntimeApiRequestValidationCodeByHash{ + ValidationCodeHash: validationCodeHash, + Ch: chValidationCodeByHashRes, + }, + } + + validationCodeByHashRes := <-chValidationCodeByHashRes + if validationCodeByHashRes.Err != nil { + return fmt.Errorf("getting validation code by hash: %w", validationCodeByHashRes.Err) + } + + // executorParamsAtRelayParent() should be called after it is implemented #3544 + executorParams, err := executorParamsAtRelayParentFunc(relayParent, subSystemToOverseer) + if err != nil { + return fmt.Errorf("getting executor params at relay parent: %w", err) + } + + pvfExecTimeoutKind := parachaintypes.NewPvfExecTimeoutKind() + err = pvfExecTimeoutKind.Set(parachaintypes.Backing{}) + if err != nil { + return fmt.Errorf("setting pvfExecTimeoutKind: %w", err) + } + + chValidationResultRes := make(chan parachaintypes.OverseerFuncRes[parachaintypes.ValidationResult]) + subSystemToOverseer <- parachaintypes.CandidateValidationMessageValidateFromExhaustive{ + PersistedValidationData: pvd, + ValidationCode: validationCodeByHashRes.Data, + CandidateReceipt: candidateReceipt, + PoV: pov, + ExecutorParams: executorParams, + PvfExecTimeoutKind: pvfExecTimeoutKind, + Ch: chValidationResultRes, + } + + validationResultRes := <-chValidationResultRes + if validationResultRes.Err != nil { + return fmt.Errorf("getting validation result: %w", validationResultRes.Err) + } + + var bgValidationResult backgroundValidationResult + + if validationResultRes.Data.IsValid { // Valid + // Important: the `av-store` subsystem will check if the erasure root of the `available_data` + // matches `expected_erasure_root` which was provided by the collator in the `CandidateReceipt`. + // This check is consensus critical and the `backing` subsystem relies on it for ensuring + // candidate validity. + + logger.Debugf("validation successful! candidateHash=%s", candidateHash) + + chStoreAvailableDataError := make(chan error) + subSystemToOverseer <- availabilitystore.StoreAvailableData{ + CandidateHash: candidateHash, + NumValidators: numValidator, + AvailableData: availabilitystore.AvailableData{ + PoV: pov, + ValidationData: pvd, + }, + ExpectedErasureRoot: candidateReceipt.Descriptor.ErasureRoot, + Sender: chStoreAvailableDataError, + } + + storeAvailableDataError := <-chStoreAvailableDataError + switch { + case storeAvailableDataError == nil: + bgValidationResult = backgroundValidationResult{ + candidateReceipt: &candidateReceipt, + candidateCommitments: &validationResultRes.Data.CandidateCommitments, + persistedValidationData: &validationResultRes.Data.PersistedValidationData, + err: nil, + } + case errors.Is(storeAvailableDataError, errInvalidErasureRoot): + logger.Debug(errInvalidErasureRoot.Error()) + + bgValidationResult = backgroundValidationResult{ + candidateReceipt: &candidateReceipt, + err: errInvalidErasureRoot, + } + default: + return fmt.Errorf("storing available data: %w", storeAvailableDataError) + } + + } else { // Invalid + logger.Error(validationResultRes.Data.Err.Error()) + bgValidationResult = backgroundValidationResult{ + candidateReceipt: &candidateReceipt, + err: validationResultRes.Data.Err, + } + } + + chRelayParentAndCommand <- relayParentAndCommand{ + relayParent: relayParent, + command: makeCommand, + validationRes: bgValidationResult, + candidateHash: candidateHash, + } + return nil +} + +func executorParamsAtRelayParent( + relayParent common.Hash, subSystemToOverseer chan<- any, +) (parachaintypes.ExecutorParams, error) { + // TODO: Implement this #3544 + // https://github.com/paritytech/polkadot-sdk/blob/7ca0d65f19497ac1c3c7ad6315f1a0acb2ca32f8/polkadot/node/subsystem-util/src/lib.rs#L241-L242 + return parachaintypes.ExecutorParams{}, nil +} diff --git a/dot/parachain/backing/statement_table.go b/dot/parachain/backing/statement_table.go new file mode 100644 index 0000000000..f7e0956347 --- /dev/null +++ b/dot/parachain/backing/statement_table.go @@ -0,0 +1,39 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + +type Table interface { + getCandidate(parachaintypes.CandidateHash) (*parachaintypes.CommittedCandidateReceipt, error) + importStatement(*TableContext, SignedFullStatementWithPVD) (*Summary, error) + attestedCandidate(*parachaintypes.CandidateHash, *TableContext) (*AttestedCandidate, error) + drainMisbehaviors() []parachaintypes.ProvisionableDataMisbehaviorReport +} + +// Summary represents summary of import of a statement. +type Summary struct { + // The digest of the candidate referenced. + Candidate parachaintypes.CandidateHash + // The group that the candidate is in. + GroupID parachaintypes.ParaID + // How many validity votes are currently witnessed. + ValidityVotes uint64 +} + +// AttestedCandidate represents an attested-to candidate. +type AttestedCandidate struct { + // The group ID that the candidate is in. + GroupID parachaintypes.ParaID + // The candidate data. + Candidate parachaintypes.CommittedCandidateReceipt + // Validity attestations. + ValidityVotes []validityVote +} + +// validityVote represents a vote on the validity of a candidate by a validator. +type validityVote struct { + ValidatorIndex parachaintypes.ValidatorIndex + ValidityAttestation parachaintypes.ValidityAttestation +} diff --git a/dot/parachain/backing/validated_candidate_command.go b/dot/parachain/backing/validated_candidate_command.go new file mode 100644 index 0000000000..d2919015db --- /dev/null +++ b/dot/parachain/backing/validated_candidate_command.go @@ -0,0 +1,44 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +// processValidatedCandidateCommand notes the result of a background validation of a candidate and reacts accordingly.. +func (cb *CandidateBacking) processValidatedCandidateCommand(rpAndCmd relayParentAndCommand) error { + // TODO: Implement this #3571 + return nil +} + +type backgroundValidationResult struct { + candidateReceipt *parachaintypes.CandidateReceipt + candidateCommitments *parachaintypes.CandidateCommitments + persistedValidationData *parachaintypes.PersistedValidationData + err error +} + +// relayParentAndCommand contains the relay parent and the command to be executed on validated candidate, +// along with the result of the background validation. +type relayParentAndCommand struct { + relayParent common.Hash + command validatedCandidateCommand + validationRes backgroundValidationResult + candidateHash parachaintypes.CandidateHash +} + +// validatedCandidateCommand represents commands for handling validated candidates. +// This is not a command to validate a candidate, but to react to a validation result. +type validatedCandidateCommand byte + +const ( + // We were instructed to second the candidate that has been already validated. + second = validatedCandidateCommand(iota) //nolint:unused + // We were instructed to validate the candidate. + attest + // We were not able to `Attest` because backing validator did not send us the PoV. + attestNoPoV //nolint:unused +) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 921518ac11..42a27185f7 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -195,7 +195,7 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( candidateHash parachaintypes.CandidateHash, parentHeadDataHash common.Hash, ) bool { - canSecondRequest := backing.CanSecond{ + canSecondRequest := backing.CanSecondMessage{ CandidateParaID: candidateParaID, CandidateRelayParent: candidateRelayParent, CandidateHash: candidateHash, @@ -206,7 +206,7 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( cpvs.SubSystemToOverseer <- struct { responseChan chan bool - canSecondRequest backing.CanSecond + canSecondRequest backing.CanSecondMessage }{ responseChan: responseChan, canSecondRequest: canSecondRequest, @@ -256,7 +256,7 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( logger.Error("candidate relay parent went out of view for valid advertisement") return ErrRelayParentUnknown } - if perRelayParent.prospectiveParachainMode.isEnabled { + if perRelayParent.prospectiveParachainMode.IsEnabled { return cpvs.fetchCollation(pendingCollation) } else { logger.Debug("a collation has already been seconded") @@ -334,7 +334,7 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo } // Note: Prospective Parachain mode would be set or edited when the view gets updated. - if perRelayParent.prospectiveParachainMode.isEnabled && prospectiveCandidate == nil { + if perRelayParent.prospectiveParachainMode.IsEnabled && prospectiveCandidate == nil { // Expected v2 advertisement. return ErrProtocolMismatch } @@ -366,7 +366,7 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo } /*NOTE:---------------------------------------Matters only in V2----------------------------------------------*/ - isSecondingAllowed := !perRelayParent.prospectiveParachainMode.isEnabled || cpvs.canSecond( + isSecondingAllowed := !perRelayParent.prospectiveParachainMode.IsEnabled || cpvs.canSecond( collatorParaID, relayParent, prospectiveCandidate.CandidateHash, diff --git a/dot/parachain/collator-protocol/message_test.go b/dot/parachain/collator-protocol/message_test.go index 82db19de53..3ed7f5b4a1 100644 --- a/dot/parachain/collator-protocol/message_test.go +++ b/dot/parachain/collator-protocol/message_test.go @@ -415,7 +415,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { peerData map[peer.ID]PeerData perRelayParent map[common.Hash]PerRelayParent net Network - activeLeaves map[common.Hash]ProspectiveParachainsMode + activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode errString string }{ { @@ -509,8 +509,8 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { perRelayParent: map[common.Hash]PerRelayParent{ testRelayParent: { assignment: &testParaID, - prospectiveParachainMode: ProspectiveParachainsMode{ - isEnabled: true, + prospectiveParachainMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, }, }, }, @@ -561,7 +561,7 @@ func TestHandleCollationMessageAdvertiseCollation(t *testing.T) { }, peerID) return net }(), - activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + activeLeaves: map[common.Hash]parachaintypes.ProspectiveParachainsMode{}, errString: ErrSecondedLimitReached.Error(), }, } @@ -608,10 +608,10 @@ func TestInsertAdvertisement(t *testing.T) { testCases := []struct { description string peerData PeerData - relayParentMode ProspectiveParachainsMode + relayParentMode parachaintypes.ProspectiveParachainsMode candidateHash *parachaintypes.CandidateHash implicitView ImplicitView - activeLeaves map[common.Hash]ProspectiveParachainsMode + activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode err error }{ { @@ -632,9 +632,9 @@ func TestInsertAdvertisement(t *testing.T) { PeerState: Collating, }, }, - relayParentMode: ProspectiveParachainsMode{}, + relayParentMode: parachaintypes.ProspectiveParachainsMode{}, candidateHash: nil, - activeLeaves: map[common.Hash]ProspectiveParachainsMode{}, + activeLeaves: map[common.Hash]parachaintypes.ProspectiveParachainsMode{}, err: ErrOutOfView, }, { @@ -651,11 +651,11 @@ func TestInsertAdvertisement(t *testing.T) { }, }, }, - relayParentMode: ProspectiveParachainsMode{}, + relayParentMode: parachaintypes.ProspectiveParachainsMode{}, candidateHash: nil, - activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + activeLeaves: map[common.Hash]parachaintypes.ProspectiveParachainsMode{ relayParent: { - isEnabled: false, + IsEnabled: false, }, }, err: ErrDuplicateAdvertisement, @@ -672,11 +672,11 @@ func TestInsertAdvertisement(t *testing.T) { }, }, }, - relayParentMode: ProspectiveParachainsMode{}, + relayParentMode: parachaintypes.ProspectiveParachainsMode{}, candidateHash: &candidateHash, - activeLeaves: map[common.Hash]ProspectiveParachainsMode{ + activeLeaves: map[common.Hash]parachaintypes.ProspectiveParachainsMode{ relayParent: { - isEnabled: false, + IsEnabled: false, }, }, }, diff --git a/dot/parachain/collator-protocol/messages/overseer_messages.go b/dot/parachain/collator-protocol/messages/overseer_messages.go new file mode 100644 index 0000000000..ed29c3fe89 --- /dev/null +++ b/dot/parachain/collator-protocol/messages/overseer_messages.go @@ -0,0 +1,43 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package messages + +import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +type CollateOn parachaintypes.ParaID + +type DistributeCollation struct { + CandidateReceipt parachaintypes.CandidateReceipt + PoV parachaintypes.PoV +} + +type ReportCollator parachaintypes.CollatorID + +type NetworkBridgeUpdate struct { + // TODO: not quite sure if we would need this or something similar to this +} + +// Seconded represents that the candidate we recommended to be seconded was validated +// successfully. +type Seconded struct { + Parent common.Hash + Stmt parachaintypes.UncheckedSignedFullStatement +} + +// Backed message indicates that the candidate received enough validity votes from the backing group. +type Backed struct { + ParaID parachaintypes.ParaID + // Hash of the para head generated by candidate + ParaHead common.Hash +} + +// Invalid represents an invalid candidata. +// We recommended a particular candidate to be seconded, but it was invalid; penalise the collator. +type Invalid struct { + Parent common.Hash + CandidateReceipt parachaintypes.CandidateReceipt +} diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index d1feb167f8..7ecfb3b0ec 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ChainSafe/gossamer/dot/network" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/internal/log" @@ -47,7 +48,7 @@ var ( ) func (cpvs CollatorProtocolValidatorSide) Run( - ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error { + ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) { inactivityTicker := time.NewTicker(activityPoll) for { @@ -55,7 +56,7 @@ func (cpvs CollatorProtocolValidatorSide) Run( // TODO: polkadot-rust changes reputation in batches, so we do the same? case msg, ok := <-cpvs.OverseerToSubSystem: if !ok { - return nil + return } err := cpvs.processMessage(msg) @@ -86,7 +87,6 @@ func (cpvs CollatorProtocolValidatorSide) Run( if err := cpvs.ctx.Err(); err != nil { logger.Errorf("ctx error: %v\n", err) } - return nil } } } @@ -189,19 +189,19 @@ func (peerData *PeerData) SetCollating(collatorID parachaintypes.CollatorID, par func IsRelayParentInImplicitView( relayParent common.Hash, - relayParentMode ProspectiveParachainsMode, + relayParentMode parachaintypes.ProspectiveParachainsMode, implicitView ImplicitView, - activeLeaves map[common.Hash]ProspectiveParachainsMode, + activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode, paraID parachaintypes.ParaID, ) bool { - if !relayParentMode.isEnabled { + if !relayParentMode.IsEnabled { _, ok := activeLeaves[relayParent] return ok } for hash, mode := range activeLeaves { knownAllowedRelayParent := implicitView.KnownAllowedRelayParentsUnder(hash, paraID) - if mode.isEnabled && knownAllowedRelayParent.String() == relayParent.String() { + if mode.IsEnabled && knownAllowedRelayParent.String() == relayParent.String() { return true } } @@ -214,10 +214,10 @@ func IsRelayParentInImplicitView( // declared itself a collator. func (peerData *PeerData) InsertAdvertisement( onRelayParent common.Hash, - relayParentMode ProspectiveParachainsMode, + relayParentMode parachaintypes.ProspectiveParachainsMode, candidateHash *parachaintypes.CandidateHash, implicitView ImplicitView, - activeLeaves map[common.Hash]ProspectiveParachainsMode, + activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode, ) (isAdvertisementInvalid bool, err error) { switch peerData.state.PeerState { case Connected: @@ -228,14 +228,14 @@ func (peerData *PeerData) InsertAdvertisement( return false, ErrOutOfView } - if relayParentMode.isEnabled { + if relayParentMode.IsEnabled { // relayParentMode.maxCandidateDepth candidates, ok := peerData.state.CollatingPeerState.advertisements[onRelayParent] if ok && slices.Contains[[]parachaintypes.CandidateHash](candidates, *candidateHash) { return false, ErrDuplicateAdvertisement } - if len(candidates) > int(relayParentMode.maxCandidateDepth) { + if len(candidates) > int(relayParentMode.MaxCandidateDepth) { return false, ErrPeerLimitReached } candidates = append(candidates, *candidateHash) @@ -350,7 +350,7 @@ type CollatorProtocolValidatorSide struct { /// support prospective parachains. This mapping works as a replacement for /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. - activeLeaves map[common.Hash]ProspectiveParachainsMode + activeLeaves map[common.Hash]parachaintypes.ProspectiveParachainsMode // Collations that we have successfully requested from peers and waiting // on validation. @@ -372,23 +372,8 @@ func (f fetchedCollationInfo) String() string { f.relayParent.String(), f.paraID, f.candidateHash.Value.String(), f.collatorID) } -// Prospective parachains mode of a relay parent. Defined by -// the Runtime API version. -// -// Needed for the period of transition to asynchronous backing. -type ProspectiveParachainsMode struct { - // if disabled, there are no prospective parachains. Runtime API does not have support for `async_backing_params` - isEnabled bool - - // these values would be present only if `isEnabled` is true - - // The maximum number of para blocks between the para head in a relay parent and a new candidate. - // Restricts nodes from building arbitrary long chains and spamming other validators. - maxCandidateDepth uint -} - type PerRelayParent struct { - prospectiveParachainMode ProspectiveParachainsMode + prospectiveParachainMode parachaintypes.ProspectiveParachainsMode assignment *parachaintypes.ParaID collations Collations } @@ -403,10 +388,10 @@ type Collations struct { } // IsSecondedLimitReached check the limit of seconded candidates for a given para has been reached. -func (collations Collations) IsSecondedLimitReached(relayParentMode ProspectiveParachainsMode) bool { +func (collations Collations) IsSecondedLimitReached(relayParentMode parachaintypes.ProspectiveParachainsMode) bool { var secondedLimit uint - if relayParentMode.isEnabled { - secondedLimit = relayParentMode.maxCandidateDepth + 1 + if relayParentMode.IsEnabled { + secondedLimit = relayParentMode.MaxCandidateDepth + 1 } else { secondedLimit = 1 } @@ -425,47 +410,15 @@ func (cpvs CollatorProtocolValidatorSide) getPeerIDFromCollatorID(collatorID par return "", false } -type CollateOn parachaintypes.ParaID - -type DistributeCollation struct { - CandidateReceipt parachaintypes.CandidateReceipt - PoV parachaintypes.PoV -} - -type ReportCollator parachaintypes.CollatorID - -type NetworkBridgeUpdate struct { - // TODO: not quite sure if we would need this or something similar to this -} - -// SecondedOverseerMsg represents that the candidate we recommended to be seconded was validated successfully. -type SecondedOverseerMsg struct { - Parent common.Hash - Stmt parachaintypes.UncheckedSignedFullStatement -} - -type Backed struct { - ParaID parachaintypes.ParaID - // Hash of the para head generated by candidate - ParaHead common.Hash -} - -// InvalidOverseerMsg represents an invalid candidata. -// We recommended a particular candidate to be seconded, but it was invalid; penalise the collator. -type InvalidOverseerMsg struct { - Parent common.Hash - CandidateReceipt parachaintypes.CandidateReceipt -} - func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // run this function as a goroutine, ideally switch msg := msg.(type) { - case CollateOn: + case collatorprotocolmessages.CollateOn: return fmt.Errorf("CollateOn %w", ErrNotExpectedOnValidatorSide) - case DistributeCollation: + case collatorprotocolmessages.DistributeCollation: return fmt.Errorf("DistributeCollation %w", ErrNotExpectedOnValidatorSide) - case ReportCollator: + case collatorprotocolmessages.ReportCollator: peerID, ok := cpvs.getPeerIDFromCollatorID(parachaintypes.CollatorID(msg)) if !ok { return ErrPeerIDNotFoundForCollator @@ -474,10 +427,10 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { Value: peerset.ReportBadCollatorValue, Reason: peerset.ReportBadCollatorReason, }, peerID) - case NetworkBridgeUpdate: + case collatorprotocolmessages.NetworkBridgeUpdate: // TODO: handle network message https://github.com/ChainSafe/gossamer/issues/3515 // https://github.com/paritytech/polkadot-sdk/blob/db3fd687262c68b115ab6724dfaa6a71d4a48a59/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1457 //nolint - case SecondedOverseerMsg: + case collatorprotocolmessages.Seconded: statementV, err := msg.Stmt.Payload.Value() if err != nil { return fmt.Errorf("getting value of statement: %w", err) @@ -550,9 +503,9 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // TODO: Few more things for async backing, but we don't have async backing yet // https://github.com/paritytech/polkadot-sdk/blob/7035034710ecb9c6a786284e5f771364c520598d/polkadot/node/network/collator-protocol/src/validator_side/mod.rs#L1531-L1532 } - case Backed: + case collatorprotocolmessages.Backed: // TODO: handle backed message https://github.com/ChainSafe/gossamer/issues/3517 - case InvalidOverseerMsg: + case collatorprotocolmessages.Invalid: invalidOverseerMsg := msg fetchedCollation, err := newFetchedCollationInfo(msg.CandidateReceipt) diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index eb75df028a..eb815c7412 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -6,6 +6,7 @@ package collatorprotocol import ( "testing" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" "github.com/ChainSafe/gossamer/lib/common" @@ -58,22 +59,22 @@ func TestProcessOverseerMessage(t *testing.T) { }{ { description: "CollateOn message fails with message not expected", - msg: CollateOn(2), + msg: collatorprotocolmessages.CollateOn(2), errString: ErrNotExpectedOnValidatorSide.Error(), }, { description: "DistributeCollation message fails with message not expected", - msg: DistributeCollation{}, + msg: collatorprotocolmessages.DistributeCollation{}, errString: ErrNotExpectedOnValidatorSide.Error(), }, { description: "ReportCollator message fails with peer not found for collator", - msg: ReportCollator(testCollatorID), + msg: collatorprotocolmessages.ReportCollator(testCollatorID), errString: ErrPeerIDNotFoundForCollator.Error(), }, { description: "ReportCollator message succeeds and reports a bad collator", - msg: ReportCollator(testCollatorID), + msg: collatorprotocolmessages.ReportCollator(testCollatorID), net: func() Network { ctrl := gomock.NewController(t) net := NewMockNetwork(ctrl) @@ -100,7 +101,7 @@ func TestProcessOverseerMessage(t *testing.T) { }, { description: "InvalidOverseerMsg message fails with peer not found for collator", - msg: InvalidOverseerMsg{ + msg: collatorprotocolmessages.Invalid{ Parent: testRelayParent, CandidateReceipt: testCandidateReceipt, }, @@ -122,7 +123,7 @@ func TestProcessOverseerMessage(t *testing.T) { }, { description: "InvalidOverseerMsg message succeeds, reports a bad collator and removes fetchedCandidate", - msg: InvalidOverseerMsg{ + msg: collatorprotocolmessages.Invalid{ Parent: testRelayParent, CandidateReceipt: testCandidateReceipt, }, @@ -166,7 +167,7 @@ func TestProcessOverseerMessage(t *testing.T) { }, { description: "SecondedOverseerMsg message fails with peer not found for collator and removes fetchedCandidate", - msg: SecondedOverseerMsg{ + msg: collatorprotocolmessages.Seconded{ Parent: testRelayParent, Stmt: func() parachaintypes.UncheckedSignedFullStatement { vdt := parachaintypes.NewStatementVDT() @@ -198,7 +199,7 @@ func TestProcessOverseerMessage(t *testing.T) { }, { description: "SecondedOverseerMsg message succceds, reports a good collator and removes fetchedCandidate", - msg: SecondedOverseerMsg{ + msg: collatorprotocolmessages.Seconded{ Parent: testRelayParent, Stmt: func() parachaintypes.UncheckedSignedFullStatement { vdt := parachaintypes.NewStatementVDT() diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 9d24727028..2db2326022 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -11,7 +11,7 @@ import ( availability_store "github.com/ChainSafe/gossamer/dot/parachain/availability-store" "github.com/ChainSafe/gossamer/dot/parachain/backing" - collatorprotocol "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" @@ -87,10 +87,7 @@ func (o *Overseer) Start() error { for subsystem, overseerToSubSystem := range o.subsystems { o.wg.Add(1) go func(sub Subsystem, overseerToSubSystem chan any) { - err := sub.Run(o.ctx, overseerToSubSystem, o.SubsystemsToOverseer) - if err != nil { - logger.Errorf("running subsystem %v failed: %v", sub, err) - } + sub.Run(o.ctx, overseerToSubSystem, o.SubsystemsToOverseer) logger.Infof("subsystem %v stopped", sub) o.wg.Done() }(subsystem, overseerToSubSystem) @@ -110,12 +107,12 @@ func (o *Overseer) processMessages() { var subsystem Subsystem switch msg.(type) { - case backing.GetBackedCandidates, backing.CanSecond, backing.Second, backing.Statement: + case backing.GetBackedCandidatesMessage, backing.CanSecondMessage, backing.SecondMessage, backing.StatementMessage: subsystem = o.nameToSubsystem[parachaintypes.CandidateBacking] - case collatorprotocol.CollateOn, collatorprotocol.DistributeCollation, collatorprotocol.ReportCollator, - collatorprotocol.Backed, collatorprotocol.AdvertiseCollation, collatorprotocol.InvalidOverseerMsg, - collatorprotocol.SecondedOverseerMsg: + case collatorprotocolmessages.CollateOn, collatorprotocolmessages.DistributeCollation, + collatorprotocolmessages.ReportCollator, collatorprotocolmessages.Backed, + collatorprotocolmessages.Invalid, collatorprotocolmessages.Seconded: subsystem = o.nameToSubsystem[parachaintypes.CollationProtocol] diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index acc1788d58..67448b199e 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -25,7 +25,7 @@ func (s *TestSubsystem) Name() parachaintypes.SubSystemName { return parachaintypes.SubSystemName(s.name) } -func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error { +func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) { fmt.Printf("%s run\n", s.name) counter := 0 for { @@ -35,7 +35,7 @@ func (s *TestSubsystem) Run(ctx context.Context, OverseerToSubSystem chan any, S fmt.Printf("%s ctx error: %v\n", s.name, err) } fmt.Printf("%s overseer stopping\n", s.name) - return nil + return case overseerSignal := <-OverseerToSubSystem: fmt.Printf("%s received from overseer %v\n", s.name, overseerSignal) default: diff --git a/dot/parachain/overseer/types.go b/dot/parachain/overseer/types.go index 3e2d1d6150..9a5b49a961 100644 --- a/dot/parachain/overseer/types.go +++ b/dot/parachain/overseer/types.go @@ -12,7 +12,7 @@ import ( // Subsystem is an interface for subsystems to be registered with the overseer. type Subsystem interface { // Run runs the subsystem. - Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) error + Run(ctx context.Context, OverseerToSubSystem chan any, SubSystemToOverseer chan any) Name() parachaintypes.SubSystemName ProcessActiveLeavesUpdateSignal() ProcessBlockFinalizedSignal() diff --git a/dot/parachain/types/executor_params.go b/dot/parachain/types/executor_params.go new file mode 100644 index 0000000000..ee137ee8ef --- /dev/null +++ b/dot/parachain/types/executor_params.go @@ -0,0 +1,239 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// ExecutorParams represents the abstract semantics of an execution environment and should remain +// as abstract as possible. There are no mandatory parameters defined at the moment, and if any +// are introduced in the future, they must be clearly documented as mandatory. +type ExecutorParams scale.VaryingDataTypeSlice + +// NewExecutorParams returns a new ExecutorParams varying data type slice +func NewExecutorParams() ExecutorParams { + vdt := NewExecutorParam() + vdts := scale.NewVaryingDataTypeSlice(scale.VaryingDataType(vdt)) + return ExecutorParams(vdts) +} + +// Add takes variadic parameter values to add VaryingDataTypeValue +func (e *ExecutorParams) Add(val scale.VaryingDataTypeValue) (err error) { + slice := scale.VaryingDataTypeSlice(*e) + err = slice.Add(val) + if err != nil { + return fmt.Errorf("adding value to varying data type slice: %w", err) + } + + *e = ExecutorParams(slice) + return nil +} + +// ExecutorParam represents the various parameters for modifying the semantics of the execution environment. +type ExecutorParam scale.VaryingDataType + +// NewExecutorParam returns a new ExecutorParam varying data type +func NewExecutorParam() ExecutorParam { + vdt := scale.MustNewVaryingDataType( + MaxMemoryPages(0), + StackLogicalMax(0), + StackNativeMax(0), + PrecheckingMaxMemory(0), + PvfPrepTimeout{}, + PvfExecTimeout{}, + WasmExtBulkMemory{}, + ) + return ExecutorParam(vdt) +} + +// New will enable scale to create new instance when needed +func (ExecutorParam) New() ExecutorParam { + return NewExecutorParam() +} + +// Set will set a value using the underlying varying data type +func (s *ExecutorParam) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*s) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *s = ExecutorParam(vdt) + return nil +} + +// Value returns the value from the underlying varying data type +func (s *ExecutorParam) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*s) + return vdt.Value() +} + +// MaxMemoryPages represents the maximum number of memory pages (64KiB bytes per page) that the executor can allocate. +type MaxMemoryPages uint32 + +// Index returns the index of varying data type +func (MaxMemoryPages) Index() uint { + return 1 +} + +// StackLogicalMax defines the limit for the logical stack size in Wasm (maximum number of Wasm values on the stack). +type StackLogicalMax uint32 + +// Index returns the index of varying data type +func (StackLogicalMax) Index() uint { + return 2 +} + +// StackNativeMax represents the limit of the executor's machine stack size in bytes. +type StackNativeMax uint32 + +// Index returns the index of varying data type +func (StackNativeMax) Index() uint { + return 3 +} + +// PrecheckingMaxMemory represents the maximum memory allowance for the preparation worker during pre-checking, +// measured in bytes. +type PrecheckingMaxMemory uint64 + +// Index returns the index of varying data type +func (PrecheckingMaxMemory) Index() uint { + return 4 +} + +// PvfPrepTimeout defines the timeouts for PVF preparation in milliseconds. +type PvfPrepTimeout struct { + PvfPrepTimeoutKind PvfPrepTimeoutKind `scale:"1"` + Millisec uint64 `scale:"2"` +} + +// Index returns the index of varying data type +func (PvfPrepTimeout) Index() uint { + return 5 +} + +// PvfExecTimeout represents the timeouts for PVF execution in milliseconds. +type PvfExecTimeout struct { + PvfExecTimeoutKind PvfExecTimeoutKind `scale:"1"` + Millisec uint64 `scale:"2"` +} + +// Index returns the index of varying data type +func (PvfExecTimeout) Index() uint { + return 6 +} + +// WasmExtBulkMemory enables the WASM bulk memory proposal. +type WasmExtBulkMemory struct{} + +// Index returns the index of varying data type +func (WasmExtBulkMemory) Index() uint { + return 7 +} + +// PvfPrepTimeoutKind is an enumeration representing the type discriminator for PVF preparation timeouts +type PvfPrepTimeoutKind scale.VaryingDataType + +// NewPvfPrepTimeoutKind returns a new PvfPrepTimeoutKind varying data type +func NewPvfPrepTimeoutKind() PvfPrepTimeoutKind { + vdt := scale.MustNewVaryingDataType(Precheck{}, Lenient{}) + return PvfPrepTimeoutKind(vdt) +} + +// New will enable scale to create new instance when needed +func (PvfPrepTimeoutKind) New() PvfPrepTimeoutKind { + return NewPvfPrepTimeoutKind() +} + +// Set will set a value using the underlying varying data type +func (p *PvfPrepTimeoutKind) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*p) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *p = PvfPrepTimeoutKind(vdt) + return nil +} + +// Value returns the value from the underlying varying data type +func (s *PvfPrepTimeoutKind) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*s) + return vdt.Value() +} + +// Precheck defines the time period for prechecking requests. After this duration, +// an unresponsive preparation worker is considered and will be terminated. +type Precheck struct{} + +// Index returns the index of varying data type +func (Precheck) Index() uint { + return 0 +} + +// Lenient refers to the time period for execution and heads-up requests. It is the duration +// after which the preparation worker is deemed unresponsive and terminated. This timeout +// is more forgiving than the prechecking timeout to avoid honest validators timing out on valid PVFs. +type Lenient struct{} + +// Index returns the index of varying data type +func (Lenient) Index() uint { + return 1 +} + +// PvfExecTimeoutKind is an enumeration representing the type discriminator for PVF execution timeouts +type PvfExecTimeoutKind scale.VaryingDataType + +// NewPvfExecTimeoutKind returns a new PvfExecTimeoutKind varying data type +func NewPvfExecTimeoutKind() PvfExecTimeoutKind { + vdt := scale.MustNewVaryingDataType(Backing{}, Approval{}) + return PvfExecTimeoutKind(vdt) +} + +// New will enable scale to create new instance when needed +func (PvfExecTimeoutKind) New() PvfExecTimeoutKind { + return NewPvfExecTimeoutKind() +} + +// Set will set a value using the underlying varying data type +func (s *PvfExecTimeoutKind) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*s) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + + *s = PvfExecTimeoutKind(vdt) + return nil +} + +// Value returns the value from the underlying varying data type +func (s *PvfExecTimeoutKind) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*s) + return vdt.Value() +} + +// Backing represents the amount of time to spend on execution during backing. +type Backing struct{} + +// Index returns the index of varying data type +func (Backing) Index() uint { + return 0 +} + +// Approval represents the amount of time to spend on execution during approval or disputes. +// This timeout should be much longer than the backing execution timeout to ensure that, +// in the absence of extremely large disparities between hardware, blocks that pass +// backing are considered executable by approval checkers or dispute participants. +type Approval struct{} + +// Index returns the index of varying data type +func (Approval) Index() uint { + return 1 +} diff --git a/dot/parachain/types/misbehavior.go b/dot/parachain/types/misbehavior.go new file mode 100644 index 0000000000..35c0ff9d67 --- /dev/null +++ b/dot/parachain/types/misbehavior.go @@ -0,0 +1,95 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +var ( + _ Misbehaviour = (*MultipleCandidates)(nil) + _ Misbehaviour = (*UnauthorizedStatement)(nil) + _ Misbehaviour = (*IssuedAndValidity)(nil) + _ Misbehaviour = (*OnSeconded)(nil) + _ Misbehaviour = (*OnValidity)(nil) + _ DoubleSign = (*OnSeconded)(nil) + _ DoubleSign = (*OnValidity)(nil) + _ ValidityDoubleVote = (*IssuedAndValidity)(nil) +) + +// Misbehaviour is intended to represent different kinds of misbehaviour along with supporting proofs. +type Misbehaviour interface { + IsMisbehaviour() +} + +// ValidityDoubleVote misbehaviour: voting more than one way on candidate validity. +// Since there are three possible ways to vote, a double vote is possible in +// three possible combinations (unordered) +type ValidityDoubleVote interface { + Misbehaviour + IsValidityDoubleVote() +} + +// IssuedAndValidity represents an implicit vote by issuing and explicit voting for validity. +type IssuedAndValidity struct { + CommittedCandidateReceiptAndSign CommittedCandidateReceiptAndSign + CandidateHashAndSign struct { + CandidateHash CandidateHash + Signature ValidatorSignature + } +} + +func (IssuedAndValidity) IsMisbehaviour() {} +func (IssuedAndValidity) IsValidityDoubleVote() {} + +// CommittedCandidateReceiptAndSign combines a committed candidate receipt and its associated signature. +type CommittedCandidateReceiptAndSign struct { + CommittedCandidateReceipt CommittedCandidateReceipt + Signature ValidatorSignature +} + +// MultipleCandidates misbehaviour: declaring multiple candidates. +type MultipleCandidates struct { + First CommittedCandidateReceiptAndSign + Second CommittedCandidateReceiptAndSign +} + +func (MultipleCandidates) IsMisbehaviour() {} + +// SignedStatement represents signed statements about candidates. +type SignedStatement struct { + Statement StatementVDT `scale:"1"` + Signature ValidatorSignature `scale:"2"` + Sender ValidatorIndex `scale:"3"` +} + +// UnauthorizedStatement misbehaviour: submitted statement for wrong group. +type UnauthorizedStatement struct { + // A signed statement which was submitted without proper authority. + Statement SignedStatement +} + +func (UnauthorizedStatement) IsMisbehaviour() {} + +// DoubleSign misbehaviour: multiple signatures on same statement. +type DoubleSign interface { + Misbehaviour + IsDoubleSign() +} + +// OnSeconded represents a double sign on a candidate. +type OnSeconded struct { + Candidate CommittedCandidateReceipt + Sign1 ValidatorSignature + Sign2 ValidatorSignature +} + +func (OnSeconded) IsMisbehaviour() {} +func (OnSeconded) IsDoubleSign() {} + +// OnValidity represents a double sign on validity. +type OnValidity struct { + CandidateHash CandidateHash + Sign1 ValidatorSignature + Sign2 ValidatorSignature +} + +func (OnValidity) IsMisbehaviour() {} +func (OnValidity) IsDoubleSign() {} diff --git a/dot/parachain/types/overseer_message.go b/dot/parachain/types/overseer_message.go new file mode 100644 index 0000000000..0c59ffed2a --- /dev/null +++ b/dot/parachain/types/overseer_message.go @@ -0,0 +1,126 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +import "github.com/ChainSafe/gossamer/lib/common" + +var ( + _ ProvisionableData = (*ProvisionableDataBackedCandidate)(nil) + _ ProvisionableData = (*ProvisionableDataMisbehaviorReport)(nil) + _ RuntimeApiRequest = (*RuntimeApiRequestValidationCodeByHash)(nil) +) + +// OverseerFuncRes is a result of an overseer function +type OverseerFuncRes[T any] struct { + Err error + Data T +} + +// ProvisionerMessageProvisionableData is a provisioner message. +// This data should become part of a relay chain block. +type ProvisionerMessageProvisionableData struct { + RelayParent common.Hash + ProvisionableData ProvisionableData +} + +// ProvisionableData becomes intrinsics or extrinsics which should be included in a future relay chain block. +type ProvisionableData interface { + IsProvisionableData() +} + +// ProvisionableDataBackedCandidate is a provisionable data. +// The Candidate Backing subsystem believes that this candidate is valid, pending availability. +type ProvisionableDataBackedCandidate CandidateReceipt + +func (ProvisionableDataBackedCandidate) IsProvisionableData() {} + +// ProvisionableDataMisbehaviorReport represents self-contained proofs of validator misbehaviour. +type ProvisionableDataMisbehaviorReport struct { + ValidatorIndex ValidatorIndex + Misbehaviour Misbehaviour +} + +func (ProvisionableDataMisbehaviorReport) IsProvisionableData() {} + +// StatementDistributionMessageBacked is a statement distribution message. +// it represents a message indicating that a candidate has received sufficient +// validity votes from the backing group. If backed as a result of a local statement, +// it must be preceded by a `Share` message for that statement to ensure awareness of +// full candidates before the `Backed` notification, even in groups of size 1. +type StatementDistributionMessageBacked CandidateHash + +// ProspectiveParachainsMessageCandidateBacked is a prospective parachains message. +// it informs the Prospective Parachains Subsystem that +// a previously introduced candidate has been successfully backed. +type ProspectiveParachainsMessageCandidateBacked struct { + ParaID ParaID + CandidateHash CandidateHash +} + +// ProspectiveParachainsMessageIntroduceCandidate is a prospective parachains message. +// it inform the Prospective Parachains Subsystem about a new candidate. +type ProspectiveParachainsMessageIntroduceCandidate struct { + IntroduceCandidateRequest IntroduceCandidateRequest + Ch chan error +} + +// ProspectiveParachainsMessageCandidateSeconded is a prospective parachains message. +// it informs the Prospective Parachains Subsystem that a previously introduced candidate +// has been seconded. This requires that the candidate was successfully introduced in +// the past. +type ProspectiveParachainsMessageCandidateSeconded struct { + ParaID ParaID + CandidateHash CandidateHash +} + +// IntroduceCandidateRequest is a request to introduce a candidate into the Prospective Parachains Subsystem. +type IntroduceCandidateRequest struct { + // The para-id of the candidate. + CandidateParaID ParaID + // The candidate receipt itself. + CommittedCandidateReceipt CommittedCandidateReceipt + // The persisted validation data of the candidate. + PersistedValidationData PersistedValidationData +} + +type RuntimeApiMessageRequest struct { + RelayParent common.Hash + // Make a request of the runtime API against the post-state of the given relay-parent. + RuntimeApiRequest RuntimeApiRequest +} + +type RuntimeApiRequest interface { + IsRuntimeApiRequest() +} + +// RuntimeApiRequestValidationCodeByHash retrieves validation code by its hash. It can return +// past, current, or future code as long as state is available. +type RuntimeApiRequestValidationCodeByHash struct { + ValidationCodeHash ValidationCodeHash + Ch chan OverseerFuncRes[ValidationCode] +} + +func (RuntimeApiRequestValidationCodeByHash) IsRuntimeApiRequest() {} + +// CandidateValidationMessageValidateFromExhaustive performs full validation of a candidate with provided parameters, +// including `PersistedValidationData` and `ValidationCode`. It doesn't involve acceptance +// criteria checking and is typically used when the candidate's validity is established +// through prior relay-chain checks. +type CandidateValidationMessageValidateFromExhaustive struct { + PersistedValidationData PersistedValidationData + ValidationCode ValidationCode + CandidateReceipt CandidateReceipt + PoV PoV + ExecutorParams ExecutorParams + PvfExecTimeoutKind PvfExecTimeoutKind + Ch chan OverseerFuncRes[ValidationResult] +} + +// ValidationResult represents the result coming from the candidate validation subsystem. +type ValidationResult struct { + IsValid bool + CandidateCommitments CandidateCommitments + PersistedValidationData PersistedValidationData + Err error +} diff --git a/dot/parachain/types/overseer_signals.go b/dot/parachain/types/overseer_signals.go index 2c5d143570..6799479c92 100644 --- a/dot/parachain/types/overseer_signals.go +++ b/dot/parachain/types/overseer_signals.go @@ -1,3 +1,6 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + package parachaintypes import ( diff --git a/dot/parachain/types/statement_test.go b/dot/parachain/types/statement_test.go index 4fcd230b47..45c4043e22 100644 --- a/dot/parachain/types/statement_test.go +++ b/dot/parachain/types/statement_test.go @@ -5,7 +5,6 @@ package parachaintypes import ( _ "embed" - "errors" "fmt" "math" "testing" @@ -29,9 +28,6 @@ func init() { } } -var ErrInvalidVayingDataTypeValue = errors.New( - "setting value to varying data type: unsupported VaryingDataTypeValue: {} (parachaintypes.invalidVayingDataTypeValue)") - type invalidVayingDataTypeValue struct{} func (invalidVayingDataTypeValue) Index() uint { @@ -103,7 +99,7 @@ func TestStatementVDT(t *testing.T) { { name: "invalid struct", enumValue: invalidVayingDataTypeValue{}, - expectedErr: ErrInvalidVayingDataTypeValue, + expectedErr: scale.ErrUnsupportedVaryingDataTypeValue, }, } @@ -118,7 +114,7 @@ func TestStatementVDT(t *testing.T) { err := vdt.Set(c.enumValue) if c.expectedErr != nil { - require.EqualError(t, err, c.expectedErr.Error()) + require.ErrorContains(t, err, c.expectedErr.Error()) return } diff --git a/dot/parachain/types/subsystem_names.go b/dot/parachain/types/subsystems.go similarity index 100% rename from dot/parachain/types/subsystem_names.go rename to dot/parachain/types/subsystems.go diff --git a/dot/parachain/types/subsystems_test.go b/dot/parachain/types/subsystems_test.go new file mode 100644 index 0000000000..9ad9bfd7e8 --- /dev/null +++ b/dot/parachain/types/subsystems_test.go @@ -0,0 +1,145 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +import ( + "testing" + + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +var testCasesExecutorParam = []struct { + name string + enumValue scale.VaryingDataTypeValue + encodingValue []byte + expectedErr error +}{ + { + name: "MaxMemoryPages", + enumValue: MaxMemoryPages(9), + encodingValue: []byte{1, 9, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "StackLogicalMax", + enumValue: StackLogicalMax(8), + encodingValue: []byte{2, 8, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "StackNativeMax", + enumValue: StackNativeMax(7), + encodingValue: []byte{3, 7, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "PrecheckingMaxMemory", + enumValue: PrecheckingMaxMemory(6), + encodingValue: []byte{4, 6, 0, 0, 0, 0, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "PvfPrepTimeout", + enumValue: PvfPrepTimeout{ + PvfPrepTimeoutKind: func() PvfPrepTimeoutKind { + kind := NewPvfPrepTimeoutKind() + if err := kind.Set(Lenient{}); err != nil { + panic(err) + } + + return kind + }(), + Millisec: 5, + }, + encodingValue: []byte{5, 1, 5, 0, 0, 0, 0, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "PvfExecTimeout", + enumValue: PvfExecTimeout{ + PvfExecTimeoutKind: func() PvfExecTimeoutKind { + kind := NewPvfExecTimeoutKind() + if err := kind.Set(Approval{}); err != nil { + panic(err) + } + + return kind + }(), + Millisec: 4, + }, + encodingValue: []byte{6, 1, 4, 0, 0, 0, 0, 0, 0, 0}, + expectedErr: nil, + }, + { + name: "invalid_struct", + enumValue: invalidVayingDataTypeValue{}, + expectedErr: scale.ErrUnsupportedVaryingDataTypeValue, + }, +} + +func TestExecutorParam(t *testing.T) { + t.Parallel() + + for _, c := range testCasesExecutorParam { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + t.Run("marshal", func(t *testing.T) { + t.Parallel() + + vdt := NewExecutorParam() + err := vdt.Set(c.enumValue) + + if c.expectedErr != nil { + require.ErrorContains(t, err, c.expectedErr.Error()) + return + } + + require.NoError(t, err) + bytes, err := scale.Marshal(vdt) + require.NoError(t, err) + + require.Equal(t, c.encodingValue, bytes) + }) + + t.Run("unmarshal", func(t *testing.T) { + t.Parallel() + + if c.expectedErr != nil { + return + } + + vdt := NewExecutorParam() + err := scale.Unmarshal(c.encodingValue, &vdt) + require.NoError(t, err) + + actualData, err := vdt.Value() + require.NoError(t, err) + + require.EqualValues(t, c.enumValue, actualData) + }) + }) + } +} + +func TestExecutorParams(t *testing.T) { + t.Parallel() + + for _, c := range testCasesExecutorParam { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + params := NewExecutorParams() + err := params.Add(c.enumValue) + + if c.expectedErr == nil { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.expectedErr.Error()) + } + }) + } +} diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index e95bd9c273..899cd44b5a 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -263,6 +263,10 @@ func (ccr CommittedCandidateReceipt) ToPlain() CandidateReceipt { } } +func (c CommittedCandidateReceipt) Hash() (common.Hash, error) { + return c.ToPlain().Hash() +} + // AssignmentID The public key of a keypair used by a validator for determining assignments // to approve included parachain candidates. type AssignmentID [sr25519.PublicKeyLength]byte @@ -554,5 +558,46 @@ type Collation struct { // ValidatorSignature represents the signature with which parachain validators sign blocks. type ValidatorSignature Signature +func (v ValidatorSignature) String() string { return Signature(v).String() } + // Signature represents a cryptographic signature. type Signature [64]byte + +func (s Signature) String() string { return fmt.Sprintf("0x%x", s[:]) } + +// BackedCandidate is a backed (or backable, depending on context) candidate. +type BackedCandidate struct { + // The candidate referred to. + Candidate CommittedCandidateReceipt `scale:"1"` + // The validity votes themselves, expressed as signatures. + ValidityVotes []ValidityAttestation `scale:"2"` + // The indices of the validators within the group, expressed as a bitfield. + ValidatorIndices scale.BitVec `scale:"3"` // TODO: it's a bitvec in rust, figure out actual type +} + +type ProspectiveParachainsMode struct { + // Runtime API without support of `async_backing_params`: no prospective parachains. + // v6 runtime API: prospective parachains. + // NOTE: MaxCandidateDepth and AllowedAncestryLen need to be set if this is enabled. + IsEnabled bool + + // The maximum number of para blocks between the para head in a relay parent + // and a new candidate. Restricts nodes from building arbitrary long chains + // and spamming other validators. + MaxCandidateDepth uint + // How many ancestors of a relay parent are allowed to build candidates on top of. + AllowedAncestryLen uint +} + +// UncheckedSignedAvailabilityBitfield a signed bitfield with signature not yet checked +type UncheckedSignedAvailabilityBitfield struct { + // The payload is part of the signed data. The rest is the signing context, + // which is known both at signing and at validation. + Payload scale.BitVec `scale:"1"` + + // The index of the validator signing this statement. + ValidatorIndex ValidatorIndex `scale:"2"` + + // The signature by the validator of the signed payload. + Signature ValidatorSignature `scale:"3"` +} diff --git a/dot/parachain/types/validity_attestation.go b/dot/parachain/types/validity_attestation.go new file mode 100644 index 0000000000..40ab842505 --- /dev/null +++ b/dot/parachain/types/validity_attestation.go @@ -0,0 +1,63 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +import ( + "fmt" + + "github.com/ChainSafe/gossamer/pkg/scale" +) + +// ValidityAttestation is an implicit or explicit attestation to the validity of a parachain +// candidate. +type ValidityAttestation scale.VaryingDataType + +// Set will set a VaryingDataTypeValue using the underlying VaryingDataType +func (va *ValidityAttestation) Set(val scale.VaryingDataTypeValue) (err error) { + // cast to VaryingDataType to use VaryingDataType.Set method + vdt := scale.VaryingDataType(*va) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value to varying data type: %w", err) + } + // store original ParentVDT with VaryingDataType that has been set + *va = ValidityAttestation(vdt) + return nil +} + +// Value returns the value from the underlying VaryingDataType +func (va *ValidityAttestation) Value() (scale.VaryingDataTypeValue, error) { + vdt := scale.VaryingDataType(*va) + return vdt.Value() +} + +// Implicit is for Implicit attestation. +type Implicit ValidatorSignature + +// Index returns VDT index +func (Implicit) Index() uint { + return 1 +} + +func (i Implicit) String() string { //skipcq:SCC-U1000 + return fmt.Sprintf("implicit(%s)", ValidatorSignature(i)) +} + +// Explicit is for Explicit attestation. +type Explicit ValidatorSignature //skipcq + +// Index returns VDT index +func (Explicit) Index() uint { //skipcq + return 2 +} + +func (e Explicit) String() string { //skipcq:SCC-U1000 + return fmt.Sprintf("explicit(%s)", ValidatorSignature(e)) +} + +// NewValidityAttestation creates a ValidityAttestation varying data type. +func NewValidityAttestation() ValidityAttestation { + vdt := scale.MustNewVaryingDataType(Implicit{}, Explicit{}) + return ValidityAttestation(vdt) +} diff --git a/dot/parachain/types/validity_attestation_test.go b/dot/parachain/types/validity_attestation_test.go new file mode 100644 index 0000000000..60cd7282cd --- /dev/null +++ b/dot/parachain/types/validity_attestation_test.go @@ -0,0 +1,54 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package parachaintypes + +import ( + "testing" + + "github.com/ChainSafe/gossamer/pkg/scale" + "github.com/stretchr/testify/require" +) + +func TestValidityAttestation(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + enumValue scale.VaryingDataTypeValue + encodingValue []byte + }{ + { + name: "Implicit", + enumValue: Implicit(ValidatorSignature{}), + encodingValue: []byte{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, //nolint:lll + }, + { + name: "Explicit", + enumValue: Explicit(ValidatorSignature{}), + encodingValue: []byte{0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, //nolint:lll + }, + } + + for _, c := range testCases { + c := c + t.Run(c.name, func(t *testing.T) { + t.Parallel() + + validityAttestation := NewValidityAttestation() + err := validityAttestation.Set(c.enumValue) + require.NoError(t, err) + + bytes, err := scale.Marshal(validityAttestation) + require.NoError(t, err) + + require.Equal(t, c.encodingValue, bytes) + + newDst := NewValidityAttestation() + err = scale.Unmarshal(bytes, &newDst) + require.NoError(t, err) + + require.Equal(t, validityAttestation, newDst) + }) + } +} diff --git a/dot/parachain/validation_protocol.go b/dot/parachain/validation_protocol.go index 74d10ce5bb..f00d2b9fea 100644 --- a/dot/parachain/validation_protocol.go +++ b/dot/parachain/validation_protocol.go @@ -15,23 +15,10 @@ import ( const MaxValidationMessageSize uint64 = 100 * 1024 -// UncheckedSignedAvailabilityBitfield a signed bitfield with signature not yet checked -type UncheckedSignedAvailabilityBitfield struct { - // The payload is part of the signed data. The rest is the signing context, - // which is known both at signing and at validation. - Payload scale.BitVec `scale:"1"` - - // The index of the validator signing this statement. - ValidatorIndex parachaintypes.ValidatorIndex `scale:"2"` - - // The signature by the validator of the signed payload. - Signature parachaintypes.ValidatorSignature `scale:"3"` -} - // Bitfield avalibility bitfield for given relay-parent hash type Bitfield struct { - Hash common.Hash `scale:"1"` - UncheckedSignedAvailabilityBitfield UncheckedSignedAvailabilityBitfield `scale:"2"` + Hash common.Hash `scale:"1"` + UncheckedSignedAvailabilityBitfield parachaintypes.UncheckedSignedAvailabilityBitfield `scale:"2"` } // Index returns the VaryingDataType Index diff --git a/dot/parachain/validation_protocol_test.go b/dot/parachain/validation_protocol_test.go index 23b3eb809f..51d1ab79cb 100644 --- a/dot/parachain/validation_protocol_test.go +++ b/dot/parachain/validation_protocol_test.go @@ -244,7 +244,7 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) { bitfieldDistribution := BitfieldDistribution{NewBitfieldDistributionMessageVDT()} bitfieldDistribution.Set(Bitfield{ Hash: hashA, - UncheckedSignedAvailabilityBitfield: UncheckedSignedAvailabilityBitfield{ + UncheckedSignedAvailabilityBitfield: parachaintypes.UncheckedSignedAvailabilityBitfield{ Payload: scale.NewBitVec([]bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}), diff --git a/lib/babe/inherents/parachain_inherents.go b/lib/babe/inherents/parachain_inherents.go index 39004b98bc..cfdb1e7a50 100644 --- a/lib/babe/inherents/parachain_inherents.go +++ b/lib/babe/inherents/parachain_inherents.go @@ -1,79 +1,17 @@ -// Copyright 2022 ChainSafe Systems (ON) // SPDX-License-Identifier: LGPL-3.0-only +// Copyright 2022 ChainSafe Systems (ON) package inherents import ( "fmt" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" - "github.com/ChainSafe/gossamer/lib/crypto/sr25519" "github.com/ChainSafe/gossamer/pkg/scale" ) -// signature could be one of Ed25519 signature, Sr25519 signature or ECDSA/SECP256k1 signature. -type signature [64]byte - -func (s signature) String() string { return fmt.Sprintf("0x%x", s[:]) } - -// validityAttestation is an implicit or explicit attestation to the validity of a parachain -// candidate. -type validityAttestation scale.VaryingDataType - -// Set will set a VaryingDataTypeValue using the underlying VaryingDataType -func (va *validityAttestation) Set(val scale.VaryingDataTypeValue) (err error) { - // cast to VaryingDataType to use VaryingDataType.Set method - vdt := scale.VaryingDataType(*va) - err = vdt.Set(val) - if err != nil { - return fmt.Errorf("setting value to varying data type: %w", err) - } - // store original ParentVDT with VaryingDataType that has been set - *va = validityAttestation(vdt) - return nil -} - -// Value returns the value from the underlying VaryingDataType -func (va *validityAttestation) Value() (scale.VaryingDataTypeValue, error) { - vdt := scale.VaryingDataType(*va) - return vdt.Value() -} - -// implicit is for implicit attestation. -type implicit validatorSignature //skipcq - -// Index returns VDT index -func (implicit) Index() uint { //skipcq - return 1 -} - -func (i implicit) String() string { //skipcq:SCC-U1000 - return fmt.Sprintf("implicit(%s)", validatorSignature(i)) -} - -// explicit is for explicit attestation. -type explicit validatorSignature //skipcq - -// Index returns VDT index -func (explicit) Index() uint { //skipcq - return 2 -} - -func (e explicit) String() string { //skipcq:SCC-U1000 - return fmt.Sprintf("explicit(%s)", validatorSignature(e)) -} - -// newValidityAttestation creates a ValidityAttestation varying data type. -func newValidityAttestation() validityAttestation { //skipcq - vdt, err := scale.NewVaryingDataType(implicit{}, explicit{}) - if err != nil { - panic(err) - } - - return validityAttestation(vdt) -} - // disputeStatement is a statement about a candidate, to be used within the dispute // resolution process. Statements are either in favour of the candidate's validity // or against it. @@ -240,124 +178,14 @@ func newDisputeStatement() disputeStatement { //skipcq return disputeStatement(vdt) } -// collatorID is the collator's relay-chain account ID -type collatorID sr25519.PublicKey - -// collatorSignature is the signature on a candidate's block data signed by a collator. -type collatorSignature signature - -// validationCodeHash is the blake2-256 hash of the validation code bytes. -type validationCodeHash common.Hash - -// candidateDescriptor is a unique descriptor of the candidate receipt. -type candidateDescriptor struct { - // The ID of the para this is a candidate for. - ParaID uint32 `scale:"1"` - - // RelayParent is the hash of the relay-chain block this should be executed in - // the context of. - // NOTE: the fact that the hash includes this value means that code depends - // on this for deduplication. Removing this field is likely to break things. - RelayParent common.Hash `scale:"2"` - - // Collator is the collator's relay-chain account ID - Collator collatorID `scale:"3"` - - // PersistedValidationDataHash is the blake2-256 hash of the persisted validation data. This is extra data derived from - // relay-chain state which may vary based on bitfields included before the candidate. - // Thus it cannot be derived entirely from the relay-parent. - PersistedValidationDataHash common.Hash `scale:"4"` - - // PovHash is the hash of the `pov-block`. - PovHash common.Hash `scale:"5"` - // ErasureRoot is the root of a block's erasure encoding Merkle tree. - ErasureRoot common.Hash `scale:"6"` - - // Signature on blake2-256 of components of this receipt: - // The parachain index, the relay parent, the validation data hash, and the `pov_hash`. - Signature collatorSignature `scale:"7"` - - // ParaHead is the hash of the para header that is being generated by this candidate. - ParaHead common.Hash `scale:"8"` - // ValidationCodeHash is the blake2-256 hash of the validation code bytes. - ValidationCodeHash validationCodeHash `scale:"9"` -} - -// upwardMessage is a message from a parachain to its Relay Chain. -type upwardMessage []byte - -// outboundHrmpMessage is an HRMP message seen from the perspective of a sender. -type outboundHrmpMessage struct { - Recipient uint32 `scale:"1"` - Data []byte `scale:"2"` -} - -// validationCode is Parachain validation code. -type validationCode []byte - -// headData is Parachain head data included in the chain. -type headData []byte - -// candidateCommitments are Commitments made in a `CandidateReceipt`. Many of these are outputs of validation. -type candidateCommitments struct { - // Messages destined to be interpreted by the Relay chain itself. - UpwardMessages []upwardMessage `scale:"1"` - // Horizontal messages sent by the parachain. - HorizontalMessages []outboundHrmpMessage `scale:"2"` - // New validation code. - NewValidationCode *validationCode `scale:"3"` - // The head-data produced as a result of execution. - HeadData headData `scale:"4"` - // The number of messages processed from the DMQ. - ProcessedDownwardMessages uint32 `scale:"5"` - // The mark which specifies the block number up to which all inbound HRMP messages are processed. - HrmpWatermark uint32 `scale:"6"` -} - -// committedCandidateReceipt is a candidate-receipt with commitments directly included. -type committedCandidateReceipt struct { - Descriptor candidateDescriptor `scale:"1"` - Commitments candidateCommitments `scale:"2"` -} - -// uncheckedSignedAvailabilityBitfield is a set of unchecked signed availability bitfields. -// Should be sorted by validator index, ascending. -type uncheckedSignedAvailabilityBitfield struct { - // The payload is part of the signed data. The rest is the signing context, - // which is known both at signing and at validation. - Payload []byte `scale:"1"` - // The index of the validator signing this statement. - ValidatorIndex uint32 `scale:"2"` - // The signature by the validator of the signed payload. - Signature signature `scale:"3"` -} - -// backedCandidate is a backed (or backable, depending on context) candidate. -type backedCandidate struct { - // The candidate referred to. - Candidate committedCandidateReceipt `scale:"1"` - // The validity votes themselves, expressed as signatures. - ValidityVotes []validityAttestation `scale:"2"` - // The indices of the validators within the group, expressed as a bitfield. - ValidatorIndices []byte `scale:"3"` -} - // multiDisputeStatementSet is a set of dispute statements. type multiDisputeStatementSet []disputeStatementSet -// validatorIndex is the index of the validator. -type validatorIndex uint32 - -// validatorSignature is the signature with which parachain validators sign blocks. -type validatorSignature signature - -func (v validatorSignature) String() string { return signature(v).String() } - // statement about the candidate. // Used as translation of `Vec<(DisputeStatement, ValidatorIndex, ValidatorSignature)>` from rust to go type statement struct { - ValidatorIndex validatorIndex - ValidatorSignature validatorSignature + ValidatorIndex parachaintypes.ValidatorIndex + ValidatorSignature parachaintypes.ValidatorSignature DisputeStatement disputeStatement } @@ -374,9 +202,9 @@ type disputeStatementSet struct { // ParachainInherentData is parachains inherent-data passed into the runtime by a block author. type ParachainInherentData struct { // Signed bitfields by validators about availability. - Bitfields []uncheckedSignedAvailabilityBitfield `scale:"1"` + Bitfields []parachaintypes.UncheckedSignedAvailabilityBitfield `scale:"1"` // Backed candidates for inclusion in the block. - BackedCandidates []backedCandidate `scale:"2"` + BackedCandidates []parachaintypes.BackedCandidate `scale:"2"` // Sets of dispute votes for inclusion, Disputes multiDisputeStatementSet `scale:"3"` // The parent block header. Used for checking state proofs. diff --git a/lib/babe/inherents/parachain_inherents_test.go b/lib/babe/inherents/parachain_inherents_test.go index 820949d5b8..e063bae568 100644 --- a/lib/babe/inherents/parachain_inherents_test.go +++ b/lib/babe/inherents/parachain_inherents_test.go @@ -6,6 +6,7 @@ package inherents import ( "testing" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/ChainSafe/gossamer/pkg/scale" @@ -217,12 +218,12 @@ func TestValidityAttestation(t *testing.T) { }{ { name: "Implicit", - enumValue: implicit(validatorSignature{}), + enumValue: parachaintypes.Implicit(parachaintypes.ValidatorSignature{}), encodingValue: []byte{0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, //nolint:lll }, { name: "Explicit", - enumValue: explicit(validatorSignature{}), + enumValue: parachaintypes.Explicit(parachaintypes.ValidatorSignature{}), encodingValue: []byte{0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, //nolint:lll }, } @@ -232,7 +233,7 @@ func TestValidityAttestation(t *testing.T) { t.Run(c.name, func(t *testing.T) { t.Parallel() - validityAttestation := newValidityAttestation() + validityAttestation := parachaintypes.NewValidityAttestation() err := validityAttestation.Set(c.enumValue) require.NoError(t, err) @@ -241,7 +242,7 @@ func TestValidityAttestation(t *testing.T) { require.Equal(t, c.encodingValue, bytes) - newDst := newValidityAttestation() + newDst := parachaintypes.NewValidityAttestation() err = scale.Unmarshal(bytes, &newDst) require.NoError(t, err) From b131ff9387de16875dc05e142cebdb908719bdfc Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Tue, 23 Jan 2024 19:24:53 +0530 Subject: [PATCH 69/85] chore(dot/parachain/overseer): fix `TestHandleBlockEvents` the flaky test (#3707) --- dot/parachain/overseer/overseer.go | 4 -- dot/parachain/overseer/overseer_test.go | 53 ++++++++++++------------- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 2db2326022..f56c9565c1 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -232,10 +232,6 @@ func (o *Overseer) Stop() error { // close the errorChan to unblock any listeners on the errChan close(o.errChan) - for _, sub := range o.subsystems { - close(sub) - } - // wait for subsystems to stop // TODO: determine reasonable timeout duration for production, currently this is just for testing timedOut := waitTimeout(&o.wg, 500*time.Millisecond) diff --git a/dot/parachain/overseer/overseer_test.go b/dot/parachain/overseer/overseer_test.go index 67448b199e..35017abdd8 100644 --- a/dot/parachain/overseer/overseer_test.go +++ b/dot/parachain/overseer/overseer_test.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "math/rand" + "sync" "sync/atomic" "testing" "time" @@ -88,39 +89,20 @@ func TestHandleBlockEvents(t *testing.T) { var finalizedCounter atomic.Int32 var importedCounter atomic.Int32 + var wg sync.WaitGroup + wg.Add(4) // number of subsystems * 2 + + // mocked subsystems go func() { for { select { case msg := <-overseerToSubSystem1: - if msg == nil { - continue - } - - _, ok := msg.(parachaintypes.BlockFinalizedSignal) - if ok { - finalizedCounter.Add(1) - } - - _, ok = msg.(parachaintypes.ActiveLeavesUpdateSignal) - if ok { - importedCounter.Add(1) - } + go incrementCounters(t, msg, &finalizedCounter, &importedCounter) + wg.Done() case msg := <-overseerToSubSystem2: - if msg == nil { - continue - } - - _, ok := msg.(parachaintypes.BlockFinalizedSignal) - if ok { - finalizedCounter.Add(1) - } - - _, ok = msg.(parachaintypes.ActiveLeavesUpdateSignal) - if ok { - importedCounter.Add(1) - } + go incrementCounters(t, msg, &finalizedCounter, &importedCounter) + wg.Done() } - } }() @@ -129,7 +111,7 @@ func TestHandleBlockEvents(t *testing.T) { finalizedNotifierChan <- &types.FinalisationInfo{} importedBlockNotiferChan <- &types.Block{} - time.Sleep(1000 * time.Millisecond) + wg.Wait() err = overseer.Stop() require.NoError(t, err) @@ -137,3 +119,18 @@ func TestHandleBlockEvents(t *testing.T) { require.Equal(t, int32(2), finalizedCounter.Load()) require.Equal(t, int32(2), importedCounter.Load()) } + +func incrementCounters(t *testing.T, msg any, finalizedCounter *atomic.Int32, importedCounter *atomic.Int32) { + t.Helper() + + if msg == nil { + return + } + + switch msg.(type) { + case parachaintypes.BlockFinalizedSignal: + finalizedCounter.Add(1) + case parachaintypes.ActiveLeavesUpdateSignal: + importedCounter.Add(1) + } +} From 19a437a1d419d53d0d70d97aa2993942e6e13733 Mon Sep 17 00:00:00 2001 From: Axay Sagathiya Date: Tue, 23 Jan 2024 19:52:12 +0530 Subject: [PATCH 70/85] feat(dot/parachain/backing): handle CanSecond incoming overseer message (#3600) - implements logic in the Candidate Backing Subsystem to handle canSecond message. - The subsystem checks if it's allowed to second a candidate based on specific rules: - collations must be built on the root of a fragment tree or have a parent node representing a backed candidate. - If async backing is disabled for the candidate's relay parent, the response is always false. --- .../backing/backing_implicit_view.go | 17 + dot/parachain/backing/can_second.go | 179 +++++++++ dot/parachain/backing/can_second_test.go | 347 ++++++++++++++++++ dot/parachain/backing/candidate_backing.go | 22 +- dot/parachain/backing/mocks_generate_test.go | 2 +- dot/parachain/backing/mocks_test.go | 40 +- dot/parachain/types/overseer_message.go | 95 ++++- dot/parachain/types/types.go | 21 +- go.mod | 1 + go.sum | 2 + tests/rpc/system_integration_test.go | 2 + 11 files changed, 705 insertions(+), 23 deletions(-) create mode 100644 dot/parachain/backing/backing_implicit_view.go create mode 100644 dot/parachain/backing/can_second.go create mode 100644 dot/parachain/backing/can_second_test.go diff --git a/dot/parachain/backing/backing_implicit_view.go b/dot/parachain/backing/backing_implicit_view.go new file mode 100644 index 0000000000..681a2394d3 --- /dev/null +++ b/dot/parachain/backing/backing_implicit_view.go @@ -0,0 +1,17 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +// NOTE: this is a temp file, will be a separate package for backing implicit view +package backing + +import ( + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" +) + +// ImplicitView handles the implicit view of the relay chain derived from the immediate/explicit view, +// which is composed of active leaves, and the minimum relay-parents allowed for candidates of various +// parachains at those leaves +type ImplicitView interface { + knownAllowedRelayParentsUnder(blockHash common.Hash, paraID parachaintypes.ParaID) []common.Hash +} diff --git a/dot/parachain/backing/can_second.go b/dot/parachain/backing/can_second.go new file mode 100644 index 0000000000..8902b92fcc --- /dev/null +++ b/dot/parachain/backing/can_second.go @@ -0,0 +1,179 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import ( + "errors" + "fmt" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" + "golang.org/x/exp/slices" +) + +var ( + errUnknwnRelayParent = errors.New("unknown relay parent") + errProspectiveParachainsModeDisabled = errors.New("async backing is disabled") + errCandidateNotRecognised = errors.New("candidate not recognised by any fragment tree") + errLeafOccupied = errors.New("can't second the candidate, leaf is already occupied") + errNoActiveLeaves = errors.New("no active leaves found for the candidate relay parent") + errDepthOccupied = errors.New("can't second the candidate, depth is already occupied") +) + +// handleCanSecondMessage performs seconding sanity check for an advertisement. +func (cb *CandidateBacking) handleCanSecondMessage(msg CanSecondMessage) error { + rpState, ok := cb.perRelayParent[msg.CandidateRelayParent] + if !ok { + msg.ResponseCh <- false + return fmt.Errorf("%w: %s", errUnknwnRelayParent, msg.CandidateRelayParent.String()) + } + + if rpState == nil { + msg.ResponseCh <- false + return fmt.Errorf("%w; relay parent: %s", errNilRelayParentState, msg.CandidateRelayParent.String()) + } + + ppMode := rpState.prospectiveParachainsMode + if !ppMode.IsEnabled { + msg.ResponseCh <- false + return fmt.Errorf("%w; relay parent: %s", errProspectiveParachainsModeDisabled, msg.CandidateRelayParent.String()) + } + + hypotheticalCandidate := parachaintypes.HypotheticalCandidateIncomplete{ + CandidateHash: msg.CandidateHash, + CandidateParaID: msg.CandidateParaID, + ParentHeadDataHash: msg.ParentHeadDataHash, + RelayParent: msg.CandidateRelayParent, + } + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + if err != nil { + msg.ResponseCh <- false + return err + } + + for _, fragmentTree := range membership { + // candidate should be recognised by at least some fragment tree. + if len(fragmentTree) != 0 { + msg.ResponseCh <- true + return nil + } + } + return fmt.Errorf("%w; candidate hash: %s", errCandidateNotRecognised, msg.CandidateHash.Value) +} + +// secondingSanityCheck checks whether a candidate can be seconded based on its +// hypothetical frontiers in the fragment tree and what we've already seconded in +// all active leaves. +// +// If the candidate can be seconded, returns nil error and a map of the heads of active leaves to the depths, +// where the candidate is a member of the fragment tree. +// Returns error if the candidate cannot be seconded. +func (cb *CandidateBacking) secondingSanityCheck( + hypotheticalCandidate parachaintypes.HypotheticalCandidate, + backedInPathOnly bool, //nolint:unparam +) (map[common.Hash][]uint, error) { + var ( + candidateParaID parachaintypes.ParaID + candidateRelayParent common.Hash + candidateHash parachaintypes.CandidateHash + membership = make(map[common.Hash][]uint) + ) + + switch v := hypotheticalCandidate.(type) { + case parachaintypes.HypotheticalCandidateIncomplete: + candidateParaID = v.CandidateParaID + candidateRelayParent = v.RelayParent + candidateHash = v.CandidateHash + case parachaintypes.HypotheticalCandidateComplete: + candidateParaID = parachaintypes.ParaID(v.CommittedCandidateReceipt.Descriptor.ParaID) + candidateRelayParent = v.CommittedCandidateReceipt.Descriptor.RelayParent + candidateHash = v.CandidateHash + } + + for head, leafState := range cb.perLeaf { + if leafState.prospectiveParachainsMode.IsEnabled { + + // check that the candidate relay parent is allowed for parachain, skip the leaf otherwise. + allowedParentsForPara := cb.implicitView.knownAllowedRelayParentsUnder(head, candidateParaID) + if !slices.Contains(allowedParentsForPara, candidateRelayParent) { + continue + } + + responseCh := make(chan parachaintypes.HypotheticalFrontierResponses) + cb.SubSystemToOverseer <- parachaintypes.ProspectiveParachainsMessageGetHypotheticalFrontier{ + HypotheticalFrontierRequest: parachaintypes.HypotheticalFrontierRequest{ + Candidates: []parachaintypes.HypotheticalCandidate{hypotheticalCandidate}, + FragmentTreeRelayParent: &head, + BackedInPathOnly: backedInPathOnly, + }, + ResponseCh: responseCh, + } + + res, ok := <-responseCh + if ok { + var depths []uint + // collect all depths from all fragment trees + for _, val := range res { + for _, membership := range val.Memberships { + depths = append(depths, membership.Depths...) + } + } + + if isSeconded, atDepth := checkDepthsAgainstLeaftState(depths, leafState, candidateParaID); isSeconded { + return nil, fmt.Errorf( + "%w; candidate hash: %s; relay parent: %s; parachain id: %v; depth: %d", + errDepthOccupied, + candidateHash.Value.String(), + candidateRelayParent.String(), + candidateParaID, + *atDepth, + ) + } + + membership[head] = depths + + } else { + logger.Error("prospective parachains message get hypothetical frontier's response channel is closed") + } + } else if head == candidateRelayParent { + if isSeconded := isSecondedAtDepth(0, leafState, candidateParaID); isSeconded { + return nil, fmt.Errorf( + "%w; candidate hash: %s; relay parent: %s; parachain id: %v", + errLeafOccupied, + candidateHash, + candidateRelayParent.String(), + candidateParaID, + ) + } + membership[head] = []uint{0} + } + } + + if len(membership) == 0 { + return nil, fmt.Errorf("%w: %s", errNoActiveLeaves, candidateRelayParent.String()) + } + + // At this point we've checked the depths of the candidate against all active leaves. + return membership, nil +} + +func checkDepthsAgainstLeaftState(depths []uint, leafState activeLeafState, paraID parachaintypes.ParaID, +) (bool, *uint) { + for _, depth := range depths { + if isSeconded := isSecondedAtDepth(depth, leafState, paraID); isSeconded { + return true, &depth + } + } + return false, nil +} + +func isSecondedAtDepth(depth uint, leafState activeLeafState, candidateParaID parachaintypes.ParaID) bool { + if bTreeMap, ok := leafState.secondedAtDepth[candidateParaID]; ok { + if _, ok := bTreeMap.Get(depth); ok { + return true + } + } + return false +} diff --git a/dot/parachain/backing/can_second_test.go b/dot/parachain/backing/can_second_test.go new file mode 100644 index 0000000000..6144927228 --- /dev/null +++ b/dot/parachain/backing/can_second_test.go @@ -0,0 +1,347 @@ +// Copyright 2024 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package backing + +import ( + "testing" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + "github.com/ChainSafe/gossamer/lib/common" + gomock "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "github.com/tidwall/btree" +) + +func ignoreChanVal(t *testing.T, ch chan bool) { + t.Helper() + // ignore received value + <-ch +} + +func TestHandleCanSecondMessage(t *testing.T) { + hash, err := getDummyCommittedCandidateReceipt(t).ToPlain().Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + msg := CanSecondMessage{ + CandidateParaID: 1, + CandidateRelayParent: getDummyHash(t, 5), + CandidateHash: candidateHash, + ParentHeadDataHash: getDummyHash(t, 4), + ResponseCh: make(chan bool), + } + + t.Run("relay_parent_is_unknown", func(t *testing.T) { + cb := CandidateBacking{} + + go ignoreChanVal(t, msg.ResponseCh) + err := cb.handleCanSecondMessage(msg) + require.ErrorIs(t, err, errUnknwnRelayParent) + }) + t.Run("async_backing_is_disabled", func(t *testing.T) { + cb := CandidateBacking{ + perRelayParent: map[common.Hash]*perRelayParentState{ + msg.CandidateRelayParent: { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{IsEnabled: false}, + }, + }, + } + + go ignoreChanVal(t, msg.ResponseCh) + err := cb.handleCanSecondMessage(msg) + require.ErrorIs(t, err, errProspectiveParachainsModeDisabled) + }) + + t.Run("candidate_can_not_be_seconded", func(t *testing.T) { + cb := CandidateBacking{ + perRelayParent: map[common.Hash]*perRelayParentState{ + msg.CandidateRelayParent: { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + }, + }, + } + + go ignoreChanVal(t, msg.ResponseCh) + err := cb.handleCanSecondMessage(msg) + require.ErrorIs(t, err, errNoActiveLeaves) + }) + + t.Run("candidate_recognised_by_at_least_one_fragment_tree", func(t *testing.T) { + ctrl := gomock.NewController(t) + mockImplicitView := NewMockImplicitView(ctrl) + + mockImplicitView.EXPECT().knownAllowedRelayParentsUnder( + gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf(parachaintypes.ParaID(0)), + ).Return([]common.Hash{msg.CandidateRelayParent}) + + subSystemToOverseer := make(chan any) + + cb := CandidateBacking{ + SubSystemToOverseer: subSystemToOverseer, + perRelayParent: map[common.Hash]*perRelayParentState{ + msg.CandidateRelayParent: { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + }, + }, + perLeaf: map[common.Hash]activeLeafState{ + getDummyHash(t, 1): { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + secondedAtDepth: map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash]{ + msg.CandidateParaID: {}, + }, + }, + }, + implicitView: mockImplicitView, + } + + go func(subSystemToOverseer chan any) { + in := <-subSystemToOverseer + responseCh := in.(parachaintypes.ProspectiveParachainsMessageGetHypotheticalFrontier).ResponseCh + responseCh <- parachaintypes.HypotheticalFrontierResponses{ + { + HypotheticalCandidate: parachaintypes.HypotheticalCandidateIncomplete{ + CandidateHash: candidateHash, + CandidateParaID: 1, + ParentHeadDataHash: getDummyHash(t, 4), + RelayParent: getDummyHash(t, 5), + }, + Memberships: []parachaintypes.FragmentTreeMembership{{ + RelayParent: getDummyHash(t, 5), + Depths: []uint{1, 2, 3}, + }}, + }, + } + }(subSystemToOverseer) + + go ignoreChanVal(t, msg.ResponseCh) + err := cb.handleCanSecondMessage(msg) + require.NoError(t, err) + }) +} + +func TestSecondingSanityCheck(t *testing.T) { + hash, err := getDummyCommittedCandidateReceipt(t).ToPlain().Hash() + require.NoError(t, err) + + candidateHash := parachaintypes.CandidateHash{Value: hash} + + hypotheticalCandidate := parachaintypes.HypotheticalCandidateIncomplete{ + CandidateHash: candidateHash, + CandidateParaID: 1, + ParentHeadDataHash: getDummyHash(t, 4), + RelayParent: getDummyHash(t, 5), + } + + t.Run("empty_active_leaves", func(t *testing.T) { + cb := CandidateBacking{} + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.ErrorIs(t, err, errNoActiveLeaves) + require.Nil(t, membership) + }) + + t.Run("prospective_parachains_mode_enabled_and_candidate_relay_parent_not_allowed_for_parachain", func(t *testing.T) { + ctrl := gomock.NewController(t) + mockImplicitView := NewMockImplicitView(ctrl) + + mockImplicitView.EXPECT().knownAllowedRelayParentsUnder( + gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf(parachaintypes.ParaID(0)), + ).Return([]common.Hash{}) + + cb := CandidateBacking{ + perRelayParent: map[common.Hash]*perRelayParentState{ + hypotheticalCandidate.RelayParent: {}, + }, + perLeaf: map[common.Hash]activeLeafState{ + getDummyHash(t, 1): { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + }, + }, + implicitView: mockImplicitView, + } + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.ErrorIs(t, err, errNoActiveLeaves) + require.Nil(t, membership) + }) + + t.Run("prospective_parachains_mode_enabled_and_depth_already_occupied", func(t *testing.T) { + ctrl := gomock.NewController(t) + mockImplicitView := NewMockImplicitView(ctrl) + + mockImplicitView.EXPECT().knownAllowedRelayParentsUnder( + gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf(parachaintypes.ParaID(0)), + ).Return([]common.Hash{hypotheticalCandidate.RelayParent}) + + subSystemToOverseer := make(chan any) + + cb := CandidateBacking{ + SubSystemToOverseer: subSystemToOverseer, + perRelayParent: map[common.Hash]*perRelayParentState{ + hypotheticalCandidate.RelayParent: {}, + }, + perLeaf: map[common.Hash]activeLeafState{ + getDummyHash(t, 1): { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + secondedAtDepth: map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash]{ + hypotheticalCandidate.CandidateParaID: func() btree.Map[uint, parachaintypes.CandidateHash] { + var btm btree.Map[uint, parachaintypes.CandidateHash] + btm.Set(1, hypotheticalCandidate.CandidateHash) + return btm + }(), + }, + }, + }, + implicitView: mockImplicitView, + } + + go func(subSystemToOverseer chan any) { + in := <-subSystemToOverseer + in.(parachaintypes.ProspectiveParachainsMessageGetHypotheticalFrontier). + ResponseCh <- parachaintypes.HypotheticalFrontierResponses{ + { + HypotheticalCandidate: hypotheticalCandidate, + Memberships: []parachaintypes.FragmentTreeMembership{{ + RelayParent: hypotheticalCandidate.RelayParent, + Depths: []uint{1, 2, 3}, + }}, + }, + } + }(subSystemToOverseer) + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.ErrorIs(t, err, errDepthOccupied) + require.Nil(t, membership) + }) + + t.Run("prospective_parachains_mode_enabled_and_depth_not_occupied", func(t *testing.T) { + ctrl := gomock.NewController(t) + mockImplicitView := NewMockImplicitView(ctrl) + + mockImplicitView.EXPECT().knownAllowedRelayParentsUnder( + gomock.AssignableToTypeOf(common.Hash{}), + gomock.AssignableToTypeOf(parachaintypes.ParaID(0)), + ).Return([]common.Hash{hypotheticalCandidate.RelayParent}) + + subSystemToOverseer := make(chan any) + + cb := CandidateBacking{ + SubSystemToOverseer: subSystemToOverseer, + perRelayParent: map[common.Hash]*perRelayParentState{ + hypotheticalCandidate.RelayParent: {}, + }, + perLeaf: map[common.Hash]activeLeafState{ + getDummyHash(t, 1): { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: true, + MaxCandidateDepth: 4, + AllowedAncestryLen: 2, + }, + secondedAtDepth: map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash]{ + hypotheticalCandidate.CandidateParaID: {}, + }, + }, + }, + implicitView: mockImplicitView, + } + + go func(subSystemToOverseer chan any) { + in := <-subSystemToOverseer + in.(parachaintypes.ProspectiveParachainsMessageGetHypotheticalFrontier). + ResponseCh <- parachaintypes.HypotheticalFrontierResponses{ + { + HypotheticalCandidate: hypotheticalCandidate, + Memberships: []parachaintypes.FragmentTreeMembership{{ + RelayParent: hypotheticalCandidate.RelayParent, + Depths: []uint{1, 2, 3}, + }}, + }, + } + }(subSystemToOverseer) + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.NoError(t, err) + require.Equal( + t, + map[common.Hash][]uint{getDummyHash(t, 1): {1, 2, 3}}, + membership, + ) + }) + + t.Run("prospective_parachains_mode_disabled_and_leaf_is_already_occupied", func(t *testing.T) { + cb := CandidateBacking{ + perRelayParent: map[common.Hash]*perRelayParentState{ + hypotheticalCandidate.RelayParent: {}, + }, + perLeaf: map[common.Hash]activeLeafState{ + hypotheticalCandidate.RelayParent: { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: false, + }, + secondedAtDepth: map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash]{ + hypotheticalCandidate.CandidateParaID: func() btree.Map[uint, parachaintypes.CandidateHash] { + var btm btree.Map[uint, parachaintypes.CandidateHash] + btm.Set(0, hypotheticalCandidate.CandidateHash) + return btm + }(), + }, + }, + }, + } + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.ErrorIs(t, err, errLeafOccupied) + require.Nil(t, membership) + }) + + t.Run("prospective_parachains_mode_disabled_and_leaf_is_not_occupied", func(t *testing.T) { + cb := CandidateBacking{ + perRelayParent: map[common.Hash]*perRelayParentState{ + hypotheticalCandidate.RelayParent: {}, + }, + perLeaf: map[common.Hash]activeLeafState{ + hypotheticalCandidate.RelayParent: { + prospectiveParachainsMode: parachaintypes.ProspectiveParachainsMode{ + IsEnabled: false, + }, + secondedAtDepth: map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash]{ + hypotheticalCandidate.CandidateParaID: {}, + }, + }, + }, + } + + membership, err := cb.secondingSanityCheck(hypotheticalCandidate, true) + require.NoError(t, err) + require.Equal( + t, + map[common.Hash][]uint{hypotheticalCandidate.RelayParent: {0}}, + membership, + ) + }) +} diff --git a/dot/parachain/backing/candidate_backing.go b/dot/parachain/backing/candidate_backing.go index 5f4baab606..564af402d6 100644 --- a/dot/parachain/backing/candidate_backing.go +++ b/dot/parachain/backing/candidate_backing.go @@ -30,6 +30,7 @@ import ( parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" + "github.com/tidwall/btree" ) var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-candidate-backing")) @@ -72,6 +73,17 @@ type CandidateBacking struct { // This is guaranteed to have an entry for each candidate with a relay parent in the implicit // or explicit view for which a `Seconded` statement has been successfully imported. perCandidate map[parachaintypes.CandidateHash]*perCandidateState + // State tracked for all active leaves, whether or not they have prospective parachains enabled. + perLeaf map[common.Hash]activeLeafState + // The utility for managing the implicit and explicit views in a consistent way. + // We only feed leaves which have prospective parachains enabled to this view. + implicitView ImplicitView +} + +type activeLeafState struct { + prospectiveParachainsMode parachaintypes.ProspectiveParachainsMode + secondedAtDepth map[parachaintypes.ParaID]btree.Map[uint, parachaintypes.CandidateHash] + perCandidate map[parachaintypes.CandidateHash]*perCandidateState //nolint:unused } // perCandidateState represents the state information for a candidate in the subsystem. @@ -125,6 +137,7 @@ type CanSecondMessage struct { CandidateRelayParent common.Hash CandidateHash parachaintypes.CandidateHash ParentHeadDataHash common.Hash + ResponseCh chan bool } // SecondMessage is a message received from overseer. Candidate Backing subsystem should second the given @@ -206,7 +219,10 @@ func (cb *CandidateBacking) processMessage(msg any, chRelayParentAndCommand chan case GetBackedCandidatesMessage: cb.handleGetBackedCandidatesMessage() case CanSecondMessage: - cb.handleCanSecondMessage() + err := cb.handleCanSecondMessage(msg) + if err != nil { + logger.Debug(fmt.Sprintf("can't second the candidate: %s", err)) + } case SecondMessage: cb.handleSecondMessage() case StatementMessage: @@ -233,10 +249,6 @@ func (cb *CandidateBacking) handleGetBackedCandidatesMessage() { // TODO: Implement this #3504 } -func (cb *CandidateBacking) handleCanSecondMessage() { - // TODO: Implement this #3505 -} - func (cb *CandidateBacking) handleSecondMessage() { // TODO: Implement this #3506 } diff --git a/dot/parachain/backing/mocks_generate_test.go b/dot/parachain/backing/mocks_generate_test.go index 97b30c4324..ba2e82eecd 100644 --- a/dot/parachain/backing/mocks_generate_test.go +++ b/dot/parachain/backing/mocks_generate_test.go @@ -3,4 +3,4 @@ package backing -//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Table +//go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Table,ImplicitView diff --git a/dot/parachain/backing/mocks_test.go b/dot/parachain/backing/mocks_test.go index c1930ef798..8f184254ad 100644 --- a/dot/parachain/backing/mocks_test.go +++ b/dot/parachain/backing/mocks_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/ChainSafe/gossamer/dot/parachain/backing (interfaces: Table) +// Source: github.com/ChainSafe/gossamer/dot/parachain/backing (interfaces: Table,ImplicitView) // Package backing is a generated GoMock package. package backing @@ -8,6 +8,7 @@ import ( reflect "reflect" types "github.com/ChainSafe/gossamer/dot/parachain/types" + common "github.com/ChainSafe/gossamer/lib/common" gomock "github.com/golang/mock/gomock" ) @@ -92,3 +93,40 @@ func (mr *MockTableMockRecorder) importStatement(arg0, arg1 interface{}) *gomock mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "importStatement", reflect.TypeOf((*MockTable)(nil).importStatement), arg0, arg1) } + +// MockImplicitView is a mock of ImplicitView interface. +type MockImplicitView struct { + ctrl *gomock.Controller + recorder *MockImplicitViewMockRecorder +} + +// MockImplicitViewMockRecorder is the mock recorder for MockImplicitView. +type MockImplicitViewMockRecorder struct { + mock *MockImplicitView +} + +// NewMockImplicitView creates a new mock instance. +func NewMockImplicitView(ctrl *gomock.Controller) *MockImplicitView { + mock := &MockImplicitView{ctrl: ctrl} + mock.recorder = &MockImplicitViewMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImplicitView) EXPECT() *MockImplicitViewMockRecorder { + return m.recorder +} + +// knownAllowedRelayParentsUnder mocks base method. +func (m *MockImplicitView) knownAllowedRelayParentsUnder(arg0 common.Hash, arg1 types.ParaID) []common.Hash { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "knownAllowedRelayParentsUnder", arg0, arg1) + ret0, _ := ret[0].([]common.Hash) + return ret0 +} + +// knownAllowedRelayParentsUnder indicates an expected call of knownAllowedRelayParentsUnder. +func (mr *MockImplicitViewMockRecorder) knownAllowedRelayParentsUnder(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "knownAllowedRelayParentsUnder", reflect.TypeOf((*MockImplicitView)(nil).knownAllowedRelayParentsUnder), arg0, arg1) +} diff --git a/dot/parachain/types/overseer_message.go b/dot/parachain/types/overseer_message.go index 0c59ffed2a..2fff75ef9f 100644 --- a/dot/parachain/types/overseer_message.go +++ b/dot/parachain/types/overseer_message.go @@ -6,9 +6,11 @@ package parachaintypes import "github.com/ChainSafe/gossamer/lib/common" var ( - _ ProvisionableData = (*ProvisionableDataBackedCandidate)(nil) - _ ProvisionableData = (*ProvisionableDataMisbehaviorReport)(nil) - _ RuntimeApiRequest = (*RuntimeApiRequestValidationCodeByHash)(nil) + _ ProvisionableData = (*ProvisionableDataBackedCandidate)(nil) + _ ProvisionableData = (*ProvisionableDataMisbehaviorReport)(nil) + _ RuntimeApiRequest = (*RuntimeApiRequestValidationCodeByHash)(nil) + _ HypotheticalCandidate = (*HypotheticalCandidateIncomplete)(nil) + _ HypotheticalCandidate = (*HypotheticalCandidateComplete)(nil) ) // OverseerFuncRes is a result of an overseer function @@ -65,6 +67,16 @@ type ProspectiveParachainsMessageIntroduceCandidate struct { Ch chan error } +// IntroduceCandidateRequest is a request to introduce a candidate into the Prospective Parachains Subsystem. +type IntroduceCandidateRequest struct { + // The para-id of the candidate. + CandidateParaID ParaID + // The candidate receipt itself. + CommittedCandidateReceipt CommittedCandidateReceipt + // The persisted validation data of the candidate. + PersistedValidationData PersistedValidationData +} + // ProspectiveParachainsMessageCandidateSeconded is a prospective parachains message. // it informs the Prospective Parachains Subsystem that a previously introduced candidate // has been seconded. This requires that the candidate was successfully introduced in @@ -74,16 +86,81 @@ type ProspectiveParachainsMessageCandidateSeconded struct { CandidateHash CandidateHash } -// IntroduceCandidateRequest is a request to introduce a candidate into the Prospective Parachains Subsystem. -type IntroduceCandidateRequest struct { - // The para-id of the candidate. +// Get the hypothetical frontier membership of candidates with the given properties +// under the specified active leaves' fragment trees. +// +// For any candidate which is already known, this returns the depths the candidate +// occupies. +type ProspectiveParachainsMessageGetHypotheticalFrontier struct { + HypotheticalFrontierRequest HypotheticalFrontierRequest + ResponseCh chan HypotheticalFrontierResponses +} + +// Request specifying which candidates are either already included +// or might be included in the hypothetical frontier of fragment trees +// under a given active leaf +type HypotheticalFrontierRequest struct { + // Candidates, in arbitrary order, which should be checked for possible membership in fragment trees + Candidates []HypotheticalCandidate + // Either a specific fragment tree to check, otherwise all. + FragmentTreeRelayParent *common.Hash + // Only return membership if all candidates in the path from the root are backed. + BackedInPathOnly bool +} + +type HypotheticalFrontierResponses []HypotheticalFrontierResponse + +type HypotheticalFrontierResponse struct { + HypotheticalCandidate HypotheticalCandidate + Memberships []FragmentTreeMembership +} + +// Indicates the relay-parents whose fragment tree a candidate +// is present in and the depths of that tree the candidate is present in. +type FragmentTreeMembership struct { + RelayParent common.Hash + Depths []uint +} + +// HypotheticalCandidate represents a candidate to be evaluated for membership +// in the prospective parachains subsystem. +// +// Hypothetical candidates can be categorised into two types: complete and incomplete. +// +// - Complete candidates have already had their potentially heavy candidate receipt +// fetched, making them suitable for stricter evaluation. +// +// - Incomplete candidates are simply claims about properties that a fetched candidate +// would have and are evaluated less strictly. +type HypotheticalCandidate interface { + isHypotheticalCandidate() +} + +// HypotheticalCandidateIncomplete represents an incomplete hypothetical candidate. +// this +type HypotheticalCandidateIncomplete struct { + // CandidateHash is the claimed hash of the candidate. + CandidateHash CandidateHash + // ParaID is the claimed para-ID of the candidate. CandidateParaID ParaID - // The candidate receipt itself. + // ParentHeadDataHash is the claimed head-data hash of the candidate. + ParentHeadDataHash common.Hash + // RelayParent is the claimed relay parent of the candidate. + RelayParent common.Hash +} + +func (HypotheticalCandidateIncomplete) isHypotheticalCandidate() {} + +// HypotheticalCandidateComplete represents a complete candidate, including its hash, committed candidate receipt, +// and persisted validation data. +type HypotheticalCandidateComplete struct { + CandidateHash CandidateHash CommittedCandidateReceipt CommittedCandidateReceipt - // The persisted validation data of the candidate. - PersistedValidationData PersistedValidationData + PersistedValidationData PersistedValidationData } +func (HypotheticalCandidateComplete) isHypotheticalCandidate() {} + type RuntimeApiMessageRequest struct { RelayParent common.Hash // Make a request of the runtime API against the post-state of the given relay-parent. diff --git a/dot/parachain/types/types.go b/dot/parachain/types/types.go index 899cd44b5a..e5d2ff86a2 100644 --- a/dot/parachain/types/types.go +++ b/dot/parachain/types/types.go @@ -575,17 +575,24 @@ type BackedCandidate struct { ValidatorIndices scale.BitVec `scale:"3"` // TODO: it's a bitvec in rust, figure out actual type } +// ProspectiveParachainsMode represents the mode of a relay parent in the context +// of prospective parachains, as defined by the Runtime API version. type ProspectiveParachainsMode struct { - // Runtime API without support of `async_backing_params`: no prospective parachains. - // v6 runtime API: prospective parachains. - // NOTE: MaxCandidateDepth and AllowedAncestryLen need to be set if this is enabled. + // IsEnabled indicates whether prospective parachains are enabled or disabled. + // - Disabled: When the Runtime API lacks support for `async_backing_params`, + // there are no prospective parachains. + // - Enabled: For v6 runtime API, prospective parachains are enabled. + // NOTE: MaxCandidateDepth and AllowedAncestryLen should be set only if this is enabled. IsEnabled bool - // The maximum number of para blocks between the para head in a relay parent - // and a new candidate. Restricts nodes from building arbitrary long chains - // and spamming other validators. + // MaxCandidateDepth specifies the maximum number of para blocks that can exist + // between the para head in a relay parent and a new candidate. This limitation + // helps prevent the construction of arbitrarily long chains and spamming by + // other validators. MaxCandidateDepth uint - // How many ancestors of a relay parent are allowed to build candidates on top of. + + // AllowedAncestryLen determines how many ancestors of a relay parent are allowed + // to build candidates on top of it. AllowedAncestryLen uint } diff --git a/go.mod b/go.mod index cedf4ea205..54abe12d50 100644 --- a/go.mod +++ b/go.mod @@ -37,6 +37,7 @@ require ( github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/tetratelabs/wazero v1.1.0 + github.com/tidwall/btree v1.7.0 github.com/whyrusleeping/mdns v0.0.0-20190826153040-b9b60ed33aa9 golang.org/x/crypto v0.12.0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 diff --git a/go.sum b/go.sum index b371295068..a3626a3977 100644 --- a/go.sum +++ b/go.sum @@ -761,6 +761,8 @@ github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= +github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI= +github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY= github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp3vU4= github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= diff --git a/tests/rpc/system_integration_test.go b/tests/rpc/system_integration_test.go index e7230f3700..4f1b0f3a81 100644 --- a/tests/rpc/system_integration_test.go +++ b/tests/rpc/system_integration_test.go @@ -24,6 +24,8 @@ import ( ) func TestStableNetworkRPC(t *testing.T) { //nolint:tparallel + t.Skip() + if utils.MODE != "rpc" { t.Skip("RPC tests are disabled, going to skip.") } From 7c94a6f315f9aebd55557707d15eb668d229e3ec Mon Sep 17 00:00:00 2001 From: Edward Mack Date: Tue, 23 Jan 2024 14:17:07 -0500 Subject: [PATCH 71/85] feat(parachain/availabilitystore): implement database access functions for availability store subsystem (#3640) Co-authored-by: Kishan Sagathiya --- .github/workflows/integration-tests.yml | 3 + Dockerfile | 12 + Makefile | 2 +- .../availability-store/availability_store.go | 394 ++++++++++++--- .../availability_store_test.go | 456 ++++++++++++++++-- dot/parachain/availability-store/messages.go | 56 ++- dot/parachain/availability-store/register.go | 1 + 7 files changed, 794 insertions(+), 130 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 1d6619f270..45874fb45f 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -70,5 +70,8 @@ jobs: key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} restore-keys: ${{ runner.os }}-go-mod + - name: generate a shared library file for erasure + run: make compile-erasure + - name: Run integration tests run: go test -timeout=45m -tags integration ${{ matrix.packages }} diff --git a/Dockerfile b/Dockerfile index 98337d9650..6d5ad3a8ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,12 @@ RUN wget -qO- https://deb.nodesource.com/setup_14.x | bash - && \ RUN wget -O /usr/local/bin/subkey https://chainbridge.ams3.digitaloceanspaces.com/subkey-v2.0.0 && \ chmod +x /usr/local/bin/subkey +# Get Rust; NOTE: using sh for better compatibility with other base images +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y + +# Add .cargo/bin to PATH +ENV PATH="/root/.cargo/bin:${PATH}" + WORKDIR /go/src/github.com/ChainSafe/gossamer # Go dependencies @@ -26,6 +32,9 @@ RUN go mod download # Copy gossamer sources COPY . . +# build erasure lib +RUN cargo build --release --manifest-path=./lib/erasure/rustlib/Cargo.toml + # Build ARG GO_BUILD_FLAGS RUN go build \ @@ -43,5 +52,8 @@ EXPOSE 7001 8546 8540 ENTRYPOINT [ "/gossamer/bin/gossamer" ] +ENV LD_LIBRARY_PATH=/usr/local/lib + COPY chain /gossamer/chain +COPY --from=builder /go/src/github.com/ChainSafe/gossamer/lib/erasure/rustlib/target/release/liberasure.so /usr/local/lib/liberasure.so COPY --from=builder /go/src/github.com/ChainSafe/gossamer/bin/gossamer /gossamer/bin/gossamer diff --git a/Makefile b/Makefile index ddae5fb5bc..eaef052ec2 100644 --- a/Makefile +++ b/Makefile @@ -75,7 +75,7 @@ deps: go mod download ## build: Builds application binary and stores it in `./bin/gossamer` -build: +build: compile-erasure @echo " > \033[32mBuilding binary...\033[0m " go build -trimpath -o ./bin/gossamer -ldflags="-s -w" ./cmd/gossamer diff --git a/dot/parachain/availability-store/availability_store.go b/dot/parachain/availability-store/availability_store.go index 4717cdd7cf..f519f6a094 100644 --- a/dot/parachain/availability-store/availability_store.go +++ b/dot/parachain/availability-store/availability_store.go @@ -6,27 +6,70 @@ package availabilitystore import ( "context" "encoding/binary" - "encoding/json" "errors" "fmt" "sync" + "time" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/internal/database" "github.com/ChainSafe/gossamer/internal/log" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/erasure" + "github.com/ChainSafe/gossamer/lib/trie" + "github.com/ChainSafe/gossamer/pkg/scale" ) var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain-availability-store")) const ( - avaliableDataPrefix = "available" + availableDataPrefix = "available" chunkPrefix = "chunk" metaPrefix = "meta" unfinalizedPrefix = "unfinalized" pruneByTimePrefix = "prune_by_time" + + // Unavailable blocks are kept for 1 hour. + keepUnavilableFor = time.Hour + + // Finalized data is kept for 25 hours. + keepFinalizedFor = time.Hour * 25 + + // The pruning interval. + pruningInterval = time.Minute * 5 ) +// BETimestamp is a unix time wrapper with big-endian encoding +type BETimestamp uint64 + +// ToBigEndianBytes returns the big-endian encoding of the timestamp +func (b BETimestamp) ToBigEndianBytes() []byte { + res := make([]byte, 8) + binary.BigEndian.PutUint64(res, uint64(b)) + return res +} + +type subsystemClock struct{} + +func (sc *subsystemClock) Now() BETimestamp { + return BETimestamp(time.Now().Unix()) +} + +// pruningConfig Struct holding pruning timing configuration. +// The only purpose of this structure is to use different timing +// configurations in production and in testing. +type pruningConfig struct { + keepUnavailableFor time.Duration + keepFinalizedFor time.Duration + pruningInterval time.Duration +} + +var defaultPruningConfig = pruningConfig{ + keepUnavailableFor: keepUnavilableFor, + keepFinalizedFor: keepFinalizedFor, + pruningInterval: pruningInterval, +} + // AvailabilityStoreSubsystem is the struct that holds subsystem data for the availability store type AvailabilityStoreSubsystem struct { ctx context.Context @@ -35,143 +78,319 @@ type AvailabilityStoreSubsystem struct { SubSystemToOverseer chan<- any OverseerToSubSystem <-chan any - availabilityStore AvailabilityStore + availabilityStore availabilityStore + pruningConfig pruningConfig + clock subsystemClock //TODO: pruningConfig PruningConfig //TODO: clock Clock //TODO: metrics Metrics } -// AvailabilityStore is the struct that holds data for the availability store -type AvailabilityStore struct { - availableTable database.Table - chunkTable database.Table - metaTable database.Table - //TODO: unfinalizedTable database.Table - //TODO: pruneByTimeTable database.Table +// availabilityStore is the struct that holds data for the availability store +type availabilityStore struct { + available database.Table + chunk database.Table + meta database.Table + unfinalized database.Table + pruneByTime database.Table +} + +type availabilityStoreBatch struct { + available database.Batch + chunk database.Batch + meta database.Batch + unfinalized database.Batch + pruneByTime database.Batch +} + +func newAvailabilityStoreBatch(as *availabilityStore) *availabilityStoreBatch { + return &availabilityStoreBatch{ + available: as.available.NewBatch(), + chunk: as.chunk.NewBatch(), + meta: as.meta.NewBatch(), + unfinalized: as.unfinalized.NewBatch(), + pruneByTime: as.pruneByTime.NewBatch(), + } +} + +// flush flushes the batch and resets the batch if error during flushing +func (asb *availabilityStoreBatch) flush() error { + err := asb.flushAll() + if err != nil { + asb.reset() + } + return err +} + +// flushAll flushes all the batches and returns the error +func (asb *availabilityStoreBatch) flushAll() error { + err := asb.available.Flush() + if err != nil { + return fmt.Errorf("writing available batch: %w", err) + } + err = asb.chunk.Flush() + if err != nil { + return fmt.Errorf("writing chunk batch: %w", err) + } + err = asb.meta.Flush() + if err != nil { + return fmt.Errorf("writing meta batch: %w", err) + } + err = asb.unfinalized.Flush() + if err != nil { + return fmt.Errorf("writing unfinalized batch: %w", err) + } + err = asb.pruneByTime.Flush() + if err != nil { + return fmt.Errorf("writing prune by time batch: %w", err) + } + return nil +} + +// reset resets the batch and returns the error +func (asb *availabilityStoreBatch) reset() { + asb.available.Reset() + asb.chunk.Reset() + asb.meta.Reset() + asb.unfinalized.Reset() + asb.pruneByTime.Reset() } // NewAvailabilityStore creates a new instance of AvailabilityStore -func NewAvailabilityStore(db database.Database) *AvailabilityStore { - return &AvailabilityStore{ - availableTable: database.NewTable(db, avaliableDataPrefix), - chunkTable: database.NewTable(db, chunkPrefix), - metaTable: database.NewTable(db, metaPrefix), +func NewAvailabilityStore(db database.Database) *availabilityStore { + return &availabilityStore{ + available: database.NewTable(db, availableDataPrefix), + chunk: database.NewTable(db, chunkPrefix), + meta: database.NewTable(db, metaPrefix), + unfinalized: database.NewTable(db, unfinalizedPrefix), + pruneByTime: database.NewTable(db, pruneByTimePrefix), } } // loadAvailableData loads available data from the availability store -func (as *AvailabilityStore) loadAvailableData(candidate common.Hash) (*AvailableData, error) { - resultBytes, err := as.availableTable.Get(candidate[:]) +func (as *availabilityStore) loadAvailableData(candidate parachaintypes.CandidateHash) (*AvailableData, error) { + resultBytes, err := as.available.Get(candidate.Value[:]) if err != nil { - return nil, fmt.Errorf("getting candidate %v from available table: %w", candidate, err) + return nil, fmt.Errorf("getting candidate %v from available table: %w", candidate.Value, err) } result := AvailableData{} - err = json.Unmarshal(resultBytes, &result) + err = scale.Unmarshal(resultBytes, &result) if err != nil { return nil, fmt.Errorf("unmarshalling available data: %w", err) } return &result, nil } -// loadMetaData loads metadata from the availability store -func (as *AvailabilityStore) loadMetaData(candidate common.Hash) (*CandidateMeta, error) { - resultBytes, err := as.metaTable.Get(candidate[:]) +// loadMeta loads meta data from the availability store +func (as *availabilityStore) loadMeta(candidate parachaintypes.CandidateHash) (*CandidateMeta, error) { + resultBytes, err := as.meta.Get(candidate.Value[:]) if err != nil { - return nil, fmt.Errorf("getting candidate %v from available table: %w", candidate, err) + return nil, fmt.Errorf("getting candidate %v from meta table: %w", candidate.Value, err) } result := CandidateMeta{} - err = json.Unmarshal(resultBytes, &result) + err = scale.Unmarshal(resultBytes, &result) if err != nil { return nil, fmt.Errorf("unmarshalling candidate meta: %w", err) } return &result, nil } -// storeMetaData stores metadata in the availability store -func (as *AvailabilityStore) storeMetaData(candidate common.Hash, meta CandidateMeta) error { - dataBytes, err := json.Marshal(meta) - if err != nil { - return fmt.Errorf("marshalling meta for candidate: %w", err) - } - err = as.metaTable.Put(candidate[:], dataBytes) - if err != nil { - return fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) - } - return nil -} - // loadChunk loads a chunk from the availability store -func (as *AvailabilityStore) loadChunk(candidate common.Hash, validatorIndex uint32) (*ErasureChunk, error) { - resultBytes, err := as.chunkTable.Get(append(candidate[:], uint32ToBytes(validatorIndex)...)) +func (as *availabilityStore) loadChunk(candidate parachaintypes.CandidateHash, validatorIndex uint32) (*ErasureChunk, + error) { + resultBytes, err := as.chunk.Get(append(candidate.Value[:], uint32ToBytes(validatorIndex)...)) if err != nil { - return nil, fmt.Errorf("getting candidate %v, index %d from chunk table: %w", candidate, validatorIndex, err) + return nil, fmt.Errorf("getting candidate %v, index %d from chunk table: %w", candidate.Value, validatorIndex, err) } result := ErasureChunk{} - err = json.Unmarshal(resultBytes, &result) + err = scale.Unmarshal(resultBytes, &result) if err != nil { return nil, fmt.Errorf("unmarshalling chunk: %w", err) } return &result, nil } -// storeChunk stores a chunk in the availability store -func (as *AvailabilityStore) storeChunk(candidate common.Hash, chunk ErasureChunk) error { - meta, err := as.loadMetaData(candidate) - if err != nil { +// storeChunk stores a chunk in the availability store, returns true on success, false on failure, +// and error on internal error. +func (as *availabilityStore) storeChunk(candidate parachaintypes.CandidateHash, chunk ErasureChunk) (bool, + error) { + batch := newAvailabilityStoreBatch(as) + meta, err := as.loadMeta(candidate) + if err != nil { if errors.Is(err, database.ErrNotFound) { - // TODO: were creating metadata here, but we should be doing it in the parachain block import? - // TODO: also we need to determine how many chunks we need to store - meta = &CandidateMeta{ - ChunksStored: make([]bool, 16), - } + // we weren't informed of this candidate by import events + return false, nil } else { - return fmt.Errorf("load metadata: %w", err) + return false, fmt.Errorf("load metadata: %w", err) } } if meta.ChunksStored[chunk.Index] { logger.Debugf("Chunk %d already stored", chunk.Index) - return nil // already stored - } else { - dataBytes, err := json.Marshal(chunk) - if err != nil { - return fmt.Errorf("marshalling chunk: %w", err) + return true, nil // already stored + } + + dataBytes, err := scale.Marshal(chunk) + if err != nil { + return false, fmt.Errorf("marshalling chunk for candidate %v, index %d: %w", candidate, chunk.Index, err) + } + err = batch.chunk.Put(append(candidate.Value[:], uint32ToBytes(chunk.Index)...), dataBytes) + if err != nil { + return false, fmt.Errorf("writing chunk for candidate %v, index %d: %w", candidate, chunk.Index, err) + } + + meta.ChunksStored[chunk.Index] = true + + dataBytes, err = scale.Marshal(*meta) + if err != nil { + return false, fmt.Errorf("marshalling meta for candidate: %w", err) + } + err = batch.meta.Put(candidate.Value[:], dataBytes) + if err != nil { + return false, fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) + } + + err = batch.flush() + if err != nil { + return false, fmt.Errorf("writing batch: %w", err) + } + + logger.Debugf("stored chuck %d for %v", chunk.Index, candidate) + return true, nil +} + +func (as *availabilityStore) storeAvailableData(subsystem *AvailabilityStoreSubsystem, + candidate parachaintypes.CandidateHash, nValidators uint, data AvailableData, + expectedErasureRoot common.Hash) (bool, error) { + batch := newAvailabilityStoreBatch(as) + meta, err := as.loadMeta(candidate) + if err != nil && !errors.Is(err, database.ErrNotFound) { + return false, fmt.Errorf("load metadata: %w", err) + } + if meta != nil && meta.DataAvailable { + return true, nil // already stored + } + + meta = &CandidateMeta{} + + now := subsystem.clock.Now() + pruneAt := now + BETimestamp(subsystem.pruningConfig.keepUnavailableFor.Seconds()) + + pruneKey := append(pruneAt.ToBigEndianBytes(), candidate.Value[:]...) + err = batch.pruneByTime.Put(pruneKey, nil) + if err != nil { + return false, fmt.Errorf("writing pruning key: %w", err) + } + + meta.State = NewStateVDT() + err = meta.State.Set(Unavailable{Timestamp: now}) + if err != nil { + return false, fmt.Errorf("setting state to unavailable: %w", err) + } + meta.DataAvailable = false + meta.ChunksStored = make([]bool, nValidators) + + dataEncoded, err := scale.Marshal(data) + if err != nil { + return false, fmt.Errorf("encoding data: %w", err) + } + + chunks, err := erasure.ObtainChunks(nValidators, dataEncoded) + if err != nil { + return false, fmt.Errorf("obtaining chunks: %w", err) + } + + branches, err := branchesFromChunks(chunks) + if err != nil { + return false, fmt.Errorf("creating branches from chunks: %w", err) + } + if branches.root != expectedErasureRoot { + return false, errInvalidErasureRoot + } + + for i, chunk := range chunks { + erasureChunk := ErasureChunk{ + Index: uint32(i), + Chunk: chunk, } - err = as.chunkTable.Put(append(candidate[:], uint32ToBytes(chunk.Index)...), dataBytes) + + dataBytes, err := scale.Marshal(erasureChunk) if err != nil { - return fmt.Errorf("storing chunk for candidate %v, index %d: %w", candidate, chunk.Index, err) + return false, fmt.Errorf("marshalling chunk for candidate %v, index %d: %w", candidate, erasureChunk.Index, err) } - - meta.ChunksStored[chunk.Index] = true - err = as.storeMetaData(candidate, *meta) + err = batch.chunk.Put(append(candidate.Value[:], uint32ToBytes(erasureChunk.Index)...), dataBytes) if err != nil { - return fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) + return false, fmt.Errorf("writing chunk for candidate %v, index %d: %w", candidate, erasureChunk.Index, err) } + + meta.ChunksStored[i] = true + } + + meta.DataAvailable = true + meta.ChunksStored = make([]bool, nValidators) + for i := range meta.ChunksStored { + meta.ChunksStored[i] = true } - logger.Debugf("stored chuck %d for %v", chunk.Index, candidate) - return nil -} -// storeAvailableData stores available data in the availability store -func (as *AvailabilityStore) storeAvailableData(candidate common.Hash, data AvailableData) error { - dataBytes, err := json.Marshal(data) + dataBytes, err := scale.Marshal(meta) if err != nil { - return fmt.Errorf("marshalling available data: %w", err) + return false, fmt.Errorf("marshalling meta for candidate: %w", err) } - err = as.availableTable.Put(candidate[:], dataBytes) + err = batch.meta.Put(candidate.Value[:], dataBytes) if err != nil { - return fmt.Errorf("storing available data for candidate %v: %w", candidate, err) + return false, fmt.Errorf("storing metadata for candidate %v: %w", candidate, err) } - return nil + + dataBytes, err = scale.Marshal(data) + if err != nil { + return false, fmt.Errorf("marshalling available data: %w", err) + } + err = batch.available.Put(candidate.Value[:], dataBytes) + if err != nil { + return false, fmt.Errorf("storing available data for candidate %v: %w", candidate, err) + } + + err = batch.flush() + if err != nil { + return false, fmt.Errorf("writing batch: %w", err) + } + + logger.Debugf("stored data and chunks for %v", candidate.Value) + return true, nil } +// todo(ed) determine if this should be LittleEndian or BigEndian func uint32ToBytes(value uint32) []byte { result := make([]byte, 4) binary.LittleEndian.PutUint32(result, value) return result } +func uint32ToBytesBigEndian(value uint32) []byte { + result := make([]byte, 4) + binary.BigEndian.PutUint32(result, value) + return result +} + +func branchesFromChunks(chunks [][]byte) (branches, error) { + tr := trie.NewEmptyTrie() + + for i, chunk := range chunks { + err := tr.Put(uint32ToBytes(uint32(i)), common.MustBlake2bHash(chunk).ToBytes()) + if err != nil { + return branches{}, fmt.Errorf("putting chunk %d in trie: %w", i, err) + } + } + b := branches{ + trieStorage: tr, + root: tr.MustHash(), + chunks: chunks, + currentPos: 0, + } + return b, nil +} + // Run runs the availability store subsystem func (av *AvailabilityStoreSubsystem) Run(ctx context.Context, OverseerToSubsystem chan any, SubsystemToOverseer chan any) { @@ -272,7 +491,7 @@ func (av *AvailabilityStoreSubsystem) handleQueryAvailableData(msg QueryAvailabl } func (av *AvailabilityStoreSubsystem) handleQueryDataAvailability(msg QueryDataAvailability) error { - _, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + _, err := av.availabilityStore.loadMeta(msg.CandidateHash) if err != nil { if errors.Is(err, database.ErrNotFound) { msg.Sender <- false @@ -296,7 +515,7 @@ func (av *AvailabilityStoreSubsystem) handleQueryChunk(msg QueryChunk) error { } func (av *AvailabilityStoreSubsystem) handleQueryChunkSize(msg QueryChunkSize) error { - meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + meta, err := av.availabilityStore.loadMeta(msg.CandidateHash) if err != nil { return fmt.Errorf("load metadata: %w", err) } @@ -317,7 +536,7 @@ func (av *AvailabilityStoreSubsystem) handleQueryChunkSize(msg QueryChunkSize) e } func (av *AvailabilityStoreSubsystem) handleQueryAllChunks(msg QueryAllChunks) error { - meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + meta, err := av.availabilityStore.loadMeta(msg.CandidateHash) if err != nil { msg.Sender <- []ErasureChunk{} return fmt.Errorf("load metadata: %w", err) @@ -339,7 +558,7 @@ func (av *AvailabilityStoreSubsystem) handleQueryAllChunks(msg QueryAllChunks) e } func (av *AvailabilityStoreSubsystem) handleQueryChunkAvailability(msg QueryChunkAvailability) error { - meta, err := av.availabilityStore.loadMetaData(msg.CandidateHash) + meta, err := av.availabilityStore.loadMeta(msg.CandidateHash) if err != nil { msg.Sender <- false return fmt.Errorf("load metadata: %w", err) @@ -349,7 +568,7 @@ func (av *AvailabilityStoreSubsystem) handleQueryChunkAvailability(msg QueryChun } func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) error { - err := av.availabilityStore.storeChunk(msg.CandidateHash, msg.Chunk) + _, err := av.availabilityStore.storeChunk(msg.CandidateHash, msg.Chunk) if err != nil { msg.Sender <- err return fmt.Errorf("store chunk: %w", err) @@ -359,12 +578,27 @@ func (av *AvailabilityStoreSubsystem) handleStoreChunk(msg StoreChunk) error { } func (av *AvailabilityStoreSubsystem) handleStoreAvailableData(msg StoreAvailableData) error { - err := av.availabilityStore.storeAvailableData(msg.CandidateHash.Value, msg.AvailableData) - if err != nil { + // TODO: add to metric on_chunks_received + + res, err := av.availabilityStore.storeAvailableData(av, msg.CandidateHash, uint(msg.NumValidators), + msg.AvailableData, + msg.ExpectedErasureRoot) + if res { + msg.Sender <- nil + return nil + } + if err != nil && errors.Is(err, errInvalidErasureRoot) { msg.Sender <- err return fmt.Errorf("store available data: %w", err) } - msg.Sender <- err // TODO: determine how to replicate Rust's Result type + if err != nil { + // We do not bubble up internal errors to caller subsystems, instead the + // tx channel is dropped and that error is caught by the caller subsystem. + // + // We bubble up the specific error here so `av-store` logs still tell what + // happened. + return fmt.Errorf("store available data: %w", err) + } return nil } diff --git a/dot/parachain/availability-store/availability_store_test.go b/dot/parachain/availability-store/availability_store_test.go index a0bc7017a3..04ec82e328 100644 --- a/dot/parachain/availability-store/availability_store_test.go +++ b/dot/parachain/availability-store/availability_store_test.go @@ -4,6 +4,7 @@ package availabilitystore import ( + "bytes" "errors" "testing" @@ -11,6 +12,7 @@ import ( "github.com/ChainSafe/gossamer/dot/state" "github.com/ChainSafe/gossamer/internal/database" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/pkg/scale" "github.com/stretchr/testify/require" ) @@ -31,52 +33,370 @@ var ( ParentHead: parachaintypes.HeadData{Data: []byte("parentHead")}, }, } + + testCandidateHash = parachaintypes.CandidateHash{Value: common.Hash{0x01}} ) func setupTestDB(t *testing.T) database.Database { inmemoryDB := state.NewInMemoryDB(t) as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + metaState := NewStateVDT() + err := metaState.Set(Unavailable{}) + require.NoError(t, err) + meta := CandidateMeta{ + State: metaState, + DataAvailable: false, + ChunksStored: []bool{false, false, false}, + } + + dataBytes, err := scale.Marshal(meta) + require.NoError(t, err) + err = batch.meta.Put(testCandidateHash.Value[:], dataBytes) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) - err := as.storeChunk(common.Hash{0x01}, testChunk1) + stored, err := as.storeChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, testChunk1) require.NoError(t, err) - err = as.storeChunk(common.Hash{0x01}, testChunk2) + require.Equal(t, true, stored) + stored, err = as.storeChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, testChunk2) require.NoError(t, err) + require.Equal(t, true, stored) - err = as.storeAvailableData(common.Hash{0x01}, testavailableData1) + batch = newAvailabilityStoreBatch(as) + dataBytes, err = scale.Marshal(testavailableData1) + require.NoError(t, err) + err = batch.available.Put(testCandidateHash.Value[:], dataBytes) + require.NoError(t, err) + + err = batch.flush() require.NoError(t, err) return inmemoryDB } -func TestAvailabilityStore_StoreLoadAvailableData(t *testing.T) { + +func TestAvailabilityStore_WriteLoadDeleteAvailableData(t *testing.T) { inmemoryDB := state.NewInMemoryDB(t) as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + + dataBytes, err := scale.Marshal(testavailableData1) + require.NoError(t, err) + err = batch.available.Put(testCandidateHash.Value[:], dataBytes) + require.NoError(t, err) - err := as.storeAvailableData(common.Hash{0x01}, testavailableData1) + err = batch.flush() require.NoError(t, err) - got, err := as.loadAvailableData(common.Hash{0x01}) + got, err := as.loadAvailableData(parachaintypes.CandidateHash{Value: common.Hash{0x01}}) require.NoError(t, err) require.Equal(t, &testavailableData1, got) - got, err = as.loadAvailableData(common.Hash{0x02}) + got, err = as.loadAvailableData(parachaintypes.CandidateHash{Value: common.Hash{0x02}}) require.EqualError(t, err, "getting candidate 0x0200000000000000000000000000000000000000000000000000000000000000"+ " from available table: pebble: not found") - var ExpectedAvailableData *AvailableData = nil - require.Equal(t, ExpectedAvailableData, got) + require.Equal(t, (*AvailableData)(nil), got) + + batch = newAvailabilityStoreBatch(as) + + err = batch.available.Del(testCandidateHash.Value[:]) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + got, err = as.loadAvailableData(parachaintypes.CandidateHash{Value: common.Hash{0x01}}) + require.EqualError(t, err, "getting candidate 0x0100000000000000000000000000000000000000000000000000000000000000"+ + " from available table: pebble: not found") + require.Equal(t, (*AvailableData)(nil), got) } -func TestAvailabilityStore_StoreLoadChuckData(t *testing.T) { +func TestAvailabilityStore_WriteLoadDeleteChuckData(t *testing.T) { inmemoryDB := state.NewInMemoryDB(t) as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + metaState := NewStateVDT() + err := metaState.Set(Unavailable{}) + require.NoError(t, err) + meta := CandidateMeta{ + State: metaState, + DataAvailable: false, + ChunksStored: []bool{false, false}, + } + dataBytes, err := scale.Marshal(meta) + require.NoError(t, err) + err = batch.meta.Put(parachaintypes.CandidateHash{Value: common.Hash{0x01}}.Value.ToBytes(), dataBytes) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) - err := as.storeChunk(common.Hash{0x01}, testChunk1) + got, err := as.storeChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, testChunk1) require.NoError(t, err) - err = as.storeChunk(common.Hash{0x01}, testChunk2) + require.Equal(t, true, got) + got, err = as.storeChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, testChunk2) require.NoError(t, err) + require.Equal(t, true, got) - resultChunk, err := as.loadChunk(common.Hash{0x01}, 0) + resultChunk, err := as.loadChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, 0) require.NoError(t, err) require.Equal(t, &testChunk1, resultChunk) + + resultChunk, err = as.loadChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, 1) + require.NoError(t, err) + require.Equal(t, &testChunk2, resultChunk) + + batch = newAvailabilityStoreBatch(as) + err = batch.chunk.Del(append(testCandidateHash.Value[:], uint32ToBytes(0)...)) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + resultChunk, err = as.loadChunk(parachaintypes.CandidateHash{Value: common.Hash{0x01}}, 0) + require.EqualError(t, err, "getting candidate 0x0100000000000000000000000000000000000000000000000000000000000000,"+ + " index 0 from chunk table: pebble: not found") + require.Equal(t, (*ErasureChunk)(nil), resultChunk) +} + +func TestAvailabilityStore_WriteLoadDeleteMeta(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + metaState := NewStateVDT() + err := metaState.Set(Unavailable{}) + require.NoError(t, err) + meta := &CandidateMeta{ + State: metaState, + } + + dataBytes, err := scale.Marshal(*meta) + require.NoError(t, err) + err = batch.meta.Put(testCandidateHash.Value[:], dataBytes) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + got, err := as.loadMeta(parachaintypes.CandidateHash{Value: common.Hash{0x01}}) + require.NoError(t, err) + require.Equal(t, meta, got) + + batch = newAvailabilityStoreBatch(as) + + err = batch.meta.Del(testCandidateHash.Value[:]) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + got, err = as.loadMeta(parachaintypes.CandidateHash{Value: common.Hash{0x01}}) + require.EqualError(t, err, "getting candidate 0x0100000000000000000000000000000000000000000000000000000000000000"+ + " from meta table: pebble: not found") + require.Equal(t, (*CandidateMeta)(nil), got) +} + +func TestAvailabilityStore_WriteLoadDeleteUnfinalizedHeight(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + blockNumber := parachaintypes.BlockNumber(1) + hash := common.Hash{0x02} + hash6 := common.Hash{0x06} + candidateHash := parachaintypes.CandidateHash{Value: common.Hash{0x03}} + + key := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err := batch.unfinalized.Put(key, nil) + require.NoError(t, err) + + key = append(uint32ToBytesBigEndian(uint32(blockNumber)), hash6[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + require.NoError(t, err) + key = append(uint32ToBytesBigEndian(uint32(0)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + require.NoError(t, err) + key = append(uint32ToBytesBigEndian(uint32(2)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + // check that the key is written + key12 := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash[:]...) + key12 = append(key12, candidateHash.Value[:]...) + + got, err := as.unfinalized.Get(key12) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + key16 := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash6[:]...) + key16 = append(key16, candidateHash.Value[:]...) + + got, err = as.unfinalized.Get(key16) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + // delete height, (block 1) + batch = newAvailabilityStoreBatch(as) + keyPrefix := append([]byte(unfinalizedPrefix), uint32ToBytesBigEndian(uint32(blockNumber))...) + itr := as.unfinalized.NewIterator() + defer itr.Release() + + for itr.First(); itr.Valid(); itr.Next() { + comp := bytes.Compare(itr.Key()[0:len(keyPrefix)], keyPrefix) + if comp < 0 { + continue + } else if comp > 0 { + break + } + err := batch.unfinalized.Del(itr.Key()[len(unfinalizedPrefix):]) + require.NoError(t, err) + } + err = batch.flush() + require.NoError(t, err) + + // check that the key is deleted + got, err = as.unfinalized.Get(key12) + require.EqualError(t, err, "pebble: not found") + require.Equal(t, []byte(nil), got) + + got, err = as.unfinalized.Get(key16) + require.EqualError(t, err, "pebble: not found") + require.Equal(t, []byte(nil), got) + + // check that the other keys are not deleted + key = append(uint32ToBytesBigEndian(uint32(0)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + got, err = as.unfinalized.Get(key) + require.NoError(t, err) + require.Equal(t, []byte{}, got) +} + +func TestAvailabilityStore_WriteLoadDeleteUnfinalizedInclusion(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + blockNumber := parachaintypes.BlockNumber(1) + hash := common.Hash{0x02} + hash6 := common.Hash{0x06} + candidateHash := parachaintypes.CandidateHash{Value: common.Hash{0x03}} + key := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err := batch.unfinalized.Put(key, nil) + require.NoError(t, err) + key = append(uint32ToBytesBigEndian(uint32(blockNumber)), hash6[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + require.NoError(t, err) + key = append(uint32ToBytesBigEndian(uint32(0)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + require.NoError(t, err) + key = append(uint32ToBytesBigEndian(uint32(2)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Put(key, nil) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + // check that the key is written + key12 := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash[:]...) + key12 = append(key12, candidateHash.Value[:]...) + + got, err := as.unfinalized.Get(key12) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + key16 := append(uint32ToBytesBigEndian(uint32(blockNumber)), hash6[:]...) + key16 = append(key16, candidateHash.Value[:]...) + + got, err = as.unfinalized.Get(key16) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + // delete inclusion, (block 1, hash 2) + batch = newAvailabilityStoreBatch(as) + key = append(uint32ToBytesBigEndian(uint32(blockNumber)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + err = batch.unfinalized.Del(key) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + // check that the key is deleted + got, err = as.unfinalized.Get(key12) + require.EqualError(t, err, "pebble: not found") + require.Equal(t, []byte(nil), got) + + // check that the other keys are not deleted + got, err = as.unfinalized.Get(key16) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + key = append(uint32ToBytesBigEndian(uint32(0)), hash[:]...) + key = append(key, candidateHash.Value[:]...) + got, err = as.unfinalized.Get(key) + require.NoError(t, err) + require.Equal(t, []byte{}, got) +} + +func TestAvailabilityStore_WriteDeletePruningKey(t *testing.T) { + inmemoryDB := state.NewInMemoryDB(t) + as := NewAvailabilityStore(inmemoryDB) + batch := newAvailabilityStoreBatch(as) + candidateHash := parachaintypes.CandidateHash{Value: common.Hash{0x03}} + + pruneKey := append(BETimestamp(1).ToBigEndianBytes(), candidateHash.Value[:]...) + err := batch.pruneByTime.Put(pruneKey, nil) + require.NoError(t, err) + + pruneKey = append(BETimestamp(2).ToBigEndianBytes(), candidateHash.Value[:]...) + err = batch.pruneByTime.Put(pruneKey, nil) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + // check that the key is written + key1 := append(BETimestamp(1).ToBigEndianBytes(), candidateHash.Value[:]...) + + got, err := as.pruneByTime.Get(key1) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + key2 := append(BETimestamp(2).ToBigEndianBytes(), candidateHash.Value[:]...) + got, err = as.pruneByTime.Get(key2) + require.NoError(t, err) + require.Equal(t, []byte{}, got) + + // delete pruning key, timestamp 1 + batch = newAvailabilityStoreBatch(as) + pruneKey = append(BETimestamp(1).ToBigEndianBytes(), candidateHash.Value[:]...) + err = batch.pruneByTime.Del(pruneKey) + require.NoError(t, err) + + err = batch.flush() + require.NoError(t, err) + + // check that the key is deleted + got, err = as.pruneByTime.Get(key1) + require.EqualError(t, err, "pebble: not found") + require.Equal(t, []byte(nil), got) + + // check that the other keys are not deleted + got, err = as.pruneByTime.Get(key2) + require.NoError(t, err) + require.Equal(t, []byte{}, got) } func TestAvailabilityStoreSubsystem_handleQueryAvailableData(t *testing.T) { @@ -95,7 +415,7 @@ func TestAvailabilityStoreSubsystem_handleQueryAvailableData(t *testing.T) { }{ "available_data_found": { msg: QueryAvailableData{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, }, msgSenderChan: make(chan AvailableData), expectedResult: testavailableData1, @@ -103,7 +423,7 @@ func TestAvailabilityStoreSubsystem_handleQueryAvailableData(t *testing.T) { }, "available_data_not_found": { msg: QueryAvailableData{ - CandidateHash: common.Hash{0x07}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x07}}, }, msgSenderChan: make(chan AvailableData), expectedResult: AvailableData{}, @@ -150,7 +470,7 @@ func TestAvailabilityStoreSubsystem_handleQueryDataAvailability(t *testing.T) { }{ "data_available_true": { msg: QueryDataAvailability{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, }, msgSenderChan: make(chan bool), expectedResult: true, @@ -158,7 +478,7 @@ func TestAvailabilityStoreSubsystem_handleQueryDataAvailability(t *testing.T) { }, "data_available_false": { msg: QueryDataAvailability{ - CandidateHash: common.Hash{0x07}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x07}}, }, msgSenderChan: make(chan bool), expectedResult: false, @@ -200,7 +520,7 @@ func TestAvailabilityStoreSubsystem_handleQueryChunk(t *testing.T) { }{ "chunk_found": { msg: QueryChunk{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, }, msgSenderChan: make(chan ErasureChunk), expectedResult: testChunk1, @@ -208,7 +528,7 @@ func TestAvailabilityStoreSubsystem_handleQueryChunk(t *testing.T) { }, "query_chunk_not_found": { msg: QueryChunk{ - CandidateHash: common.Hash{0x07}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x07}}, }, msgSenderChan: make(chan ErasureChunk), expectedResult: ErasureChunk{}, @@ -255,7 +575,7 @@ func TestAvailabilityStoreSubsystem_handleQueryAllChunks(t *testing.T) { }{ "chunks_found": { msg: QueryAllChunks{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, }, msgSenderChan: make(chan []ErasureChunk), expectedResult: []ErasureChunk{testChunk1, testChunk2}, @@ -263,13 +583,13 @@ func TestAvailabilityStoreSubsystem_handleQueryAllChunks(t *testing.T) { }, "query_chunks_not_found": { msg: QueryAllChunks{ - CandidateHash: common.Hash{0x07}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x07}}, }, msgSenderChan: make(chan []ErasureChunk), expectedResult: []ErasureChunk{}, err: errors.New( "load metadata: getting candidate 0x0700000000000000000000000000000000000000000000000000000000000000" + - " from available table: pebble: not found"), + " from meta table: pebble: not found"), }, } for name, tt := range tests { @@ -310,7 +630,7 @@ func TestAvailabilityStoreSubsystem_handleQueryChunkAvailability(t *testing.T) { }{ "query_chuck_availability_true": { msg: QueryChunkAvailability{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, ValidatorIndex: 0, }, msgSenderChan: make(chan bool), @@ -318,7 +638,7 @@ func TestAvailabilityStoreSubsystem_handleQueryChunkAvailability(t *testing.T) { }, "query_chuck_availability_false": { msg: QueryChunkAvailability{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, ValidatorIndex: 2, }, msgSenderChan: make(chan bool), @@ -326,14 +646,14 @@ func TestAvailabilityStoreSubsystem_handleQueryChunkAvailability(t *testing.T) { }, "query_chuck_availability_candidate_not_found_false": { msg: QueryChunkAvailability{ - CandidateHash: common.Hash{0x07}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x07}}, ValidatorIndex: 0, }, msgSenderChan: make(chan bool), expectedResult: false, err: errors.New( "load metadata: getting candidate 0x0700000000000000000000000000000000000000000000000000000000000000" + - " from available table: pebble: not found"), + " from meta table: pebble: not found"), }, } for name, tt := range tests { @@ -366,7 +686,7 @@ func TestAvailabilityStore_handleStoreChunk(t *testing.T) { } msgSenderChan := make(chan any) msg := StoreChunk{ - CandidateHash: common.Hash{0x01}, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, Chunk: testChunk1, Sender: msgSenderChan, } @@ -384,16 +704,86 @@ func TestAvailabilityStore_handleStoreAvailableData(t *testing.T) { } msgSenderChan := make(chan error) msg := StoreAvailableData{ - CandidateHash: parachaintypes.CandidateHash{ - Value: common.Hash{0x01}, - }, - NumValidators: 0, + CandidateHash: parachaintypes.CandidateHash{Value: common.Hash{0x01}}, + NumValidators: 2, AvailableData: AvailableData{}, - ExpectedErasureRoot: common.Hash{}, + ExpectedErasureRoot: common.MustHexToHash("0xc3d486f444a752cbf49857ceb2fce0a235268fb8b63e9e019eab619d192650bc"), Sender: msgSenderChan, } - go asSub.handleStoreAvailableData(msg) + go func() { + err := asSub.handleStoreAvailableData(msg) + require.NoError(t, err) + }() msgSenderChanResult := <-msg.Sender require.Equal(t, nil, msgSenderChanResult) } + +func TestAvailabilityStore_storeAvailableData(t *testing.T) { + t.Parallel() + type args struct { + candidate parachaintypes.CandidateHash + nValidators uint + data AvailableData + expectedErasureRoot common.Hash + } + tests := map[string]struct { + args args + want bool + err error + }{ + "empty_availableData": { + args: args{ + candidate: parachaintypes.CandidateHash{}, + nValidators: 0, + data: AvailableData{}, + expectedErasureRoot: common.Hash{}, + }, + want: false, + err: errors.New("obtaining chunks: expected at least 2 validators"), + }, + "2_validators": { + args: args{ + candidate: parachaintypes.CandidateHash{}, + nValidators: 2, + data: AvailableData{ + PoV: parachaintypes.PoV{BlockData: []byte{2}}, + }, + expectedErasureRoot: common.MustHexToHash("0x513489282098e960bfd57ed52d62838ce9395f3f59257f1f40fadd02261a7991"), + }, + want: true, + err: nil, + }, + "2_validators_error_erasure_root": { + args: args{ + candidate: parachaintypes.CandidateHash{}, + nValidators: 2, + data: AvailableData{ + PoV: parachaintypes.PoV{BlockData: []byte{2}}, + }, + expectedErasureRoot: common.Hash{}, + }, + want: false, + err: errInvalidErasureRoot, + }, + } + for name, tt := range tests { + tt := tt + t.Run(name, func(t *testing.T) { + t.Parallel() + inmemoryDB := setupTestDB(t) + as := NewAvailabilityStore(inmemoryDB) + asSub := &AvailabilityStoreSubsystem{ + availabilityStore: *as, + } + got, err := as.storeAvailableData(asSub, tt.args.candidate, tt.args.nValidators, + tt.args.data, tt.args.expectedErasureRoot) + if tt.err == nil { + require.NoError(t, err) + } else { + require.EqualError(t, err, tt.err.Error()) + } + require.Equal(t, tt.want, got) + }) + } +} diff --git a/dot/parachain/availability-store/messages.go b/dot/parachain/availability-store/messages.go index 1486f4fc37..1435f6862f 100644 --- a/dot/parachain/availability-store/messages.go +++ b/dot/parachain/availability-store/messages.go @@ -4,16 +4,21 @@ package availabilitystore import ( + "errors" + "fmt" "time" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/lib/common" + "github.com/ChainSafe/gossamer/lib/trie" "github.com/ChainSafe/gossamer/pkg/scale" ) +var errInvalidErasureRoot = errors.New("Invalid erasure root") + // QueryAvailableData query a AvailableData from the AV store type QueryAvailableData struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash Sender chan AvailableData } @@ -23,7 +28,7 @@ type QueryAvailableData struct { // matters, but we don't want to necessarily pass around multiple // megabytes of data to get a single bit of information. type QueryDataAvailability struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash Sender chan bool } @@ -36,20 +41,20 @@ type ErasureChunk struct { // QueryChunk query an `ErasureChunk` from the AV store by candidate hash and validator index type QueryChunk struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash ValidatorIndex uint32 Sender chan ErasureChunk } // QueryChunkSize get the size of an `ErasureChunk` from the AV store by candidate hash type QueryChunkSize struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash Sender chan uint32 } // QueryAllChunks query all chunks that we have for the given candidate hash type QueryAllChunks struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash Sender chan []ErasureChunk } @@ -59,14 +64,14 @@ type QueryAllChunks struct { // matters, but we don't want to necessarily pass around multiple // megabytes of data to get a single bit of information. type QueryChunkAvailability struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash ValidatorIndex uint32 Sender chan bool } // StoreChunk store an `ErasureChunk` in the AV store type StoreChunk struct { - CandidateHash common.Hash + CandidateHash parachaintypes.CandidateHash Chunk ErasureChunk Sender chan any } @@ -74,13 +79,9 @@ type StoreChunk struct { // StoreAvailableData computes and checks the erasure root of `AvailableData` // before storing its chunks in the AV store. type StoreAvailableData struct { - // A hash of the candidate this `ASMStoreAvailableData` belongs to. - CandidateHash parachaintypes.CandidateHash - // The number of validators in the session. - NumValidators uint32 - // The `AvailableData` itself. - AvailableData AvailableData - // Erasure root we expect to get after chunking. + CandidateHash parachaintypes.CandidateHash + NumValidators uint32 + AvailableData AvailableData ExpectedErasureRoot common.Hash // channel to send result to. Sender chan error @@ -102,15 +103,31 @@ type CandidateMeta struct { // State is the state of candidate data type State scale.VaryingDataType +// New will enable scale to create new instance when needed +func (State) New() State { + return NewStateVDT() +} + // NewState creates a new State -func NewState() State { +func NewStateVDT() State { vdt := scale.MustNewVaryingDataType(Unavailable{}, Unfinalized{}, Finalized{}) return State(vdt) } +// Set will set VaryingDataTypeValue using underlying VaryingDataType +func (s *State) Set(val scale.VaryingDataTypeValue) (err error) { + vdt := scale.VaryingDataType(*s) + err = vdt.Set(val) + if err != nil { + return fmt.Errorf("setting value te varying data type: %w", err) + } + *s = State(vdt) + return nil +} + // Unavailable candidate data was first observed at the given time but in not available in any black type Unavailable struct { - Timestamp time.Time + Timestamp BETimestamp } // Index returns the index of the varying data type @@ -148,3 +165,10 @@ type BlockNumberHash struct { blockNumber parachaintypes.BlockNumber //nolint:unused,structcheck blockHash common.Hash //nolint:unused,structcheck } + +type branches struct { + trieStorage *trie.Trie + root common.Hash + chunks [][]byte + currentPos uint +} diff --git a/dot/parachain/availability-store/register.go b/dot/parachain/availability-store/register.go index 932945b405..b77e81df4b 100644 --- a/dot/parachain/availability-store/register.go +++ b/dot/parachain/availability-store/register.go @@ -9,6 +9,7 @@ func Register(overseerChan chan<- any, st *state.Service) (*AvailabilityStoreSub availabilityStore := NewAvailabilityStore(st.DB()) availabilityStoreSubsystem := AvailabilityStoreSubsystem{ + pruningConfig: defaultPruningConfig, SubSystemToOverseer: overseerChan, availabilityStore: *availabilityStore, } From bfa13b0ab5ef2da8baff74094a178d798e4f7fa0 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 24 Jan 2024 19:40:31 +0530 Subject: [PATCH 72/85] temp --- dot/parachain/collator-protocol/message.go | 2 ++ .../collator-protocol/validator_side.go | 12 +++++++-- .../collator-protocol/validator_side_test.go | 25 +++++++++++++++++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 37b4710036..e7757c6603 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -227,6 +227,8 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( collatorID parachaintypes.CollatorID, prospectiveCandidate *ProspectiveCandidate) error { + fmt.Println("230") + // TODO: return errors pendingCollation := PendingCollation{ RelayParent: relayParent, diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 8b7238c894..ba2efbea4f 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -527,7 +527,11 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { _, ok := cpvs.BlockedAdvertisements[backed.String()] if ok { delete(cpvs.BlockedAdvertisements, backed.String()) - cpvs.requestUnblockedCollations(backed) + + err := cpvs.requestUnblockedCollations(backed) + if err != nil { + return fmt.Errorf("requesting unblocked collations: %w", err) + } } case collatorprotocolmessages.Invalid: invalidOverseerMsg := msg @@ -572,24 +576,28 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // requestUnblockedCollations Checks whether any of the advertisements are unblocked and attempts to fetch them. func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed collatorprotocolmessages.Backed) error { + fmt.Println("78") for _, blockedAdvertisements := range cpvs.BlockedAdvertisements { - + fmt.Println("580") newBlockedAdvertisements := []BlockedAdvertisement{} for _, blockedAdvertisement := range blockedAdvertisements { isSecondingAllowed := cpvs.canSecond( backed.ParaID, blockedAdvertisement.candidateRelayParent, blockedAdvertisement.candidateHash, backed.ParaHead) + fmt.Println("586") if !isSecondingAllowed { newBlockedAdvertisements = append(newBlockedAdvertisements, blockedAdvertisement) continue } + fmt.Println("591") perRelayParent, ok := cpvs.perRelayParent[blockedAdvertisement.candidateRelayParent] if !ok { return ErrRelayParentUnknown } + fmt.Println("597") err := cpvs.enqueueCollation( perRelayParent.collations, diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index eb815c7412..41f2b529a3 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -55,6 +55,7 @@ func TestProcessOverseerMessage(t *testing.T) { net Network fetchedCandidates map[string]CollationEvent deletesFetchCandidate bool + blockedAdvertisements map[string][]BlockedAdvertisement errString string }{ { @@ -253,6 +254,25 @@ func TestProcessOverseerMessage(t *testing.T) { deletesFetchCandidate: true, errString: "", }, + { + description: "Backed message fails with unknown relay parent", + msg: collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(6), + ParaHead: common.Hash{}, + }, + + blockedAdvertisements: map[string][]BlockedAdvertisement{ + "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + }, + errString: ErrRelayParentUnknown.Error(), + }, } for _, c := range testCases { c := c @@ -261,8 +281,9 @@ func TestProcessOverseerMessage(t *testing.T) { cpvs := CollatorProtocolValidatorSide{ net: c.net, // perRelayParent: c.perRelayParent, - fetchedCandidates: c.fetchedCandidates, - peerData: c.peerData, + fetchedCandidates: c.fetchedCandidates, + peerData: c.peerData, + BlockedAdvertisements: c.blockedAdvertisements, // activeLeaves: c.activeLeaves, } From 0db0f7cb0ee08cbe1f0e78b98b134ef8e81cd37c Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 25 Jan 2024 13:59:35 +0530 Subject: [PATCH 73/85] temp --- dot/parachain/collator-protocol/validator_side_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 41f2b529a3..a82730f068 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -270,6 +270,14 @@ func TestProcessOverseerMessage(t *testing.T) { candidateHash: parachaintypes.CandidateHash{}, }, }, + "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, }, errString: ErrRelayParentUnknown.Error(), }, From 8b82afc668c770f30221cd160e570908fa0b23d8 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 30 Jan 2024 15:12:41 +0530 Subject: [PATCH 74/85] temp --- dot/parachain/collator-protocol/message.go | 41 +++++++++++-------- .../collator-protocol/validator_side.go | 7 +++- dot/parachain/overseer/overseer.go | 6 ++- dot/parachain/service.go | 4 ++ dot/parachain/types/subsystems.go | 8 ++++ 5 files changed, 46 insertions(+), 20 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index e7757c6603..3efaa5d11d 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -6,6 +6,7 @@ package collatorprotocol import ( "errors" "fmt" + "time" "github.com/ChainSafe/gossamer/dot/network" "github.com/ChainSafe/gossamer/dot/parachain/backing" @@ -195,26 +196,24 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( candidateRelayParent common.Hash, candidateHash parachaintypes.CandidateHash, parentHeadDataHash common.Hash, -) bool { +) (bool, error) { + canSecondRequest := backing.CanSecondMessage{ CandidateParaID: candidateParaID, CandidateRelayParent: candidateRelayParent, CandidateHash: candidateHash, ParentHeadDataHash: parentHeadDataHash, + ResponseCh: make(chan bool), } - responseChan := make(chan bool) + cpvs.SubSystemToOverseer <- canSecondRequest - cpvs.SubSystemToOverseer <- struct { - responseChan chan bool - canSecondRequest backing.CanSecondMessage - }{ - responseChan: responseChan, - canSecondRequest: canSecondRequest, + select { + case canSecondResponse := <-canSecondRequest.ResponseCh: + return canSecondResponse, nil + case <-time.After(parachaintypes.SubsystemRequestTimeout): + return false, parachaintypes.ErrSubsystemRequestTimeout } - - // TODO: Add timeout - return <-responseChan } // Enqueue collation for fetching. The advertisement is expected to be @@ -365,12 +364,20 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo } /*NOTE:---------------------------------------Matters only in V2----------------------------------------------*/ - isSecondingAllowed := !perRelayParent.prospectiveParachainMode.IsEnabled || cpvs.canSecond( - collatorParaID, - relayParent, - prospectiveCandidate.CandidateHash, - prospectiveCandidate.ParentHeadDataHash, - ) + var isSecondingAllowed bool + if !perRelayParent.prospectiveParachainMode.IsEnabled { + isSecondingAllowed = true + } else { + isSecondingAllowed, err = cpvs.canSecond( + collatorParaID, + relayParent, + prospectiveCandidate.CandidateHash, + prospectiveCandidate.ParentHeadDataHash, + ) + if err != nil { + return fmt.Errorf("checking if seconding is allowed: %w", err) + } + } if !isSecondingAllowed { logger.Infof("Seconding is not allowed by backing, queueing advertisement,"+ diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index ba2efbea4f..a3d4fb5f05 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -583,8 +583,11 @@ func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed coll newBlockedAdvertisements := []BlockedAdvertisement{} for _, blockedAdvertisement := range blockedAdvertisements { - isSecondingAllowed := cpvs.canSecond( + isSecondingAllowed, err := cpvs.canSecond( backed.ParaID, blockedAdvertisement.candidateRelayParent, blockedAdvertisement.candidateHash, backed.ParaHead) + if err != nil { + return fmt.Errorf("checking if seconding is allowed: %w", err) + } fmt.Println("586") if !isSecondingAllowed { @@ -599,7 +602,7 @@ func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed coll } fmt.Println("597") - err := cpvs.enqueueCollation( + err = cpvs.enqueueCollation( perRelayParent.collations, blockedAdvertisement.candidateRelayParent, backed.ParaID, diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index f56c9565c1..7c2727c6d2 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -59,7 +59,7 @@ func NewOverseer(blockState BlockState) *Overseer { errChan: make(chan error), blockState: blockState, activeLeaves: make(map[common.Hash]uint32), - SubsystemsToOverseer: make(chan any), + SubsystemsToOverseer: make(chan any, 128), subsystems: make(map[Subsystem]chan any), nameToSubsystem: make(map[parachaintypes.SubSystemName]Subsystem), } @@ -104,10 +104,14 @@ func (o *Overseer) processMessages() { for { select { case msg := <-o.SubsystemsToOverseer: + + fmt.Println("we come here") var subsystem Subsystem switch msg.(type) { case backing.GetBackedCandidatesMessage, backing.CanSecondMessage, backing.SecondMessage, backing.StatementMessage: + fmt.Println("we come here too") + subsystem = o.nameToSubsystem[parachaintypes.CandidateBacking] case collatorprotocolmessages.CollateOn, collatorprotocolmessages.DistributeCollation, diff --git a/dot/parachain/service.go b/dot/parachain/service.go index bbeeafdf00..5c994f0f5c 100644 --- a/dot/parachain/service.go +++ b/dot/parachain/service.go @@ -35,6 +35,10 @@ var logger = log.NewFromGlobal(log.AddContext("pkg", "parachain")) func NewService(net Network, forkID string, st *state.Service) (*Service, error) { overseer := overseer.NewOverseer(st.Block) + err := overseer.Start() + if err != nil { + return nil, fmt.Errorf("starting overseer: %w", err) + } genesisHash := st.Block.GenesisHash() availabilityStore, err := availability_store.Register(overseer.SubsystemsToOverseer, st) diff --git a/dot/parachain/types/subsystems.go b/dot/parachain/types/subsystems.go index 5e40a1d420..4d423910d6 100644 --- a/dot/parachain/types/subsystems.go +++ b/dot/parachain/types/subsystems.go @@ -3,6 +3,11 @@ package parachaintypes +import ( + "errors" + "time" +) + type SubSystemName string const ( @@ -10,3 +15,6 @@ const ( CollationProtocol SubSystemName = "CollationProtocol" AvailabilityStore SubSystemName = "AvailabilityStore" ) + +var SubsystemRequestTimeout = 400 * time.Millisecond +var ErrSubsystemRequestTimeout = errors.New("subsystem request timed out") From f6033300994a6427105a2235b6c061159cb05e25 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Tue, 13 Feb 2024 16:00:45 +0530 Subject: [PATCH 75/85] temp --- dot/parachain/collator-protocol/message.go | 2 +- dot/parachain/collator-protocol/validator_side_test.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 3efaa5d11d..1da0ceb4ab 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -207,7 +207,7 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( } cpvs.SubSystemToOverseer <- canSecondRequest - + // we never reach this line. Nothing happens after this channel. select { case canSecondResponse := <-canSecondRequest.ResponseCh: return canSecondResponse, nil diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 80bfd8075e..0e0e0da2ac 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -287,7 +287,8 @@ func TestProcessOverseerMessage(t *testing.T) { t.Run(c.description, func(t *testing.T) { t.Parallel() cpvs := CollatorProtocolValidatorSide{ - net: c.net, + net: c.net, + SubSystemToOverseer: make(chan<- any), // perRelayParent: c.perRelayParent, fetchedCandidates: c.fetchedCandidates, peerData: c.peerData, @@ -312,3 +313,5 @@ func TestProcessOverseerMessage(t *testing.T) { }) } } + +// TODO: Just Write those black box tests. From f4e02bdea1a61cfd29303a4becb6b85cd573e26f Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 15:49:16 +0530 Subject: [PATCH 76/85] tests for backed message --- .../collator-protocol/backed_test.go | 152 ++++++++++++++++++ dot/parachain/collator-protocol/message.go | 3 - .../collator-protocol/mock_blockstate_test.go | 92 +++++++++++ .../collator-protocol/mock_subsystem_test.go | 103 ++++++++++++ .../collator-protocol/mocks_generate_test.go | 2 + dot/parachain/collator-protocol/mocks_test.go | 17 +- .../collator-protocol/validator_side.go | 6 - .../collator-protocol/validator_side_test.go | 38 +---- dot/parachain/overseer/overseer.go | 6 +- dot/parachain/types/subsystems.go | 2 +- 10 files changed, 365 insertions(+), 56 deletions(-) create mode 100644 dot/parachain/collator-protocol/backed_test.go create mode 100644 dot/parachain/collator-protocol/mock_blockstate_test.go create mode 100644 dot/parachain/collator-protocol/mock_subsystem_test.go diff --git a/dot/parachain/collator-protocol/backed_test.go b/dot/parachain/collator-protocol/backed_test.go new file mode 100644 index 0000000000..368c706dc9 --- /dev/null +++ b/dot/parachain/collator-protocol/backed_test.go @@ -0,0 +1,152 @@ +// Copyright 2023 ChainSafe Systems (ON) +// SPDX-License-Identifier: LGPL-3.0-only + +package collatorprotocol + +import ( + "testing" + + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain/backing" + collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" + "github.com/ChainSafe/gossamer/dot/parachain/overseer" + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + types "github.com/ChainSafe/gossamer/dot/types" + "github.com/ChainSafe/gossamer/lib/common" + "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" + "github.com/stretchr/testify/require" + gomock "go.uber.org/mock/gomock" +) + +func TestProcessBackedOverseerMessage(t *testing.T) { + t.Parallel() + + var testCollatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(testCollatorID[:], tempCollatID) + peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + + testCases := []struct { + description string + msg any + deletesBlockedAdvertisement bool + blockedAdvertisements map[string][]BlockedAdvertisement + canSecond bool + errString string + }{ + { + description: "Backed message fails with unknown relay parent", + msg: collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(6), + ParaHead: common.Hash{}, + }, + canSecond: true, + deletesBlockedAdvertisement: true, + blockedAdvertisements: map[string][]BlockedAdvertisement{ + "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + }, + errString: ErrRelayParentUnknown.Error(), + }, + { + description: "Backed message gets processed successfully when seconding is not allowed", + msg: collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(6), + ParaHead: common.Hash{}, + }, + canSecond: false, + blockedAdvertisements: map[string][]BlockedAdvertisement{ + "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + }, + errString: "", + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBlockState := NewMockBlockState(ctrl) + finalizedNotifierChan := make(chan *types.FinalisationInfo) + importedBlockNotiferChan := make(chan *types.Block) + + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(finalizedNotifierChan) + mockBlockState.EXPECT().GetImportedBlockNotifierChannel().Return(importedBlockNotiferChan) + mockBlockState.EXPECT().FreeFinalisedNotifierChannel(finalizedNotifierChan) + mockBlockState.EXPECT().FreeImportedBlockNotifierChannel(importedBlockNotiferChan) + + overseer := overseer.NewOverseer(mockBlockState) + err := overseer.Start() + require.NoError(t, err) + + defer overseer.Stop() + + collationProtocolID := "/6761727661676500000000000000000000000000000000000000000000000000/1/collations/1" + + net := NewMockNetwork(ctrl) + net.EXPECT().RegisterNotificationsProtocol(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + net.EXPECT().GetRequestResponseProtocol(gomock.Any(), collationFetchingRequestTimeout, uint64(collationFetchingMaxResponseSize)).Return(&network.RequestResponseProtocol{}) + cpvs, err := Register(net, protocol.ID(collationProtocolID), overseer.SubsystemsToOverseer) + require.NoError(t, err) + + cpvs.BlockedAdvertisements = c.blockedAdvertisements + + mockBacking := NewMockSubsystem(ctrl) + mockBacking.EXPECT().Name().Return(parachaintypes.CandidateBacking) + overseerToBacking := overseer.RegisterSubsystem(mockBacking) + + go func() { + msg, _ := (<-overseerToBacking).(backing.CanSecondMessage) + msg.ResponseCh <- c.canSecond + }() + + lenBlackedAdvertisementsBefore := len(cpvs.BlockedAdvertisements) + + err = cpvs.processMessage(c.msg) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + if c.deletesBlockedAdvertisement { + require.Equal(t, lenBlackedAdvertisementsBefore-1, len(cpvs.BlockedAdvertisements)) + } else { + require.Equal(t, lenBlackedAdvertisementsBefore, len(cpvs.BlockedAdvertisements)) + } + }) + } +} diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 1da0ceb4ab..15e9bf088a 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -207,7 +207,6 @@ func (cpvs CollatorProtocolValidatorSide) canSecond( } cpvs.SubSystemToOverseer <- canSecondRequest - // we never reach this line. Nothing happens after this channel. select { case canSecondResponse := <-canSecondRequest.ResponseCh: return canSecondResponse, nil @@ -226,8 +225,6 @@ func (cpvs CollatorProtocolValidatorSide) enqueueCollation( collatorID parachaintypes.CollatorID, prospectiveCandidate *ProspectiveCandidate) error { - fmt.Println("230") - // TODO: return errors pendingCollation := PendingCollation{ RelayParent: relayParent, diff --git a/dot/parachain/collator-protocol/mock_blockstate_test.go b/dot/parachain/collator-protocol/mock_blockstate_test.go new file mode 100644 index 0000000000..8beef6c54b --- /dev/null +++ b/dot/parachain/collator-protocol/mock_blockstate_test.go @@ -0,0 +1,92 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/overseer (interfaces: BlockState) +// +// Generated by this command: +// +// mockgen -destination=mock_blockstate_test.go -package=collatorprotocol github.com/ChainSafe/gossamer/dot/parachain/overseer BlockState +// + +// Package collatorprotocol is a generated GoMock package. +package collatorprotocol + +import ( + reflect "reflect" + + types "github.com/ChainSafe/gossamer/dot/types" + gomock "go.uber.org/mock/gomock" +) + +// MockBlockState is a mock of BlockState interface. +type MockBlockState struct { + ctrl *gomock.Controller + recorder *MockBlockStateMockRecorder +} + +// MockBlockStateMockRecorder is the mock recorder for MockBlockState. +type MockBlockStateMockRecorder struct { + mock *MockBlockState +} + +// NewMockBlockState creates a new mock instance. +func NewMockBlockState(ctrl *gomock.Controller) *MockBlockState { + mock := &MockBlockState{ctrl: ctrl} + mock.recorder = &MockBlockStateMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBlockState) EXPECT() *MockBlockStateMockRecorder { + return m.recorder +} + +// FreeFinalisedNotifierChannel mocks base method. +func (m *MockBlockState) FreeFinalisedNotifierChannel(arg0 chan *types.FinalisationInfo) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeFinalisedNotifierChannel", arg0) +} + +// FreeFinalisedNotifierChannel indicates an expected call of FreeFinalisedNotifierChannel. +func (mr *MockBlockStateMockRecorder) FreeFinalisedNotifierChannel(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeFinalisedNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).FreeFinalisedNotifierChannel), arg0) +} + +// FreeImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) FreeImportedBlockNotifierChannel(arg0 chan *types.Block) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "FreeImportedBlockNotifierChannel", arg0) +} + +// FreeImportedBlockNotifierChannel indicates an expected call of FreeImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) FreeImportedBlockNotifierChannel(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FreeImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).FreeImportedBlockNotifierChannel), arg0) +} + +// GetFinalisedNotifierChannel mocks base method. +func (m *MockBlockState) GetFinalisedNotifierChannel() chan *types.FinalisationInfo { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetFinalisedNotifierChannel") + ret0, _ := ret[0].(chan *types.FinalisationInfo) + return ret0 +} + +// GetFinalisedNotifierChannel indicates an expected call of GetFinalisedNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetFinalisedNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFinalisedNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetFinalisedNotifierChannel)) +} + +// GetImportedBlockNotifierChannel mocks base method. +func (m *MockBlockState) GetImportedBlockNotifierChannel() chan *types.Block { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetImportedBlockNotifierChannel") + ret0, _ := ret[0].(chan *types.Block) + return ret0 +} + +// GetImportedBlockNotifierChannel indicates an expected call of GetImportedBlockNotifierChannel. +func (mr *MockBlockStateMockRecorder) GetImportedBlockNotifierChannel() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetImportedBlockNotifierChannel", reflect.TypeOf((*MockBlockState)(nil).GetImportedBlockNotifierChannel)) +} diff --git a/dot/parachain/collator-protocol/mock_subsystem_test.go b/dot/parachain/collator-protocol/mock_subsystem_test.go new file mode 100644 index 0000000000..33f13b48c7 --- /dev/null +++ b/dot/parachain/collator-protocol/mock_subsystem_test.go @@ -0,0 +1,103 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/ChainSafe/gossamer/dot/parachain/overseer (interfaces: Subsystem) +// +// Generated by this command: +// +// mockgen -destination=mock_subsystem_test.go -package=collatorprotocol github.com/ChainSafe/gossamer/dot/parachain/overseer Subsystem +// + +// Package collatorprotocol is a generated GoMock package. +package collatorprotocol + +import ( + context "context" + reflect "reflect" + + parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" + gomock "go.uber.org/mock/gomock" +) + +// MockSubsystem is a mock of Subsystem interface. +type MockSubsystem struct { + ctrl *gomock.Controller + recorder *MockSubsystemMockRecorder +} + +// MockSubsystemMockRecorder is the mock recorder for MockSubsystem. +type MockSubsystemMockRecorder struct { + mock *MockSubsystem +} + +// NewMockSubsystem creates a new mock instance. +func NewMockSubsystem(ctrl *gomock.Controller) *MockSubsystem { + mock := &MockSubsystem{ctrl: ctrl} + mock.recorder = &MockSubsystemMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSubsystem) EXPECT() *MockSubsystemMockRecorder { + return m.recorder +} + +// Name mocks base method. +func (m *MockSubsystem) Name() parachaintypes.SubSystemName { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(parachaintypes.SubSystemName) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockSubsystemMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockSubsystem)(nil).Name)) +} + +// ProcessActiveLeavesUpdateSignal mocks base method. +func (m *MockSubsystem) ProcessActiveLeavesUpdateSignal() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ProcessActiveLeavesUpdateSignal") +} + +// ProcessActiveLeavesUpdateSignal indicates an expected call of ProcessActiveLeavesUpdateSignal. +func (mr *MockSubsystemMockRecorder) ProcessActiveLeavesUpdateSignal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessActiveLeavesUpdateSignal", reflect.TypeOf((*MockSubsystem)(nil).ProcessActiveLeavesUpdateSignal)) +} + +// ProcessBlockFinalizedSignal mocks base method. +func (m *MockSubsystem) ProcessBlockFinalizedSignal() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ProcessBlockFinalizedSignal") +} + +// ProcessBlockFinalizedSignal indicates an expected call of ProcessBlockFinalizedSignal. +func (mr *MockSubsystemMockRecorder) ProcessBlockFinalizedSignal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessBlockFinalizedSignal", reflect.TypeOf((*MockSubsystem)(nil).ProcessBlockFinalizedSignal)) +} + +// Run mocks base method. +func (m *MockSubsystem) Run(arg0 context.Context, arg1, arg2 chan any) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Run", arg0, arg1, arg2) +} + +// Run indicates an expected call of Run. +func (mr *MockSubsystemMockRecorder) Run(arg0, arg1, arg2 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*MockSubsystem)(nil).Run), arg0, arg1, arg2) +} + +// Stop mocks base method. +func (m *MockSubsystem) Stop() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stop") +} + +// Stop indicates an expected call of Stop. +func (mr *MockSubsystemMockRecorder) Stop() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stop", reflect.TypeOf((*MockSubsystem)(nil).Stop)) +} diff --git a/dot/parachain/collator-protocol/mocks_generate_test.go b/dot/parachain/collator-protocol/mocks_generate_test.go index 88c5538252..e366716aa0 100644 --- a/dot/parachain/collator-protocol/mocks_generate_test.go +++ b/dot/parachain/collator-protocol/mocks_generate_test.go @@ -4,3 +4,5 @@ package collatorprotocol //go:generate mockgen -destination=mocks_test.go -package=$GOPACKAGE . Network +//go:generate mockgen -destination=mock_blockstate_test.go -package=$GOPACKAGE github.com/ChainSafe/gossamer/dot/parachain/overseer BlockState +//go:generate mockgen -destination=mock_subsystem_test.go -package=$GOPACKAGE github.com/ChainSafe/gossamer/dot/parachain/overseer Subsystem diff --git a/dot/parachain/collator-protocol/mocks_test.go b/dot/parachain/collator-protocol/mocks_test.go index 63dd483f67..3590bbb20d 100644 --- a/dot/parachain/collator-protocol/mocks_test.go +++ b/dot/parachain/collator-protocol/mocks_test.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: github.com/ChainSafe/gossamer/dot/parachain/collator-protocol (interfaces: Network) +// +// Generated by this command: +// +// mockgen -destination=mocks_test.go -package=collatorprotocol . Network +// // Package collatorprotocol is a generated GoMock package. package collatorprotocol @@ -10,9 +15,9 @@ import ( network "github.com/ChainSafe/gossamer/dot/network" peerset "github.com/ChainSafe/gossamer/dot/peerset" - gomock "go.uber.org/mock/gomock" peer "github.com/libp2p/go-libp2p/core/peer" protocol "github.com/libp2p/go-libp2p/core/protocol" + gomock "go.uber.org/mock/gomock" ) // MockNetwork is a mock of Network interface. @@ -47,7 +52,7 @@ func (m *MockNetwork) GetRequestResponseProtocol(arg0 string, arg1 time.Duration } // GetRequestResponseProtocol indicates an expected call of GetRequestResponseProtocol. -func (mr *MockNetworkMockRecorder) GetRequestResponseProtocol(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) GetRequestResponseProtocol(arg0, arg1, arg2 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRequestResponseProtocol", reflect.TypeOf((*MockNetwork)(nil).GetRequestResponseProtocol), arg0, arg1, arg2) } @@ -59,7 +64,7 @@ func (m *MockNetwork) GossipMessage(arg0 network.NotificationsMessage) { } // GossipMessage indicates an expected call of GossipMessage. -func (mr *MockNetworkMockRecorder) GossipMessage(arg0 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) GossipMessage(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GossipMessage", reflect.TypeOf((*MockNetwork)(nil).GossipMessage), arg0) } @@ -73,7 +78,7 @@ func (m *MockNetwork) RegisterNotificationsProtocol(arg0 protocol.ID, arg1 netwo } // RegisterNotificationsProtocol indicates an expected call of RegisterNotificationsProtocol. -func (mr *MockNetworkMockRecorder) RegisterNotificationsProtocol(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) RegisterNotificationsProtocol(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegisterNotificationsProtocol", reflect.TypeOf((*MockNetwork)(nil).RegisterNotificationsProtocol), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } @@ -85,7 +90,7 @@ func (m *MockNetwork) ReportPeer(arg0 peerset.ReputationChange, arg1 peer.ID) { } // ReportPeer indicates an expected call of ReportPeer. -func (mr *MockNetworkMockRecorder) ReportPeer(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) ReportPeer(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReportPeer", reflect.TypeOf((*MockNetwork)(nil).ReportPeer), arg0, arg1) } @@ -99,7 +104,7 @@ func (m *MockNetwork) SendMessage(arg0 peer.ID, arg1 network.NotificationsMessag } // SendMessage indicates an expected call of SendMessage. -func (mr *MockNetworkMockRecorder) SendMessage(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockNetworkMockRecorder) SendMessage(arg0, arg1 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockNetwork)(nil).SendMessage), arg0, arg1) } diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index fb2aea1268..c1940ba46b 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -576,10 +576,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // requestUnblockedCollations Checks whether any of the advertisements are unblocked and attempts to fetch them. func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed collatorprotocolmessages.Backed) error { - fmt.Println("78") - for _, blockedAdvertisements := range cpvs.BlockedAdvertisements { - fmt.Println("580") newBlockedAdvertisements := []BlockedAdvertisement{} for _, blockedAdvertisement := range blockedAdvertisements { @@ -588,19 +585,16 @@ func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed coll if err != nil { return fmt.Errorf("checking if seconding is allowed: %w", err) } - fmt.Println("586") if !isSecondingAllowed { newBlockedAdvertisements = append(newBlockedAdvertisements, blockedAdvertisement) continue } - fmt.Println("591") perRelayParent, ok := cpvs.perRelayParent[blockedAdvertisement.candidateRelayParent] if !ok { return ErrRelayParentUnknown } - fmt.Println("597") err = cpvs.enqueueCollation( perRelayParent.collations, diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 0e0e0da2ac..4019deeb6f 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -55,7 +55,6 @@ func TestProcessOverseerMessage(t *testing.T) { net Network fetchedCandidates map[string]CollationEvent deletesFetchCandidate bool - blockedAdvertisements map[string][]BlockedAdvertisement errString string }{ { @@ -254,45 +253,16 @@ func TestProcessOverseerMessage(t *testing.T) { deletesFetchCandidate: true, errString: "", }, - { - description: "Backed message fails with unknown relay parent", - msg: collatorprotocolmessages.Backed{ - ParaID: parachaintypes.ParaID(6), - ParaHead: common.Hash{}, - }, - - blockedAdvertisements: map[string][]BlockedAdvertisement{ - "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - }, - errString: ErrRelayParentUnknown.Error(), - }, } for _, c := range testCases { c := c t.Run(c.description, func(t *testing.T) { t.Parallel() cpvs := CollatorProtocolValidatorSide{ - net: c.net, - SubSystemToOverseer: make(chan<- any), + net: c.net, // perRelayParent: c.perRelayParent, - fetchedCandidates: c.fetchedCandidates, - peerData: c.peerData, - BlockedAdvertisements: c.blockedAdvertisements, + fetchedCandidates: c.fetchedCandidates, + peerData: c.peerData, // activeLeaves: c.activeLeaves, } @@ -313,5 +283,3 @@ func TestProcessOverseerMessage(t *testing.T) { }) } } - -// TODO: Just Write those black box tests. diff --git a/dot/parachain/overseer/overseer.go b/dot/parachain/overseer/overseer.go index 7c2727c6d2..f56c9565c1 100644 --- a/dot/parachain/overseer/overseer.go +++ b/dot/parachain/overseer/overseer.go @@ -59,7 +59,7 @@ func NewOverseer(blockState BlockState) *Overseer { errChan: make(chan error), blockState: blockState, activeLeaves: make(map[common.Hash]uint32), - SubsystemsToOverseer: make(chan any, 128), + SubsystemsToOverseer: make(chan any), subsystems: make(map[Subsystem]chan any), nameToSubsystem: make(map[parachaintypes.SubSystemName]Subsystem), } @@ -104,14 +104,10 @@ func (o *Overseer) processMessages() { for { select { case msg := <-o.SubsystemsToOverseer: - - fmt.Println("we come here") var subsystem Subsystem switch msg.(type) { case backing.GetBackedCandidatesMessage, backing.CanSecondMessage, backing.SecondMessage, backing.StatementMessage: - fmt.Println("we come here too") - subsystem = o.nameToSubsystem[parachaintypes.CandidateBacking] case collatorprotocolmessages.CollateOn, collatorprotocolmessages.DistributeCollation, diff --git a/dot/parachain/types/subsystems.go b/dot/parachain/types/subsystems.go index 4d423910d6..0aa384411f 100644 --- a/dot/parachain/types/subsystems.go +++ b/dot/parachain/types/subsystems.go @@ -16,5 +16,5 @@ const ( AvailabilityStore SubSystemName = "AvailabilityStore" ) -var SubsystemRequestTimeout = 400 * time.Millisecond +var SubsystemRequestTimeout = 1 * time.Second var ErrSubsystemRequestTimeout = errors.New("subsystem request timed out") From 21f97ef69f736771bda297909fa33586f4acc005 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 15:55:49 +0530 Subject: [PATCH 77/85] removed backed_test file --- .../collator-protocol/backed_test.go | 152 ------------------ .../collator-protocol/validator_side_test.go | 137 ++++++++++++++++ 2 files changed, 137 insertions(+), 152 deletions(-) delete mode 100644 dot/parachain/collator-protocol/backed_test.go diff --git a/dot/parachain/collator-protocol/backed_test.go b/dot/parachain/collator-protocol/backed_test.go deleted file mode 100644 index 368c706dc9..0000000000 --- a/dot/parachain/collator-protocol/backed_test.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2023 ChainSafe Systems (ON) -// SPDX-License-Identifier: LGPL-3.0-only - -package collatorprotocol - -import ( - "testing" - - "github.com/ChainSafe/gossamer/dot/network" - "github.com/ChainSafe/gossamer/dot/parachain/backing" - collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" - "github.com/ChainSafe/gossamer/dot/parachain/overseer" - parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" - types "github.com/ChainSafe/gossamer/dot/types" - "github.com/ChainSafe/gossamer/lib/common" - "github.com/libp2p/go-libp2p/core/peer" - protocol "github.com/libp2p/go-libp2p/core/protocol" - "github.com/stretchr/testify/require" - gomock "go.uber.org/mock/gomock" -) - -func TestProcessBackedOverseerMessage(t *testing.T) { - t.Parallel() - - var testCollatorID parachaintypes.CollatorID - tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") - copy(testCollatorID[:], tempCollatID) - peerID := peer.ID("testPeerID") - testRelayParent := getDummyHash(5) - - testCases := []struct { - description string - msg any - deletesBlockedAdvertisement bool - blockedAdvertisements map[string][]BlockedAdvertisement - canSecond bool - errString string - }{ - { - description: "Backed message fails with unknown relay parent", - msg: collatorprotocolmessages.Backed{ - ParaID: parachaintypes.ParaID(6), - ParaHead: common.Hash{}, - }, - canSecond: true, - deletesBlockedAdvertisement: true, - blockedAdvertisements: map[string][]BlockedAdvertisement{ - "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - }, - errString: ErrRelayParentUnknown.Error(), - }, - { - description: "Backed message gets processed successfully when seconding is not allowed", - msg: collatorprotocolmessages.Backed{ - ParaID: parachaintypes.ParaID(6), - ParaHead: common.Hash{}, - }, - canSecond: false, - blockedAdvertisements: map[string][]BlockedAdvertisement{ - "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { - { - peerID: peerID, - collatorID: testCollatorID, - candidateRelayParent: testRelayParent, - candidateHash: parachaintypes.CandidateHash{}, - }, - }, - }, - errString: "", - }, - } - for _, c := range testCases { - c := c - t.Run(c.description, func(t *testing.T) { - t.Parallel() - - ctrl := gomock.NewController(t) - defer ctrl.Finish() - mockBlockState := NewMockBlockState(ctrl) - finalizedNotifierChan := make(chan *types.FinalisationInfo) - importedBlockNotiferChan := make(chan *types.Block) - - mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(finalizedNotifierChan) - mockBlockState.EXPECT().GetImportedBlockNotifierChannel().Return(importedBlockNotiferChan) - mockBlockState.EXPECT().FreeFinalisedNotifierChannel(finalizedNotifierChan) - mockBlockState.EXPECT().FreeImportedBlockNotifierChannel(importedBlockNotiferChan) - - overseer := overseer.NewOverseer(mockBlockState) - err := overseer.Start() - require.NoError(t, err) - - defer overseer.Stop() - - collationProtocolID := "/6761727661676500000000000000000000000000000000000000000000000000/1/collations/1" - - net := NewMockNetwork(ctrl) - net.EXPECT().RegisterNotificationsProtocol(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - net.EXPECT().GetRequestResponseProtocol(gomock.Any(), collationFetchingRequestTimeout, uint64(collationFetchingMaxResponseSize)).Return(&network.RequestResponseProtocol{}) - cpvs, err := Register(net, protocol.ID(collationProtocolID), overseer.SubsystemsToOverseer) - require.NoError(t, err) - - cpvs.BlockedAdvertisements = c.blockedAdvertisements - - mockBacking := NewMockSubsystem(ctrl) - mockBacking.EXPECT().Name().Return(parachaintypes.CandidateBacking) - overseerToBacking := overseer.RegisterSubsystem(mockBacking) - - go func() { - msg, _ := (<-overseerToBacking).(backing.CanSecondMessage) - msg.ResponseCh <- c.canSecond - }() - - lenBlackedAdvertisementsBefore := len(cpvs.BlockedAdvertisements) - - err = cpvs.processMessage(c.msg) - if c.errString == "" { - require.NoError(t, err) - } else { - require.ErrorContains(t, err, c.errString) - } - - if c.deletesBlockedAdvertisement { - require.Equal(t, lenBlackedAdvertisementsBefore-1, len(cpvs.BlockedAdvertisements)) - } else { - require.Equal(t, lenBlackedAdvertisementsBefore, len(cpvs.BlockedAdvertisements)) - } - }) - } -} diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 4019deeb6f..0ce02e2ca5 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -6,11 +6,16 @@ package collatorprotocol import ( "testing" + "github.com/ChainSafe/gossamer/dot/network" + "github.com/ChainSafe/gossamer/dot/parachain/backing" collatorprotocolmessages "github.com/ChainSafe/gossamer/dot/parachain/collator-protocol/messages" + "github.com/ChainSafe/gossamer/dot/parachain/overseer" parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types" "github.com/ChainSafe/gossamer/dot/peerset" + types "github.com/ChainSafe/gossamer/dot/types" "github.com/ChainSafe/gossamer/lib/common" "github.com/libp2p/go-libp2p/core/peer" + protocol "github.com/libp2p/go-libp2p/core/protocol" "github.com/stretchr/testify/require" gomock "go.uber.org/mock/gomock" ) @@ -283,3 +288,135 @@ func TestProcessOverseerMessage(t *testing.T) { }) } } + +func TestProcessBackedOverseerMessage(t *testing.T) { + t.Parallel() + + var testCollatorID parachaintypes.CollatorID + tempCollatID := common.MustHexToBytes("0x48215b9d322601e5b1a95164cea0dc4626f545f98343d07f1551eb9543c4b147") + copy(testCollatorID[:], tempCollatID) + peerID := peer.ID("testPeerID") + testRelayParent := getDummyHash(5) + + testCases := []struct { + description string + msg any + deletesBlockedAdvertisement bool + blockedAdvertisements map[string][]BlockedAdvertisement + canSecond bool + errString string + }{ + { + description: "Backed message fails with unknown relay parent", + msg: collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(6), + ParaHead: common.Hash{}, + }, + canSecond: true, + deletesBlockedAdvertisement: true, + blockedAdvertisements: map[string][]BlockedAdvertisement{ + "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + }, + errString: ErrRelayParentUnknown.Error(), + }, + { + description: "Backed message gets processed successfully when seconding is not allowed", + msg: collatorprotocolmessages.Backed{ + ParaID: parachaintypes.ParaID(6), + ParaHead: common.Hash{}, + }, + canSecond: false, + blockedAdvertisements: map[string][]BlockedAdvertisement{ + "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + { + peerID: peerID, + collatorID: testCollatorID, + candidateRelayParent: testRelayParent, + candidateHash: parachaintypes.CandidateHash{}, + }, + }, + }, + errString: "", + }, + } + for _, c := range testCases { + c := c + t.Run(c.description, func(t *testing.T) { + t.Parallel() + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockBlockState := NewMockBlockState(ctrl) + finalizedNotifierChan := make(chan *types.FinalisationInfo) + importedBlockNotiferChan := make(chan *types.Block) + + mockBlockState.EXPECT().GetFinalisedNotifierChannel().Return(finalizedNotifierChan) + mockBlockState.EXPECT().GetImportedBlockNotifierChannel().Return(importedBlockNotiferChan) + mockBlockState.EXPECT().FreeFinalisedNotifierChannel(finalizedNotifierChan) + mockBlockState.EXPECT().FreeImportedBlockNotifierChannel(importedBlockNotiferChan) + + overseer := overseer.NewOverseer(mockBlockState) + err := overseer.Start() + require.NoError(t, err) + + defer overseer.Stop() + + collationProtocolID := "/6761727661676500000000000000000000000000000000000000000000000000/1/collations/1" + + net := NewMockNetwork(ctrl) + net.EXPECT().RegisterNotificationsProtocol(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + net.EXPECT().GetRequestResponseProtocol(gomock.Any(), collationFetchingRequestTimeout, uint64(collationFetchingMaxResponseSize)).Return(&network.RequestResponseProtocol{}) + cpvs, err := Register(net, protocol.ID(collationProtocolID), overseer.SubsystemsToOverseer) + require.NoError(t, err) + + cpvs.BlockedAdvertisements = c.blockedAdvertisements + + mockBacking := NewMockSubsystem(ctrl) + mockBacking.EXPECT().Name().Return(parachaintypes.CandidateBacking) + overseerToBacking := overseer.RegisterSubsystem(mockBacking) + + go func() { + msg, _ := (<-overseerToBacking).(backing.CanSecondMessage) + msg.ResponseCh <- c.canSecond + }() + + lenBlackedAdvertisementsBefore := len(cpvs.BlockedAdvertisements) + + err = cpvs.processMessage(c.msg) + if c.errString == "" { + require.NoError(t, err) + } else { + require.ErrorContains(t, err, c.errString) + } + + if c.deletesBlockedAdvertisement { + require.Equal(t, lenBlackedAdvertisementsBefore-1, len(cpvs.BlockedAdvertisements)) + } else { + require.Equal(t, lenBlackedAdvertisementsBefore, len(cpvs.BlockedAdvertisements)) + } + }) + } +} From 0b7e8eba4828f04fa343bcc0673faa24e22b4f59 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 15:58:19 +0530 Subject: [PATCH 78/85] temp --- dot/parachain/collator-protocol/validator_side.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index c1940ba46b..f057bd1d4c 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -330,7 +330,7 @@ type CollatorProtocolValidatorSide struct { currentAssignments map[parachaintypes.ParaID]uint // state tracked per relay parent - perRelayParent map[common.Hash]PerRelayParent // map[replay parent]PerRelayParent + perRelayParent map[common.Hash]PerRelayParent // map[relay parent]PerRelayParent // Advertisements that were accepted as valid by collator protocol but rejected by backing. // From 6e39ad7023fde6f4a30c38c5a5b9449867e187b7 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 16:02:37 +0530 Subject: [PATCH 79/85] fix lint --- dot/parachain/collator-protocol/validator_side_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 0ce02e2ca5..a1e97bca87 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -387,8 +387,11 @@ func TestProcessBackedOverseerMessage(t *testing.T) { collationProtocolID := "/6761727661676500000000000000000000000000000000000000000000000000/1/collations/1" net := NewMockNetwork(ctrl) - net.EXPECT().RegisterNotificationsProtocol(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) - net.EXPECT().GetRequestResponseProtocol(gomock.Any(), collationFetchingRequestTimeout, uint64(collationFetchingMaxResponseSize)).Return(&network.RequestResponseProtocol{}) + net.EXPECT().RegisterNotificationsProtocol( + gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), + gomock.Any(), gomock.Any(), gomock.Any()).Return(nil) + net.EXPECT().GetRequestResponseProtocol(gomock.Any(), collationFetchingRequestTimeout, + uint64(collationFetchingMaxResponseSize)).Return(&network.RequestResponseProtocol{}) cpvs, err := Register(net, protocol.ID(collationProtocolID), overseer.SubsystemsToOverseer) require.NoError(t, err) From 12820333421e50ee7b12b8b5c36349974738dfd4 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 16:23:24 +0530 Subject: [PATCH 80/85] fix lint --- .../collator-protocol/messages/overseer_messages.go | 2 +- dot/parachain/collator-protocol/validator_side_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dot/parachain/collator-protocol/messages/overseer_messages.go b/dot/parachain/collator-protocol/messages/overseer_messages.go index 9994c5a789..33dedd00ad 100644 --- a/dot/parachain/collator-protocol/messages/overseer_messages.go +++ b/dot/parachain/collator-protocol/messages/overseer_messages.go @@ -38,7 +38,7 @@ type Backed struct { } func (b Backed) String() string { - return fmt.Sprintf("para id: %d, para head: %s", b.ParaID, b.ParaHead.String()) + return fmt.Sprintf("para_id: %d,_para_head: %s", b.ParaID, b.ParaHead.String()) } // Invalid represents an invalid candidata. diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index a1e97bca87..0055d6303d 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -315,7 +315,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { canSecond: true, deletesBlockedAdvertisement: true, blockedAdvertisements: map[string][]BlockedAdvertisement{ - "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + "para_id:_6,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000000": { { peerID: peerID, collatorID: testCollatorID, @@ -323,7 +323,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { candidateHash: parachaintypes.CandidateHash{}, }, }, - "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + "para_id:_7,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000001": { { peerID: peerID, collatorID: testCollatorID, @@ -342,7 +342,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { }, canSecond: false, blockedAdvertisements: map[string][]BlockedAdvertisement{ - "para id: 6, para head: 0x0000000000000000000000000000000000000000000000000000000000000000": { + "para_id:_6,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000000": { { peerID: peerID, collatorID: testCollatorID, @@ -350,7 +350,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { candidateHash: parachaintypes.CandidateHash{}, }, }, - "para id: 7, para head: 0x0000000000000000000000000000000000000000000000000000000000000001": { + "para_id:_7,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000001": { { peerID: peerID, collatorID: testCollatorID, From cef9c2bbfedec09243e5a032fdca81190986e57b Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Thu, 15 Feb 2024 17:20:09 +0530 Subject: [PATCH 81/85] test fix --- dot/parachain/collator-protocol/messages/overseer_messages.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot/parachain/collator-protocol/messages/overseer_messages.go b/dot/parachain/collator-protocol/messages/overseer_messages.go index 33dedd00ad..656c13eeb9 100644 --- a/dot/parachain/collator-protocol/messages/overseer_messages.go +++ b/dot/parachain/collator-protocol/messages/overseer_messages.go @@ -38,7 +38,7 @@ type Backed struct { } func (b Backed) String() string { - return fmt.Sprintf("para_id: %d,_para_head: %s", b.ParaID, b.ParaHead.String()) + return fmt.Sprintf("para_id:_%d,_para_head:_%s", b.ParaID, b.ParaHead.String()) } // Invalid represents an invalid candidata. From 5840653ac5c9931fc557964b365eae46c1c688f9 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 19 Feb 2024 13:30:03 +0530 Subject: [PATCH 82/85] temp --- dot/parachain/collator-protocol/validator_side.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index f057bd1d4c..80c8d90e13 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -376,21 +376,6 @@ func (f fetchedCollationInfo) String() string { f.relayParent.String(), f.paraID, f.candidateHash.Value.String(), f.collatorID) } -// Prospective parachains mode of a relay parent. Defined by -// the Runtime API version. -// -// Needed for the period of transition to asynchronous backing. -type ProspectiveParachainsMode struct { - // if disabled, there are no prospective parachains. Runtime API does not have support for `async_backing_params` - isEnabled bool - - // these values would be present only if `isEnabled` is true - - // The maximum number of para blocks between the para head in a relay parent and a new candidate. - // Restricts nodes from building arbitrary long chains and spamming other validators. - maxCandidateDepth uint -} - type PerRelayParent struct { prospectiveParachainMode parachaintypes.ProspectiveParachainsMode assignment *parachaintypes.ParaID From 21b0433f87a903caeb2349d8f8fff46881540bcb Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Mon, 19 Feb 2024 13:46:07 +0530 Subject: [PATCH 83/85] addressed reviews --- dot/parachain/collator-protocol/message.go | 8 ++++---- dot/parachain/collator-protocol/validator_side.go | 4 ++-- dot/parachain/collator-protocol/validator_side_test.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dot/parachain/collator-protocol/message.go b/dot/parachain/collator-protocol/message.go index 15e9bf088a..15fce9fbae 100644 --- a/dot/parachain/collator-protocol/message.go +++ b/dot/parachain/collator-protocol/message.go @@ -180,10 +180,10 @@ const ( Seconded ) -// BlockedAdvertisement is vstaging advertisement that was rejected by the backing +// blockedAdvertisement is vstaging advertisement that was rejected by the backing // subsystem. Validator may fetch it later if its fragment // membership gets recognised before relay parent goes out of view. -type BlockedAdvertisement struct { +type blockedAdvertisement struct { // peer that advertised the collation peerID peer.ID collatorID parachaintypes.CollatorID @@ -386,13 +386,13 @@ func (cpvs *CollatorProtocolValidatorSide) handleAdvertisement(relayParent commo ParaHead: prospectiveCandidate.ParentHeadDataHash, } - blockedAdvertisement := BlockedAdvertisement{ + blockedAd := blockedAdvertisement{ peerID: sender, collatorID: peerData.state.CollatingPeerState.CollatorID, candidateRelayParent: relayParent, candidateHash: prospectiveCandidate.CandidateHash, } - cpvs.BlockedAdvertisements[backed.String()] = []BlockedAdvertisement{blockedAdvertisement} + cpvs.BlockedAdvertisements[backed.String()] = []blockedAdvertisement{blockedAd} return nil } /*--------------------------------------------END----------------------------------------------------------*/ diff --git a/dot/parachain/collator-protocol/validator_side.go b/dot/parachain/collator-protocol/validator_side.go index 80c8d90e13..d37f6e0d86 100644 --- a/dot/parachain/collator-protocol/validator_side.go +++ b/dot/parachain/collator-protocol/validator_side.go @@ -338,7 +338,7 @@ type CollatorProtocolValidatorSide struct { // of some fragment tree or have a parent node which represents backed candidate. // Otherwise, a validator will keep such advertisement in the memory and re-trigger // requests to backing on new backed candidates and activations. - BlockedAdvertisements map[string][]BlockedAdvertisement + BlockedAdvertisements map[string][]blockedAdvertisement // Leaves that do support asynchronous backing along with // implicit ancestry. Leaves from the implicit view are present in @@ -562,7 +562,7 @@ func (cpvs CollatorProtocolValidatorSide) processMessage(msg any) error { // requestUnblockedCollations Checks whether any of the advertisements are unblocked and attempts to fetch them. func (cpvs CollatorProtocolValidatorSide) requestUnblockedCollations(backed collatorprotocolmessages.Backed) error { for _, blockedAdvertisements := range cpvs.BlockedAdvertisements { - newBlockedAdvertisements := []BlockedAdvertisement{} + newBlockedAdvertisements := []blockedAdvertisement{} for _, blockedAdvertisement := range blockedAdvertisements { isSecondingAllowed, err := cpvs.canSecond( diff --git a/dot/parachain/collator-protocol/validator_side_test.go b/dot/parachain/collator-protocol/validator_side_test.go index 0055d6303d..9177524aba 100644 --- a/dot/parachain/collator-protocol/validator_side_test.go +++ b/dot/parachain/collator-protocol/validator_side_test.go @@ -302,7 +302,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { description string msg any deletesBlockedAdvertisement bool - blockedAdvertisements map[string][]BlockedAdvertisement + blockedAdvertisements map[string][]blockedAdvertisement canSecond bool errString string }{ @@ -314,7 +314,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { }, canSecond: true, deletesBlockedAdvertisement: true, - blockedAdvertisements: map[string][]BlockedAdvertisement{ + blockedAdvertisements: map[string][]blockedAdvertisement{ "para_id:_6,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000000": { { peerID: peerID, @@ -341,7 +341,7 @@ func TestProcessBackedOverseerMessage(t *testing.T) { ParaHead: common.Hash{}, }, canSecond: false, - blockedAdvertisements: map[string][]BlockedAdvertisement{ + blockedAdvertisements: map[string][]blockedAdvertisement{ "para_id:_6,_para_head:_0x0000000000000000000000000000000000000000000000000000000000000000": { { peerID: peerID, From 0ef41611020d8690c5152a6640ba55af375b176e Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 21 Feb 2024 14:47:21 +0530 Subject: [PATCH 84/85] fix tests --- dot/mock_node_builder_test.go | 2 +- dot/node_integration_test.go | 18 +++++++++++++++++- dot/state/block.go | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/dot/mock_node_builder_test.go b/dot/mock_node_builder_test.go index a912587758..569273fc47 100644 --- a/dot/mock_node_builder_test.go +++ b/dot/mock_node_builder_test.go @@ -151,7 +151,7 @@ func (m *MocknodeBuilderIface) createParachainHostService(net *network.Service, } // createParachainHostService indicates an expected call of createParachainHostService. -func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, forkID, st interface{}) *gomock.Call { +func (mr *MocknodeBuilderIfaceMockRecorder) createParachainHostService(net, forkID, st any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "createParachainHostService", reflect.TypeOf((*MocknodeBuilderIface)(nil).createParachainHostService), net, forkID, st) } diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 477dc6749d..269f93dade 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -120,7 +120,23 @@ func TestNewNode(t *testing.T) { return stateSrvc, nil }) - mockBlockState := &state.BlockState{} + tries := state.NewTries() + tries.SetEmptyTrie() + + db := state.NewInMemoryDB(t) + prefix := []byte{98, 108, 111, 99, 107} + + err = db.Put(append(prefix, []byte{104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0}...), []byte{98, 108, 111, 99, 107, 104, 115, 104}) + require.NoError(t, err) + err = db.Put(append(prefix, []byte{104, 114, 115}...), []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) + require.NoError(t, err) + err = db.Put(append(prefix, []byte{102, 105, 110, 97, 108, 105, 115, 101, 100, 95, 104, 101, 97, 100, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}...), []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) + require.NoError(t, err) + err = db.Put(append(prefix, []byte{104, 100, 114, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}...), + []byte{59, 69, 201, 194, 45, 206, 206, 117, 163, 10, 204, 156, 41, 104, 203, 49, 30, 107, 5, 87, 53, 15, 131, 180, 48, 244, 117, 89, 219, 120, 105, 117, 0, 9, 249, 202, 40, 223, 5, 96, 194, 41, 26, 161, 107, 86, 225, 94, 7, 209, 225, 146, 112, 136, 245, 19, 86, 213, 34, 114, 42, 169, 12, 167, 203, 218, 38, 220, 140, 20, 85, 248, 248, 28, 174, 18, 228, 252, 89, 226, 60, 233, 97, 178, 200, 55, 246, 211, 246, 100, 40, 58, 249, 6, 211, 68, 224, 0}) + + mockBlockState, err := state.NewBlockState(db, tries, nil) + require.NoError(t, err) mockStateService := &state.Service{ Block: mockBlockState, } diff --git a/dot/state/block.go b/dot/state/block.go index 150dcd0179..a0dfeff09f 100644 --- a/dot/state/block.go +++ b/dot/state/block.go @@ -82,6 +82,7 @@ func NewBlockState(db database.Database, trs *Tries, telemetry Telemetry) (*Bloc tries: trs, imported: make(map[chan *types.Block]struct{}), finalised: make(map[chan *types.FinalisationInfo]struct{}), + importedLock: sync.RWMutex{}, runtimeUpdateSubscriptions: make(map[uint32]chan<- runtime.Version), telemetry: telemetry, } From dc00ad7247ba146b543161bebecc7ade34640368 Mon Sep 17 00:00:00 2001 From: Kishan Mohanbhai Sagathiya Date: Wed, 21 Feb 2024 15:49:02 +0530 Subject: [PATCH 85/85] temp --- dot/node_integration_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dot/node_integration_test.go b/dot/node_integration_test.go index 269f93dade..5537eb3559 100644 --- a/dot/node_integration_test.go +++ b/dot/node_integration_test.go @@ -126,13 +126,16 @@ func TestNewNode(t *testing.T) { db := state.NewInMemoryDB(t) prefix := []byte{98, 108, 111, 99, 107} - err = db.Put(append(prefix, []byte{104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0}...), []byte{98, 108, 111, 99, 107, 104, 115, 104}) + err = db.Put(append(prefix, []byte{104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0}...), + []byte{98, 108, 111, 99, 107, 104, 115, 104}) require.NoError(t, err) - err = db.Put(append(prefix, []byte{104, 114, 115}...), []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) + err = db.Put(append(prefix, []byte{104, 114, 115}...), + []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) require.NoError(t, err) - err = db.Put(append(prefix, []byte{102, 105, 110, 97, 108, 105, 115, 101, 100, 95, 104, 101, 97, 100, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}...), []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) + err = db.Put(append(prefix, []byte{102, 105, 110, 97, 108, 105, 115, 101, 100, 95, 104, 101, 97, 100, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}...), //nolint + []byte{98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104}) require.NoError(t, err) - err = db.Put(append(prefix, []byte{104, 100, 114, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}...), + err = db.Put(append(prefix, []byte{104, 100, 114, 98, 108, 111, 99, 107, 104, 115, 104, 98, 108, 111, 99, 107, 104, 115, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}...), //nolint []byte{59, 69, 201, 194, 45, 206, 206, 117, 163, 10, 204, 156, 41, 104, 203, 49, 30, 107, 5, 87, 53, 15, 131, 180, 48, 244, 117, 89, 219, 120, 105, 117, 0, 9, 249, 202, 40, 223, 5, 96, 194, 41, 26, 161, 107, 86, 225, 94, 7, 209, 225, 146, 112, 136, 245, 19, 86, 213, 34, 114, 42, 169, 12, 167, 203, 218, 38, 220, 140, 20, 85, 248, 248, 28, 174, 18, 228, 252, 89, 226, 60, 233, 97, 178, 200, 55, 246, 211, 246, 100, 40, 58, 249, 6, 211, 68, 224, 0}) mockBlockState, err := state.NewBlockState(db, tries, nil)