From 5a42b5c7aed915759b249387a91ffbdda1d74590 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 16:51:12 +0300 Subject: [PATCH 1/8] network: correct block addition check Fixes missing an error on block addition when the header actually went it, but the block didn't. --- pkg/network/blockqueue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/network/blockqueue.go b/pkg/network/blockqueue.go index ed6eac1ae..179c468a1 100644 --- a/pkg/network/blockqueue.go +++ b/pkg/network/blockqueue.go @@ -48,7 +48,7 @@ func (bq *blockQueue) run() { err := bq.chain.AddBlock(minblock) if err != nil { // The block might already be added by consensus. - if _, errget := bq.chain.GetBlock(minblock.Hash()); errget != nil { + if bq.chain.BlockHeight() < minblock.Index { bq.log.Warn("blockQueue: failed adding block into the blockchain", zap.String("error", err.Error()), zap.Uint32("blockHeight", bq.chain.BlockHeight()), From 98888def16116ef0a8ec14df73d2b8964234647d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 16:58:38 +0300 Subject: [PATCH 2/8] cli/server: drop key mangler from dumper It's no longer needed (yay!) --- cli/server/dump.go | 42 ++---------------------------------------- 1 file changed, 2 insertions(+), 40 deletions(-) diff --git a/cli/server/dump.go b/cli/server/dump.go index 303c7e903..ccf958087 100644 --- a/cli/server/dump.go +++ b/cli/server/dump.go @@ -25,42 +25,6 @@ type storageOp struct { Value string `json:"value,omitempty"` } -// NEO has some differences of key storing (that should go away post-preview2). -// ours format: contract's ID (uint32le) + key -// theirs format: contract's ID (uint32le) + key with 16 between every 16 bytes, padded to len 16. -func toNeoStorageKey(key []byte) []byte { - if len(key) < 4 { - panic("invalid key in storage") - } - - // Prefix is a contract's ID in LE. - nkey := make([]byte, 4, len(key)) - copy(nkey, key[:4]) - key = key[4:] - - index := 0 - remain := len(key) - for remain >= 16 { - nkey = append(nkey, key[index:index+16]...) - nkey = append(nkey, 16) - index += 16 - remain -= 16 - } - - if remain > 0 { - nkey = append(nkey, key[index:]...) - } - - padding := 16 - remain - for i := 0; i < padding; i++ { - nkey = append(nkey, 0) - } - - nkey = append(nkey, byte(remain)) - - return nkey -} - // batchToMap converts batch to a map so that JSON is compatible // with https://github.com/NeoResearch/neo-storage-audit/ func batchToMap(index uint32, batch *storage.MemBatch) blockDump { @@ -77,10 +41,9 @@ func batchToMap(index uint32, batch *storage.MemBatch) blockDump { op = "Changed" } - key = toNeoStorageKey(key[1:]) ops = append(ops, storageOp{ State: op, - Key: hex.EncodeToString(key), + Key: hex.EncodeToString(key[1:]), Value: hex.EncodeToString(batch.Put[i].Value), }) } @@ -91,10 +54,9 @@ func batchToMap(index uint32, batch *storage.MemBatch) blockDump { continue } - key = toNeoStorageKey(key[1:]) ops = append(ops, storageOp{ State: "Deleted", - Key: hex.EncodeToString(key), + Key: hex.EncodeToString(key[1:]), }) } From f287681fa739e3f3ec85978e77662192cdf8179b Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 17:07:14 +0300 Subject: [PATCH 3/8] core/native: fix VotersCount zero value encoding Empty byte array is enough to be bigint value of zero. Fixes state differences with C# node. --- pkg/core/native/native_neo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 52e02a663..43253bcaa 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -142,7 +142,7 @@ func (n *NEO) Initialize(ic *interop.Context) error { } n.mint(ic, h, big.NewInt(NEOTotalSupply)) - err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{0}}) + err = ic.DAO.PutStorageItem(n.ContractID, []byte{prefixVotersCount}, &state.StorageItem{Value: []byte{}}) if err != nil { return err } From 6e252fbaae173334eaa110e44c948350c292ab1d Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 17:50:19 +0300 Subject: [PATCH 4/8] rpc: answer with zero-length when there are no registered validators There is a huge difference between "result" : [], and "result" : null, --- pkg/rpc/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index e783a43f5..f89bf00ac 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -856,7 +856,7 @@ func (s *Server) getValidators(_ request.Params) (interface{}, *response.Error) if err != nil { return nil, response.NewRPCError("can't get enrollments", "", err) } - var res []result.Validator + var res = make([]result.Validator, 0) for _, v := range enrollments { res = append(res, result.Validator{ PublicKey: *v.Key, From fb97ea94584170eaa7736a5887a54a23997ddefa Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 17:51:46 +0300 Subject: [PATCH 5/8] native: don't register standby validators on initialization C# doesn't do that since neo-project/neo#1762. --- pkg/core/native/native_gas.go | 9 ++++----- pkg/core/native/native_neo.go | 8 +------- pkg/core/native_neo_test.go | 15 ++------------- pkg/rpc/server/server_test.go | 2 ++ 4 files changed, 9 insertions(+), 25 deletions(-) diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index add761dc4..75b158e8e 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -8,7 +8,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" ) @@ -73,7 +72,7 @@ func (g *GAS) Initialize(ic *interop.Context) error { if g.nep5TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { return errors.New("already initialized") } - h, _, err := getStandbyValidatorsHash(ic) + h, err := getStandbyValidatorsHash(ic) if err != nil { return err } @@ -103,13 +102,13 @@ func (g *GAS) OnPersist(ic *interop.Context) error { return nil } -func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, []*keys.PublicKey, error) { +func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { vs := ic.Chain.GetStandByValidators() s, err := smartcontract.CreateMultiSigRedeemScript(len(vs)/2+1, vs) if err != nil { - return util.Uint160{}, nil, err + return util.Uint160{}, err } - return hash.Hash160(s), vs, nil + return hash.Hash160(s), nil } func chainOnPersist(fs ...func(*interop.Context) error) func(*interop.Context) error { diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 43253bcaa..66430b673 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -136,7 +136,7 @@ func (n *NEO) Initialize(ic *interop.Context) error { return errors.New("already initialized") } - h, vs, err := getStandbyValidatorsHash(ic) + h, err := getStandbyValidatorsHash(ic) if err != nil { return err } @@ -147,12 +147,6 @@ func (n *NEO) Initialize(ic *interop.Context) error { return err } - for i := range vs { - if err := n.RegisterCandidateInternal(ic, vs[i]); err != nil { - return err - } - } - return nil } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 71df775e0..250c9195f 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -62,21 +62,10 @@ func TestNEO_Vote(t *testing.T) { require.NoError(t, neo.VoteInternal(ic, h, candidates[i])) } - // First 3 validators must be the ones we have voted for. + // We still haven't voted enough validators in. pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) - for i := 1; i < sz; i++ { - require.Equal(t, pubs[i-1], candidates[i]) - } - - var ok bool - for _, p := range bc.GetStandByValidators() { - if pubs[sz-1].Equal(p) { - ok = true - break - } - } - require.True(t, ok, "last validator must be stand by") + require.Equal(t, bc.GetStandByValidators(), pubs) // Register and give some value to the last validator. require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0])) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8c45b8ac4..18ea335fe 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -451,6 +451,7 @@ var rpcTestCases = map[string][]rpcTestCase{ result: func(*executor) interface{} { return &[]result.Validator{} }, + /* preview3 doesn't return any validators until there is a vote check: func(t *testing.T, e *executor, validators interface{}) { var expected []result.Validator sBValidators := e.chain.GetStandByValidators() @@ -467,6 +468,7 @@ var rpcTestCases = map[string][]rpcTestCase{ assert.ElementsMatch(t, expected, *actual) }, + */ }, }, "getversion": { From 80302c5c0708bce8b013def256f23f63be17db9c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 18:49:48 +0300 Subject: [PATCH 6/8] consensus: use dbft.M() for new block witness We're collecting dbft.M() number of signatures in getBlockWitness(), so we should use the same value for M here. --- pkg/consensus/consensus.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index d41373af4..7dca1ef13 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -518,7 +518,7 @@ func (s *service) newBlockFromContext(ctx *dbft.Context) block.Block { if err != nil { return nil } - script, err := smartcontract.CreateMultiSigRedeemScript(len(validators)-(len(validators)-1)/3, validators) + script, err := smartcontract.CreateMultiSigRedeemScript(s.dbft.Context.M(), validators) if err != nil { return nil } From dba248236c4614acb602e7c7146bc7c8fb198776 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 18:58:11 +0300 Subject: [PATCH 7/8] smartcontract: add CreateDefaultMultiSigRedeemScript And use it where appropriate. Some of our code was just plain wrong (like the one in GAS contract) and unification is always useful here. --- pkg/core/helper_test.go | 8 ++--- pkg/core/native/native_gas.go | 3 +- pkg/core/util.go | 6 +--- pkg/internal/testchain/address.go | 2 +- pkg/smartcontract/contract.go | 11 ++++++- pkg/smartcontract/contract_test.go | 47 ++++++++++++++++++++++++++++++ 6 files changed, 62 insertions(+), 15 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index e4ff60d17..9b6e2f9fa 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -51,11 +51,7 @@ func (bc *Blockchain) newBlock(txs ...*transaction.Transaction) *block.Block { func newBlock(cfg config.ProtocolConfiguration, index uint32, prev util.Uint256, txs ...*transaction.Transaction) *block.Block { validators, _ := validatorsFromConfig(cfg) - vlen := len(validators) - valScript, _ := smartcontract.CreateMultiSigRedeemScript( - vlen-(vlen-1)/3, - validators, - ) + valScript, _ := smartcontract.CreateDefaultMultiSigRedeemScript(validators) witness := transaction.Witness{ VerificationScript: valScript, } @@ -393,7 +389,7 @@ func addSigners(txs ...*transaction.Transaction) { func signTx(bc *Blockchain, txs ...*transaction.Transaction) error { validators := bc.GetStandByValidators() - rawScript, err := smartcontract.CreateMultiSigRedeemScript(bc.config.ValidatorsCount/2+1, validators) + rawScript, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators) if err != nil { return fmt.Errorf("failed to sign tx: %w", err) } diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 75b158e8e..524d6879d 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -103,8 +103,7 @@ func (g *GAS) OnPersist(ic *interop.Context) error { } func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { - vs := ic.Chain.GetStandByValidators() - s, err := smartcontract.CreateMultiSigRedeemScript(len(vs)/2+1, vs) + s, err := smartcontract.CreateDefaultMultiSigRedeemScript(ic.Chain.GetStandByValidators()) if err != nil { return util.Uint160{}, err } diff --git a/pkg/core/util.go b/pkg/core/util.go index 7f8d4eb20..999a5eef5 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -118,11 +118,7 @@ func committeeFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, e } func getNextConsensusAddress(validators []*keys.PublicKey) (val util.Uint160, err error) { - vlen := len(validators) - raw, err := smartcontract.CreateMultiSigRedeemScript( - vlen-(vlen-1)/3, - validators, - ) + raw, err := smartcontract.CreateDefaultMultiSigRedeemScript(validators) if err != nil { return val, err } diff --git a/pkg/internal/testchain/address.go b/pkg/internal/testchain/address.go index 2c31486fd..b242b5747 100644 --- a/pkg/internal/testchain/address.go +++ b/pkg/internal/testchain/address.go @@ -64,7 +64,7 @@ func MultisigVerificationScript() []byte { pubs = append(pubs, priv.PublicKey()) } - script, err := smartcontract.CreateMultiSigRedeemScript(3, pubs) + script, err := smartcontract.CreateDefaultMultiSigRedeemScript(pubs) if err != nil { panic(err) } diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index 0b12dc3ba..811ac6ae2 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -10,7 +10,8 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// CreateMultiSigRedeemScript creates a script runnable by the VM. +// CreateMultiSigRedeemScript creates an "m out of n" type verification script +// where n is the length of publicKeys. func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, error) { if m < 1 { return nil, fmt.Errorf("param m cannot be smaller or equal to 1 got %d", m) @@ -34,3 +35,11 @@ func CreateMultiSigRedeemScript(m int, publicKeys keys.PublicKeys) ([]byte, erro return buf.Bytes(), nil } + +// CreateDefaultMultiSigRedeemScript creates an "m out of n" type verification script +// using publicKeys length with the default BFT assumptions of (n - (n-1)/3) for m. +func CreateDefaultMultiSigRedeemScript(publicKeys keys.PublicKeys) ([]byte, error) { + n := len(publicKeys) + m := n - (n-1)/3 + return CreateMultiSigRedeemScript(m, publicKeys) +} diff --git a/pkg/smartcontract/contract_test.go b/pkg/smartcontract/contract_test.go index 889def6be..1c8e74f9c 100644 --- a/pkg/smartcontract/contract_test.go +++ b/pkg/smartcontract/contract_test.go @@ -36,3 +36,50 @@ func TestCreateMultiSigRedeemScript(t *testing.T) { assert.Equal(t, opcode.SYSCALL, opcode.Opcode(br.ReadB())) assert.Equal(t, emit.InteropNameToID([]byte("Neo.Crypto.CheckMultisigWithECDsaSecp256r1")), br.ReadU32LE()) } + +func TestCreateDefaultMultiSigRedeemScript(t *testing.T) { + var validators = make([]*keys.PublicKey, 0) + + var addKey = func() { + key, err := keys.NewPrivateKey() + require.NoError(t, err) + validators = append(validators, key.PublicKey()) + } + var checkM = func(m int) { + validScript, err := CreateMultiSigRedeemScript(m, validators) + require.NoError(t, err) + defaultScript, err := CreateDefaultMultiSigRedeemScript(validators) + require.NoError(t, err) + require.Equal(t, validScript, defaultScript) + } + + // 1 out of 1 + addKey() + checkM(1) + + // 2 out of 2 + addKey() + checkM(2) + + // 3 out of 4 + for i := 0; i < 2; i++ { + addKey() + } + checkM(3) + + // 5 out of 6 + for i := 0; i < 2; i++ { + addKey() + } + checkM(5) + + // 5 out of 7 + addKey() + checkM(5) + + // 7 out of 10 + for i := 0; i < 3; i++ { + addKey() + } + checkM(7) +} From c16040aecc8973662e5b7c0bd78d25286036931e Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 10 Aug 2020 19:16:54 +0300 Subject: [PATCH 8/8] native: sort GetValidators result As it's returned sorted now. Fixes state change mismatch for NextValidators. It also partially reverts 2f8e7e4d33d0c38cbd1310136f7a663942c3f640 and significantly changes the test chain as the fees are no longer being sent to the same account. --- pkg/consensus/consensus_test.go | 2 +- pkg/core/native/native_neo.go | 1 + pkg/core/native_neo_test.go | 11 ++++++++--- pkg/rpc/server/server_test.go | 16 +++------------- pkg/rpc/server/testdata/testblocks.acc | Bin 7236 -> 7230 bytes 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index 4dd826e94..acf219fe1 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -226,7 +226,7 @@ func newTestService(t *testing.T) *service { } func getTestValidator(i int) (*privateKey, *publicKey) { - key := testchain.PrivateKeyByID(i) + key := testchain.PrivateKey(i) return &privateKey{PrivateKey: key}, &publicKey{PublicKey: key.PublicKey()} } diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 66430b673..12b0dc1c6 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -416,6 +416,7 @@ func (n *NEO) GetValidatorsInternal(bc blockchainer.Blockchainer, d dao.DAO) (ke count = len(result) } result = result[:count] + sort.Sort(result) n.validators.Store(result) return result, nil } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 250c9195f..3fe7591b9 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -2,6 +2,7 @@ package core import ( "math/big" + "sort" "testing" "github.com/nspcc-dev/neo-go/pkg/config/netmode" @@ -31,9 +32,11 @@ func TestNEO_Vote(t *testing.T) { ic := bc.newInteropContext(trigger.System, bc.dao, nil, tx) ic.VM = vm.New() + standBySorted := bc.GetStandByValidators() + sort.Sort(standBySorted) pubs, err := neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) - require.Equal(t, bc.GetStandByValidators(), pubs) + require.Equal(t, standBySorted, pubs) sz := testchain.Size() @@ -65,7 +68,7 @@ func TestNEO_Vote(t *testing.T) { // We still haven't voted enough validators in. pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) - require.Equal(t, bc.GetStandByValidators(), pubs) + require.Equal(t, standBySorted, pubs) // Register and give some value to the last validator. require.NoError(t, neo.RegisterCandidateInternal(ic, candidates[0])) @@ -77,7 +80,9 @@ func TestNEO_Vote(t *testing.T) { pubs, err = neo.GetValidatorsInternal(bc, ic.DAO) require.NoError(t, err) - require.Equal(t, candidates, pubs) + sortedCandidates := candidates.Copy() + sort.Sort(sortedCandidates) + require.EqualValues(t, sortedCandidates, pubs) require.NoError(t, neo.UnregisterCandidateInternal(ic, candidates[0])) require.Error(t, neo.VoteInternal(ic, h, candidates[0])) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 18ea335fe..8c6c3cba6 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -52,8 +52,8 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672" -const deploymentTxHash = "60a1fc8ceb7948d9933aec0cedd148441568575c40af7e0985cc366ed153d57e" +const testContractHash = "5b5f77b947194ba45ff5fa1ba6e066af5636d110" +const deploymentTxHash = "af0f94f6bdc5aada7abf1db19f1fcd2ea56cea596c41dc4abdfa6cd9664a7d72" var rpcTestCases = map[string][]rpcTestCase{ "getapplicationlog": { @@ -1027,7 +1027,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "915.78962700", + Amount: "915.61054740", LastUpdated: 6, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), @@ -1145,16 +1145,6 @@ func checkNep5TransfersAux(t *testing.T, e *executor, acc interface{}, start, en } netFee += b.Transactions[j].NetworkFee } - if i > 0 { - expected.Received = append(expected.Received, result.NEP5Transfer{ - Timestamp: b.Timestamp, - Asset: e.chain.UtilityTokenHash(), - Address: "", // minted from network fees. - Amount: amountToString(big.NewInt(netFee), 8), - Index: b.Index, - TxHash: b.Hash(), - }) - } } require.Equal(t, expected.Address, res.Address) diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index e34c40cd33d2b134f7426c85964d380bcec29d4a..430f85dcd94de8c18a4ecd3ec43ef900b4ba7326 100644 GIT binary patch delta 2799 zcmaKtc{J4P8^9T|&3BA-j3t?ILw1?5OqQ=L%GeoOS?116NMjp~lKmEu#FwN}*)QWN zYmr-*!L@X4=TXzL}LlY$`1ME}#)LPbX!mMU2V!0VC1j(#q|(T7=1&84ZKTOUwggyRDpz6>`FYVh}`e)FKZ>*Thd4>s6%!BJ-;BpZdM{FyLGIza+JN{-x0P_`@M z7R#F+W&q-w?#s5EXZP`BvIjf5qwC8-1o)OaqW zh}r$w1wE3ZGyqN=x(PQ_& zq0B`?^U;HjIRV1dcJ~G`w~3ndm0U!a2mr2#c~C{Om`^KR;2jeU0;=Z=tUZkbd!{-v z&Te(uG`H}EZ+#|R@*a=;@@3IY$uwE6;^9Gs_yj<}WP=qelnx!*8}S1q@BVD3;I6Q} zbkR>0@|Bf0=i=ING{*}db)ZP9UX1xFM(5#vXBT2uLEHO4?+1t*dKXZNE|AMi^KGA&@=cA z$Q7=@gVV?VDA~frp>INxFr49D2@PT3#!!%u7);S4Gzg5gi6_=i&>umo?Y70}?_P<>XBc8E*i`Wq=xK_&`CzZC_igYB0w}!C! z;H{PhZ_#w@PDy3{+b#8{ijWiXD6ynZcQE;$E-GtB7hNiQ;&GkJvd8FPP0{A%P_MO< zXeSewa`DBH&d+}Oi@uhYq~wFix6NJRX*~7qSsL}i)sb_1YZ<}K#l^D+0Mda;#_b4j z17IXSyH}HcOpu&1t5zIM=ZbOr>p*5tKwq(NjJW0VU+dZRd?O1*CQqz%4Pxl`d3@K+ z1WNi!uaMc|XaTNn)>9*l>@~MUMqKwIeSSC( z%uR2{SXS=uD3l@Ud|ISmkMxi>Q7XH+;#+JhZQaQ9^)@5Ncsp~O?_IQAGo!dr*emgH z-OWd>`Z^8rO^$lDjvf^T>1#NuLioGTqgoz=JZK=B5Dhm%FKCm*{ptB!REUckGCRJ#3=#9F3d!p3aga0^+C z>p~r18!JjyD^1Md2F8b;dq1xvB#wzOt+615l>s0Er@BT_o)`m(b8n2BX&+MU^7JP7o zwm;-#%4t_gGeto%j9PW4x*Pu$hw5^FKE|lF;fYN$cHUZ58z;24yhF@?Xz}x7Dap;t ze|+!rgW4sD9ZRv{OtTvj#@?_4!ll-N#S?_uWA`!&zrQ)rdf?~iJ63SSj+lwy{2g5J z%rZ~IR_yyQv}Hs0nNG~LTSi^0z>z7=k2hNq8&`B1UK$cD!MTOB?{$u~a=V-@9>sKk zj-uz!<*<+Vc{Q));(P1i+F6er?#`y=DIN+Aw2M}=xsni4|3a^OHx&RYz6a5rn#nxO zB@bx~oA2$X<&)hT*==(?b4IpR{orxOjopt$cfWXts@c-YJQ7!Hbj7B@TXZzC5Q^ytKAHIeff&Qa6R<=NeT z?FKmQt{=8JfqRO%*yG1SuIm7x6`h?9dhK+jE6e6f9hCkFu4eVnqh0Cyli4Mek^x#b z+URtHQ6@R!Yw)DBz_ro}(BQO@nn?$H<2>uQzp>_ay1leMT_ND%qE#4pF3jcBy5K{cvitrt+qpO(#);#^vOYW{GwZ zF-#10=Ukvmy!+(MhM)WTx3YZyr)<{6CrqyS>YuI~G|Ri(TD|o8gqHD>%8yqJQ%)Cj zJ3!`pityRYuvN zy~v{u{%vpx@hs>_CIAkDU2}8SjK+i*{A-7L^xq@w5fObx)5x_4>aT)+1J0(0UWgfS zn}wa1!(@q#HIn?@ml2*+s+>jb)J!p$Cnj0@NCx2c`FE1%lkAdFSo9zL{2>(6%}b24 zsI)HAH~IB%)8-QrwFm4a7ZSA=%H1(`k;pgNQ=wajmdwdxM-HxWMVvCs$VrjeU#b59 D6IkgN delta 2831 zcmaKuXHb*d7KTYECgg)qq_+^70%}4pf?x20fPfk)f*=qKy%&*YkfMUrd^G7@kt&D< zkRl~W6_F+&h#m!$jz|%?;63Ng`Ell~nLT^2J$u%V_kEw4-Kf&0l3>8V_lPwo@jRBo zLg(V%+LV0kqDOin2XCWhM~Qyu;X4*~Fpk%-yM6iXJJ(KE2|7+UtoK(RB_21N3%M4l z=P5Hqn|$fT`jjO-3;^9)2;yozoO^}w ztcHk3_63M@7hlX@h|q{D_rVSF@Jzp_l=4#m@Ke{Acynsz=NtI(xq!ZoGv^99Ei+)R zpcJdTcw=hxvQ)TN$7Poqk!cz+(V61D7BTtf!T@68FdQJcKc0f6TUABGY z%F!leXo@?xh8J$mV8Dc=`(4@0+nt5}W>Inh6eCvMTB%yqbp6fYtBhy8KykUa3uCpa zi-jf<-L3C#I^FNF{eEoeG3T?(p`B-**?%Re-%)en-&YuV!Gxuhz}})e?2UF#mrOPd zH9takSXqWYK3kt;7R>%JxZ@_0aA;>b@mnUg^#nQC5;e}@HLUqON9(zrh4(D-&LmI< zfDFf&Utw`gN~wYirc*j>^G7rMcq=>vX_*s0YsN3s^GdgjmUzc#-P8a@ z<1^Tl!-iuS7tLNT>1_}dkc6$g#p}s~8^iOBvFAB<5uekuN&#*MuYHM=p;H1VG{b?u zpHRl|{cYY~8amG_tVKt+Ys$o;zz~GBB{^Z$g0POZK8{#5Ebh9zlASyWXQ!fgRaHd^ zr+k%!!;zHncoh{zvYk9x0gu0`O2R8C%R%c1OKwj;UsGFGKQdMp>tS#2L-xh0DdOd! z6jUr+RTUCNia-s_ob2wlZhwEFpaNl#7|0#P0jZ-nV5)z0>`v}ZzJHq}FZX-GPZTHA z&&&l^R)Ib1 zjVN2k{*iwF9*(MEW`V)Mmyv>AawfkLQa7R_!KA2^_x@cXlYD*#cF&u12Qg+`Gz3i#uZeoNCU?jIB5+9?%pIt@U(p4chOW|t-QEwbgE4#L zZsOIF;2rrJbqnnU`bCXubVjqw)aNc)%{N(^y|+dM+cE`^-&2LtLv=4Pu;Jhd*98vZ z+k(bYhPO1&I6pX5TXl(A&NsK92Bf!z~(|{gM=QkX`|c37oRZUpPeAf zMct<&m(r7Ro9`)YkLAsV{X#fC4A; z5n`K_E)sLX9e>3aiee{Pd2?q75j4L=@_M1FLTu8kI;&ch2&9n9M3@!;!m8cV+%P{a z?`6j`M4B4++wLG5X=72X-<-t5wDvx7f3J{tp0~(d zZC{E_IPmcZBmMt;bTkjJ*{M#<9cggfVexjNjvwb#i9quem0n#5w=lPChoU(!41xz{ zRd8S_4(T4IwA0AdD7_6^79KYtq-5#{Jd6PEd%pI z)hDyIijxqMN}R7YrUb-E0BB|cue7E%D`)@lEdTZmNeL?#|0uE3=i;!fQGeW$N9ySZ zk7Rw~-!@9qG2+^KamTRL(`@TW(fQ+p8bPQhl>6*70Q4}=d_NdOLnVC7{ZGYw-3(Qn z+5?1mhnUje5|f$9z?@$EX&tUnxT79!=3`!;=KeJW%&4R6NUpDXEQ|(@g##e(!&!vE z>|%Avvx4f?#Ojd`qBh1iiEjSwQ&t44w~pVM^cNOzkN8&eX6xMzEF=0nx|yMZs%R^O z>6D*~bZ+nqstLC?9G({SqzAw+HMtLH(~{xOVwkoQmUn0K zmD^sGk}D8BDO)l&UaF(hCL(KEZ6C6dQ^Q^QjvQh;VduVEr0x0$A^S^W?n3T%$iC9= z{v8MH^OU_{R>Pq_>$bDj?W?qK{ssQPkhDgJc>M&Ovd;Y6eipBO58^Fx#@igG_nbFs zbvE>2K>_}2Rae)U-0WBnaDYHw1Ij-^$Jq=Ze7$YmeeB8Jz~O&-@f%B(11>elgj5{& z@{mc2AoXiUEpoodSWMe2JXwaGe(I>YH9g)c;A7;%O{4Do%~>=i!XyixB3F$_pKaYV z$dWUggFr4BV}?Jmyo9;rKVYf}5*^7o$pY42>N^f+`ArEdHEoyJy|~85lILSoqIOG5tML>W@{Aqh@854O`ia)clH_FGpD0 z4{7kdL|72X;}?PN_KJo-(Zt7KHdc*W`0OVJ_Xo8F9cCzv9dlgpA}gFSo7=-tmRWJ6m6`j3EtyQ(>KU@S^PMImOTU4 zV_M%tl`s8jXUGiZ8n_!#(Bl)MaAN;Qm{A(G5Zr1w*L(NZ-J}(wZIb^>) zslPk#Xsrwzw+;XDzaeNeFle@ zEh_?71N%lJ%FcOe*Dgjn2}cw%^+$<@Y6LDMtG9-}+4|5}qx~>%2-ljW{Q-Mn*|wI` M_0~I#-$Ahc3%#rIR{#J2