From 542df8898ef6d718647058c129069804bc463ea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 6 Aug 2019 13:40:28 +0300 Subject: [PATCH 001/103] core: initial version of state snapshots --- accounts/abi/bind/backends/simulated.go | 6 +- cmd/evm/runner.go | 4 +- cmd/geth/chaincmd.go | 2 +- core/blockchain.go | 56 ++-- core/chain_makers.go | 2 +- core/genesis.go | 4 +- core/rawdb/accessors_snapshot.go | 99 +++++++ core/rawdb/database.go | 8 + core/rawdb/schema.go | 23 +- core/state/snapshot/account.go | 54 ++++ core/state/snapshot/difflayer.go | 337 +++++++++++++++++++++++ core/state/snapshot/difflayer_journal.go | 140 ++++++++++ core/state/snapshot/disklayer.go | 115 ++++++++ core/state/snapshot/generate.go | 212 ++++++++++++++ core/state/snapshot/generate_test.go | 111 ++++++++ core/state/snapshot/snapshot.go | 244 ++++++++++++++++ core/state/snapshot/snapshot_test.go | 17 ++ core/state/snapshot/sort.go | 62 +++++ core/state/state_object.go | 59 +++- core/state/statedb.go | 129 +++++++-- core/state_prefetcher.go | 2 + core/vm/runtime/runtime.go | 4 +- core/vm/runtime/runtime_test.go | 4 +- eth/api_test.go | 6 +- eth/api_tracer.go | 6 +- eth/handler_test.go | 2 +- light/odr_test.go | 4 +- light/trie.go | 2 +- tests/state_test_util.go | 4 +- trie/iterator.go | 2 + 30 files changed, 1635 insertions(+), 85 deletions(-) create mode 100644 core/rawdb/accessors_snapshot.go create mode 100644 core/state/snapshot/account.go create mode 100644 core/state/snapshot/difflayer.go create mode 100644 core/state/snapshot/difflayer_journal.go create mode 100644 core/state/snapshot/disklayer.go create mode 100644 core/state/snapshot/generate.go create mode 100644 core/state/snapshot/generate_test.go create mode 100644 core/state/snapshot/snapshot.go create mode 100644 core/state/snapshot/snapshot_test.go create mode 100644 core/state/snapshot/sort.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index f7f3dec839..2dbc593569 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -124,7 +124,7 @@ func (b *SimulatedBackend) rollback() { statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) } // stateByBlockNumber retrieves a state by a given blocknumber. @@ -480,7 +480,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) return nil } @@ -593,7 +593,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] - b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database()) + b.pendingState, _ = state.New(b.pendingBlock.Root(), statedb.Database(), nil) return nil } diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index da301ff5ee..0a9c19f5bc 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -129,10 +129,10 @@ func runCmd(ctx *cli.Context) error { genesisConfig = gen db := rawdb.NewMemoryDatabase() genesis := gen.ToBlock(db) - statedb, _ = state.New(genesis.Root(), state.NewDatabase(db)) + statedb, _ = state.New(genesis.Root(), state.NewDatabase(db), nil) chainConfig = gen.Config } else { - statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) genesisConfig = new(core.Genesis) } if ctx.GlobalString(SenderFlag.Name) != "" { diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 5b176b6da1..9d4835a16c 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -544,7 +544,7 @@ func dump(ctx *cli.Context) error { fmt.Println("{}") utils.Fatalf("block not found") } else { - state, err := state.New(block.Root(), state.NewDatabase(chainDb)) + state, err := state.New(block.Root(), state.NewDatabase(chainDb), nil) if err != nil { utils.Fatalf("could not create new state: %v", err) } diff --git a/core/blockchain.go b/core/blockchain.go index d7fcbd5e31..676a72c779 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -34,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/ethdb" @@ -61,6 +62,10 @@ var ( storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) + snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/accountreads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storagereads", nil) + snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) + blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) blockValidationTimer = metrics.NewRegisteredTimer("chain/validation", nil) blockExecutionTimer = metrics.NewRegisteredTimer("chain/execution", nil) @@ -135,9 +140,10 @@ type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning - db ethdb.Database // Low level persistent database to store final content in - triegc *prque.Prque // Priority queue mapping block numbers to tries to gc - gcproc time.Duration // Accumulates canonical block processing for trie dumping + db ethdb.Database // Low level persistent database to store final content in + snaps *snapshot.SnapshotTree // Snapshot tree for fast trie leaf access + triegc *prque.Prque // Priority queue mapping block numbers to tries to gc + gcproc time.Duration // Accumulates canonical block processing for trie dumping hc *HeaderChain rmLogsFeed event.Feed @@ -293,6 +299,11 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } } + // Load any existing snapshot, regenerating it if loading failed + head := bc.CurrentBlock() + if bc.snaps, err = snapshot.New(bc.db, "snapshot.rlp", head.NumberU64(), head.Root()); err != nil { + return nil, err + } // Take ownership of this particular state go bc.update() return bc, nil @@ -339,7 +350,7 @@ func (bc *BlockChain) loadLastState() error { return bc.Reset() } // Make sure the state associated with the block is available - if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil { + if _, err := state.New(currentBlock.Root(), bc.stateCache, bc.snaps); err != nil { // Dangling block without a state associated, init from scratch log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash()) if err := bc.repair(¤tBlock); err != nil { @@ -401,7 +412,7 @@ func (bc *BlockChain) SetHead(head uint64) error { if newHeadBlock == nil { newHeadBlock = bc.genesisBlock } else { - if _, err := state.New(newHeadBlock.Root(), bc.stateCache); err != nil { + if _, err := state.New(newHeadBlock.Root(), bc.stateCache, bc.snaps); err != nil { // Rewound state missing, rolled back to before pivot, reset to genesis newHeadBlock = bc.genesisBlock } @@ -524,7 +535,7 @@ func (bc *BlockChain) State() (*state.StateDB, error) { // StateAt returns a new mutable state based on a particular point in time. func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) { - return state.New(root, bc.stateCache) + return state.New(root, bc.stateCache, bc.snaps) } // StateCache returns the caching database underpinning the blockchain instance. @@ -576,7 +587,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { func (bc *BlockChain) repair(head **types.Block) error { for { // Abort if we've rewound to a head block that does have associated state - if _, err := state.New((*head).Root(), bc.stateCache); err == nil { + if _, err := state.New((*head).Root(), bc.stateCache, bc.snaps); err == nil { log.Info("Rewound blockchain to past state", "number", (*head).Number(), "hash", (*head).Hash()) return nil } @@ -839,6 +850,10 @@ func (bc *BlockChain) Stop() { bc.wg.Wait() + // Ensure that the entirety of the state snapshot is journalled to disk. + if err := bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { + log.Error("Failed to journal state snapshot", "err", err) + } // Ensure the state of a recent block is also stored to disk before exiting. // We're writing three different states to catch different restart scenarios: // - HEAD: So we don't need to reprocess any blocks in the general case @@ -1647,7 +1662,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } - statedb, err := state.New(parent.Root, bc.stateCache) + statedb, err := state.New(parent.Root, bc.stateCache, bc.snaps) if err != nil { return it.index, err } @@ -1656,9 +1671,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er var followupInterrupt uint32 if !bc.cacheConfig.TrieCleanNoPrefetch { if followup, err := it.peek(); followup != nil && err == nil { - throwaway, _ := state.New(parent.Root, bc.stateCache) + throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) go func(start time.Time, followup *types.Block, throwaway *state.StateDB, interrupt *uint32) { - bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, interrupt) + bc.prefetcher.Prefetch(followup, throwaway, bc.vmConfig, &followupInterrupt) blockPrefetchExecuteTimer.Update(time.Since(start)) if atomic.LoadUint32(interrupt) == 1 { @@ -1676,14 +1691,16 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er return it.index, err } // Update the metrics touched during block processing - accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them - storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them - accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them - storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them + accountReadTimer.Update(statedb.AccountReads) // Account reads are complete, we can mark them + storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete, we can mark them + accountUpdateTimer.Update(statedb.AccountUpdates) // Account updates are complete, we can mark them + storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete, we can mark them + snapshotAccountReadTimer.Update(statedb.SnapshotAccountReads) // Account reads are complete, we can mark them + snapshotStorageReadTimer.Update(statedb.SnapshotStorageReads) // Storage reads are complete, we can mark them triehash := statedb.AccountHashes + statedb.StorageHashes // Save to not double count in validation - trieproc := statedb.AccountReads + statedb.AccountUpdates - trieproc += statedb.StorageReads + statedb.StorageUpdates + trieproc := statedb.SnapshotAccountReads + statedb.AccountReads + statedb.AccountUpdates + trieproc += statedb.SnapshotStorageReads + statedb.StorageReads + statedb.StorageUpdates blockExecutionTimer.Update(time.Since(substart) - trieproc - triehash) @@ -1712,10 +1729,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er atomic.StoreUint32(&followupInterrupt, 1) // Update the metrics touched during block commit - accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them - storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(statedb.StorageCommits) // Storage commits are complete, we can mark them + snapshotCommitTimer.Update(statedb.SnapshotCommits) // Snapshot commits are complete, we can mark them - blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits) + blockWriteTimer.Update(time.Since(substart) - statedb.AccountCommits - statedb.StorageCommits - statedb.SnapshotCommits) blockInsertTimer.UpdateSince(start) switch status { diff --git a/core/chain_makers.go b/core/chain_makers.go index fc4f7d182d..6524087d4e 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -228,7 +228,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse return nil, nil } for i := 0; i < n; i++ { - statedb, err := state.New(parent.Root(), state.NewDatabase(db)) + statedb, err := state.New(parent.Root(), state.NewDatabase(db), nil) if err != nil { panic(err) } diff --git a/core/genesis.go b/core/genesis.go index 92e654da83..06d347f736 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -178,7 +178,7 @@ func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, override // We have the genesis block in database(perhaps in ancient database) // but the corresponding state is missing. header := rawdb.ReadHeader(db, stored, 0) - if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0)); err != nil { + if _, err := state.New(header.Root, state.NewDatabaseWithCache(db, 0), nil); err != nil { if genesis == nil { genesis = DefaultGenesisBlock() } @@ -259,7 +259,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { if db == nil { db = rawdb.NewMemoryDatabase() } - statedb, _ := state.New(common.Hash{}, state.NewDatabase(db)) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(db), nil) for addr, account := range g.Alloc { statedb.AddBalance(addr, account.Balance) statedb.SetCode(addr, account.Code) diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go new file mode 100644 index 0000000000..9989e6b50e --- /dev/null +++ b/core/rawdb/accessors_snapshot.go @@ -0,0 +1,99 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "encoding/binary" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// ReadSnapshotBlock retrieves the number and root of the block whose state is +// contained in the persisted snapshot. +func ReadSnapshotBlock(db ethdb.KeyValueReader) (uint64, common.Hash) { + data, _ := db.Get(snapshotBlockKey) + if len(data) != 8+common.HashLength { + return 0, common.Hash{} + } + return binary.BigEndian.Uint64(data[:8]), common.BytesToHash(data[8:]) +} + +// WriteSnapshotBlock stores the number and root of the block whose state is +// contained in the persisted snapshot. +func WriteSnapshotBlock(db ethdb.KeyValueWriter, number uint64, root common.Hash) { + if err := db.Put(snapshotBlockKey, append(encodeBlockNumber(number), root.Bytes()...)); err != nil { + log.Crit("Failed to store snapsnot block's number and root", "err", err) + } +} + +// DeleteSnapshotBlock deletes the number and hash of the block whose state is +// contained in the persisted snapshot. Since snapshots are not immutable, this +// method can be used during updates, so a crash or failure will mark the entire +// snapshot invalid. +func DeleteSnapshotBlock(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotBlockKey); err != nil { + log.Crit("Failed to remove snapsnot block's number and hash", "err", err) + } +} + +// ReadAccountSnapshot retrieves the snapshot entry of an account trie leaf. +func ReadAccountSnapshot(db ethdb.KeyValueReader, hash common.Hash) []byte { + data, _ := db.Get(accountSnapshotKey(hash)) + return data +} + +// WriteAccountSnapshot stores the snapshot entry of an account trie leaf. +func WriteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash, entry []byte) { + if err := db.Put(accountSnapshotKey(hash), entry); err != nil { + log.Crit("Failed to store account snapshot", "err", err) + } +} + +// DeleteAccountSnapshot removes the snapshot entry of an account trie leaf. +func DeleteAccountSnapshot(db ethdb.KeyValueWriter, hash common.Hash) { + if err := db.Delete(accountSnapshotKey(hash)); err != nil { + log.Crit("Failed to delete account snapshot", "err", err) + } +} + +// ReadStorageSnapshot retrieves the snapshot entry of an storage trie leaf. +func ReadStorageSnapshot(db ethdb.KeyValueReader, accountHash, storageHash common.Hash) []byte { + data, _ := db.Get(storageSnapshotKey(accountHash, storageHash)) + return data +} + +// WriteStorageSnapshot stores the snapshot entry of an storage trie leaf. +func WriteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash, entry []byte) { + if err := db.Put(storageSnapshotKey(accountHash, storageHash), entry); err != nil { + log.Crit("Failed to store storage snapshot", "err", err) + } +} + +// DeleteStorageSnapshot removes the snapshot entry of an storage trie leaf. +func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash common.Hash) { + if err := db.Delete(storageSnapshotKey(accountHash, storageHash)); err != nil { + log.Crit("Failed to delete storage snapshot", "err", err) + } +} + +// IterateStorageSnapshots returns an iterator for walking the entire storage +// space of a specific account. +func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { + return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 838c084359..7abd07359f 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -239,6 +239,8 @@ func InspectDatabase(db ethdb.Database) error { hashNumPairing common.StorageSize trieSize common.StorageSize txlookupSize common.StorageSize + accountSnapSize common.StorageSize + storageSnapSize common.StorageSize preimageSize common.StorageSize bloomBitsSize common.StorageSize cliqueSnapsSize common.StorageSize @@ -280,6 +282,10 @@ func InspectDatabase(db ethdb.Database) error { receiptSize += size case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): txlookupSize += size + case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+common.HashLength): + accountSnapSize += size + case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+2*common.HashLength): + storageSnapSize += size case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): preimageSize += size case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): @@ -331,6 +337,8 @@ func InspectDatabase(db ethdb.Database) error { {"Key-Value store", "Bloombit index", bloomBitsSize.String()}, {"Key-Value store", "Trie nodes", trieSize.String()}, {"Key-Value store", "Trie preimages", preimageSize.String()}, + {"Key-Value store", "Account snapshot", accountSnapSize.String()}, + {"Key-Value store", "Storage snapshot", storageSnapSize.String()}, {"Key-Value store", "Clique snapshots", cliqueSnapsSize.String()}, {"Key-Value store", "Singleton metadata", metadata.String()}, {"Ancient store", "Headers", ancientHeaders.String()}, diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index a44a2c99f9..8e611246a1 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -41,6 +41,9 @@ var ( // fastTrieProgressKey tracks the number of trie entries imported during fast sync. fastTrieProgressKey = []byte("TrieSync") + // snapshotBlockKey tracks the number and hash of the last snapshot. + snapshotBlockKey = []byte("SnapshotBlock") + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td @@ -50,8 +53,9 @@ var ( blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts - txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata - bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits + txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata + bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits + StateSnapshotPrefix = []byte("s") // StateSnapshotPrefix + account hash [+ storage hash] -> account/storage trie value preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -145,6 +149,21 @@ func txLookupKey(hash common.Hash) []byte { return append(txLookupPrefix, hash.Bytes()...) } +// accountSnapshotKey = StateSnapshotPrefix + hash +func accountSnapshotKey(hash common.Hash) []byte { + return append(StateSnapshotPrefix, hash.Bytes()...) +} + +// storageSnapshotKey = StateSnapshotPrefix + account hash + storage hash +func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { + return append(append(StateSnapshotPrefix, accountHash.Bytes()...), storageHash.Bytes()...) +} + +// storageSnapshotsKey = StateSnapshotPrefix + account hash + storage hash +func storageSnapshotsKey(accountHash common.Hash) []byte { + return append(StateSnapshotPrefix, accountHash.Bytes()...) +} + // bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash func bloomBitsKey(bit uint, section uint64, hash common.Hash) []byte { key := append(append(bloomBitsPrefix, make([]byte, 10)...), hash.Bytes()...) diff --git a/core/state/snapshot/account.go b/core/state/snapshot/account.go new file mode 100644 index 0000000000..1068dc2a01 --- /dev/null +++ b/core/state/snapshot/account.go @@ -0,0 +1,54 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// Account is a slim version of a state.Account, where the root and code hash +// are replaced with a nil byte slice for empty accounts. +type Account struct { + Nonce uint64 + Balance *big.Int + Root []byte + CodeHash []byte +} + +// AccountRLP converts a state.Account content into a slim snapshot version RLP +// encoded. +func AccountRLP(nonce uint64, balance *big.Int, root common.Hash, codehash []byte) []byte { + slim := Account{ + Nonce: nonce, + Balance: balance, + } + if root != emptyRoot { + slim.Root = root[:] + } + if !bytes.Equal(codehash, emptyCode[:]) { + slim.CodeHash = codehash + } + data, err := rlp.EncodeToBytes(slim) + if err != nil { + panic(err) + } + return data +} diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go new file mode 100644 index 0000000000..f163feb561 --- /dev/null +++ b/core/state/snapshot/difflayer.go @@ -0,0 +1,337 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "fmt" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +// diffLayer represents a collection of modifications made to a state snapshot +// after running a block on top. It contains one sorted list for the account trie +// and one-one list for each storage tries. +// +// The goal of a diff layer is to act as a journal, tracking recent modifications +// made to the state, that have not yet graduated into a semi-immutable state. +type diffLayer struct { + parent snapshot // Parent snapshot modified by this one, never nil + memory uint64 // Approximate guess as to how much memory we use + + number uint64 // Block number to which this snapshot diff belongs to + root common.Hash // Root hash to which this snapshot diff belongs to + + accountList []common.Hash // List of account for iteration, might not be sorted yet (lazy) + accountSorted bool // Flag whether the account list has alreayd been sorted or not + accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) + storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account + storageSorted map[common.Hash]bool // Flag whether the storage slot list has alreayd been sorted or not + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + + lock sync.RWMutex +} + +// newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low +// level persistent database or a hierarchical diff already. +func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + // Create the new layer with some pre-allocated data segments + dl := &diffLayer{ + parent: parent, + number: number, + root: root, + accountData: accounts, + storageData: storage, + } + // Fill the account hashes and sort them for the iterator + accountList := make([]common.Hash, 0, len(accounts)) + for hash, data := range accounts { + accountList = append(accountList, hash) + dl.memory += uint64(len(data)) + } + sort.Sort(hashes(accountList)) + dl.accountList = accountList + dl.accountSorted = true + + dl.memory += uint64(len(dl.accountList) * common.HashLength) + + // Fill the storage hashes and sort them for the iterator + dl.storageList = make(map[common.Hash][]common.Hash, len(storage)) + dl.storageSorted = make(map[common.Hash]bool, len(storage)) + + for accountHash, slots := range storage { + // If the slots are nil, sanity check that it's a deleted account + if slots == nil { + // Ensure that the account was just marked as deleted + if account, ok := accounts[accountHash]; account != nil || !ok { + panic(fmt.Sprintf("storage in %#x nil, but account conflicts (%#x, exists: %v)", accountHash, account, ok)) + } + // Everything ok, store the deletion mark and continue + dl.storageList[accountHash] = nil + continue + } + // Storage slots are not nil so entire contract was not deleted, ensure the + // account was just updated. + if account, ok := accounts[accountHash]; account == nil || !ok { + log.Error(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) + //panic(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) + } + // Fill the storage hashes for this account and sort them for the iterator + storageList := make([]common.Hash, 0, len(slots)) + for storageHash, data := range slots { + storageList = append(storageList, storageHash) + dl.memory += uint64(len(data)) + } + sort.Sort(hashes(storageList)) + dl.storageList[accountHash] = storageList + dl.storageSorted[accountHash] = true + + dl.memory += uint64(len(storageList) * common.HashLength) + } + dl.memory += uint64(len(dl.storageList) * common.HashLength) + + return dl +} + +// Info returns the block number and root hash for which this snapshot was made. +func (dl *diffLayer) Info() (uint64, common.Hash) { + return dl.number, dl.root +} + +// Account directly retrieves the account associated with a particular hash in +// the snapshot slim data format. +func (dl *diffLayer) Account(hash common.Hash) *Account { + data := dl.AccountRLP(hash) + if len(data) == 0 { // can be both nil and []byte{} + return nil + } + account := new(Account) + if err := rlp.DecodeBytes(data, account); err != nil { + panic(err) + } + return account +} + +// AccountRLP directly retrieves the account RLP associated with a particular +// hash in the snapshot slim data format. +func (dl *diffLayer) AccountRLP(hash common.Hash) []byte { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the account is known locally, return it. Note, a nil account means it was + // deleted, and is a different notion than an unknown account! + if data, ok := dl.accountData[hash]; ok { + return data + } + // Account unknown to this diff, resolve from parent + return dl.parent.AccountRLP(hash) +} + +// Storage directly retrieves the storage data associated with a particular hash, +// within a particular account. If the slot is unknown to this diff, it's parent +// is consulted. +func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) []byte { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the account is known locally, try to resolve the slot locally. Note, a nil + // account means it was deleted, and is a different notion than an unknown account! + if storage, ok := dl.storageData[accountHash]; ok { + if storage == nil { + return nil + } + if data, ok := storage[storageHash]; ok { + return data + } + } + // Account - or slot within - unknown to this diff, resolve from parent + return dl.parent.Storage(accountHash, storageHash) +} + +// Update creates a new layer on top of the existing snapshot diff tree with +// the specified data items. +func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, dl.number+1, blockRoot, accounts, storage) +} + +// Cap traverses downwards the diff tree until the number of allowed layers are +// crossed. All diffs beyond the permitted number are flattened downwards. If +// the layer limit is reached, memory cap is also enforced (but not before). The +// block numbers for the disk layer and first diff layer are returned for GC. +func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { + // Dive until we run out of layers or reach the persistent database + if layers > 2 { + // If we still have diff layers below, recurse + if parent, ok := dl.parent.(*diffLayer); ok { + return parent.Cap(layers-1, memory) + } + // Diff stack too shallow, return block numbers without modifications + return dl.parent.(*diskLayer).number, dl.number + } + // We're out of layers, flatten anything below, stopping if it's the disk or if + // the memory limit is not yet exceeded. + switch parent := dl.parent.(type) { + case *diskLayer: + return parent.number, dl.number + case *diffLayer: + dl.lock.Lock() + defer dl.lock.Unlock() + + dl.parent = parent.flatten() + if dl.parent.(*diffLayer).memory < memory { + diskNumber, _ := parent.parent.Info() + return diskNumber, parent.number + } + default: + panic(fmt.Sprintf("unknown data layer: %T", parent)) + } + // If the bottommost layer is larger than our memory cap, persist to disk + var ( + parent = dl.parent.(*diffLayer) + base = parent.parent.(*diskLayer) + batch = base.db.NewBatch() + ) + parent.lock.RLock() + defer parent.lock.RUnlock() + + // Start by temporarilly deleting the current snapshot block marker. This + // ensures that in the case of a crash, the entire snapshot is invalidated. + rawdb.DeleteSnapshotBlock(batch) + + // Push all the accounts into the database + for hash, data := range parent.accountData { + if len(data) > 0 { + // Account was updated, push to disk + rawdb.WriteAccountSnapshot(batch, hash, data) + base.cache.Set(string(hash[:]), data) + + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write account snapshot", "err", err) + } + batch.Reset() + } + } else { + // Account was deleted, remove all storage slots too + rawdb.DeleteAccountSnapshot(batch, hash) + base.cache.Set(string(hash[:]), nil) + + it := rawdb.IterateStorageSnapshots(base.db, hash) + for it.Next() { + if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator + batch.Delete(key) + base.cache.Delete(string(key[1:])) + } + } + it.Release() + } + } + // Push all the storage slots into the database + for accountHash, storage := range parent.storageData { + for storageHash, data := range storage { + if len(data) > 0 { + rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) + } else { + rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), nil) + } + } + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage snapshot", "err", err) + } + batch.Reset() + } + } + // Update the snapshot block marker and write any remainder data + base.number, base.root = parent.number, parent.root + + rawdb.WriteSnapshotBlock(batch, base.number, base.root) + if err := batch.Write(); err != nil { + log.Crit("Failed to write leftover snapshot", "err", err) + } + dl.parent = base + + return base.number, dl.number +} + +// flatten pushes all data from this point downwards, flattening everything into +// a single diff at the bottom. Since usually the lowermost diff is the largest, +// the flattening bulds up from there in reverse. +func (dl *diffLayer) flatten() snapshot { + // If the parent is not diff, we're the first in line, return unmodified + parent, ok := dl.parent.(*diffLayer) + if !ok { + return dl + } + // Parent is a diff, flatten it first (note, apart from weird corned cases, + // flatten will realistically only ever merge 1 layer, so there's no need to + // be smarter about grouping flattens together). + parent = parent.flatten().(*diffLayer) + + // Overwrite all the updated accounts blindly, merge the sorted list + for hash, data := range dl.accountData { + parent.accountData[hash] = data + } + parent.accountList = append(parent.accountList, dl.accountList...) // TODO(karalabe): dedup!! + parent.accountSorted = false + + // Overwrite all the updates storage slots (individually) + for accountHash, storage := range dl.storageData { + // If storage didn't exist (or was deleted) in the parent; or if the storage + // was freshly deleted in the child, overwrite blindly + if parent.storageData[accountHash] == nil || storage == nil { + parent.storageList[accountHash] = dl.storageList[accountHash] + parent.storageData[accountHash] = storage + continue + } + // Storage exists in both parent and child, merge the slots + comboData := parent.storageData[accountHash] + for storageHash, data := range storage { + comboData[storageHash] = data + } + parent.storageData[accountHash] = comboData + parent.storageList[accountHash] = append(parent.storageList[accountHash], dl.storageList[accountHash]...) // TODO(karalabe): dedup!! + parent.storageSorted[accountHash] = false + } + // Return the combo parent + parent.number = dl.number + parent.root = dl.root + parent.memory += dl.memory + return parent +} + +// Journal commits an entire diff hierarchy to disk into a single journal file. +// This is meant to be used during shutdown to persist the snapshot without +// flattening everything down (bad for reorgs). +func (dl *diffLayer) Journal() error { + dl.lock.RLock() + defer dl.lock.RUnlock() + + writer, err := dl.journal() + if err != nil { + return err + } + writer.Close() + return nil +} diff --git a/core/state/snapshot/difflayer_journal.go b/core/state/snapshot/difflayer_journal.go new file mode 100644 index 0000000000..844ee88592 --- /dev/null +++ b/core/state/snapshot/difflayer_journal.go @@ -0,0 +1,140 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "fmt" + "io" + "os" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" +) + +// journalAccount is an account entry in a diffLayer's disk journal. +type journalAccount struct { + Hash common.Hash + Blob []byte +} + +// journalStorage is an account's storage map in a diffLayer's disk journal. +type journalStorage struct { + Hash common.Hash + Keys []common.Hash + Vals [][]byte +} + +// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new +// diff and verifying that it can be linked to the requested parent. +func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { + // Read the next diff journal entry + var ( + number uint64 + root common.Hash + ) + if err := r.Decode(&number); err != nil { + // The first read may fail with EOF, marking the end of the journal + if err == io.EOF { + return parent, nil + } + return nil, fmt.Errorf("load diff number: %v", err) + } + if err := r.Decode(&root); err != nil { + return nil, fmt.Errorf("load diff root: %v", err) + } + var accounts []journalAccount + if err := r.Decode(&accounts); err != nil { + return nil, fmt.Errorf("load diff accounts: %v", err) + } + accountData := make(map[common.Hash][]byte) + for _, entry := range accounts { + accountData[entry.Hash] = entry.Blob + } + var storage []journalStorage + if err := r.Decode(&storage); err != nil { + return nil, fmt.Errorf("load diff storage: %v", err) + } + storageData := make(map[common.Hash]map[common.Hash][]byte) + for _, entry := range storage { + slots := make(map[common.Hash][]byte) + for i, key := range entry.Keys { + slots[key] = entry.Vals[i] + } + storageData[entry.Hash] = slots + } + // Validate the block number to avoid state corruption + if parent, ok := parent.(*diffLayer); ok { + if number != parent.number+1 { + return nil, fmt.Errorf("snapshot chain broken: block #%d after #%d", number, parent.number) + } + } + return loadDiffLayer(newDiffLayer(parent, number, root, accountData, storageData), r) +} + +// journal is the internal version of Journal that also returns the journal file +// so subsequent layers know where to write to. +func (dl *diffLayer) journal() (io.WriteCloser, error) { + // If we've reached the bottom, open the journal + var writer io.WriteCloser + if parent, ok := dl.parent.(*diskLayer); ok { + file, err := os.Create(parent.journal) + if err != nil { + return nil, err + } + writer = file + } + // If we haven't reached the bottom yet, journal the parent first + if writer == nil { + file, err := dl.parent.(*diffLayer).journal() + if err != nil { + return nil, err + } + writer = file + } + // Everything below was journalled, persist this layer too + if err := rlp.Encode(writer, dl.number); err != nil { + writer.Close() + return nil, err + } + if err := rlp.Encode(writer, dl.root); err != nil { + writer.Close() + return nil, err + } + accounts := make([]journalAccount, 0, len(dl.accountData)) + for hash, blob := range dl.accountData { + accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) + } + if err := rlp.Encode(writer, accounts); err != nil { + writer.Close() + return nil, err + } + storage := make([]journalStorage, 0, len(dl.storageData)) + for hash, slots := range dl.storageData { + keys := make([]common.Hash, 0, len(slots)) + vals := make([][]byte, 0, len(slots)) + for key, val := range slots { + keys = append(keys, key) + vals = append(vals, val) + } + storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) + } + if err := rlp.Encode(writer, storage); err != nil { + writer.Close() + return nil, err + } + return writer, nil +} diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go new file mode 100644 index 0000000000..0406d298fb --- /dev/null +++ b/core/state/snapshot/disklayer.go @@ -0,0 +1,115 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "github.com/allegro/bigcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" +) + +// diskLayer is a low level persistent snapshot built on top of a key-value store. +type diskLayer struct { + journal string // Path of the snapshot journal to use on shutdown + db ethdb.KeyValueStore // Key-value store containing the base snapshot + cache *bigcache.BigCache // Cache to avoid hitting the disk for direct access + + number uint64 // Block number of the base snapshot + root common.Hash // Root hash of the base snapshot +} + +// Info returns the block number and root hash for which this snapshot was made. +func (dl *diskLayer) Info() (uint64, common.Hash) { + return dl.number, dl.root +} + +// Account directly retrieves the account associated with a particular hash in +// the snapshot slim data format. +func (dl *diskLayer) Account(hash common.Hash) *Account { + data := dl.AccountRLP(hash) + if len(data) == 0 { // can be both nil and []byte{} + return nil + } + account := new(Account) + if err := rlp.DecodeBytes(data, account); err != nil { + panic(err) + } + return account +} + +// AccountRLP directly retrieves the account RLP associated with a particular +// hash in the snapshot slim data format. +func (dl *diskLayer) AccountRLP(hash common.Hash) []byte { + key := string(hash[:]) + + // Try to retrieve the account from the memory cache + if blob, err := dl.cache.Get(key); err == nil { + snapshotCleanHitMeter.Mark(1) + snapshotCleanReadMeter.Mark(int64(len(blob))) + return blob + } + // Cache doesn't contain account, pull from disk and cache for later + blob := rawdb.ReadAccountSnapshot(dl.db, hash) + dl.cache.Set(key, blob) + + snapshotCleanMissMeter.Mark(1) + snapshotCleanWriteMeter.Mark(int64(len(blob))) + + return blob +} + +// Storage directly retrieves the storage data associated with a particular hash, +// within a particular account. +func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) []byte { + key := string(append(accountHash[:], storageHash[:]...)) + + // Try to retrieve the storage slot from the memory cache + if blob, err := dl.cache.Get(key); err == nil { + snapshotCleanHitMeter.Mark(1) + snapshotCleanReadMeter.Mark(int64(len(blob))) + return blob + } + // Cache doesn't contain storage slot, pull from disk and cache for later + blob := rawdb.ReadStorageSnapshot(dl.db, accountHash, storageHash) + dl.cache.Set(key, blob) + + snapshotCleanMissMeter.Mark(1) + snapshotCleanWriteMeter.Mark(int64(len(blob))) + + return blob +} + +// Update creates a new layer on top of the existing snapshot diff tree with +// the specified data items. Note, the maps are retained by the method to avoid +// copying everything. +func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, dl.number+1, blockHash, accounts, storage) +} + +// Cap traverses downwards the diff tree until the number of allowed layers are +// crossed. All diffs beyond the permitted number are flattened downwards. +func (dl *diskLayer) Cap(layers int, memory uint64) (uint64, uint64) { + return dl.number, dl.number +} + +// Journal commits an entire diff hierarchy to disk into a single journal file. +func (dl *diskLayer) Journal() error { + // There's no journalling a disk layer + return nil +} diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go new file mode 100644 index 0000000000..0d451fe50d --- /dev/null +++ b/core/state/snapshot/generate.go @@ -0,0 +1,212 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "math/big" + "time" + + "github.com/allegro/bigcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +var ( + // emptyRoot is the known root hash of an empty trie. + emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + + // emptyCode is the known hash of the empty EVM bytecode. + emptyCode = crypto.Keccak256Hash(nil) +) + +// wipeSnapshot iterates over the entire key-value database and deletes all the +// data associated with the snapshot (accounts, storage, metadata). After all is +// done, the snapshot range of the database is compacted to free up unused data +// blocks. +func wipeSnapshot(db ethdb.KeyValueStore) error { + // Batch deletions together to avoid holding an iterator for too long + var ( + batch = db.NewBatch() + items int + ) + // Iterate over the snapshot key-range and delete all of them + log.Info("Deleting previous snapshot leftovers") + start, logged := time.Now(), time.Now() + + it := db.NewIteratorWithStart(rawdb.StateSnapshotPrefix) + for it.Next() { + // Skip any keys with the correct prefix but wrong lenth (trie nodes) + key := it.Key() + if !bytes.HasPrefix(key, rawdb.StateSnapshotPrefix) { + break + } + if len(key) != len(rawdb.StateSnapshotPrefix)+common.HashLength && len(key) != len(rawdb.StateSnapshotPrefix)+2*common.HashLength { + continue + } + // Delete the key and periodically recreate the batch and iterator + batch.Delete(key) + items++ + + if items%10000 == 0 { + // Batch too large (or iterator too long lived, flush and recreate) + it.Release() + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + it = db.NewIteratorWithStart(key) + + if time.Since(logged) > 8*time.Second { + log.Info("Deleting previous snapshot leftovers", "wiped", items, "elapsed", time.Since(start)) + logged = time.Now() + } + } + } + it.Release() + + rawdb.DeleteSnapshotBlock(batch) + if err := batch.Write(); err != nil { + return err + } + log.Info("Deleted previous snapshot leftovers", "wiped", items, "elapsed", time.Since(start)) + + // Compact the snapshot section of the database to get rid of unused space + log.Info("Compacting snapshot area in database") + start = time.Now() + + end := common.CopyBytes(rawdb.StateSnapshotPrefix) + end[len(end)-1]++ + + if err := db.Compact(rawdb.StateSnapshotPrefix, end); err != nil { + return err + } + log.Info("Compacted snapshot area in database", "elapsed", time.Since(start)) + + return nil +} + +// generateSnapshot regenerates a brand new snapshot based on an existing state database and head block. +func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (snapshot, error) { + // Wipe any previously existing snapshot from the database + if err := wipeSnapshot(db); err != nil { + return nil, err + } + // Iterate the entire storage trie and re-generate the state snapshot + var ( + accountCount int + storageCount int + storageNodes int + accountSize common.StorageSize + storageSize common.StorageSize + logged time.Time + ) + batch := db.NewBatch() + triedb := trie.NewDatabase(db) + + accTrie, err := trie.NewSecure(headRoot, triedb) + if err != nil { + return nil, err + } + accIt := trie.NewIterator(accTrie.NodeIterator(nil)) + for accIt.Next() { + var ( + curStorageCount int + curStorageNodes int + curAccountSize common.StorageSize + curStorageSize common.StorageSize + ) + var acc struct { + Nonce uint64 + Balance *big.Int + Root common.Hash + CodeHash []byte + } + if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { + return nil, err + } + data := AccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) + curAccountSize += common.StorageSize(1 + common.HashLength + len(data)) + + rawdb.WriteAccountSnapshot(batch, common.BytesToHash(accIt.Key), data) + if batch.ValueSize() > ethdb.IdealBatchSize { + batch.Write() + batch.Reset() + } + if acc.Root != emptyRoot { + storeTrie, err := trie.NewSecure(acc.Root, triedb) + if err != nil { + return nil, err + } + storeIt := trie.NewIterator(storeTrie.NodeIterator(nil)) + for storeIt.Next() { + curStorageSize += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) + curStorageCount++ + + rawdb.WriteStorageSnapshot(batch, common.BytesToHash(accIt.Key), common.BytesToHash(storeIt.Key), storeIt.Value) + if batch.ValueSize() > ethdb.IdealBatchSize { + batch.Write() + batch.Reset() + } + } + curStorageNodes = storeIt.Nodes + } + accountCount++ + storageCount += curStorageCount + accountSize += curAccountSize + storageSize += curStorageSize + storageNodes += curStorageNodes + + if time.Since(logged) > 8*time.Second { + fmt.Printf("%#x: %9s + %9s (%6d slots, %6d nodes), total %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accIt.Key, curAccountSize.TerminalString(), curStorageSize.TerminalString(), curStorageCount, curStorageNodes, accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + logged = time.Now() + } + } + fmt.Printf("Totals: %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + + // Update the snapshot block marker and write any remainder data + rawdb.WriteSnapshotBlock(batch, headNumber, headRoot) + batch.Write() + batch.Reset() + + // Compact the snapshot section of the database to get rid of unused space + log.Info("Compacting snapshot in chain database") + if err := db.Compact([]byte{'s'}, []byte{'s' + 1}); err != nil { + return nil, err + } + // New snapshot generated, construct a brand new base layer + cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup + Shards: 1024, + LifeWindow: time.Hour, + MaxEntriesInWindow: 512 * 1024, + MaxEntrySize: 512, + HardMaxCacheSize: 512, + }) + return &diskLayer{ + journal: journal, + db: db, + cache: cache, + number: headNumber, + root: headRoot, + }, nil +} diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go new file mode 100644 index 0000000000..1206445c58 --- /dev/null +++ b/core/state/snapshot/generate_test.go @@ -0,0 +1,111 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} + +// Tests that given a database with random data content, all parts of a snapshot +// can be crrectly wiped without touching anything else. +func TestWipe(t *testing.T) { + // Create a database with some random snapshot data + db := memorydb.New() + + for i := 0; i < 128; i++ { + account := randomHash() + rawdb.WriteAccountSnapshot(db, account, randomHash().Bytes()) + for j := 0; j < 1024; j++ { + rawdb.WriteStorageSnapshot(db, account, randomHash(), randomHash().Bytes()) + } + } + rawdb.WriteSnapshotBlock(db, 123, randomHash()) + + // Add some random non-snapshot data too to make wiping harder + for i := 0; i < 65536; i++ { + // Generate a key that's the wrong length for a state snapshot item + var keysize int + for keysize == 0 || keysize == 32 || keysize == 64 { + keysize = 8 + rand.Intn(64) // +8 to ensure we will "never" randomize duplicates + } + // Randomize the suffix, dedup and inject it under the snapshot namespace + keysuffix := make([]byte, keysize) + rand.Read(keysuffix) + db.Put(append(rawdb.StateSnapshotPrefix, keysuffix...), randomHash().Bytes()) + } + // Sanity check that all the keys are present + var items int + + it := db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) + defer it.Release() + + for it.Next() { + key := it.Key() + if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { + items++ + } + } + if items != 128+128*1024 { + t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128+128*1024) + } + if number, hash := rawdb.ReadSnapshotBlock(db); number != 123 || hash == (common.Hash{}) { + t.Errorf("snapshot block marker mismatch: have #%d [%#x], want #%d []", number, hash, 123) + } + // Wipe all snapshot entries from the database + if err := wipeSnapshot(db); err != nil { + t.Fatalf("failed to wipe snapshot: %v", err) + } + // Iterate over the database end ensure no snapshot information remains + it = db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) + defer it.Release() + + for it.Next() { + key := it.Key() + if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { + t.Errorf("snapshot entry remained after wipe: %x", key) + } + } + if number, hash := rawdb.ReadSnapshotBlock(db); number != 0 || hash != (common.Hash{}) { + t.Errorf("snapshot block marker remained after wipe: #%d [%#x]", number, hash) + } + // Iterate over the database and ensure miscellaneous items are present + items = 0 + + it = db.NewIterator() + defer it.Release() + + for it.Next() { + items++ + } + if items != 65536 { + t.Fatalf("misc item count mismatch: have %d, want %d", items, 65536) + } +} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go new file mode 100644 index 0000000000..6d4df96da8 --- /dev/null +++ b/core/state/snapshot/snapshot.go @@ -0,0 +1,244 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package snapshot implements a journalled, dynamic state dump. +package snapshot + +import ( + "errors" + "fmt" + "os" + "sync" + "time" + + "github.com/allegro/bigcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" +) + +var ( + snapshotCleanHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/hit", nil) + snapshotCleanMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/miss", nil) + snapshotCleanReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/read", nil) + snapshotCleanWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/write", nil) +) + +// Snapshot represents the functionality supported by a snapshot storage layer. +type Snapshot interface { + // Info returns the block number and root hash for which this snapshot was made. + Info() (uint64, common.Hash) + + // Account directly retrieves the account associated with a particular hash in + // the snapshot slim data format. + Account(hash common.Hash) *Account + + // AccountRLP directly retrieves the account RLP associated with a particular + // hash in the snapshot slim data format. + AccountRLP(hash common.Hash) []byte + + // Storage directly retrieves the storage data associated with a particular hash, + // within a particular account. + Storage(accountHash, storageHash common.Hash) []byte +} + +// snapshot is the internal version of the snapshot data layer that supports some +// additional methods compared to the public API. +type snapshot interface { + Snapshot + + // Update creates a new layer on top of the existing snapshot diff tree with + // the specified data items. Note, the maps are retained by the method to avoid + // copying everything. + Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer + + // Cap traverses downwards the diff tree until the number of allowed layers are + // crossed. All diffs beyond the permitted number are flattened downwards. The + // block numbers for the disk layer and first diff layer are returned for GC. + Cap(layers int, memory uint64) (uint64, uint64) + + // Journal commits an entire diff hierarchy to disk into a single journal file. + // This is meant to be used during shutdown to persist the snapshot without + // flattening everything down (bad for reorgs). + Journal() error +} + +// SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent +// base layer backed by a key-value store, on top of which arbitrarilly many in- +// memory diff layers are topped. The memory diffs can form a tree with branching, +// but the disk layer is singleton and common to all. If a reorg goes deeper than +// the disk layer, everything needs to be deleted. +// +// The goal of a state snapshot is twofold: to allow direct access to account and +// storage data to avoid expensive multi-level trie lookups; and to allow sorted, +// cheap iteration of the account/storage tries for sync aid. +type SnapshotTree struct { + layers map[common.Hash]snapshot // Collection of all known layers // TODO(karalabe): split Clique overlaps + lock sync.RWMutex +} + +// New attempts to load an already existing snapshot from a persistent key-value +// store (with a number of memory layers from a journal), ensuring that the head +// of the snapshot matches the expected one. +// +// If the snapshot is missing or inconsistent, the entirety is deleted and will +// be reconstructed from scratch based on the tries in the key-value store. +func New(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (*SnapshotTree, error) { + // Attempt to load a previously persisted snapshot + head, err := loadSnapshot(db, journal, headNumber, headRoot) + if err != nil { + log.Warn("Failed to load snapshot, regenerating", "err", err) + if head, err = generateSnapshot(db, journal, headNumber, headRoot); err != nil { + return nil, err + } + } + // Existing snapshot loaded or one regenerated, seed all the layers + snap := &SnapshotTree{ + layers: make(map[common.Hash]snapshot), + } + for head != nil { + _, root := head.Info() + snap.layers[root] = head + + switch self := head.(type) { + case *diffLayer: + head = self.parent + case *diskLayer: + head = nil + default: + panic(fmt.Sprintf("unknown data layer: %T", self)) + } + } + return snap, nil +} + +// Snapshot retrieves a snapshot belonging to the given block root, or nil if no +// snapshot is maintained for that block. +func (st *SnapshotTree) Snapshot(blockRoot common.Hash) Snapshot { + st.lock.RLock() + defer st.lock.RUnlock() + + return st.layers[blockRoot] +} + +// Update adds a new snapshot into the tree, if that can be linked to an existing +// old parent. It is disallowed to insert a disk layer (the origin of all). +func (st *SnapshotTree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { + // Generate a new snapshot on top of the parent + parent := st.Snapshot(parentRoot).(snapshot) + if parent == nil { + return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) + } + snap := parent.Update(blockRoot, accounts, storage) + + // Save the new snapshot for later + st.lock.Lock() + defer st.lock.Unlock() + + st.layers[snap.root] = snap + return nil +} + +// Cap traverses downwards the snapshot tree from a head block hash until the +// number of allowed layers are crossed. All layers beyond the permitted number +// are flattened downwards. +func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) error { + // Retrieve the head snapshot to cap from + snap := st.Snapshot(blockRoot).(snapshot) + if snap == nil { + return fmt.Errorf("snapshot [%#x] missing", blockRoot) + } + // Run the internal capping and discard all stale layers + st.lock.Lock() + defer st.lock.Unlock() + + diskNumber, diffNumber := snap.Cap(layers, memory) + for root, snap := range st.layers { + if number, _ := snap.Info(); number != diskNumber && number < diffNumber { + delete(st.layers, root) + } + } + return nil +} + +// Journal commits an entire diff hierarchy to disk into a single journal file. +// This is meant to be used during shutdown to persist the snapshot without +// flattening everything down (bad for reorgs). +func (st *SnapshotTree) Journal(blockRoot common.Hash) error { + // Retrieve the head snapshot to journal from + snap := st.Snapshot(blockRoot).(snapshot) + if snap == nil { + return fmt.Errorf("snapshot [%#x] missing", blockRoot) + } + // Run the journaling + st.lock.Lock() + defer st.lock.Unlock() + + return snap.Journal() +} + +// loadSnapshot loads a pre-existing state snapshot backed by a key-value store. +func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (snapshot, error) { + // Retrieve the block number and hash of the snapshot, failing if no snapshot + // is present in the database (or crashed mid-update). + number, root := rawdb.ReadSnapshotBlock(db) + if root == (common.Hash{}) { + return nil, errors.New("missing or corrupted snapshot") + } + cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup + Shards: 1024, + LifeWindow: time.Hour, + MaxEntriesInWindow: 512 * 1024, + MaxEntrySize: 512, + HardMaxCacheSize: 512, + }) + base := &diskLayer{ + journal: journal, + db: db, + cache: cache, + number: number, + root: root, + } + // Load all the snapshot diffs from the journal, failing if their chain is broken + // or does not lead from the disk snapshot to the specified head. + if _, err := os.Stat(journal); os.IsNotExist(err) { + // Journal doesn't exist, don't worry if it's not supposed to + if number != headNumber || root != headRoot { + return nil, fmt.Errorf("snapshot journal missing, head does't match snapshot: #%d [%#x] vs. #%d [%#x]", + headNumber, headRoot, number, root) + } + return base, nil + } + file, err := os.Open(journal) + if err != nil { + return nil, err + } + snapshot, err := loadDiffLayer(base, rlp.NewStream(file, 0)) + if err != nil { + return nil, err + } + // Entire snapshot journal loaded, sanity check the head and return + // Journal doesn't exist, don't worry if it's not supposed to + number, root = snapshot.Info() + if number != headNumber || root != headRoot { + return nil, fmt.Errorf("head does't match snapshot: #%d [%#x] vs. #%d [%#x]", + headNumber, headRoot, number, root) + } + return snapshot, nil +} diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go new file mode 100644 index 0000000000..903bd4a6f6 --- /dev/null +++ b/core/state/snapshot/snapshot_test.go @@ -0,0 +1,17 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot diff --git a/core/state/snapshot/sort.go b/core/state/snapshot/sort.go new file mode 100644 index 0000000000..04729c60b2 --- /dev/null +++ b/core/state/snapshot/sort.go @@ -0,0 +1,62 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// hashes is a helper to implement sort.Interface. +type hashes []common.Hash + +// Len is the number of elements in the collection. +func (hs hashes) Len() int { return len(hs) } + +// Less reports whether the element with index i should sort before the element +// with index j. +func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) < 0 } + +// Swap swaps the elements with indexes i and j. +func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] } + +// merge combines two sorted lists of hashes into a combo sorted one. +func merge(a, b []common.Hash) []common.Hash { + result := make([]common.Hash, len(a)+len(b)) + + i := 0 + for len(a) > 0 && len(b) > 0 { + if bytes.Compare(a[0][:], b[0][:]) < 0 { + result[i] = a[0] + a = a[1:] + } else { + result[i] = b[0] + b = b[1:] + } + i++ + } + for j := 0; j < len(a); j++ { + result[i] = a[j] + i++ + } + for j := 0; j < len(b); j++ { + result[i] = b[j] + i++ + } + return result +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 667d5ec02e..98be566716 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -195,15 +195,26 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if value, cached := s.originStorage[key]; cached { return value } - // Track the amount of time wasted on reading the storage trie - if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) - } - // Otherwise load the value from the database - enc, err := s.getTrie(db).TryGet(key[:]) - if err != nil { - s.setError(err) - return common.Hash{} + // If no live objects are available, attempt to use snapshots + var ( + enc []byte + err error + ) + if s.db.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) + } + enc = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) + } else { + // Track the amount of time wasted on reading the storage trie + if metrics.EnabledExpensive { + defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) + } + // Otherwise load the value from the database + if enc, err = s.getTrie(db).TryGet(key[:]); err != nil { + s.setError(err) + return common.Hash{} + } } var value common.Hash if len(enc) > 0 { @@ -283,6 +294,23 @@ func (s *stateObject) updateTrie(db Database) Trie { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) } + // Retrieve the snapshot storage map for the object + var storage map[common.Hash][]byte + if s.db.snap != nil { + // Retrieve the old storage map, if available + s.db.snapLock.RLock() + storage = s.db.snapStorage[s.addrHash] + s.db.snapLock.RUnlock() + + // If no old storage map was available, create a new one + if storage == nil { + storage = make(map[common.Hash][]byte) + + s.db.snapLock.Lock() + s.db.snapStorage[s.addrHash] = storage + s.db.snapLock.Unlock() + } + } // Insert all the pending updates into the trie tr := s.getTrie(db) for key, value := range s.pendingStorage { @@ -292,13 +320,18 @@ func (s *stateObject) updateTrie(db Database) Trie { } s.originStorage[key] = value + var v []byte if (value == common.Hash{}) { s.setError(tr.TryDelete(key[:])) - continue + } else { + // Encoding []byte cannot fail, ok to ignore the error. + v, _ = rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) + s.setError(tr.TryUpdate(key[:], v)) + } + // If state snapshotting is active, cache the data til commit + if storage != nil { + storage[crypto.Keccak256Hash(key[:])] = v // v will be nil if value is 0x00 } - // Encoding []byte cannot fail, ok to ignore the error. - v, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(value[:])) - s.setError(tr.TryUpdate(key[:], v)) } if len(s.pendingStorage) > 0 { s.pendingStorage = make(Storage) diff --git a/core/state/statedb.go b/core/state/statedb.go index 5d40f59c65..0fb1095ce2 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,9 +22,11 @@ import ( "fmt" "math/big" "sort" + "sync" "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -66,6 +68,12 @@ type StateDB struct { db Database trie Trie + snaps *snapshot.SnapshotTree + snap snapshot.Snapshot + snapAccounts map[common.Hash][]byte + snapStorage map[common.Hash]map[common.Hash][]byte + snapLock sync.RWMutex // Lock for the concurrent storage updaters + // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*stateObject stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie @@ -95,32 +103,43 @@ type StateDB struct { nextRevisionId int // Measurements gathered during execution for debugging purposes - AccountReads time.Duration - AccountHashes time.Duration - AccountUpdates time.Duration - AccountCommits time.Duration - StorageReads time.Duration - StorageHashes time.Duration - StorageUpdates time.Duration - StorageCommits time.Duration + AccountReads time.Duration + AccountHashes time.Duration + AccountUpdates time.Duration + AccountCommits time.Duration + StorageReads time.Duration + StorageHashes time.Duration + StorageUpdates time.Duration + StorageCommits time.Duration + SnapshotAccountReads time.Duration + SnapshotStorageReads time.Duration + SnapshotCommits time.Duration } // Create a new state from a given trie. -func New(root common.Hash, db Database) (*StateDB, error) { +func New(root common.Hash, db Database, snaps *snapshot.SnapshotTree) (*StateDB, error) { tr, err := db.OpenTrie(root) if err != nil { return nil, err } - return &StateDB{ + sdb := &StateDB{ db: db, trie: tr, + snaps: snaps, stateObjects: make(map[common.Address]*stateObject), stateObjectsPending: make(map[common.Address]struct{}), stateObjectsDirty: make(map[common.Address]struct{}), logs: make(map[common.Hash][]*types.Log), preimages: make(map[common.Hash][]byte), journal: newJournal(), - }, nil + } + if sdb.snaps != nil { + if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { + sdb.snapAccounts = make(map[common.Hash][]byte) + sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + } + } + return sdb, nil } // setError remembers the first non-nil error it is called with. @@ -152,6 +171,14 @@ func (s *StateDB) Reset(root common.Hash) error { s.logSize = 0 s.preimages = make(map[common.Hash][]byte) s.clearJournalAndRefund() + + if s.snaps != nil { + s.snapAccounts, s.snapStorage = nil, nil + if s.snap = s.snaps.Snapshot(root); s.snap != nil { + s.snapAccounts = make(map[common.Hash][]byte) + s.snapStorage = make(map[common.Hash]map[common.Hash][]byte) + } + } return nil } @@ -438,6 +465,11 @@ func (s *StateDB) updateStateObject(obj *stateObject) { panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) } s.setError(s.trie.TryUpdate(addr[:], data)) + + // If state snapshotting is active, cache the data til commit + if s.snap != nil { + s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + } } // deleteStateObject removes the given object from the state trie. @@ -449,6 +481,14 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // Delete the account from the trie addr := obj.Address() s.setError(s.trie.TryDelete(addr[:])) + + // If state snapshotting is active, cache the data til commit + if s.snap != nil { + s.snapLock.Lock() + s.snapAccounts[obj.addrHash] = nil // We need to maintain account deletions explicitly + s.snapStorage[obj.addrHash] = nil // We need to maintain storage deletions explicitly + s.snapLock.Unlock() + } } // getStateObject retrieves a state object given by the address, returning nil if @@ -470,20 +510,38 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { if obj := s.stateObjects[addr]; obj != nil { return obj } - // Track the amount of time wasted on loading the object from the database - if metrics.EnabledExpensive { - defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) - } - // Load the object from the database - enc, err := s.trie.TryGet(addr[:]) - if len(enc) == 0 { - s.setError(err) - return nil - } + // If no live objects are available, attempt to use snapshots var data Account - if err := rlp.DecodeBytes(enc, &data); err != nil { - log.Error("Failed to decode state object", "addr", addr, "err", err) - return nil + if s.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) + } + acc := s.snap.Account(crypto.Keccak256Hash(addr[:])) + if acc == nil { + return nil + } + data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash + if len(data.CodeHash) == 0 { + data.CodeHash = emptyCodeHash + } + data.Root = common.BytesToHash(acc.Root) + if data.Root == (common.Hash{}) { + data.Root = emptyRoot + } + } else { + // Snapshot unavailable, fall back to the trie + if metrics.EnabledExpensive { + defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) + } + enc, err := s.trie.TryGet(addr[:]) + if len(enc) == 0 { + s.setError(err) + return nil + } + if err := rlp.DecodeBytes(enc, &data); err != nil { + log.Error("Failed to decode state object", "addr", addr, "err", err) + return nil + } } // Insert into the live set obj := newObject(s, addr, data) @@ -748,13 +806,14 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { s.stateObjectsDirty = make(map[common.Address]struct{}) } // Write the account trie changes, measuing the amount of wasted time + var start time.Time if metrics.EnabledExpensive { - defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now()) + start = time.Now() } // The onleaf func is called _serially_, so we can reuse the same account // for unmarshalling every time. var account Account - return s.trie.Commit(func(leaf []byte, parent common.Hash) error { + root, err := s.trie.Commit(func(leaf []byte, parent common.Hash) error { if err := rlp.DecodeBytes(leaf, &account); err != nil { return nil } @@ -767,4 +826,22 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } return nil }) + if metrics.EnabledExpensive { + s.AccountCommits += time.Since(start) + } + // If snapshotting is enabled, update the snapshot tree with this new version + if s.snap != nil { + if metrics.EnabledExpensive { + defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now()) + } + _, parentRoot := s.snap.Info() + if err := s.snaps.Update(root, parentRoot, s.snapAccounts, s.snapStorage); err != nil { + log.Warn("Failed to update snapshot tree", "from", parentRoot, "to", root, "err", err) + } + if err := s.snaps.Cap(root, 16, 4*1024*1024); err != nil { + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 16, "memory", 4*1024*1024, "err", err) + } + s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil + } + return root, err } diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index cb85a05b57..bb5db4ced1 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -65,6 +65,8 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return // Ugh, something went horribly wrong, bail out } } + // All transactions processed, finalize the block to force loading written-only trie paths + statedb.Finalise(true) // TODO(karalabe): should we run this on interrupt too? } // precacheTransaction attempts to apply a transaction to the given state database diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index dd5dba66f0..9cb492786b 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -99,7 +99,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { setDefaults(cfg) if cfg.State == nil { - cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) } var ( address = common.BytesToAddress([]byte("contract")) @@ -129,7 +129,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { setDefaults(cfg) if cfg.State == nil { - cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) } var ( vmenv = NewEnv(cfg) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index f2d05118ce..fb07d69d09 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -98,7 +98,7 @@ func TestExecute(t *testing.T) { } func TestCall(t *testing.T) { - state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) address := common.HexToAddress("0x0a") state.SetCode(address, []byte{ byte(vm.PUSH1), 10, @@ -154,7 +154,7 @@ func BenchmarkCall(b *testing.B) { } func benchmarkEVM_Create(bench *testing.B, code string) { var ( - statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) sender = common.BytesToAddress([]byte("sender")) receiver = common.BytesToAddress([]byte("receiver")) ) diff --git a/eth/api_test.go b/eth/api_test.go index 1e7c489c32..ab846db3ea 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -64,7 +64,7 @@ func (h resultHash) Less(i, j int) bool { return bytes.Compare(h[i].Bytes(), h[j func TestAccountRange(t *testing.T) { var ( statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) - state, _ = state.New(common.Hash{}, statedb) + state, _ = state.New(common.Hash{}, statedb, nil) addrs = [AccountRangeMaxResults * 2]common.Address{} m = map[common.Address]bool{} ) @@ -162,7 +162,7 @@ func TestAccountRange(t *testing.T) { func TestEmptyAccountRange(t *testing.T) { var ( statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) - state, _ = state.New(common.Hash{}, statedb) + state, _ = state.New(common.Hash{}, statedb, nil) ) state.Commit(true) @@ -188,7 +188,7 @@ func TestEmptyAccountRange(t *testing.T) { func TestStorageRangeAt(t *testing.T) { // Create a state where account 0x010000... has a few storage entries. var ( - state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + state, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) addr = common.Address{0x01} keys = []common.Hash{ // hashes of Keys of storage common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"), diff --git a/eth/api_tracer.go b/eth/api_tracer.go index ce211cbd99..560f460445 100644 --- a/eth/api_tracer.go +++ b/eth/api_tracer.go @@ -155,7 +155,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl return nil, fmt.Errorf("parent block #%d not found", number-1) } } - statedb, err := state.New(start.Root(), database) + statedb, err := state.New(start.Root(), database, nil) if err != nil { // If the starting state is missing, allow some number of blocks to be reexecuted reexec := defaultTraceReexec @@ -168,7 +168,7 @@ func (api *PrivateDebugAPI) traceChain(ctx context.Context, start, end *types.Bl if start == nil { break } - if statedb, err = state.New(start.Root(), database); err == nil { + if statedb, err = state.New(start.Root(), database, nil); err == nil { break } } @@ -648,7 +648,7 @@ func (api *PrivateDebugAPI) computeStateDB(block *types.Block, reexec uint64) (* if block == nil { break } - if statedb, err = state.New(block.Root(), database); err == nil { + if statedb, err = state.New(block.Root(), database, nil); err == nil { break } } diff --git a/eth/handler_test.go b/eth/handler_test.go index 4a4e1f9559..670fd2b14c 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -349,7 +349,7 @@ func testGetNodeData(t *testing.T, protocol int) { } accounts := []common.Address{testBank, acc1Addr, acc2Addr} for i := uint64(0); i <= pm.blockchain.CurrentBlock().NumberU64(); i++ { - trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb)) + trie, _ := state.New(pm.blockchain.GetBlockByNumber(i).Root(), state.NewDatabase(statedb), nil) for j, acc := range accounts { state, _ := pm.blockchain.State() diff --git a/light/odr_test.go b/light/odr_test.go index debd5544c3..9149c02fc2 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -149,7 +149,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc st = NewState(ctx, header, lc.Odr()) } else { header := bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, state.NewDatabase(db)) + st, _ = state.New(header.Root, state.NewDatabase(db), nil) } var res []byte @@ -189,7 +189,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain } else { chain = bc header = bc.GetHeaderByHash(bhash) - st, _ = state.New(header.Root, state.NewDatabase(db)) + st, _ = state.New(header.Root, state.NewDatabase(db), nil) } // Perform read-only call. diff --git a/light/trie.go b/light/trie.go index e512bf6f95..0d69e74e21 100644 --- a/light/trie.go +++ b/light/trie.go @@ -30,7 +30,7 @@ import ( ) func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { - state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr)) + state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr), nil) return state } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 59ebcb6e1e..a10d044cd0 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -206,7 +206,7 @@ func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { sdb := state.NewDatabase(db) - statedb, _ := state.New(common.Hash{}, sdb) + statedb, _ := state.New(common.Hash{}, sdb, nil) for addr, a := range accounts { statedb.SetCode(addr, a.Code) statedb.SetNonce(addr, a.Nonce) @@ -217,7 +217,7 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB } // Commit and re-open to start with a clean state. root, _ := statedb.Commit(false) - statedb, _ = state.New(root, sdb) + statedb, _ = state.New(root, sdb, nil) return statedb } diff --git a/trie/iterator.go b/trie/iterator.go index bb4025d8f3..88189c5420 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -29,6 +29,7 @@ import ( type Iterator struct { nodeIt NodeIterator + Nodes int // Number of nodes iterated over Key []byte // Current data key on which the iterator is positioned on Value []byte // Current data value on which the iterator is positioned on Err error @@ -46,6 +47,7 @@ func NewIterator(it NodeIterator) *Iterator { // Next moves the iterator forward one key-value entry. func (it *Iterator) Next() bool { for it.nodeIt.Next(true) { + it.Nodes++ if it.nodeIt.Leaf() { it.Key = it.nodeIt.LeafKey() it.Value = it.nodeIt.LeafBlob() From e146fbe4e739e5912aadcceb77f9aff803b4a052 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 4 Oct 2019 15:24:01 +0200 Subject: [PATCH 002/103] core/state: lazy sorting, snapshot invalidation --- core/state/snapshot/difflayer.go | 177 ++++++---- core/state/snapshot/difflayer_test.go | 448 ++++++++++++++++++++++++++ core/state/snapshot/disklayer.go | 44 ++- core/state/snapshot/generate.go | 5 +- core/state/snapshot/snapshot.go | 17 +- core/state/snapshot/sort.go | 30 ++ core/state/state_object.go | 8 +- core/state/statedb.go | 34 +- 8 files changed, 671 insertions(+), 92 deletions(-) create mode 100644 core/state/snapshot/difflayer_test.go diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index f163feb561..c7a65e2a4b 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -40,13 +40,12 @@ type diffLayer struct { number uint64 // Block number to which this snapshot diff belongs to root common.Hash // Root hash to which this snapshot diff belongs to + stale bool // Signals that the layer became stale (state progressed) - accountList []common.Hash // List of account for iteration, might not be sorted yet (lazy) - accountSorted bool // Flag whether the account list has alreayd been sorted or not - accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) - storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account - storageSorted map[common.Hash]bool // Flag whether the storage slot list has alreayd been sorted or not - storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil + accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) + storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil + storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) lock sync.RWMutex } @@ -62,21 +61,13 @@ func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map accountData: accounts, storageData: storage, } - // Fill the account hashes and sort them for the iterator - accountList := make([]common.Hash, 0, len(accounts)) - for hash, data := range accounts { - accountList = append(accountList, hash) + // Determine mem size + for _, data := range accounts { dl.memory += uint64(len(data)) } - sort.Sort(hashes(accountList)) - dl.accountList = accountList - dl.accountSorted = true - - dl.memory += uint64(len(dl.accountList) * common.HashLength) // Fill the storage hashes and sort them for the iterator - dl.storageList = make(map[common.Hash][]common.Hash, len(storage)) - dl.storageSorted = make(map[common.Hash]bool, len(storage)) + dl.storageList = make(map[common.Hash][]common.Hash) for accountHash, slots := range storage { // If the slots are nil, sanity check that it's a deleted account @@ -93,19 +84,11 @@ func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map // account was just updated. if account, ok := accounts[accountHash]; account == nil || !ok { log.Error(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) - //panic(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) } - // Fill the storage hashes for this account and sort them for the iterator - storageList := make([]common.Hash, 0, len(slots)) - for storageHash, data := range slots { - storageList = append(storageList, storageHash) + // Determine mem size + for _, data := range slots { dl.memory += uint64(len(data)) } - sort.Sort(hashes(storageList)) - dl.storageList[accountHash] = storageList - dl.storageSorted[accountHash] = true - - dl.memory += uint64(len(storageList) * common.HashLength) } dl.memory += uint64(len(dl.storageList) * common.HashLength) @@ -119,28 +102,36 @@ func (dl *diffLayer) Info() (uint64, common.Hash) { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. -func (dl *diffLayer) Account(hash common.Hash) *Account { - data := dl.AccountRLP(hash) +func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { + data, err := dl.AccountRLP(hash) + if err != nil { + return nil, err + } if len(data) == 0 { // can be both nil and []byte{} - return nil + return nil, nil } account := new(Account) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } - return account + return account, nil } // AccountRLP directly retrieves the account RLP associated with a particular // hash in the snapshot slim data format. -func (dl *diffLayer) AccountRLP(hash common.Hash) []byte { +func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } // If the account is known locally, return it. Note, a nil account means it was // deleted, and is a different notion than an unknown account! if data, ok := dl.accountData[hash]; ok { - return data + return data, nil } // Account unknown to this diff, resolve from parent return dl.parent.AccountRLP(hash) @@ -149,18 +140,23 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) []byte { // Storage directly retrieves the storage data associated with a particular hash, // within a particular account. If the slot is unknown to this diff, it's parent // is consulted. -func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) []byte { +func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } // If the account is known locally, try to resolve the slot locally. Note, a nil // account means it was deleted, and is a different notion than an unknown account! if storage, ok := dl.storageData[accountHash]; ok { if storage == nil { - return nil + return nil, nil } if data, ok := storage[storageHash]; ok { - return data + return data, nil } } // Account - or slot within - unknown to this diff, resolve from parent @@ -193,13 +189,17 @@ func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { case *diskLayer: return parent.number, dl.number case *diffLayer: + // Flatten the parent into the grandparent. The flattening internally obtains a + // write lock on grandparent. + flattened := parent.flatten().(*diffLayer) + dl.lock.Lock() defer dl.lock.Unlock() - dl.parent = parent.flatten() - if dl.parent.(*diffLayer).memory < memory { - diskNumber, _ := parent.parent.Info() - return diskNumber, parent.number + dl.parent = flattened + if flattened.memory < memory { + diskNumber, _ := flattened.parent.Info() + return diskNumber, flattened.number } default: panic(fmt.Sprintf("unknown data layer: %T", parent)) @@ -213,10 +213,18 @@ func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { parent.lock.RLock() defer parent.lock.RUnlock() - // Start by temporarilly deleting the current snapshot block marker. This + // Start by temporarily deleting the current snapshot block marker. This // ensures that in the case of a crash, the entire snapshot is invalidated. rawdb.DeleteSnapshotBlock(batch) + // Mark the original base as stale as we're going to create a new wrapper + base.lock.Lock() + if base.stale { + panic("parent disk layer is stale") // we've committed into the same base from two children, boo + } + base.stale = true + base.lock.Unlock() + // Push all the accounts into the database for hash, data := range parent.accountData { if len(data) > 0 { @@ -264,15 +272,20 @@ func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { } } // Update the snapshot block marker and write any remainder data - base.number, base.root = parent.number, parent.root - - rawdb.WriteSnapshotBlock(batch, base.number, base.root) + newBase := &diskLayer{ + root: parent.root, + number: parent.number, + cache: base.cache, + db: base.db, + journal: base.journal, + } + rawdb.WriteSnapshotBlock(batch, newBase.number, newBase.root) if err := batch.Write(); err != nil { log.Crit("Failed to write leftover snapshot", "err", err) } - dl.parent = base + dl.parent = newBase - return base.number, dl.number + return newBase.number, dl.number } // flatten pushes all data from this point downwards, flattening everything into @@ -289,19 +302,25 @@ func (dl *diffLayer) flatten() snapshot { // be smarter about grouping flattens together). parent = parent.flatten().(*diffLayer) + parent.lock.Lock() + defer parent.lock.Unlock() + + // Before actually writing all our data to the parent, first ensure that the + // parent hasn't been 'corrupted' by someone else already flattening into it + if parent.stale { + panic("parent diff layer is stale") // we've flattened into the same parent from two children, boo + } + parent.stale = true + // Overwrite all the updated accounts blindly, merge the sorted list for hash, data := range dl.accountData { parent.accountData[hash] = data } - parent.accountList = append(parent.accountList, dl.accountList...) // TODO(karalabe): dedup!! - parent.accountSorted = false - // Overwrite all the updates storage slots (individually) for accountHash, storage := range dl.storageData { // If storage didn't exist (or was deleted) in the parent; or if the storage // was freshly deleted in the child, overwrite blindly if parent.storageData[accountHash] == nil || storage == nil { - parent.storageList[accountHash] = dl.storageList[accountHash] parent.storageData[accountHash] = storage continue } @@ -311,14 +330,18 @@ func (dl *diffLayer) flatten() snapshot { comboData[storageHash] = data } parent.storageData[accountHash] = comboData - parent.storageList[accountHash] = append(parent.storageList[accountHash], dl.storageList[accountHash]...) // TODO(karalabe): dedup!! - parent.storageSorted[accountHash] = false } // Return the combo parent - parent.number = dl.number - parent.root = dl.root - parent.memory += dl.memory - return parent + return &diffLayer{ + parent: parent.parent, + number: dl.number, + root: dl.root, + storageList: parent.storageList, + storageData: parent.storageData, + accountList: parent.accountList, + accountData: parent.accountData, + memory: parent.memory + dl.memory, + } } // Journal commits an entire diff hierarchy to disk into a single journal file. @@ -335,3 +358,45 @@ func (dl *diffLayer) Journal() error { writer.Close() return nil } + +// AccountList returns a sorted list of all accounts in this difflayer. +func (dl *diffLayer) AccountList() []common.Hash { + dl.lock.Lock() + defer dl.lock.Unlock() + if dl.accountList != nil { + return dl.accountList + } + accountList := make([]common.Hash, len(dl.accountData)) + i := 0 + for k, _ := range dl.accountData { + accountList[i] = k + i++ + // This would be a pretty good opportunity to also + // calculate the size, if we want to + } + sort.Sort(hashes(accountList)) + dl.accountList = accountList + return dl.accountList +} + +// StorageList returns a sorted list of all storage slot hashes +// in this difflayer for the given account. +func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { + dl.lock.Lock() + defer dl.lock.Unlock() + if dl.storageList[accountHash] != nil { + return dl.storageList[accountHash] + } + accountStorageMap := dl.storageData[accountHash] + accountStorageList := make([]common.Hash, len(accountStorageMap)) + i := 0 + for k, _ := range accountStorageMap { + accountStorageList[i] = k + i++ + // This would be a pretty good opportunity to also + // calculate the size, if we want to + } + sort.Sort(hashes(accountStorageList)) + dl.storageList[accountHash] = accountStorageList + return accountStorageList +} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go new file mode 100644 index 0000000000..5a718c6171 --- /dev/null +++ b/core/state/snapshot/difflayer_test.go @@ -0,0 +1,448 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "math/big" + "math/rand" + "testing" + "time" + + "github.com/allegro/bigcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/rlp" +) + +func randomAccount() []byte { + root := randomHash() + a := Account{ + Balance: big.NewInt(rand.Int63()), + Nonce: rand.Uint64(), + Root: root[:], + CodeHash: emptyCode[:], + } + data, _ := rlp.EncodeToBytes(a) + return data +} + +// TestMergeBasics tests some simple merges +func TestMergeBasics(t *testing.T) { + var ( + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + for i := 0; i < 100; i++ { + h := randomHash() + data := randomAccount() + + accounts[h] = data + if rand.Intn(20) < 10 { + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + storage[h] = accStorage + } + } + // Add some (identical) layers on top + parent := newDiffLayer(emptyLayer{}, 1, common.Hash{}, accounts, storage) + child := newDiffLayer(parent, 1, common.Hash{}, accounts, storage) + child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) + child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) + child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) + // And flatten + merged := (child.flatten()).(*diffLayer) + + { // Check account lists + // Should be zero/nil first + if got, exp := len(merged.accountList), 0; got != exp { + t.Errorf("accountList wrong, got %v exp %v", got, exp) + } + // Then set when we call AccountList + if got, exp := len(merged.AccountList()), len(accounts); got != exp { + t.Errorf("AccountList() wrong, got %v exp %v", got, exp) + } + if got, exp := len(merged.accountList), len(accounts); got != exp { + t.Errorf("accountList [2] wrong, got %v exp %v", got, exp) + } + } + { // Check storage lists + i := 0 + for aHash, sMap := range storage { + if got, exp := len(merged.storageList), i; got != exp { + t.Errorf("[1] storageList wrong, got %v exp %v", got, exp) + } + if got, exp := len(merged.StorageList(aHash)), len(sMap); got != exp { + t.Errorf("[2] StorageList() wrong, got %v exp %v", got, exp) + } + if got, exp := len(merged.storageList[aHash]), len(sMap); got != exp { + t.Errorf("storageList wrong, got %v exp %v", got, exp) + } + i++ + } + } +} + +// TestMergeDelete tests some deletion +func TestMergeDelete(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + h1 := common.HexToHash("0x01") + h2 := common.HexToHash("0x02") + + flip := func() map[common.Hash][]byte { + accs := make(map[common.Hash][]byte) + accs[h1] = randomAccount() + accs[h2] = nil + return accs + } + flop := func() map[common.Hash][]byte { + accs := make(map[common.Hash][]byte) + accs[h1] = nil + accs[h2] = randomAccount() + return accs + } + + // Add some flip-flopping layers on top + parent := newDiffLayer(emptyLayer{}, 1, common.Hash{}, flip(), storage) + child := parent.Update(common.Hash{}, flop(), storage) + child = child.Update(common.Hash{}, flip(), storage) + child = child.Update(common.Hash{}, flop(), storage) + child = child.Update(common.Hash{}, flip(), storage) + child = child.Update(common.Hash{}, flop(), storage) + child = child.Update(common.Hash{}, flip(), storage) + + if data, _ := child.Account(h1); data == nil { + t.Errorf("last diff layer: expected %x to be non-nil", h1) + } + if data, _ := child.Account(h2); data != nil { + t.Errorf("last diff layer: expected %x to be nil", h2) + } + // And flatten + merged := (child.flatten()).(*diffLayer) + + // check number + if got, exp := merged.number, child.number; got != exp { + t.Errorf("merged layer: wrong number - exp %d got %d", exp, got) + } + if data, _ := merged.Account(h1); data == nil { + t.Errorf("merged layer: expected %x to be non-nil", h1) + } + if data, _ := merged.Account(h2); data != nil { + t.Errorf("merged layer: expected %x to be nil", h2) + } + // If we add more granular metering of memory, we can enable this again, + // but it's not implemented for now + //if got, exp := merged.memory, child.memory; got != exp { + // t.Errorf("mem wrong, got %d, exp %d", got, exp) + //} +} + +// This tests that if we create a new account, and set a slot, and then merge +// it, the lists will be correct. +func TestInsertAndMerge(t *testing.T) { + // Fill up a parent + var ( + acc = common.HexToHash("0x01") + slot = common.HexToHash("0x02") + parent *diffLayer + child *diffLayer + ) + { + var accounts = make(map[common.Hash][]byte) + var storage = make(map[common.Hash]map[common.Hash][]byte) + parent = newDiffLayer(emptyLayer{}, 1, common.Hash{}, accounts, storage) + } + { + var accounts = make(map[common.Hash][]byte) + var storage = make(map[common.Hash]map[common.Hash][]byte) + accounts[acc] = randomAccount() + accstorage := make(map[common.Hash][]byte) + storage[acc] = accstorage + storage[acc][slot] = []byte{0x01} + child = newDiffLayer(parent, 2, common.Hash{}, accounts, storage) + } + // And flatten + merged := (child.flatten()).(*diffLayer) + { // Check that slot value is present + got, _ := merged.Storage(acc, slot) + if exp := []byte{0x01}; bytes.Compare(got, exp) != 0 { + t.Errorf("merged slot value wrong, got %x, exp %x", got, exp) + } + } +} + +// TestCapTree tests some functionality regarding capping/flattening +func TestCapTree(t *testing.T) { + + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + // the bottom-most layer, aside from the 'disk layer' + cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup + Shards: 1, + LifeWindow: time.Hour, + MaxEntriesInWindow: 1 * 1024, + MaxEntrySize: 1, + HardMaxCacheSize: 1, + }) + + base := &diskLayer{ + journal: "", + db: rawdb.NewMemoryDatabase(), + cache: cache, + number: 0, + root: common.HexToHash("0x01"), + } + // The lowest difflayer + a1 := base.Update(common.HexToHash("0xa1"), setAccount("0xa1"), storage) + + a2 := a1.Update(common.HexToHash("0xa2"), setAccount("0xa2"), storage) + b2 := a1.Update(common.HexToHash("0xb2"), setAccount("0xb2"), storage) + + a3 := a2.Update(common.HexToHash("0xa3"), setAccount("0xa3"), storage) + b3 := b2.Update(common.HexToHash("0xb3"), setAccount("0xb3"), storage) + + checkExist := func(layer *diffLayer, key string) error { + accountKey := common.HexToHash(key) + data, _ := layer.Account(accountKey) + if data == nil { + return fmt.Errorf("expected %x to exist, got nil", accountKey) + } + return nil + } + shouldErr := func(layer *diffLayer, key string) error { + accountKey := common.HexToHash(key) + data, err := layer.Account(accountKey) + if err == nil { + return fmt.Errorf("expected error, got data %x", data) + } + return nil + } + + // check basics + if err := checkExist(b3, "0xa1"); err != nil { + t.Error(err) + } + if err := checkExist(b3, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(b3, "0xb3"); err != nil { + t.Error(err) + } + // Now, merge the a-chain + diskNum, diffNum := a3.Cap(0, 1024) + if diskNum != 0 { + t.Errorf("disk layer err, got %d exp %d", diskNum, 0) + } + if diffNum != 2 { + t.Errorf("diff layer err, got %d exp %d", diffNum, 2) + } + // At this point, a2 got merged into a1. Thus, a1 is now modified, + // and as a1 is the parent of b2, b2 should no longer be able to iterate into parent + + // These should still be accessible + if err := checkExist(b3, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(b3, "0xb3"); err != nil { + t.Error(err) + } + //b2ParentNum, _ := b2.parent.Info() + //if b2.parent.invalid == false + // t.Errorf("err, exp parent to be invalid, got %v", b2.parent, b2ParentNum) + //} + // But these would need iteration into the modified parent: + if err := shouldErr(b3, "0xa1"); err != nil { + t.Error(err) + } + if err := shouldErr(b3, "0xa2"); err != nil { + t.Error(err) + } + if err := shouldErr(b3, "0xa3"); err != nil { + t.Error(err) + } +} + +type emptyLayer struct{} + +func (emptyLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + panic("implement me") +} + +func (emptyLayer) Cap(layers int, memory uint64) (uint64, uint64) { + panic("implement me") +} + +func (emptyLayer) Journal() error { + panic("implement me") +} + +func (emptyLayer) Info() (uint64, common.Hash) { + return 0, common.Hash{} +} +func (emptyLayer) Number() uint64 { + return 0 +} + +func (emptyLayer) Account(hash common.Hash) (*Account, error) { + return nil, nil +} + +func (emptyLayer) AccountRLP(hash common.Hash) ([]byte, error) { + return nil, nil +} + +func (emptyLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { + return nil, nil +} + +// BenchmarkSearch checks how long it takes to find a non-existing key +// BenchmarkSearch-6 200000 10481 ns/op (1K per layer) +// BenchmarkSearch-6 200000 10760 ns/op (10K per layer) +// BenchmarkSearch-6 100000 17866 ns/op +// +// BenchmarkSearch-6 500000 3723 ns/op (10k per layer, only top-level RLock() +func BenchmarkSearch(b *testing.B) { + // First, we set up 128 diff layers, with 1K items each + + blocknum := uint64(0) + fill := func(parent snapshot) *diffLayer { + accounts := make(map[common.Hash][]byte) + storage := make(map[common.Hash]map[common.Hash][]byte) + + for i := 0; i < 10000; i++ { + accounts[randomHash()] = randomAccount() + } + blocknum++ + return newDiffLayer(parent, blocknum, common.Hash{}, accounts, storage) + } + + var layer snapshot + layer = emptyLayer{} + for i := 0; i < 128; i++ { + layer = fill(layer) + } + + key := common.Hash{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + layer.AccountRLP(key) + } +} + +// BenchmarkSearchSlot checks how long it takes to find a non-existing key +// - Number of layers: 128 +// - Each layers contains the account, with a couple of storage slots +// BenchmarkSearchSlot-6 100000 14554 ns/op +// BenchmarkSearchSlot-6 100000 22254 ns/op (when checking parent root using mutex) +// BenchmarkSearchSlot-6 100000 14551 ns/op (when checking parent number using atomic) +func BenchmarkSearchSlot(b *testing.B) { + // First, we set up 128 diff layers, with 1K items each + + blocknum := uint64(0) + accountKey := common.Hash{} + storageKey := common.HexToHash("0x1337") + accountRLP := randomAccount() + fill := func(parent snapshot) *diffLayer { + accounts := make(map[common.Hash][]byte) + accounts[accountKey] = accountRLP + storage := make(map[common.Hash]map[common.Hash][]byte) + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 5; i++ { + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + storage[accountKey] = accStorage + } + blocknum++ + return newDiffLayer(parent, blocknum, common.Hash{}, accounts, storage) + } + + var layer snapshot + layer = emptyLayer{} + for i := 0; i < 128; i++ { + layer = fill(layer) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + layer.Storage(accountKey, storageKey) + } +} + +// With accountList and sorting +//BenchmarkFlatten-6 50 29890856 ns/op +// +// Without sorting and tracking accountlist +// BenchmarkFlatten-6 300 5511511 ns/op +func BenchmarkFlatten(b *testing.B) { + + fill := func(parent snapshot, blocknum int) *diffLayer { + accounts := make(map[common.Hash][]byte) + storage := make(map[common.Hash]map[common.Hash][]byte) + + for i := 0; i < 100; i++ { + accountKey := randomHash() + accounts[accountKey] = randomAccount() + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 20; i++ { + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + + } + storage[accountKey] = accStorage + } + return newDiffLayer(parent, uint64(blocknum), common.Hash{}, accounts, storage) + } + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + b.StopTimer() + var layer snapshot + layer = emptyLayer{} + for i := 1; i < 128; i++ { + layer = fill(layer, i) + } + b.StartTimer() + + for i := 1; i < 128; i++ { + dl, ok := layer.(*diffLayer) + if !ok { + break + } + + layer = dl.flatten() + } + b.StopTimer() + } +} diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 0406d298fb..a9839f01a8 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -17,6 +17,8 @@ package snapshot import ( + "sync" + "github.com/allegro/bigcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -32,6 +34,9 @@ type diskLayer struct { number uint64 // Block number of the base snapshot root common.Hash // Root hash of the base snapshot + stale bool // Signals that the layer became stale (state progressed) + + lock sync.RWMutex } // Info returns the block number and root hash for which this snapshot was made. @@ -41,28 +46,39 @@ func (dl *diskLayer) Info() (uint64, common.Hash) { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. -func (dl *diskLayer) Account(hash common.Hash) *Account { - data := dl.AccountRLP(hash) +func (dl *diskLayer) Account(hash common.Hash) (*Account, error) { + data, err := dl.AccountRLP(hash) + if err != nil { + return nil, err + } if len(data) == 0 { // can be both nil and []byte{} - return nil + return nil, nil } account := new(Account) if err := rlp.DecodeBytes(data, account); err != nil { panic(err) } - return account + return account, nil } // AccountRLP directly retrieves the account RLP associated with a particular // hash in the snapshot slim data format. -func (dl *diskLayer) AccountRLP(hash common.Hash) []byte { +func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } key := string(hash[:]) // Try to retrieve the account from the memory cache if blob, err := dl.cache.Get(key); err == nil { snapshotCleanHitMeter.Mark(1) snapshotCleanReadMeter.Mark(int64(len(blob))) - return blob + return blob, nil } // Cache doesn't contain account, pull from disk and cache for later blob := rawdb.ReadAccountSnapshot(dl.db, hash) @@ -71,19 +87,27 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) []byte { snapshotCleanMissMeter.Mark(1) snapshotCleanWriteMeter.Mark(int64(len(blob))) - return blob + return blob, nil } // Storage directly retrieves the storage data associated with a particular hash, // within a particular account. -func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) []byte { +func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { + dl.lock.RLock() + defer dl.lock.RUnlock() + + // If the layer was flattened into, consider it invalid (any live reference to + // the original should be marked as unusable). + if dl.stale { + return nil, ErrSnapshotStale + } key := string(append(accountHash[:], storageHash[:]...)) // Try to retrieve the storage slot from the memory cache if blob, err := dl.cache.Get(key); err == nil { snapshotCleanHitMeter.Mark(1) snapshotCleanReadMeter.Mark(int64(len(blob))) - return blob + return blob, nil } // Cache doesn't contain storage slot, pull from disk and cache for later blob := rawdb.ReadStorageSnapshot(dl.db, accountHash, storageHash) @@ -92,7 +116,7 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) []byte { snapshotCleanMissMeter.Mark(1) snapshotCleanWriteMeter.Mark(int64(len(blob))) - return blob + return blob, nil } // Update creates a new layer on top of the existing snapshot diff tree with diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 0d451fe50d..4a66e0626c 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -135,6 +135,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, curStorageNodes int curAccountSize common.StorageSize curStorageSize common.StorageSize + accountHash = common.BytesToHash(accIt.Key) ) var acc struct { Nonce uint64 @@ -148,7 +149,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, data := AccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) curAccountSize += common.StorageSize(1 + common.HashLength + len(data)) - rawdb.WriteAccountSnapshot(batch, common.BytesToHash(accIt.Key), data) + rawdb.WriteAccountSnapshot(batch, accountHash, data) if batch.ValueSize() > ethdb.IdealBatchSize { batch.Write() batch.Reset() @@ -163,7 +164,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, curStorageSize += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) curStorageCount++ - rawdb.WriteStorageSnapshot(batch, common.BytesToHash(accIt.Key), common.BytesToHash(storeIt.Key), storeIt.Value) + rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value) if batch.ValueSize() > ethdb.IdealBatchSize { batch.Write() batch.Reset() diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 6d4df96da8..6a21d57dcb 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -38,6 +38,11 @@ var ( snapshotCleanMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/miss", nil) snapshotCleanReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/read", nil) snapshotCleanWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/write", nil) + + // ErrSnapshotStale is returned from data accessors if the underlying snapshot + // layer had been invalidated due to the chain progressing forward far enough + // to not maintain the layer's original state. + ErrSnapshotStale = errors.New("snapshot stale") ) // Snapshot represents the functionality supported by a snapshot storage layer. @@ -47,15 +52,15 @@ type Snapshot interface { // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. - Account(hash common.Hash) *Account + Account(hash common.Hash) (*Account, error) // AccountRLP directly retrieves the account RLP associated with a particular // hash in the snapshot slim data format. - AccountRLP(hash common.Hash) []byte + AccountRLP(hash common.Hash) ([]byte, error) // Storage directly retrieves the storage data associated with a particular hash, // within a particular account. - Storage(accountHash, storageHash common.Hash) []byte + Storage(accountHash, storageHash common.Hash) ([]byte, error) } // snapshot is the internal version of the snapshot data layer that supports some @@ -80,7 +85,7 @@ type snapshot interface { } // SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent -// base layer backed by a key-value store, on top of which arbitrarilly many in- +// base layer backed by a key-value store, on top of which arbitrarily many in- // memory diff layers are topped. The memory diffs can form a tree with branching, // but the disk layer is singleton and common to all. If a reorg goes deeper than // the disk layer, everything needs to be deleted. @@ -220,7 +225,7 @@ func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, hea if _, err := os.Stat(journal); os.IsNotExist(err) { // Journal doesn't exist, don't worry if it's not supposed to if number != headNumber || root != headRoot { - return nil, fmt.Errorf("snapshot journal missing, head does't match snapshot: #%d [%#x] vs. #%d [%#x]", + return nil, fmt.Errorf("snapshot journal missing, head doesn't match snapshot: #%d [%#x] vs. #%d [%#x]", headNumber, headRoot, number, root) } return base, nil @@ -237,7 +242,7 @@ func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, hea // Journal doesn't exist, don't worry if it's not supposed to number, root = snapshot.Info() if number != headNumber || root != headRoot { - return nil, fmt.Errorf("head does't match snapshot: #%d [%#x] vs. #%d [%#x]", + return nil, fmt.Errorf("head doesn't match snapshot: #%d [%#x] vs. #%d [%#x]", headNumber, headRoot, number, root) } return snapshot, nil diff --git a/core/state/snapshot/sort.go b/core/state/snapshot/sort.go index 04729c60b2..ee7cc4990e 100644 --- a/core/state/snapshot/sort.go +++ b/core/state/snapshot/sort.go @@ -60,3 +60,33 @@ func merge(a, b []common.Hash) []common.Hash { } return result } + +// dedupMerge combines two sorted lists of hashes into a combo sorted one, +// and removes duplicates in the process +func dedupMerge(a, b []common.Hash) []common.Hash { + result := make([]common.Hash, len(a)+len(b)) + i := 0 + for len(a) > 0 && len(b) > 0 { + if diff := bytes.Compare(a[0][:], b[0][:]); diff < 0 { + result[i] = a[0] + a = a[1:] + } else { + result[i] = b[0] + b = b[1:] + // If they were equal, progress a too + if diff == 0 { + a = a[1:] + } + } + i++ + } + for j := 0; j < len(a); j++ { + result[i] = a[j] + i++ + } + for j := 0; j < len(b); j++ { + result[i] = b[j] + i++ + } + return result[:i] +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 98be566716..d10caa8319 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -204,13 +204,13 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if metrics.EnabledExpensive { defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) } - enc = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) - } else { - // Track the amount of time wasted on reading the storage trie + enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) + } + // If snapshot unavailable or reading from it failed, load from the database + if s.db.snap == nil || err != nil { if metrics.EnabledExpensive { defer func(start time.Time) { s.db.StorageReads += time.Since(start) }(time.Now()) } - // Otherwise load the value from the database if enc, err = s.getTrie(db).TryGet(key[:]); err != nil { s.setError(err) return common.Hash{} diff --git a/core/state/statedb.go b/core/state/statedb.go index 0fb1095ce2..7d7499892f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -511,25 +511,31 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { return obj } // If no live objects are available, attempt to use snapshots - var data Account + var ( + data Account + err error + ) if s.snap != nil { if metrics.EnabledExpensive { defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now()) } - acc := s.snap.Account(crypto.Keccak256Hash(addr[:])) - if acc == nil { - return nil + var acc *snapshot.Account + if acc, err = s.snap.Account(crypto.Keccak256Hash(addr[:])); err == nil { + if acc == nil { + return nil + } + data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash + if len(data.CodeHash) == 0 { + data.CodeHash = emptyCodeHash + } + data.Root = common.BytesToHash(acc.Root) + if data.Root == (common.Hash{}) { + data.Root = emptyRoot + } } - data.Nonce, data.Balance, data.CodeHash = acc.Nonce, acc.Balance, acc.CodeHash - if len(data.CodeHash) == 0 { - data.CodeHash = emptyCodeHash - } - data.Root = common.BytesToHash(acc.Root) - if data.Root == (common.Hash{}) { - data.Root = emptyRoot - } - } else { - // Snapshot unavailable, fall back to the trie + } + // If snapshot unavailable or reading from it failed, load from the database + if s.snap == nil || err != nil { if metrics.EnabledExpensive { defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now()) } From d7d81d7c1255d613400e833d634579129c73f8de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 17 Oct 2019 18:30:31 +0300 Subject: [PATCH 003/103] core/state/snapshot: extract and split cap method, cover corners --- core/state/snapshot/difflayer.go | 121 ----------- core/state/snapshot/difflayer_test.go | 106 ---------- core/state/snapshot/disklayer.go | 6 - core/state/snapshot/snapshot.go | 181 +++++++++++++++- core/state/snapshot/snapshot_test.go | 286 ++++++++++++++++++++++++++ 5 files changed, 461 insertions(+), 239 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index c7a65e2a4b..0f7a4223fb 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -22,8 +22,6 @@ import ( "sync" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" ) @@ -169,125 +167,6 @@ func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]by return newDiffLayer(dl, dl.number+1, blockRoot, accounts, storage) } -// Cap traverses downwards the diff tree until the number of allowed layers are -// crossed. All diffs beyond the permitted number are flattened downwards. If -// the layer limit is reached, memory cap is also enforced (but not before). The -// block numbers for the disk layer and first diff layer are returned for GC. -func (dl *diffLayer) Cap(layers int, memory uint64) (uint64, uint64) { - // Dive until we run out of layers or reach the persistent database - if layers > 2 { - // If we still have diff layers below, recurse - if parent, ok := dl.parent.(*diffLayer); ok { - return parent.Cap(layers-1, memory) - } - // Diff stack too shallow, return block numbers without modifications - return dl.parent.(*diskLayer).number, dl.number - } - // We're out of layers, flatten anything below, stopping if it's the disk or if - // the memory limit is not yet exceeded. - switch parent := dl.parent.(type) { - case *diskLayer: - return parent.number, dl.number - case *diffLayer: - // Flatten the parent into the grandparent. The flattening internally obtains a - // write lock on grandparent. - flattened := parent.flatten().(*diffLayer) - - dl.lock.Lock() - defer dl.lock.Unlock() - - dl.parent = flattened - if flattened.memory < memory { - diskNumber, _ := flattened.parent.Info() - return diskNumber, flattened.number - } - default: - panic(fmt.Sprintf("unknown data layer: %T", parent)) - } - // If the bottommost layer is larger than our memory cap, persist to disk - var ( - parent = dl.parent.(*diffLayer) - base = parent.parent.(*diskLayer) - batch = base.db.NewBatch() - ) - parent.lock.RLock() - defer parent.lock.RUnlock() - - // Start by temporarily deleting the current snapshot block marker. This - // ensures that in the case of a crash, the entire snapshot is invalidated. - rawdb.DeleteSnapshotBlock(batch) - - // Mark the original base as stale as we're going to create a new wrapper - base.lock.Lock() - if base.stale { - panic("parent disk layer is stale") // we've committed into the same base from two children, boo - } - base.stale = true - base.lock.Unlock() - - // Push all the accounts into the database - for hash, data := range parent.accountData { - if len(data) > 0 { - // Account was updated, push to disk - rawdb.WriteAccountSnapshot(batch, hash, data) - base.cache.Set(string(hash[:]), data) - - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write account snapshot", "err", err) - } - batch.Reset() - } - } else { - // Account was deleted, remove all storage slots too - rawdb.DeleteAccountSnapshot(batch, hash) - base.cache.Set(string(hash[:]), nil) - - it := rawdb.IterateStorageSnapshots(base.db, hash) - for it.Next() { - if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator - batch.Delete(key) - base.cache.Delete(string(key[1:])) - } - } - it.Release() - } - } - // Push all the storage slots into the database - for accountHash, storage := range parent.storageData { - for storageHash, data := range storage { - if len(data) > 0 { - rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) - base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) - } else { - rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) - base.cache.Set(string(append(accountHash[:], storageHash[:]...)), nil) - } - } - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write storage snapshot", "err", err) - } - batch.Reset() - } - } - // Update the snapshot block marker and write any remainder data - newBase := &diskLayer{ - root: parent.root, - number: parent.number, - cache: base.cache, - db: base.db, - journal: base.journal, - } - rawdb.WriteSnapshotBlock(batch, newBase.number, newBase.root) - if err := batch.Write(); err != nil { - log.Crit("Failed to write leftover snapshot", "err", err) - } - dl.parent = newBase - - return newBase.number, dl.number -} - // flatten pushes all data from this point downwards, flattening everything into // a single diff at the bottom. Since usually the lowermost diff is the largest, // the flattening bulds up from there in reverse. diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 5a718c6171..5499f20161 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -18,15 +18,11 @@ package snapshot import ( "bytes" - "fmt" "math/big" "math/rand" "testing" - "time" - "github.com/allegro/bigcache" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/rlp" ) @@ -192,113 +188,12 @@ func TestInsertAndMerge(t *testing.T) { } } -// TestCapTree tests some functionality regarding capping/flattening -func TestCapTree(t *testing.T) { - - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - setAccount := func(accKey string) map[common.Hash][]byte { - return map[common.Hash][]byte{ - common.HexToHash(accKey): randomAccount(), - } - } - // the bottom-most layer, aside from the 'disk layer' - cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup - Shards: 1, - LifeWindow: time.Hour, - MaxEntriesInWindow: 1 * 1024, - MaxEntrySize: 1, - HardMaxCacheSize: 1, - }) - - base := &diskLayer{ - journal: "", - db: rawdb.NewMemoryDatabase(), - cache: cache, - number: 0, - root: common.HexToHash("0x01"), - } - // The lowest difflayer - a1 := base.Update(common.HexToHash("0xa1"), setAccount("0xa1"), storage) - - a2 := a1.Update(common.HexToHash("0xa2"), setAccount("0xa2"), storage) - b2 := a1.Update(common.HexToHash("0xb2"), setAccount("0xb2"), storage) - - a3 := a2.Update(common.HexToHash("0xa3"), setAccount("0xa3"), storage) - b3 := b2.Update(common.HexToHash("0xb3"), setAccount("0xb3"), storage) - - checkExist := func(layer *diffLayer, key string) error { - accountKey := common.HexToHash(key) - data, _ := layer.Account(accountKey) - if data == nil { - return fmt.Errorf("expected %x to exist, got nil", accountKey) - } - return nil - } - shouldErr := func(layer *diffLayer, key string) error { - accountKey := common.HexToHash(key) - data, err := layer.Account(accountKey) - if err == nil { - return fmt.Errorf("expected error, got data %x", data) - } - return nil - } - - // check basics - if err := checkExist(b3, "0xa1"); err != nil { - t.Error(err) - } - if err := checkExist(b3, "0xb2"); err != nil { - t.Error(err) - } - if err := checkExist(b3, "0xb3"); err != nil { - t.Error(err) - } - // Now, merge the a-chain - diskNum, diffNum := a3.Cap(0, 1024) - if diskNum != 0 { - t.Errorf("disk layer err, got %d exp %d", diskNum, 0) - } - if diffNum != 2 { - t.Errorf("diff layer err, got %d exp %d", diffNum, 2) - } - // At this point, a2 got merged into a1. Thus, a1 is now modified, - // and as a1 is the parent of b2, b2 should no longer be able to iterate into parent - - // These should still be accessible - if err := checkExist(b3, "0xb2"); err != nil { - t.Error(err) - } - if err := checkExist(b3, "0xb3"); err != nil { - t.Error(err) - } - //b2ParentNum, _ := b2.parent.Info() - //if b2.parent.invalid == false - // t.Errorf("err, exp parent to be invalid, got %v", b2.parent, b2ParentNum) - //} - // But these would need iteration into the modified parent: - if err := shouldErr(b3, "0xa1"); err != nil { - t.Error(err) - } - if err := shouldErr(b3, "0xa2"); err != nil { - t.Error(err) - } - if err := shouldErr(b3, "0xa3"); err != nil { - t.Error(err) - } -} - type emptyLayer struct{} func (emptyLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { panic("implement me") } -func (emptyLayer) Cap(layers int, memory uint64) (uint64, uint64) { - panic("implement me") -} - func (emptyLayer) Journal() error { panic("implement me") } @@ -403,7 +298,6 @@ func BenchmarkSearchSlot(b *testing.B) { // Without sorting and tracking accountlist // BenchmarkFlatten-6 300 5511511 ns/op func BenchmarkFlatten(b *testing.B) { - fill := func(parent snapshot, blocknum int) *diffLayer { accounts := make(map[common.Hash][]byte) storage := make(map[common.Hash]map[common.Hash][]byte) diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index a9839f01a8..50321f1540 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -126,12 +126,6 @@ func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]by return newDiffLayer(dl, dl.number+1, blockHash, accounts, storage) } -// Cap traverses downwards the diff tree until the number of allowed layers are -// crossed. All diffs beyond the permitted number are flattened downwards. -func (dl *diskLayer) Cap(layers int, memory uint64) (uint64, uint64) { - return dl.number, dl.number -} - // Journal commits an entire diff hierarchy to disk into a single journal file. func (dl *diskLayer) Journal() error { // There's no journalling a disk layer diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 6a21d57dcb..a181789779 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -73,11 +73,6 @@ type snapshot interface { // copying everything. Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer - // Cap traverses downwards the diff tree until the number of allowed layers are - // crossed. All diffs beyond the permitted number are flattened downwards. The - // block numbers for the disk layer and first diff layer are returned for GC. - Cap(layers int, memory uint64) (uint64, uint64) - // Journal commits an entire diff hierarchy to disk into a single journal file. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). @@ -169,11 +164,56 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er if snap == nil { return fmt.Errorf("snapshot [%#x] missing", blockRoot) } + diff, ok := snap.(*diffLayer) + if !ok { + return fmt.Errorf("snapshot [%#x] is base layer", blockRoot) + } // Run the internal capping and discard all stale layers st.lock.Lock() defer st.lock.Unlock() - diskNumber, diffNumber := snap.Cap(layers, memory) + var ( + diskNumber uint64 + diffNumber uint64 + ) + // Flattening the bottom-most diff layer requires special casing since there's + // no child to rewire to the grandparent. In that case we can fake a temporary + // child for the capping and then remove it. + switch layers { + case 0: + // If full commit was requested, flatten the diffs and merge onto disk + diff.lock.RLock() + base := diffToDisk(diff.flatten().(*diffLayer)) + diff.lock.RUnlock() + + st.layers[base.root] = base + diskNumber, diffNumber = base.number, base.number + + case 1: + // If full flattening was requested, flatten the diffs but only merge if the + // memory limit was reached + var ( + bottom *diffLayer + base *diskLayer + ) + diff.lock.RLock() + bottom = diff.flatten().(*diffLayer) + if bottom.memory >= memory { + base = diffToDisk(bottom) + } + diff.lock.RUnlock() + + if base != nil { + st.layers[base.root] = base + diskNumber, diffNumber = base.number, base.number + } else { + st.layers[bottom.root] = bottom + diskNumber, diffNumber = bottom.parent.(*diskLayer).number, bottom.number + } + + default: + diskNumber, diffNumber = st.cap(diff, layers, memory) + } for root, snap := range st.layers { if number, _ := snap.Info(); number != diskNumber && number < diffNumber { delete(st.layers, root) @@ -182,6 +222,135 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er return nil } +// cap traverses downwards the diff tree until the number of allowed layers are +// crossed. All diffs beyond the permitted number are flattened downwards. If +// the layer limit is reached, memory cap is also enforced (but not before). The +// block numbers for the disk layer and first diff layer are returned for GC. +func (st *SnapshotTree) cap(diff *diffLayer, layers int, memory uint64) (uint64, uint64) { + // Dive until we run out of layers or reach the persistent database + if layers > 2 { + // If we still have diff layers below, recurse + if parent, ok := diff.parent.(*diffLayer); ok { + return st.cap(parent, layers-1, memory) + } + // Diff stack too shallow, return block numbers without modifications + return diff.parent.(*diskLayer).number, diff.number + } + // We're out of layers, flatten anything below, stopping if it's the disk or if + // the memory limit is not yet exceeded. + switch parent := diff.parent.(type) { + case *diskLayer: + return parent.number, diff.number + + case *diffLayer: + // Flatten the parent into the grandparent. The flattening internally obtains a + // write lock on grandparent. + flattened := parent.flatten().(*diffLayer) + st.layers[flattened.root] = flattened + + diff.lock.Lock() + defer diff.lock.Unlock() + + diff.parent = flattened + if flattened.memory < memory { + diskNumber, _ := flattened.parent.Info() + return diskNumber, flattened.number + } + default: + panic(fmt.Sprintf("unknown data layer: %T", parent)) + } + // If the bottom-most layer is larger than our memory cap, persist to disk + bottom := diff.parent.(*diffLayer) + + bottom.lock.RLock() + base := diffToDisk(bottom) + bottom.lock.RUnlock() + + st.layers[base.root] = base + diff.parent = base + + return base.number, diff.number +} + +// diffToDisk merges a bottom-most diff into the persistent disk layer underneath +// it. The method will panic if called onto a non-bottom-most diff layer. +func diffToDisk(bottom *diffLayer) *diskLayer { + var ( + base = bottom.parent.(*diskLayer) + batch = base.db.NewBatch() + ) + // Start by temporarily deleting the current snapshot block marker. This + // ensures that in the case of a crash, the entire snapshot is invalidated. + rawdb.DeleteSnapshotBlock(batch) + + // Mark the original base as stale as we're going to create a new wrapper + base.lock.Lock() + if base.stale { + panic("parent disk layer is stale") // we've committed into the same base from two children, boo + } + base.stale = true + base.lock.Unlock() + + // Push all the accounts into the database + for hash, data := range bottom.accountData { + if len(data) > 0 { + // Account was updated, push to disk + rawdb.WriteAccountSnapshot(batch, hash, data) + base.cache.Set(string(hash[:]), data) + + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write account snapshot", "err", err) + } + batch.Reset() + } + } else { + // Account was deleted, remove all storage slots too + rawdb.DeleteAccountSnapshot(batch, hash) + base.cache.Set(string(hash[:]), nil) + + it := rawdb.IterateStorageSnapshots(base.db, hash) + for it.Next() { + if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator + batch.Delete(key) + base.cache.Delete(string(key[1:])) + } + } + it.Release() + } + } + // Push all the storage slots into the database + for accountHash, storage := range bottom.storageData { + for storageHash, data := range storage { + if len(data) > 0 { + rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) + } else { + rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) + base.cache.Set(string(append(accountHash[:], storageHash[:]...)), nil) + } + } + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write storage snapshot", "err", err) + } + batch.Reset() + } + } + // Update the snapshot block marker and write any remainder data + rawdb.WriteSnapshotBlock(batch, bottom.number, bottom.root) + if err := batch.Write(); err != nil { + log.Crit("Failed to write leftover snapshot", "err", err) + } + return &diskLayer{ + root: bottom.root, + number: bottom.number, + cache: base.cache, + db: base.db, + journal: base.journal, + } +} + // Journal commits an entire diff hierarchy to disk into a single journal file. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 903bd4a6f6..ecd39bf3e8 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -15,3 +15,289 @@ // along with the go-ethereum library. If not, see . package snapshot + +import ( + "fmt" + "testing" + "time" + + "github.com/allegro/bigcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" +) + +// Tests that if a disk layer becomes stale, no active external references will +// be returned with junk data. This version of the test flattens every diff layer +// to check internal corner case around the bottom-most memory accumulator. +func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + base := &diskLayer{ + db: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: cache, + } + snaps := &SnapshotTree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Retrieve a reference to the base and commit a diff on top + ref := snaps.Snapshot(base.root) + + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + storage := make(map[common.Hash]map[common.Hash][]byte) + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 2 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) + } + // Commit the diff layer onto the disk and ensure it's persisted + if err := snaps.Cap(common.HexToHash("0x02"), 0, 0); err != nil { + t.Fatalf("failed to merge diff layer onto disk: %v", err) + } + // Since the base layer was modified, ensure that data retrievald on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 1 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 1) + fmt.Println(snaps.layers) + } +} + +// Tests that if a disk layer becomes stale, no active external references will +// be returned with junk data. This version of the test retains the bottom diff +// layer to check the usual mode of operation where the accumulator is retained. +func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + base := &diskLayer{ + db: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: cache, + } + snaps := &SnapshotTree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Retrieve a reference to the base and commit two diffs on top + ref := snaps.Snapshot(base.root) + + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + storage := make(map[common.Hash]map[common.Hash][]byte) + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 3 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) + } + // Commit the diff layer onto the disk and ensure it's persisted + if err := snaps.Cap(common.HexToHash("0x03"), 2, 0); err != nil { + t.Fatalf("failed to merge diff layer onto disk: %v", err) + } + // Since the base layer was modified, ensure that data retrievald on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 2 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) + fmt.Println(snaps.layers) + } +} + +// Tests that if a diff layer becomes stale, no active external references will +// be returned with junk data. This version of the test flattens every diff layer +// to check internal corner case around the bottom-most memory accumulator. +func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + base := &diskLayer{ + db: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: cache, + } + snaps := &SnapshotTree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Commit two diffs on top and retrieve a reference to the bottommost + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + storage := make(map[common.Hash]map[common.Hash][]byte) + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 3 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) + } + ref := snaps.Snapshot(common.HexToHash("0x02")) + + // Flatten the diff layer into the bottom accumulator + if err := snaps.Cap(common.HexToHash("0x03"), 1, 1024*1024); err != nil { + t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + } + // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 2 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 2) + fmt.Println(snaps.layers) + } +} + +// Tests that if a diff layer becomes stale, no active external references will +// be returned with junk data. This version of the test retains the bottom diff +// layer to check the usual mode of operation where the accumulator is retained. +func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + base := &diskLayer{ + db: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: cache, + } + snaps := &SnapshotTree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Commit three diffs on top and retrieve a reference to the bottommost + accounts := map[common.Hash][]byte{ + common.HexToHash("0xa1"): randomAccount(), + } + storage := make(map[common.Hash]map[common.Hash][]byte) + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), accounts, storage); err != nil { + t.Fatalf("failed to create a diff layer: %v", err) + } + if n := len(snaps.layers); n != 4 { + t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 4) + } + ref := snaps.Snapshot(common.HexToHash("0x02")) + + // Flatten the diff layer into the bottom accumulator + if err := snaps.Cap(common.HexToHash("0x04"), 2, 1024*1024); err != nil { + t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + } + // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail + if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { + t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) + } + if slot, err := ref.Storage(common.HexToHash("0xa1"), common.HexToHash("0xb1")); err != ErrSnapshotStale { + t.Errorf("stale reference returned storage slot: %#x (err: %v)", slot, err) + } + if n := len(snaps.layers); n != 3 { + t.Errorf("post-cap layer count mismatch: have %d, want %d", n, 3) + fmt.Println(snaps.layers) + } +} + +// TestPostCapBasicDataAccess tests some functionality regarding capping/flattening. +func TestPostCapBasicDataAccess(t *testing.T) { + // setAccount is a helper to construct a random account entry and assign it to + // an account slot in a snapshot + setAccount := func(accKey string) map[common.Hash][]byte { + return map[common.Hash][]byte{ + common.HexToHash(accKey): randomAccount(), + } + } + // Create a starting base layer and a snapshot tree out of it + cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) + base := &diskLayer{ + db: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: cache, + } + snaps := &SnapshotTree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // The lowest difflayer + snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), setAccount("0xa1"), nil) + snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), setAccount("0xa2"), nil) + snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), setAccount("0xb2"), nil) + + snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), setAccount("0xa3"), nil) + snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), setAccount("0xb3"), nil) + + // checkExist verifies if an account exiss in a snapshot + checkExist := func(layer *diffLayer, key string) error { + if data, _ := layer.Account(common.HexToHash(key)); data == nil { + return fmt.Errorf("expected %x to exist, got nil", common.HexToHash(key)) + } + return nil + } + // shouldErr checks that an account access errors as expected + shouldErr := func(layer *diffLayer, key string) error { + if data, err := layer.Account(common.HexToHash(key)); err == nil { + return fmt.Errorf("expected error, got data %x", data) + } + return nil + } + // check basics + snap := snaps.Snapshot(common.HexToHash("0xb3")).(*diffLayer) + + if err := checkExist(snap, "0xa1"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb3"); err != nil { + t.Error(err) + } + // Now, merge the a-chain + snaps.Cap(common.HexToHash("0xa3"), 0, 1024) + + // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is + // the parent of b2, b2 should no longer be able to iterate into parent. + + // These should still be accessible + if err := checkExist(snap, "0xb2"); err != nil { + t.Error(err) + } + if err := checkExist(snap, "0xb3"); err != nil { + t.Error(err) + } + // But these would need iteration into the modified parent + if err := shouldErr(snap, "0xa1"); err != nil { + t.Error(err) + } + if err := shouldErr(snap, "0xa2"); err != nil { + t.Error(err) + } + if err := shouldErr(snap, "0xa3"); err != nil { + t.Error(err) + } +} From cdf3f016dfc19b0f5a6471f2b649519b7256d68d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 23 Oct 2019 15:19:02 +0200 Subject: [PATCH 004/103] snapshot: iteration and buffering optimizations --- core/state/snapshot/difflayer.go | 3 -- core/state/snapshot/difflayer_journal.go | 22 +++++++++--- core/state/snapshot/difflayer_test.go | 45 ++++++++++++++++++++++++ core/state/snapshot/snapshot.go | 25 +++++++------ core/state/snapshot/snapshot_test.go | 18 ++++++++++ 5 files changed, 96 insertions(+), 17 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0f7a4223fb..644c8fb4be 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -227,9 +227,6 @@ func (dl *diffLayer) flatten() snapshot { // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). func (dl *diffLayer) Journal() error { - dl.lock.RLock() - defer dl.lock.RUnlock() - writer, err := dl.journal() if err != nil { return err diff --git a/core/state/snapshot/difflayer_journal.go b/core/state/snapshot/difflayer_journal.go index 844ee88592..16fdb8a973 100644 --- a/core/state/snapshot/difflayer_journal.go +++ b/core/state/snapshot/difflayer_journal.go @@ -17,6 +17,7 @@ package snapshot import ( + "bufio" "fmt" "io" "os" @@ -105,12 +106,22 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { } writer = file } + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + writer.Close() + return nil, ErrSnapshotStale + } + buf := bufio.NewWriter(writer) // Everything below was journalled, persist this layer too - if err := rlp.Encode(writer, dl.number); err != nil { + if err := rlp.Encode(buf, dl.number); err != nil { + buf.Flush() writer.Close() return nil, err } - if err := rlp.Encode(writer, dl.root); err != nil { + if err := rlp.Encode(buf, dl.root); err != nil { + buf.Flush() writer.Close() return nil, err } @@ -118,7 +129,8 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { for hash, blob := range dl.accountData { accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) } - if err := rlp.Encode(writer, accounts); err != nil { + if err := rlp.Encode(buf, accounts); err != nil { + buf.Flush() writer.Close() return nil, err } @@ -132,9 +144,11 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { } storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) } - if err := rlp.Encode(writer, storage); err != nil { + if err := rlp.Encode(buf, storage); err != nil { + buf.Flush() writer.Close() return nil, err } + buf.Flush() return writer, nil } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 5499f20161..5b79073012 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -20,6 +20,8 @@ import ( "bytes" "math/big" "math/rand" + "os" + "path" "testing" "github.com/ethereum/go-ethereum/common" @@ -340,3 +342,46 @@ func BenchmarkFlatten(b *testing.B) { b.StopTimer() } } + +// This test writes ~324M of diff layers to disk, spread over +// - 128 individual layers, +// - each with 200 accounts +// - containing 200 slots +// +// BenchmarkJournal-6 1 1471373923 ns/ops +// BenchmarkJournal-6 1 1208083335 ns/op // bufio writer +func BenchmarkJournal(b *testing.B) { + fill := func(parent snapshot, blocknum int) *diffLayer { + accounts := make(map[common.Hash][]byte) + storage := make(map[common.Hash]map[common.Hash][]byte) + + for i := 0; i < 200; i++ { + accountKey := randomHash() + accounts[accountKey] = randomAccount() + + accStorage := make(map[common.Hash][]byte) + for i := 0; i < 200; i++ { + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + + } + storage[accountKey] = accStorage + } + return newDiffLayer(parent, uint64(blocknum), common.Hash{}, accounts, storage) + } + + var layer snapshot + layer = &diskLayer{ + journal: path.Join(os.TempDir(), "difflayer_journal.tmp"), + } + for i := 1; i < 128; i++ { + layer = fill(layer, i) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + f, _ := layer.(*diffLayer).journal() + f.Close() + } +} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index a181789779..668522feca 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -160,13 +160,15 @@ func (st *SnapshotTree) Update(blockRoot common.Hash, parentRoot common.Hash, ac // are flattened downwards. func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) error { // Retrieve the head snapshot to cap from - snap := st.Snapshot(blockRoot).(snapshot) - if snap == nil { + var snap snapshot + if s := st.Snapshot(blockRoot); s == nil { return fmt.Errorf("snapshot [%#x] missing", blockRoot) + } else { + snap = s.(snapshot) } diff, ok := snap.(*diffLayer) if !ok { - return fmt.Errorf("snapshot [%#x] is base layer", blockRoot) + return fmt.Errorf("snapshot [%#x] is disk layer", blockRoot) } // Run the internal capping and discard all stale layers st.lock.Lock() @@ -228,13 +230,14 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er // block numbers for the disk layer and first diff layer are returned for GC. func (st *SnapshotTree) cap(diff *diffLayer, layers int, memory uint64) (uint64, uint64) { // Dive until we run out of layers or reach the persistent database - if layers > 2 { - // If we still have diff layers below, recurse + for ; layers > 2; layers-- { + // If we still have diff layers below, continue down if parent, ok := diff.parent.(*diffLayer); ok { - return st.cap(parent, layers-1, memory) + diff = parent + } else { + // Diff stack too shallow, return block numbers without modifications + return diff.parent.(*diskLayer).number, diff.number } - // Diff stack too shallow, return block numbers without modifications - return diff.parent.(*diskLayer).number, diff.number } // We're out of layers, flatten anything below, stopping if it's the disk or if // the memory limit is not yet exceeded. @@ -356,9 +359,11 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // flattening everything down (bad for reorgs). func (st *SnapshotTree) Journal(blockRoot common.Hash) error { // Retrieve the head snapshot to journal from - snap := st.Snapshot(blockRoot).(snapshot) - if snap == nil { + var snap snapshot + if s := st.Snapshot(blockRoot); s == nil { return fmt.Errorf("snapshot [%#x] missing", blockRoot) + } else { + snap = s.(snapshot) } // Run the journaling st.lock.Lock() diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index ecd39bf3e8..1551a71a2f 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -205,6 +205,15 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { } ref := snaps.Snapshot(common.HexToHash("0x02")) + // Doing a Cap operation with many allowed layers should be a no-op + exp := len(snaps.layers) + if err := snaps.Cap(common.HexToHash("0x04"), 2000, 1024*1024); err != nil { + t.Fatalf("failed to flatten diff layer into accumulator: %v", err) + } + if got := len(snaps.layers); got != exp { + t.Errorf("layers modified, got %d exp %d", got, exp) + } + // Flatten the diff layer into the bottom accumulator if err := snaps.Cap(common.HexToHash("0x04"), 2, 1024*1024); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) @@ -277,6 +286,10 @@ func TestPostCapBasicDataAccess(t *testing.T) { if err := checkExist(snap, "0xb3"); err != nil { t.Error(err) } + // Cap to a bad root should fail + if err := snaps.Cap(common.HexToHash("0x1337"), 0, 1024); err == nil { + t.Errorf("expected error, got none") + } // Now, merge the a-chain snaps.Cap(common.HexToHash("0xa3"), 0, 1024) @@ -300,4 +313,9 @@ func TestPostCapBasicDataAccess(t *testing.T) { if err := shouldErr(snap, "0xa3"); err != nil { t.Error(err) } + // Now, merge it again, just for fun. It should now error, since a3 + // is a disk layer + if err := snaps.Cap(common.HexToHash("0xa3"), 0, 1024); err == nil { + t.Error("expected error capping the disk layer, got none") + } } From d754091a87703278b744815ccdcc499df66c9c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Fri, 22 Nov 2019 13:23:49 +0200 Subject: [PATCH 005/103] core/state/snapshot: unlink snapshots from blocks, quad->linear cleanup --- core/blockchain.go | 10 +- core/rawdb/accessors_snapshot.go | 40 +++--- core/rawdb/schema.go | 4 +- core/state/snapshot/difflayer.go | 27 ++-- core/state/snapshot/difflayer_journal.go | 25 +--- core/state/snapshot/difflayer_test.go | 56 +++----- core/state/snapshot/disklayer.go | 22 ++- core/state/snapshot/generate.go | 11 +- core/state/snapshot/generate_test.go | 10 +- core/state/snapshot/snapshot.go | 175 +++++++++++++---------- core/state/snapshot/snapshot_test.go | 11 +- core/state/statedb.go | 18 +-- 12 files changed, 203 insertions(+), 206 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 676a72c779..6fb722d2db 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -140,10 +140,10 @@ type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning - db ethdb.Database // Low level persistent database to store final content in - snaps *snapshot.SnapshotTree // Snapshot tree for fast trie leaf access - triegc *prque.Prque // Priority queue mapping block numbers to tries to gc - gcproc time.Duration // Accumulates canonical block processing for trie dumping + db ethdb.Database // Low level persistent database to store final content in + snaps *snapshot.Tree // Snapshot tree for fast trie leaf access + triegc *prque.Prque // Priority queue mapping block numbers to tries to gc + gcproc time.Duration // Accumulates canonical block processing for trie dumping hc *HeaderChain rmLogsFeed event.Feed @@ -301,7 +301,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } // Load any existing snapshot, regenerating it if loading failed head := bc.CurrentBlock() - if bc.snaps, err = snapshot.New(bc.db, "snapshot.rlp", head.NumberU64(), head.Root()); err != nil { + if bc.snaps, err = snapshot.New(bc.db, "snapshot.rlp", head.Root()); err != nil { return nil, err } // Take ownership of this particular state diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 9989e6b50e..9388e857b7 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -17,38 +17,36 @@ package rawdb import ( - "encoding/binary" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) -// ReadSnapshotBlock retrieves the number and root of the block whose state is -// contained in the persisted snapshot. -func ReadSnapshotBlock(db ethdb.KeyValueReader) (uint64, common.Hash) { - data, _ := db.Get(snapshotBlockKey) - if len(data) != 8+common.HashLength { - return 0, common.Hash{} +// ReadSnapshotRoot retrieves the root of the block whose state is contained in +// the persisted snapshot. +func ReadSnapshotRoot(db ethdb.KeyValueReader) common.Hash { + data, _ := db.Get(snapshotRootKey) + if len(data) != common.HashLength { + return common.Hash{} } - return binary.BigEndian.Uint64(data[:8]), common.BytesToHash(data[8:]) + return common.BytesToHash(data) } -// WriteSnapshotBlock stores the number and root of the block whose state is -// contained in the persisted snapshot. -func WriteSnapshotBlock(db ethdb.KeyValueWriter, number uint64, root common.Hash) { - if err := db.Put(snapshotBlockKey, append(encodeBlockNumber(number), root.Bytes()...)); err != nil { - log.Crit("Failed to store snapsnot block's number and root", "err", err) +// WriteSnapshotRoot stores the root of the block whose state is contained in +// the persisted snapshot. +func WriteSnapshotRoot(db ethdb.KeyValueWriter, root common.Hash) { + if err := db.Put(snapshotRootKey, root[:]); err != nil { + log.Crit("Failed to store snapshot root", "err", err) } } -// DeleteSnapshotBlock deletes the number and hash of the block whose state is -// contained in the persisted snapshot. Since snapshots are not immutable, this -// method can be used during updates, so a crash or failure will mark the entire -// snapshot invalid. -func DeleteSnapshotBlock(db ethdb.KeyValueWriter) { - if err := db.Delete(snapshotBlockKey); err != nil { - log.Crit("Failed to remove snapsnot block's number and hash", "err", err) +// DeleteSnapshotRoot deletes the hash of the block whose state is contained in +// the persisted snapshot. Since snapshots are not immutable, this method can +// be used during updates, so a crash or failure will mark the entire snapshot +// invalid. +func DeleteSnapshotRoot(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotRootKey); err != nil { + log.Crit("Failed to remove snapshot root", "err", err) } } diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 8e611246a1..d206587928 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -41,8 +41,8 @@ var ( // fastTrieProgressKey tracks the number of trie entries imported during fast sync. fastTrieProgressKey = []byte("TrieSync") - // snapshotBlockKey tracks the number and hash of the last snapshot. - snapshotBlockKey = []byte("SnapshotBlock") + // snapshotRootKey tracks the number and hash of the last snapshot. + snapshotRootKey = []byte("SnapshotRoot") // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 644c8fb4be..7e8487ea84 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -36,9 +36,8 @@ type diffLayer struct { parent snapshot // Parent snapshot modified by this one, never nil memory uint64 // Approximate guess as to how much memory we use - number uint64 // Block number to which this snapshot diff belongs to - root common.Hash // Root hash to which this snapshot diff belongs to - stale bool // Signals that the layer became stale (state progressed) + root common.Hash // Root hash to which this snapshot diff belongs to + stale bool // Signals that the layer became stale (state progressed) accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) @@ -50,11 +49,10 @@ type diffLayer struct { // newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low // level persistent database or a hierarchical diff already. -func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { +func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { // Create the new layer with some pre-allocated data segments dl := &diffLayer{ parent: parent, - number: number, root: root, accountData: accounts, storageData: storage, @@ -63,7 +61,6 @@ func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map for _, data := range accounts { dl.memory += uint64(len(data)) } - // Fill the storage hashes and sort them for the iterator dl.storageList = make(map[common.Hash][]common.Hash) @@ -93,9 +90,18 @@ func newDiffLayer(parent snapshot, number uint64, root common.Hash, accounts map return dl } -// Info returns the block number and root hash for which this snapshot was made. -func (dl *diffLayer) Info() (uint64, common.Hash) { - return dl.number, dl.root +// Root returns the root hash for which this snapshot was made. +func (dl *diffLayer) Root() common.Hash { + return dl.root +} + +// Stale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diffLayer) Stale() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.stale } // Account directly retrieves the account associated with a particular hash in @@ -164,7 +170,7 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // Update creates a new layer on top of the existing snapshot diff tree with // the specified data items. func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { - return newDiffLayer(dl, dl.number+1, blockRoot, accounts, storage) + return newDiffLayer(dl, blockRoot, accounts, storage) } // flatten pushes all data from this point downwards, flattening everything into @@ -213,7 +219,6 @@ func (dl *diffLayer) flatten() snapshot { // Return the combo parent return &diffLayer{ parent: parent.parent, - number: dl.number, root: dl.root, storageList: parent.storageList, storageData: parent.storageData, diff --git a/core/state/snapshot/difflayer_journal.go b/core/state/snapshot/difflayer_journal.go index 16fdb8a973..5490531be5 100644 --- a/core/state/snapshot/difflayer_journal.go +++ b/core/state/snapshot/difflayer_journal.go @@ -43,18 +43,12 @@ type journalStorage struct { // diff and verifying that it can be linked to the requested parent. func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { // Read the next diff journal entry - var ( - number uint64 - root common.Hash - ) - if err := r.Decode(&number); err != nil { + var root common.Hash + if err := r.Decode(&root); err != nil { // The first read may fail with EOF, marking the end of the journal if err == io.EOF { return parent, nil } - return nil, fmt.Errorf("load diff number: %v", err) - } - if err := r.Decode(&root); err != nil { return nil, fmt.Errorf("load diff root: %v", err) } var accounts []journalAccount @@ -77,13 +71,7 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { } storageData[entry.Hash] = slots } - // Validate the block number to avoid state corruption - if parent, ok := parent.(*diffLayer); ok { - if number != parent.number+1 { - return nil, fmt.Errorf("snapshot chain broken: block #%d after #%d", number, parent.number) - } - } - return loadDiffLayer(newDiffLayer(parent, number, root, accountData, storageData), r) + return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) } // journal is the internal version of Journal that also returns the journal file @@ -113,13 +101,8 @@ func (dl *diffLayer) journal() (io.WriteCloser, error) { writer.Close() return nil, ErrSnapshotStale } - buf := bufio.NewWriter(writer) // Everything below was journalled, persist this layer too - if err := rlp.Encode(buf, dl.number); err != nil { - buf.Flush() - writer.Close() - return nil, err - } + buf := bufio.NewWriter(writer) if err := rlp.Encode(buf, dl.root); err != nil { buf.Flush() writer.Close() diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 5b79073012..7cd1e8062d 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -61,11 +61,11 @@ func TestMergeBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer{}, 1, common.Hash{}, accounts, storage) - child := newDiffLayer(parent, 1, common.Hash{}, accounts, storage) - child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) - child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) - child = newDiffLayer(child, 1, common.Hash{}, accounts, storage) + parent := newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) + child := newDiffLayer(parent, common.Hash{}, accounts, storage) + child = newDiffLayer(child, common.Hash{}, accounts, storage) + child = newDiffLayer(child, common.Hash{}, accounts, storage) + child = newDiffLayer(child, common.Hash{}, accounts, storage) // And flatten merged := (child.flatten()).(*diffLayer) @@ -122,7 +122,7 @@ func TestMergeDelete(t *testing.T) { } // Add some flip-flopping layers on top - parent := newDiffLayer(emptyLayer{}, 1, common.Hash{}, flip(), storage) + parent := newDiffLayer(emptyLayer{}, common.Hash{}, flip(), storage) child := parent.Update(common.Hash{}, flop(), storage) child = child.Update(common.Hash{}, flip(), storage) child = child.Update(common.Hash{}, flop(), storage) @@ -139,10 +139,6 @@ func TestMergeDelete(t *testing.T) { // And flatten merged := (child.flatten()).(*diffLayer) - // check number - if got, exp := merged.number, child.number; got != exp { - t.Errorf("merged layer: wrong number - exp %d got %d", exp, got) - } if data, _ := merged.Account(h1); data == nil { t.Errorf("merged layer: expected %x to be non-nil", h1) } @@ -169,7 +165,7 @@ func TestInsertAndMerge(t *testing.T) { { var accounts = make(map[common.Hash][]byte) var storage = make(map[common.Hash]map[common.Hash][]byte) - parent = newDiffLayer(emptyLayer{}, 1, common.Hash{}, accounts, storage) + parent = newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) } { var accounts = make(map[common.Hash][]byte) @@ -178,7 +174,7 @@ func TestInsertAndMerge(t *testing.T) { accstorage := make(map[common.Hash][]byte) storage[acc] = accstorage storage[acc][slot] = []byte{0x01} - child = newDiffLayer(parent, 2, common.Hash{}, accounts, storage) + child = newDiffLayer(parent, common.Hash{}, accounts, storage) } // And flatten merged := (child.flatten()).(*diffLayer) @@ -200,11 +196,12 @@ func (emptyLayer) Journal() error { panic("implement me") } -func (emptyLayer) Info() (uint64, common.Hash) { - return 0, common.Hash{} +func (emptyLayer) Stale() bool { + panic("implement me") } -func (emptyLayer) Number() uint64 { - return 0 + +func (emptyLayer) Root() common.Hash { + return common.Hash{} } func (emptyLayer) Account(hash common.Hash) (*Account, error) { @@ -227,8 +224,6 @@ func (emptyLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) // BenchmarkSearch-6 500000 3723 ns/op (10k per layer, only top-level RLock() func BenchmarkSearch(b *testing.B) { // First, we set up 128 diff layers, with 1K items each - - blocknum := uint64(0) fill := func(parent snapshot) *diffLayer { accounts := make(map[common.Hash][]byte) storage := make(map[common.Hash]map[common.Hash][]byte) @@ -236,10 +231,8 @@ func BenchmarkSearch(b *testing.B) { for i := 0; i < 10000; i++ { accounts[randomHash()] = randomAccount() } - blocknum++ - return newDiffLayer(parent, blocknum, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, accounts, storage) } - var layer snapshot layer = emptyLayer{} for i := 0; i < 128; i++ { @@ -261,8 +254,6 @@ func BenchmarkSearch(b *testing.B) { // BenchmarkSearchSlot-6 100000 14551 ns/op (when checking parent number using atomic) func BenchmarkSearchSlot(b *testing.B) { // First, we set up 128 diff layers, with 1K items each - - blocknum := uint64(0) accountKey := common.Hash{} storageKey := common.HexToHash("0x1337") accountRLP := randomAccount() @@ -278,16 +269,13 @@ func BenchmarkSearchSlot(b *testing.B) { accStorage[randomHash()] = value storage[accountKey] = accStorage } - blocknum++ - return newDiffLayer(parent, blocknum, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, accounts, storage) } - var layer snapshot layer = emptyLayer{} for i := 0; i < 128; i++ { layer = fill(layer) } - b.ResetTimer() for i := 0; i < b.N; i++ { layer.Storage(accountKey, storageKey) @@ -300,7 +288,7 @@ func BenchmarkSearchSlot(b *testing.B) { // Without sorting and tracking accountlist // BenchmarkFlatten-6 300 5511511 ns/op func BenchmarkFlatten(b *testing.B) { - fill := func(parent snapshot, blocknum int) *diffLayer { + fill := func(parent snapshot) *diffLayer { accounts := make(map[common.Hash][]byte) storage := make(map[common.Hash]map[common.Hash][]byte) @@ -317,7 +305,7 @@ func BenchmarkFlatten(b *testing.B) { } storage[accountKey] = accStorage } - return newDiffLayer(parent, uint64(blocknum), common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, accounts, storage) } b.ResetTimer() @@ -327,7 +315,7 @@ func BenchmarkFlatten(b *testing.B) { var layer snapshot layer = emptyLayer{} for i := 1; i < 128; i++ { - layer = fill(layer, i) + layer = fill(layer) } b.StartTimer() @@ -336,7 +324,6 @@ func BenchmarkFlatten(b *testing.B) { if !ok { break } - layer = dl.flatten() } b.StopTimer() @@ -351,7 +338,7 @@ func BenchmarkFlatten(b *testing.B) { // BenchmarkJournal-6 1 1471373923 ns/ops // BenchmarkJournal-6 1 1208083335 ns/op // bufio writer func BenchmarkJournal(b *testing.B) { - fill := func(parent snapshot, blocknum int) *diffLayer { + fill := func(parent snapshot) *diffLayer { accounts := make(map[common.Hash][]byte) storage := make(map[common.Hash]map[common.Hash][]byte) @@ -368,15 +355,14 @@ func BenchmarkJournal(b *testing.B) { } storage[accountKey] = accStorage } - return newDiffLayer(parent, uint64(blocknum), common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, accounts, storage) } - var layer snapshot layer = &diskLayer{ journal: path.Join(os.TempDir(), "difflayer_journal.tmp"), } for i := 1; i < 128; i++ { - layer = fill(layer, i) + layer = fill(layer) } b.ResetTimer() diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 50321f1540..f302652eb7 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -32,16 +32,24 @@ type diskLayer struct { db ethdb.KeyValueStore // Key-value store containing the base snapshot cache *bigcache.BigCache // Cache to avoid hitting the disk for direct access - number uint64 // Block number of the base snapshot - root common.Hash // Root hash of the base snapshot - stale bool // Signals that the layer became stale (state progressed) + root common.Hash // Root hash of the base snapshot + stale bool // Signals that the layer became stale (state progressed) lock sync.RWMutex } -// Info returns the block number and root hash for which this snapshot was made. -func (dl *diskLayer) Info() (uint64, common.Hash) { - return dl.number, dl.root +// Root returns root hash for which this snapshot was made. +func (dl *diskLayer) Root() common.Hash { + return dl.root +} + +// Stale return whether this layer has become stale (was flattened across) or if +// it's still live. +func (dl *diskLayer) Stale() bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + + return dl.stale } // Account directly retrieves the account associated with a particular hash in @@ -123,7 +131,7 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // the specified data items. Note, the maps are retained by the method to avoid // copying everything. func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { - return newDiffLayer(dl, dl.number+1, blockHash, accounts, storage) + return newDiffLayer(dl, blockHash, accounts, storage) } // Journal commits an entire diff hierarchy to disk into a single journal file. diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 4a66e0626c..a2197557e3 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -85,7 +85,7 @@ func wipeSnapshot(db ethdb.KeyValueStore) error { } it.Release() - rawdb.DeleteSnapshotBlock(batch) + rawdb.DeleteSnapshotRoot(batch) if err := batch.Write(); err != nil { return err } @@ -107,7 +107,7 @@ func wipeSnapshot(db ethdb.KeyValueStore) error { } // generateSnapshot regenerates a brand new snapshot based on an existing state database and head block. -func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (snapshot, error) { +func generateSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) (snapshot, error) { // Wipe any previously existing snapshot from the database if err := wipeSnapshot(db); err != nil { return nil, err @@ -124,7 +124,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, batch := db.NewBatch() triedb := trie.NewDatabase(db) - accTrie, err := trie.NewSecure(headRoot, triedb) + accTrie, err := trie.NewSecure(root, triedb) if err != nil { return nil, err } @@ -186,7 +186,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, fmt.Printf("Totals: %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) // Update the snapshot block marker and write any remainder data - rawdb.WriteSnapshotBlock(batch, headNumber, headRoot) + rawdb.WriteSnapshotRoot(batch, root) batch.Write() batch.Reset() @@ -207,7 +207,6 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, journal: journal, db: db, cache: cache, - number: headNumber, - root: headRoot, + root: root, }, nil } diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 1206445c58..180db920a8 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -47,7 +47,7 @@ func TestWipe(t *testing.T) { rawdb.WriteStorageSnapshot(db, account, randomHash(), randomHash().Bytes()) } } - rawdb.WriteSnapshotBlock(db, 123, randomHash()) + rawdb.WriteSnapshotRoot(db, randomHash()) // Add some random non-snapshot data too to make wiping harder for i := 0; i < 65536; i++ { @@ -76,8 +76,8 @@ func TestWipe(t *testing.T) { if items != 128+128*1024 { t.Fatalf("snapshot size mismatch: have %d, want %d", items, 128+128*1024) } - if number, hash := rawdb.ReadSnapshotBlock(db); number != 123 || hash == (common.Hash{}) { - t.Errorf("snapshot block marker mismatch: have #%d [%#x], want #%d []", number, hash, 123) + if hash := rawdb.ReadSnapshotRoot(db); hash == (common.Hash{}) { + t.Errorf("snapshot block marker mismatch: have %#x, want ", hash) } // Wipe all snapshot entries from the database if err := wipeSnapshot(db); err != nil { @@ -93,8 +93,8 @@ func TestWipe(t *testing.T) { t.Errorf("snapshot entry remained after wipe: %x", key) } } - if number, hash := rawdb.ReadSnapshotBlock(db); number != 0 || hash != (common.Hash{}) { - t.Errorf("snapshot block marker remained after wipe: #%d [%#x]", number, hash) + if hash := rawdb.ReadSnapshotRoot(db); hash != (common.Hash{}) { + t.Errorf("snapshot block marker remained after wipe: %#x", hash) } // Iterate over the database and ensure miscellaneous items are present items = 0 diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 668522feca..edca5781d6 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -43,12 +43,16 @@ var ( // layer had been invalidated due to the chain progressing forward far enough // to not maintain the layer's original state. ErrSnapshotStale = errors.New("snapshot stale") + + // errSnapshotCycle is returned if a snapshot is attempted to be inserted + // that forms a cycle in the snapshot tree. + errSnapshotCycle = errors.New("snapshot cycle") ) // Snapshot represents the functionality supported by a snapshot storage layer. type Snapshot interface { - // Info returns the block number and root hash for which this snapshot was made. - Info() (uint64, common.Hash) + // Root returns the root hash for which this snapshot was made. + Root() common.Hash // Account directly retrieves the account associated with a particular hash in // the snapshot slim data format. @@ -77,6 +81,10 @@ type snapshot interface { // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). Journal() error + + // Stale return whether this layer has become stale (was flattened across) or + // if it's still live. + Stale() bool } // SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent @@ -88,7 +96,7 @@ type snapshot interface { // The goal of a state snapshot is twofold: to allow direct access to account and // storage data to avoid expensive multi-level trie lookups; and to allow sorted, // cheap iteration of the account/storage tries for sync aid. -type SnapshotTree struct { +type Tree struct { layers map[common.Hash]snapshot // Collection of all known layers // TODO(karalabe): split Clique overlaps lock sync.RWMutex } @@ -99,22 +107,21 @@ type SnapshotTree struct { // // If the snapshot is missing or inconsistent, the entirety is deleted and will // be reconstructed from scratch based on the tries in the key-value store. -func New(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (*SnapshotTree, error) { +func New(db ethdb.KeyValueStore, journal string, root common.Hash) (*Tree, error) { // Attempt to load a previously persisted snapshot - head, err := loadSnapshot(db, journal, headNumber, headRoot) + head, err := loadSnapshot(db, journal, root) if err != nil { log.Warn("Failed to load snapshot, regenerating", "err", err) - if head, err = generateSnapshot(db, journal, headNumber, headRoot); err != nil { + if head, err = generateSnapshot(db, journal, root); err != nil { return nil, err } } // Existing snapshot loaded or one regenerated, seed all the layers - snap := &SnapshotTree{ + snap := &Tree{ layers: make(map[common.Hash]snapshot), } for head != nil { - _, root := head.Info() - snap.layers[root] = head + snap.layers[head.Root()] = head switch self := head.(type) { case *diffLayer: @@ -130,54 +137,57 @@ func New(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot com // Snapshot retrieves a snapshot belonging to the given block root, or nil if no // snapshot is maintained for that block. -func (st *SnapshotTree) Snapshot(blockRoot common.Hash) Snapshot { - st.lock.RLock() - defer st.lock.RUnlock() +func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { + t.lock.RLock() + defer t.lock.RUnlock() - return st.layers[blockRoot] + return t.layers[blockRoot] } // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). -func (st *SnapshotTree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { +func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { + // Reject noop updates to avoid self-loops in the snapshot tree. This is a + // special case that can only happen for Clique networks where empty blocks + // don't modify the state (0 block subsidy). + // + // Although we could silently ignore this internally, it should be the caller's + // responsibility to avoid even attempting to insert such a snapshot. + if blockRoot == parentRoot { + return errSnapshotCycle + } // Generate a new snapshot on top of the parent - parent := st.Snapshot(parentRoot).(snapshot) + parent := t.Snapshot(parentRoot).(snapshot) if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } snap := parent.Update(blockRoot, accounts, storage) // Save the new snapshot for later - st.lock.Lock() - defer st.lock.Unlock() + t.lock.Lock() + defer t.lock.Unlock() - st.layers[snap.root] = snap + t.layers[snap.root] = snap return nil } // Cap traverses downwards the snapshot tree from a head block hash until the // number of allowed layers are crossed. All layers beyond the permitted number // are flattened downwards. -func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) error { +func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { // Retrieve the head snapshot to cap from - var snap snapshot - if s := st.Snapshot(blockRoot); s == nil { - return fmt.Errorf("snapshot [%#x] missing", blockRoot) - } else { - snap = s.(snapshot) + snap := t.Snapshot(root) + if snap == nil { + return fmt.Errorf("snapshot [%#x] missing", root) } diff, ok := snap.(*diffLayer) if !ok { - return fmt.Errorf("snapshot [%#x] is disk layer", blockRoot) + return fmt.Errorf("snapshot [%#x] is disk layer", root) } // Run the internal capping and discard all stale layers - st.lock.Lock() - defer st.lock.Unlock() + t.lock.Lock() + defer t.lock.Unlock() - var ( - diskNumber uint64 - diffNumber uint64 - ) // Flattening the bottom-most diff layer requires special casing since there's // no child to rewire to the grandparent. In that case we can fake a temporary // child for the capping and then remove it. @@ -188,8 +198,9 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er base := diffToDisk(diff.flatten().(*diffLayer)) diff.lock.RUnlock() - st.layers[base.root] = base - diskNumber, diffNumber = base.number, base.number + // Replace the entire snapshot tree with the flat base + t.layers = map[common.Hash]snapshot{base.root: base} + return nil case 1: // If full flattening was requested, flatten the diffs but only merge if the @@ -205,59 +216,74 @@ func (st *SnapshotTree) Cap(blockRoot common.Hash, layers int, memory uint64) er } diff.lock.RUnlock() + // If all diff layers were removed, replace the entire snapshot tree if base != nil { - st.layers[base.root] = base - diskNumber, diffNumber = base.number, base.number - } else { - st.layers[bottom.root] = bottom - diskNumber, diffNumber = bottom.parent.(*diskLayer).number, bottom.number + t.layers = map[common.Hash]snapshot{base.root: base} + return nil } + // Merge the new aggregated layer into the snapshot tree, clean stales below + t.layers[bottom.root] = bottom default: - diskNumber, diffNumber = st.cap(diff, layers, memory) + // Many layers requested to be retained, cap normally + t.cap(diff, layers, memory) } - for root, snap := range st.layers { - if number, _ := snap.Info(); number != diskNumber && number < diffNumber { - delete(st.layers, root) + // Remove any layer that is stale or links into a stale layer + children := make(map[common.Hash][]common.Hash) + for root, snap := range t.layers { + if diff, ok := snap.(*diffLayer); ok { + parent := diff.parent.Root() + children[parent] = append(children[parent], root) + } + } + var remove func(root common.Hash) + remove = func(root common.Hash) { + delete(t.layers, root) + for _, child := range children[root] { + remove(child) + } + delete(children, root) + } + for root, snap := range t.layers { + if snap.Stale() { + remove(root) } } return nil } // cap traverses downwards the diff tree until the number of allowed layers are -// crossed. All diffs beyond the permitted number are flattened downwards. If -// the layer limit is reached, memory cap is also enforced (but not before). The -// block numbers for the disk layer and first diff layer are returned for GC. -func (st *SnapshotTree) cap(diff *diffLayer, layers int, memory uint64) (uint64, uint64) { +// crossed. All diffs beyond the permitted number are flattened downwards. If the +// layer limit is reached, memory cap is also enforced (but not before). +func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { // Dive until we run out of layers or reach the persistent database for ; layers > 2; layers-- { // If we still have diff layers below, continue down if parent, ok := diff.parent.(*diffLayer); ok { diff = parent } else { - // Diff stack too shallow, return block numbers without modifications - return diff.parent.(*diskLayer).number, diff.number + // Diff stack too shallow, return without modifications + return } } // We're out of layers, flatten anything below, stopping if it's the disk or if // the memory limit is not yet exceeded. switch parent := diff.parent.(type) { case *diskLayer: - return parent.number, diff.number + return case *diffLayer: // Flatten the parent into the grandparent. The flattening internally obtains a // write lock on grandparent. flattened := parent.flatten().(*diffLayer) - st.layers[flattened.root] = flattened + t.layers[flattened.root] = flattened diff.lock.Lock() defer diff.lock.Unlock() diff.parent = flattened if flattened.memory < memory { - diskNumber, _ := flattened.parent.Info() - return diskNumber, flattened.number + return } default: panic(fmt.Sprintf("unknown data layer: %T", parent)) @@ -269,10 +295,8 @@ func (st *SnapshotTree) cap(diff *diffLayer, layers int, memory uint64) (uint64, base := diffToDisk(bottom) bottom.lock.RUnlock() - st.layers[base.root] = base + t.layers[base.root] = base diff.parent = base - - return base.number, diff.number } // diffToDisk merges a bottom-most diff into the persistent disk layer underneath @@ -284,7 +308,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { ) // Start by temporarily deleting the current snapshot block marker. This // ensures that in the case of a crash, the entire snapshot is invalidated. - rawdb.DeleteSnapshotBlock(batch) + rawdb.DeleteSnapshotRoot(batch) // Mark the original base as stale as we're going to create a new wrapper base.lock.Lock() @@ -341,13 +365,12 @@ func diffToDisk(bottom *diffLayer) *diskLayer { } } // Update the snapshot block marker and write any remainder data - rawdb.WriteSnapshotBlock(batch, bottom.number, bottom.root) + rawdb.WriteSnapshotRoot(batch, bottom.root) if err := batch.Write(); err != nil { log.Crit("Failed to write leftover snapshot", "err", err) } return &diskLayer{ root: bottom.root, - number: bottom.number, cache: base.cache, db: base.db, journal: base.journal, @@ -357,27 +380,25 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Journal commits an entire diff hierarchy to disk into a single journal file. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). -func (st *SnapshotTree) Journal(blockRoot common.Hash) error { - // Retrieve the head snapshot to journal from - var snap snapshot - if s := st.Snapshot(blockRoot); s == nil { +func (t *Tree) Journal(blockRoot common.Hash) error { + // Retrieve the head snapshot to journal from var snap snapshot + snap := t.Snapshot(blockRoot) + if snap == nil { return fmt.Errorf("snapshot [%#x] missing", blockRoot) - } else { - snap = s.(snapshot) } // Run the journaling - st.lock.Lock() - defer st.lock.Unlock() + t.lock.Lock() + defer t.lock.Unlock() - return snap.Journal() + return snap.(snapshot).Journal() } // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. -func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, headRoot common.Hash) (snapshot, error) { +func loadSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) (snapshot, error) { // Retrieve the block number and hash of the snapshot, failing if no snapshot // is present in the database (or crashed mid-update). - number, root := rawdb.ReadSnapshotBlock(db) - if root == (common.Hash{}) { + baseRoot := rawdb.ReadSnapshotRoot(db) + if baseRoot == (common.Hash{}) { return nil, errors.New("missing or corrupted snapshot") } cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup @@ -391,16 +412,14 @@ func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, hea journal: journal, db: db, cache: cache, - number: number, - root: root, + root: baseRoot, } // Load all the snapshot diffs from the journal, failing if their chain is broken // or does not lead from the disk snapshot to the specified head. if _, err := os.Stat(journal); os.IsNotExist(err) { // Journal doesn't exist, don't worry if it's not supposed to - if number != headNumber || root != headRoot { - return nil, fmt.Errorf("snapshot journal missing, head doesn't match snapshot: #%d [%#x] vs. #%d [%#x]", - headNumber, headRoot, number, root) + if baseRoot != root { + return nil, fmt.Errorf("snapshot journal missing, head doesn't match snapshot: have %#x, want %#x", baseRoot, root) } return base, nil } @@ -414,10 +433,8 @@ func loadSnapshot(db ethdb.KeyValueStore, journal string, headNumber uint64, hea } // Entire snapshot journal loaded, sanity check the head and return // Journal doesn't exist, don't worry if it's not supposed to - number, root = snapshot.Info() - if number != headNumber || root != headRoot { - return nil, fmt.Errorf("head doesn't match snapshot: #%d [%#x] vs. #%d [%#x]", - headNumber, headRoot, number, root) + if head := snapshot.Root(); head != root { + return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) } return snapshot, nil } diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 1551a71a2f..40edf79e84 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -37,7 +37,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { root: common.HexToHash("0x01"), cache: cache, } - snaps := &SnapshotTree{ + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, }, @@ -83,7 +83,7 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { root: common.HexToHash("0x01"), cache: cache, } - snaps := &SnapshotTree{ + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, }, @@ -132,7 +132,7 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { root: common.HexToHash("0x01"), cache: cache, } - snaps := &SnapshotTree{ + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, }, @@ -181,7 +181,7 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { root: common.HexToHash("0x01"), cache: cache, } - snaps := &SnapshotTree{ + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, }, @@ -213,7 +213,6 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { if got := len(snaps.layers); got != exp { t.Errorf("layers modified, got %d exp %d", got, exp) } - // Flatten the diff layer into the bottom accumulator if err := snaps.Cap(common.HexToHash("0x04"), 2, 1024*1024); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) @@ -247,7 +246,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { root: common.HexToHash("0x01"), cache: cache, } - snaps := &SnapshotTree{ + snaps := &Tree{ layers: map[common.Hash]snapshot{ base.root: base, }, diff --git a/core/state/statedb.go b/core/state/statedb.go index 7d7499892f..f11bd2adbd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -68,7 +68,7 @@ type StateDB struct { db Database trie Trie - snaps *snapshot.SnapshotTree + snaps *snapshot.Tree snap snapshot.Snapshot snapAccounts map[common.Hash][]byte snapStorage map[common.Hash]map[common.Hash][]byte @@ -117,7 +117,7 @@ type StateDB struct { } // Create a new state from a given trie. -func New(root common.Hash, db Database, snaps *snapshot.SnapshotTree) (*StateDB, error) { +func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { tr, err := db.OpenTrie(root) if err != nil { return nil, err @@ -840,12 +840,14 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if metrics.EnabledExpensive { defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now()) } - _, parentRoot := s.snap.Info() - if err := s.snaps.Update(root, parentRoot, s.snapAccounts, s.snapStorage); err != nil { - log.Warn("Failed to update snapshot tree", "from", parentRoot, "to", root, "err", err) - } - if err := s.snaps.Cap(root, 16, 4*1024*1024); err != nil { - log.Warn("Failed to cap snapshot tree", "root", root, "layers", 16, "memory", 4*1024*1024, "err", err) + // Only update if there's a state transition (skip empty Clique blocks) + if parent := s.snap.Root(); parent != root { + if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil { + log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) + } + if err := s.snaps.Cap(root, 16, 4*1024*1024); err != nil { + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 16, "memory", 4*1024*1024, "err", err) + } } s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil } From f300c0df01dde6b23f5fec4f1b2f91bc75f32c2f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 25 Nov 2019 15:30:29 +0100 Subject: [PATCH 006/103] core/state/snapshot: replace bigcache with fastcache --- core/state/snapshot/disklayer.go | 14 ++++++-------- core/state/snapshot/generate.go | 10 ++-------- core/state/snapshot/snapshot.go | 22 +++++++--------------- core/state/snapshot/snapshot_test.go | 18 ++++++------------ 4 files changed, 21 insertions(+), 43 deletions(-) diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index f302652eb7..474182f1dd 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -19,7 +19,7 @@ package snapshot import ( "sync" - "github.com/allegro/bigcache" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" @@ -30,7 +30,7 @@ import ( type diskLayer struct { journal string // Path of the snapshot journal to use on shutdown db ethdb.KeyValueStore // Key-value store containing the base snapshot - cache *bigcache.BigCache // Cache to avoid hitting the disk for direct access + cache *fastcache.Cache // Cache to avoid hitting the disk for direct access root common.Hash // Root hash of the base snapshot stale bool // Signals that the layer became stale (state progressed) @@ -80,17 +80,15 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { if dl.stale { return nil, ErrSnapshotStale } - key := string(hash[:]) - // Try to retrieve the account from the memory cache - if blob, err := dl.cache.Get(key); err == nil { + if blob := dl.cache.Get(nil, hash[:]); blob != nil { snapshotCleanHitMeter.Mark(1) snapshotCleanReadMeter.Mark(int64(len(blob))) return blob, nil } // Cache doesn't contain account, pull from disk and cache for later blob := rawdb.ReadAccountSnapshot(dl.db, hash) - dl.cache.Set(key, blob) + dl.cache.Set(hash[:], blob) snapshotCleanMissMeter.Mark(1) snapshotCleanWriteMeter.Mark(int64(len(blob))) @@ -109,10 +107,10 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro if dl.stale { return nil, ErrSnapshotStale } - key := string(append(accountHash[:], storageHash[:]...)) + key := append(accountHash[:], storageHash[:]...) // Try to retrieve the storage slot from the memory cache - if blob, err := dl.cache.Get(key); err == nil { + if blob := dl.cache.Get(nil, key); blob != nil { snapshotCleanHitMeter.Mark(1) snapshotCleanReadMeter.Mark(int64(len(blob))) return blob, nil diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index a2197557e3..445a6ebd9f 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -22,7 +22,7 @@ import ( "math/big" "time" - "github.com/allegro/bigcache" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" @@ -196,13 +196,7 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) return nil, err } // New snapshot generated, construct a brand new base layer - cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup - Shards: 1024, - LifeWindow: time.Hour, - MaxEntriesInWindow: 512 * 1024, - MaxEntrySize: 512, - HardMaxCacheSize: 512, - }) + cache := fastcache.New(512 * 1024 * 1024) return &diskLayer{ journal: journal, db: db, diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index edca5781d6..d35d698399 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -22,9 +22,8 @@ import ( "fmt" "os" "sync" - "time" - "github.com/allegro/bigcache" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" @@ -323,7 +322,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if len(data) > 0 { // Account was updated, push to disk rawdb.WriteAccountSnapshot(batch, hash, data) - base.cache.Set(string(hash[:]), data) + base.cache.Set(hash[:], data) if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { @@ -334,13 +333,13 @@ func diffToDisk(bottom *diffLayer) *diskLayer { } else { // Account was deleted, remove all storage slots too rawdb.DeleteAccountSnapshot(batch, hash) - base.cache.Set(string(hash[:]), nil) + base.cache.Set(hash[:], nil) it := rawdb.IterateStorageSnapshots(base.db, hash) for it.Next() { if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator batch.Delete(key) - base.cache.Delete(string(key[1:])) + base.cache.Del(key[1:]) } } it.Release() @@ -351,10 +350,10 @@ func diffToDisk(bottom *diffLayer) *diskLayer { for storageHash, data := range storage { if len(data) > 0 { rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) - base.cache.Set(string(append(accountHash[:], storageHash[:]...)), data) + base.cache.Set(append(accountHash[:], storageHash[:]...), data) } else { rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) - base.cache.Set(string(append(accountHash[:], storageHash[:]...)), nil) + base.cache.Set(append(accountHash[:], storageHash[:]...), nil) } } if batch.ValueSize() > ethdb.IdealBatchSize { @@ -401,17 +400,10 @@ func loadSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) (sna if baseRoot == (common.Hash{}) { return nil, errors.New("missing or corrupted snapshot") } - cache, _ := bigcache.NewBigCache(bigcache.Config{ // TODO(karalabe): dedup - Shards: 1024, - LifeWindow: time.Hour, - MaxEntriesInWindow: 512 * 1024, - MaxEntrySize: 512, - HardMaxCacheSize: 512, - }) base := &diskLayer{ journal: journal, db: db, - cache: cache, + cache: fastcache.New(512 * 1024 * 1024), root: baseRoot, } // Load all the snapshot diffs from the journal, failing if their chain is broken diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 40edf79e84..9c872a8955 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -19,9 +19,8 @@ package snapshot import ( "fmt" "testing" - "time" - "github.com/allegro/bigcache" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" ) @@ -31,11 +30,10 @@ import ( // to check internal corner case around the bottom-most memory accumulator. func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it - cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) base := &diskLayer{ db: rawdb.NewMemoryDatabase(), root: common.HexToHash("0x01"), - cache: cache, + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -77,11 +75,10 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { // layer to check the usual mode of operation where the accumulator is retained. func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it - cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) base := &diskLayer{ db: rawdb.NewMemoryDatabase(), root: common.HexToHash("0x01"), - cache: cache, + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -126,11 +123,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { // to check internal corner case around the bottom-most memory accumulator. func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it - cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) base := &diskLayer{ db: rawdb.NewMemoryDatabase(), root: common.HexToHash("0x01"), - cache: cache, + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -175,11 +171,10 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { // layer to check the usual mode of operation where the accumulator is retained. func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it - cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) base := &diskLayer{ db: rawdb.NewMemoryDatabase(), root: common.HexToHash("0x01"), - cache: cache, + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -240,11 +235,10 @@ func TestPostCapBasicDataAccess(t *testing.T) { } } // Create a starting base layer and a snapshot tree out of it - cache, _ := bigcache.NewBigCache(bigcache.DefaultConfig(time.Minute)) base := &diskLayer{ db: rawdb.NewMemoryDatabase(), root: common.HexToHash("0x01"), - cache: cache, + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ From 351a5903b0ccb9c77b5f0983fdd17c3d4de7acf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 26 Nov 2019 09:48:29 +0200 Subject: [PATCH 007/103] core/rawdb, core/state/snapshot: runtime snapshot generation --- cmd/geth/main.go | 1 + cmd/geth/usage.go | 1 + cmd/utils/flags.go | 13 +- core/blockchain.go | 24 +- core/rawdb/database.go | 4 +- core/rawdb/schema.go | 19 +- core/state/snapshot/difflayer.go | 206 ++++++++- core/state/snapshot/difflayer_journal.go | 137 ------ core/state/snapshot/difflayer_test.go | 54 +-- core/state/snapshot/disklayer.go | 57 ++- core/state/snapshot/disklayer_test.go | 433 ++++++++++++++++++ core/state/snapshot/generate.go | 330 +++++++------ core/state/snapshot/journal.go | 257 +++++++++++ core/state/snapshot/snapshot.go | 270 ++++++++--- core/state/snapshot/snapshot_test.go | 49 +- core/state/snapshot/wipe.go | 130 ++++++ .../{generate_test.go => wipe_test.go} | 38 +- core/state/statedb.go | 4 +- eth/backend.go | 6 +- eth/config.go | 2 + trie/iterator.go | 2 - 21 files changed, 1551 insertions(+), 486 deletions(-) delete mode 100644 core/state/snapshot/difflayer_journal.go create mode 100644 core/state/snapshot/disklayer_test.go create mode 100644 core/state/snapshot/journal.go create mode 100644 core/state/snapshot/wipe.go rename core/state/snapshot/{generate_test.go => wipe_test.go} (77%) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 99ef78238f..36187e484d 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -106,6 +106,7 @@ var ( utils.CacheDatabaseFlag, utils.CacheTrieFlag, utils.CacheGCFlag, + utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, utils.ListenPortFlag, utils.MaxPeersFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 6f3197b9c6..f2f3b5756b 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -137,6 +137,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.CacheDatabaseFlag, utils.CacheTrieFlag, utils.CacheGCFlag, + utils.CacheSnapshotFlag, utils.CacheNoPrefetchFlag, }, }, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index bdadebd852..22fe677fa2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -383,14 +383,19 @@ var ( } CacheTrieFlag = cli.IntFlag{ Name: "cache.trie", - Usage: "Percentage of cache memory allowance to use for trie caching (default = 25% full mode, 50% archive mode)", - Value: 25, + Usage: "Percentage of cache memory allowance to use for trie caching (default = 15% full mode, 30% archive mode)", + Value: 15, } CacheGCFlag = cli.IntFlag{ Name: "cache.gc", Usage: "Percentage of cache memory allowance to use for trie pruning (default = 25% full mode, 0% archive mode)", Value: 25, } + CacheSnapshotFlag = cli.IntFlag{ + Name: "cache.snapshot", + Usage: "Percentage of cache memory allowance to use for snapshot caching (default = 10% full mode, 20% archive mode)", + Value: 10, + } CacheNoPrefetchFlag = cli.BoolFlag{ Name: "cache.noprefetch", Usage: "Disable heuristic state prefetch during block import (less CPU and disk IO, more time waiting for data)", @@ -1463,6 +1468,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) { cfg.TrieDirtyCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100 } + if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) { + cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 + } if ctx.GlobalIsSet(DocRootFlag.Name) { cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) } @@ -1724,6 +1732,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai TrieDirtyLimit: eth.DefaultConfig.TrieDirtyCache, TrieDirtyDisabled: ctx.GlobalString(GCModeFlag.Name) == "archive", TrieTimeLimit: eth.DefaultConfig.TrieTimeout, + SnapshotLimit: eth.DefaultConfig.SnapshotCache, } if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100 diff --git a/core/blockchain.go b/core/blockchain.go index 6fb722d2db..3932baf557 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -62,8 +62,8 @@ var ( storageUpdateTimer = metrics.NewRegisteredTimer("chain/storage/updates", nil) storageCommitTimer = metrics.NewRegisteredTimer("chain/storage/commits", nil) - snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/accountreads", nil) - snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storagereads", nil) + snapshotAccountReadTimer = metrics.NewRegisteredTimer("chain/snapshot/account/reads", nil) + snapshotStorageReadTimer = metrics.NewRegisteredTimer("chain/snapshot/storage/reads", nil) snapshotCommitTimer = metrics.NewRegisteredTimer("chain/snapshot/commits", nil) blockInsertTimer = metrics.NewRegisteredTimer("chain/inserts", nil) @@ -120,6 +120,7 @@ type CacheConfig struct { TrieDirtyLimit int // Memory limit (MB) at which to start flushing dirty trie nodes to disk TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk + SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory } // BlockChain represents the canonical chain given a database with a genesis @@ -194,6 +195,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, + SnapshotLimit: 256, } } bodyCache, _ := lru.New(bodyCacheLimit) @@ -300,10 +302,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } // Load any existing snapshot, regenerating it if loading failed - head := bc.CurrentBlock() - if bc.snaps, err = snapshot.New(bc.db, "snapshot.rlp", head.Root()); err != nil { - return nil, err - } + bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), "snapshot.rlp", bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) + // Take ownership of this particular state go bc.update() return bc, nil @@ -497,6 +497,9 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { headBlockGauge.Update(int64(block.NumberU64())) bc.chainmu.Unlock() + // Destroy any existing state snapshot and regenerate it in the background + bc.snaps.Rebuild(block.Root()) + log.Info("Committed new head block", "number", block.Number(), "hash", hash) return nil } @@ -851,7 +854,8 @@ func (bc *BlockChain) Stop() { bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. - if err := bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { + snapBase, err := bc.snaps.Journal(bc.CurrentBlock().Root(), "snapshot.rlp") + if err != nil { log.Error("Failed to journal state snapshot", "err", err) } // Ensure the state of a recent block is also stored to disk before exiting. @@ -872,6 +876,12 @@ func (bc *BlockChain) Stop() { } } } + if snapBase != (common.Hash{}) { + log.Info("Writing snapshot state to disk", "root", snapBase) + if err := triedb.Commit(snapBase, true); err != nil { + log.Error("Failed to commit recent state trie", "err", err) + } + } for !bc.triegc.Empty() { triedb.Dereference(bc.triegc.PopItem().(common.Hash)) } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 7abd07359f..b74d8e2e39 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -282,9 +282,9 @@ func InspectDatabase(db ethdb.Database) error { receiptSize += size case bytes.HasPrefix(key, txLookupPrefix) && len(key) == (len(txLookupPrefix)+common.HashLength): txlookupSize += size - case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+common.HashLength): + case bytes.HasPrefix(key, SnapshotAccountPrefix) && len(key) == (len(SnapshotAccountPrefix)+common.HashLength): accountSnapSize += size - case bytes.HasPrefix(key, StateSnapshotPrefix) && len(key) == (len(StateSnapshotPrefix)+2*common.HashLength): + case bytes.HasPrefix(key, SnapshotStoragePrefix) && len(key) == (len(SnapshotStoragePrefix)+2*common.HashLength): storageSnapSize += size case bytes.HasPrefix(key, preimagePrefix) && len(key) == (len(preimagePrefix)+common.HashLength): preimageSize += size diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index d206587928..1b8e53eb61 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -53,9 +53,10 @@ var ( blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts - txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata - bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits - StateSnapshotPrefix = []byte("s") // StateSnapshotPrefix + account hash [+ storage hash] -> account/storage trie value + txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata + bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits + SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value + SnapshotStoragePrefix = []byte("s") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db @@ -149,19 +150,19 @@ func txLookupKey(hash common.Hash) []byte { return append(txLookupPrefix, hash.Bytes()...) } -// accountSnapshotKey = StateSnapshotPrefix + hash +// accountSnapshotKey = SnapshotAccountPrefix + hash func accountSnapshotKey(hash common.Hash) []byte { - return append(StateSnapshotPrefix, hash.Bytes()...) + return append(SnapshotAccountPrefix, hash.Bytes()...) } -// storageSnapshotKey = StateSnapshotPrefix + account hash + storage hash +// storageSnapshotKey = SnapshotStoragePrefix + account hash + storage hash func storageSnapshotKey(accountHash, storageHash common.Hash) []byte { - return append(append(StateSnapshotPrefix, accountHash.Bytes()...), storageHash.Bytes()...) + return append(append(SnapshotStoragePrefix, accountHash.Bytes()...), storageHash.Bytes()...) } -// storageSnapshotsKey = StateSnapshotPrefix + account hash + storage hash +// storageSnapshotsKey = SnapshotStoragePrefix + account hash + storage hash func storageSnapshotsKey(accountHash common.Hash) []byte { - return append(StateSnapshotPrefix, accountHash.Bytes()...) + return append(SnapshotStoragePrefix, accountHash.Bytes()...) } // bloomBitsKey = bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 7e8487ea84..0743e47597 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -17,13 +17,52 @@ package snapshot import ( + "encoding/binary" "fmt" + "math" "sort" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" + "github.com/steakknife/bloomfilter" +) + +var ( + // aggregatorMemoryLimit is the maximum size of the bottom-most diff layer + // that aggregates the writes from above until it's flushed into the disk + // layer. + // + // Note, bumping this up might drastically increase the size of the bloom + // filters that's stored in every diff layer. Don't do that without fully + // understanding all the implications. + aggregatorMemoryLimit = uint64(4 * 1024 * 1024) + + // aggregatorItemLimit is an approximate number of items that will end up + // in the agregator layer before it's flushed out to disk. A plain account + // weighs around 14B (+hash), a storage slot 32B (+hash), so 50 is a very + // rough average of what we might see. + aggregatorItemLimit = aggregatorMemoryLimit / 55 + + // bloomTargetError is the target false positive rate when the aggregator + // layer is at its fullest. The actual value will probably move around up + // and down from this number, it's mostly a ballpark figure. + // + // Note, dropping this down might drastically increase the size of the bloom + // filters that's stored in every diff layer. Don't do that without fully + // understanding all the implications. + bloomTargetError = 0.02 + + // bloomSize is the ideal bloom filter size given the maximum number of items + // it's expected to hold and the target false positive error rate. + bloomSize = math.Ceil(float64(aggregatorItemLimit) * math.Log(bloomTargetError) / math.Log(1/math.Pow(2, math.Log(2)))) + + // bloomFuncs is the ideal number of bits a single entry should set in the + // bloom filter to keep its size to a minimum (given it's size and maximum + // entry count). + bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2)) ) // diffLayer represents a collection of modifications made to a state snapshot @@ -33,8 +72,9 @@ import ( // The goal of a diff layer is to act as a journal, tracking recent modifications // made to the state, that have not yet graduated into a semi-immutable state. type diffLayer struct { - parent snapshot // Parent snapshot modified by this one, never nil - memory uint64 // Approximate guess as to how much memory we use + origin *diskLayer // Base disk layer to directly use on bloom misses + parent snapshot // Parent snapshot modified by this one, never nil + memory uint64 // Approximate guess as to how much memory we use root common.Hash // Root hash to which this snapshot diff belongs to stale bool // Signals that the layer became stale (state progressed) @@ -44,9 +84,39 @@ type diffLayer struct { storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil storageData map[common.Hash]map[common.Hash][]byte // Keyed storage slots for direct retrival. one per account (nil means deleted) + diffed *bloomfilter.Filter // Bloom filter tracking all the diffed items up to the disk layer + lock sync.RWMutex } +// accountBloomHasher is a wrapper around a common.Hash to satisfy the interface +// API requirements of the bloom library used. It's used to convert an account +// hash into a 64 bit mini hash. +type accountBloomHasher common.Hash + +func (h accountBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (h accountBloomHasher) Sum(b []byte) []byte { panic("not implemented") } +func (h accountBloomHasher) Reset() { panic("not implemented") } +func (h accountBloomHasher) BlockSize() int { panic("not implemented") } +func (h accountBloomHasher) Size() int { return 8 } +func (h accountBloomHasher) Sum64() uint64 { + return binary.BigEndian.Uint64(h[:8]) +} + +// storageBloomHasher is a wrapper around a [2]common.Hash to satisfy the interface +// API requirements of the bloom library used. It's used to convert an account +// hash into a 64 bit mini hash. +type storageBloomHasher [2]common.Hash + +func (h storageBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (h storageBloomHasher) Sum(b []byte) []byte { panic("not implemented") } +func (h storageBloomHasher) Reset() { panic("not implemented") } +func (h storageBloomHasher) BlockSize() int { panic("not implemented") } +func (h storageBloomHasher) Size() int { return 8 } +func (h storageBloomHasher) Sum64() uint64 { + return binary.BigEndian.Uint64(h[0][:8]) ^ binary.BigEndian.Uint64(h[1][:8]) +} + // newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low // level persistent database or a hierarchical diff already. func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { @@ -57,9 +127,18 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] accountData: accounts, storageData: storage, } - // Determine mem size + switch parent := parent.(type) { + case *diskLayer: + dl.rebloom(parent) + case *diffLayer: + dl.rebloom(parent.origin) + default: + panic("unknown parent type") + } + // Determine memory size and track the dirty writes for _, data := range accounts { - dl.memory += uint64(len(data)) + dl.memory += uint64(common.HashLength + len(data)) + snapshotDirtyAccountWriteMeter.Mark(int64(len(data))) } // Fill the storage hashes and sort them for the iterator dl.storageList = make(map[common.Hash][]common.Hash) @@ -80,16 +159,56 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] if account, ok := accounts[accountHash]; account == nil || !ok { log.Error(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) } - // Determine mem size + // Determine memory size and track the dirty writes for _, data := range slots { - dl.memory += uint64(len(data)) + dl.memory += uint64(common.HashLength + len(data)) + snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) } } dl.memory += uint64(len(dl.storageList) * common.HashLength) - return dl } +// rebloom discards the layer's current bloom and rebuilds it from scratch based +// on the parent's and the local diffs. +func (dl *diffLayer) rebloom(origin *diskLayer) { + dl.lock.Lock() + defer dl.lock.Unlock() + + defer func(start time.Time) { + snapshotBloomIndexTimer.Update(time.Since(start)) + }(time.Now()) + + // Inject the new origin that triggered the rebloom + dl.origin = origin + + // Retrieve the parent bloom or create a fresh empty one + if parent, ok := dl.parent.(*diffLayer); ok { + parent.lock.RLock() + dl.diffed, _ = parent.diffed.Copy() + parent.lock.RUnlock() + } else { + dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs)) + } + // Iterate over all the accounts and storage slots and index them + for hash := range dl.accountData { + dl.diffed.Add(accountBloomHasher(hash)) + } + for accountHash, slots := range dl.storageData { + for storageHash := range slots { + dl.diffed.Add(storageBloomHasher{accountHash, storageHash}) + } + } + // Calculate the current false positive rate and update the error rate meter. + // This is a bit cheating because subsequent layers will overwrite it, but it + // should be fine, we're only interested in ballpark figures. + k := float64(dl.diffed.K()) + n := float64(dl.diffed.N()) + m := float64(dl.diffed.M()) + + snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)) +} + // Root returns the root hash for which this snapshot was made. func (dl *diffLayer) Root() common.Hash { return dl.root @@ -124,6 +243,26 @@ func (dl *diffLayer) Account(hash common.Hash) (*Account, error) { // AccountRLP directly retrieves the account RLP associated with a particular // hash in the snapshot slim data format. func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { + // Check the bloom filter first whether there's even a point in reaching into + // all the maps in all the layers below + dl.lock.RLock() + hit := dl.diffed.Contains(accountBloomHasher(hash)) + dl.lock.RUnlock() + + // If the bloom filter misses, don't even bother with traversing the memory + // diff layers, reach straight into the bottom persistent disk layer + if !hit { + snapshotBloomAccountMissMeter.Mark(1) + return dl.origin.AccountRLP(hash) + } + // The bloom filter hit, start poking in the internal maps + return dl.accountRLP(hash) +} + +// accountRLP is an internal version of AccountRLP that skips the bloom filter +// checks and uses the internal maps to try and retrieve the data. It's meant +// to be used if a higher layer's bloom filter hit already. +func (dl *diffLayer) accountRLP(hash common.Hash) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() @@ -135,9 +274,17 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { // If the account is known locally, return it. Note, a nil account means it was // deleted, and is a different notion than an unknown account! if data, ok := dl.accountData[hash]; ok { + snapshotDirtyAccountHitMeter.Mark(1) + snapshotDirtyAccountReadMeter.Mark(int64(len(data))) + snapshotBloomAccountTrueHitMeter.Mark(1) return data, nil } // Account unknown to this diff, resolve from parent + if diff, ok := dl.parent.(*diffLayer); ok { + return diff.accountRLP(hash) + } + // Failed to resolve through diff layers, mark a bloom error and use the disk + snapshotBloomAccountFalseHitMeter.Mark(1) return dl.parent.AccountRLP(hash) } @@ -145,6 +292,26 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { // within a particular account. If the slot is unknown to this diff, it's parent // is consulted. func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { + // Check the bloom filter first whether there's even a point in reaching into + // all the maps in all the layers below + dl.lock.RLock() + hit := dl.diffed.Contains(storageBloomHasher{accountHash, storageHash}) + dl.lock.RUnlock() + + // If the bloom filter misses, don't even bother with traversing the memory + // diff layers, reach straight into the bottom persistent disk layer + if !hit { + snapshotBloomStorageMissMeter.Mark(1) + return dl.origin.Storage(accountHash, storageHash) + } + // The bloom filter hit, start poking in the internal maps + return dl.storage(accountHash, storageHash) +} + +// storage is an internal version of Storage that skips the bloom filter checks +// and uses the internal maps to try and retrieve the data. It's meant to be +// used if a higher layer's bloom filter hit already. +func (dl *diffLayer) storage(accountHash, storageHash common.Hash) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() @@ -157,13 +324,23 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // account means it was deleted, and is a different notion than an unknown account! if storage, ok := dl.storageData[accountHash]; ok { if storage == nil { + snapshotDirtyStorageHitMeter.Mark(1) + snapshotBloomStorageTrueHitMeter.Mark(1) return nil, nil } if data, ok := storage[storageHash]; ok { + snapshotDirtyStorageHitMeter.Mark(1) + snapshotDirtyStorageReadMeter.Mark(int64(len(data))) + snapshotBloomStorageTrueHitMeter.Mark(1) return data, nil } } - // Account - or slot within - unknown to this diff, resolve from parent + // Storage slot unknown to this diff, resolve from parent + if diff, ok := dl.parent.(*diffLayer); ok { + return diff.storage(accountHash, storageHash) + } + // Failed to resolve through diff layers, mark a bloom error and use the disk + snapshotBloomStorageFalseHitMeter.Mark(1) return dl.parent.Storage(accountHash, storageHash) } @@ -224,22 +401,11 @@ func (dl *diffLayer) flatten() snapshot { storageData: parent.storageData, accountList: parent.accountList, accountData: parent.accountData, + diffed: dl.diffed, memory: parent.memory + dl.memory, } } -// Journal commits an entire diff hierarchy to disk into a single journal file. -// This is meant to be used during shutdown to persist the snapshot without -// flattening everything down (bad for reorgs). -func (dl *diffLayer) Journal() error { - writer, err := dl.journal() - if err != nil { - return err - } - writer.Close() - return nil -} - // AccountList returns a sorted list of all accounts in this difflayer. func (dl *diffLayer) AccountList() []common.Hash { dl.lock.Lock() diff --git a/core/state/snapshot/difflayer_journal.go b/core/state/snapshot/difflayer_journal.go deleted file mode 100644 index 5490531be5..0000000000 --- a/core/state/snapshot/difflayer_journal.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package snapshot - -import ( - "bufio" - "fmt" - "io" - "os" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rlp" -) - -// journalAccount is an account entry in a diffLayer's disk journal. -type journalAccount struct { - Hash common.Hash - Blob []byte -} - -// journalStorage is an account's storage map in a diffLayer's disk journal. -type journalStorage struct { - Hash common.Hash - Keys []common.Hash - Vals [][]byte -} - -// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new -// diff and verifying that it can be linked to the requested parent. -func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { - // Read the next diff journal entry - var root common.Hash - if err := r.Decode(&root); err != nil { - // The first read may fail with EOF, marking the end of the journal - if err == io.EOF { - return parent, nil - } - return nil, fmt.Errorf("load diff root: %v", err) - } - var accounts []journalAccount - if err := r.Decode(&accounts); err != nil { - return nil, fmt.Errorf("load diff accounts: %v", err) - } - accountData := make(map[common.Hash][]byte) - for _, entry := range accounts { - accountData[entry.Hash] = entry.Blob - } - var storage []journalStorage - if err := r.Decode(&storage); err != nil { - return nil, fmt.Errorf("load diff storage: %v", err) - } - storageData := make(map[common.Hash]map[common.Hash][]byte) - for _, entry := range storage { - slots := make(map[common.Hash][]byte) - for i, key := range entry.Keys { - slots[key] = entry.Vals[i] - } - storageData[entry.Hash] = slots - } - return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) -} - -// journal is the internal version of Journal that also returns the journal file -// so subsequent layers know where to write to. -func (dl *diffLayer) journal() (io.WriteCloser, error) { - // If we've reached the bottom, open the journal - var writer io.WriteCloser - if parent, ok := dl.parent.(*diskLayer); ok { - file, err := os.Create(parent.journal) - if err != nil { - return nil, err - } - writer = file - } - // If we haven't reached the bottom yet, journal the parent first - if writer == nil { - file, err := dl.parent.(*diffLayer).journal() - if err != nil { - return nil, err - } - writer = file - } - dl.lock.RLock() - defer dl.lock.RUnlock() - - if dl.stale { - writer.Close() - return nil, ErrSnapshotStale - } - // Everything below was journalled, persist this layer too - buf := bufio.NewWriter(writer) - if err := rlp.Encode(buf, dl.root); err != nil { - buf.Flush() - writer.Close() - return nil, err - } - accounts := make([]journalAccount, 0, len(dl.accountData)) - for hash, blob := range dl.accountData { - accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) - } - if err := rlp.Encode(buf, accounts); err != nil { - buf.Flush() - writer.Close() - return nil, err - } - storage := make([]journalStorage, 0, len(dl.storageData)) - for hash, slots := range dl.storageData { - keys := make([]common.Hash, 0, len(slots)) - vals := make([][]byte, 0, len(slots)) - for key, val := range slots { - keys = append(keys, key) - vals = append(vals, val) - } - storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) - } - if err := rlp.Encode(buf, storage); err != nil { - buf.Flush() - writer.Close() - return nil, err - } - buf.Flush() - return writer, nil -} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 7cd1e8062d..9029bb04b8 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -24,7 +24,9 @@ import ( "path" "testing" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/rlp" ) @@ -61,7 +63,7 @@ func TestMergeBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) child := newDiffLayer(parent, common.Hash{}, accounts, storage) child = newDiffLayer(child, common.Hash{}, accounts, storage) child = newDiffLayer(child, common.Hash{}, accounts, storage) @@ -122,7 +124,7 @@ func TestMergeDelete(t *testing.T) { } // Add some flip-flopping layers on top - parent := newDiffLayer(emptyLayer{}, common.Hash{}, flip(), storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, flip(), storage) child := parent.Update(common.Hash{}, flop(), storage) child = child.Update(common.Hash{}, flip(), storage) child = child.Update(common.Hash{}, flop(), storage) @@ -165,7 +167,7 @@ func TestInsertAndMerge(t *testing.T) { { var accounts = make(map[common.Hash][]byte) var storage = make(map[common.Hash]map[common.Hash][]byte) - parent = newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) + parent = newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) } { var accounts = make(map[common.Hash][]byte) @@ -186,34 +188,11 @@ func TestInsertAndMerge(t *testing.T) { } } -type emptyLayer struct{} - -func (emptyLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { - panic("implement me") -} - -func (emptyLayer) Journal() error { - panic("implement me") -} - -func (emptyLayer) Stale() bool { - panic("implement me") -} - -func (emptyLayer) Root() common.Hash { - return common.Hash{} -} - -func (emptyLayer) Account(hash common.Hash) (*Account, error) { - return nil, nil -} - -func (emptyLayer) AccountRLP(hash common.Hash) ([]byte, error) { - return nil, nil -} - -func (emptyLayer) Storage(accountHash, storageHash common.Hash) ([]byte, error) { - return nil, nil +func emptyLayer() *diskLayer { + return &diskLayer{ + diskdb: memorydb.New(), + cache: fastcache.New(500 * 1024), + } } // BenchmarkSearch checks how long it takes to find a non-existing key @@ -234,7 +213,7 @@ func BenchmarkSearch(b *testing.B) { return newDiffLayer(parent, common.Hash{}, accounts, storage) } var layer snapshot - layer = emptyLayer{} + layer = emptyLayer() for i := 0; i < 128; i++ { layer = fill(layer) } @@ -272,7 +251,7 @@ func BenchmarkSearchSlot(b *testing.B) { return newDiffLayer(parent, common.Hash{}, accounts, storage) } var layer snapshot - layer = emptyLayer{} + layer = emptyLayer() for i := 0; i < 128; i++ { layer = fill(layer) } @@ -313,7 +292,7 @@ func BenchmarkFlatten(b *testing.B) { for i := 0; i < b.N; i++ { b.StopTimer() var layer snapshot - layer = emptyLayer{} + layer = emptyLayer() for i := 1; i < 128; i++ { layer = fill(layer) } @@ -357,17 +336,14 @@ func BenchmarkJournal(b *testing.B) { } return newDiffLayer(parent, common.Hash{}, accounts, storage) } - var layer snapshot - layer = &diskLayer{ - journal: path.Join(os.TempDir(), "difflayer_journal.tmp"), - } + layer := snapshot(new(diskLayer)) for i := 1; i < 128; i++ { layer = fill(layer) } b.ResetTimer() for i := 0; i < b.N; i++ { - f, _ := layer.(*diffLayer).journal() + f, _, _ := layer.Journal(path.Join(os.TempDir(), "difflayer_journal.tmp")) f.Close() } } diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 474182f1dd..b1934d2739 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -17,6 +17,7 @@ package snapshot import ( + "bytes" "sync" "github.com/VictoriaMetrics/fastcache" @@ -24,17 +25,21 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) // diskLayer is a low level persistent snapshot built on top of a key-value store. type diskLayer struct { - journal string // Path of the snapshot journal to use on shutdown - db ethdb.KeyValueStore // Key-value store containing the base snapshot - cache *fastcache.Cache // Cache to avoid hitting the disk for direct access + diskdb ethdb.KeyValueStore // Key-value store containing the base snapshot + triedb *trie.Database // Trie node cache for reconstuction purposes + cache *fastcache.Cache // Cache to avoid hitting the disk for direct access root common.Hash // Root hash of the base snapshot stale bool // Signals that the layer became stale (state progressed) + genMarker []byte // Marker for the state that's indexed during initial layer generation + genAbort chan chan *generatorStats // Notification channel to abort generating the snapshot in this layer + lock sync.RWMutex } @@ -80,18 +85,26 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { if dl.stale { return nil, ErrSnapshotStale } + // If the layer is being generated, ensure the requested hash has already been + // covered by the generator. + if dl.genMarker != nil && bytes.Compare(hash[:], dl.genMarker) > 0 { + return nil, ErrNotCoveredYet + } + // If we're in the disk layer, all diff layers missed + snapshotDirtyAccountMissMeter.Mark(1) + // Try to retrieve the account from the memory cache - if blob := dl.cache.Get(nil, hash[:]); blob != nil { - snapshotCleanHitMeter.Mark(1) - snapshotCleanReadMeter.Mark(int64(len(blob))) + if blob, found := dl.cache.HasGet(nil, hash[:]); found { + snapshotCleanAccountHitMeter.Mark(1) + snapshotCleanAccountReadMeter.Mark(int64(len(blob))) return blob, nil } // Cache doesn't contain account, pull from disk and cache for later - blob := rawdb.ReadAccountSnapshot(dl.db, hash) + blob := rawdb.ReadAccountSnapshot(dl.diskdb, hash) dl.cache.Set(hash[:], blob) - snapshotCleanMissMeter.Mark(1) - snapshotCleanWriteMeter.Mark(int64(len(blob))) + snapshotCleanAccountMissMeter.Mark(1) + snapshotCleanAccountWriteMeter.Mark(int64(len(blob))) return blob, nil } @@ -109,18 +122,26 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro } key := append(accountHash[:], storageHash[:]...) + // If the layer is being generated, ensure the requested hash has already been + // covered by the generator. + if dl.genMarker != nil && bytes.Compare(key, dl.genMarker) > 0 { + return nil, ErrNotCoveredYet + } + // If we're in the disk layer, all diff layers missed + snapshotDirtyStorageMissMeter.Mark(1) + // Try to retrieve the storage slot from the memory cache - if blob := dl.cache.Get(nil, key); blob != nil { - snapshotCleanHitMeter.Mark(1) - snapshotCleanReadMeter.Mark(int64(len(blob))) + if blob, found := dl.cache.HasGet(nil, key); found { + snapshotCleanStorageHitMeter.Mark(1) + snapshotCleanStorageReadMeter.Mark(int64(len(blob))) return blob, nil } // Cache doesn't contain storage slot, pull from disk and cache for later - blob := rawdb.ReadStorageSnapshot(dl.db, accountHash, storageHash) + blob := rawdb.ReadStorageSnapshot(dl.diskdb, accountHash, storageHash) dl.cache.Set(key, blob) - snapshotCleanMissMeter.Mark(1) - snapshotCleanWriteMeter.Mark(int64(len(blob))) + snapshotCleanStorageMissMeter.Mark(1) + snapshotCleanStorageWriteMeter.Mark(int64(len(blob))) return blob, nil } @@ -131,9 +152,3 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { return newDiffLayer(dl, blockHash, accounts, storage) } - -// Journal commits an entire diff hierarchy to disk into a single journal file. -func (dl *diskLayer) Journal() error { - // There's no journalling a disk layer - return nil -} diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go new file mode 100644 index 0000000000..30b6904546 --- /dev/null +++ b/core/state/snapshot/disklayer_test.go @@ -0,0 +1,433 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "testing" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb/memorydb" +) + +// reverse reverses the contents of a byte slice. It's used to update random accs +// with deterministic changes. +func reverse(blob []byte) []byte { + res := make([]byte, len(blob)) + for i, b := range blob { + res[len(blob)-1-i] = b + } + return res +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values. +func TestDiskMerge(t *testing.T) { + // Create some accounts in the disk layer + db := memorydb.New() + + var ( + accNoModNoCache = common.Hash{0x1} + accNoModCache = common.Hash{0x2} + accModNoCache = common.Hash{0x3} + accModCache = common.Hash{0x4} + accDelNoCache = common.Hash{0x5} + accDelCache = common.Hash{0x6} + conNoModNoCache = common.Hash{0x7} + conNoModNoCacheSlot = common.Hash{0x70} + conNoModCache = common.Hash{0x8} + conNoModCacheSlot = common.Hash{0x80} + conModNoCache = common.Hash{0x9} + conModNoCacheSlot = common.Hash{0x90} + conModCache = common.Hash{0xa} + conModCacheSlot = common.Hash{0xa0} + conDelNoCache = common.Hash{0xb} + conDelNoCacheSlot = common.Hash{0xb0} + conDelCache = common.Hash{0xc} + conDelCacheSlot = common.Hash{0xc0} + conNukeNoCache = common.Hash{0xd} + conNukeNoCacheSlot = common.Hash{0xd0} + conNukeCache = common.Hash{0xe} + conNukeCacheSlot = common.Hash{0xe0} + baseRoot = randomHash() + diffRoot = randomHash() + ) + + rawdb.WriteAccountSnapshot(db, accNoModNoCache, accNoModNoCache[:]) + rawdb.WriteAccountSnapshot(db, accNoModCache, accNoModCache[:]) + rawdb.WriteAccountSnapshot(db, accModNoCache, accModNoCache[:]) + rawdb.WriteAccountSnapshot(db, accModCache, accModCache[:]) + rawdb.WriteAccountSnapshot(db, accDelNoCache, accDelNoCache[:]) + rawdb.WriteAccountSnapshot(db, accDelCache, accDelCache[:]) + + rawdb.WriteAccountSnapshot(db, conNoModNoCache, conNoModNoCache[:]) + rawdb.WriteStorageSnapshot(db, conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conNoModCache, conNoModCache[:]) + rawdb.WriteStorageSnapshot(db, conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conModNoCache, conModNoCache[:]) + rawdb.WriteStorageSnapshot(db, conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conModCache, conModCache[:]) + rawdb.WriteStorageSnapshot(db, conModCache, conModCacheSlot, conModCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conDelNoCache, conDelNoCache[:]) + rawdb.WriteStorageSnapshot(db, conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conDelCache, conDelCache[:]) + rawdb.WriteStorageSnapshot(db, conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + + rawdb.WriteAccountSnapshot(db, conNukeNoCache, conNukeNoCache[:]) + rawdb.WriteStorageSnapshot(db, conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) + rawdb.WriteAccountSnapshot(db, conNukeCache, conNukeCache[:]) + rawdb.WriteStorageSnapshot(db, conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on the above and cache in some data + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + base := snaps.Snapshot(baseRoot) + base.AccountRLP(accNoModCache) + base.AccountRLP(accModCache) + base.AccountRLP(accDelCache) + base.Storage(conNoModCache, conNoModCacheSlot) + base.Storage(conModCache, conModCacheSlot) + base.Storage(conDelCache, conDelCacheSlot) + base.Storage(conNukeCache, conNukeCacheSlot) + + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), + accDelNoCache: nil, + accDelCache: nil, + conNukeNoCache: nil, + conNukeCache: nil, + }, map[common.Hash]map[common.Hash][]byte{ + conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, + conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, + conDelNoCache: {conDelNoCacheSlot: nil}, + conDelCache: {conDelCacheSlot: nil}, + }); err != nil { + t.Fatalf("failed to update snapshot tree: %v", err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("failed to flatten snapshot tree: %v", err) + } + // Retrieve all the data through the disk layer and validate it + base = snaps.Snapshot(diffRoot) + if _, ok := base.(*diskLayer); !ok { + t.Fatalf("update not flattend into the disk layer") + } + + // assertAccount ensures that an account matches the given blob. + assertAccount := func(account common.Hash, data []byte) { + t.Helper() + blob, err := base.AccountRLP(account) + if err != nil { + t.Errorf("account access (%x) failed: %v", account, err) + } else if !bytes.Equal(blob, data) { + t.Errorf("account access (%x) mismatch: have %x, want %x", account, blob, data) + } + } + assertAccount(accNoModNoCache, accNoModNoCache[:]) + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModNoCache, reverse(accModNoCache[:])) + assertAccount(accModCache, reverse(accModCache[:])) + assertAccount(accDelNoCache, nil) + assertAccount(accDelCache, nil) + + // assertStorage ensures that a storage slot matches the given blob. + assertStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob, err := base.Storage(account, slot) + if err != nil { + t.Errorf("storage access (%x:%x) failed: %v", account, slot, err) + } else if !bytes.Equal(blob, data) { + t.Errorf("storage access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) + } + } + assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertStorage(conDelCache, conDelCacheSlot, nil) + assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertStorage(conNukeCache, conNukeCacheSlot, nil) + + // Retrieve all the data directly from the database and validate it + + // assertDatabaseAccount ensures that an account from the database matches the given blob. + assertDatabaseAccount := func(account common.Hash, data []byte) { + t.Helper() + if blob := rawdb.ReadAccountSnapshot(db, account); !bytes.Equal(blob, data) { + t.Errorf("account database access (%x) mismatch: have %x, want %x", account, blob, data) + } + } + assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) + assertDatabaseAccount(accNoModCache, accNoModCache[:]) + assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) + assertDatabaseAccount(accModCache, reverse(accModCache[:])) + assertDatabaseAccount(accDelNoCache, nil) + assertDatabaseAccount(accDelCache, nil) + + // assertDatabaseStorage ensures that a storage slot from the database matches the given blob. + assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + if blob := rawdb.ReadStorageSnapshot(db, account, slot); !bytes.Equal(blob, data) { + t.Errorf("storage database access (%x:%x) mismatch: have %x, want %x", account, slot, blob, data) + } + } + assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) + assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values, discarding anything +// after the in-progress generation marker. +func TestDiskPartialMerge(t *testing.T) { + // Iterate the test a few times to ensure we pick various internal orderings + // for the data slots as well as the progress marker. + for i := 0; i < 1024; i++ { + // Create some accounts in the disk layer + db := memorydb.New() + + var ( + accNoModNoCache = randomHash() + accNoModCache = randomHash() + accModNoCache = randomHash() + accModCache = randomHash() + accDelNoCache = randomHash() + accDelCache = randomHash() + conNoModNoCache = randomHash() + conNoModNoCacheSlot = randomHash() + conNoModCache = randomHash() + conNoModCacheSlot = randomHash() + conModNoCache = randomHash() + conModNoCacheSlot = randomHash() + conModCache = randomHash() + conModCacheSlot = randomHash() + conDelNoCache = randomHash() + conDelNoCacheSlot = randomHash() + conDelCache = randomHash() + conDelCacheSlot = randomHash() + conNukeNoCache = randomHash() + conNukeNoCacheSlot = randomHash() + conNukeCache = randomHash() + conNukeCacheSlot = randomHash() + baseRoot = randomHash() + diffRoot = randomHash() + genMarker = append(randomHash().Bytes(), randomHash().Bytes()...) + ) + + // insertAccount injects an account into the database if it's after the + // generator marker, drops the op otherwise. This is needed to seed the + // database with a valid starting snapshot. + insertAccount := func(account common.Hash, data []byte) { + if bytes.Compare(account[:], genMarker) <= 0 { + rawdb.WriteAccountSnapshot(db, account, data[:]) + } + } + insertAccount(accNoModNoCache, accNoModNoCache[:]) + insertAccount(accNoModCache, accNoModCache[:]) + insertAccount(accModNoCache, accModNoCache[:]) + insertAccount(accModCache, accModCache[:]) + insertAccount(accDelNoCache, accDelNoCache[:]) + insertAccount(accDelCache, accDelCache[:]) + + // insertStorage injects a storage slot into the database if it's after + // the generator marker, drops the op otherwise. This is needed to seed + // the database with a valid starting snapshot. + insertStorage := func(account common.Hash, slot common.Hash, data []byte) { + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 { + rawdb.WriteStorageSnapshot(db, account, slot, data[:]) + } + } + insertAccount(conNoModNoCache, conNoModNoCache[:]) + insertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + insertAccount(conNoModCache, conNoModCache[:]) + insertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + insertAccount(conModNoCache, conModNoCache[:]) + insertStorage(conModNoCache, conModNoCacheSlot, conModNoCacheSlot[:]) + insertAccount(conModCache, conModCache[:]) + insertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) + insertAccount(conDelNoCache, conDelNoCache[:]) + insertStorage(conDelNoCache, conDelNoCacheSlot, conDelNoCacheSlot[:]) + insertAccount(conDelCache, conDelCache[:]) + insertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + + insertAccount(conNukeNoCache, conNukeNoCache[:]) + insertStorage(conNukeNoCache, conNukeNoCacheSlot, conNukeNoCacheSlot[:]) + insertAccount(conNukeCache, conNukeCache[:]) + insertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + rawdb.WriteSnapshotRoot(db, baseRoot) + + // Create a disk layer based on the above using a random progress marker + // and cache in some data. + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + snaps.layers[baseRoot].(*diskLayer).genMarker = genMarker + base := snaps.Snapshot(baseRoot) + + // assertAccount ensures that an account matches the given blob if it's + // already covered by the disk snapshot, and errors out otherwise. + assertAccount := func(account common.Hash, data []byte) { + t.Helper() + blob, err := base.AccountRLP(account) + if bytes.Compare(account[:], genMarker) > 0 && err != ErrNotCoveredYet { + t.Fatalf("test %d: post-marker (%x) account access (%x) succeded: %x", i, genMarker, account, blob) + } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) account access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) + } + } + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModCache, accModCache[:]) + assertAccount(accDelCache, accDelCache[:]) + + // assertStorage ensures that a storage slot matches the given blob if + // it's already covered by the disk snapshot, and errors out otherwise. + assertStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob, err := base.Storage(account, slot) + if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && err != ErrNotCoveredYet { + t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeded: %x", i, genMarker, account, slot, blob) + } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) storage access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) + } + } + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModCache, conModCacheSlot, conModCacheSlot[:]) + assertStorage(conDelCache, conDelCacheSlot, conDelCacheSlot[:]) + assertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) + + // Modify or delete some accounts, flatten everything onto disk + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), + accDelNoCache: nil, + accDelCache: nil, + conNukeNoCache: nil, + conNukeCache: nil, + }, map[common.Hash]map[common.Hash][]byte{ + conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, + conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, + conDelNoCache: {conDelNoCacheSlot: nil}, + conDelCache: {conDelCacheSlot: nil}, + }); err != nil { + t.Fatalf("test %d: failed to update snapshot tree: %v", i, err) + } + if err := snaps.Cap(diffRoot, 0); err != nil { + t.Fatalf("test %d: failed to flatten snapshot tree: %v", i, err) + } + // Retrieve all the data through the disk layer and validate it + base = snaps.Snapshot(diffRoot) + if _, ok := base.(*diskLayer); !ok { + t.Fatalf("test %d: update not flattend into the disk layer", i) + } + assertAccount(accNoModNoCache, accNoModNoCache[:]) + assertAccount(accNoModCache, accNoModCache[:]) + assertAccount(accModNoCache, reverse(accModNoCache[:])) + assertAccount(accModCache, reverse(accModCache[:])) + assertAccount(accDelNoCache, nil) + assertAccount(accDelCache, nil) + + assertStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertStorage(conDelCache, conDelCacheSlot, nil) + assertStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertStorage(conNukeCache, conNukeCacheSlot, nil) + + // Retrieve all the data directly from the database and validate it + + // assertDatabaseAccount ensures that an account inside the database matches + // the given blob if it's already covered by the disk snapshot, and does not + // exist otherwise. + assertDatabaseAccount := func(account common.Hash, data []byte) { + t.Helper() + blob := rawdb.ReadAccountSnapshot(db, account) + if bytes.Compare(account[:], genMarker) > 0 && blob != nil { + t.Fatalf("test %d: post-marker (%x) account database access (%x) succeded: %x", i, genMarker, account, blob) + } + if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) account database access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) + } + } + assertDatabaseAccount(accNoModNoCache, accNoModNoCache[:]) + assertDatabaseAccount(accNoModCache, accNoModCache[:]) + assertDatabaseAccount(accModNoCache, reverse(accModNoCache[:])) + assertDatabaseAccount(accModCache, reverse(accModCache[:])) + assertDatabaseAccount(accDelNoCache, nil) + assertDatabaseAccount(accDelCache, nil) + + // assertDatabaseStorage ensures that a storage slot inside the database + // matches the given blob if it's already covered by the disk snapshot, + // and does not exist otherwise. + assertDatabaseStorage := func(account common.Hash, slot common.Hash, data []byte) { + t.Helper() + blob := rawdb.ReadStorageSnapshot(db, account, slot) + if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && blob != nil { + t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeded: %x", i, genMarker, account, slot, blob) + } + if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { + t.Fatalf("test %d: pre-marker (%x) storage database access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) + } + } + assertDatabaseStorage(conNoModNoCache, conNoModNoCacheSlot, conNoModNoCacheSlot[:]) + assertDatabaseStorage(conNoModCache, conNoModCacheSlot, conNoModCacheSlot[:]) + assertDatabaseStorage(conModNoCache, conModNoCacheSlot, reverse(conModNoCacheSlot[:])) + assertDatabaseStorage(conModCache, conModCacheSlot, reverse(conModCacheSlot[:])) + assertDatabaseStorage(conDelNoCache, conDelNoCacheSlot, nil) + assertDatabaseStorage(conDelCache, conDelCacheSlot, nil) + assertDatabaseStorage(conNukeNoCache, conNukeNoCacheSlot, nil) + assertDatabaseStorage(conNukeCache, conNukeCacheSlot, nil) + } +} + +// Tests that merging something into a disk layer persists it into the database +// and invalidates any previously written and cached values, discarding anything +// after the in-progress generation marker. +// +// This test case is a tiny specialized case of TestDiskPartialMerge, which tests +// some very specific cornercases that random tests won't ever trigger. +func TestDiskMidAccountPartialMerge(t *testing.T) { +} diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 445a6ebd9f..0f9e5fae59 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -18,12 +18,13 @@ package snapshot import ( "bytes" - "fmt" + "encoding/binary" "math/big" "time" "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -40,103 +41,122 @@ var ( emptyCode = crypto.Keccak256Hash(nil) ) -// wipeSnapshot iterates over the entire key-value database and deletes all the -// data associated with the snapshot (accounts, storage, metadata). After all is -// done, the snapshot range of the database is compacted to free up unused data -// blocks. -func wipeSnapshot(db ethdb.KeyValueStore) error { - // Batch deletions together to avoid holding an iterator for too long - var ( - batch = db.NewBatch() - items int - ) - // Iterate over the snapshot key-range and delete all of them - log.Info("Deleting previous snapshot leftovers") - start, logged := time.Now(), time.Now() - - it := db.NewIteratorWithStart(rawdb.StateSnapshotPrefix) - for it.Next() { - // Skip any keys with the correct prefix but wrong lenth (trie nodes) - key := it.Key() - if !bytes.HasPrefix(key, rawdb.StateSnapshotPrefix) { - break - } - if len(key) != len(rawdb.StateSnapshotPrefix)+common.HashLength && len(key) != len(rawdb.StateSnapshotPrefix)+2*common.HashLength { - continue - } - // Delete the key and periodically recreate the batch and iterator - batch.Delete(key) - items++ - - if items%10000 == 0 { - // Batch too large (or iterator too long lived, flush and recreate) - it.Release() - if err := batch.Write(); err != nil { - return err - } - batch.Reset() - it = db.NewIteratorWithStart(key) - - if time.Since(logged) > 8*time.Second { - log.Info("Deleting previous snapshot leftovers", "wiped", items, "elapsed", time.Since(start)) - logged = time.Now() - } - } - } - it.Release() - - rawdb.DeleteSnapshotRoot(batch) - if err := batch.Write(); err != nil { - return err - } - log.Info("Deleted previous snapshot leftovers", "wiped", items, "elapsed", time.Since(start)) - - // Compact the snapshot section of the database to get rid of unused space - log.Info("Compacting snapshot area in database") - start = time.Now() - - end := common.CopyBytes(rawdb.StateSnapshotPrefix) - end[len(end)-1]++ - - if err := db.Compact(rawdb.StateSnapshotPrefix, end); err != nil { - return err - } - log.Info("Compacted snapshot area in database", "elapsed", time.Since(start)) - - return nil +// generatorStats is a collection of statistics gathered by the snapshot generator +// for logging purposes. +type generatorStats struct { + wiping chan struct{} // Notification channel if wiping is in progress + origin uint64 // Origin prefix where generation started + start time.Time // Timestamp when generation started + accounts uint64 // Number of accounts indexed + slots uint64 // Number of storage slots indexed + storage common.StorageSize // Account and storage slot size } -// generateSnapshot regenerates a brand new snapshot based on an existing state database and head block. -func generateSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) (snapshot, error) { - // Wipe any previously existing snapshot from the database - if err := wipeSnapshot(db); err != nil { - return nil, err - } - // Iterate the entire storage trie and re-generate the state snapshot - var ( - accountCount int - storageCount int - storageNodes int - accountSize common.StorageSize - storageSize common.StorageSize - logged time.Time - ) - batch := db.NewBatch() - triedb := trie.NewDatabase(db) +// Log creates an contextual log with the given message and the context pulled +// from the internally maintained statistics. +func (gs *generatorStats) Log(msg string, marker []byte) { + var ctx []interface{} - accTrie, err := trie.NewSecure(root, triedb) - if err != nil { - return nil, err + // Figure out whether we're after or within an account + switch len(marker) { + case common.HashLength: + ctx = append(ctx, []interface{}{"at", common.BytesToHash(marker)}...) + case 2 * common.HashLength: + ctx = append(ctx, []interface{}{ + "in", common.BytesToHash(marker[:common.HashLength]), + "at", common.BytesToHash(marker[common.HashLength:]), + }...) } - accIt := trie.NewIterator(accTrie.NodeIterator(nil)) + // Add the usual measurements + ctx = append(ctx, []interface{}{ + "accounts", gs.accounts, + "slots", gs.slots, + "storage", gs.storage, + "elapsed", common.PrettyDuration(time.Since(gs.start)), + }...) + // Calculate the estimated indexing time based on current stats + if len(marker) > 0 { + if done := binary.BigEndian.Uint64(marker[:8]) - gs.origin; done > 0 { + left := math.MaxUint64 - binary.BigEndian.Uint64(marker[:8]) + + speed := done/uint64(time.Since(gs.start)/time.Millisecond+1) + 1 // +1s to avoid division by zero + ctx = append(ctx, []interface{}{ + "eta", common.PrettyDuration(time.Duration(left/speed) * time.Millisecond), + }...) + } + } + log.Info(msg, ctx...) +} + +// generateSnapshot regenerates a brand new snapshot based on an existing state +// database and head block asynchronously. The snapshot is returned immediately +// and generation is continued in the background until done. +func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, wiper chan struct{}) *diskLayer { + // Wipe any previously existing snapshot from the database if no wiper is + // currenty in progress. + if wiper == nil { + wiper = wipeSnapshot(diskdb, true) + } + // Create a new disk layer with an initialized state marker at zero + rawdb.WriteSnapshotRoot(diskdb, root) + + base := &diskLayer{ + diskdb: diskdb, + triedb: triedb, + root: root, + cache: fastcache.New(cache * 1024 * 1024), + genMarker: []byte{}, // Initialized but empty! + genAbort: make(chan chan *generatorStats), + } + go base.generate(&generatorStats{wiping: wiper, start: time.Now()}) + return base +} + +// generate is a background thread that iterates over the state and storage tries, +// constructing the state snapshot. All the arguments are purely for statistics +// gethering and logging, since the method surfs the blocks as they arrive, often +// being restarted. +func (dl *diskLayer) generate(stats *generatorStats) { + // If a database wipe is in operation, wait until it's done + if stats.wiping != nil { + stats.Log("Wiper running, state snapshotting paused", dl.genMarker) + select { + // If wiper is done, resume normal mode of operation + case <-stats.wiping: + stats.wiping = nil + stats.start = time.Now() + + // If generator was aboted during wipe, return + case abort := <-dl.genAbort: + abort <- stats + return + } + } + // Create an account and state iterator pointing to the current generator marker + accTrie, err := trie.NewSecure(dl.root, dl.triedb) + if err != nil { + // The account trie is missing (GC), surf the chain until one becomes available + stats.Log("Trie missing, state snapshotting paused", dl.genMarker) + + abort := <-dl.genAbort + abort <- stats + return + } + stats.Log("Resuming state snapshot generation", dl.genMarker) + + var accMarker []byte + if len(dl.genMarker) > 0 { // []byte{} is the start, use nil for that + accMarker = dl.genMarker[:common.HashLength] + } + accIt := trie.NewIterator(accTrie.NodeIterator(accMarker)) + batch := dl.diskdb.NewBatch() + + // Iterate from the previous marker and continue generating the state snapshot + logged := time.Now() for accIt.Next() { - var ( - curStorageCount int - curStorageNodes int - curAccountSize common.StorageSize - curStorageSize common.StorageSize - accountHash = common.BytesToHash(accIt.Key) - ) + // Retrieve the current account and flatten it into the internal format + accountHash := common.BytesToHash(accIt.Key) + var acc struct { Nonce uint64 Balance *big.Int @@ -144,63 +164,97 @@ func generateSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) CodeHash []byte } if err := rlp.DecodeBytes(accIt.Value, &acc); err != nil { - return nil, err + log.Crit("Invalid account encountered during snapshot creation", "err", err) } data := AccountRLP(acc.Nonce, acc.Balance, acc.Root, acc.CodeHash) - curAccountSize += common.StorageSize(1 + common.HashLength + len(data)) - rawdb.WriteAccountSnapshot(batch, accountHash, data) - if batch.ValueSize() > ethdb.IdealBatchSize { - batch.Write() - batch.Reset() + // If the account is not yet in-progress, write it out + if accMarker == nil || !bytes.Equal(accountHash[:], accMarker) { + rawdb.WriteAccountSnapshot(batch, accountHash, data) + stats.storage += common.StorageSize(1 + common.HashLength + len(data)) + stats.accounts++ } - if acc.Root != emptyRoot { - storeTrie, err := trie.NewSecure(acc.Root, triedb) - if err != nil { - return nil, err - } - storeIt := trie.NewIterator(storeTrie.NodeIterator(nil)) - for storeIt.Next() { - curStorageSize += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) - curStorageCount++ + // If we've exceeded our batch allowance or termination was requested, flush to disk + var abort chan *generatorStats + select { + case abort = <-dl.genAbort: + default: + } + if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { + // Only write and set the marker if we actually did something useful + if batch.ValueSize() > 0 { + batch.Write() + batch.Reset() + dl.lock.Lock() + dl.genMarker = accountHash[:] + dl.lock.Unlock() + } + if abort != nil { + stats.Log("Aborting state snapshot generation", accountHash[:]) + abort <- stats + return + } + } + // If the account is in-progress, continue where we left off (otherwise iterate all) + if acc.Root != emptyRoot { + storeTrie, err := trie.NewSecure(acc.Root, dl.triedb) + if err != nil { + log.Crit("Storage trie inaccessible for snapshot generation", "err", err) + } + var storeMarker []byte + if accMarker != nil && bytes.Equal(accountHash[:], accMarker) && len(dl.genMarker) > common.HashLength { + storeMarker = dl.genMarker[common.HashLength:] + } + storeIt := trie.NewIterator(storeTrie.NodeIterator(storeMarker)) + for storeIt.Next() { rawdb.WriteStorageSnapshot(batch, accountHash, common.BytesToHash(storeIt.Key), storeIt.Value) - if batch.ValueSize() > ethdb.IdealBatchSize { - batch.Write() - batch.Reset() + stats.storage += common.StorageSize(1 + 2*common.HashLength + len(storeIt.Value)) + stats.slots++ + + // If we've exceeded our batch allowance or termination was requested, flush to disk + var abort chan *generatorStats + select { + case abort = <-dl.genAbort: + default: + } + if batch.ValueSize() > ethdb.IdealBatchSize || abort != nil { + // Only write and set the marker if we actually did something useful + if batch.ValueSize() > 0 { + batch.Write() + batch.Reset() + + dl.lock.Lock() + dl.genMarker = append(accountHash[:], storeIt.Key...) + dl.lock.Unlock() + } + if abort != nil { + stats.Log("Aborting state snapshot generation", append(accountHash[:], storeIt.Key...)) + abort <- stats + return + } } } - curStorageNodes = storeIt.Nodes } - accountCount++ - storageCount += curStorageCount - accountSize += curAccountSize - storageSize += curStorageSize - storageNodes += curStorageNodes - if time.Since(logged) > 8*time.Second { - fmt.Printf("%#x: %9s + %9s (%6d slots, %6d nodes), total %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accIt.Key, curAccountSize.TerminalString(), curStorageSize.TerminalString(), curStorageCount, curStorageNodes, accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) + stats.Log("Generating state snapshot", accIt.Key) logged = time.Now() } + // Some account processed, unmark the marker + accMarker = nil } - fmt.Printf("Totals: %9s (%d accs, %d nodes) + %9s (%d slots, %d nodes)\n", accountSize.TerminalString(), accountCount, accIt.Nodes, storageSize.TerminalString(), storageCount, storageNodes) - - // Update the snapshot block marker and write any remainder data - rawdb.WriteSnapshotRoot(batch, root) - batch.Write() - batch.Reset() - - // Compact the snapshot section of the database to get rid of unused space - log.Info("Compacting snapshot in chain database") - if err := db.Compact([]byte{'s'}, []byte{'s' + 1}); err != nil { - return nil, err + // Snapshot fully generated, set the marker to nil + if batch.ValueSize() > 0 { + batch.Write() } - // New snapshot generated, construct a brand new base layer - cache := fastcache.New(512 * 1024 * 1024) - return &diskLayer{ - journal: journal, - db: db, - cache: cache, - root: root, - }, nil + log.Info("Generated state snapshot", "accounts", stats.accounts, "slots", stats.slots, + "storage", stats.storage, "elapsed", common.PrettyDuration(time.Since(stats.start))) + + dl.lock.Lock() + dl.genMarker = nil + dl.lock.Unlock() + + // Someone will be looking for us, wait it out + abort := <-dl.genAbort + abort <- nil } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go new file mode 100644 index 0000000000..1c6c63a0b9 --- /dev/null +++ b/core/state/snapshot/journal.go @@ -0,0 +1,257 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bufio" + "encoding/binary" + "errors" + "fmt" + "io" + "os" + "time" + + "github.com/VictoriaMetrics/fastcache" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// journalGenerator is a disk layer entry containing the generator progress marker. +type journalGenerator struct { + Wiping bool // Whether the database was in progress of being wiped + Done bool // Whether the generator finished creating the snapshot + Marker []byte + Accounts uint64 + Slots uint64 + Storage uint64 +} + +// journalAccount is an account entry in a diffLayer's disk journal. +type journalAccount struct { + Hash common.Hash + Blob []byte +} + +// journalStorage is an account's storage map in a diffLayer's disk journal. +type journalStorage struct { + Hash common.Hash + Keys []common.Hash + Vals [][]byte +} + +// loadSnapshot loads a pre-existing state snapshot backed by a key-value store. +func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cache int, root common.Hash) (snapshot, error) { + // Retrieve the block number and hash of the snapshot, failing if no snapshot + // is present in the database (or crashed mid-update). + baseRoot := rawdb.ReadSnapshotRoot(diskdb) + if baseRoot == (common.Hash{}) { + return nil, errors.New("missing or corrupted snapshot") + } + base := &diskLayer{ + diskdb: diskdb, + triedb: triedb, + cache: fastcache.New(cache * 1024 * 1024), + root: baseRoot, + } + // Open the journal, it must exist since even for 0 layer it stores whether + // we've already generated the snapshot or are in progress only + file, err := os.Open(journal) + if err != nil { + return nil, err + } + r := rlp.NewStream(file, 0) + + // Read the snapshot generation progress for the disk layer + var generator journalGenerator + if err := r.Decode(&generator); err != nil { + return nil, fmt.Errorf("failed to load snapshot progress marker: %v", err) + } + // Load all the snapshot diffs from the journal + snapshot, err := loadDiffLayer(base, r) + if err != nil { + return nil, err + } + // Entire snapshot journal loaded, sanity check the head and return + // Journal doesn't exist, don't worry if it's not supposed to + if head := snapshot.Root(); head != root { + return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) + } + // Everything loaded correctly, resume any suspended operations + if !generator.Done { + // If the generator was still wiping, restart one from scratch (fine for + // now as it's rare and the wiper deletes the stuff it touches anyway, so + // restarting won't incur a lot of extra database hops. + var wiper chan struct{} + if generator.Wiping { + log.Info("Resuming previous snapshot wipe") + wiper = wipeSnapshot(diskdb, false) + } + // Whether or not wiping was in progress, load any generator progress too + base.genMarker = generator.Marker + if base.genMarker == nil { + base.genMarker = []byte{} + } + base.genAbort = make(chan chan *generatorStats) + + var origin uint64 + if len(generator.Marker) >= 8 { + origin = binary.BigEndian.Uint64(generator.Marker) + } + go base.generate(&generatorStats{ + wiping: wiper, + origin: origin, + start: time.Now(), + accounts: generator.Accounts, + slots: generator.Slots, + storage: common.StorageSize(generator.Storage), + }) + } + return snapshot, nil +} + +// loadDiffLayer reads the next sections of a snapshot journal, reconstructing a new +// diff and verifying that it can be linked to the requested parent. +func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { + // Read the next diff journal entry + var root common.Hash + if err := r.Decode(&root); err != nil { + // The first read may fail with EOF, marking the end of the journal + if err == io.EOF { + return parent, nil + } + return nil, fmt.Errorf("load diff root: %v", err) + } + var accounts []journalAccount + if err := r.Decode(&accounts); err != nil { + return nil, fmt.Errorf("load diff accounts: %v", err) + } + accountData := make(map[common.Hash][]byte) + for _, entry := range accounts { + accountData[entry.Hash] = entry.Blob + } + var storage []journalStorage + if err := r.Decode(&storage); err != nil { + return nil, fmt.Errorf("load diff storage: %v", err) + } + storageData := make(map[common.Hash]map[common.Hash][]byte) + for _, entry := range storage { + slots := make(map[common.Hash][]byte) + for i, key := range entry.Keys { + slots[key] = entry.Vals[i] + } + storageData[entry.Hash] = slots + } + return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) +} + +// Journal is the internal version of Journal that also returns the journal file +// so subsequent layers know where to write to. +func (dl *diskLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { + // If the snapshot is currenty being generated, abort it + var stats *generatorStats + if dl.genAbort != nil { + abort := make(chan *generatorStats) + dl.genAbort <- abort + + if stats = <-abort; stats != nil { + stats.Log("Journalling in-progress snapshot", dl.genMarker) + } + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + return nil, common.Hash{}, ErrSnapshotStale + } + // We've reached the bottom, open the journal + file, err := os.Create(path) + if err != nil { + return nil, common.Hash{}, err + } + // Write out the generator marker + entry := journalGenerator{ + Done: dl.genMarker == nil, + Marker: dl.genMarker, + } + if stats != nil { + entry.Wiping = (stats.wiping != nil) + entry.Accounts = stats.accounts + entry.Slots = stats.slots + entry.Storage = uint64(stats.storage) + } + if err := rlp.Encode(file, entry); err != nil { + file.Close() + return nil, common.Hash{}, err + } + return file, dl.root, nil +} + +// Journal is the internal version of Journal that also returns the journal file +// so subsequent layers know where to write to. +func (dl *diffLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { + // Journal the parent first + writer, base, err := dl.parent.Journal(path) + if err != nil { + return nil, common.Hash{}, err + } + // Ensure the layer didn't get stale + dl.lock.RLock() + defer dl.lock.RUnlock() + + if dl.stale { + writer.Close() + return nil, common.Hash{}, ErrSnapshotStale + } + // Everything below was journalled, persist this layer too + buf := bufio.NewWriter(writer) + if err := rlp.Encode(buf, dl.root); err != nil { + buf.Flush() + writer.Close() + return nil, common.Hash{}, err + } + accounts := make([]journalAccount, 0, len(dl.accountData)) + for hash, blob := range dl.accountData { + accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) + } + if err := rlp.Encode(buf, accounts); err != nil { + buf.Flush() + writer.Close() + return nil, common.Hash{}, err + } + storage := make([]journalStorage, 0, len(dl.storageData)) + for hash, slots := range dl.storageData { + keys := make([]common.Hash, 0, len(slots)) + vals := make([][]byte, 0, len(slots)) + for key, val := range slots { + keys = append(keys, key) + vals = append(vals, val) + } + storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) + } + if err := rlp.Encode(buf, storage); err != nil { + buf.Flush() + writer.Close() + return nil, common.Hash{}, err + } + buf.Flush() + return writer, base, nil +} diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index d35d698399..744d56c1bc 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -18,31 +18,67 @@ package snapshot import ( + "bytes" "errors" "fmt" - "os" + "io" "sync" - "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" - "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" ) var ( - snapshotCleanHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/hit", nil) - snapshotCleanMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/miss", nil) - snapshotCleanReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/read", nil) - snapshotCleanWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/write", nil) + snapshotCleanAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/hit", nil) + snapshotCleanAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/miss", nil) + snapshotCleanAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/read", nil) + snapshotCleanAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/write", nil) + + snapshotCleanStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/hit", nil) + snapshotCleanStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/miss", nil) + snapshotCleanStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/read", nil) + snapshotCleanStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/write", nil) + + snapshotDirtyAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/hit", nil) + snapshotDirtyAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/miss", nil) + snapshotDirtyAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/read", nil) + snapshotDirtyAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/write", nil) + + snapshotDirtyStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/hit", nil) + snapshotDirtyStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/miss", nil) + snapshotDirtyStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/read", nil) + snapshotDirtyStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/write", nil) + + snapshotFlushAccountItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/item", nil) + snapshotFlushAccountSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/size", nil) + snapshotFlushStorageItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/storage/item", nil) + snapshotFlushStorageSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/storage/size", nil) + + snapshotBloomIndexTimer = metrics.NewRegisteredResettingTimer("state/snapshot/bloom/index", nil) + snapshotBloomErrorGauge = metrics.NewRegisteredGaugeFloat64("state/snapshot/bloom/error", nil) + + snapshotBloomAccountTrueHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/truehit", nil) + snapshotBloomAccountFalseHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/falsehit", nil) + snapshotBloomAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/account/miss", nil) + + snapshotBloomStorageTrueHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/truehit", nil) + snapshotBloomStorageFalseHitMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/falsehit", nil) + snapshotBloomStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/bloom/storage/miss", nil) // ErrSnapshotStale is returned from data accessors if the underlying snapshot // layer had been invalidated due to the chain progressing forward far enough // to not maintain the layer's original state. ErrSnapshotStale = errors.New("snapshot stale") + // ErrNotCoveredYet is returned from data accessors if the underlying snapshot + // is being generated currently and the requested data item is not yet in the + // range of accounts covered. + ErrNotCoveredYet = errors.New("not covered yet") + // errSnapshotCycle is returned if a snapshot is attempted to be inserted // that forms a cycle in the snapshot tree. errSnapshotCycle = errors.New("snapshot cycle") @@ -79,7 +115,7 @@ type snapshot interface { // Journal commits an entire diff hierarchy to disk into a single journal file. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). - Journal() error + Journal(path string) (io.WriteCloser, common.Hash, error) // Stale return whether this layer has become stale (was flattened across) or // if it's still live. @@ -96,7 +132,10 @@ type snapshot interface { // storage data to avoid expensive multi-level trie lookups; and to allow sorted, // cheap iteration of the account/storage tries for sync aid. type Tree struct { - layers map[common.Hash]snapshot // Collection of all known layers // TODO(karalabe): split Clique overlaps + diskdb ethdb.KeyValueStore // Persistent database to store the snapshot + triedb *trie.Database // In-memory cache to access the trie through + cache int // Megabytes permitted to use for read caches + layers map[common.Hash]snapshot // Collection of all known layers lock sync.RWMutex } @@ -105,20 +144,24 @@ type Tree struct { // of the snapshot matches the expected one. // // If the snapshot is missing or inconsistent, the entirety is deleted and will -// be reconstructed from scratch based on the tries in the key-value store. -func New(db ethdb.KeyValueStore, journal string, root common.Hash) (*Tree, error) { - // Attempt to load a previously persisted snapshot - head, err := loadSnapshot(db, journal, root) - if err != nil { - log.Warn("Failed to load snapshot, regenerating", "err", err) - if head, err = generateSnapshot(db, journal, root); err != nil { - return nil, err - } - } - // Existing snapshot loaded or one regenerated, seed all the layers +// be reconstructed from scratch based on the tries in the key-value store, on a +// background thread. +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cache int, root common.Hash) *Tree { + // Create a new, empty snapshot tree snap := &Tree{ + diskdb: diskdb, + triedb: triedb, + cache: cache, layers: make(map[common.Hash]snapshot), } + // Attempt to load a previously persisted snapshot and rebuild one if failed + head, err := loadSnapshot(diskdb, triedb, journal, cache, root) + if err != nil { + log.Warn("Failed to load snapshot, regenerating", "err", err) + snap.Rebuild(root) + return snap + } + // Existing snapshot loaded, seed all the layers for head != nil { snap.layers[head.Root()] = head @@ -131,7 +174,7 @@ func New(db ethdb.KeyValueStore, journal string, root common.Hash) (*Tree, error panic(fmt.Sprintf("unknown data layer: %T", self)) } } - return snap, nil + return snap } // Snapshot retrieves a snapshot belonging to the given block root, or nil if no @@ -173,7 +216,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts ma // Cap traverses downwards the snapshot tree from a head block hash until the // number of allowed layers are crossed. All layers beyond the permitted number // are flattened downwards. -func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { +func (t *Tree) Cap(root common.Hash, layers int) error { // Retrieve the head snapshot to cap from snap := t.Snapshot(root) if snap == nil { @@ -190,6 +233,8 @@ func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { // Flattening the bottom-most diff layer requires special casing since there's // no child to rewire to the grandparent. In that case we can fake a temporary // child for the capping and then remove it. + var persisted *diskLayer + switch layers { case 0: // If full commit was requested, flatten the diffs and merge onto disk @@ -210,7 +255,7 @@ func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { ) diff.lock.RLock() bottom = diff.flatten().(*diffLayer) - if bottom.memory >= memory { + if bottom.memory >= aggregatorMemoryLimit { base = diffToDisk(bottom) } diff.lock.RUnlock() @@ -225,7 +270,7 @@ func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { default: // Many layers requested to be retained, cap normally - t.cap(diff, layers, memory) + persisted = t.cap(diff, layers) } // Remove any layer that is stale or links into a stale layer children := make(map[common.Hash][]common.Hash) @@ -248,13 +293,28 @@ func (t *Tree) Cap(root common.Hash, layers int, memory uint64) error { remove(root) } } + // If the disk layer was modified, regenerate all the cummulative blooms + if persisted != nil { + var rebloom func(root common.Hash) + rebloom = func(root common.Hash) { + if diff, ok := t.layers[root].(*diffLayer); ok { + diff.rebloom(persisted) + } + for _, child := range children[root] { + rebloom(child) + } + } + rebloom(persisted.root) + } return nil } // cap traverses downwards the diff tree until the number of allowed layers are // crossed. All diffs beyond the permitted number are flattened downwards. If the // layer limit is reached, memory cap is also enforced (but not before). -func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { +// +// The method returns the new disk layer if diffs were persistend into it. +func (t *Tree) cap(diff *diffLayer, layers int) *diskLayer { // Dive until we run out of layers or reach the persistent database for ; layers > 2; layers-- { // If we still have diff layers below, continue down @@ -262,14 +322,14 @@ func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { diff = parent } else { // Diff stack too shallow, return without modifications - return + return nil } } // We're out of layers, flatten anything below, stopping if it's the disk or if // the memory limit is not yet exceeded. switch parent := diff.parent.(type) { case *diskLayer: - return + return nil case *diffLayer: // Flatten the parent into the grandparent. The flattening internally obtains a @@ -281,8 +341,14 @@ func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { defer diff.lock.Unlock() diff.parent = flattened - if flattened.memory < memory { - return + if flattened.memory < aggregatorMemoryLimit { + // Accumulator layer is smaller than the limit, so we can abort, unless + // there's a snapshot being generated currently. In that case, the trie + // will move fron underneath the generator so we **must** merge all the + // partial data down into the snapshot and restart the generation. + if flattened.parent.(*diskLayer).genAbort == nil { + return nil + } } default: panic(fmt.Sprintf("unknown data layer: %T", parent)) @@ -296,6 +362,7 @@ func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { t.layers[base.root] = base diff.parent = base + return base } // diffToDisk merges a bottom-most diff into the persistent disk layer underneath @@ -303,8 +370,15 @@ func (t *Tree) cap(diff *diffLayer, layers int, memory uint64) { func diffToDisk(bottom *diffLayer) *diskLayer { var ( base = bottom.parent.(*diskLayer) - batch = base.db.NewBatch() + batch = base.diskdb.NewBatch() + stats *generatorStats ) + // If the disk layer is running a snapshot generator, abort it + if base.genAbort != nil { + abort := make(chan *generatorStats) + base.genAbort <- abort + stats = <-abort + } // Start by temporarily deleting the current snapshot block marker. This // ensures that in the case of a crash, the entire snapshot is invalidated. rawdb.DeleteSnapshotRoot(batch) @@ -319,6 +393,10 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Push all the accounts into the database for hash, data := range bottom.accountData { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(hash[:], base.genMarker) > 0 { + continue + } if len(data) > 0 { // Account was updated, push to disk rawdb.WriteAccountSnapshot(batch, hash, data) @@ -335,19 +413,35 @@ func diffToDisk(bottom *diffLayer) *diskLayer { rawdb.DeleteAccountSnapshot(batch, hash) base.cache.Set(hash[:], nil) - it := rawdb.IterateStorageSnapshots(base.db, hash) + it := rawdb.IterateStorageSnapshots(base.diskdb, hash) for it.Next() { if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator batch.Delete(key) base.cache.Del(key[1:]) + + snapshotFlushStorageItemMeter.Mark(1) + snapshotFlushStorageSizeMeter.Mark(int64(len(data))) } } it.Release() } + snapshotFlushAccountItemMeter.Mark(1) + snapshotFlushAccountSizeMeter.Mark(int64(len(data))) } // Push all the storage slots into the database for accountHash, storage := range bottom.storageData { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(accountHash[:], base.genMarker) > 0 { + continue + } + // Generation might be mid-account, track that case too + midAccount := base.genMarker != nil && bytes.Equal(accountHash[:], base.genMarker[:common.HashLength]) + for storageHash, data := range storage { + // Skip any slot not covered yet by the snapshot + if midAccount && bytes.Compare(storageHash[:], base.genMarker[common.HashLength:]) > 0 { + continue + } if len(data) > 0 { rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) base.cache.Set(append(accountHash[:], storageHash[:]...), data) @@ -355,6 +449,8 @@ func diffToDisk(bottom *diffLayer) *diskLayer { rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) base.cache.Set(append(accountHash[:], storageHash[:]...), nil) } + snapshotFlushStorageItemMeter.Mark(1) + snapshotFlushStorageSizeMeter.Mark(int64(len(data))) } if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { @@ -368,65 +464,91 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if err := batch.Write(); err != nil { log.Crit("Failed to write leftover snapshot", "err", err) } - return &diskLayer{ - root: bottom.root, - cache: base.cache, - db: base.db, - journal: base.journal, + res := &diskLayer{ + root: bottom.root, + cache: base.cache, + diskdb: base.diskdb, + triedb: base.triedb, + genMarker: base.genMarker, } + // If snapshot generation hasn't finished yet, port over all the starts and + // continue where the previous round left off. + // + // Note, the `base.genAbort` comparison is not used normally, it's checked + // to allow the tests to play with the marker without triggering this path. + if base.genMarker != nil && base.genAbort != nil { + res.genMarker = base.genMarker + res.genAbort = make(chan chan *generatorStats) + go res.generate(stats) + } + return res } // Journal commits an entire diff hierarchy to disk into a single journal file. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). -func (t *Tree) Journal(blockRoot common.Hash) error { +// +// The method returns the root hash of the base layer that needs to be persisted +// to disk as a trie too to allow continuing any pending generation op. +func (t *Tree) Journal(root common.Hash, path string) (common.Hash, error) { // Retrieve the head snapshot to journal from var snap snapshot - snap := t.Snapshot(blockRoot) + snap := t.Snapshot(root) if snap == nil { - return fmt.Errorf("snapshot [%#x] missing", blockRoot) + return common.Hash{}, fmt.Errorf("snapshot [%#x] missing", root) } // Run the journaling t.lock.Lock() defer t.lock.Unlock() - return snap.(snapshot).Journal() + writer, base, err := snap.(snapshot).Journal(path) + if err != nil { + return common.Hash{}, err + } + return base, writer.Close() } -// loadSnapshot loads a pre-existing state snapshot backed by a key-value store. -func loadSnapshot(db ethdb.KeyValueStore, journal string, root common.Hash) (snapshot, error) { - // Retrieve the block number and hash of the snapshot, failing if no snapshot - // is present in the database (or crashed mid-update). - baseRoot := rawdb.ReadSnapshotRoot(db) - if baseRoot == (common.Hash{}) { - return nil, errors.New("missing or corrupted snapshot") - } - base := &diskLayer{ - journal: journal, - db: db, - cache: fastcache.New(512 * 1024 * 1024), - root: baseRoot, - } - // Load all the snapshot diffs from the journal, failing if their chain is broken - // or does not lead from the disk snapshot to the specified head. - if _, err := os.Stat(journal); os.IsNotExist(err) { - // Journal doesn't exist, don't worry if it's not supposed to - if baseRoot != root { - return nil, fmt.Errorf("snapshot journal missing, head doesn't match snapshot: have %#x, want %#x", baseRoot, root) +// Rebuild wipes all available snapshot data from the persistent database and +// discard all caches and diff layers. Afterwards, it starts a new snapshot +// generator with the given root hash. +func (t *Tree) Rebuild(root common.Hash) { + t.lock.Lock() + defer t.lock.Unlock() + + // Track whether there's a wipe currently running and keep it alive if so + var wiper chan struct{} + + // Iterate over and mark all layers stale + for _, layer := range t.layers { + switch layer := layer.(type) { + case *diskLayer: + // If the base layer is generating, abort it and save + if layer.genAbort != nil { + abort := make(chan *generatorStats) + layer.genAbort <- abort + + if stats := <-abort; stats != nil { + wiper = stats.wiping + } + } + // Layer should be inactive now, mark it as stale + layer.lock.Lock() + layer.stale = true + layer.lock.Unlock() + + case *diffLayer: + // If the layer is a simple diff, simply mark as stale + layer.lock.Lock() + layer.stale = true + layer.lock.Unlock() + + default: + panic(fmt.Sprintf("unknown layer type: %T", layer)) } - return base, nil } - file, err := os.Open(journal) - if err != nil { - return nil, err + // Start generating a new snapshot from scratch on a backgroung thread. The + // generator will run a wiper first if there's not one running right now. + log.Info("Rebuilding state snapshot") + t.layers = map[common.Hash]snapshot{ + root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper), } - snapshot, err := loadDiffLayer(base, rlp.NewStream(file, 0)) - if err != nil { - return nil, err - } - // Entire snapshot journal loaded, sanity check the head and return - // Journal doesn't exist, don't worry if it's not supposed to - if head := snapshot.Root(); head != root { - return nil, fmt.Errorf("head doesn't match snapshot: have %#x, want %#x", head, root) - } - return snapshot, nil } diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 9c872a8955..44b8f3cefd 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -31,9 +31,9 @@ import ( func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ - db: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -54,7 +54,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 2) } // Commit the diff layer onto the disk and ensure it's persisted - if err := snaps.Cap(common.HexToHash("0x02"), 0, 0); err != nil { + if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { t.Fatalf("failed to merge diff layer onto disk: %v", err) } // Since the base layer was modified, ensure that data retrievald on the external reference fail @@ -76,9 +76,9 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ - db: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -102,7 +102,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { t.Errorf("pre-cap layer count mismatch: have %d, want %d", n, 3) } // Commit the diff layer onto the disk and ensure it's persisted - if err := snaps.Cap(common.HexToHash("0x03"), 2, 0); err != nil { + defer func(memcap uint64) { aggregatorMemoryLimit = memcap }(aggregatorMemoryLimit) + aggregatorMemoryLimit = 0 + + if err := snaps.Cap(common.HexToHash("0x03"), 2); err != nil { t.Fatalf("failed to merge diff layer onto disk: %v", err) } // Since the base layer was modified, ensure that data retrievald on the external reference fail @@ -124,9 +127,9 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ - db: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -150,7 +153,7 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { ref := snaps.Snapshot(common.HexToHash("0x02")) // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x03"), 1, 1024*1024); err != nil { + if err := snaps.Cap(common.HexToHash("0x03"), 1); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail @@ -172,9 +175,9 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ - db: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -202,14 +205,14 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { // Doing a Cap operation with many allowed layers should be a no-op exp := len(snaps.layers) - if err := snaps.Cap(common.HexToHash("0x04"), 2000, 1024*1024); err != nil { + if err := snaps.Cap(common.HexToHash("0x04"), 2000); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } if got := len(snaps.layers); got != exp { t.Errorf("layers modified, got %d exp %d", got, exp) } // Flatten the diff layer into the bottom accumulator - if err := snaps.Cap(common.HexToHash("0x04"), 2, 1024*1024); err != nil { + if err := snaps.Cap(common.HexToHash("0x04"), 2); err != nil { t.Fatalf("failed to flatten diff layer into accumulator: %v", err) } // Since the accumulator diff layer was modified, ensure that data retrievald on the external reference fail @@ -236,9 +239,9 @@ func TestPostCapBasicDataAccess(t *testing.T) { } // Create a starting base layer and a snapshot tree out of it base := &diskLayer{ - db: rawdb.NewMemoryDatabase(), - root: common.HexToHash("0x01"), - cache: fastcache.New(1024 * 500), + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } snaps := &Tree{ layers: map[common.Hash]snapshot{ @@ -280,11 +283,11 @@ func TestPostCapBasicDataAccess(t *testing.T) { t.Error(err) } // Cap to a bad root should fail - if err := snaps.Cap(common.HexToHash("0x1337"), 0, 1024); err == nil { + if err := snaps.Cap(common.HexToHash("0x1337"), 0); err == nil { t.Errorf("expected error, got none") } // Now, merge the a-chain - snaps.Cap(common.HexToHash("0xa3"), 0, 1024) + snaps.Cap(common.HexToHash("0xa3"), 0) // At this point, a2 got merged into a1. Thus, a1 is now modified, and as a1 is // the parent of b2, b2 should no longer be able to iterate into parent. @@ -308,7 +311,7 @@ func TestPostCapBasicDataAccess(t *testing.T) { } // Now, merge it again, just for fun. It should now error, since a3 // is a disk layer - if err := snaps.Cap(common.HexToHash("0xa3"), 0, 1024); err == nil { + if err := snaps.Cap(common.HexToHash("0xa3"), 0); err == nil { t.Error("expected error capping the disk layer, got none") } } diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go new file mode 100644 index 0000000000..052af6f1f1 --- /dev/null +++ b/core/state/snapshot/wipe.go @@ -0,0 +1,130 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" +) + +// wipeSnapshot starts a goroutine to iterate over the entire key-value database +// and delete all the data associated with the snapshot (accounts, storage, +// metadata). After all is done, the snapshot range of the database is compacted +// to free up unused data blocks. +func wipeSnapshot(db ethdb.KeyValueStore, full bool) chan struct{} { + // Wipe the snapshot root marker synchronously + if full { + rawdb.DeleteSnapshotRoot(db) + } + // Wipe everything else asynchronously + wiper := make(chan struct{}, 1) + go func() { + if err := wipeContent(db); err != nil { + log.Error("Failed to wipe state snapshot", "err", err) // Database close will trigger this + return + } + close(wiper) + }() + return wiper +} + +// wipeContent iterates over the entire key-value database and deletes all the +// data associated with the snapshot (accounts, storage), but not the root hash +// as the wiper is meant to run on a background thread but the root needs to be +// removed in sync to avoid data races. After all is done, the snapshot range of +// the database is compacted to free up unused data blocks. +func wipeContent(db ethdb.KeyValueStore) error { + if err := wipeKeyRange(db, "accounts", rawdb.SnapshotAccountPrefix, len(rawdb.SnapshotAccountPrefix)+common.HashLength); err != nil { + return err + } + if err := wipeKeyRange(db, "storage", rawdb.SnapshotStoragePrefix, len(rawdb.SnapshotStoragePrefix)+2*common.HashLength); err != nil { + return err + } + // Compact the snapshot section of the database to get rid of unused space + start := time.Now() + + log.Info("Compacting snapshot account area ") + end := common.CopyBytes(rawdb.SnapshotAccountPrefix) + end[len(end)-1]++ + + if err := db.Compact(rawdb.SnapshotAccountPrefix, end); err != nil { + return err + } + log.Info("Compacting snapshot storage area ") + end = common.CopyBytes(rawdb.SnapshotStoragePrefix) + end[len(end)-1]++ + + if err := db.Compact(rawdb.SnapshotStoragePrefix, end); err != nil { + return err + } + log.Info("Compacted snapshot area in database", "elapsed", common.PrettyDuration(time.Since(start))) + + return nil +} + +// wipeKeyRange deletes a range of keys from the database starting with prefix +// and having a specific total key length. +func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int) error { + // Batch deletions together to avoid holding an iterator for too long + var ( + batch = db.NewBatch() + items int + ) + // Iterate over the key-range and delete all of them + start, logged := time.Now(), time.Now() + + it := db.NewIteratorWithStart(prefix) + for it.Next() { + // Skip any keys with the correct prefix but wrong lenth (trie nodes) + key := it.Key() + if !bytes.HasPrefix(key, prefix) { + break + } + if len(key) != keylen { + continue + } + // Delete the key and periodically recreate the batch and iterator + batch.Delete(key) + items++ + + if items%10000 == 0 { + // Batch too large (or iterator too long lived, flush and recreate) + it.Release() + if err := batch.Write(); err != nil { + return err + } + batch.Reset() + it = db.NewIteratorWithStart(key) + + if time.Since(logged) > 8*time.Second { + log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) + logged = time.Now() + } + } + } + it.Release() + if err := batch.Write(); err != nil { + return err + } + log.Info("Deleted state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) + return nil +} diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/wipe_test.go similarity index 77% rename from core/state/snapshot/generate_test.go rename to core/state/snapshot/wipe_test.go index 180db920a8..f12769a950 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/wipe_test.go @@ -59,17 +59,31 @@ func TestWipe(t *testing.T) { // Randomize the suffix, dedup and inject it under the snapshot namespace keysuffix := make([]byte, keysize) rand.Read(keysuffix) - db.Put(append(rawdb.StateSnapshotPrefix, keysuffix...), randomHash().Bytes()) + + if rand.Int31n(2) == 0 { + db.Put(append(rawdb.SnapshotAccountPrefix, keysuffix...), randomHash().Bytes()) + } else { + db.Put(append(rawdb.SnapshotStoragePrefix, keysuffix...), randomHash().Bytes()) + } } // Sanity check that all the keys are present var items int - it := db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) + it := db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) defer it.Release() for it.Next() { key := it.Key() - if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { + if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength { + items++ + } + } + it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) + defer it.Release() + + for it.Next() { + key := it.Key() + if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength { items++ } } @@ -80,16 +94,24 @@ func TestWipe(t *testing.T) { t.Errorf("snapshot block marker mismatch: have %#x, want ", hash) } // Wipe all snapshot entries from the database - if err := wipeSnapshot(db); err != nil { - t.Fatalf("failed to wipe snapshot: %v", err) - } + <-wipeSnapshot(db, true) + // Iterate over the database end ensure no snapshot information remains - it = db.NewIteratorWithPrefix(rawdb.StateSnapshotPrefix) + it = db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) defer it.Release() for it.Next() { key := it.Key() - if len(key) == len(rawdb.StateSnapshotPrefix)+32 || len(key) == len(rawdb.StateSnapshotPrefix)+64 { + if len(key) == len(rawdb.SnapshotAccountPrefix)+common.HashLength { + t.Errorf("snapshot entry remained after wipe: %x", key) + } + } + it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) + defer it.Release() + + for it.Next() { + key := it.Key() + if len(key) == len(rawdb.SnapshotStoragePrefix)+2*common.HashLength { t.Errorf("snapshot entry remained after wipe: %x", key) } } diff --git a/core/state/statedb.go b/core/state/statedb.go index f11bd2adbd..1528b45aa3 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -845,8 +845,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } - if err := s.snaps.Cap(root, 16, 4*1024*1024); err != nil { - log.Warn("Failed to cap snapshot tree", "root", root, "layers", 16, "memory", 4*1024*1024, "err", err) + if err := s.snaps.Cap(root, 128); err != nil { + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err) } } s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil diff --git a/eth/backend.go b/eth/backend.go index bda307d95b..ed79340f59 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -127,7 +127,8 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { config.Miner.GasPrice = new(big.Int).Set(DefaultConfig.Miner.GasPrice) } if config.NoPruning && config.TrieDirtyCache > 0 { - config.TrieCleanCache += config.TrieDirtyCache + config.TrieCleanCache += config.TrieDirtyCache * 3 / 5 + config.SnapshotCache += config.TrieDirtyCache * 3 / 5 config.TrieDirtyCache = 0 } log.Info("Allocated trie memory caches", "clean", common.StorageSize(config.TrieCleanCache)*1024*1024, "dirty", common.StorageSize(config.TrieDirtyCache)*1024*1024) @@ -184,6 +185,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { TrieDirtyLimit: config.TrieDirtyCache, TrieDirtyDisabled: config.NoPruning, TrieTimeLimit: config.TrieTimeout, + SnapshotLimit: config.SnapshotCache, } ) eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, chainConfig, eth.engine, vmConfig, eth.shouldPreserve) @@ -204,7 +206,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) // Permit the downloader to use the trie cache allowance during fast sync - cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit checkpoint := config.Checkpoint if checkpoint == nil { checkpoint = params.TrustedCheckpoints[genesisHash] diff --git a/eth/config.go b/eth/config.go index 2eaf21fbc3..160ce8aa59 100644 --- a/eth/config.go +++ b/eth/config.go @@ -50,6 +50,7 @@ var DefaultConfig = Config{ TrieCleanCache: 256, TrieDirtyCache: 256, TrieTimeout: 60 * time.Minute, + SnapshotCache: 256, Miner: miner.Config{ GasFloor: 8000000, GasCeil: 8000000, @@ -125,6 +126,7 @@ type Config struct { TrieCleanCache int TrieDirtyCache int TrieTimeout time.Duration + SnapshotCache int // Mining options Miner miner.Config diff --git a/trie/iterator.go b/trie/iterator.go index 88189c5420..bb4025d8f3 100644 --- a/trie/iterator.go +++ b/trie/iterator.go @@ -29,7 +29,6 @@ import ( type Iterator struct { nodeIt NodeIterator - Nodes int // Number of nodes iterated over Key []byte // Current data key on which the iterator is positioned on Value []byte // Current data value on which the iterator is positioned on Err error @@ -47,7 +46,6 @@ func NewIterator(it NodeIterator) *Iterator { // Next moves the iterator forward one key-value entry. func (it *Iterator) Next() bool { for it.nodeIt.Next(true) { - it.Nodes++ if it.nodeIt.Leaf() { it.Key = it.nodeIt.LeafKey() it.Value = it.nodeIt.LeafBlob() From d5d7c0c24b824b0a166c606b4e71a92bd5e16e21 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 1 Dec 2019 20:49:00 +0100 Subject: [PATCH 008/103] core/state/snapshot: fix difflayer origin-initalization after flatten --- core/state/snapshot/difflayer.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0743e47597..cf8c47c3eb 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -396,6 +396,7 @@ func (dl *diffLayer) flatten() snapshot { // Return the combo parent return &diffLayer{ parent: parent.parent, + origin: parent.origin, root: dl.root, storageList: parent.storageList, storageData: parent.storageData, From fd39f722a3ff506aba9a993bb10ef176f8d654d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 2 Dec 2019 13:27:20 +0200 Subject: [PATCH 009/103] core: journal the snapshot inside leveldb, not a flat file --- core/blockchain.go | 4 +- core/rawdb/accessors_snapshot.go | 23 +++++++++ core/rawdb/schema.go | 5 +- core/state/snapshot/difflayer_test.go | 5 +- core/state/snapshot/journal.go | 70 +++++++++++---------------- core/state/snapshot/snapshot.go | 22 +++++---- core/state/statedb.go | 4 +- 7 files changed, 72 insertions(+), 61 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 3932baf557..f868f7301f 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -302,7 +302,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } // Load any existing snapshot, regenerating it if loading failed - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), "snapshot.rlp", bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) + bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) // Take ownership of this particular state go bc.update() @@ -854,7 +854,7 @@ func (bc *BlockChain) Stop() { bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. - snapBase, err := bc.snaps.Journal(bc.CurrentBlock().Root(), "snapshot.rlp") + snapBase, err := bc.snaps.Journal(bc.CurrentBlock().Root()) if err != nil { log.Error("Failed to journal state snapshot", "err", err) } diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 9388e857b7..3a8d6c779e 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -95,3 +95,26 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) } + +// ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at +// the last shutdown. The blob is expected to be max a few 10s of megabytes. +func ReadSnapshotJournal(db ethdb.KeyValueReader) []byte { + data, _ := db.Get(snapshotJournalKey) + return data +} + +// WriteSnapshotJournal stores the serialized in-memory diff layers to save at +// shutdown. The blob is expected to be max a few 10s of megabytes. +func WriteSnapshotJournal(db ethdb.KeyValueWriter, journal []byte) { + if err := db.Put(snapshotJournalKey, journal); err != nil { + log.Crit("Failed to store snapshot journal", "err", err) + } +} + +// DeleteSnapshotJournal deletes the serialized in-memory diff layers saved at +// the last shutdown +func DeleteSnapshotJournal(db ethdb.KeyValueWriter) { + if err := db.Delete(snapshotJournalKey); err != nil { + log.Crit("Failed to remove snapshot journal", "err", err) + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 1b8e53eb61..dc8faca32d 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -41,9 +41,12 @@ var ( // fastTrieProgressKey tracks the number of trie entries imported during fast sync. fastTrieProgressKey = []byte("TrieSync") - // snapshotRootKey tracks the number and hash of the last snapshot. + // snapshotRootKey tracks the hash of the last snapshot. snapshotRootKey = []byte("SnapshotRoot") + // snapshotJournalKey tracks the in-memory diff layers across restarts. + snapshotJournalKey = []byte("SnapshotJournal") + // Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes). headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 9029bb04b8..84220e359b 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -20,8 +20,6 @@ import ( "bytes" "math/big" "math/rand" - "os" - "path" "testing" "github.com/VictoriaMetrics/fastcache" @@ -343,7 +341,6 @@ func BenchmarkJournal(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - f, _, _ := layer.Journal(path.Join(os.TempDir(), "difflayer_journal.tmp")) - f.Close() + layer.Journal(new(bytes.Buffer)) } } diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 1c6c63a0b9..1c36e06233 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -17,12 +17,11 @@ package snapshot import ( - "bufio" + "bytes" "encoding/binary" "errors" "fmt" "io" - "os" "time" "github.com/VictoriaMetrics/fastcache" @@ -58,7 +57,7 @@ type journalStorage struct { } // loadSnapshot loads a pre-existing state snapshot backed by a key-value store. -func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cache int, root common.Hash) (snapshot, error) { +func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) (snapshot, error) { // Retrieve the block number and hash of the snapshot, failing if no snapshot // is present in the database (or crashed mid-update). baseRoot := rawdb.ReadSnapshotRoot(diskdb) @@ -71,13 +70,13 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal str cache: fastcache.New(cache * 1024 * 1024), root: baseRoot, } - // Open the journal, it must exist since even for 0 layer it stores whether + // Retrieve the journal, it must exist since even for 0 layer it stores whether // we've already generated the snapshot or are in progress only - file, err := os.Open(journal) - if err != nil { - return nil, err + journal := rawdb.ReadSnapshotJournal(diskdb) + if len(journal) == 0 { + return nil, errors.New("missing or corrupted snapshot journal") } - r := rlp.NewStream(file, 0) + r := rlp.NewStream(bytes.NewReader(journal), 0) // Read the snapshot generation progress for the disk layer var generator journalGenerator @@ -162,9 +161,9 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) } -// Journal is the internal version of Journal that also returns the journal file -// so subsequent layers know where to write to. -func (dl *diskLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { +// Journal writes the persistent layer generator stats into a buffer to be stored +// in the database as the snapshot journal. +func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { // If the snapshot is currenty being generated, abort it var stats *generatorStats if dl.genAbort != nil { @@ -180,12 +179,7 @@ func (dl *diskLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { defer dl.lock.RUnlock() if dl.stale { - return nil, common.Hash{}, ErrSnapshotStale - } - // We've reached the bottom, open the journal - file, err := os.Create(path) - if err != nil { - return nil, common.Hash{}, err + return common.Hash{}, ErrSnapshotStale } // Write out the generator marker entry := journalGenerator{ @@ -198,44 +192,37 @@ func (dl *diskLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { entry.Slots = stats.slots entry.Storage = uint64(stats.storage) } - if err := rlp.Encode(file, entry); err != nil { - file.Close() - return nil, common.Hash{}, err + if err := rlp.Encode(buffer, entry); err != nil { + return common.Hash{}, err } - return file, dl.root, nil + return dl.root, nil } -// Journal is the internal version of Journal that also returns the journal file -// so subsequent layers know where to write to. -func (dl *diffLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { +// Journal writes the memory layer contents into a buffer to be stored in the +// database as the snapshot journal. +func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { // Journal the parent first - writer, base, err := dl.parent.Journal(path) + base, err := dl.parent.Journal(buffer) if err != nil { - return nil, common.Hash{}, err + return common.Hash{}, err } // Ensure the layer didn't get stale dl.lock.RLock() defer dl.lock.RUnlock() if dl.stale { - writer.Close() - return nil, common.Hash{}, ErrSnapshotStale + return common.Hash{}, ErrSnapshotStale } // Everything below was journalled, persist this layer too - buf := bufio.NewWriter(writer) - if err := rlp.Encode(buf, dl.root); err != nil { - buf.Flush() - writer.Close() - return nil, common.Hash{}, err + if err := rlp.Encode(buffer, dl.root); err != nil { + return common.Hash{}, err } accounts := make([]journalAccount, 0, len(dl.accountData)) for hash, blob := range dl.accountData { accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) } - if err := rlp.Encode(buf, accounts); err != nil { - buf.Flush() - writer.Close() - return nil, common.Hash{}, err + if err := rlp.Encode(buffer, accounts); err != nil { + return common.Hash{}, err } storage := make([]journalStorage, 0, len(dl.storageData)) for hash, slots := range dl.storageData { @@ -247,11 +234,8 @@ func (dl *diffLayer) Journal(path string) (io.WriteCloser, common.Hash, error) { } storage = append(storage, journalStorage{Hash: hash, Keys: keys, Vals: vals}) } - if err := rlp.Encode(buf, storage); err != nil { - buf.Flush() - writer.Close() - return nil, common.Hash{}, err + if err := rlp.Encode(buffer, storage); err != nil { + return common.Hash{}, err } - buf.Flush() - return writer, base, nil + return base, nil } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 744d56c1bc..749f610789 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -21,7 +21,6 @@ import ( "bytes" "errors" "fmt" - "io" "sync" "github.com/ethereum/go-ethereum/common" @@ -112,10 +111,10 @@ type snapshot interface { // copying everything. Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer - // Journal commits an entire diff hierarchy to disk into a single journal file. + // Journal commits an entire diff hierarchy to disk into a single journal entry. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). - Journal(path string) (io.WriteCloser, common.Hash, error) + Journal(buffer *bytes.Buffer) (common.Hash, error) // Stale return whether this layer has become stale (was flattened across) or // if it's still live. @@ -146,7 +145,7 @@ type Tree struct { // If the snapshot is missing or inconsistent, the entirety is deleted and will // be reconstructed from scratch based on the tries in the key-value store, on a // background thread. -func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cache int, root common.Hash) *Tree { +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) *Tree { // Create a new, empty snapshot tree snap := &Tree{ diskdb: diskdb, @@ -155,7 +154,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, journal string, cach layers: make(map[common.Hash]snapshot), } // Attempt to load a previously persisted snapshot and rebuild one if failed - head, err := loadSnapshot(diskdb, triedb, journal, cache, root) + head, err := loadSnapshot(diskdb, triedb, cache, root) if err != nil { log.Warn("Failed to load snapshot, regenerating", "err", err) snap.Rebuild(root) @@ -401,6 +400,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { // Account was updated, push to disk rawdb.WriteAccountSnapshot(batch, hash, data) base.cache.Set(hash[:], data) + snapshotCleanAccountWriteMeter.Mark(int64(len(data))) if batch.ValueSize() > ethdb.IdealBatchSize { if err := batch.Write(); err != nil { @@ -445,6 +445,7 @@ func diffToDisk(bottom *diffLayer) *diskLayer { if len(data) > 0 { rawdb.WriteStorageSnapshot(batch, accountHash, storageHash, data) base.cache.Set(append(accountHash[:], storageHash[:]...), data) + snapshotCleanStorageWriteMeter.Mark(int64(len(data))) } else { rawdb.DeleteStorageSnapshot(batch, accountHash, storageHash) base.cache.Set(append(accountHash[:], storageHash[:]...), nil) @@ -484,13 +485,13 @@ func diffToDisk(bottom *diffLayer) *diskLayer { return res } -// Journal commits an entire diff hierarchy to disk into a single journal file. +// Journal commits an entire diff hierarchy to disk into a single journal entry. // This is meant to be used during shutdown to persist the snapshot without // flattening everything down (bad for reorgs). // // The method returns the root hash of the base layer that needs to be persisted // to disk as a trie too to allow continuing any pending generation op. -func (t *Tree) Journal(root common.Hash, path string) (common.Hash, error) { +func (t *Tree) Journal(root common.Hash) (common.Hash, error) { // Retrieve the head snapshot to journal from var snap snapshot snap := t.Snapshot(root) if snap == nil { @@ -500,11 +501,14 @@ func (t *Tree) Journal(root common.Hash, path string) (common.Hash, error) { t.lock.Lock() defer t.lock.Unlock() - writer, base, err := snap.(snapshot).Journal(path) + journal := new(bytes.Buffer) + base, err := snap.(snapshot).Journal(journal) if err != nil { return common.Hash{}, err } - return base, writer.Close() + // Store the journal into the database and return + rawdb.WriteSnapshotJournal(t.diskdb, journal.Bytes()) + return base, nil } // Rebuild wipes all available snapshot data from the persistent database and diff --git a/core/state/statedb.go b/core/state/statedb.go index 1528b45aa3..b3ea95a464 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -845,8 +845,8 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } - if err := s.snaps.Cap(root, 128); err != nil { - log.Warn("Failed to cap snapshot tree", "root", root, "layers", 128, "err", err) + if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie + log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err) } } s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil From 3ad4335accd08f2160aac489e4e16dceaae695be Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 2 Dec 2019 09:31:07 +0100 Subject: [PATCH 010/103] core/state/snapshot: node behavioural difference on bloom content --- core/state/snapshot/difflayer.go | 19 ++++++++++++++++--- core/state/snapshot/difflayer_test.go | 9 ++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index cf8c47c3eb..634118a104 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "fmt" "math" + "math/rand" "sort" "sync" "time" @@ -63,8 +64,20 @@ var ( // bloom filter to keep its size to a minimum (given it's size and maximum // entry count). bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2)) + + // bloomHashesOffset is a runtime constant which determines which part of the + // the account/storage hash the hasher functions looks at, to determine the + // bloom key for an account/slot. This is randomized at init(), so that the + // global population of nodes do not all display the exact same behaviour with + // regards to bloom content + bloomHasherOffset = 0 ) +func init() { + // Init bloomHasherOffset in the range [0:24] (requires 8 bytes) + bloomHasherOffset = rand.Intn(25) +} + // diffLayer represents a collection of modifications made to a state snapshot // after running a block on top. It contains one sorted list for the account trie // and one-one list for each storage tries. @@ -100,7 +113,7 @@ func (h accountBloomHasher) Reset() { panic("not impl func (h accountBloomHasher) BlockSize() int { panic("not implemented") } func (h accountBloomHasher) Size() int { return 8 } func (h accountBloomHasher) Sum64() uint64 { - return binary.BigEndian.Uint64(h[:8]) + return binary.BigEndian.Uint64(h[bloomHasherOffset : bloomHasherOffset+8]) } // storageBloomHasher is a wrapper around a [2]common.Hash to satisfy the interface @@ -114,7 +127,8 @@ func (h storageBloomHasher) Reset() { panic("not impl func (h storageBloomHasher) BlockSize() int { panic("not implemented") } func (h storageBloomHasher) Size() int { return 8 } func (h storageBloomHasher) Sum64() uint64 { - return binary.BigEndian.Uint64(h[0][:8]) ^ binary.BigEndian.Uint64(h[1][:8]) + return binary.BigEndian.Uint64(h[0][bloomHasherOffset:bloomHasherOffset+8]) ^ + binary.BigEndian.Uint64(h[1][bloomHasherOffset:bloomHasherOffset+8]) } // newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low @@ -205,7 +219,6 @@ func (dl *diffLayer) rebloom(origin *diskLayer) { k := float64(dl.diffed.K()) n := float64(dl.diffed.N()) m := float64(dl.diffed.M()) - snapshotBloomErrorGauge.Update(math.Pow(1.0-math.Exp((-k)*(n+0.5)/(m-1)), k)) } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 84220e359b..7d7b21eb05 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -24,6 +24,7 @@ import ( "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/rlp" ) @@ -216,7 +217,7 @@ func BenchmarkSearch(b *testing.B) { layer = fill(layer) } - key := common.Hash{} + key := crypto.Keccak256Hash([]byte{0x13, 0x38}) b.ResetTimer() for i := 0; i < b.N; i++ { layer.AccountRLP(key) @@ -229,10 +230,12 @@ func BenchmarkSearch(b *testing.B) { // BenchmarkSearchSlot-6 100000 14554 ns/op // BenchmarkSearchSlot-6 100000 22254 ns/op (when checking parent root using mutex) // BenchmarkSearchSlot-6 100000 14551 ns/op (when checking parent number using atomic) +// With bloom filter: +// BenchmarkSearchSlot-6 3467835 351 ns/op func BenchmarkSearchSlot(b *testing.B) { // First, we set up 128 diff layers, with 1K items each - accountKey := common.Hash{} - storageKey := common.HexToHash("0x1337") + accountKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) + storageKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) accountRLP := randomAccount() fill := func(parent snapshot) *diffLayer { accounts := make(map[common.Hash][]byte) From 22c494d3996db9d7a09c8e5fcbfd15592b36f57a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 3 Dec 2019 10:00:26 +0200 Subject: [PATCH 011/103] core/state/snapshot: bloom, metrics and prefetcher fixes --- core/state/snapshot/difflayer.go | 36 ++++++++++++++++++++++---------- core/state/snapshot/disklayer.go | 14 +++++++++---- core/state/snapshot/snapshot.go | 7 +++++++ core/state_prefetcher.go | 11 ++++++++-- 4 files changed, 51 insertions(+), 17 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 634118a104..05d55a6fa3 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -43,9 +43,11 @@ var ( // aggregatorItemLimit is an approximate number of items that will end up // in the agregator layer before it's flushed out to disk. A plain account - // weighs around 14B (+hash), a storage slot 32B (+hash), so 50 is a very - // rough average of what we might see. - aggregatorItemLimit = aggregatorMemoryLimit / 55 + // weighs around 14B (+hash), a storage slot 32B (+hash), a deleted slot + // 0B (+hash). Slots are mostly set/unset in lockstep, so thet average at + // 16B (+hash). All in all, the average entry seems to be 15+32=47B. Use a + // smaller number to be on the safe side. + aggregatorItemLimit = aggregatorMemoryLimit / 42 // bloomTargetError is the target false positive rate when the aggregator // layer is at its fullest. The actual value will probably move around up @@ -269,13 +271,13 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { return dl.origin.AccountRLP(hash) } // The bloom filter hit, start poking in the internal maps - return dl.accountRLP(hash) + return dl.accountRLP(hash, 0) } // accountRLP is an internal version of AccountRLP that skips the bloom filter // checks and uses the internal maps to try and retrieve the data. It's meant // to be used if a higher layer's bloom filter hit already. -func (dl *diffLayer) accountRLP(hash common.Hash) ([]byte, error) { +func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() @@ -288,13 +290,18 @@ func (dl *diffLayer) accountRLP(hash common.Hash) ([]byte, error) { // deleted, and is a different notion than an unknown account! if data, ok := dl.accountData[hash]; ok { snapshotDirtyAccountHitMeter.Mark(1) - snapshotDirtyAccountReadMeter.Mark(int64(len(data))) + snapshotDirtyAccountHitDepthHist.Update(int64(depth)) + if n := len(data); n > 0 { + snapshotDirtyAccountReadMeter.Mark(int64(n)) + } else { + snapshotDirtyAccountInexMeter.Mark(1) + } snapshotBloomAccountTrueHitMeter.Mark(1) return data, nil } // Account unknown to this diff, resolve from parent if diff, ok := dl.parent.(*diffLayer); ok { - return diff.accountRLP(hash) + return diff.accountRLP(hash, depth+1) } // Failed to resolve through diff layers, mark a bloom error and use the disk snapshotBloomAccountFalseHitMeter.Mark(1) @@ -318,13 +325,13 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro return dl.origin.Storage(accountHash, storageHash) } // The bloom filter hit, start poking in the internal maps - return dl.storage(accountHash, storageHash) + return dl.storage(accountHash, storageHash, 0) } // storage is an internal version of Storage that skips the bloom filter checks // and uses the internal maps to try and retrieve the data. It's meant to be // used if a higher layer's bloom filter hit already. -func (dl *diffLayer) storage(accountHash, storageHash common.Hash) ([]byte, error) { +func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([]byte, error) { dl.lock.RLock() defer dl.lock.RUnlock() @@ -338,19 +345,26 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash) ([]byte, erro if storage, ok := dl.storageData[accountHash]; ok { if storage == nil { snapshotDirtyStorageHitMeter.Mark(1) + snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + snapshotDirtyStorageInexMeter.Mark(1) snapshotBloomStorageTrueHitMeter.Mark(1) return nil, nil } if data, ok := storage[storageHash]; ok { snapshotDirtyStorageHitMeter.Mark(1) - snapshotDirtyStorageReadMeter.Mark(int64(len(data))) + snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + if n := len(data); n > 0 { + snapshotDirtyStorageReadMeter.Mark(int64(n)) + } else { + snapshotDirtyStorageInexMeter.Mark(1) + } snapshotBloomStorageTrueHitMeter.Mark(1) return data, nil } } // Storage slot unknown to this diff, resolve from parent if diff, ok := dl.parent.(*diffLayer); ok { - return diff.storage(accountHash, storageHash) + return diff.storage(accountHash, storageHash, depth+1) } // Failed to resolve through diff layers, mark a bloom error and use the disk snapshotBloomStorageFalseHitMeter.Mark(1) diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index b1934d2739..7c5b3e3e91 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -104,8 +104,11 @@ func (dl *diskLayer) AccountRLP(hash common.Hash) ([]byte, error) { dl.cache.Set(hash[:], blob) snapshotCleanAccountMissMeter.Mark(1) - snapshotCleanAccountWriteMeter.Mark(int64(len(blob))) - + if n := len(blob); n > 0 { + snapshotCleanAccountWriteMeter.Mark(int64(n)) + } else { + snapshotCleanAccountInexMeter.Mark(1) + } return blob, nil } @@ -141,8 +144,11 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro dl.cache.Set(key, blob) snapshotCleanStorageMissMeter.Mark(1) - snapshotCleanStorageWriteMeter.Mark(int64(len(blob))) - + if n := len(blob); n > 0 { + snapshotCleanStorageWriteMeter.Mark(int64(n)) + } else { + snapshotCleanStorageInexMeter.Mark(1) + } return blob, nil } diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 749f610789..7650cf2c13 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -34,24 +34,31 @@ import ( var ( snapshotCleanAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/hit", nil) snapshotCleanAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/miss", nil) + snapshotCleanAccountInexMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/inex", nil) snapshotCleanAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/read", nil) snapshotCleanAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/account/write", nil) snapshotCleanStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/hit", nil) snapshotCleanStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/miss", nil) + snapshotCleanStorageInexMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/inex", nil) snapshotCleanStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/read", nil) snapshotCleanStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/clean/storage/write", nil) snapshotDirtyAccountHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/hit", nil) snapshotDirtyAccountMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/miss", nil) + snapshotDirtyAccountInexMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/inex", nil) snapshotDirtyAccountReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/read", nil) snapshotDirtyAccountWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/account/write", nil) snapshotDirtyStorageHitMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/hit", nil) snapshotDirtyStorageMissMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/miss", nil) + snapshotDirtyStorageInexMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/inex", nil) snapshotDirtyStorageReadMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/read", nil) snapshotDirtyStorageWriteMeter = metrics.NewRegisteredMeter("state/snapshot/dirty/storage/write", nil) + snapshotDirtyAccountHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/account/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + snapshotDirtyStorageHitDepthHist = metrics.NewRegisteredHistogram("state/snapshot/dirty/storage/hit/depth", nil, metrics.NewExpDecaySample(1028, 0.015)) + snapshotFlushAccountItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/item", nil) snapshotFlushAccountSizeMeter = metrics.NewRegisteredMeter("state/snapshot/flush/account/size", nil) snapshotFlushStorageItemMeter = metrics.NewRegisteredMeter("state/snapshot/flush/storage/item", nil) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index bb5db4ced1..2624f38db1 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -54,6 +54,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c gaspool = new(GasPool).AddGas(block.GasLimit()) ) // Iterate over and process the individual transactions + byzantium := p.config.IsByzantium(block.Number()) for i, tx := range block.Transactions() { // If block precaching was interrupted, abort if interrupt != nil && atomic.LoadUint32(interrupt) == 1 { @@ -64,9 +65,15 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c if err := precacheTransaction(p.config, p.bc, nil, gaspool, statedb, header, tx, cfg); err != nil { return // Ugh, something went horribly wrong, bail out } + // If we're pre-byzantium, pre-load trie nodes for the intermediate root + if !byzantium { + statedb.IntermediateRoot(true) + } + } + // If were post-byzantium, pre-load trie nodes for the final root hash + if byzantium { + statedb.IntermediateRoot(true) } - // All transactions processed, finalize the block to force loading written-only trie paths - statedb.Finalise(true) // TODO(karalabe): should we run this on interrupt too? } // precacheTransaction attempts to apply a transaction to the given state database From 7e389963014ebd175260b7128873a85e5c74bb7b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 5 Nov 2019 19:06:37 +0100 Subject: [PATCH 012/103] core/state/snapshot: implement snapshot layer iteration --- core/state/snapshot/difflayer.go | 289 ++++++++++++++++++++ core/state/snapshot/difflayer_test.go | 363 ++++++++++++++++++++++++++ core/state/snapshot/iteration.md | 60 +++++ 3 files changed, 712 insertions(+) create mode 100644 core/state/snapshot/iteration.md diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 05d55a6fa3..0d97fbdc8f 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -18,6 +18,7 @@ package snapshot import ( "encoding/binary" + "bytes" "fmt" "math" "math/rand" @@ -475,3 +476,291 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { dl.storageList[accountHash] = accountStorageList return accountStorageList } + +type Iterator interface { + // Next steps the iterator forward one element, and returns false if + // the iterator is exhausted + Next() bool + // Key returns the current key + Key() common.Hash + // Seek steps the iterator forward as many elements as needed, so that after + // calling Next(), the iterator will be at a key higher than the given hash + Seek(common.Hash) +} + +func (dl *diffLayer) newIterator() Iterator { + dl.AccountList() + return &dlIterator{dl, -1} +} + +type dlIterator struct { + layer *diffLayer + index int +} + +func (it *dlIterator) Next() bool { + if it.index < len(it.layer.accountList) { + it.index++ + } + return it.index < len(it.layer.accountList) +} + +func (it *dlIterator) Key() common.Hash { + if it.index < len(it.layer.accountList) { + return it.layer.accountList[it.index] + } + return common.Hash{} +} + +func (it *dlIterator) Seek(key common.Hash) { + // Search uses binary search to find and return the smallest index i + // in [0, n) at which f(i) is true + size := len(it.layer.accountList) + index := sort.Search(size, + func(i int) bool { + v := it.layer.accountList[i] + return bytes.Compare(key[:], v[:]) < 0 + }) + it.index = index - 1 +} + +type binaryIterator struct { + a Iterator + b Iterator + aDone bool + bDone bool + k common.Hash +} + +func (dl *diffLayer) newBinaryIterator() Iterator { + parent, ok := dl.parent.(*diffLayer) + if !ok { + // parent is the disk layer + return dl.newIterator() + } + l := &binaryIterator{ + a: dl.newIterator(), + b: parent.newBinaryIterator()} + + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l +} + +func (it *binaryIterator) Next() bool { + + if it.aDone && it.bDone { + return false + } + nextB := it.b.Key() +first: + nextA := it.a.Key() + if it.aDone { + it.bDone = !it.b.Next() + it.k = nextB + return true + } + if it.bDone { + it.aDone = !it.a.Next() + it.k = nextA + return true + } + if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { + it.aDone = !it.a.Next() + it.k = nextA + return true + } else if diff == 0 { + // Now we need to advance one of them + it.aDone = !it.a.Next() + goto first + } + it.bDone = !it.b.Next() + it.k = nextB + return true +} + +func (it *binaryIterator) Key() common.Hash { + return it.k +} +func (it *binaryIterator) Seek(key common.Hash) { + panic("todo: implement") +} + +func (dl *diffLayer) iterators() []Iterator { + if parent, ok := dl.parent.(*diffLayer); ok { + iterators := parent.iterators() + return append(iterators, dl.newIterator()) + } + return []Iterator{dl.newIterator()} +} + +// fastIterator is a more optimized multi-layer iterator which maintains a +// direct mapping of all iterators leading down to the bottom layer +type fastIterator struct { + iterators []Iterator + initiated bool +} + +// Len returns the number of active iterators +func (fi *fastIterator) Len() int { + return len(fi.iterators) +} + +// Less implements sort.Interface +func (fi *fastIterator) Less(i, j int) bool { + a := fi.iterators[i].Key() + b := fi.iterators[j].Key() + return bytes.Compare(a[:], b[:]) < 0 +} + +// Swap implements sort.Interface +func (fi *fastIterator) Swap(i, j int) { + fi.iterators[i], fi.iterators[j] = fi.iterators[j], fi.iterators[i] +} + +// Next implements the Iterator interface. It returns false if no more elemnts +// can be retrieved (false == exhausted) +func (fi *fastIterator) Next() bool { + if len(fi.iterators) == 0 { + return false + } + if !fi.initiated { + // Don't forward first time -- we had to 'Next' once in order to + // do the sorting already + fi.initiated = true + return true + } + return fi.innerNext(0) +} + +// innerNext handles the next operation internally, +// and should be invoked when we know that two elements in the list may have +// the same value. +// For example, if the list becomes [2,3,5,5,8,9,10], then we should invoke +// innerNext(3), which will call Next on elem 3 (the second '5'). It will continue +// along the list and apply the same operation if needed +func (fi *fastIterator) innerNext(pos int) bool { + if !fi.iterators[pos].Next() { + //Exhausted, remove this iterator + fi.remove(pos) + if len(fi.iterators) == 0 { + return false + } + return true + } + if pos == len(fi.iterators)-1 { + // Only one iterator left + return true + } + // We next:ed the elem at 'pos'. Now we may have to re-sort that elem + val, neighbour := fi.iterators[pos].Key(), fi.iterators[pos+1].Key() + diff := bytes.Compare(val[:], neighbour[:]) + if diff < 0 { + // It is still in correct place + return true + } + if diff == 0 { + // It has same value as the neighbour. So still in correct place, but + // we need to iterate on the neighbour + fi.innerNext(pos + 1) + return true + } + // At this point, the elem is in the wrong location, but the + // remaining list is sorted. Find out where to move the elem + iterationNeeded := false + index := sort.Search(len(fi.iterators), func(n int) bool { + if n <= pos { + // No need to search 'behind' us + return false + } + if n == len(fi.iterators)-1 { + // Can always place an elem last + return true + } + neighbour := fi.iterators[n+1].Key() + diff := bytes.Compare(val[:], neighbour[:]) + if diff == 0 { + // The elem we're placing it next to has the same value, + // so it's going to need further iteration + iterationNeeded = true + } + return diff < 0 + }) + fi.move(pos, index) + if iterationNeeded { + fi.innerNext(index) + } + return true +} + +// move moves an iterator to another position in the list +func (fi *fastIterator) move(index, newpos int) { + if newpos > len(fi.iterators)-1 { + newpos = len(fi.iterators) - 1 + } + var ( + elem = fi.iterators[index] + middle = fi.iterators[index+1 : newpos+1] + suffix []Iterator + ) + if newpos < len(fi.iterators)-1 { + suffix = fi.iterators[newpos+1:] + } + fi.iterators = append(fi.iterators[:index], middle...) + fi.iterators = append(fi.iterators, elem) + fi.iterators = append(fi.iterators, suffix...) +} + +// remove drops an iterator from the list +func (fi *fastIterator) remove(index int) { + fi.iterators = append(fi.iterators[:index], fi.iterators[index+1:]...) +} + +// Key returns the current key +func (fi *fastIterator) Key() common.Hash { + return fi.iterators[0].Key() +} + +func (fi *fastIterator) Seek(key common.Hash) { + // We need to apply this across all iterators + var seen = make(map[common.Hash]struct{}) + + length := len(fi.iterators) + for i, it := range fi.iterators { + it.Seek(key) + for { + if !it.Next() { + // To be removed + // swap it to the last position for now + fi.iterators[i], fi.iterators[length-1] = fi.iterators[length-1], fi.iterators[i] + length-- + break + } + v := it.Key() + if _, exist := seen[v]; !exist { + seen[v] = struct{}{} + break + } + } + } + // Now remove those that were placed in the end + fi.iterators = fi.iterators[:length] + // The list is now totally unsorted, need to re-sort the entire list + sort.Sort(fi) + fi.initiated = false +} + +// The fast iterator does not query parents as much. +func (dl *diffLayer) newFastIterator() Iterator { + f := &fastIterator{dl.iterators(), false} + f.Seek(common.Hash{}) + return f +} + +// Debug is a convencience helper during testing +func (fi *fastIterator) Debug() { + for _, it := range fi.iterators { + fmt.Printf(" %v ", it.Key()[31]) + } + fmt.Println() +} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 7d7b21eb05..5f914f6266 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -18,6 +18,7 @@ package snapshot import ( "bytes" + "encoding/binary" "math/big" "math/rand" "testing" @@ -347,3 +348,365 @@ func BenchmarkJournal(b *testing.B) { layer.Journal(new(bytes.Buffer)) } } + +// TestIteratorBasics tests some simple single-layer iteration +func TestIteratorBasics(t *testing.T) { + var ( + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + for i := 0; i < 100; i++ { + h := randomHash() + data := randomAccount() + accounts[h] = data + if rand.Intn(20) < 10 { + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + storage[h] = accStorage + } + } + // Add some (identical) layers on top + parent := newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) + it := parent.newIterator() + verifyIterator(t, 100, it) +} + +type testIterator struct { + values []byte +} + +func newTestIterator(values ...byte) *testIterator { + return &testIterator{values} +} +func (ti *testIterator) Next() bool { + ti.values = ti.values[1:] + if len(ti.values) == 0 { + return false + } + return true +} + +func (ti *testIterator) Key() common.Hash { + return common.BytesToHash([]byte{ti.values[0]}) +} + +func (ti *testIterator) Seek(common.Hash) { + panic("implement me") +} + +func TestFastIteratorBasics(t *testing.T) { + type testCase struct { + lists [][]byte + expKeys []byte + } + for i, tc := range []testCase{ + {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, + {7, 14, 15}, {9, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 4, 7, 8, 9, 13, 14, 15, 16}}, + {lists: [][]byte{{0, 8}, {1, 2, 8}, {7, 14, 15}, {8, 9}, + {9, 10}, {10, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, + } { + var iterators []Iterator + for _, data := range tc.lists { + iterators = append(iterators, newTestIterator(data...)) + + } + fi := &fastIterator{ + iterators: iterators, + initiated: false, + } + count := 0 + for fi.Next() { + if got, exp := fi.Key()[31], tc.expKeys[count]; exp != got { + t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) + } + count++ + } + } +} + +func verifyIterator(t *testing.T, expCount int, it Iterator) { + var ( + i = 0 + last = common.Hash{} + ) + for it.Next() { + v := it.Key() + if bytes.Compare(last[:], v[:]) >= 0 { + t.Errorf("Wrong order:\n%x \n>=\n%x", last, v) + } + i++ + } + if i != expCount { + t.Errorf("iterator len wrong, expected %d, got %d", expCount, i) + } +} + +// TestIteratorTraversal tests some simple multi-layer iteration +func TestIteratorTraversal(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + // entries in multiple layers should only become output once + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + // single layer iterator + verifyIterator(t, 3, child.newIterator()) + // multi-layered binary iterator + verifyIterator(t, 7, child.newBinaryIterator()) + // multi-layered fast iterator + verifyIterator(t, 7, child.newFastIterator()) +} + +func TestIteratorLargeTraversal(t *testing.T) { + // This testcase is a bit notorious -- all layers contain the exact + // same 200 accounts. + var storage = make(map[common.Hash]map[common.Hash][]byte) + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts(200), storage) + child := parent.Update(common.Hash{}, + mkAccounts(200), storage) + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(200), storage) + } + // single layer iterator + verifyIterator(t, 200, child.newIterator()) + // multi-layered binary iterator + verifyIterator(t, 200, child.newBinaryIterator()) + // multi-layered fast iterator + verifyIterator(t, 200, child.newFastIterator()) +} + +// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the exact +// same 200 accounts. That means that we need to process 2000 items, but only +// spit out 200 values eventually. +// +//BenchmarkIteratorTraversal/binary_iterator-6 2008 573290 ns/op 9520 B/op 199 allocs/op +//BenchmarkIteratorTraversal/fast_iterator-6 1946 575596 ns/op 20146 B/op 134 allocs/op +func BenchmarkIteratorTraversal(b *testing.B) { + + var storage = make(map[common.Hash]map[common.Hash][]byte) + + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts(200), storage) + + child := parent.Update(common.Hash{}, + mkAccounts(200), storage) + + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(200), storage) + + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + child.newBinaryIterator() + b.Run("binary iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryIterator() + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastIterator() + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +// BenchmarkIteratorLargeBaselayer is a pretty realistic benchmark, where +// the baselayer is a lot larger than the upper layer. +// +// This is heavy on the binary iterator, which in most cases will have to +// call recursively 100 times for the majority of the values +// +// BenchmarkIteratorLargeBaselayer/binary_iterator-6 585 2067377 ns/op 9520 B/op 199 allocs/op +// BenchmarkIteratorLargeBaselayer/fast_iterator-6 13198 91043 ns/op 8601 B/op 118 allocs/op +func BenchmarkIteratorLargeBaselayer(b *testing.B) { + var storage = make(map[common.Hash]map[common.Hash][]byte) + + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts(2000), storage) + + child := parent.Update(common.Hash{}, + mkAccounts(20), storage) + + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(20), storage) + + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + child.newBinaryIterator() + b.Run("binary iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryIterator() + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastIterator() + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +// TestIteratorFlatting tests what happens when we +// - have a live iterator on child C (parent C1 -> C2 .. CN) +// - flattens C2 all the way into CN +// - continues iterating +// Right now, this "works" simply because the keys do not change -- the +// iterator is not aware that a layer has become stale. This naive +// solution probably won't work in the long run, however +func TestIteratorFlattning(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + // entries in multiple layers should only become output once + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + it := child.newFastIterator() + child.parent.(*diffLayer).flatten() + // The parent should now be stale + verifyIterator(t, 7, it) +} + +func TestIteratorSeek(t *testing.T) { + storage := make(map[common.Hash]map[common.Hash][]byte) + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer{}, common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + it := parent.newIterator() + // expected: ee, f0, ff + it.Seek(common.HexToHash("0xdd")) + verifyIterator(t, 3, it) + + it = parent.newIterator().(*dlIterator) + // expected: ee, f0, ff + it.Seek(common.HexToHash("0xaa")) + verifyIterator(t, 3, it) + + it = parent.newIterator().(*dlIterator) + // expected: nothing + it.Seek(common.HexToHash("0xff")) + verifyIterator(t, 0, it) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + it = child.newFastIterator() + // expected: cc, dd, ee, f0, ff + it.Seek(common.HexToHash("0xbb")) + verifyIterator(t, 5, it) + + it = child.newFastIterator() + it.Seek(common.HexToHash("0xef")) + // exp: f0, ff + verifyIterator(t, 2, it) + + it = child.newFastIterator() + it.Seek(common.HexToHash("0xf0")) + verifyIterator(t, 1, it) + + it.Seek(common.HexToHash("0xff")) + verifyIterator(t, 0, it) + +} diff --git a/core/state/snapshot/iteration.md b/core/state/snapshot/iteration.md new file mode 100644 index 0000000000..ca1962d42b --- /dev/null +++ b/core/state/snapshot/iteration.md @@ -0,0 +1,60 @@ + +## How the fast iterator works + +Consider the following example, where we have `6` iterators, sorted from +left to right in ascending order. + +Our 'primary' `A` iterator is on the left, containing the elements `[0,1,8]` +``` + A B C D E F + + 0 1 2 4 7 9 + 1 2 9 - 14 13 + 8 8 - 15 15 + - - - 16 + - +``` +When we call `Next` on the primary iterator, we get (ignoring the future keys) + +``` +A B C D E F + +1 1 2 4 7 9 +``` +We detect that we now got an equality between our element and the next element. +And we need to continue `Next`ing on the next element + +``` +1 2 2 4 7 9 +``` +And move on: +``` +A B C D E F + +1 2 9 4 7 9 +``` +Now we broke out of the equality, but we need to re-sort the element `C` + +``` +A B D E F C + +1 2 4 7 9 9 +``` + +And after shifting it rightwards, we check equality again, and find `C == F`, and thus +call `Next` on `C` + +``` +A B D E F C + +1 2 4 7 9 - +``` +At this point, `C` was exhausted, and is removed + +``` +A B D E F + +1 2 4 7 9 +``` +And we're done with this step. + From e567675473606cb325c6f51c83b9c5cb0592c8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 5 Dec 2019 15:37:25 +0200 Subject: [PATCH 013/103] core/state/snapshot: move iterator out into its own files --- core/state/snapshot/difflayer.go | 289 ------------------ core/state/snapshot/difflayer_test.go | 363 ----------------------- core/state/snapshot/iterator.go | 116 ++++++++ core/state/snapshot/iterator_binary.go | 115 +++++++ core/state/snapshot/iterator_fast.go | 211 +++++++++++++ core/state/snapshot/iterator_test.go | 396 +++++++++++++++++++++++++ 6 files changed, 838 insertions(+), 652 deletions(-) create mode 100644 core/state/snapshot/iterator.go create mode 100644 core/state/snapshot/iterator_binary.go create mode 100644 core/state/snapshot/iterator_fast.go create mode 100644 core/state/snapshot/iterator_test.go diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0d97fbdc8f..05d55a6fa3 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -18,7 +18,6 @@ package snapshot import ( "encoding/binary" - "bytes" "fmt" "math" "math/rand" @@ -476,291 +475,3 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { dl.storageList[accountHash] = accountStorageList return accountStorageList } - -type Iterator interface { - // Next steps the iterator forward one element, and returns false if - // the iterator is exhausted - Next() bool - // Key returns the current key - Key() common.Hash - // Seek steps the iterator forward as many elements as needed, so that after - // calling Next(), the iterator will be at a key higher than the given hash - Seek(common.Hash) -} - -func (dl *diffLayer) newIterator() Iterator { - dl.AccountList() - return &dlIterator{dl, -1} -} - -type dlIterator struct { - layer *diffLayer - index int -} - -func (it *dlIterator) Next() bool { - if it.index < len(it.layer.accountList) { - it.index++ - } - return it.index < len(it.layer.accountList) -} - -func (it *dlIterator) Key() common.Hash { - if it.index < len(it.layer.accountList) { - return it.layer.accountList[it.index] - } - return common.Hash{} -} - -func (it *dlIterator) Seek(key common.Hash) { - // Search uses binary search to find and return the smallest index i - // in [0, n) at which f(i) is true - size := len(it.layer.accountList) - index := sort.Search(size, - func(i int) bool { - v := it.layer.accountList[i] - return bytes.Compare(key[:], v[:]) < 0 - }) - it.index = index - 1 -} - -type binaryIterator struct { - a Iterator - b Iterator - aDone bool - bDone bool - k common.Hash -} - -func (dl *diffLayer) newBinaryIterator() Iterator { - parent, ok := dl.parent.(*diffLayer) - if !ok { - // parent is the disk layer - return dl.newIterator() - } - l := &binaryIterator{ - a: dl.newIterator(), - b: parent.newBinaryIterator()} - - l.aDone = !l.a.Next() - l.bDone = !l.b.Next() - return l -} - -func (it *binaryIterator) Next() bool { - - if it.aDone && it.bDone { - return false - } - nextB := it.b.Key() -first: - nextA := it.a.Key() - if it.aDone { - it.bDone = !it.b.Next() - it.k = nextB - return true - } - if it.bDone { - it.aDone = !it.a.Next() - it.k = nextA - return true - } - if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { - it.aDone = !it.a.Next() - it.k = nextA - return true - } else if diff == 0 { - // Now we need to advance one of them - it.aDone = !it.a.Next() - goto first - } - it.bDone = !it.b.Next() - it.k = nextB - return true -} - -func (it *binaryIterator) Key() common.Hash { - return it.k -} -func (it *binaryIterator) Seek(key common.Hash) { - panic("todo: implement") -} - -func (dl *diffLayer) iterators() []Iterator { - if parent, ok := dl.parent.(*diffLayer); ok { - iterators := parent.iterators() - return append(iterators, dl.newIterator()) - } - return []Iterator{dl.newIterator()} -} - -// fastIterator is a more optimized multi-layer iterator which maintains a -// direct mapping of all iterators leading down to the bottom layer -type fastIterator struct { - iterators []Iterator - initiated bool -} - -// Len returns the number of active iterators -func (fi *fastIterator) Len() int { - return len(fi.iterators) -} - -// Less implements sort.Interface -func (fi *fastIterator) Less(i, j int) bool { - a := fi.iterators[i].Key() - b := fi.iterators[j].Key() - return bytes.Compare(a[:], b[:]) < 0 -} - -// Swap implements sort.Interface -func (fi *fastIterator) Swap(i, j int) { - fi.iterators[i], fi.iterators[j] = fi.iterators[j], fi.iterators[i] -} - -// Next implements the Iterator interface. It returns false if no more elemnts -// can be retrieved (false == exhausted) -func (fi *fastIterator) Next() bool { - if len(fi.iterators) == 0 { - return false - } - if !fi.initiated { - // Don't forward first time -- we had to 'Next' once in order to - // do the sorting already - fi.initiated = true - return true - } - return fi.innerNext(0) -} - -// innerNext handles the next operation internally, -// and should be invoked when we know that two elements in the list may have -// the same value. -// For example, if the list becomes [2,3,5,5,8,9,10], then we should invoke -// innerNext(3), which will call Next on elem 3 (the second '5'). It will continue -// along the list and apply the same operation if needed -func (fi *fastIterator) innerNext(pos int) bool { - if !fi.iterators[pos].Next() { - //Exhausted, remove this iterator - fi.remove(pos) - if len(fi.iterators) == 0 { - return false - } - return true - } - if pos == len(fi.iterators)-1 { - // Only one iterator left - return true - } - // We next:ed the elem at 'pos'. Now we may have to re-sort that elem - val, neighbour := fi.iterators[pos].Key(), fi.iterators[pos+1].Key() - diff := bytes.Compare(val[:], neighbour[:]) - if diff < 0 { - // It is still in correct place - return true - } - if diff == 0 { - // It has same value as the neighbour. So still in correct place, but - // we need to iterate on the neighbour - fi.innerNext(pos + 1) - return true - } - // At this point, the elem is in the wrong location, but the - // remaining list is sorted. Find out where to move the elem - iterationNeeded := false - index := sort.Search(len(fi.iterators), func(n int) bool { - if n <= pos { - // No need to search 'behind' us - return false - } - if n == len(fi.iterators)-1 { - // Can always place an elem last - return true - } - neighbour := fi.iterators[n+1].Key() - diff := bytes.Compare(val[:], neighbour[:]) - if diff == 0 { - // The elem we're placing it next to has the same value, - // so it's going to need further iteration - iterationNeeded = true - } - return diff < 0 - }) - fi.move(pos, index) - if iterationNeeded { - fi.innerNext(index) - } - return true -} - -// move moves an iterator to another position in the list -func (fi *fastIterator) move(index, newpos int) { - if newpos > len(fi.iterators)-1 { - newpos = len(fi.iterators) - 1 - } - var ( - elem = fi.iterators[index] - middle = fi.iterators[index+1 : newpos+1] - suffix []Iterator - ) - if newpos < len(fi.iterators)-1 { - suffix = fi.iterators[newpos+1:] - } - fi.iterators = append(fi.iterators[:index], middle...) - fi.iterators = append(fi.iterators, elem) - fi.iterators = append(fi.iterators, suffix...) -} - -// remove drops an iterator from the list -func (fi *fastIterator) remove(index int) { - fi.iterators = append(fi.iterators[:index], fi.iterators[index+1:]...) -} - -// Key returns the current key -func (fi *fastIterator) Key() common.Hash { - return fi.iterators[0].Key() -} - -func (fi *fastIterator) Seek(key common.Hash) { - // We need to apply this across all iterators - var seen = make(map[common.Hash]struct{}) - - length := len(fi.iterators) - for i, it := range fi.iterators { - it.Seek(key) - for { - if !it.Next() { - // To be removed - // swap it to the last position for now - fi.iterators[i], fi.iterators[length-1] = fi.iterators[length-1], fi.iterators[i] - length-- - break - } - v := it.Key() - if _, exist := seen[v]; !exist { - seen[v] = struct{}{} - break - } - } - } - // Now remove those that were placed in the end - fi.iterators = fi.iterators[:length] - // The list is now totally unsorted, need to re-sort the entire list - sort.Sort(fi) - fi.initiated = false -} - -// The fast iterator does not query parents as much. -func (dl *diffLayer) newFastIterator() Iterator { - f := &fastIterator{dl.iterators(), false} - f.Seek(common.Hash{}) - return f -} - -// Debug is a convencience helper during testing -func (fi *fastIterator) Debug() { - for _, it := range fi.iterators { - fmt.Printf(" %v ", it.Key()[31]) - } - fmt.Println() -} diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 5f914f6266..7d7b21eb05 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -18,7 +18,6 @@ package snapshot import ( "bytes" - "encoding/binary" "math/big" "math/rand" "testing" @@ -348,365 +347,3 @@ func BenchmarkJournal(b *testing.B) { layer.Journal(new(bytes.Buffer)) } } - -// TestIteratorBasics tests some simple single-layer iteration -func TestIteratorBasics(t *testing.T) { - var ( - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - // Fill up a parent - for i := 0; i < 100; i++ { - h := randomHash() - data := randomAccount() - accounts[h] = data - if rand.Intn(20) < 10 { - accStorage := make(map[common.Hash][]byte) - value := make([]byte, 32) - rand.Read(value) - accStorage[randomHash()] = value - storage[h] = accStorage - } - } - // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer{}, common.Hash{}, accounts, storage) - it := parent.newIterator() - verifyIterator(t, 100, it) -} - -type testIterator struct { - values []byte -} - -func newTestIterator(values ...byte) *testIterator { - return &testIterator{values} -} -func (ti *testIterator) Next() bool { - ti.values = ti.values[1:] - if len(ti.values) == 0 { - return false - } - return true -} - -func (ti *testIterator) Key() common.Hash { - return common.BytesToHash([]byte{ti.values[0]}) -} - -func (ti *testIterator) Seek(common.Hash) { - panic("implement me") -} - -func TestFastIteratorBasics(t *testing.T) { - type testCase struct { - lists [][]byte - expKeys []byte - } - for i, tc := range []testCase{ - {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, - {7, 14, 15}, {9, 13, 15, 16}}, - expKeys: []byte{0, 1, 2, 4, 7, 8, 9, 13, 14, 15, 16}}, - {lists: [][]byte{{0, 8}, {1, 2, 8}, {7, 14, 15}, {8, 9}, - {9, 10}, {10, 13, 15, 16}}, - expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, - } { - var iterators []Iterator - for _, data := range tc.lists { - iterators = append(iterators, newTestIterator(data...)) - - } - fi := &fastIterator{ - iterators: iterators, - initiated: false, - } - count := 0 - for fi.Next() { - if got, exp := fi.Key()[31], tc.expKeys[count]; exp != got { - t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) - } - count++ - } - } -} - -func verifyIterator(t *testing.T, expCount int, it Iterator) { - var ( - i = 0 - last = common.Hash{} - ) - for it.Next() { - v := it.Key() - if bytes.Compare(last[:], v[:]) >= 0 { - t.Errorf("Wrong order:\n%x \n>=\n%x", last, v) - } - i++ - } - if i != expCount { - t.Errorf("iterator len wrong, expected %d, got %d", expCount, i) - } -} - -// TestIteratorTraversal tests some simple multi-layer iteration -func TestIteratorTraversal(t *testing.T) { - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts - } - // entries in multiple layers should only become output once - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) - - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) - - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) - - // single layer iterator - verifyIterator(t, 3, child.newIterator()) - // multi-layered binary iterator - verifyIterator(t, 7, child.newBinaryIterator()) - // multi-layered fast iterator - verifyIterator(t, 7, child.newFastIterator()) -} - -func TestIteratorLargeTraversal(t *testing.T) { - // This testcase is a bit notorious -- all layers contain the exact - // same 200 accounts. - var storage = make(map[common.Hash]map[common.Hash][]byte) - mkAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts(200), storage) - child := parent.Update(common.Hash{}, - mkAccounts(200), storage) - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(200), storage) - } - // single layer iterator - verifyIterator(t, 200, child.newIterator()) - // multi-layered binary iterator - verifyIterator(t, 200, child.newBinaryIterator()) - // multi-layered fast iterator - verifyIterator(t, 200, child.newFastIterator()) -} - -// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the exact -// same 200 accounts. That means that we need to process 2000 items, but only -// spit out 200 values eventually. -// -//BenchmarkIteratorTraversal/binary_iterator-6 2008 573290 ns/op 9520 B/op 199 allocs/op -//BenchmarkIteratorTraversal/fast_iterator-6 1946 575596 ns/op 20146 B/op 134 allocs/op -func BenchmarkIteratorTraversal(b *testing.B) { - - var storage = make(map[common.Hash]map[common.Hash][]byte) - - mkAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts(200), storage) - - child := parent.Update(common.Hash{}, - mkAccounts(200), storage) - - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(200), storage) - - } - // We call this once before the benchmark, so the creation of - // sorted accountlists are not included in the results. - child.newBinaryIterator() - b.Run("binary iterator", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := child.newBinaryIterator() - for it.Next() { - got++ - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := child.newFastIterator() - for it.Next() { - got++ - } - if exp := 200; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) -} - -// BenchmarkIteratorLargeBaselayer is a pretty realistic benchmark, where -// the baselayer is a lot larger than the upper layer. -// -// This is heavy on the binary iterator, which in most cases will have to -// call recursively 100 times for the majority of the values -// -// BenchmarkIteratorLargeBaselayer/binary_iterator-6 585 2067377 ns/op 9520 B/op 199 allocs/op -// BenchmarkIteratorLargeBaselayer/fast_iterator-6 13198 91043 ns/op 8601 B/op 118 allocs/op -func BenchmarkIteratorLargeBaselayer(b *testing.B) { - var storage = make(map[common.Hash]map[common.Hash][]byte) - - mkAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts(2000), storage) - - child := parent.Update(common.Hash{}, - mkAccounts(20), storage) - - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(20), storage) - - } - // We call this once before the benchmark, so the creation of - // sorted accountlists are not included in the results. - child.newBinaryIterator() - b.Run("binary iterator", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := child.newBinaryIterator() - for it.Next() { - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := child.newFastIterator() - for it.Next() { - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) -} - -// TestIteratorFlatting tests what happens when we -// - have a live iterator on child C (parent C1 -> C2 .. CN) -// - flattens C2 all the way into CN -// - continues iterating -// Right now, this "works" simply because the keys do not change -- the -// iterator is not aware that a layer has become stale. This naive -// solution probably won't work in the long run, however -func TestIteratorFlattning(t *testing.T) { - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts - } - // entries in multiple layers should only become output once - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) - - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) - - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) - - it := child.newFastIterator() - child.parent.(*diffLayer).flatten() - // The parent should now be stale - verifyIterator(t, 7, it) -} - -func TestIteratorSeek(t *testing.T) { - storage := make(map[common.Hash]map[common.Hash][]byte) - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts - } - parent := newDiffLayer(emptyLayer{}, common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) - it := parent.newIterator() - // expected: ee, f0, ff - it.Seek(common.HexToHash("0xdd")) - verifyIterator(t, 3, it) - - it = parent.newIterator().(*dlIterator) - // expected: ee, f0, ff - it.Seek(common.HexToHash("0xaa")) - verifyIterator(t, 3, it) - - it = parent.newIterator().(*dlIterator) - // expected: nothing - it.Seek(common.HexToHash("0xff")) - verifyIterator(t, 0, it) - - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) - - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) - - it = child.newFastIterator() - // expected: cc, dd, ee, f0, ff - it.Seek(common.HexToHash("0xbb")) - verifyIterator(t, 5, it) - - it = child.newFastIterator() - it.Seek(common.HexToHash("0xef")) - // exp: f0, ff - verifyIterator(t, 2, it) - - it = child.newFastIterator() - it.Seek(common.HexToHash("0xf0")) - verifyIterator(t, 1, it) - - it.Seek(common.HexToHash("0xff")) - verifyIterator(t, 0, it) - -} diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go new file mode 100644 index 0000000000..6df7b3147e --- /dev/null +++ b/core/state/snapshot/iterator.go @@ -0,0 +1,116 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "sort" + + "github.com/ethereum/go-ethereum/common" +) + +// AccountIterator is an iterator to step over all the accounts in a snapshot, +// which may or may npt be composed of multiple layers. +type AccountIterator interface { + // Seek steps the iterator forward as many elements as needed, so that after + // calling Next(), the iterator will be at a key higher than the given hash. + Seek(hash common.Hash) + + // Next steps the iterator forward one element, returning false if exhausted, + // or an error if iteration failed for some reason (e.g. root being iterated + // becomes stale and garbage collected). + Next() bool + + // Error returns any failure that occurred during iteration, which might have + // caused a premature iteration exit (e.g. snapshot stack becoming stale). + Error() error + + // Key returns the hash of the account the iterator is currently at. + Key() common.Hash + + // Value returns the RLP encoded slim account the iterator is currently at. + // An error will be returned if the iterator becomes invalid (e.g. snaph + Value() []byte +} + +// diffAccountIterator is an account iterator that steps over the accounts (both +// live and deleted) contained within a single +type diffAccountIterator struct { + layer *diffLayer + index int +} + +func (dl *diffLayer) newAccountIterator() *diffAccountIterator { + dl.AccountList() + return &diffAccountIterator{layer: dl, index: -1} +} + +// Seek steps the iterator forward as many elements as needed, so that after +// calling Next(), the iterator will be at a key higher than the given hash. +func (it *diffAccountIterator) Seek(key common.Hash) { + // Search uses binary search to find and return the smallest index i + // in [0, n) at which f(i) is true + index := sort.Search(len(it.layer.accountList), func(i int) bool { + return bytes.Compare(key[:], it.layer.accountList[i][:]) < 0 + }) + it.index = index - 1 +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diffAccountIterator) Next() bool { + if it.index < len(it.layer.accountList) { + it.index++ + } + return it.index < len(it.layer.accountList) +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +// +// A diff layer is immutable after creation content wise and can always be fully +// iterated without error, so this method always returns nil. +func (it *diffAccountIterator) Error() error { + return nil +} + +// Key returns the hash of the account the iterator is currently at. +func (it *diffAccountIterator) Key() common.Hash { + if it.index < len(it.layer.accountList) { + return it.layer.accountList[it.index] + } + return common.Hash{} +} + +// Value returns the RLP encoded slim account the iterator is currently at. +func (it *diffAccountIterator) Value() []byte { + it.layer.lock.RLock() + defer it.layer.lock.RUnlock() + + hash := it.layer.accountList[it.index] + if data, ok := it.layer.accountData[hash]; ok { + return data + } + panic("iterator references non-existent layer account") +} + +func (dl *diffLayer) iterators() []AccountIterator { + if parent, ok := dl.parent.(*diffLayer); ok { + iterators := parent.iterators() + return append(iterators, dl.newAccountIterator()) + } + return []AccountIterator{dl.newAccountIterator()} +} diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go new file mode 100644 index 0000000000..7ff6e3337d --- /dev/null +++ b/core/state/snapshot/iterator_binary.go @@ -0,0 +1,115 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + + "github.com/ethereum/go-ethereum/common" +) + +// binaryAccountIterator is a simplistic iterator to step over the accounts in +// a snapshot, which may or may npt be composed of multiple layers. Performance +// wise this iterator is slow, it's meant for cross validating the fast one, +type binaryAccountIterator struct { + a *diffAccountIterator + b AccountIterator + aDone bool + bDone bool + k common.Hash + fail error +} + +// newBinaryAccountIterator creates a simplistic account iterator to step over +// all the accounts in a slow, but eaily verifyable way. +func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { + parent, ok := dl.parent.(*diffLayer) + if !ok { + // parent is the disk layer + return dl.newAccountIterator() + } + l := &binaryAccountIterator{ + a: dl.newAccountIterator(), + b: parent.newBinaryAccountIterator(), + } + l.aDone = !l.a.Next() + l.bDone = !l.b.Next() + return l +} + +// Seek steps the iterator forward as many elements as needed, so that after +// calling Next(), the iterator will be at a key higher than the given hash. +func (it *binaryAccountIterator) Seek(key common.Hash) { + panic("todo: implement") +} + +// Next steps the iterator forward one element, returning false if exhausted, +// or an error if iteration failed for some reason (e.g. root being iterated +// becomes stale and garbage collected). +func (it *binaryAccountIterator) Next() bool { + if it.aDone && it.bDone { + return false + } + nextB := it.b.Key() +first: + nextA := it.a.Key() + if it.aDone { + it.bDone = !it.b.Next() + it.k = nextB + return true + } + if it.bDone { + it.aDone = !it.a.Next() + it.k = nextA + return true + } + if diff := bytes.Compare(nextA[:], nextB[:]); diff < 0 { + it.aDone = !it.a.Next() + it.k = nextA + return true + } else if diff == 0 { + // Now we need to advance one of them + it.aDone = !it.a.Next() + goto first + } + it.bDone = !it.b.Next() + it.k = nextB + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (it *binaryAccountIterator) Error() error { + return it.fail +} + +// Key returns the hash of the account the iterator is currently at. +func (it *binaryAccountIterator) Key() common.Hash { + return it.k +} + +// Value returns the RLP encoded slim account the iterator is currently at, or +// nil if the iterated snapshot stack became stale (you can check Error after +// to see if it failed or not). +func (it *binaryAccountIterator) Value() []byte { + blob, err := it.a.layer.AccountRLP(it.k) + if err != nil { + it.fail = err + return nil + } + return blob +} diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go new file mode 100644 index 0000000000..d3f3153532 --- /dev/null +++ b/core/state/snapshot/iterator_fast.go @@ -0,0 +1,211 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "fmt" + "sort" + + "github.com/ethereum/go-ethereum/common" +) + +// fastAccountIterator is a more optimized multi-layer iterator which maintains a +// direct mapping of all iterators leading down to the bottom layer +type fastAccountIterator struct { + iterators []AccountIterator + initiated bool + fail error +} + +// The fast iterator does not query parents as much. +func (dl *diffLayer) newFastAccountIterator() AccountIterator { + f := &fastAccountIterator{ + iterators: dl.iterators(), + initiated: false, + } + f.Seek(common.Hash{}) + return f +} + +// Len returns the number of active iterators +func (fi *fastAccountIterator) Len() int { + return len(fi.iterators) +} + +// Less implements sort.Interface +func (fi *fastAccountIterator) Less(i, j int) bool { + a := fi.iterators[i].Key() + b := fi.iterators[j].Key() + return bytes.Compare(a[:], b[:]) < 0 +} + +// Swap implements sort.Interface +func (fi *fastAccountIterator) Swap(i, j int) { + fi.iterators[i], fi.iterators[j] = fi.iterators[j], fi.iterators[i] +} + +func (fi *fastAccountIterator) Seek(key common.Hash) { + // We need to apply this across all iterators + var seen = make(map[common.Hash]struct{}) + + length := len(fi.iterators) + for i, it := range fi.iterators { + it.Seek(key) + for { + if !it.Next() { + // To be removed + // swap it to the last position for now + fi.iterators[i], fi.iterators[length-1] = fi.iterators[length-1], fi.iterators[i] + length-- + break + } + v := it.Key() + if _, exist := seen[v]; !exist { + seen[v] = struct{}{} + break + } + } + } + // Now remove those that were placed in the end + fi.iterators = fi.iterators[:length] + // The list is now totally unsorted, need to re-sort the entire list + sort.Sort(fi) + fi.initiated = false +} + +// Next implements the Iterator interface. It returns false if no more elemnts +// can be retrieved (false == exhausted) +func (fi *fastAccountIterator) Next() bool { + if len(fi.iterators) == 0 { + return false + } + if !fi.initiated { + // Don't forward first time -- we had to 'Next' once in order to + // do the sorting already + fi.initiated = true + return true + } + return fi.innerNext(0) +} + +// innerNext handles the next operation internally, +// and should be invoked when we know that two elements in the list may have +// the same value. +// For example, if the list becomes [2,3,5,5,8,9,10], then we should invoke +// innerNext(3), which will call Next on elem 3 (the second '5'). It will continue +// along the list and apply the same operation if needed +func (fi *fastAccountIterator) innerNext(pos int) bool { + if !fi.iterators[pos].Next() { + //Exhausted, remove this iterator + fi.remove(pos) + if len(fi.iterators) == 0 { + return false + } + return true + } + if pos == len(fi.iterators)-1 { + // Only one iterator left + return true + } + // We next:ed the elem at 'pos'. Now we may have to re-sort that elem + val, neighbour := fi.iterators[pos].Key(), fi.iterators[pos+1].Key() + diff := bytes.Compare(val[:], neighbour[:]) + if diff < 0 { + // It is still in correct place + return true + } + if diff == 0 { + // It has same value as the neighbour. So still in correct place, but + // we need to iterate on the neighbour + fi.innerNext(pos + 1) + return true + } + // At this point, the elem is in the wrong location, but the + // remaining list is sorted. Find out where to move the elem + iterationNeeded := false + index := sort.Search(len(fi.iterators), func(n int) bool { + if n <= pos { + // No need to search 'behind' us + return false + } + if n == len(fi.iterators)-1 { + // Can always place an elem last + return true + } + neighbour := fi.iterators[n+1].Key() + diff := bytes.Compare(val[:], neighbour[:]) + if diff == 0 { + // The elem we're placing it next to has the same value, + // so it's going to need further iteration + iterationNeeded = true + } + return diff < 0 + }) + fi.move(pos, index) + if iterationNeeded { + fi.innerNext(index) + } + return true +} + +// move moves an iterator to another position in the list +func (fi *fastAccountIterator) move(index, newpos int) { + if newpos > len(fi.iterators)-1 { + newpos = len(fi.iterators) - 1 + } + var ( + elem = fi.iterators[index] + middle = fi.iterators[index+1 : newpos+1] + suffix []AccountIterator + ) + if newpos < len(fi.iterators)-1 { + suffix = fi.iterators[newpos+1:] + } + fi.iterators = append(fi.iterators[:index], middle...) + fi.iterators = append(fi.iterators, elem) + fi.iterators = append(fi.iterators, suffix...) +} + +// remove drops an iterator from the list +func (fi *fastAccountIterator) remove(index int) { + fi.iterators = append(fi.iterators[:index], fi.iterators[index+1:]...) +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (fi *fastAccountIterator) Error() error { + return fi.fail +} + +// Key returns the current key +func (fi *fastAccountIterator) Key() common.Hash { + return fi.iterators[0].Key() +} + +// Value returns the current key +func (fi *fastAccountIterator) Value() []byte { + panic("todo") +} + +// Debug is a convencience helper during testing +func (fi *fastAccountIterator) Debug() { + for _, it := range fi.iterators { + fmt.Printf(" %v ", it.Key()[31]) + } + fmt.Println() +} diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go new file mode 100644 index 0000000000..5975231891 --- /dev/null +++ b/core/state/snapshot/iterator_test.go @@ -0,0 +1,396 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "bytes" + "encoding/binary" + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +// TestIteratorBasics tests some simple single-layer iteration +func TestIteratorBasics(t *testing.T) { + var ( + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + // Fill up a parent + for i := 0; i < 100; i++ { + h := randomHash() + data := randomAccount() + accounts[h] = data + if rand.Intn(20) < 10 { + accStorage := make(map[common.Hash][]byte) + value := make([]byte, 32) + rand.Read(value) + accStorage[randomHash()] = value + storage[h] = accStorage + } + } + // Add some (identical) layers on top + parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) + it := parent.newAccountIterator() + verifyIterator(t, 100, it) +} + +type testIterator struct { + values []byte +} + +func newTestIterator(values ...byte) *testIterator { + return &testIterator{values} +} + +func (ti *testIterator) Seek(common.Hash) { + panic("implement me") +} + +func (ti *testIterator) Next() bool { + ti.values = ti.values[1:] + if len(ti.values) == 0 { + return false + } + return true +} + +func (ti *testIterator) Error() error { + panic("implement me") +} + +func (ti *testIterator) Key() common.Hash { + return common.BytesToHash([]byte{ti.values[0]}) +} + +func (ti *testIterator) Value() []byte { + panic("implement me") +} + +func TestFastIteratorBasics(t *testing.T) { + type testCase struct { + lists [][]byte + expKeys []byte + } + for i, tc := range []testCase{ + {lists: [][]byte{{0, 1, 8}, {1, 2, 8}, {2, 9}, {4}, + {7, 14, 15}, {9, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 4, 7, 8, 9, 13, 14, 15, 16}}, + {lists: [][]byte{{0, 8}, {1, 2, 8}, {7, 14, 15}, {8, 9}, + {9, 10}, {10, 13, 15, 16}}, + expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, + } { + var iterators []AccountIterator + for _, data := range tc.lists { + iterators = append(iterators, newTestIterator(data...)) + + } + fi := &fastAccountIterator{ + iterators: iterators, + initiated: false, + } + count := 0 + for fi.Next() { + if got, exp := fi.Key()[31], tc.expKeys[count]; exp != got { + t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) + } + count++ + } + } +} + +func verifyIterator(t *testing.T, expCount int, it AccountIterator) { + var ( + i = 0 + last = common.Hash{} + ) + for it.Next() { + v := it.Key() + if bytes.Compare(last[:], v[:]) >= 0 { + t.Errorf("Wrong order:\n%x \n>=\n%x", last, v) + } + i++ + } + if i != expCount { + t.Errorf("iterator len wrong, expected %d, got %d", expCount, i) + } +} + +// TestIteratorTraversal tests some simple multi-layer iteration +func TestIteratorTraversal(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + // entries in multiple layers should only become output once + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + // single layer iterator + verifyIterator(t, 3, child.newAccountIterator()) + // multi-layered binary iterator + verifyIterator(t, 7, child.newBinaryAccountIterator()) + // multi-layered fast iterator + verifyIterator(t, 7, child.newFastAccountIterator()) +} + +func TestIteratorLargeTraversal(t *testing.T) { + // This testcase is a bit notorious -- all layers contain the exact + // same 200 accounts. + var storage = make(map[common.Hash]map[common.Hash][]byte) + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts(200), storage) + child := parent.Update(common.Hash{}, + mkAccounts(200), storage) + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(200), storage) + } + // single layer iterator + verifyIterator(t, 200, child.newAccountIterator()) + // multi-layered binary iterator + verifyIterator(t, 200, child.newBinaryAccountIterator()) + // multi-layered fast iterator + verifyIterator(t, 200, child.newFastAccountIterator()) +} + +// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the exact +// same 200 accounts. That means that we need to process 2000 items, but only +// spit out 200 values eventually. +// +//BenchmarkIteratorTraversal/binary_iterator-6 2008 573290 ns/op 9520 B/op 199 allocs/op +//BenchmarkIteratorTraversal/fast_iterator-6 1946 575596 ns/op 20146 B/op 134 allocs/op +func BenchmarkIteratorTraversal(b *testing.B) { + + var storage = make(map[common.Hash]map[common.Hash][]byte) + + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts(200), storage) + + child := parent.Update(common.Hash{}, + mkAccounts(200), storage) + + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(200), storage) + + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + child.newBinaryAccountIterator() + b.Run("binary iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryAccountIterator() + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastAccountIterator() + for it.Next() { + got++ + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +// BenchmarkIteratorLargeBaselayer is a pretty realistic benchmark, where +// the baselayer is a lot larger than the upper layer. +// +// This is heavy on the binary iterator, which in most cases will have to +// call recursively 100 times for the majority of the values +// +// BenchmarkIteratorLargeBaselayer/binary_iterator-6 585 2067377 ns/op 9520 B/op 199 allocs/op +// BenchmarkIteratorLargeBaselayer/fast_iterator-6 13198 91043 ns/op 8601 B/op 118 allocs/op +func BenchmarkIteratorLargeBaselayer(b *testing.B) { + var storage = make(map[common.Hash]map[common.Hash][]byte) + + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts(2000), storage) + + child := parent.Update(common.Hash{}, + mkAccounts(20), storage) + + for i := 2; i < 100; i++ { + child = child.Update(common.Hash{}, + mkAccounts(20), storage) + + } + // We call this once before the benchmark, so the creation of + // sorted accountlists are not included in the results. + child.newBinaryAccountIterator() + b.Run("binary iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryAccountIterator() + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastAccountIterator() + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) +} + +// TestIteratorFlatting tests what happens when we +// - have a live iterator on child C (parent C1 -> C2 .. CN) +// - flattens C2 all the way into CN +// - continues iterating +// Right now, this "works" simply because the keys do not change -- the +// iterator is not aware that a layer has become stale. This naive +// solution probably won't work in the long run, however +func TestIteratorFlattning(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + // entries in multiple layers should only become output once + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + it := child.newFastAccountIterator() + child.parent.(*diffLayer).flatten() + // The parent should now be stale + verifyIterator(t, 7, it) +} + +func TestIteratorSeek(t *testing.T) { + storage := make(map[common.Hash]map[common.Hash][]byte) + mkAccounts := func(args ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, h := range args { + accounts[common.HexToHash(h)] = randomAccount() + } + return accounts + } + parent := newDiffLayer(emptyLayer(), common.Hash{}, + mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + it := AccountIterator(parent.newAccountIterator()) + // expected: ee, f0, ff + it.Seek(common.HexToHash("0xdd")) + verifyIterator(t, 3, it) + + it = parent.newAccountIterator() + // expected: ee, f0, ff + it.Seek(common.HexToHash("0xaa")) + verifyIterator(t, 3, it) + + it = parent.newAccountIterator() + // expected: nothing + it.Seek(common.HexToHash("0xff")) + verifyIterator(t, 0, it) + + child := parent.Update(common.Hash{}, + mkAccounts("0xbb", "0xdd", "0xf0"), storage) + + child = child.Update(common.Hash{}, + mkAccounts("0xcc", "0xf0", "0xff"), storage) + + it = child.newFastAccountIterator() + // expected: cc, dd, ee, f0, ff + it.Seek(common.HexToHash("0xbb")) + verifyIterator(t, 5, it) + + it = child.newFastAccountIterator() + it.Seek(common.HexToHash("0xef")) + // exp: f0, ff + verifyIterator(t, 2, it) + + it = child.newFastAccountIterator() + it.Seek(common.HexToHash("0xf0")) + verifyIterator(t, 1, it) + + it.Seek(common.HexToHash("0xff")) + verifyIterator(t, 0, it) +} From e5708353562405684ef7d34602635cc25231ad36 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 6 Dec 2019 23:27:18 +0100 Subject: [PATCH 014/103] core/state/snapshot: implement iterator priority for fast direct data lookup --- core/state/snapshot/iterator_fast.go | 117 +++++++++++------ core/state/snapshot/iterator_test.go | 180 +++++++++++++++++++++++++-- 2 files changed, 250 insertions(+), 47 deletions(-) diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index d3f3153532..8df037e9f4 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -24,20 +24,27 @@ import ( "github.com/ethereum/go-ethereum/common" ) +type weightedIterator struct { + it AccountIterator + priority int +} + // fastAccountIterator is a more optimized multi-layer iterator which maintains a // direct mapping of all iterators leading down to the bottom layer type fastAccountIterator struct { - iterators []AccountIterator + iterators []*weightedIterator initiated bool fail error } -// The fast iterator does not query parents as much. +// newFastAccountIterator creates a new fastAccountIterator func (dl *diffLayer) newFastAccountIterator() AccountIterator { f := &fastAccountIterator{ - iterators: dl.iterators(), initiated: false, } + for i, it := range dl.iterators() { + f.iterators = append(f.iterators, &weightedIterator{it, -i}) + } f.Seek(common.Hash{}) return f } @@ -49,9 +56,17 @@ func (fi *fastAccountIterator) Len() int { // Less implements sort.Interface func (fi *fastAccountIterator) Less(i, j int) bool { - a := fi.iterators[i].Key() - b := fi.iterators[j].Key() - return bytes.Compare(a[:], b[:]) < 0 + a := fi.iterators[i].it.Key() + b := fi.iterators[j].it.Key() + bDiff := bytes.Compare(a[:], b[:]) + if bDiff < 0 { + return true + } + if bDiff > 0 { + return false + } + // keys are equal, sort by iterator priority + return fi.iterators[i].priority < fi.iterators[j].priority } // Swap implements sort.Interface @@ -61,23 +76,42 @@ func (fi *fastAccountIterator) Swap(i, j int) { func (fi *fastAccountIterator) Seek(key common.Hash) { // We need to apply this across all iterators - var seen = make(map[common.Hash]struct{}) + var seen = make(map[common.Hash]int) length := len(fi.iterators) - for i, it := range fi.iterators { - it.Seek(key) + for i := 0; i < len(fi.iterators); i++ { + //for i, it := range fi.iterators { + it := fi.iterators[i] + it.it.Seek(key) for { - if !it.Next() { + if !it.it.Next() { // To be removed // swap it to the last position for now fi.iterators[i], fi.iterators[length-1] = fi.iterators[length-1], fi.iterators[i] length-- break } - v := it.Key() - if _, exist := seen[v]; !exist { - seen[v] = struct{}{} + v := it.it.Key() + if other, exist := seen[v]; !exist { + seen[v] = i break + } else { + // This whole else-block can be avoided, if we instead + // do an inital priority-sort of the iterators. If we do that, + // then we'll only wind up here if a lower-priority (preferred) iterator + // has the same value, and then we will always just continue. + // However, it costs an extra sort, so it's probably not better + + // One needs to be progressed, use priority to determine which + if fi.iterators[other].priority < it.priority { + // the 'it' should be progressed + continue + } else { + // the 'other' should be progressed - swap them + it = fi.iterators[other] + fi.iterators[other], fi.iterators[i] = fi.iterators[i], fi.iterators[other] + continue + } } } } @@ -110,7 +144,7 @@ func (fi *fastAccountIterator) Next() bool { // innerNext(3), which will call Next on elem 3 (the second '5'). It will continue // along the list and apply the same operation if needed func (fi *fastAccountIterator) innerNext(pos int) bool { - if !fi.iterators[pos].Next() { + if !fi.iterators[pos].it.Next() { //Exhausted, remove this iterator fi.remove(pos) if len(fi.iterators) == 0 { @@ -123,23 +157,23 @@ func (fi *fastAccountIterator) innerNext(pos int) bool { return true } // We next:ed the elem at 'pos'. Now we may have to re-sort that elem - val, neighbour := fi.iterators[pos].Key(), fi.iterators[pos+1].Key() - diff := bytes.Compare(val[:], neighbour[:]) - if diff < 0 { + var ( + current, neighbour = fi.iterators[pos], fi.iterators[pos+1] + val, neighbourVal = current.it.Key(), neighbour.it.Key() + ) + if diff := bytes.Compare(val[:], neighbourVal[:]); diff < 0 { // It is still in correct place return true - } - if diff == 0 { - // It has same value as the neighbour. So still in correct place, but - // we need to iterate on the neighbour + } else if diff == 0 && current.priority < neighbour.priority { + // So still in correct place, but we need to iterate on the neighbour fi.innerNext(pos + 1) return true } // At this point, the elem is in the wrong location, but the // remaining list is sorted. Find out where to move the elem - iterationNeeded := false + iteratee := -1 index := sort.Search(len(fi.iterators), func(n int) bool { - if n <= pos { + if n < pos { // No need to search 'behind' us return false } @@ -147,18 +181,29 @@ func (fi *fastAccountIterator) innerNext(pos int) bool { // Can always place an elem last return true } - neighbour := fi.iterators[n+1].Key() - diff := bytes.Compare(val[:], neighbour[:]) - if diff == 0 { - // The elem we're placing it next to has the same value, - // so it's going to need further iteration - iterationNeeded = true + neighbour := fi.iterators[n+1].it.Key() + if diff := bytes.Compare(val[:], neighbour[:]); diff < 0 { + return true + } else if diff > 0 { + return false } - return diff < 0 + // The elem we're placing it next to has the same value, + // so whichever winds up on n+1 will need further iteraton + iteratee = n + 1 + if current.priority < fi.iterators[n+1].priority { + // We can drop the iterator here + return true + } + // We need to move it one step further + return false + // TODO benchmark which is best, this works too: + //iteratee = n + //return true + // Doing so should finish the current search earlier }) fi.move(pos, index) - if iterationNeeded { - fi.innerNext(index) + if iteratee != -1 { + fi.innerNext(iteratee) } return true } @@ -171,7 +216,7 @@ func (fi *fastAccountIterator) move(index, newpos int) { var ( elem = fi.iterators[index] middle = fi.iterators[index+1 : newpos+1] - suffix []AccountIterator + suffix []*weightedIterator ) if newpos < len(fi.iterators)-1 { suffix = fi.iterators[newpos+1:] @@ -194,18 +239,18 @@ func (fi *fastAccountIterator) Error() error { // Key returns the current key func (fi *fastAccountIterator) Key() common.Hash { - return fi.iterators[0].Key() + return fi.iterators[0].it.Key() } // Value returns the current key func (fi *fastAccountIterator) Value() []byte { - panic("todo") + return fi.iterators[0].it.Value() } // Debug is a convencience helper during testing func (fi *fastAccountIterator) Debug() { for _, it := range fi.iterators { - fmt.Printf(" %v ", it.Key()[31]) + fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Key()[0]) } fmt.Println() } diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 5975231891..01e525653d 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -19,6 +19,7 @@ package snapshot import ( "bytes" "encoding/binary" + "fmt" "math/rand" "testing" @@ -95,9 +96,10 @@ func TestFastIteratorBasics(t *testing.T) { {9, 10}, {10, 13, 15, 16}}, expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, } { - var iterators []AccountIterator - for _, data := range tc.lists { - iterators = append(iterators, newTestIterator(data...)) + var iterators []*weightedIterator + for i, data := range tc.lists { + it := newTestIterator(data...) + iterators = append(iterators, &weightedIterator{it, i}) } fi := &fastAccountIterator{ @@ -162,6 +164,69 @@ func TestIteratorTraversal(t *testing.T) { verifyIterator(t, 7, child.newFastAccountIterator()) } +// TestIteratorTraversalValues tests some multi-layer iteration, where we +// also expect the correct values to show up +func TestIteratorTraversalValues(t *testing.T) { + var ( + storage = make(map[common.Hash]map[common.Hash][]byte) + a = make(map[common.Hash][]byte) + b = make(map[common.Hash][]byte) + c = make(map[common.Hash][]byte) + d = make(map[common.Hash][]byte) + e = make(map[common.Hash][]byte) + f = make(map[common.Hash][]byte) + g = make(map[common.Hash][]byte) + h = make(map[common.Hash][]byte) + ) + // entries in multiple layers should only become output once + for i := byte(2); i < 0xff; i++ { + a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) + if i > 20 && i%2 == 0 { + b[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 1, i)) + } + if i%4 == 0 { + c[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 2, i)) + } + if i%7 == 0 { + d[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 3, i)) + } + if i%8 == 0 { + e[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 4, i)) + } + if i > 50 || i < 85 { + f[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 5, i)) + } + if i%64 == 0 { + g[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 6, i)) + } + if i%128 == 0 { + h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) + } + } + child := newDiffLayer(emptyLayer(), common.Hash{}, a, storage). + Update(common.Hash{}, b, storage). + Update(common.Hash{}, c, storage). + Update(common.Hash{}, d, storage). + Update(common.Hash{}, e, storage). + Update(common.Hash{}, f, storage). + Update(common.Hash{}, g, storage). + Update(common.Hash{}, h, storage) + + it := child.newFastAccountIterator() + for it.Next() { + key := it.Key() + exp, err := child.accountRLP(key, 0) + if err != nil { + t.Fatal(err) + } + got := it.Value() + if !bytes.Equal(exp, got) { + t.Fatalf("Error on key %x, got %v exp %v", key, string(got), string(exp)) + } + //fmt.Printf("val: %v\n", string(it.Value())) + } +} + func TestIteratorLargeTraversal(t *testing.T) { // This testcase is a bit notorious -- all layers contain the exact // same 200 accounts. @@ -195,8 +260,14 @@ func TestIteratorLargeTraversal(t *testing.T) { // same 200 accounts. That means that we need to process 2000 items, but only // spit out 200 values eventually. // -//BenchmarkIteratorTraversal/binary_iterator-6 2008 573290 ns/op 9520 B/op 199 allocs/op -//BenchmarkIteratorTraversal/fast_iterator-6 1946 575596 ns/op 20146 B/op 134 allocs/op +// The value-fetching benchmark is easy on the binary iterator, since it never has to reach +// down at any depth for retrieving the values -- all are on the toppmost layer +// +// BenchmarkIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op +// BenchmarkIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op +// BenchmarkIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op +// BenchmarkIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op +// func BenchmarkIteratorTraversal(b *testing.B) { var storage = make(map[common.Hash]map[common.Hash][]byte) @@ -224,7 +295,7 @@ func BenchmarkIteratorTraversal(b *testing.B) { // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. child.newBinaryAccountIterator() - b.Run("binary iterator", func(b *testing.B) { + b.Run("binary iterator keys", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := child.newBinaryAccountIterator() @@ -236,7 +307,20 @@ func BenchmarkIteratorTraversal(b *testing.B) { } } }) - b.Run("fast iterator", func(b *testing.B) { + b.Run("binary iterator values", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryAccountIterator() + for it.Next() { + got++ + child.accountRLP(it.Key(), 0) + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + b.Run("fast iterator keys", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := child.newFastAccountIterator() @@ -248,6 +332,19 @@ func BenchmarkIteratorTraversal(b *testing.B) { } } }) + b.Run("fast iterator values", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastAccountIterator() + for it.Next() { + got++ + it.Value() + } + if exp := 200; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) } // BenchmarkIteratorLargeBaselayer is a pretty realistic benchmark, where @@ -256,8 +353,10 @@ func BenchmarkIteratorTraversal(b *testing.B) { // This is heavy on the binary iterator, which in most cases will have to // call recursively 100 times for the majority of the values // -// BenchmarkIteratorLargeBaselayer/binary_iterator-6 585 2067377 ns/op 9520 B/op 199 allocs/op -// BenchmarkIteratorLargeBaselayer/fast_iterator-6 13198 91043 ns/op 8601 B/op 118 allocs/op +// BenchmarkIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op +// BenchmarkIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op +// BenchmarkIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op +// BenchmarkIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op func BenchmarkIteratorLargeBaselayer(b *testing.B) { var storage = make(map[common.Hash]map[common.Hash][]byte) @@ -285,7 +384,7 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. child.newBinaryAccountIterator() - b.Run("binary iterator", func(b *testing.B) { + b.Run("binary iterator (keys)", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := child.newBinaryAccountIterator() @@ -297,7 +396,7 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { } } }) - b.Run("fast iterator", func(b *testing.B) { + b.Run("fast iterator (keys)", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 it := child.newFastAccountIterator() @@ -309,6 +408,34 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { } } }) + b.Run("binary iterator (values)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newBinaryAccountIterator() + for it.Next() { + got++ + v := it.Key() + child.accountRLP(v, -0) + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) + + b.Run("fast iterator (values)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + got := 0 + it := child.newFastAccountIterator() + for it.Next() { + it.Value() + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) } // TestIteratorFlatting tests what happens when we @@ -394,3 +521,34 @@ func TestIteratorSeek(t *testing.T) { it.Seek(common.HexToHash("0xff")) verifyIterator(t, 0, it) } + +//BenchmarkIteratorSeek/init+seek-6 4328 245477 ns/op +func BenchmarkIteratorSeek(b *testing.B) { + + var storage = make(map[common.Hash]map[common.Hash][]byte) + mkAccounts := func(num int) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for i := 0; i < num; i++ { + h := common.Hash{} + binary.BigEndian.PutUint64(h[:], uint64(i+1)) + accounts[h] = randomAccount() + } + return accounts + } + layer := newDiffLayer(emptyLayer(), common.Hash{}, mkAccounts(200), storage) + for i := 1; i < 100; i++ { + layer = layer.Update(common.Hash{}, + mkAccounts(200), storage) + } + b.Run("init+seek", func(b *testing.B) { + b.ResetTimer() + seekpos := make([]byte, 20) + for i := 0; i < b.N; i++ { + b.StopTimer() + rand.Read(seekpos) + it := layer.newFastAccountIterator() + b.StartTimer() + it.Seek(common.BytesToHash(seekpos)) + } + }) +} From 6ddb92a089c7b07f512a1236a69b1cd568a660b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 10 Dec 2019 11:00:03 +0200 Subject: [PATCH 015/103] core/state/snapshot: full featured account iteration --- core/state/snapshot/difflayer.go | 80 ++-- core/state/snapshot/difflayer_test.go | 14 - core/state/snapshot/disklayer.go | 5 + core/state/snapshot/iterator.go | 188 ++++++--- core/state/snapshot/iterator_binary.go | 28 +- core/state/snapshot/iterator_fast.go | 272 ++++++------ core/state/snapshot/iterator_test.go | 559 ++++++++++++++----------- core/state/snapshot/snapshot.go | 31 +- core/state/snapshot/snapshot_test.go | 55 ++- core/state/snapshot/wipe_test.go | 9 - 10 files changed, 717 insertions(+), 524 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 05d55a6fa3..855d862de2 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -229,6 +229,11 @@ func (dl *diffLayer) Root() common.Hash { return dl.root } +// Parent returns the subsequent layer of a diff layer. +func (dl *diffLayer) Parent() snapshot { + return dl.parent +} + // Stale return whether this layer has become stale (was flattened across) or if // it's still live. func (dl *diffLayer) Stale() bool { @@ -405,7 +410,7 @@ func (dl *diffLayer) flatten() snapshot { for hash, data := range dl.accountData { parent.accountData[hash] = data } - // Overwrite all the updates storage slots (individually) + // Overwrite all the updated storage slots (individually) for accountHash, storage := range dl.storageData { // If storage didn't exist (or was deleted) in the parent; or if the storage // was freshly deleted in the child, overwrite blindly @@ -425,53 +430,62 @@ func (dl *diffLayer) flatten() snapshot { parent: parent.parent, origin: parent.origin, root: dl.root, - storageList: parent.storageList, - storageData: parent.storageData, - accountList: parent.accountList, accountData: parent.accountData, + storageData: parent.storageData, + storageList: make(map[common.Hash][]common.Hash), diffed: dl.diffed, memory: parent.memory + dl.memory, } } -// AccountList returns a sorted list of all accounts in this difflayer. +// AccountList returns a sorted list of all accounts in this difflayer, including +// the deleted ones. +// +// Note, the returned slice is not a copy, so do not modify it. func (dl *diffLayer) AccountList() []common.Hash { + // If an old list already exists, return it + dl.lock.RLock() + list := dl.accountList + dl.lock.RUnlock() + + if list != nil { + return list + } + // No old sorted account list exists, generate a new one dl.lock.Lock() defer dl.lock.Unlock() - if dl.accountList != nil { - return dl.accountList + + dl.accountList = make([]common.Hash, 0, len(dl.accountData)) + for hash := range dl.accountData { + dl.accountList = append(dl.accountList, hash) } - accountList := make([]common.Hash, len(dl.accountData)) - i := 0 - for k, _ := range dl.accountData { - accountList[i] = k - i++ - // This would be a pretty good opportunity to also - // calculate the size, if we want to - } - sort.Sort(hashes(accountList)) - dl.accountList = accountList + sort.Sort(hashes(dl.accountList)) return dl.accountList } -// StorageList returns a sorted list of all storage slot hashes -// in this difflayer for the given account. +// StorageList returns a sorted list of all storage slot hashes in this difflayer +// for the given account. +// +// Note, the returned slice is not a copy, so do not modify it. func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { + // If an old list already exists, return it + dl.lock.RLock() + list := dl.storageList[accountHash] + dl.lock.RUnlock() + + if list != nil { + return list + } + // No old sorted account list exists, generate a new one dl.lock.Lock() defer dl.lock.Unlock() - if dl.storageList[accountHash] != nil { - return dl.storageList[accountHash] + + storageMap := dl.storageData[accountHash] + storageList := make([]common.Hash, 0, len(storageMap)) + for k, _ := range storageMap { + storageList = append(storageList, k) } - accountStorageMap := dl.storageData[accountHash] - accountStorageList := make([]common.Hash, len(accountStorageMap)) - i := 0 - for k, _ := range accountStorageMap { - accountStorageList[i] = k - i++ - // This would be a pretty good opportunity to also - // calculate the size, if we want to - } - sort.Sort(hashes(accountStorageList)) - dl.storageList[accountHash] = accountStorageList - return accountStorageList + sort.Sort(hashes(storageList)) + dl.storageList[accountHash] = storageList + return storageList } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 7d7b21eb05..80a9b4093f 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -18,7 +18,6 @@ package snapshot import ( "bytes" - "math/big" "math/rand" "testing" @@ -26,21 +25,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb/memorydb" - "github.com/ethereum/go-ethereum/rlp" ) -func randomAccount() []byte { - root := randomHash() - a := Account{ - Balance: big.NewInt(rand.Int63()), - Nonce: rand.Uint64(), - Root: root[:], - CodeHash: emptyCode[:], - } - data, _ := rlp.EncodeToBytes(a) - return data -} - // TestMergeBasics tests some simple merges func TestMergeBasics(t *testing.T) { var ( diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 7c5b3e3e91..0c4c3deb1b 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -48,6 +48,11 @@ func (dl *diskLayer) Root() common.Hash { return dl.root } +// Parent always returns nil as there's no layer below the disk. +func (dl *diskLayer) Parent() snapshot { + return nil +} + // Stale return whether this layer has become stale (was flattened across) or if // it's still live. func (dl *diskLayer) Stale() bool { diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 6df7b3147e..4005cb3ca1 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -18,18 +18,17 @@ package snapshot import ( "bytes" + "fmt" "sort" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" ) // AccountIterator is an iterator to step over all the accounts in a snapshot, // which may or may npt be composed of multiple layers. type AccountIterator interface { - // Seek steps the iterator forward as many elements as needed, so that after - // calling Next(), the iterator will be at a key higher than the given hash. - Seek(hash common.Hash) - // Next steps the iterator forward one element, returning false if exhausted, // or an error if iteration failed for some reason (e.g. root being iterated // becomes stale and garbage collected). @@ -39,43 +38,133 @@ type AccountIterator interface { // caused a premature iteration exit (e.g. snapshot stack becoming stale). Error() error - // Key returns the hash of the account the iterator is currently at. - Key() common.Hash + // Hash returns the hash of the account the iterator is currently at. + Hash() common.Hash - // Value returns the RLP encoded slim account the iterator is currently at. + // Account returns the RLP encoded slim account the iterator is currently at. // An error will be returned if the iterator becomes invalid (e.g. snaph - Value() []byte + Account() []byte + + // Release releases associated resources. Release should always succeed and + // can be called multiple times without causing error. + Release() } // diffAccountIterator is an account iterator that steps over the accounts (both -// live and deleted) contained within a single +// live and deleted) contained within a single diff layer. Higher order iterators +// will use the deleted accounts to skip deeper iterators. type diffAccountIterator struct { - layer *diffLayer - index int + // curHash is the current hash the iterator is positioned on. The field is + // explicitly tracked since the referenced diff layer might go stale after + // the iterator was positioned and we don't want to fail accessing the old + // hash as long as the iterator is not touched any more. + curHash common.Hash + + // curAccount is the current value the iterator is positioned on. The field + // is explicitly tracked since the referenced diff layer might go stale after + // the iterator was positioned and we don't want to fail accessing the old + // value as long as the iterator is not touched any more. + curAccount []byte + + layer *diffLayer // Live layer to retrieve values from + keys []common.Hash // Keys left in the layer to iterate + fail error // Any failures encountered (stale) } -func (dl *diffLayer) newAccountIterator() *diffAccountIterator { - dl.AccountList() - return &diffAccountIterator{layer: dl, index: -1} -} - -// Seek steps the iterator forward as many elements as needed, so that after -// calling Next(), the iterator will be at a key higher than the given hash. -func (it *diffAccountIterator) Seek(key common.Hash) { - // Search uses binary search to find and return the smallest index i - // in [0, n) at which f(i) is true - index := sort.Search(len(it.layer.accountList), func(i int) bool { - return bytes.Compare(key[:], it.layer.accountList[i][:]) < 0 +// AccountIterator creates an account iterator over a single diff layer. +func (dl *diffLayer) AccountIterator(seek common.Hash) AccountIterator { + // Seek out the requested starting account + hashes := dl.AccountList() + index := sort.Search(len(hashes), func(i int) bool { + return bytes.Compare(seek[:], hashes[i][:]) < 0 }) - it.index = index - 1 + // Assemble and returned the already seeked iterator + return &diffAccountIterator{ + layer: dl, + keys: hashes[index:], + } } // Next steps the iterator forward one element, returning false if exhausted. func (it *diffAccountIterator) Next() bool { - if it.index < len(it.layer.accountList) { - it.index++ + // If the iterator was already stale, consider it a programmer error. Although + // we could just return false here, triggering this path would probably mean + // somebody forgot to check for Error, so lets blow up instead of undefined + // behavior that's hard to debug. + if it.fail != nil { + panic(fmt.Sprintf("called Next of failed iterator: %v", it.fail)) } - return it.index < len(it.layer.accountList) + // Stop iterating if all keys were exhausted + if len(it.keys) == 0 { + return false + } + // Iterator seems to be still alive, retrieve and cache the live hash and + // account value, or fail now if layer became stale + it.layer.lock.RLock() + defer it.layer.lock.RUnlock() + + if it.layer.stale { + it.fail, it.keys = ErrSnapshotStale, nil + return false + } + it.curHash = it.keys[0] + if blob, ok := it.layer.accountData[it.curHash]; !ok { + panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) + } else { + it.curAccount = blob + } + // Values cached, shift the iterator and notify the user of success + it.keys = it.keys[1:] + return true +} + +// Error returns any failure that occurred during iteration, which might have +// caused a premature iteration exit (e.g. snapshot stack becoming stale). +func (it *diffAccountIterator) Error() error { + return it.fail +} + +// Hash returns the hash of the account the iterator is currently at. +func (it *diffAccountIterator) Hash() common.Hash { + return it.curHash +} + +// Account returns the RLP encoded slim account the iterator is currently at. +func (it *diffAccountIterator) Account() []byte { + return it.curAccount +} + +// Release is a noop for diff account iterators as there are no held resources. +func (it *diffAccountIterator) Release() {} + +// diskAccountIterator is an account iterator that steps over the live accounts +// contained within a disk layer. +type diskAccountIterator struct { + layer *diskLayer + it ethdb.Iterator +} + +// AccountIterator creates an account iterator over a disk layer. +func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { + return &diskAccountIterator{ + layer: dl, + it: dl.diskdb.NewIteratorWithPrefix(append(rawdb.SnapshotAccountPrefix, seek[:]...)), + } +} + +// Next steps the iterator forward one element, returning false if exhausted. +func (it *diskAccountIterator) Next() bool { + // If the iterator was already exhausted, don't bother + if it.it == nil { + return false + } + // Try to advance the iterator and release it if we reahed the end + if !it.it.Next() || !bytes.HasPrefix(it.it.Key(), rawdb.SnapshotAccountPrefix) { + it.it.Release() + it.it = nil + return false + } + return true } // Error returns any failure that occurred during iteration, which might have @@ -83,34 +172,25 @@ func (it *diffAccountIterator) Next() bool { // // A diff layer is immutable after creation content wise and can always be fully // iterated without error, so this method always returns nil. -func (it *diffAccountIterator) Error() error { - return nil +func (it *diskAccountIterator) Error() error { + return it.it.Error() } -// Key returns the hash of the account the iterator is currently at. -func (it *diffAccountIterator) Key() common.Hash { - if it.index < len(it.layer.accountList) { - return it.layer.accountList[it.index] +// Hash returns the hash of the account the iterator is currently at. +func (it *diskAccountIterator) Hash() common.Hash { + return common.BytesToHash(it.it.Key()) +} + +// Account returns the RLP encoded slim account the iterator is currently at. +func (it *diskAccountIterator) Account() []byte { + return it.it.Value() +} + +// Release releases the database snapshot held during iteration. +func (it *diskAccountIterator) Release() { + // The iterator is auto-released on exhaustion, so make sure it's still alive + if it.it != nil { + it.it.Release() + it.it = nil } - return common.Hash{} -} - -// Value returns the RLP encoded slim account the iterator is currently at. -func (it *diffAccountIterator) Value() []byte { - it.layer.lock.RLock() - defer it.layer.lock.RUnlock() - - hash := it.layer.accountList[it.index] - if data, ok := it.layer.accountData[hash]; ok { - return data - } - panic("iterator references non-existent layer account") -} - -func (dl *diffLayer) iterators() []AccountIterator { - if parent, ok := dl.parent.(*diffLayer); ok { - iterators := parent.iterators() - return append(iterators, dl.newAccountIterator()) - } - return []AccountIterator{dl.newAccountIterator()} } diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go index 7ff6e3337d..39288e6fb9 100644 --- a/core/state/snapshot/iterator_binary.go +++ b/core/state/snapshot/iterator_binary.go @@ -40,10 +40,10 @@ func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { parent, ok := dl.parent.(*diffLayer) if !ok { // parent is the disk layer - return dl.newAccountIterator() + return dl.AccountIterator(common.Hash{}) } l := &binaryAccountIterator{ - a: dl.newAccountIterator(), + a: dl.AccountIterator(common.Hash{}).(*diffAccountIterator), b: parent.newBinaryAccountIterator(), } l.aDone = !l.a.Next() @@ -51,12 +51,6 @@ func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { return l } -// Seek steps the iterator forward as many elements as needed, so that after -// calling Next(), the iterator will be at a key higher than the given hash. -func (it *binaryAccountIterator) Seek(key common.Hash) { - panic("todo: implement") -} - // Next steps the iterator forward one element, returning false if exhausted, // or an error if iteration failed for some reason (e.g. root being iterated // becomes stale and garbage collected). @@ -64,9 +58,9 @@ func (it *binaryAccountIterator) Next() bool { if it.aDone && it.bDone { return false } - nextB := it.b.Key() + nextB := it.b.Hash() first: - nextA := it.a.Key() + nextA := it.a.Hash() if it.aDone { it.bDone = !it.b.Next() it.k = nextB @@ -97,15 +91,15 @@ func (it *binaryAccountIterator) Error() error { return it.fail } -// Key returns the hash of the account the iterator is currently at. -func (it *binaryAccountIterator) Key() common.Hash { +// Hash returns the hash of the account the iterator is currently at. +func (it *binaryAccountIterator) Hash() common.Hash { return it.k } -// Value returns the RLP encoded slim account the iterator is currently at, or +// Account returns the RLP encoded slim account the iterator is currently at, or // nil if the iterated snapshot stack became stale (you can check Error after // to see if it failed or not). -func (it *binaryAccountIterator) Value() []byte { +func (it *binaryAccountIterator) Account() []byte { blob, err := it.a.layer.AccountRLP(it.k) if err != nil { it.fail = err @@ -113,3 +107,9 @@ func (it *binaryAccountIterator) Value() []byte { } return blob } + +// Release recursively releases all the iterators in the stack. +func (it *binaryAccountIterator) Release() { + it.a.Release() + it.b.Release() +} diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 8df037e9f4..676a3af175 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -24,90 +24,121 @@ import ( "github.com/ethereum/go-ethereum/common" ) -type weightedIterator struct { +// weightedAccountIterator is an account iterator with an assigned weight. It is +// used to prioritise which account is the correct one if multiple iterators find +// the same one (modified in multiple consecutive blocks). +type weightedAccountIterator struct { it AccountIterator priority int } +// weightedAccountIterators is a set of iterators implementing the sort.Interface. +type weightedAccountIterators []*weightedAccountIterator + +// Len implements sort.Interface, returning the number of active iterators. +func (its weightedAccountIterators) Len() int { return len(its) } + +// Less implements sort.Interface, returning which of two iterators in the stack +// is before the other. +func (its weightedAccountIterators) Less(i, j int) bool { + // Order the iterators primarilly by the account hashes + hashI := its[i].it.Hash() + hashJ := its[j].it.Hash() + + switch bytes.Compare(hashI[:], hashJ[:]) { + case -1: + return true + case 1: + return false + } + // Same account in multiple layers, split by priority + return its[i].priority < its[j].priority +} + +// Swap implements sort.Interface, swapping two entries in the iterator stack. +func (its weightedAccountIterators) Swap(i, j int) { + its[i], its[j] = its[j], its[i] +} + // fastAccountIterator is a more optimized multi-layer iterator which maintains a -// direct mapping of all iterators leading down to the bottom layer +// direct mapping of all iterators leading down to the bottom layer. type fastAccountIterator struct { - iterators []*weightedIterator + tree *Tree // Snapshot tree to reinitialize stale sub-iterators with + root common.Hash // Root hash to reinitialize stale sub-iterators through + + iterators weightedAccountIterators initiated bool fail error } -// newFastAccountIterator creates a new fastAccountIterator -func (dl *diffLayer) newFastAccountIterator() AccountIterator { - f := &fastAccountIterator{ - initiated: false, +// newFastAccountIterator creates a new hierarhical account iterator with one +// element per diff layer. The returned combo iterator can be used to walk over +// the entire snapshot diff stack simultaneously. +func newFastAccountIterator(tree *Tree, root common.Hash, seek common.Hash) (AccountIterator, error) { + snap := tree.Snapshot(root) + if snap == nil { + return nil, fmt.Errorf("unknown snapshot: %x", root) } - for i, it := range dl.iterators() { - f.iterators = append(f.iterators, &weightedIterator{it, -i}) + fi := &fastAccountIterator{ + tree: tree, + root: root, } - f.Seek(common.Hash{}) - return f + current := snap.(snapshot) + for depth := 0; current != nil; depth++ { + fi.iterators = append(fi.iterators, &weightedAccountIterator{ + it: current.AccountIterator(seek), + priority: depth, + }) + current = current.Parent() + } + fi.init() + return fi, nil } -// Len returns the number of active iterators -func (fi *fastAccountIterator) Len() int { - return len(fi.iterators) -} +// init walks over all the iterators and resolves any clashes between them, after +// which it prepares the stack for step-by-step iteration. +func (fi *fastAccountIterator) init() { + // Track which account hashes are iterators positioned on + var positioned = make(map[common.Hash]int) -// Less implements sort.Interface -func (fi *fastAccountIterator) Less(i, j int) bool { - a := fi.iterators[i].it.Key() - b := fi.iterators[j].it.Key() - bDiff := bytes.Compare(a[:], b[:]) - if bDiff < 0 { - return true - } - if bDiff > 0 { - return false - } - // keys are equal, sort by iterator priority - return fi.iterators[i].priority < fi.iterators[j].priority -} - -// Swap implements sort.Interface -func (fi *fastAccountIterator) Swap(i, j int) { - fi.iterators[i], fi.iterators[j] = fi.iterators[j], fi.iterators[i] -} - -func (fi *fastAccountIterator) Seek(key common.Hash) { - // We need to apply this across all iterators - var seen = make(map[common.Hash]int) - - length := len(fi.iterators) + // Position all iterators and track how many remain live for i := 0; i < len(fi.iterators); i++ { - //for i, it := range fi.iterators { + // Retrieve the first element and if it clashes with a previous iterator, + // advance either the current one or the old one. Repeat until nothing is + // clashing any more. it := fi.iterators[i] - it.it.Seek(key) for { + // If the iterator is exhausted, drop it off the end if !it.it.Next() { - // To be removed - // swap it to the last position for now - fi.iterators[i], fi.iterators[length-1] = fi.iterators[length-1], fi.iterators[i] - length-- + it.it.Release() + last := len(fi.iterators) - 1 + + fi.iterators[i] = fi.iterators[last] + fi.iterators[last] = nil + fi.iterators = fi.iterators[:last] + + i-- break } - v := it.it.Key() - if other, exist := seen[v]; !exist { - seen[v] = i + // The iterator is still alive, check for collisions with previous ones + hash := it.it.Hash() + if other, exist := positioned[hash]; !exist { + positioned[hash] = i break } else { + // Iterators collide, one needs to be progressed, use priority to + // determine which. + // // This whole else-block can be avoided, if we instead // do an inital priority-sort of the iterators. If we do that, // then we'll only wind up here if a lower-priority (preferred) iterator // has the same value, and then we will always just continue. // However, it costs an extra sort, so it's probably not better - - // One needs to be progressed, use priority to determine which if fi.iterators[other].priority < it.priority { - // the 'it' should be progressed + // The 'it' should be progressed continue } else { - // the 'other' should be progressed - swap them + // The 'other' should be progressed, swap them it = fi.iterators[other] fi.iterators[other], fi.iterators[i] = fi.iterators[i], fi.iterators[other] continue @@ -115,15 +146,12 @@ func (fi *fastAccountIterator) Seek(key common.Hash) { } } } - // Now remove those that were placed in the end - fi.iterators = fi.iterators[:length] - // The list is now totally unsorted, need to re-sort the entire list - sort.Sort(fi) + // Re-sort the entire list + sort.Sort(fi.iterators) fi.initiated = false } -// Next implements the Iterator interface. It returns false if no more elemnts -// can be retrieved (false == exhausted) +// Next steps the iterator forward one element, returning false if exhausted. func (fi *fastAccountIterator) Next() bool { if len(fi.iterators) == 0 { return false @@ -134,101 +162,88 @@ func (fi *fastAccountIterator) Next() bool { fi.initiated = true return true } - return fi.innerNext(0) + return fi.next(0) } -// innerNext handles the next operation internally, -// and should be invoked when we know that two elements in the list may have -// the same value. -// For example, if the list becomes [2,3,5,5,8,9,10], then we should invoke -// innerNext(3), which will call Next on elem 3 (the second '5'). It will continue -// along the list and apply the same operation if needed -func (fi *fastAccountIterator) innerNext(pos int) bool { - if !fi.iterators[pos].it.Next() { - //Exhausted, remove this iterator - fi.remove(pos) - if len(fi.iterators) == 0 { - return false - } +// next handles the next operation internally and should be invoked when we know +// that two elements in the list may have the same value. +// +// For example, if the iterated hashes become [2,3,5,5,8,9,10], then we should +// invoke next(3), which will call Next on elem 3 (the second '5') and will +// cascade along the list, applying the same operation if needed. +func (fi *fastAccountIterator) next(idx int) bool { + // If this particular iterator got exhausted, remove it and return true (the + // next one is surely not exhausted yet, otherwise it would have been removed + // already). + if it := fi.iterators[idx].it; !it.Next() { + it.Release() + + fi.iterators = append(fi.iterators[:idx], fi.iterators[idx+1:]...) + return len(fi.iterators) > 0 + } + // If there's noone left to cascade into, return + if idx == len(fi.iterators)-1 { return true } - if pos == len(fi.iterators)-1 { - // Only one iterator left - return true - } - // We next:ed the elem at 'pos'. Now we may have to re-sort that elem + // We next-ed the iterator at 'idx', now we may have to re-sort that element var ( - current, neighbour = fi.iterators[pos], fi.iterators[pos+1] - val, neighbourVal = current.it.Key(), neighbour.it.Key() + cur, next = fi.iterators[idx], fi.iterators[idx+1] + curHash, nextHash = cur.it.Hash(), next.it.Hash() ) - if diff := bytes.Compare(val[:], neighbourVal[:]); diff < 0 { + if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { // It is still in correct place return true - } else if diff == 0 && current.priority < neighbour.priority { - // So still in correct place, but we need to iterate on the neighbour - fi.innerNext(pos + 1) + } else if diff == 0 && cur.priority < next.priority { + // So still in correct place, but we need to iterate on the next + fi.next(idx + 1) return true } - // At this point, the elem is in the wrong location, but the - // remaining list is sorted. Find out where to move the elem - iteratee := -1 + // At this point, the iterator is in the wrong location, but the remaining + // list is sorted. Find out where to move the item. + clash := -1 index := sort.Search(len(fi.iterators), func(n int) bool { - if n < pos { - // No need to search 'behind' us + // The iterator always advances forward, so anything before the old slot + // is known to be behind us, so just skip them altogether. This actually + // is an important clause since the sort order got invalidated. + if n < idx { return false } if n == len(fi.iterators)-1 { // Can always place an elem last return true } - neighbour := fi.iterators[n+1].it.Key() - if diff := bytes.Compare(val[:], neighbour[:]); diff < 0 { + nextHash := fi.iterators[n+1].it.Hash() + if diff := bytes.Compare(curHash[:], nextHash[:]); diff < 0 { return true } else if diff > 0 { return false } // The elem we're placing it next to has the same value, // so whichever winds up on n+1 will need further iteraton - iteratee = n + 1 - if current.priority < fi.iterators[n+1].priority { + clash = n + 1 + if cur.priority < fi.iterators[n+1].priority { // We can drop the iterator here return true } // We need to move it one step further return false // TODO benchmark which is best, this works too: - //iteratee = n + //clash = n //return true // Doing so should finish the current search earlier }) - fi.move(pos, index) - if iteratee != -1 { - fi.innerNext(iteratee) + fi.move(idx, index) + if clash != -1 { + fi.next(clash) } return true } -// move moves an iterator to another position in the list +// move advances an iterator to another position in the list. func (fi *fastAccountIterator) move(index, newpos int) { - if newpos > len(fi.iterators)-1 { - newpos = len(fi.iterators) - 1 - } - var ( - elem = fi.iterators[index] - middle = fi.iterators[index+1 : newpos+1] - suffix []*weightedIterator - ) - if newpos < len(fi.iterators)-1 { - suffix = fi.iterators[newpos+1:] - } - fi.iterators = append(fi.iterators[:index], middle...) - fi.iterators = append(fi.iterators, elem) - fi.iterators = append(fi.iterators, suffix...) -} - -// remove drops an iterator from the list -func (fi *fastAccountIterator) remove(index int) { - fi.iterators = append(fi.iterators[:index], fi.iterators[index+1:]...) + elem := fi.iterators[index] + copy(fi.iterators[index:], fi.iterators[index+1:newpos+1]) + fi.iterators[newpos] = elem } // Error returns any failure that occurred during iteration, which might have @@ -237,20 +252,29 @@ func (fi *fastAccountIterator) Error() error { return fi.fail } -// Key returns the current key -func (fi *fastAccountIterator) Key() common.Hash { - return fi.iterators[0].it.Key() +// Hash returns the current key +func (fi *fastAccountIterator) Hash() common.Hash { + return fi.iterators[0].it.Hash() } -// Value returns the current key -func (fi *fastAccountIterator) Value() []byte { - return fi.iterators[0].it.Value() +// Account returns the current key +func (fi *fastAccountIterator) Account() []byte { + return fi.iterators[0].it.Account() +} + +// Release iterates over all the remaining live layer iterators and releases each +// of thme individually. +func (fi *fastAccountIterator) Release() { + for _, it := range fi.iterators { + it.it.Release() + } + fi.iterators = nil } // Debug is a convencience helper during testing func (fi *fastAccountIterator) Debug() { for _, it := range fi.iterators { - fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Key()[0]) + fmt.Printf("[p=%v v=%v] ", it.priority, it.it.Hash()[0]) } fmt.Println() } diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 01e525653d..902985cf6c 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -23,7 +23,9 @@ import ( "math/rand" "testing" + "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" ) // TestIteratorBasics tests some simple single-layer iteration @@ -47,7 +49,7 @@ func TestIteratorBasics(t *testing.T) { } // Add some (identical) layers on top parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) - it := parent.newAccountIterator() + it := parent.AccountIterator(common.Hash{}) verifyIterator(t, 100, it) } @@ -75,14 +77,16 @@ func (ti *testIterator) Error() error { panic("implement me") } -func (ti *testIterator) Key() common.Hash { +func (ti *testIterator) Hash() common.Hash { return common.BytesToHash([]byte{ti.values[0]}) } -func (ti *testIterator) Value() []byte { +func (ti *testIterator) Account() []byte { panic("implement me") } +func (ti *testIterator) Release() {} + func TestFastIteratorBasics(t *testing.T) { type testCase struct { lists [][]byte @@ -96,10 +100,10 @@ func TestFastIteratorBasics(t *testing.T) { {9, 10}, {10, 13, 15, 16}}, expKeys: []byte{0, 1, 2, 7, 8, 9, 10, 13, 14, 15, 16}}, } { - var iterators []*weightedIterator + var iterators []*weightedAccountIterator for i, data := range tc.lists { it := newTestIterator(data...) - iterators = append(iterators, &weightedIterator{it, i}) + iterators = append(iterators, &weightedAccountIterator{it, i}) } fi := &fastAccountIterator{ @@ -108,7 +112,7 @@ func TestFastIteratorBasics(t *testing.T) { } count := 0 for fi.Next() { - if got, exp := fi.Key()[31], tc.expKeys[count]; exp != got { + if got, exp := fi.Hash()[31], tc.expKeys[count]; exp != got { t.Errorf("tc %d, [%d]: got %d exp %d", i, count, got, exp) } count++ @@ -117,68 +121,86 @@ func TestFastIteratorBasics(t *testing.T) { } func verifyIterator(t *testing.T, expCount int, it AccountIterator) { + t.Helper() + var ( - i = 0 - last = common.Hash{} + count = 0 + last = common.Hash{} ) for it.Next() { - v := it.Key() - if bytes.Compare(last[:], v[:]) >= 0 { - t.Errorf("Wrong order:\n%x \n>=\n%x", last, v) + if hash := it.Hash(); bytes.Compare(last[:], hash[:]) >= 0 { + t.Errorf("wrong order: %x >= %x", last, hash) } - i++ + count++ } - if i != expCount { - t.Errorf("iterator len wrong, expected %d, got %d", expCount, i) + if count != expCount { + t.Errorf("iterator count mismatch: have %d, want %d", count, expCount) + } + if err := it.Error(); err != nil { + t.Errorf("iterator failed: %v", err) } } -// TestIteratorTraversal tests some simple multi-layer iteration +// TestIteratorTraversal tests some simple multi-layer iteration. func TestIteratorTraversal(t *testing.T) { - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } - // entries in multiple layers should only become output once - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + randomAccountSet("0xcc", "0xf0", "0xff"), nil) - // single layer iterator - verifyIterator(t, 3, child.newAccountIterator()) - // multi-layered binary iterator - verifyIterator(t, 7, child.newBinaryAccountIterator()) - // multi-layered fast iterator - verifyIterator(t, 7, child.newFastAccountIterator()) + // Verify the single and multi-layer iterators + head := snaps.Snapshot(common.HexToHash("0x04")) + + verifyIterator(t, 3, head.(snapshot).AccountIterator(common.Hash{})) + verifyIterator(t, 7, head.(*diffLayer).newBinaryAccountIterator()) + + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + defer it.Release() + + verifyIterator(t, 7, it) } // TestIteratorTraversalValues tests some multi-layer iteration, where we -// also expect the correct values to show up +// also expect the correct values to show up. func TestIteratorTraversalValues(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Create a batch of account sets to seed subsequent layers with var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - a = make(map[common.Hash][]byte) - b = make(map[common.Hash][]byte) - c = make(map[common.Hash][]byte) - d = make(map[common.Hash][]byte) - e = make(map[common.Hash][]byte) - f = make(map[common.Hash][]byte) - g = make(map[common.Hash][]byte) - h = make(map[common.Hash][]byte) + a = make(map[common.Hash][]byte) + b = make(map[common.Hash][]byte) + c = make(map[common.Hash][]byte) + d = make(map[common.Hash][]byte) + e = make(map[common.Hash][]byte) + f = make(map[common.Hash][]byte) + g = make(map[common.Hash][]byte) + h = make(map[common.Hash][]byte) ) - // entries in multiple layers should only become output once for i := byte(2); i < 0xff; i++ { a[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 0, i)) if i > 20 && i%2 == 0 { @@ -203,35 +225,36 @@ func TestIteratorTraversalValues(t *testing.T) { h[common.Hash{i}] = []byte(fmt.Sprintf("layer-%d, key %d", 7, i)) } } - child := newDiffLayer(emptyLayer(), common.Hash{}, a, storage). - Update(common.Hash{}, b, storage). - Update(common.Hash{}, c, storage). - Update(common.Hash{}, d, storage). - Update(common.Hash{}, e, storage). - Update(common.Hash{}, f, storage). - Update(common.Hash{}, g, storage). - Update(common.Hash{}, h, storage) + // Assemble a stack of snapshots from the account layers + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), a, nil) + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), b, nil) + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), c, nil) + snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), d, nil) + snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), e, nil) + snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), f, nil) + snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), g, nil) + snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), h, nil) - it := child.newFastAccountIterator() + it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) + defer it.Release() + + head := snaps.Snapshot(common.HexToHash("0x09")) for it.Next() { - key := it.Key() - exp, err := child.accountRLP(key, 0) + hash := it.Hash() + want, err := head.AccountRLP(hash) if err != nil { - t.Fatal(err) + t.Fatalf("failed to retrieve expected account: %v", err) } - got := it.Value() - if !bytes.Equal(exp, got) { - t.Fatalf("Error on key %x, got %v exp %v", key, string(got), string(exp)) + if have := it.Account(); !bytes.Equal(want, have) { + t.Fatalf("hash %x: account mismatch: have %x, want %x", hash, have, want) } - //fmt.Printf("val: %v\n", string(it.Value())) } } +// This testcase is notorious, all layers contain the exact same 200 accounts. func TestIteratorLargeTraversal(t *testing.T) { - // This testcase is a bit notorious -- all layers contain the exact - // same 200 accounts. - var storage = make(map[common.Hash]map[common.Hash][]byte) - mkAccounts := func(num int) map[common.Hash][]byte { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) for i := 0; i < num; i++ { h := common.Hash{} @@ -240,25 +263,121 @@ func TestIteratorLargeTraversal(t *testing.T) { } return accounts } - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts(200), storage) - child := parent.Update(common.Hash{}, - mkAccounts(200), storage) - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(200), storage) + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), } - // single layer iterator - verifyIterator(t, 200, child.newAccountIterator()) - // multi-layered binary iterator - verifyIterator(t, 200, child.newBinaryAccountIterator()) - // multi-layered fast iterator - verifyIterator(t, 200, child.newFastAccountIterator()) + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + for i := 1; i < 128; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(200), nil) + } + // Iterate the entire stack and ensure everything is hit only once + head := snaps.Snapshot(common.HexToHash("0x80")) + verifyIterator(t, 200, head.(snapshot).AccountIterator(common.Hash{})) + verifyIterator(t, 200, head.(*diffLayer).newBinaryAccountIterator()) + + it, _ := snaps.AccountIterator(common.HexToHash("0x80"), common.Hash{}) + defer it.Release() + + verifyIterator(t, 200, it) } -// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the exact -// same 200 accounts. That means that we need to process 2000 items, but only -// spit out 200 values eventually. +// TestIteratorFlattening tests what happens when we +// - have a live iterator on child C (parent C1 -> C2 .. CN) +// - flattens C2 all the way into CN +// - continues iterating +func TestIteratorFlattening(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Create a stack of diffs on top + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + randomAccountSet("0xcc", "0xf0", "0xff"), nil) + + // Create an iterator and flatten the data from underneath it + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + defer it.Release() + + if err := snaps.Cap(common.HexToHash("0x04"), 1); err != nil { + t.Fatalf("failed to flatten snapshot stack: %v", err) + } + //verifyIterator(t, 7, it) +} + +func TestIteratorSeek(t *testing.T) { + // Create a snapshot stack with some initial data + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + randomAccountSet("0xbb", "0xdd", "0xf0"), nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + randomAccountSet("0xcc", "0xf0", "0xff"), nil) + + // Construct various iterators and ensure their tranversal is correct + it, _ := snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xdd")) + defer it.Release() + verifyIterator(t, 3, it) // expected: ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xaa")) + defer it.Release() + verifyIterator(t, 3, it) // expected: ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x02"), common.HexToHash("0xff")) + defer it.Release() + verifyIterator(t, 0, it) // expected: nothing + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xbb")) + defer it.Release() + verifyIterator(t, 5, it) // expected: cc, dd, ee, f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xef")) + defer it.Release() + verifyIterator(t, 2, it) // expected: f0, ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xf0")) + defer it.Release() + verifyIterator(t, 1, it) // expected: ff + + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.HexToHash("0xff")) + defer it.Release() + verifyIterator(t, 0, it) // expected: nothing +} + +// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the +// exact same 200 accounts. That means that we need to process 2000 items, but +// only spit out 200 values eventually. // // The value-fetching benchmark is easy on the binary iterator, since it never has to reach // down at any depth for retrieving the values -- all are on the toppmost layer @@ -267,12 +386,9 @@ func TestIteratorLargeTraversal(t *testing.T) { // BenchmarkIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op // BenchmarkIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op // BenchmarkIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op -// func BenchmarkIteratorTraversal(b *testing.B) { - - var storage = make(map[common.Hash]map[common.Hash][]byte) - - mkAccounts := func(num int) map[common.Hash][]byte { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) for i := 0; i < num; i++ { h := common.Hash{} @@ -281,24 +397,29 @@ func BenchmarkIteratorTraversal(b *testing.B) { } return accounts } - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts(200), storage) - - child := parent.Update(common.Hash{}, - mkAccounts(200), storage) - - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(200), storage) - + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + for i := 1; i <= 100; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(200), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. - child.newBinaryAccountIterator() + head := snaps.Snapshot(common.HexToHash("0x65")) + head.(*diffLayer).newBinaryAccountIterator() + b.Run("binary iterator keys", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 - it := child.newBinaryAccountIterator() + it := head.(*diffLayer).newBinaryAccountIterator() for it.Next() { got++ } @@ -310,10 +431,10 @@ func BenchmarkIteratorTraversal(b *testing.B) { b.Run("binary iterator values", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 - it := child.newBinaryAccountIterator() + it := head.(*diffLayer).newBinaryAccountIterator() for it.Next() { got++ - child.accountRLP(it.Key(), 0) + head.(*diffLayer).accountRLP(it.Hash(), 0) } if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) @@ -322,8 +443,10 @@ func BenchmarkIteratorTraversal(b *testing.B) { }) b.Run("fast iterator keys", func(b *testing.B) { for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + got := 0 - it := child.newFastAccountIterator() for it.Next() { got++ } @@ -334,11 +457,13 @@ func BenchmarkIteratorTraversal(b *testing.B) { }) b.Run("fast iterator values", func(b *testing.B) { for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + got := 0 - it := child.newFastAccountIterator() for it.Next() { got++ - it.Value() + it.Account() } if exp := 200; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) @@ -354,13 +479,12 @@ func BenchmarkIteratorTraversal(b *testing.B) { // call recursively 100 times for the majority of the values // // BenchmarkIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op -// BenchmarkIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op // BenchmarkIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op +// BenchmarkIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op // BenchmarkIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op func BenchmarkIteratorLargeBaselayer(b *testing.B) { - var storage = make(map[common.Hash]map[common.Hash][]byte) - - mkAccounts := func(num int) map[common.Hash][]byte { + // Create a custom account factory to recreate the same addresses + makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) for i := 0; i < num; i++ { h := common.Hash{} @@ -369,37 +493,30 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { } return accounts } - - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts(2000), storage) - - child := parent.Update(common.Hash{}, - mkAccounts(20), storage) - - for i := 2; i < 100; i++ { - child = child.Update(common.Hash{}, - mkAccounts(20), storage) - + // Build up a large stack of snapshots + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), makeAccounts(2000), nil) + for i := 2; i <= 100; i++ { + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(20), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. - child.newBinaryAccountIterator() + head := snaps.Snapshot(common.HexToHash("0x65")) + head.(*diffLayer).newBinaryAccountIterator() + b.Run("binary iterator (keys)", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 - it := child.newBinaryAccountIterator() - for it.Next() { - got++ - } - if exp := 2000; got != exp { - b.Errorf("iterator len wrong, expected %d, got %d", exp, got) - } - } - }) - b.Run("fast iterator (keys)", func(b *testing.B) { - for i := 0; i < b.N; i++ { - got := 0 - it := child.newFastAccountIterator() + it := head.(*diffLayer).newBinaryAccountIterator() for it.Next() { got++ } @@ -411,24 +528,39 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { b.Run("binary iterator (values)", func(b *testing.B) { for i := 0; i < b.N; i++ { got := 0 - it := child.newBinaryAccountIterator() + it := head.(*diffLayer).newBinaryAccountIterator() for it.Next() { got++ - v := it.Key() - child.accountRLP(v, -0) + v := it.Hash() + head.(*diffLayer).accountRLP(v, 0) } if exp := 2000; got != exp { b.Errorf("iterator len wrong, expected %d, got %d", exp, got) } } }) + b.Run("fast iterator (keys)", func(b *testing.B) { + for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + got := 0 + for it.Next() { + got++ + } + if exp := 2000; got != exp { + b.Errorf("iterator len wrong, expected %d, got %d", exp, got) + } + } + }) b.Run("fast iterator (values)", func(b *testing.B) { for i := 0; i < b.N; i++ { + it, _ := snaps.AccountIterator(common.HexToHash("0x65"), common.Hash{}) + defer it.Release() + got := 0 - it := child.newFastAccountIterator() for it.Next() { - it.Value() + it.Account() got++ } if exp := 2000; got != exp { @@ -438,117 +570,38 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { }) } -// TestIteratorFlatting tests what happens when we -// - have a live iterator on child C (parent C1 -> C2 .. CN) -// - flattens C2 all the way into CN -// - continues iterating -// Right now, this "works" simply because the keys do not change -- the -// iterator is not aware that a layer has become stale. This naive -// solution probably won't work in the long run, however -func TestIteratorFlattning(t *testing.T) { - var ( - storage = make(map[common.Hash]map[common.Hash][]byte) - ) - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts - } - // entries in multiple layers should only become output once - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) - - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) - - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) - - it := child.newFastAccountIterator() - child.parent.(*diffLayer).flatten() - // The parent should now be stale - verifyIterator(t, 7, it) -} - -func TestIteratorSeek(t *testing.T) { - storage := make(map[common.Hash]map[common.Hash][]byte) - mkAccounts := func(args ...string) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for _, h := range args { - accounts[common.HexToHash(h)] = randomAccount() - } - return accounts - } - parent := newDiffLayer(emptyLayer(), common.Hash{}, - mkAccounts("0xaa", "0xee", "0xff", "0xf0"), storage) - it := AccountIterator(parent.newAccountIterator()) - // expected: ee, f0, ff - it.Seek(common.HexToHash("0xdd")) - verifyIterator(t, 3, it) - - it = parent.newAccountIterator() - // expected: ee, f0, ff - it.Seek(common.HexToHash("0xaa")) - verifyIterator(t, 3, it) - - it = parent.newAccountIterator() - // expected: nothing - it.Seek(common.HexToHash("0xff")) - verifyIterator(t, 0, it) - - child := parent.Update(common.Hash{}, - mkAccounts("0xbb", "0xdd", "0xf0"), storage) - - child = child.Update(common.Hash{}, - mkAccounts("0xcc", "0xf0", "0xff"), storage) - - it = child.newFastAccountIterator() - // expected: cc, dd, ee, f0, ff - it.Seek(common.HexToHash("0xbb")) - verifyIterator(t, 5, it) - - it = child.newFastAccountIterator() - it.Seek(common.HexToHash("0xef")) - // exp: f0, ff - verifyIterator(t, 2, it) - - it = child.newFastAccountIterator() - it.Seek(common.HexToHash("0xf0")) - verifyIterator(t, 1, it) - - it.Seek(common.HexToHash("0xff")) - verifyIterator(t, 0, it) -} - -//BenchmarkIteratorSeek/init+seek-6 4328 245477 ns/op -func BenchmarkIteratorSeek(b *testing.B) { - - var storage = make(map[common.Hash]map[common.Hash][]byte) - mkAccounts := func(num int) map[common.Hash][]byte { - accounts := make(map[common.Hash][]byte) - for i := 0; i < num; i++ { - h := common.Hash{} - binary.BigEndian.PutUint64(h[:], uint64(i+1)) - accounts[h] = randomAccount() - } - return accounts - } - layer := newDiffLayer(emptyLayer(), common.Hash{}, mkAccounts(200), storage) - for i := 1; i < 100; i++ { - layer = layer.Update(common.Hash{}, - mkAccounts(200), storage) - } - b.Run("init+seek", func(b *testing.B) { - b.ResetTimer() - seekpos := make([]byte, 20) - for i := 0; i < b.N; i++ { - b.StopTimer() - rand.Read(seekpos) - it := layer.newFastAccountIterator() - b.StartTimer() - it.Seek(common.BytesToHash(seekpos)) - } +/* +func BenchmarkBinaryAccountIteration(b *testing.B) { + benchmarkAccountIteration(b, func(snap snapshot) AccountIterator { + return snap.(*diffLayer).newBinaryAccountIterator() }) } + +func BenchmarkFastAccountIteration(b *testing.B) { + benchmarkAccountIteration(b, newFastAccountIterator) +} + +func benchmarkAccountIteration(b *testing.B, iterator func(snap snapshot) AccountIterator) { + // Create a diff stack and randomize the accounts across them + layers := make([]map[common.Hash][]byte, 128) + for i := 0; i < len(layers); i++ { + layers[i] = make(map[common.Hash][]byte) + } + for i := 0; i < b.N; i++ { + depth := rand.Intn(len(layers)) + layers[depth][randomHash()] = randomAccount() + } + stack := snapshot(emptyLayer()) + for _, layer := range layers { + stack = stack.Update(common.Hash{}, layer, nil) + } + // Reset the timers and report all the stats + it := iterator(stack) + + b.ResetTimer() + b.ReportAllocs() + + for it.Next() { + } +} +*/ diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 7650cf2c13..5f9a8be637 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -113,9 +113,17 @@ type Snapshot interface { type snapshot interface { Snapshot + // Parent returns the subsequent layer of a snapshot, or nil if the base was + // reached. + // + // Note, the method is an internal helper to avoid type switching between the + // disk and diff layers. There is no locking involved. + Parent() snapshot + // Update creates a new layer on top of the existing snapshot diff tree with - // the specified data items. Note, the maps are retained by the method to avoid - // copying everything. + // the specified data items. + // + // Note, the maps are retained by the method to avoid copying everything. Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer // Journal commits an entire diff hierarchy to disk into a single journal entry. @@ -126,6 +134,9 @@ type snapshot interface { // Stale return whether this layer has become stale (was flattened across) or // if it's still live. Stale() bool + + // AccountIterator creates an account iterator over an arbitrary layer. + AccountIterator(seek common.Hash) AccountIterator } // SnapshotTree is an Ethereum state snapshot tree. It consists of one persistent @@ -170,15 +181,7 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm // Existing snapshot loaded, seed all the layers for head != nil { snap.layers[head.Root()] = head - - switch self := head.(type) { - case *diffLayer: - head = self.parent - case *diskLayer: - head = nil - default: - panic(fmt.Sprintf("unknown data layer: %T", self)) - } + head = head.Parent() } return snap } @@ -563,3 +566,9 @@ func (t *Tree) Rebuild(root common.Hash) { root: generateSnapshot(t.diskdb, t.triedb, t.cache, root, wiper), } } + +// AccountIterator creates a new account iterator for the specified root hash and +// seeks to a starting account hash. +func (t *Tree) AccountIterator(root common.Hash, seek common.Hash) (AccountIterator, error) { + return newFastAccountIterator(t, root, seek) +} diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 44b8f3cefd..2b14828178 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -18,13 +18,48 @@ package snapshot import ( "fmt" + "math/big" + "math/rand" "testing" "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/rlp" ) +// randomHash generates a random blob of data and returns it as a hash. +func randomHash() common.Hash { + var hash common.Hash + if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { + panic(err) + } + return hash +} + +// randomAccount generates a random account and returns it RLP encoded. +func randomAccount() []byte { + root := randomHash() + a := Account{ + Balance: big.NewInt(rand.Int63()), + Nonce: rand.Uint64(), + Root: root[:], + CodeHash: emptyCode[:], + } + data, _ := rlp.EncodeToBytes(a) + return data +} + +// randomAccountSet generates a set of random accounts with the given strings as +// the account address hashes. +func randomAccountSet(hashes ...string) map[common.Hash][]byte { + accounts := make(map[common.Hash][]byte) + for _, hash := range hashes { + accounts[common.HexToHash(hash)] = randomAccount() + } + return accounts +} + // Tests that if a disk layer becomes stale, no active external references will // be returned with junk data. This version of the test flattens every diff layer // to check internal corner case around the bottom-most memory accumulator. @@ -46,8 +81,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - storage := make(map[common.Hash]map[common.Hash][]byte) - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 2 { @@ -91,11 +125,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - storage := make(map[common.Hash]map[common.Hash][]byte) - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 3 { @@ -140,11 +173,10 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - storage := make(map[common.Hash]map[common.Hash][]byte) - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 3 { @@ -188,14 +220,13 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - storage := make(map[common.Hash]map[common.Hash][]byte) - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), accounts, storage); err != nil { + if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 4 { diff --git a/core/state/snapshot/wipe_test.go b/core/state/snapshot/wipe_test.go index f12769a950..cb6e174b31 100644 --- a/core/state/snapshot/wipe_test.go +++ b/core/state/snapshot/wipe_test.go @@ -25,15 +25,6 @@ import ( "github.com/ethereum/go-ethereum/ethdb/memorydb" ) -// randomHash generates a random blob of data and returns it as a hash. -func randomHash() common.Hash { - var hash common.Hash - if n, err := rand.Read(hash[:]); n != common.HashLength || err != nil { - panic(err) - } - return hash -} - // Tests that given a database with random data content, all parts of a snapshot // can be crrectly wiped without touching anything else. func TestWipe(t *testing.T) { From 19099421dc9d1c0818002fa7de948e056e1eee61 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Sun, 19 Jan 2020 20:57:56 +0100 Subject: [PATCH 016/103] core/state/snapshot: faster account iteration, CLI integration --- cmd/geth/chaincmd.go | 1 + cmd/geth/main.go | 1 + cmd/utils/flags.go | 10 +++++++++ core/blockchain.go | 19 ++++++++++------ core/state/snapshot/difflayer.go | 16 +++++--------- core/state/snapshot/iterator.go | 33 ++++++++++++++++------------ core/state/snapshot/iterator_fast.go | 22 ++++++++++++++----- core/state/snapshot/journal.go | 2 +- core/state/snapshot/snapshot.go | 3 ++- 9 files changed, 69 insertions(+), 38 deletions(-) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 9d4835a16c..c5ae550e34 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -79,6 +79,7 @@ The dumpgenesis command dumps the genesis block configuration in JSON format to utils.CacheFlag, utils.SyncModeFlag, utils.GCModeFlag, + utils.SnapshotFlag, utils.CacheDatabaseFlag, utils.CacheGCFlag, }, diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 36187e484d..3615a91660 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -91,6 +91,7 @@ var ( utils.SyncModeFlag, utils.ExitWhenSyncedFlag, utils.GCModeFlag, + utils.SnapshotFlag, utils.LightServeFlag, utils.LightLegacyServFlag, utils.LightIngressFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 22fe677fa2..cbbe530701 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -225,6 +225,10 @@ var ( Usage: `Blockchain garbage collection mode ("full", "archive")`, Value: "full", } + SnapshotFlag = cli.BoolFlag{ + Name: "snapshot", + Usage: `Enables snapshot-database mode -- experimental work in progress feature`, + } LightKDFFlag = cli.BoolFlag{ Name: "lightkdf", Usage: "Reduce key-derivation RAM & CPU usage at some expense of KDF strength", @@ -1471,6 +1475,9 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheSnapshotFlag.Name) { cfg.SnapshotCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheSnapshotFlag.Name) / 100 } + if !ctx.GlobalIsSet(SnapshotFlag.Name) { + cfg.SnapshotCache = 0 // Disabled + } if ctx.GlobalIsSet(DocRootFlag.Name) { cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name) } @@ -1734,6 +1741,9 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai TrieTimeLimit: eth.DefaultConfig.TrieTimeout, SnapshotLimit: eth.DefaultConfig.SnapshotCache, } + if !ctx.GlobalIsSet(SnapshotFlag.Name) { + cache.SnapshotLimit = 0 // Disabled + } if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheTrieFlag.Name) { cache.TrieCleanLimit = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheTrieFlag.Name) / 100 } diff --git a/core/blockchain.go b/core/blockchain.go index f868f7301f..491eccecd4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -302,8 +302,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } } // Load any existing snapshot, regenerating it if loading failed - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) - + if bc.cacheConfig.SnapshotLimit > 0 { + bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) + } // Take ownership of this particular state go bc.update() return bc, nil @@ -498,8 +499,9 @@ func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error { bc.chainmu.Unlock() // Destroy any existing state snapshot and regenerate it in the background - bc.snaps.Rebuild(block.Root()) - + if bc.snaps != nil { + bc.snaps.Rebuild(block.Root()) + } log.Info("Committed new head block", "number", block.Number(), "hash", hash) return nil } @@ -854,9 +856,12 @@ func (bc *BlockChain) Stop() { bc.wg.Wait() // Ensure that the entirety of the state snapshot is journalled to disk. - snapBase, err := bc.snaps.Journal(bc.CurrentBlock().Root()) - if err != nil { - log.Error("Failed to journal state snapshot", "err", err) + var snapBase common.Hash + if bc.snaps != nil { + var err error + if snapBase, err = bc.snaps.Journal(bc.CurrentBlock().Root()); err != nil { + log.Error("Failed to journal state snapshot", "err", err) + } } // Ensure the state of a recent block is also stored to disk before exiting. // We're writing three different states to catch different restart scenarios: diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 855d862de2..3528a04a28 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -23,6 +23,7 @@ import ( "math/rand" "sort" "sync" + "sync/atomic" "time" "github.com/ethereum/go-ethereum/common" @@ -92,7 +93,7 @@ type diffLayer struct { memory uint64 // Approximate guess as to how much memory we use root common.Hash // Root hash to which this snapshot diff belongs to - stale bool // Signals that the layer became stale (state progressed) + stale uint32 // Signals that the layer became stale (state progressed) accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) @@ -237,10 +238,7 @@ func (dl *diffLayer) Parent() snapshot { // Stale return whether this layer has become stale (was flattened across) or if // it's still live. func (dl *diffLayer) Stale() bool { - dl.lock.RLock() - defer dl.lock.RUnlock() - - return dl.stale + return atomic.LoadUint32(&dl.stale) != 0 } // Account directly retrieves the account associated with a particular hash in @@ -288,7 +286,7 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { // If the layer was flattened into, consider it invalid (any live reference to // the original should be marked as unusable). - if dl.stale { + if dl.Stale() { return nil, ErrSnapshotStale } // If the account is known locally, return it. Note, a nil account means it was @@ -342,7 +340,7 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ // If the layer was flattened into, consider it invalid (any live reference to // the original should be marked as unusable). - if dl.stale { + if dl.Stale() { return nil, ErrSnapshotStale } // If the account is known locally, try to resolve the slot locally. Note, a nil @@ -401,11 +399,9 @@ func (dl *diffLayer) flatten() snapshot { // Before actually writing all our data to the parent, first ensure that the // parent hasn't been 'corrupted' by someone else already flattening into it - if parent.stale { + if atomic.SwapUint32(&parent.stale, 1) != 0 { panic("parent diff layer is stale") // we've flattened into the same parent from two children, boo } - parent.stale = true - // Overwrite all the updated accounts blindly, merge the sorted list for hash, data := range dl.accountData { parent.accountData[hash] = data diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 4005cb3ca1..774e9f5542 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -64,7 +64,7 @@ type diffAccountIterator struct { // is explicitly tracked since the referenced diff layer might go stale after // the iterator was positioned and we don't want to fail accessing the old // value as long as the iterator is not touched any more. - curAccount []byte + //curAccount []byte layer *diffLayer // Live layer to retrieve values from keys []common.Hash // Keys left in the layer to iterate @@ -98,22 +98,13 @@ func (it *diffAccountIterator) Next() bool { if len(it.keys) == 0 { return false } - // Iterator seems to be still alive, retrieve and cache the live hash and - // account value, or fail now if layer became stale - it.layer.lock.RLock() - defer it.layer.lock.RUnlock() - - if it.layer.stale { + if it.layer.Stale() { it.fail, it.keys = ErrSnapshotStale, nil return false } + // Iterator seems to be still alive, retrieve and cache the live hash it.curHash = it.keys[0] - if blob, ok := it.layer.accountData[it.curHash]; !ok { - panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) - } else { - it.curAccount = blob - } - // Values cached, shift the iterator and notify the user of success + // key cached, shift the iterator and notify the user of success it.keys = it.keys[1:] return true } @@ -130,8 +121,22 @@ func (it *diffAccountIterator) Hash() common.Hash { } // Account returns the RLP encoded slim account the iterator is currently at. +// This method may _fail_, if the underlying layer has been flattened between +// the call to Next and Acccount. That type of error will set it.Err. +// This method assumes that flattening does not delete elements from +// the accountdata mapping (writing nil into it is fine though), and will panic +// if elements have been deleted. func (it *diffAccountIterator) Account() []byte { - return it.curAccount + it.layer.lock.RLock() + blob, ok := it.layer.accountData[it.curHash] + if !ok { + panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) + } + it.layer.lock.RUnlock() + if it.layer.Stale() { + it.fail, it.keys = ErrSnapshotStale, nil + } + return blob } // Release is a noop for diff account iterators as there are no held resources. diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index 676a3af175..b5ffab7c88 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -63,8 +63,9 @@ func (its weightedAccountIterators) Swap(i, j int) { // fastAccountIterator is a more optimized multi-layer iterator which maintains a // direct mapping of all iterators leading down to the bottom layer. type fastAccountIterator struct { - tree *Tree // Snapshot tree to reinitialize stale sub-iterators with - root common.Hash // Root hash to reinitialize stale sub-iterators through + tree *Tree // Snapshot tree to reinitialize stale sub-iterators with + root common.Hash // Root hash to reinitialize stale sub-iterators through + curAccount []byte iterators weightedAccountIterators initiated bool @@ -160,9 +161,20 @@ func (fi *fastAccountIterator) Next() bool { // Don't forward first time -- we had to 'Next' once in order to // do the sorting already fi.initiated = true - return true + fi.curAccount = fi.iterators[0].it.Account() + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { + fi.fail = innerErr + } + return fi.Error() == nil } - return fi.next(0) + if !fi.next(0) { + return false + } + fi.curAccount = fi.iterators[0].it.Account() + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { + fi.fail = innerErr + } + return fi.Error() == nil } // next handles the next operation internally and should be invoked when we know @@ -259,7 +271,7 @@ func (fi *fastAccountIterator) Hash() common.Hash { // Account returns the current key func (fi *fastAccountIterator) Account() []byte { - return fi.iterators[0].it.Account() + return fi.curAccount } // Release iterates over all the remaining live layer iterators and releases each diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 1c36e06233..01b88bae6c 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -210,7 +210,7 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { dl.lock.RLock() defer dl.lock.RUnlock() - if dl.stale { + if dl.Stale() { return common.Hash{}, ErrSnapshotStale } // Everything below was journalled, persist this layer too diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index 5f9a8be637..ad602bbbbc 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -22,6 +22,7 @@ import ( "errors" "fmt" "sync" + "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" @@ -552,7 +553,7 @@ func (t *Tree) Rebuild(root common.Hash) { case *diffLayer: // If the layer is a simple diff, simply mark as stale layer.lock.Lock() - layer.stale = true + atomic.StoreUint32(&layer.stale, 1) layer.lock.Unlock() default: From 06d4470b4146a4e2ec813a01cc101aeaff6ce1eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 24 Feb 2020 13:26:34 +0200 Subject: [PATCH 017/103] core: fix broken tests due to API changes + linter --- core/blockchain_test.go | 2 +- core/state/iterator_test.go | 2 +- core/state/snapshot/difflayer.go | 2 +- core/state/snapshot/difflayer_test.go | 2 +- core/state/snapshot/disklayer_test.go | 8 ++-- core/state/snapshot/generate.go | 2 +- core/state/snapshot/iteration.md | 60 -------------------------- core/state/snapshot/iterator_binary.go | 2 +- core/state/snapshot/iterator_fast.go | 16 ++----- core/state/snapshot/iterator_test.go | 9 ++-- core/state/snapshot/journal.go | 2 +- core/state/snapshot/sort.go | 56 ------------------------ core/state/state_test.go | 4 +- core/state/statedb_test.go | 20 ++++----- core/state/sync_test.go | 6 +-- core/tx_pool_test.go | 38 ++++++++-------- core/vm/gas_table_test.go | 2 +- les/odr_test.go | 4 +- 18 files changed, 55 insertions(+), 182 deletions(-) delete mode 100644 core/state/snapshot/iteration.md diff --git a/core/blockchain_test.go b/core/blockchain_test.go index de23ead210..4bed1b4518 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -144,7 +144,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { } return err } - statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache) + statedb, err := state.New(blockchain.GetBlockByHash(block.ParentHash()).Root(), blockchain.stateCache, nil) if err != nil { return err } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index 69f51c4c7d..e9946e9b31 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -29,7 +29,7 @@ func TestNodeIteratorCoverage(t *testing.T) { // Create some arbitrary test state to iterate db, root, _ := makeTestState() - state, err := New(root, db) + state, err := New(root, db, nil) if err != nil { t.Fatalf("failed to create state trie at %x: %v", root, err) } diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 3528a04a28..3c1bea421e 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -478,7 +478,7 @@ func (dl *diffLayer) StorageList(accountHash common.Hash) []common.Hash { storageMap := dl.storageData[accountHash] storageList := make([]common.Hash, 0, len(storageMap)) - for k, _ := range storageMap { + for k := range storageMap { storageList = append(storageList, k) } sort.Sort(hashes(storageList)) diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 80a9b4093f..d8212d317f 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -167,7 +167,7 @@ func TestInsertAndMerge(t *testing.T) { merged := (child.flatten()).(*diffLayer) { // Check that slot value is present got, _ := merged.Storage(acc, slot) - if exp := []byte{0x01}; bytes.Compare(got, exp) != 0 { + if exp := []byte{0x01}; !bytes.Equal(got, exp) { t.Errorf("merged slot value wrong, got %x, exp %x", got, exp) } } diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index 30b6904546..b8dded0d80 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -310,7 +310,7 @@ func TestDiskPartialMerge(t *testing.T) { t.Helper() blob, err := base.AccountRLP(account) if bytes.Compare(account[:], genMarker) > 0 && err != ErrNotCoveredYet { - t.Fatalf("test %d: post-marker (%x) account access (%x) succeded: %x", i, genMarker, account, blob) + t.Fatalf("test %d: post-marker (%x) account access (%x) succeeded: %x", i, genMarker, account, blob) } if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) account access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) @@ -326,7 +326,7 @@ func TestDiskPartialMerge(t *testing.T) { t.Helper() blob, err := base.Storage(account, slot) if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && err != ErrNotCoveredYet { - t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeded: %x", i, genMarker, account, slot, blob) + t.Fatalf("test %d: post-marker (%x) storage access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) } if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) storage access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) @@ -386,7 +386,7 @@ func TestDiskPartialMerge(t *testing.T) { t.Helper() blob := rawdb.ReadAccountSnapshot(db, account) if bytes.Compare(account[:], genMarker) > 0 && blob != nil { - t.Fatalf("test %d: post-marker (%x) account database access (%x) succeded: %x", i, genMarker, account, blob) + t.Fatalf("test %d: post-marker (%x) account database access (%x) succeeded: %x", i, genMarker, account, blob) } if bytes.Compare(account[:], genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) account database access (%x) mismatch: have %x, want %x", i, genMarker, account, blob, data) @@ -406,7 +406,7 @@ func TestDiskPartialMerge(t *testing.T) { t.Helper() blob := rawdb.ReadStorageSnapshot(db, account, slot) if bytes.Compare(append(account[:], slot[:]...), genMarker) > 0 && blob != nil { - t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeded: %x", i, genMarker, account, slot, blob) + t.Fatalf("test %d: post-marker (%x) storage database access (%x:%x) succeeded: %x", i, genMarker, account, slot, blob) } if bytes.Compare(append(account[:], slot[:]...), genMarker) <= 0 && !bytes.Equal(blob, data) { t.Fatalf("test %d: pre-marker (%x) storage database access (%x:%x) mismatch: have %x, want %x", i, genMarker, account, slot, blob, data) diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 0f9e5fae59..8a407e30d2 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -93,7 +93,7 @@ func (gs *generatorStats) Log(msg string, marker []byte) { // and generation is continued in the background until done. func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, wiper chan struct{}) *diskLayer { // Wipe any previously existing snapshot from the database if no wiper is - // currenty in progress. + // currently in progress. if wiper == nil { wiper = wipeSnapshot(diskdb, true) } diff --git a/core/state/snapshot/iteration.md b/core/state/snapshot/iteration.md deleted file mode 100644 index ca1962d42b..0000000000 --- a/core/state/snapshot/iteration.md +++ /dev/null @@ -1,60 +0,0 @@ - -## How the fast iterator works - -Consider the following example, where we have `6` iterators, sorted from -left to right in ascending order. - -Our 'primary' `A` iterator is on the left, containing the elements `[0,1,8]` -``` - A B C D E F - - 0 1 2 4 7 9 - 1 2 9 - 14 13 - 8 8 - 15 15 - - - - 16 - - -``` -When we call `Next` on the primary iterator, we get (ignoring the future keys) - -``` -A B C D E F - -1 1 2 4 7 9 -``` -We detect that we now got an equality between our element and the next element. -And we need to continue `Next`ing on the next element - -``` -1 2 2 4 7 9 -``` -And move on: -``` -A B C D E F - -1 2 9 4 7 9 -``` -Now we broke out of the equality, but we need to re-sort the element `C` - -``` -A B D E F C - -1 2 4 7 9 9 -``` - -And after shifting it rightwards, we check equality again, and find `C == F`, and thus -call `Next` on `C` - -``` -A B D E F C - -1 2 4 7 9 - -``` -At this point, `C` was exhausted, and is removed - -``` -A B D E F - -1 2 4 7 9 -``` -And we're done with this step. - diff --git a/core/state/snapshot/iterator_binary.go b/core/state/snapshot/iterator_binary.go index 39288e6fb9..7d647ee7ba 100644 --- a/core/state/snapshot/iterator_binary.go +++ b/core/state/snapshot/iterator_binary.go @@ -35,7 +35,7 @@ type binaryAccountIterator struct { } // newBinaryAccountIterator creates a simplistic account iterator to step over -// all the accounts in a slow, but eaily verifyable way. +// all the accounts in a slow, but eaily verifiable way. func (dl *diffLayer) newBinaryAccountIterator() AccountIterator { parent, ok := dl.parent.(*diffLayer) if !ok { diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index b5ffab7c88..ef0212ac29 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -41,7 +41,7 @@ func (its weightedAccountIterators) Len() int { return len(its) } // Less implements sort.Interface, returning which of two iterators in the stack // is before the other. func (its weightedAccountIterators) Less(i, j int) bool { - // Order the iterators primarilly by the account hashes + // Order the iterators primarily by the account hashes hashI := its[i].it.Hash() hashJ := its[j].it.Hash() @@ -131,7 +131,7 @@ func (fi *fastAccountIterator) init() { // determine which. // // This whole else-block can be avoided, if we instead - // do an inital priority-sort of the iterators. If we do that, + // do an initial priority-sort of the iterators. If we do that, // then we'll only wind up here if a lower-priority (preferred) iterator // has the same value, and then we will always just continue. // However, it costs an extra sort, so it's probably not better @@ -233,16 +233,8 @@ func (fi *fastAccountIterator) next(idx int) bool { // The elem we're placing it next to has the same value, // so whichever winds up on n+1 will need further iteraton clash = n + 1 - if cur.priority < fi.iterators[n+1].priority { - // We can drop the iterator here - return true - } - // We need to move it one step further - return false - // TODO benchmark which is best, this works too: - //clash = n - //return true - // Doing so should finish the current search earlier + + return cur.priority < fi.iterators[n+1].priority }) fi.move(idx, index) if clash != -1 { diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 902985cf6c..dbfafd73df 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -67,14 +67,11 @@ func (ti *testIterator) Seek(common.Hash) { func (ti *testIterator) Next() bool { ti.values = ti.values[1:] - if len(ti.values) == 0 { - return false - } - return true + return len(ti.values) > 0 } func (ti *testIterator) Error() error { - panic("implement me") + return nil } func (ti *testIterator) Hash() common.Hash { @@ -82,7 +79,7 @@ func (ti *testIterator) Hash() common.Hash { } func (ti *testIterator) Account() []byte { - panic("implement me") + return nil } func (ti *testIterator) Release() {} diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 01b88bae6c..8e039606f1 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -164,7 +164,7 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { // Journal writes the persistent layer generator stats into a buffer to be stored // in the database as the snapshot journal. func (dl *diskLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { - // If the snapshot is currenty being generated, abort it + // If the snapshot is currently being generated, abort it var stats *generatorStats if dl.genAbort != nil { abort := make(chan *generatorStats) diff --git a/core/state/snapshot/sort.go b/core/state/snapshot/sort.go index ee7cc4990e..88841231d9 100644 --- a/core/state/snapshot/sort.go +++ b/core/state/snapshot/sort.go @@ -34,59 +34,3 @@ func (hs hashes) Less(i, j int) bool { return bytes.Compare(hs[i][:], hs[j][:]) // Swap swaps the elements with indexes i and j. func (hs hashes) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] } - -// merge combines two sorted lists of hashes into a combo sorted one. -func merge(a, b []common.Hash) []common.Hash { - result := make([]common.Hash, len(a)+len(b)) - - i := 0 - for len(a) > 0 && len(b) > 0 { - if bytes.Compare(a[0][:], b[0][:]) < 0 { - result[i] = a[0] - a = a[1:] - } else { - result[i] = b[0] - b = b[1:] - } - i++ - } - for j := 0; j < len(a); j++ { - result[i] = a[j] - i++ - } - for j := 0; j < len(b); j++ { - result[i] = b[j] - i++ - } - return result -} - -// dedupMerge combines two sorted lists of hashes into a combo sorted one, -// and removes duplicates in the process -func dedupMerge(a, b []common.Hash) []common.Hash { - result := make([]common.Hash, len(a)+len(b)) - i := 0 - for len(a) > 0 && len(b) > 0 { - if diff := bytes.Compare(a[0][:], b[0][:]); diff < 0 { - result[i] = a[0] - a = a[1:] - } else { - result[i] = b[0] - b = b[1:] - // If they were equal, progress a too - if diff == 0 { - a = a[1:] - } - } - i++ - } - for j := 0; j < len(a); j++ { - result[i] = a[j] - i++ - } - for j := 0; j < len(b); j++ { - result[i] = b[j] - i++ - } - return result[:i] -} diff --git a/core/state/state_test.go b/core/state/state_test.go index 0c920a9a26..41d9b4655e 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -36,7 +36,7 @@ type stateTest struct { func newStateTest() *stateTest { db := rawdb.NewMemoryDatabase() - sdb, _ := New(common.Hash{}, NewDatabase(db)) + sdb, _ := New(common.Hash{}, NewDatabase(db), nil) return &stateTest{db: db, state: sdb} } @@ -146,7 +146,7 @@ func TestSnapshotEmpty(t *testing.T) { } func TestSnapshot2(t *testing.T) { - state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) stateobjaddr0 := toAddr([]byte("so0")) stateobjaddr1 := toAddr([]byte("so1")) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index a065d2c556..ad6aeb22e7 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -39,7 +39,7 @@ import ( func TestUpdateLeaks(t *testing.T) { // Create an empty state database db := rawdb.NewMemoryDatabase() - state, _ := New(common.Hash{}, NewDatabase(db)) + state, _ := New(common.Hash{}, NewDatabase(db), nil) // Update it with some accounts for i := byte(0); i < 255; i++ { @@ -73,8 +73,8 @@ func TestIntermediateLeaks(t *testing.T) { // Create two state databases, one transitioning to the final state, the other final from the beginning transDb := rawdb.NewMemoryDatabase() finalDb := rawdb.NewMemoryDatabase() - transState, _ := New(common.Hash{}, NewDatabase(transDb)) - finalState, _ := New(common.Hash{}, NewDatabase(finalDb)) + transState, _ := New(common.Hash{}, NewDatabase(transDb), nil) + finalState, _ := New(common.Hash{}, NewDatabase(finalDb), nil) modify := func(state *StateDB, addr common.Address, i, tweak byte) { state.SetBalance(addr, big.NewInt(int64(11*i)+int64(tweak))) @@ -149,7 +149,7 @@ func TestIntermediateLeaks(t *testing.T) { // https://github.com/ethereum/go-ethereum/pull/15549. func TestCopy(t *testing.T) { // Create a random state test to copy and modify "independently" - orig, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + orig, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) for i := byte(0); i < 255; i++ { obj := orig.GetOrNewStateObject(common.BytesToAddress([]byte{i})) @@ -385,7 +385,7 @@ func (test *snapshotTest) String() string { func (test *snapshotTest) run() bool { // Run all actions and create snapshots. var ( - state, _ = New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ = New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) snapshotRevs = make([]int, len(test.snapshots)) sindex = 0 ) @@ -399,7 +399,7 @@ func (test *snapshotTest) run() bool { // Revert all snapshots in reverse order. Each revert must yield a state // that is equivalent to fresh state with all actions up the snapshot applied. for sindex--; sindex >= 0; sindex-- { - checkstate, _ := New(common.Hash{}, state.Database()) + checkstate, _ := New(common.Hash{}, state.Database(), nil) for _, action := range test.actions[:test.snapshots[sindex]] { action.fn(action, checkstate) } @@ -477,7 +477,7 @@ func TestTouchDelete(t *testing.T) { // TestCopyOfCopy tests that modified objects are carried over to the copy, and the copy of the copy. // See https://github.com/ethereum/go-ethereum/pull/15225#issuecomment-380191512 func TestCopyOfCopy(t *testing.T) { - state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) addr := common.HexToAddress("aaaa") state.SetBalance(addr, big.NewInt(42)) @@ -494,7 +494,7 @@ func TestCopyOfCopy(t *testing.T) { // // See https://github.com/ethereum/go-ethereum/issues/20106. func TestCopyCommitCopy(t *testing.T) { - state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) // Create an account and check if the retrieved balance is correct addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") @@ -566,7 +566,7 @@ func TestCopyCommitCopy(t *testing.T) { // // See https://github.com/ethereum/go-ethereum/issues/20106. func TestCopyCopyCommitCopy(t *testing.T) { - state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) // Create an account and check if the retrieved balance is correct addr := common.HexToAddress("0xaffeaffeaffeaffeaffeaffeaffeaffeaffeaffe") @@ -656,7 +656,7 @@ func TestCopyCopyCommitCopy(t *testing.T) { // first, but the journal wiped the entire state object on create-revert. func TestDeleteCreateRevert(t *testing.T) { // Create an initial state with a single contract - state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase())) + state, _ := New(common.Hash{}, NewDatabase(rawdb.NewMemoryDatabase()), nil) addr := toAddr([]byte("so")) state.SetBalance(addr, big.NewInt(1)) diff --git a/core/state/sync_test.go b/core/state/sync_test.go index f4a221bd9d..924c8c2f90 100644 --- a/core/state/sync_test.go +++ b/core/state/sync_test.go @@ -41,7 +41,7 @@ type testAccount struct { func makeTestState() (Database, common.Hash, []*testAccount) { // Create an empty state db := NewDatabase(rawdb.NewMemoryDatabase()) - state, _ := New(common.Hash{}, db) + state, _ := New(common.Hash{}, db, nil) // Fill it with some arbitrary data accounts := []*testAccount{} @@ -72,7 +72,7 @@ func makeTestState() (Database, common.Hash, []*testAccount) { // account array. func checkStateAccounts(t *testing.T, db ethdb.Database, root common.Hash, accounts []*testAccount) { // Check root availability and state contents - state, err := New(root, NewDatabase(db)) + state, err := New(root, NewDatabase(db), nil) if err != nil { t.Fatalf("failed to create state trie at %x: %v", root, err) } @@ -113,7 +113,7 @@ func checkStateConsistency(db ethdb.Database, root common.Hash) error { if _, err := db.Get(root.Bytes()); err != nil { return nil // Consider a non existent state consistent. } - state, err := New(root, NewDatabase(db)) + state, err := New(root, NewDatabase(db), nil) if err != nil { return err } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 4db3e6deef..a56151eba6 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -86,7 +86,7 @@ func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key } func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} key, _ := crypto.GenerateKey() @@ -171,7 +171,7 @@ func (c *testChain) State() (*state.StateDB, error) { // a state change between those fetches. stdb := c.statedb if *c.trigger { - c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + c.statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) // simulate that the new head block included tx0 and tx1 c.statedb.SetNonce(c.address, 2) c.statedb.SetBalance(c.address, new(big.Int).SetUint64(params.Ether)) @@ -189,7 +189,7 @@ func TestStateChangeDuringTransactionPoolReset(t *testing.T) { var ( key, _ = crypto.GenerateKey() address = crypto.PubkeyToAddress(key.PublicKey) - statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) trigger = false ) @@ -345,7 +345,7 @@ func TestTransactionChainFork(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} @@ -374,7 +374,7 @@ func TestTransactionDoubleNonce(t *testing.T) { addr := crypto.PubkeyToAddress(key.PublicKey) resetState := func() { - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.AddBalance(addr, big.NewInt(100000000000000)) pool.chain = &testBlockChain{statedb, 1000000, new(event.Feed)} @@ -565,7 +565,7 @@ func TestTransactionPostponing(t *testing.T) { t.Parallel() // Create the pool to test the postponing with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -778,7 +778,7 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -866,7 +866,7 @@ func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { evictionInterval = time.Second // Create the pool to test the non-expiration enforcement - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -969,7 +969,7 @@ func TestTransactionPendingGlobalLimiting(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1071,7 +1071,7 @@ func TestTransactionCapClearsFromAll(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1105,7 +1105,7 @@ func TestTransactionPendingMinimumAllowance(t *testing.T) { t.Parallel() // Create the pool to test the limit enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1153,7 +1153,7 @@ func TestTransactionPoolRepricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1274,7 +1274,7 @@ func TestTransactionPoolRepricingKeepsLocals(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1336,7 +1336,7 @@ func TestTransactionPoolUnderpricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1442,7 +1442,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1507,7 +1507,7 @@ func TestTransactionDeduplication(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1573,7 +1573,7 @@ func TestTransactionReplacement(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) @@ -1668,7 +1668,7 @@ func testTransactionJournaling(t *testing.T, nolocals bool) { os.Remove(journal) // Create the original pool to inject transaction into the journal - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} config := testTxPoolConfig @@ -1766,7 +1766,7 @@ func TestTransactionStatusCheck(t *testing.T) { t.Parallel() // Create the pool to test the status retrievals with - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} pool := NewTxPool(testTxPoolConfig, params.TestChainConfig, blockchain) diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 5d443de0ea..2d8d3c6bc3 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -81,7 +81,7 @@ func TestEIP2200(t *testing.T) { for i, tt := range eip2200Tests { address := common.BytesToAddress([]byte("contract")) - statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) + statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) statedb.CreateAccount(address) statedb.SetCode(address, hexutil.MustDecode(tt.input)) statedb.SetState(address, common.Hash{}, common.BytesToHash([]byte{tt.original})) diff --git a/les/odr_test.go b/les/odr_test.go index 7d10878226..45ed9e0659 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -91,7 +91,7 @@ func odrAccounts(ctx context.Context, db ethdb.Database, config *params.ChainCon for _, addr := range acc { if bc != nil { header := bc.GetHeaderByHash(bhash) - st, err = state.New(header.Root, state.NewDatabase(db)) + st, err = state.New(header.Root, state.NewDatabase(db), nil) } else { header := lc.GetHeaderByHash(bhash) st = light.NewState(ctx, header, lc.Odr()) @@ -122,7 +122,7 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai data[35] = byte(i) if bc != nil { header := bc.GetHeaderByHash(bhash) - statedb, err := state.New(header.Root, state.NewDatabase(db)) + statedb, err := state.New(header.Root, state.NewDatabase(db), nil) if err == nil { from := statedb.GetOrNewStateObject(bankAddr) From 92ec07d63bc06241df6b9c8cec6c9d5954a192f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 27 Feb 2020 15:03:10 +0200 Subject: [PATCH 018/103] core/state: fix an account resurrection issue --- core/state/state_object.go | 9 +-------- core/state/statedb.go | 8 ++++---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index d10caa8319..26e0b08f59 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -297,18 +297,11 @@ func (s *stateObject) updateTrie(db Database) Trie { // Retrieve the snapshot storage map for the object var storage map[common.Hash][]byte if s.db.snap != nil { - // Retrieve the old storage map, if available - s.db.snapLock.RLock() + // Retrieve the old storage map, if available, create a new one otherwise storage = s.db.snapStorage[s.addrHash] - s.db.snapLock.RUnlock() - - // If no old storage map was available, create a new one if storage == nil { storage = make(map[common.Hash][]byte) - - s.db.snapLock.Lock() s.db.snapStorage[s.addrHash] = storage - s.db.snapLock.Unlock() } } // Insert all the pending updates into the trie diff --git a/core/state/statedb.go b/core/state/statedb.go index b3ea95a464..d4a91ee715 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,7 +22,6 @@ import ( "fmt" "math/big" "sort" - "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -72,7 +71,6 @@ type StateDB struct { snap snapshot.Snapshot snapAccounts map[common.Hash][]byte snapStorage map[common.Hash]map[common.Hash][]byte - snapLock sync.RWMutex // Lock for the concurrent storage updaters // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*stateObject @@ -468,6 +466,10 @@ func (s *StateDB) updateStateObject(obj *stateObject) { // If state snapshotting is active, cache the data til commit if s.snap != nil { + // If the account is an empty resurrection, unmark the storage nil-ness + if storage, ok := s.snapStorage[obj.addrHash]; storage == nil && ok { + delete(s.snapStorage, obj.addrHash) + } s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) } } @@ -484,10 +486,8 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // If state snapshotting is active, cache the data til commit if s.snap != nil { - s.snapLock.Lock() s.snapAccounts[obj.addrHash] = nil // We need to maintain account deletions explicitly s.snapStorage[obj.addrHash] = nil // We need to maintain storage deletions explicitly - s.snapLock.Unlock() } } From 361a6f08acad506c16cef1a1436b5975478e811f Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 2 Mar 2020 13:46:56 +0100 Subject: [PATCH 019/103] core/tests: test for destroy+recreate contract with storage --- core/blockchain_test.go | 128 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 4bed1b4518..72f9898e5a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2362,3 +2362,131 @@ func TestDeleteCreateRevert(t *testing.T) { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } } + +// TestDeleteRecreate tests a state-transition that contains both deletion +// and recreation of contract state. +// Contract A exists, has slots 1 and 2 set +// Tx 1: Selfdestruct A +// Tx 2: Re-create A, set slots 3 and 4 +// Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, +// and then the new slots exist +func TestDeleteRecreate(t *testing.T) { + var ( + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + + aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), 0xFF} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. Set slots 3=3, 4=4, + // 2. Return aaCode + + initCode := []byte{ + byte(vm.PUSH1), 0x3, // value + byte(vm.PUSH1), 0x3, // location + byte(vm.SSTORE), // Set slot[3] = 1 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x4, // location + byte(vm.SSTORE), // Set slot[4] = 1 + // Slots are set, now return the code + byte(vm.PUSH2), 0x88, 0xff, // Push code on stack + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + // Code is now in memory. + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), byte(32 - 2), // offset + byte(vm.RETURN), + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + // The contract BB recreates AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + genesis := gspec.MustCommit(db) + + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA, to kill it + tx, _ := types.SignTx(types.NewTransaction(0, aa, + big.NewInt(0), 50000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to BB, to recreate AA + tx, _ = types.SignTx(types.NewTransaction(1, bb, + big.NewInt(0), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + Debug: true, + Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + // Also, 3 and 4 should be set + if got, exp := statedb.GetState(aa, common.HexToHash("03")), common.HexToHash("03"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("04")), common.HexToHash("04"); got != exp { + t.Fatalf("got %x exp %x", got, exp) + } +} From fe8347ea8a596ed233911340368ffd607fc23f70 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 2 Mar 2020 14:06:44 +0100 Subject: [PATCH 020/103] squashme --- core/blockchain_test.go | 77 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 72f9898e5a..69a2453223 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2363,14 +2363,14 @@ func TestDeleteCreateRevert(t *testing.T) { } } -// TestDeleteRecreate tests a state-transition that contains both deletion +// TestDeleteRecreateSlots tests a state-transition that contains both deletion // and recreation of contract state. // Contract A exists, has slots 1 and 2 set // Tx 1: Selfdestruct A // Tx 2: Re-create A, set slots 3 and 4 // Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, // and then the new slots exist -func TestDeleteRecreate(t *testing.T) { +func TestDeleteRecreateSlots(t *testing.T) { var ( // Generate a canonical chain to act as the main dataset engine = ethash.NewFaker() @@ -2490,3 +2490,76 @@ func TestDeleteRecreate(t *testing.T) { t.Fatalf("got %x exp %x", got, exp) } } + +// TestDeleteRecreateAccount tests a state-transition that contains deletion of a +// contract with storage, and a recreate of the same contract via a +// regular value-transfer +// Expected outcome is that _all_ slots are cleared from A +func TestDeleteRecreateAccount(t *testing.T) { + var ( + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + + aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), 0xFF} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + }, + } + genesis := gspec.MustCommit(db) + + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 1, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to AA, to kill it + tx, _ := types.SignTx(types.NewTransaction(0, aa, + big.NewInt(0), 50000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + // One transaction to AA, to recreate it (but without storage + tx, _ = types.SignTx(types.NewTransaction(1, aa, + big.NewInt(1), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + }) + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + Debug: true, + Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + if n, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + + // If all is correct, then both slots are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("got %x exp %x", got, exp) + } +} From 6e05ccd845da26774774185956b3fd67966894ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 3 Mar 2020 09:10:23 +0200 Subject: [PATCH 021/103] core/state/snapshot, tests: sync snap gen + snaps in consensus tests --- cmd/evm/staterunner.go | 2 +- core/blockchain.go | 4 +++- core/state/snapshot/disklayer.go | 5 +++-- core/state/snapshot/generate.go | 14 ++++++------ core/state/snapshot/journal.go | 1 + core/state/snapshot/snapshot.go | 37 ++++++++++++++++++++++++++------ eth/tracers/tracers_test.go | 4 ++-- tests/block_test.go | 8 ++++--- tests/block_test_util.go | 9 ++++++-- tests/state_test.go | 13 ++++++++--- tests/state_test_util.go | 19 ++++++++++------ tests/transaction_test_util.go | 1 - tests/vm_test.go | 5 ++++- tests/vm_test_util.go | 4 ++-- 14 files changed, 90 insertions(+), 36 deletions(-) diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index cef2aedb5e..52c1eca715 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -96,7 +96,7 @@ func stateTestCmd(ctx *cli.Context) error { for _, st := range test.Subtests() { // Run the test and aggregate the result result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - state, err := test.Run(st, cfg) + state, err := test.Run(st, cfg, false) // print state root for evmlab tracing if ctx.GlobalBool(MachineFlag.Name) && state != nil { fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%x\"}\n", state.IntermediateRoot(false)) diff --git a/core/blockchain.go b/core/blockchain.go index 491eccecd4..b0309ef702 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -121,6 +121,8 @@ type CacheConfig struct { TrieDirtyDisabled bool // Whether to disable trie write caching and GC altogether (archive node) TrieTimeLimit time.Duration // Time limit after which to flush the current in-memory trie to disk SnapshotLimit int // Memory allowance (MB) to use for caching snapshot entries in memory + + SnapshotWait bool // Wait for snapshot construction on startup. TODO(karalabe): This is a dirty hack for testing, nuke it } // BlockChain represents the canonical chain given a database with a genesis @@ -303,7 +305,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par } // Load any existing snapshot, regenerating it if loading failed if bc.cacheConfig.SnapshotLimit > 0 { - bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root()) + bc.snaps = snapshot.New(bc.db, bc.stateCache.TrieDB(), bc.cacheConfig.SnapshotLimit, bc.CurrentBlock().Root(), !bc.cacheConfig.SnapshotWait) } // Take ownership of this particular state go bc.update() diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 0c4c3deb1b..3266424a8a 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -37,8 +37,9 @@ type diskLayer struct { root common.Hash // Root hash of the base snapshot stale bool // Signals that the layer became stale (state progressed) - genMarker []byte // Marker for the state that's indexed during initial layer generation - genAbort chan chan *generatorStats // Notification channel to abort generating the snapshot in this layer + genMarker []byte // Marker for the state that's indexed during initial layer generation + genPending chan struct{} // Notification channel when generation is done (test synchronicity) + genAbort chan chan *generatorStats // Notification channel to abort generating the snapshot in this layer lock sync.RWMutex } diff --git a/core/state/snapshot/generate.go b/core/state/snapshot/generate.go index 8a407e30d2..4b017fe69b 100644 --- a/core/state/snapshot/generate.go +++ b/core/state/snapshot/generate.go @@ -101,12 +101,13 @@ func generateSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache i rawdb.WriteSnapshotRoot(diskdb, root) base := &diskLayer{ - diskdb: diskdb, - triedb: triedb, - root: root, - cache: fastcache.New(cache * 1024 * 1024), - genMarker: []byte{}, // Initialized but empty! - genAbort: make(chan chan *generatorStats), + diskdb: diskdb, + triedb: triedb, + root: root, + cache: fastcache.New(cache * 1024 * 1024), + genMarker: []byte{}, // Initialized but empty! + genPending: make(chan struct{}), + genAbort: make(chan chan *generatorStats), } go base.generate(&generatorStats{wiping: wiper, start: time.Now()}) return base @@ -252,6 +253,7 @@ func (dl *diskLayer) generate(stats *generatorStats) { dl.lock.Lock() dl.genMarker = nil + close(dl.genPending) dl.lock.Unlock() // Someone will be looking for us, wait it out diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index 8e039606f1..c42a26d21d 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -108,6 +108,7 @@ func loadSnapshot(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, if base.genMarker == nil { base.genMarker = []byte{} } + base.genPending = make(chan struct{}) base.genAbort = make(chan chan *generatorStats) var origin uint64 diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index ad602bbbbc..d031dd2c1a 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -164,7 +164,7 @@ type Tree struct { // If the snapshot is missing or inconsistent, the entirety is deleted and will // be reconstructed from scratch based on the tries in the key-value store, on a // background thread. -func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash) *Tree { +func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root common.Hash, async bool) *Tree { // Create a new, empty snapshot tree snap := &Tree{ diskdb: diskdb, @@ -172,6 +172,9 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm cache: cache, layers: make(map[common.Hash]snapshot), } + if !async { + defer snap.waitBuild() + } // Attempt to load a previously persisted snapshot and rebuild one if failed head, err := loadSnapshot(diskdb, triedb, cache, root) if err != nil { @@ -187,6 +190,27 @@ func New(diskdb ethdb.KeyValueStore, triedb *trie.Database, cache int, root comm return snap } +// waitBuild blocks until the snapshot finishes rebuilding. This method is meant +// to be used by tests to ensure we're testing what we believe we are. +func (t *Tree) waitBuild() { + // Find the rebuild termination channel + var done chan struct{} + + t.lock.RLock() + for _, layer := range t.layers { + if layer, ok := layer.(*diskLayer); ok { + done = layer.genPending + break + } + } + t.lock.RUnlock() + + // Wait until the snapshot is generated + if done != nil { + <-done + } +} + // Snapshot retrieves a snapshot belonging to the given block root, or nil if no // snapshot is maintained for that block. func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { @@ -477,11 +501,12 @@ func diffToDisk(bottom *diffLayer) *diskLayer { log.Crit("Failed to write leftover snapshot", "err", err) } res := &diskLayer{ - root: bottom.root, - cache: base.cache, - diskdb: base.diskdb, - triedb: base.triedb, - genMarker: base.genMarker, + root: bottom.root, + cache: base.cache, + diskdb: base.diskdb, + triedb: base.triedb, + genMarker: base.genMarker, + genPending: base.genPending, } // If snapshot generation hasn't finished yet, port over all the starts and // continue where the previous round left off. diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index 69eb80a5c5..289c6c5bb4 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -168,7 +168,7 @@ func TestPrestateTracerCreate2(t *testing.T) { Code: []byte{}, Balance: big.NewInt(500000000000000), } - statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc) + statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false) // Create the tracer, the EVM environment and run it tracer, err := New("prestateTracer") @@ -242,7 +242,7 @@ func TestCallTracer(t *testing.T) { GasLimit: uint64(test.Context.GasLimit), GasPrice: tx.GasPrice(), } - statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc) + statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false) // Create the tracer, the EVM environment and run it tracer, err := New("callTracer") diff --git a/tests/block_test.go b/tests/block_test.go index 3a55e4c34f..8fa90e3e39 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -45,11 +45,13 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*randomStatetest94.json.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if err := bt.checkFailure(t, name, test.Run()); err != nil { - t.Error(err) + if err := bt.checkFailure(t, name+"/trie", test.Run(false)); err != nil { + t.Errorf("test without snapshotter failed: %v", err) + } + if err := bt.checkFailure(t, name+"/snap", test.Run(true)); err != nil { + t.Errorf("test with snapshotter failed: %v", err) } }) - // There is also a LegacyTests folder, containing blockchain tests generated // prior to Istanbul. However, they are all derived from GeneralStateTests, // which run natively, so there's no reason to run them here. diff --git a/tests/block_test_util.go b/tests/block_test_util.go index b5f1de3efc..1ae986e3ca 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -94,7 +94,7 @@ type btHeaderMarshaling struct { Timestamp math.HexOrDecimal64 } -func (t *BlockTest) Run() error { +func (t *BlockTest) Run(snapshotter bool) error { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -118,7 +118,12 @@ func (t *BlockTest) Run() error { } else { engine = ethash.NewShared() } - chain, err := core.NewBlockChain(db, &core.CacheConfig{TrieCleanLimit: 0}, config, engine, vm.Config{}, nil) + cache := &core.CacheConfig{TrieCleanLimit: 0} + if snapshotter { + cache.SnapshotLimit = 1 + cache.SnapshotWait = true + } + chain, err := core.NewBlockChain(db, cache, config, engine, vm.Config{}, nil) if err != nil { return err } diff --git a/tests/state_test.go b/tests/state_test.go index f9499d4a89..c0a90b3a42 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -63,10 +63,17 @@ func TestState(t *testing.T) { subtest := subtest key := fmt.Sprintf("%s/%d", subtest.Fork, subtest.Index) name := name + "/" + key - t.Run(key, func(t *testing.T) { + + t.Run(key+"/trie", func(t *testing.T) { withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { - _, err := test.Run(subtest, vmconfig) - return st.checkFailure(t, name, err) + _, err := test.Run(subtest, vmconfig, false) + return st.checkFailure(t, name+"/trie", err) + }) + }) + t.Run(key+"/snap", func(t *testing.T) { + withTrace(t, test.gasLimit(subtest), func(vmconfig vm.Config) error { + _, err := test.Run(subtest, vmconfig, true) + return st.checkFailure(t, name+"/snap", err) }) }) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a10d044cd0..5e5b96d527 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -24,6 +24,8 @@ import ( "strconv" "strings" + "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -145,8 +147,8 @@ func (t *StateTest) Subtests() []StateSubtest { } // Run executes a specific subtest and verifies the post-state and logs -func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, error) { - statedb, root, err := t.RunNoVerify(subtest, vmconfig) +func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, error) { + statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter) if err != nil { return statedb, err } @@ -163,14 +165,14 @@ func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config) (*state.StateD } // RunNoVerify runs a specific subtest and returns the statedb and post-state root -func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config) (*state.StateDB, common.Hash, error) { +func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool) (*state.StateDB, common.Hash, error) { config, eips, err := getVMConfig(subtest.Fork) if err != nil { return nil, common.Hash{}, UnsupportedForkError{subtest.Fork} } vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock(nil) - statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre) + statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) post := t.json.Post[subtest.Fork][subtest.Index] msg, err := t.json.Tx.toMessage(post) @@ -204,7 +206,7 @@ func (t *StateTest) gasLimit(subtest StateSubtest) uint64 { return t.json.Tx.GasLimit[t.json.Post[subtest.Fork][subtest.Index].Indexes.Gas] } -func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { +func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool) *state.StateDB { sdb := state.NewDatabase(db) statedb, _ := state.New(common.Hash{}, sdb, nil) for addr, a := range accounts { @@ -217,7 +219,12 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB } // Commit and re-open to start with a clean state. root, _ := statedb.Commit(false) - statedb, _ = state.New(root, sdb, nil) + + var snaps *snapshot.Tree + if snapshotter { + snaps = snapshot.New(db, sdb.TrieDB(), 1, root, false) + } + statedb, _ = state.New(root, sdb, snaps) return statedb } diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 43debae830..aea90535c3 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -45,7 +45,6 @@ type ttFork struct { } func (tt *TransactionTest) Run(config *params.ChainConfig) error { - validateTx := func(rlpData hexutil.Bytes, signer types.Signer, isHomestead bool, isIstanbul bool) (*common.Address, *common.Hash, error) { tx := new(types.Transaction) if err := rlp.DecodeBytes(rlpData, tx); err != nil { diff --git a/tests/vm_test.go b/tests/vm_test.go index 441483dffa..fb839827ac 100644 --- a/tests/vm_test.go +++ b/tests/vm_test.go @@ -30,7 +30,10 @@ func TestVM(t *testing.T) { vmt.walk(t, vmTestDir, func(t *testing.T, name string, test *VMTest) { withTrace(t, test.json.Exec.GasLimit, func(vmconfig vm.Config) error { - return vmt.checkFailure(t, name, test.Run(vmconfig)) + return vmt.checkFailure(t, name+"/trie", test.Run(vmconfig, false)) + }) + withTrace(t, test.json.Exec.GasLimit, func(vmconfig vm.Config) error { + return vmt.checkFailure(t, name+"/snap", test.Run(vmconfig, true)) }) }) } diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index 91566c47e3..9acbe59f44 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -78,8 +78,8 @@ type vmExecMarshaling struct { GasPrice *math.HexOrDecimal256 } -func (t *VMTest) Run(vmconfig vm.Config) error { - statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre) +func (t *VMTest) Run(vmconfig vm.Config, snapshotter bool) error { + statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter) ret, gasRemaining, err := t.exec(statedb, vmconfig) if t.json.GasRemaining == nil { From a4cf279494f53276cf3576ae89b14b58efe644fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 3 Mar 2020 15:52:00 +0200 Subject: [PATCH 022/103] core/state: extend snapshotter to handle account resurrections --- core/blockchain.go | 1 + core/blockchain_test.go | 10 +- core/state/snapshot/difflayer.go | 142 +++++++++++++++-------- core/state/snapshot/difflayer_test.go | 157 ++++++++++++++++---------- core/state/snapshot/disklayer.go | 4 +- core/state/snapshot/disklayer_test.go | 30 ++--- core/state/snapshot/iterator_test.go | 101 +++++++++-------- core/state/snapshot/journal.go | 22 +++- core/state/snapshot/snapshot.go | 63 ++++++----- core/state/snapshot/snapshot_test.go | 28 ++--- core/state/statedb.go | 45 ++++---- core/vm/opcodes.go | 13 +-- 12 files changed, 365 insertions(+), 251 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index b0309ef702..de0d4f3993 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -198,6 +198,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 256, + SnapshotWait: true, } } bodyCache, _ := lru.New(bodyCacheLimit) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 69a2453223..5e2a21023a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2315,7 +2315,7 @@ func TestDeleteCreateRevert(t *testing.T) { // The address 0xAAAAA selfdestructs if called aa: { // Code needs to just selfdestruct - Code: []byte{byte(vm.PC), 0xFF}, + Code: []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)}, Nonce: 1, Balance: big.NewInt(0), }, @@ -2382,8 +2382,8 @@ func TestDeleteRecreateSlots(t *testing.T) { aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") - aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA - aaCode = []byte{byte(vm.PC), 0xFF} // Code for AA (simple selfdestruct) + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) ) // Populate two slots aaStorage[common.HexToHash("01")] = common.HexToHash("01") @@ -2506,8 +2506,8 @@ func TestDeleteRecreateAccount(t *testing.T) { funds = big.NewInt(1000000000) aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") - aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA - aaCode = []byte{byte(vm.PC), 0xFF} // Code for AA (simple selfdestruct) + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) ) // Populate two slots aaStorage[common.HexToHash("01")] = common.HexToHash("01") diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 3c1bea421e..0915fb6bc6 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -27,7 +27,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" "github.com/steakknife/bloomfilter" ) @@ -68,17 +67,28 @@ var ( // entry count). bloomFuncs = math.Round((bloomSize / float64(aggregatorItemLimit)) * math.Log(2)) - // bloomHashesOffset is a runtime constant which determines which part of the + // the bloom offsets are runtime constants which determines which part of the // the account/storage hash the hasher functions looks at, to determine the // bloom key for an account/slot. This is randomized at init(), so that the // global population of nodes do not all display the exact same behaviour with // regards to bloom content - bloomHasherOffset = 0 + bloomDestructHasherOffset = 0 + bloomAccountHasherOffset = 0 + bloomStorageHasherOffset = 0 ) func init() { - // Init bloomHasherOffset in the range [0:24] (requires 8 bytes) - bloomHasherOffset = rand.Intn(25) + // Init the bloom offsets in the range [0:24] (requires 8 bytes) + bloomDestructHasherOffset = rand.Intn(25) + bloomAccountHasherOffset = rand.Intn(25) + bloomStorageHasherOffset = rand.Intn(25) + + // The destruct and account blooms must be different, as the storage slots + // will check for destruction too for every bloom miss. It should not collide + // with modified accounts. + for bloomAccountHasherOffset == bloomDestructHasherOffset { + bloomAccountHasherOffset = rand.Intn(25) + } } // diffLayer represents a collection of modifications made to a state snapshot @@ -95,6 +105,7 @@ type diffLayer struct { root common.Hash // Root hash to which this snapshot diff belongs to stale uint32 // Signals that the layer became stale (state progressed) + destructSet map[common.Hash]struct{} // Keyed markers for deleted (and potentially) recreated accounts accountList []common.Hash // List of account for iteration. If it exists, it's sorted, otherwise it's nil accountData map[common.Hash][]byte // Keyed accounts for direct retrival (nil means deleted) storageList map[common.Hash][]common.Hash // List of storage slots for iterated retrievals, one per account. Any existing lists are sorted if non-nil @@ -105,6 +116,20 @@ type diffLayer struct { lock sync.RWMutex } +// destructBloomHasher is a wrapper around a common.Hash to satisfy the interface +// API requirements of the bloom library used. It's used to convert a destruct +// event into a 64 bit mini hash. +type destructBloomHasher common.Hash + +func (h destructBloomHasher) Write(p []byte) (n int, err error) { panic("not implemented") } +func (h destructBloomHasher) Sum(b []byte) []byte { panic("not implemented") } +func (h destructBloomHasher) Reset() { panic("not implemented") } +func (h destructBloomHasher) BlockSize() int { panic("not implemented") } +func (h destructBloomHasher) Size() int { return 8 } +func (h destructBloomHasher) Sum64() uint64 { + return binary.BigEndian.Uint64(h[bloomDestructHasherOffset : bloomDestructHasherOffset+8]) +} + // accountBloomHasher is a wrapper around a common.Hash to satisfy the interface // API requirements of the bloom library used. It's used to convert an account // hash into a 64 bit mini hash. @@ -116,7 +141,7 @@ func (h accountBloomHasher) Reset() { panic("not impl func (h accountBloomHasher) BlockSize() int { panic("not implemented") } func (h accountBloomHasher) Size() int { return 8 } func (h accountBloomHasher) Sum64() uint64 { - return binary.BigEndian.Uint64(h[bloomHasherOffset : bloomHasherOffset+8]) + return binary.BigEndian.Uint64(h[bloomAccountHasherOffset : bloomAccountHasherOffset+8]) } // storageBloomHasher is a wrapper around a [2]common.Hash to satisfy the interface @@ -130,17 +155,18 @@ func (h storageBloomHasher) Reset() { panic("not impl func (h storageBloomHasher) BlockSize() int { panic("not implemented") } func (h storageBloomHasher) Size() int { return 8 } func (h storageBloomHasher) Sum64() uint64 { - return binary.BigEndian.Uint64(h[0][bloomHasherOffset:bloomHasherOffset+8]) ^ - binary.BigEndian.Uint64(h[1][bloomHasherOffset:bloomHasherOffset+8]) + return binary.BigEndian.Uint64(h[0][bloomStorageHasherOffset:bloomStorageHasherOffset+8]) ^ + binary.BigEndian.Uint64(h[1][bloomStorageHasherOffset:bloomStorageHasherOffset+8]) } // newDiffLayer creates a new diff on top of an existing snapshot, whether that's a low // level persistent database or a hierarchical diff already. -func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { +func newDiffLayer(parent snapshot, root common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { // Create the new layer with some pre-allocated data segments dl := &diffLayer{ parent: parent, root: root, + destructSet: destructs, accountData: accounts, storageData: storage, } @@ -152,6 +178,17 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] default: panic("unknown parent type") } + // Sanity check that accounts or storage slots are never nil + for accountHash, blob := range accounts { + if blob == nil { + panic(fmt.Sprintf("account %#x nil", accountHash)) + } + } + for accountHash, slots := range storage { + if slots == nil { + panic(fmt.Sprintf("storage %#x nil", accountHash)) + } + } // Determine memory size and track the dirty writes for _, data := range accounts { dl.memory += uint64(common.HashLength + len(data)) @@ -159,24 +196,11 @@ func newDiffLayer(parent snapshot, root common.Hash, accounts map[common.Hash][] } // Fill the storage hashes and sort them for the iterator dl.storageList = make(map[common.Hash][]common.Hash) - - for accountHash, slots := range storage { - // If the slots are nil, sanity check that it's a deleted account - if slots == nil { - // Ensure that the account was just marked as deleted - if account, ok := accounts[accountHash]; account != nil || !ok { - panic(fmt.Sprintf("storage in %#x nil, but account conflicts (%#x, exists: %v)", accountHash, account, ok)) - } - // Everything ok, store the deletion mark and continue - dl.storageList[accountHash] = nil - continue - } - // Storage slots are not nil so entire contract was not deleted, ensure the - // account was just updated. - if account, ok := accounts[accountHash]; account == nil || !ok { - log.Error(fmt.Sprintf("storage in %#x exists, but account nil (exists: %v)", accountHash, ok)) - } - // Determine memory size and track the dirty writes + for accountHash := range destructs { + dl.storageList[accountHash] = nil + } + // Determine memory size and track the dirty writes + for _, slots := range storage { for _, data := range slots { dl.memory += uint64(common.HashLength + len(data)) snapshotDirtyStorageWriteMeter.Mark(int64(len(data))) @@ -208,6 +232,9 @@ func (dl *diffLayer) rebloom(origin *diskLayer) { dl.diffed, _ = bloomfilter.New(uint64(bloomSize), uint64(bloomFuncs)) } // Iterate over all the accounts and storage slots and index them + for hash := range dl.destructSet { + dl.diffed.Add(destructBloomHasher(hash)) + } for hash := range dl.accountData { dl.diffed.Add(accountBloomHasher(hash)) } @@ -265,6 +292,9 @@ func (dl *diffLayer) AccountRLP(hash common.Hash) ([]byte, error) { // all the maps in all the layers below dl.lock.RLock() hit := dl.diffed.Contains(accountBloomHasher(hash)) + if !hit { + hit = dl.diffed.Contains(destructBloomHasher(hash)) + } dl.lock.RUnlock() // If the bloom filter misses, don't even bother with traversing the memory @@ -289,19 +319,22 @@ func (dl *diffLayer) accountRLP(hash common.Hash, depth int) ([]byte, error) { if dl.Stale() { return nil, ErrSnapshotStale } - // If the account is known locally, return it. Note, a nil account means it was - // deleted, and is a different notion than an unknown account! + // If the account is known locally, return it if data, ok := dl.accountData[hash]; ok { snapshotDirtyAccountHitMeter.Mark(1) snapshotDirtyAccountHitDepthHist.Update(int64(depth)) - if n := len(data); n > 0 { - snapshotDirtyAccountReadMeter.Mark(int64(n)) - } else { - snapshotDirtyAccountInexMeter.Mark(1) - } + snapshotDirtyAccountReadMeter.Mark(int64(len(data))) snapshotBloomAccountTrueHitMeter.Mark(1) return data, nil } + // If the account is known locally, but deleted, return it + if _, ok := dl.destructSet[hash]; ok { + snapshotDirtyAccountHitMeter.Mark(1) + snapshotDirtyAccountHitDepthHist.Update(int64(depth)) + snapshotDirtyAccountInexMeter.Mark(1) + snapshotBloomAccountTrueHitMeter.Mark(1) + return nil, nil + } // Account unknown to this diff, resolve from parent if diff, ok := dl.parent.(*diffLayer); ok { return diff.accountRLP(hash, depth+1) @@ -319,6 +352,9 @@ func (dl *diffLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // all the maps in all the layers below dl.lock.RLock() hit := dl.diffed.Contains(storageBloomHasher{accountHash, storageHash}) + if !hit { + hit = dl.diffed.Contains(destructBloomHasher(accountHash)) + } dl.lock.RUnlock() // If the bloom filter misses, don't even bother with traversing the memory @@ -343,16 +379,8 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ if dl.Stale() { return nil, ErrSnapshotStale } - // If the account is known locally, try to resolve the slot locally. Note, a nil - // account means it was deleted, and is a different notion than an unknown account! + // If the account is known locally, try to resolve the slot locally if storage, ok := dl.storageData[accountHash]; ok { - if storage == nil { - snapshotDirtyStorageHitMeter.Mark(1) - snapshotDirtyStorageHitDepthHist.Update(int64(depth)) - snapshotDirtyStorageInexMeter.Mark(1) - snapshotBloomStorageTrueHitMeter.Mark(1) - return nil, nil - } if data, ok := storage[storageHash]; ok { snapshotDirtyStorageHitMeter.Mark(1) snapshotDirtyStorageHitDepthHist.Update(int64(depth)) @@ -365,6 +393,14 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ return data, nil } } + // If the account is known locally, but deleted, return an empty slot + if _, ok := dl.destructSet[accountHash]; ok { + snapshotDirtyStorageHitMeter.Mark(1) + snapshotDirtyStorageHitDepthHist.Update(int64(depth)) + snapshotDirtyStorageInexMeter.Mark(1) + snapshotBloomStorageTrueHitMeter.Mark(1) + return nil, nil + } // Storage slot unknown to this diff, resolve from parent if diff, ok := dl.parent.(*diffLayer); ok { return diff.storage(accountHash, storageHash, depth+1) @@ -376,8 +412,8 @@ func (dl *diffLayer) storage(accountHash, storageHash common.Hash, depth int) ([ // Update creates a new layer on top of the existing snapshot diff tree with // the specified data items. -func (dl *diffLayer) Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { - return newDiffLayer(dl, blockRoot, accounts, storage) +func (dl *diffLayer) Update(blockRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, blockRoot, destructs, accounts, storage) } // flatten pushes all data from this point downwards, flattening everything into @@ -403,14 +439,18 @@ func (dl *diffLayer) flatten() snapshot { panic("parent diff layer is stale") // we've flattened into the same parent from two children, boo } // Overwrite all the updated accounts blindly, merge the sorted list + for hash := range dl.destructSet { + parent.destructSet[hash] = struct{}{} + delete(parent.accountData, hash) + delete(parent.storageData, hash) + } for hash, data := range dl.accountData { parent.accountData[hash] = data } // Overwrite all the updated storage slots (individually) for accountHash, storage := range dl.storageData { - // If storage didn't exist (or was deleted) in the parent; or if the storage - // was freshly deleted in the child, overwrite blindly - if parent.storageData[accountHash] == nil || storage == nil { + // If storage didn't exist (or was deleted) in the parent, overwrite blindly + if _, ok := parent.storageData[accountHash]; !ok { parent.storageData[accountHash] = storage continue } @@ -426,6 +466,7 @@ func (dl *diffLayer) flatten() snapshot { parent: parent.parent, origin: parent.origin, root: dl.root, + destructSet: parent.destructSet, accountData: parent.accountData, storageData: parent.storageData, storageList: make(map[common.Hash][]common.Hash), @@ -451,7 +492,10 @@ func (dl *diffLayer) AccountList() []common.Hash { dl.lock.Lock() defer dl.lock.Unlock() - dl.accountList = make([]common.Hash, 0, len(dl.accountData)) + dl.accountList = make([]common.Hash, 0, len(dl.destructSet)+len(dl.accountData)) + for hash := range dl.destructSet { + dl.accountList = append(dl.accountList, hash) + } for hash := range dl.accountData { dl.accountList = append(dl.accountList, hash) } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index d8212d317f..61d2ed9c0b 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -30,8 +30,9 @@ import ( // TestMergeBasics tests some simple merges func TestMergeBasics(t *testing.T) { var ( - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) ) // Fill up a parent for i := 0; i < 100; i++ { @@ -39,7 +40,10 @@ func TestMergeBasics(t *testing.T) { data := randomAccount() accounts[h] = data - if rand.Intn(20) < 10 { + if rand.Intn(4) == 0 { + destructs[h] = struct{}{} + } + if rand.Intn(2) == 0 { accStorage := make(map[common.Hash][]byte) value := make([]byte, 32) rand.Read(value) @@ -48,20 +52,18 @@ func TestMergeBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) - child := newDiffLayer(parent, common.Hash{}, accounts, storage) - child = newDiffLayer(child, common.Hash{}, accounts, storage) - child = newDiffLayer(child, common.Hash{}, accounts, storage) - child = newDiffLayer(child, common.Hash{}, accounts, storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) + child := newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) + child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) + child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) + child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) // And flatten merged := (child.flatten()).(*diffLayer) { // Check account lists - // Should be zero/nil first if got, exp := len(merged.accountList), 0; got != exp { t.Errorf("accountList wrong, got %v exp %v", got, exp) } - // Then set when we call AccountList if got, exp := len(merged.AccountList()), len(accounts); got != exp { t.Errorf("AccountList() wrong, got %v exp %v", got, exp) } @@ -69,6 +71,11 @@ func TestMergeBasics(t *testing.T) { t.Errorf("accountList [2] wrong, got %v exp %v", got, exp) } } + { // Check account drops + if got, exp := len(merged.destructSet), len(destructs); got != exp { + t.Errorf("accountDrop wrong, got %v exp %v", got, exp) + } + } { // Check storage lists i := 0 for aHash, sMap := range storage { @@ -95,42 +102,61 @@ func TestMergeDelete(t *testing.T) { h1 := common.HexToHash("0x01") h2 := common.HexToHash("0x02") - flip := func() map[common.Hash][]byte { - accs := make(map[common.Hash][]byte) - accs[h1] = randomAccount() - accs[h2] = nil - return accs + flipDrops := func() map[common.Hash]struct{} { + return map[common.Hash]struct{}{ + h2: struct{}{}, + } } - flop := func() map[common.Hash][]byte { - accs := make(map[common.Hash][]byte) - accs[h1] = nil - accs[h2] = randomAccount() - return accs + flipAccs := func() map[common.Hash][]byte { + return map[common.Hash][]byte{ + h1: randomAccount(), + } } - - // Add some flip-flopping layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, flip(), storage) - child := parent.Update(common.Hash{}, flop(), storage) - child = child.Update(common.Hash{}, flip(), storage) - child = child.Update(common.Hash{}, flop(), storage) - child = child.Update(common.Hash{}, flip(), storage) - child = child.Update(common.Hash{}, flop(), storage) - child = child.Update(common.Hash{}, flip(), storage) + flopDrops := func() map[common.Hash]struct{} { + return map[common.Hash]struct{}{ + h1: struct{}{}, + } + } + flopAccs := func() map[common.Hash][]byte { + return map[common.Hash][]byte{ + h2: randomAccount(), + } + } + // Add some flipAccs-flopping layers on top + parent := newDiffLayer(emptyLayer(), common.Hash{}, flipDrops(), flipAccs(), storage) + child := parent.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) + child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) + child = child.Update(common.Hash{}, flopDrops(), flopAccs(), storage) + child = child.Update(common.Hash{}, flipDrops(), flipAccs(), storage) if data, _ := child.Account(h1); data == nil { - t.Errorf("last diff layer: expected %x to be non-nil", h1) + t.Errorf("last diff layer: expected %x account to be non-nil", h1) } if data, _ := child.Account(h2); data != nil { - t.Errorf("last diff layer: expected %x to be nil", h2) + t.Errorf("last diff layer: expected %x account to be nil", h2) + } + if _, ok := child.destructSet[h1]; ok { + t.Errorf("last diff layer: expected %x drop to be missing", h1) + } + if _, ok := child.destructSet[h2]; !ok { + t.Errorf("last diff layer: expected %x drop to be present", h1) } // And flatten merged := (child.flatten()).(*diffLayer) if data, _ := merged.Account(h1); data == nil { - t.Errorf("merged layer: expected %x to be non-nil", h1) + t.Errorf("merged layer: expected %x account to be non-nil", h1) } if data, _ := merged.Account(h2); data != nil { - t.Errorf("merged layer: expected %x to be nil", h2) + t.Errorf("merged layer: expected %x account to be nil", h2) + } + if _, ok := merged.destructSet[h1]; !ok { // Note, drops stay alive until persisted to disk! + t.Errorf("merged diff layer: expected %x drop to be present", h1) + } + if _, ok := merged.destructSet[h2]; !ok { // Note, drops stay alive until persisted to disk! + t.Errorf("merged diff layer: expected %x drop to be present", h1) } // If we add more granular metering of memory, we can enable this again, // but it's not implemented for now @@ -150,18 +176,23 @@ func TestInsertAndMerge(t *testing.T) { child *diffLayer ) { - var accounts = make(map[common.Hash][]byte) - var storage = make(map[common.Hash]map[common.Hash][]byte) - parent = newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) + parent = newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) } { - var accounts = make(map[common.Hash][]byte) - var storage = make(map[common.Hash]map[common.Hash][]byte) + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) accounts[acc] = randomAccount() - accstorage := make(map[common.Hash][]byte) - storage[acc] = accstorage + storage[acc] = make(map[common.Hash][]byte) storage[acc][slot] = []byte{0x01} - child = newDiffLayer(parent, common.Hash{}, accounts, storage) + child = newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } // And flatten merged := (child.flatten()).(*diffLayer) @@ -189,20 +220,21 @@ func emptyLayer() *diskLayer { func BenchmarkSearch(b *testing.B) { // First, we set up 128 diff layers, with 1K items each fill := func(parent snapshot) *diffLayer { - accounts := make(map[common.Hash][]byte) - storage := make(map[common.Hash]map[common.Hash][]byte) - + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) for i := 0; i < 10000; i++ { accounts[randomHash()] = randomAccount() } - return newDiffLayer(parent, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } var layer snapshot layer = emptyLayer() for i := 0; i < 128; i++ { layer = fill(layer) } - key := crypto.Keccak256Hash([]byte{0x13, 0x38}) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -224,9 +256,12 @@ func BenchmarkSearchSlot(b *testing.B) { storageKey := crypto.Keccak256Hash([]byte{0x13, 0x37}) accountRLP := randomAccount() fill := func(parent snapshot) *diffLayer { - accounts := make(map[common.Hash][]byte) + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) accounts[accountKey] = accountRLP - storage := make(map[common.Hash]map[common.Hash][]byte) accStorage := make(map[common.Hash][]byte) for i := 0; i < 5; i++ { @@ -235,7 +270,7 @@ func BenchmarkSearchSlot(b *testing.B) { accStorage[randomHash()] = value storage[accountKey] = accStorage } - return newDiffLayer(parent, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } var layer snapshot layer = emptyLayer() @@ -249,15 +284,17 @@ func BenchmarkSearchSlot(b *testing.B) { } // With accountList and sorting -//BenchmarkFlatten-6 50 29890856 ns/op +// BenchmarkFlatten-6 50 29890856 ns/op // // Without sorting and tracking accountlist // BenchmarkFlatten-6 300 5511511 ns/op func BenchmarkFlatten(b *testing.B) { fill := func(parent snapshot) *diffLayer { - accounts := make(map[common.Hash][]byte) - storage := make(map[common.Hash]map[common.Hash][]byte) - + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) for i := 0; i < 100; i++ { accountKey := randomHash() accounts[accountKey] = randomAccount() @@ -271,11 +308,9 @@ func BenchmarkFlatten(b *testing.B) { } storage[accountKey] = accStorage } - return newDiffLayer(parent, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } - b.ResetTimer() - for i := 0; i < b.N; i++ { b.StopTimer() var layer snapshot @@ -305,9 +340,11 @@ func BenchmarkFlatten(b *testing.B) { // BenchmarkJournal-6 1 1208083335 ns/op // bufio writer func BenchmarkJournal(b *testing.B) { fill := func(parent snapshot) *diffLayer { - accounts := make(map[common.Hash][]byte) - storage := make(map[common.Hash]map[common.Hash][]byte) - + var ( + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) + ) for i := 0; i < 200; i++ { accountKey := randomHash() accounts[accountKey] = randomAccount() @@ -321,7 +358,7 @@ func BenchmarkJournal(b *testing.B) { } storage[accountKey] = accStorage } - return newDiffLayer(parent, common.Hash{}, accounts, storage) + return newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) } layer := snapshot(new(diskLayer)) for i := 1; i < 128; i++ { diff --git a/core/state/snapshot/disklayer.go b/core/state/snapshot/disklayer.go index 3266424a8a..e8f2bc853f 100644 --- a/core/state/snapshot/disklayer.go +++ b/core/state/snapshot/disklayer.go @@ -161,6 +161,6 @@ func (dl *diskLayer) Storage(accountHash, storageHash common.Hash) ([]byte, erro // Update creates a new layer on top of the existing snapshot diff tree with // the specified data items. Note, the maps are retained by the method to avoid // copying everything. -func (dl *diskLayer) Update(blockHash common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { - return newDiffLayer(dl, blockHash, accounts, storage) +func (dl *diskLayer) Update(blockHash common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer { + return newDiffLayer(dl, blockHash, destructs, accounts, storage) } diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index b8dded0d80..aae2aa6b56 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -116,13 +116,14 @@ func TestDiskMerge(t *testing.T) { base.Storage(conNukeCache, conNukeCacheSlot) // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash][]byte{ - accModNoCache: reverse(accModNoCache[:]), - accModCache: reverse(accModCache[:]), - accDelNoCache: nil, - accDelCache: nil, - conNukeNoCache: nil, - conNukeCache: nil, + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + accDelNoCache: struct{}{}, + accDelCache: struct{}{}, + conNukeNoCache: struct{}{}, + conNukeCache: struct{}{}, + }, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), }, map[common.Hash]map[common.Hash][]byte{ conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, @@ -338,13 +339,14 @@ func TestDiskPartialMerge(t *testing.T) { assertStorage(conNukeCache, conNukeCacheSlot, conNukeCacheSlot[:]) // Modify or delete some accounts, flatten everything onto disk - if err := snaps.Update(diffRoot, baseRoot, map[common.Hash][]byte{ - accModNoCache: reverse(accModNoCache[:]), - accModCache: reverse(accModCache[:]), - accDelNoCache: nil, - accDelCache: nil, - conNukeNoCache: nil, - conNukeCache: nil, + if err := snaps.Update(diffRoot, baseRoot, map[common.Hash]struct{}{ + accDelNoCache: struct{}{}, + accDelCache: struct{}{}, + conNukeNoCache: struct{}{}, + conNukeCache: struct{}{}, + }, map[common.Hash][]byte{ + accModNoCache: reverse(accModNoCache[:]), + accModCache: reverse(accModCache[:]), }, map[common.Hash]map[common.Hash][]byte{ conModNoCache: {conModNoCacheSlot: reverse(conModNoCacheSlot[:])}, conModCache: {conModCacheSlot: reverse(conModCacheSlot[:])}, diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index dbfafd73df..832be10a40 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -28,18 +28,23 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" ) -// TestIteratorBasics tests some simple single-layer iteration -func TestIteratorBasics(t *testing.T) { +// TestAccountIteratorBasics tests some simple single-layer iteration +func TestAccountIteratorBasics(t *testing.T) { var ( - accounts = make(map[common.Hash][]byte) - storage = make(map[common.Hash]map[common.Hash][]byte) + destructs = make(map[common.Hash]struct{}) + accounts = make(map[common.Hash][]byte) + storage = make(map[common.Hash]map[common.Hash][]byte) ) // Fill up a parent for i := 0; i < 100; i++ { h := randomHash() data := randomAccount() + accounts[h] = data - if rand.Intn(20) < 10 { + if rand.Intn(4) == 0 { + destructs[h] = struct{}{} + } + if rand.Intn(2) == 0 { accStorage := make(map[common.Hash][]byte) value := make([]byte, 32) rand.Read(value) @@ -48,7 +53,7 @@ func TestIteratorBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, accounts, storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) it := parent.AccountIterator(common.Hash{}) verifyIterator(t, 100, it) } @@ -138,8 +143,8 @@ func verifyIterator(t *testing.T, expCount int, it AccountIterator) { } } -// TestIteratorTraversal tests some simple multi-layer iteration. -func TestIteratorTraversal(t *testing.T) { +// TestAccountIteratorTraversal tests some simple multi-layer iteration. +func TestAccountIteratorTraversal(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), @@ -152,13 +157,13 @@ func TestIteratorTraversal(t *testing.T) { }, } // Stack three diff layers on top with various overlaps - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Verify the single and multi-layer iterators @@ -173,9 +178,9 @@ func TestIteratorTraversal(t *testing.T) { verifyIterator(t, 7, it) } -// TestIteratorTraversalValues tests some multi-layer iteration, where we +// TestAccountIteratorTraversalValues tests some multi-layer iteration, where we // also expect the correct values to show up. -func TestIteratorTraversalValues(t *testing.T) { +func TestAccountIteratorTraversalValues(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), @@ -223,14 +228,14 @@ func TestIteratorTraversalValues(t *testing.T) { } } // Assemble a stack of snapshots from the account layers - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), a, nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), b, nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), c, nil) - snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), d, nil) - snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), e, nil) - snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), f, nil) - snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), g, nil) - snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), h, nil) + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, a, nil) + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, b, nil) + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, c, nil) + snaps.Update(common.HexToHash("0x05"), common.HexToHash("0x04"), nil, d, nil) + snaps.Update(common.HexToHash("0x06"), common.HexToHash("0x05"), nil, e, nil) + snaps.Update(common.HexToHash("0x07"), common.HexToHash("0x06"), nil, f, nil) + snaps.Update(common.HexToHash("0x08"), common.HexToHash("0x07"), nil, g, nil) + snaps.Update(common.HexToHash("0x09"), common.HexToHash("0x08"), nil, h, nil) it, _ := snaps.AccountIterator(common.HexToHash("0x09"), common.Hash{}) defer it.Release() @@ -249,7 +254,7 @@ func TestIteratorTraversalValues(t *testing.T) { } // This testcase is notorious, all layers contain the exact same 200 accounts. -func TestIteratorLargeTraversal(t *testing.T) { +func TestAccountIteratorLargeTraversal(t *testing.T) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) @@ -272,7 +277,7 @@ func TestIteratorLargeTraversal(t *testing.T) { }, } for i := 1; i < 128; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(200), nil) + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) } // Iterate the entire stack and ensure everything is hit only once head := snaps.Snapshot(common.HexToHash("0x80")) @@ -285,11 +290,11 @@ func TestIteratorLargeTraversal(t *testing.T) { verifyIterator(t, 200, it) } -// TestIteratorFlattening tests what happens when we +// TestAccountIteratorFlattening tests what happens when we // - have a live iterator on child C (parent C1 -> C2 .. CN) // - flattens C2 all the way into CN // - continues iterating -func TestIteratorFlattening(t *testing.T) { +func TestAccountIteratorFlattening(t *testing.T) { // Create an empty base layer and a snapshot tree out of it base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), @@ -302,13 +307,13 @@ func TestIteratorFlattening(t *testing.T) { }, } // Create a stack of diffs on top - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Create an iterator and flatten the data from underneath it @@ -321,7 +326,7 @@ func TestIteratorFlattening(t *testing.T) { //verifyIterator(t, 7, it) } -func TestIteratorSeek(t *testing.T) { +func TestAccountIteratorSeek(t *testing.T) { // Create a snapshot stack with some initial data base := &diskLayer{ diskdb: rawdb.NewMemoryDatabase(), @@ -333,13 +338,13 @@ func TestIteratorSeek(t *testing.T) { base.root: base, }, } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, randomAccountSet("0xaa", "0xee", "0xff", "0xf0"), nil) - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, randomAccountSet("0xbb", "0xdd", "0xf0"), nil) - snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, randomAccountSet("0xcc", "0xf0", "0xff"), nil) // Construct various iterators and ensure their tranversal is correct @@ -372,18 +377,18 @@ func TestIteratorSeek(t *testing.T) { verifyIterator(t, 0, it) // expected: nothing } -// BenchmarkIteratorTraversal is a bit a bit notorious -- all layers contain the +// BenchmarkAccountIteratorTraversal is a bit a bit notorious -- all layers contain the // exact same 200 accounts. That means that we need to process 2000 items, but // only spit out 200 values eventually. // // The value-fetching benchmark is easy on the binary iterator, since it never has to reach // down at any depth for retrieving the values -- all are on the toppmost layer // -// BenchmarkIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op -// BenchmarkIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op -// BenchmarkIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op -// BenchmarkIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op -func BenchmarkIteratorTraversal(b *testing.B) { +// BenchmarkAccountIteratorTraversal/binary_iterator_keys-6 2239 483674 ns/op +// BenchmarkAccountIteratorTraversal/binary_iterator_values-6 2403 501810 ns/op +// BenchmarkAccountIteratorTraversal/fast_iterator_keys-6 1923 677966 ns/op +// BenchmarkAccountIteratorTraversal/fast_iterator_values-6 1741 649967 ns/op +func BenchmarkAccountIteratorTraversal(b *testing.B) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) @@ -406,7 +411,7 @@ func BenchmarkIteratorTraversal(b *testing.B) { }, } for i := 1; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(200), nil) + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(200), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. @@ -469,17 +474,17 @@ func BenchmarkIteratorTraversal(b *testing.B) { }) } -// BenchmarkIteratorLargeBaselayer is a pretty realistic benchmark, where +// BenchmarkAccountIteratorLargeBaselayer is a pretty realistic benchmark, where // the baselayer is a lot larger than the upper layer. // // This is heavy on the binary iterator, which in most cases will have to // call recursively 100 times for the majority of the values // -// BenchmarkIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op -// BenchmarkIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op -// BenchmarkIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op -// BenchmarkIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op -func BenchmarkIteratorLargeBaselayer(b *testing.B) { +// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(keys)-6 514 1971999 ns/op +// BenchmarkAccountIteratorLargeBaselayer/binary_iterator_(values)-6 61 18997492 ns/op +// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(keys)-6 10000 114385 ns/op +// BenchmarkAccountIteratorLargeBaselayer/fast_iterator_(values)-6 4047 296823 ns/op +func BenchmarkAccountIteratorLargeBaselayer(b *testing.B) { // Create a custom account factory to recreate the same addresses makeAccounts := func(num int) map[common.Hash][]byte { accounts := make(map[common.Hash][]byte) @@ -501,9 +506,9 @@ func BenchmarkIteratorLargeBaselayer(b *testing.B) { base.root: base, }, } - snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), makeAccounts(2000), nil) + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, makeAccounts(2000), nil) for i := 2; i <= 100; i++ { - snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), makeAccounts(20), nil) + snaps.Update(common.HexToHash(fmt.Sprintf("0x%02x", i+1)), common.HexToHash(fmt.Sprintf("0x%02x", i)), nil, makeAccounts(20), nil) } // We call this once before the benchmark, so the creation of // sorted accountlists are not included in the results. @@ -590,7 +595,7 @@ func benchmarkAccountIteration(b *testing.B, iterator func(snap snapshot) Accoun } stack := snapshot(emptyLayer()) for _, layer := range layers { - stack = stack.Update(common.Hash{}, layer, nil) + stack = stack.Update(common.Hash{}, layer, nil, nil) } // Reset the timers and report all the stats it := iterator(stack) diff --git a/core/state/snapshot/journal.go b/core/state/snapshot/journal.go index c42a26d21d..66c7aee0ac 100644 --- a/core/state/snapshot/journal.go +++ b/core/state/snapshot/journal.go @@ -43,6 +43,11 @@ type journalGenerator struct { Storage uint64 } +// journalDestruct is an account deletion entry in a diffLayer's disk journal. +type journalDestruct struct { + Hash common.Hash +} + // journalAccount is an account entry in a diffLayer's disk journal. type journalAccount struct { Hash common.Hash @@ -139,6 +144,14 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { } return nil, fmt.Errorf("load diff root: %v", err) } + var destructs []journalDestruct + if err := r.Decode(&destructs); err != nil { + return nil, fmt.Errorf("load diff destructs: %v", err) + } + destructSet := make(map[common.Hash]struct{}) + for _, entry := range destructs { + destructSet[entry.Hash] = struct{}{} + } var accounts []journalAccount if err := r.Decode(&accounts); err != nil { return nil, fmt.Errorf("load diff accounts: %v", err) @@ -159,7 +172,7 @@ func loadDiffLayer(parent snapshot, r *rlp.Stream) (snapshot, error) { } storageData[entry.Hash] = slots } - return loadDiffLayer(newDiffLayer(parent, root, accountData, storageData), r) + return loadDiffLayer(newDiffLayer(parent, root, destructSet, accountData, storageData), r) } // Journal writes the persistent layer generator stats into a buffer to be stored @@ -218,6 +231,13 @@ func (dl *diffLayer) Journal(buffer *bytes.Buffer) (common.Hash, error) { if err := rlp.Encode(buffer, dl.root); err != nil { return common.Hash{}, err } + destructs := make([]journalDestruct, 0, len(dl.destructSet)) + for hash := range dl.destructSet { + destructs = append(destructs, journalDestruct{Hash: hash}) + } + if err := rlp.Encode(buffer, destructs); err != nil { + return common.Hash{}, err + } accounts := make([]journalAccount, 0, len(dl.accountData)) for hash, blob := range dl.accountData { accounts = append(accounts, journalAccount{Hash: hash, Blob: blob}) diff --git a/core/state/snapshot/snapshot.go b/core/state/snapshot/snapshot.go index d031dd2c1a..27a8c7f0bb 100644 --- a/core/state/snapshot/snapshot.go +++ b/core/state/snapshot/snapshot.go @@ -125,7 +125,7 @@ type snapshot interface { // the specified data items. // // Note, the maps are retained by the method to avoid copying everything. - Update(blockRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer + Update(blockRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) *diffLayer // Journal commits an entire diff hierarchy to disk into a single journal entry. // This is meant to be used during shutdown to persist the snapshot without @@ -222,7 +222,7 @@ func (t *Tree) Snapshot(blockRoot common.Hash) Snapshot { // Update adds a new snapshot into the tree, if that can be linked to an existing // old parent. It is disallowed to insert a disk layer (the origin of all). -func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { +func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, destructs map[common.Hash]struct{}, accounts map[common.Hash][]byte, storage map[common.Hash]map[common.Hash][]byte) error { // Reject noop updates to avoid self-loops in the snapshot tree. This is a // special case that can only happen for Clique networks where empty blocks // don't modify the state (0 block subsidy). @@ -237,7 +237,7 @@ func (t *Tree) Update(blockRoot common.Hash, parentRoot common.Hash, accounts ma if parent == nil { return fmt.Errorf("parent [%#x] snapshot missing", parentRoot) } - snap := parent.Update(blockRoot, accounts, storage) + snap := parent.Update(blockRoot, destructs, accounts, storage) // Save the new snapshot for later t.lock.Lock() @@ -425,40 +425,43 @@ func diffToDisk(bottom *diffLayer) *diskLayer { base.stale = true base.lock.Unlock() - // Push all the accounts into the database + // Destroy all the destructed accounts from the database + for hash := range bottom.destructSet { + // Skip any account not covered yet by the snapshot + if base.genMarker != nil && bytes.Compare(hash[:], base.genMarker) > 0 { + continue + } + // Remove all storage slots + rawdb.DeleteAccountSnapshot(batch, hash) + base.cache.Set(hash[:], nil) + + it := rawdb.IterateStorageSnapshots(base.diskdb, hash) + for it.Next() { + if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator + batch.Delete(key) + base.cache.Del(key[1:]) + + snapshotFlushStorageItemMeter.Mark(1) + } + } + it.Release() + } + // Push all updated accounts into the database for hash, data := range bottom.accountData { // Skip any account not covered yet by the snapshot if base.genMarker != nil && bytes.Compare(hash[:], base.genMarker) > 0 { continue } - if len(data) > 0 { - // Account was updated, push to disk - rawdb.WriteAccountSnapshot(batch, hash, data) - base.cache.Set(hash[:], data) - snapshotCleanAccountWriteMeter.Mark(int64(len(data))) + // Push the account to disk + rawdb.WriteAccountSnapshot(batch, hash, data) + base.cache.Set(hash[:], data) + snapshotCleanAccountWriteMeter.Mark(int64(len(data))) - if batch.ValueSize() > ethdb.IdealBatchSize { - if err := batch.Write(); err != nil { - log.Crit("Failed to write account snapshot", "err", err) - } - batch.Reset() + if batch.ValueSize() > ethdb.IdealBatchSize { + if err := batch.Write(); err != nil { + log.Crit("Failed to write account snapshot", "err", err) } - } else { - // Account was deleted, remove all storage slots too - rawdb.DeleteAccountSnapshot(batch, hash) - base.cache.Set(hash[:], nil) - - it := rawdb.IterateStorageSnapshots(base.diskdb, hash) - for it.Next() { - if key := it.Key(); len(key) == 65 { // TODO(karalabe): Yuck, we should move this into the iterator - batch.Delete(key) - base.cache.Del(key[1:]) - - snapshotFlushStorageItemMeter.Mark(1) - snapshotFlushStorageSizeMeter.Mark(int64(len(data))) - } - } - it.Release() + batch.Reset() } snapshotFlushAccountItemMeter.Mark(1) snapshotFlushAccountSizeMeter.Mark(int64(len(data))) diff --git a/core/state/snapshot/snapshot_test.go b/core/state/snapshot/snapshot_test.go index 2b14828178..9109238412 100644 --- a/core/state/snapshot/snapshot_test.go +++ b/core/state/snapshot/snapshot_test.go @@ -81,7 +81,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 2 { @@ -91,7 +91,7 @@ func TestDiskLayerExternalInvalidationFullFlatten(t *testing.T) { if err := snaps.Cap(common.HexToHash("0x02"), 0); err != nil { t.Fatalf("failed to merge diff layer onto disk: %v", err) } - // Since the base layer was modified, ensure that data retrievald on the external reference fail + // Since the base layer was modified, ensure that data retrieval on the external reference fail if acc, err := ref.Account(common.HexToHash("0x01")); err != ErrSnapshotStale { t.Errorf("stale reference returned account: %#x (err: %v)", acc, err) } @@ -125,10 +125,10 @@ func TestDiskLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 3 { @@ -173,10 +173,10 @@ func TestDiffLayerExternalInvalidationFullFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 3 { @@ -220,13 +220,13 @@ func TestDiffLayerExternalInvalidationPartialFlatten(t *testing.T) { accounts := map[common.Hash][]byte{ common.HexToHash("0xa1"): randomAccount(), } - if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } - if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), accounts, nil); err != nil { + if err := snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), nil, accounts, nil); err != nil { t.Fatalf("failed to create a diff layer: %v", err) } if n := len(snaps.layers); n != 4 { @@ -280,12 +280,12 @@ func TestPostCapBasicDataAccess(t *testing.T) { }, } // The lowest difflayer - snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), setAccount("0xa1"), nil) - snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), setAccount("0xa2"), nil) - snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), setAccount("0xb2"), nil) + snaps.Update(common.HexToHash("0xa1"), common.HexToHash("0x01"), nil, setAccount("0xa1"), nil) + snaps.Update(common.HexToHash("0xa2"), common.HexToHash("0xa1"), nil, setAccount("0xa2"), nil) + snaps.Update(common.HexToHash("0xb2"), common.HexToHash("0xa1"), nil, setAccount("0xb2"), nil) - snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), setAccount("0xa3"), nil) - snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), setAccount("0xb3"), nil) + snaps.Update(common.HexToHash("0xa3"), common.HexToHash("0xa2"), nil, setAccount("0xa3"), nil) + snaps.Update(common.HexToHash("0xb3"), common.HexToHash("0xb2"), nil, setAccount("0xb3"), nil) // checkExist verifies if an account exiss in a snapshot checkExist := func(layer *diffLayer, key string) error { diff --git a/core/state/statedb.go b/core/state/statedb.go index d4a91ee715..55e9752fa9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -67,10 +67,11 @@ type StateDB struct { db Database trie Trie - snaps *snapshot.Tree - snap snapshot.Snapshot - snapAccounts map[common.Hash][]byte - snapStorage map[common.Hash]map[common.Hash][]byte + snaps *snapshot.Tree + snap snapshot.Snapshot + snapDestructs map[common.Hash]struct{} + snapAccounts map[common.Hash][]byte + snapStorage map[common.Hash]map[common.Hash][]byte // This map holds 'live' objects, which will get modified while processing a state transition. stateObjects map[common.Address]*stateObject @@ -133,6 +134,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) } if sdb.snaps != nil { if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil { + sdb.snapDestructs = make(map[common.Hash]struct{}) sdb.snapAccounts = make(map[common.Hash][]byte) sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte) } @@ -171,8 +173,9 @@ func (s *StateDB) Reset(root common.Hash) error { s.clearJournalAndRefund() if s.snaps != nil { - s.snapAccounts, s.snapStorage = nil, nil + s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil if s.snap = s.snaps.Snapshot(root); s.snap != nil { + s.snapDestructs = make(map[common.Hash]struct{}) s.snapAccounts = make(map[common.Hash][]byte) s.snapStorage = make(map[common.Hash]map[common.Hash][]byte) } @@ -463,15 +466,6 @@ func (s *StateDB) updateStateObject(obj *stateObject) { panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) } s.setError(s.trie.TryUpdate(addr[:], data)) - - // If state snapshotting is active, cache the data til commit - if s.snap != nil { - // If the account is an empty resurrection, unmark the storage nil-ness - if storage, ok := s.snapStorage[obj.addrHash]; storage == nil && ok { - delete(s.snapStorage, obj.addrHash) - } - s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) - } } // deleteStateObject removes the given object from the state trie. @@ -483,12 +477,6 @@ func (s *StateDB) deleteStateObject(obj *stateObject) { // Delete the account from the trie addr := obj.Address() s.setError(s.trie.TryDelete(addr[:])) - - // If state snapshotting is active, cache the data til commit - if s.snap != nil { - s.snapAccounts[obj.addrHash] = nil // We need to maintain account deletions explicitly - s.snapStorage[obj.addrHash] = nil // We need to maintain storage deletions explicitly - } } // getStateObject retrieves a state object given by the address, returning nil if @@ -737,8 +725,23 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } if obj.suicided || (deleteEmptyObjects && obj.empty()) { obj.deleted = true + + // If state snapshotting is active, also mark the destruction there. + // Note, we can't do this only at the end of a block because multiple + // transactions within the same block might self destruct and then + // ressurrect an account and the snapshotter needs both events. + if s.snap != nil { + s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) + delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect) + delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect) + } } else { obj.finalise() + + // If state snapshotting is active, cache the data til commit + if s.snap != nil { + s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + } } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} @@ -842,7 +845,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { } // Only update if there's a state transition (skip empty Clique blocks) if parent := s.snap.Root(); parent != root { - if err := s.snaps.Update(root, parent, s.snapAccounts, s.snapStorage); err != nil { + if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil { log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err) } if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index ba0ba01b8f..71ef0724a4 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -70,7 +70,7 @@ const ( SHR SAR - SHA3 = 0x20 + SHA3 OpCode = 0x20 ) // 0x30 range - closure state. @@ -101,8 +101,8 @@ const ( NUMBER DIFFICULTY GASLIMIT - CHAINID = 0x46 - SELFBALANCE = 0x47 + CHAINID OpCode = 0x46 + SELFBALANCE OpCode = 0x47 ) // 0x50 range - 'storage' and execution. @@ -213,10 +213,9 @@ const ( RETURN DELEGATECALL CREATE2 - STATICCALL = 0xfa - - REVERT = 0xfd - SELFDESTRUCT = 0xff + STATICCALL OpCode = 0xfa + REVERT OpCode = 0xfd + SELFDESTRUCT OpCode = 0xff ) // Since the opcodes aren't all in order we can't use a regular slice. From dcb22a9f99b19bb6b6d27cdba754ad740dc426c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 3 Mar 2020 16:55:06 +0200 Subject: [PATCH 023/103] core/state: fix account root hash update point --- core/state/statedb.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 55e9752fa9..ff2c6dac26 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -466,6 +466,14 @@ func (s *StateDB) updateStateObject(obj *stateObject) { panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err)) } s.setError(s.trie.TryUpdate(addr[:], data)) + + // If state snapshotting is active, cache the data til commit. Note, this + // update mechanism is not symmetric to the deletion, because whereas it is + // enough to track account updates at commit time, deletions need tracking + // at transaction boundary level to ensure we capture state clearing. + if s.snap != nil { + s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) + } } // deleteStateObject removes the given object from the state trie. @@ -729,7 +737,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // If state snapshotting is active, also mark the destruction there. // Note, we can't do this only at the end of a block because multiple // transactions within the same block might self destruct and then - // ressurrect an account and the snapshotter needs both events. + // ressurrect an account; but the snapshotter needs both events. if s.snap != nil { s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely) delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect) @@ -737,11 +745,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } } else { obj.finalise() - - // If state snapshotting is active, cache the data til commit - if s.snap != nil { - s.snapAccounts[obj.addrHash] = snapshot.AccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash) - } } s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} From 328de180a7172d2fc2894be11ad10548ef9d27e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 4 Mar 2020 10:19:53 +0200 Subject: [PATCH 024/103] core/state: fix resurrection state clearing and access --- core/state/state_object.go | 9 +++++++++ core/state/statedb.go | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index 26e0b08f59..0833f2b0a8 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -204,6 +204,15 @@ func (s *stateObject) GetCommittedState(db Database, key common.Hash) common.Has if metrics.EnabledExpensive { defer func(start time.Time) { s.db.SnapshotStorageReads += time.Since(start) }(time.Now()) } + // If the object was destructed in *this* block (and potentially resurrected), + // the storage has been cleared out, and we should *not* consult the previous + // snapshot about any storage values. The only possible alternatives are: + // 1) resurrect happened, and new slot values were set -- those should + // have been handles via pendingStorage above. + // 2) we don't have new values, and can deliver empty response back + if _, destructed := s.db.snapDestructs[s.addrHash]; destructed { + return common.Hash{} + } enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key[:])) } // If snapshot unavailable or reading from it failed, load from the database diff --git a/core/state/statedb.go b/core/state/statedb.go index ff2c6dac26..038005685b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -595,6 +595,9 @@ func (s *StateDB) CreateAccount(addr common.Address) { if prev != nil { newObj.setBalance(prev.data.Balance) } + if s.snap != nil && prev != nil { + s.snapDestructs[prev.addrHash] = struct{}{} + } } func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { @@ -855,7 +858,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err) } } - s.snap, s.snapAccounts, s.snapStorage = nil, nil, nil + s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil } return root, err } From eff7cfbb03b74816513d415c0ecfe93ae83f4096 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Mar 2020 13:38:55 +0100 Subject: [PATCH 025/103] core/state/snapshot: handle deleted accounts in fast iterator --- core/state/snapshot/iterator_fast.go | 34 +++++++++++++----- core/state/snapshot/iterator_test.go | 53 +++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/core/state/snapshot/iterator_fast.go b/core/state/snapshot/iterator_fast.go index ef0212ac29..99734ec912 100644 --- a/core/state/snapshot/iterator_fast.go +++ b/core/state/snapshot/iterator_fast.go @@ -164,17 +164,35 @@ func (fi *fastAccountIterator) Next() bool { fi.curAccount = fi.iterators[0].it.Account() if innerErr := fi.iterators[0].it.Error(); innerErr != nil { fi.fail = innerErr + return false } - return fi.Error() == nil + if fi.curAccount != nil { + return true + } + // Implicit else: we've hit a nil-account, and need to fall through to the + // loop below to land on something non-nil } - if !fi.next(0) { - return false + // If an account is deleted in one of the layers, the key will still be there, + // but the actual value will be nil. However, the iterator should not + // export nil-values (but instead simply omit the key), so we need to loop + // here until we either + // - get a non-nil value, + // - hit an error, + // - or exhaust the iterator + for { + if !fi.next(0) { + return false // exhausted + } + fi.curAccount = fi.iterators[0].it.Account() + if innerErr := fi.iterators[0].it.Error(); innerErr != nil { + fi.fail = innerErr + return false // error + } + if fi.curAccount != nil { + break // non-nil value found + } } - fi.curAccount = fi.iterators[0].it.Account() - if innerErr := fi.iterators[0].it.Error(); innerErr != nil { - fi.fail = innerErr - } - return fi.Error() == nil + return true } // next handles the next operation internally and should be invoked when we know diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 832be10a40..935fafc2f5 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -130,9 +130,13 @@ func verifyIterator(t *testing.T, expCount int, it AccountIterator) { last = common.Hash{} ) for it.Next() { - if hash := it.Hash(); bytes.Compare(last[:], hash[:]) >= 0 { + hash := it.Hash() + if bytes.Compare(last[:], hash[:]) >= 0 { t.Errorf("wrong order: %x >= %x", last, hash) } + if it.Account() == nil { + t.Errorf("iterator returned nil-value for hash %x", hash) + } count++ } if count != expCount { @@ -377,6 +381,53 @@ func TestAccountIteratorSeek(t *testing.T) { verifyIterator(t, 0, it) // expected: nothing } +// TestIteratorDeletions tests that the iterator behaves correct when there are +// deleted accounts (where the Account() value is nil). The iterator +// should not output any accounts or nil-values for those cases. +func TestIteratorDeletions(t *testing.T) { + // Create an empty base layer and a snapshot tree out of it + base := &diskLayer{ + diskdb: rawdb.NewMemoryDatabase(), + root: common.HexToHash("0x01"), + cache: fastcache.New(1024 * 500), + } + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + base.root: base, + }, + } + // Stack three diff layers on top with various overlaps + snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), + randomAccountSet("0x11", "0x22", "0x33"), nil) + + set := randomAccountSet("0x11", "0x22", "0x33") + deleted := common.HexToHash("0x22") + set[deleted] = nil + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), set, nil) + + snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), + randomAccountSet("0x33", "0x44", "0x55"), nil) + + // The output should be 11,33,44,55 + it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + // Do a quick check + verifyIterator(t, 4, it) + it.Release() + + // And a more detailed verification that we indeed do not see '0x22' + it, _ = snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) + defer it.Release() + for it.Next() { + hash := it.Hash() + if it.Account() == nil { + t.Errorf("iterator returned nil-value for hash %x", hash) + } + if hash == deleted { + t.Errorf("expected deleted elem %x to not be returned by iterator", deleted) + } + } +} + // BenchmarkAccountIteratorTraversal is a bit a bit notorious -- all layers contain the // exact same 200 accounts. That means that we need to process 2000 items, but // only spit out 200 values eventually. From bc5d742c664245879ee80ecd968009b56f3e758c Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 4 Mar 2020 13:39:27 +0100 Subject: [PATCH 026/103] core: more blockchain tests --- core/blockchain_test.go | 207 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 201 insertions(+), 6 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 5e2a21023a..b6b497ecee 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2376,11 +2376,9 @@ func TestDeleteRecreateSlots(t *testing.T) { engine = ethash.NewFaker() db = rawdb.NewMemoryDatabase() // A sender who makes transactions, has some funds - key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - address = crypto.PubkeyToAddress(key.PublicKey) - funds = big.NewInt(1000000000) - - aa = common.HexToAddress("0x7217d81b76bdd8707601e959454e3d776aee5f43") + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) @@ -2403,7 +2401,7 @@ func TestDeleteRecreateSlots(t *testing.T) { byte(vm.PUSH1), 0x4, // location byte(vm.SSTORE), // Set slot[4] = 1 // Slots are set, now return the code - byte(vm.PUSH2), 0x88, 0xff, // Push code on stack + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack byte(vm.PUSH1), 0x0, // memory start on stack byte(vm.MSTORE), // Code is now in memory. @@ -2428,6 +2426,10 @@ func TestDeleteRecreateSlots(t *testing.T) { byte(vm.CREATE2), }...) + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + gspec := &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{ @@ -2563,3 +2565,196 @@ func TestDeleteRecreateAccount(t *testing.T) { t.Errorf("got %x exp %x", got, exp) } } + +// TestDeleteRecreateSlotsAcrossManyBlocks tests multiple state-transition that contains both deletion +// and recreation of contract state. +// Contract A exists, has slots 1 and 2 set +// Tx 1: Selfdestruct A +// Tx 2: Re-create A, set slots 3 and 4 +// Expected outcome is that _all_ slots are cleared from A, due to the selfdestruct, +// and then the new slots exist +func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { + var ( + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + aaStorage = make(map[common.Hash]common.Hash) // Initial storage in AA + aaCode = []byte{byte(vm.PC), byte(vm.SELFDESTRUCT)} // Code for AA (simple selfdestruct) + ) + // Populate two slots + aaStorage[common.HexToHash("01")] = common.HexToHash("01") + aaStorage[common.HexToHash("02")] = common.HexToHash("02") + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. Set slots 3=blocknum+1, 4=4, + // 2. Return aaCode + + initCode := []byte{ + byte(vm.PUSH1), 0x1, // + byte(vm.NUMBER), // value = number + 1 + byte(vm.ADD), // + byte(vm.PUSH1), 0x3, // location + byte(vm.SSTORE), // Set slot[3] = number + 1 + byte(vm.PUSH1), 0x4, // value + byte(vm.PUSH1), 0x4, // location + byte(vm.SSTORE), // Set slot[4] = 4 + // Slots are set, now return the code + byte(vm.PUSH2), byte(vm.PC), byte(vm.SELFDESTRUCT), // Push code on stack + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + // Code is now in memory. + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), byte(32 - 2), // offset + byte(vm.RETURN), + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address 0xAAAAA selfdestructs if called + aa: { + // Code needs to just selfdestruct + Code: aaCode, + Nonce: 1, + Balance: big.NewInt(0), + Storage: aaStorage, + }, + // The contract BB recreates AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + genesis := gspec.MustCommit(db) + var nonce uint64 + + type expectation struct { + exist bool + blocknum int + values map[int]int + } + var current = &expectation{ + exist: true, // exists in genesis + blocknum: 0, + values: map[int]int{1: 1, 2: 2}, + } + var expectations []*expectation + var newDestruct = func(e *expectation) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, aa, + big.NewInt(0), 50000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + nonce++ + if e.exist { + e.exist = false + e.values = nil + } + t.Logf("block %d; adding destruct\n", e.blocknum) + return tx + } + var newResurrect = func(e *expectation) *types.Transaction { + tx, _ := types.SignTx(types.NewTransaction(nonce, bb, + big.NewInt(0), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + nonce++ + if !e.exist { + e.exist = true + e.values = map[int]int{3: e.blocknum + 1, 4: 4} + } + t.Logf("block %d; adding resurrect\n", e.blocknum) + return tx + } + + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 150, func(i int, b *BlockGen) { + var exp = new(expectation) + exp.blocknum = i + 1 + exp.values = make(map[int]int) + for k, v := range current.values { + exp.values[k] = v + } + exp.exist = current.exist + + b.SetCoinbase(common.Address{1}) + if i%2 == 0 { + b.AddTx(newDestruct(exp)) + } + if i%3 == 0 { + b.AddTx(newResurrect(exp)) + } + if i%5 == 0 { + b.AddTx(newDestruct(exp)) + } + if i%7 == 0 { + b.AddTx(newResurrect(exp)) + } + expectations = append(expectations, exp) + current = exp + }) + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + //Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + var asHash = func(num int) common.Hash { + return common.BytesToHash([]byte{byte(num)}) + } + for i, block := range blocks { + blockNum := i + 1 + if n, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", n, err) + } + statedb, _ := chain.State() + // If all is correct, then slot 1 and 2 are zero + if got, exp := statedb.GetState(aa, common.HexToHash("01")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + if got, exp := statedb.GetState(aa, common.HexToHash("02")), (common.Hash{}); got != exp { + t.Errorf("block %d, got %x exp %x", blockNum, got, exp) + } + exp := expectations[i] + if exp.exist { + if !statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to exist, it did not", blockNum, aa) + } + for slot, val := range exp.values { + if gotValue, expValue := statedb.GetState(aa, asHash(slot)), asHash(val); gotValue != expValue { + t.Fatalf("block %d, slot %d, got %x exp %x", blockNum, slot, gotValue, expValue) + } + } + } else { + if statedb.Exist(aa) { + t.Fatalf("block %d, expected %v to not exist, it did", blockNum, aa) + } + } + } +} From fab0ee3bfad9c685064dc12a210479a196e1cb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 4 Mar 2020 15:06:04 +0200 Subject: [PATCH 027/103] core/state/snapshot: fix various iteration issues due to destruct set --- core/state/snapshot/difflayer.go | 8 +-- core/state/snapshot/difflayer_test.go | 75 ++++++++++++++++++--------- core/state/snapshot/iterator.go | 9 ++-- core/state/snapshot/iterator_test.go | 14 ++--- 4 files changed, 67 insertions(+), 39 deletions(-) diff --git a/core/state/snapshot/difflayer.go b/core/state/snapshot/difflayer.go index 0915fb6bc6..86ca5c8ba0 100644 --- a/core/state/snapshot/difflayer.go +++ b/core/state/snapshot/difflayer.go @@ -493,12 +493,14 @@ func (dl *diffLayer) AccountList() []common.Hash { defer dl.lock.Unlock() dl.accountList = make([]common.Hash, 0, len(dl.destructSet)+len(dl.accountData)) - for hash := range dl.destructSet { - dl.accountList = append(dl.accountList, hash) - } for hash := range dl.accountData { dl.accountList = append(dl.accountList, hash) } + for hash := range dl.destructSet { + if _, ok := dl.accountData[hash]; !ok { + dl.accountList = append(dl.accountList, hash) + } + } sort.Sort(hashes(dl.accountList)) return dl.accountList } diff --git a/core/state/snapshot/difflayer_test.go b/core/state/snapshot/difflayer_test.go index 61d2ed9c0b..329e0eb8e2 100644 --- a/core/state/snapshot/difflayer_test.go +++ b/core/state/snapshot/difflayer_test.go @@ -27,6 +27,33 @@ import ( "github.com/ethereum/go-ethereum/ethdb/memorydb" ) +func copyDestructs(destructs map[common.Hash]struct{}) map[common.Hash]struct{} { + copy := make(map[common.Hash]struct{}) + for hash := range destructs { + copy[hash] = struct{}{} + } + return copy +} + +func copyAccounts(accounts map[common.Hash][]byte) map[common.Hash][]byte { + copy := make(map[common.Hash][]byte) + for hash, blob := range accounts { + copy[hash] = blob + } + return copy +} + +func copyStorage(storage map[common.Hash]map[common.Hash][]byte) map[common.Hash]map[common.Hash][]byte { + copy := make(map[common.Hash]map[common.Hash][]byte) + for accHash, slots := range storage { + copy[accHash] = make(map[common.Hash][]byte) + for slotHash, blob := range slots { + copy[accHash][slotHash] = blob + } + } + return copy +} + // TestMergeBasics tests some simple merges func TestMergeBasics(t *testing.T) { var ( @@ -52,41 +79,41 @@ func TestMergeBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) - child := newDiffLayer(parent, common.Hash{}, destructs, accounts, storage) - child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) - child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) - child = newDiffLayer(child, common.Hash{}, destructs, accounts, storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child := newDiffLayer(parent, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) + child = newDiffLayer(child, common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) // And flatten merged := (child.flatten()).(*diffLayer) { // Check account lists - if got, exp := len(merged.accountList), 0; got != exp { - t.Errorf("accountList wrong, got %v exp %v", got, exp) + if have, want := len(merged.accountList), 0; have != want { + t.Errorf("accountList wrong: have %v, want %v", have, want) } - if got, exp := len(merged.AccountList()), len(accounts); got != exp { - t.Errorf("AccountList() wrong, got %v exp %v", got, exp) + if have, want := len(merged.AccountList()), len(accounts); have != want { + t.Errorf("AccountList() wrong: have %v, want %v", have, want) } - if got, exp := len(merged.accountList), len(accounts); got != exp { - t.Errorf("accountList [2] wrong, got %v exp %v", got, exp) + if have, want := len(merged.accountList), len(accounts); have != want { + t.Errorf("accountList [2] wrong: have %v, want %v", have, want) } } { // Check account drops - if got, exp := len(merged.destructSet), len(destructs); got != exp { - t.Errorf("accountDrop wrong, got %v exp %v", got, exp) + if have, want := len(merged.destructSet), len(destructs); have != want { + t.Errorf("accountDrop wrong: have %v, want %v", have, want) } } { // Check storage lists i := 0 for aHash, sMap := range storage { - if got, exp := len(merged.storageList), i; got != exp { - t.Errorf("[1] storageList wrong, got %v exp %v", got, exp) + if have, want := len(merged.storageList), i; have != want { + t.Errorf("[1] storageList wrong: have %v, want %v", have, want) } - if got, exp := len(merged.StorageList(aHash)), len(sMap); got != exp { - t.Errorf("[2] StorageList() wrong, got %v exp %v", got, exp) + if have, want := len(merged.StorageList(aHash)), len(sMap); have != want { + t.Errorf("[2] StorageList() wrong: have %v, want %v", have, want) } - if got, exp := len(merged.storageList[aHash]), len(sMap); got != exp { - t.Errorf("storageList wrong, got %v exp %v", got, exp) + if have, want := len(merged.storageList[aHash]), len(sMap); have != want { + t.Errorf("storageList wrong: have %v, want %v", have, want) } i++ } @@ -160,8 +187,8 @@ func TestMergeDelete(t *testing.T) { } // If we add more granular metering of memory, we can enable this again, // but it's not implemented for now - //if got, exp := merged.memory, child.memory; got != exp { - // t.Errorf("mem wrong, got %d, exp %d", got, exp) + //if have, want := merged.memory, child.memory; have != want { + // t.Errorf("mem wrong: have %d, want %d", have, want) //} } @@ -197,9 +224,9 @@ func TestInsertAndMerge(t *testing.T) { // And flatten merged := (child.flatten()).(*diffLayer) { // Check that slot value is present - got, _ := merged.Storage(acc, slot) - if exp := []byte{0x01}; !bytes.Equal(got, exp) { - t.Errorf("merged slot value wrong, got %x, exp %x", got, exp) + have, _ := merged.Storage(acc, slot) + if want := []byte{0x01}; !bytes.Equal(have, want) { + t.Errorf("merged slot value wrong: have %x, want %x", have, want) } } } diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index 774e9f5542..f0b1eafa99 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -60,12 +60,6 @@ type diffAccountIterator struct { // hash as long as the iterator is not touched any more. curHash common.Hash - // curAccount is the current value the iterator is positioned on. The field - // is explicitly tracked since the referenced diff layer might go stale after - // the iterator was positioned and we don't want to fail accessing the old - // value as long as the iterator is not touched any more. - //curAccount []byte - layer *diffLayer // Live layer to retrieve values from keys []common.Hash // Keys left in the layer to iterate fail error // Any failures encountered (stale) @@ -130,6 +124,9 @@ func (it *diffAccountIterator) Account() []byte { it.layer.lock.RLock() blob, ok := it.layer.accountData[it.curHash] if !ok { + if _, ok := it.layer.destructSet[it.curHash]; ok { + return nil + } panic(fmt.Sprintf("iterator referenced non-existent account: %x", it.curHash)) } it.layer.lock.RUnlock() diff --git a/core/state/snapshot/iterator_test.go b/core/state/snapshot/iterator_test.go index 935fafc2f5..5468a9a589 100644 --- a/core/state/snapshot/iterator_test.go +++ b/core/state/snapshot/iterator_test.go @@ -53,7 +53,7 @@ func TestAccountIteratorBasics(t *testing.T) { } } // Add some (identical) layers on top - parent := newDiffLayer(emptyLayer(), common.Hash{}, destructs, accounts, storage) + parent := newDiffLayer(emptyLayer(), common.Hash{}, copyDestructs(destructs), copyAccounts(accounts), copyStorage(storage)) it := parent.AccountIterator(common.Hash{}) verifyIterator(t, 100, it) } @@ -398,15 +398,17 @@ func TestIteratorDeletions(t *testing.T) { } // Stack three diff layers on top with various overlaps snaps.Update(common.HexToHash("0x02"), common.HexToHash("0x01"), - randomAccountSet("0x11", "0x22", "0x33"), nil) + nil, randomAccountSet("0x11", "0x22", "0x33"), nil) - set := randomAccountSet("0x11", "0x22", "0x33") deleted := common.HexToHash("0x22") - set[deleted] = nil - snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), set, nil) + destructed := map[common.Hash]struct{}{ + deleted: struct{}{}, + } + snaps.Update(common.HexToHash("0x03"), common.HexToHash("0x02"), + destructed, randomAccountSet("0x11", "0x33"), nil) snaps.Update(common.HexToHash("0x04"), common.HexToHash("0x03"), - randomAccountSet("0x33", "0x44", "0x55"), nil) + nil, randomAccountSet("0x33", "0x44", "0x55"), nil) // The output should be 11,33,44,55 it, _ := snaps.AccountIterator(common.HexToHash("0x04"), common.Hash{}) From 8d7aa9078f8a94c2c10b1d11e04242df0ea91e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Mon, 16 Mar 2020 13:44:58 +0200 Subject: [PATCH 028/103] params: begin v1.9.13 release cycle --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 97df554671..9c1374c224 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 12 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 13 // Patch version component of the current release + VersionMeta = "unstable" // Version metadata to append to the version string ) // Version holds the textual version string. From efd92d81a9655a5aed17cc433d1f8d2c9dcce25c Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 18 Mar 2020 17:18:14 +0800 Subject: [PATCH 029/103] cmd/checkpoint-admin: add some documentation (#20697) --- cmd/checkpoint-admin/README.md | 103 +++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 cmd/checkpoint-admin/README.md diff --git a/cmd/checkpoint-admin/README.md b/cmd/checkpoint-admin/README.md new file mode 100644 index 0000000000..43e3785ec2 --- /dev/null +++ b/cmd/checkpoint-admin/README.md @@ -0,0 +1,103 @@ +## Checkpoint-admin + +Checkpoint-admin is a tool for updating checkpoint oracle status. It provides a series of functions including deploying checkpoint oracle contract, signing for new checkpoints, and updating checkpoints in the checkpoint oracle contract. + +### Checkpoint + +In the LES protocol, there is an important concept called checkpoint. In simple terms, whenever a certain number of blocks are generated on the blockchain, a new checkpoint is generated which contains some important information such as + +* Block hash at checkpoint +* Canonical hash trie root at checkpoint +* Bloom trie root at checkpoint + +*For a more detailed introduction to checkpoint, please see the LES [spec](https://github.com/ethereum/devp2p/blob/master/caps/les.md).* + +Using this information, light clients can skip all historical block headers when synchronizing data and start synchronization from this checkpoint. Therefore, as long as the light client can obtain some latest and correct checkpoints, the amount of data and time for synchronization will be greatly reduced. + +However, from a security perspective, the most critical step in a synchronization algorithm based on checkpoints is to determine whether the checkpoint used by the light client is correct. Otherwise, all blockchain data synchronized based on this checkpoint may be wrong. For this we provide two different ways to ensure the correctness of the checkpoint used by the light client. + +#### Hardcoded checkpoint + +There are several hardcoded checkpoints in the [source code](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L38) of the go-ethereum project. These checkpoints are updated by go-ethereum developers when new versions of software are released. Because light client users trust Geth developers to some extent, hardcoded checkpoints in the code can also be considered correct. + +#### Checkpoint oracle + +Hardcoded checkpoints can solve the problem of verifying the correctness of checkpoints (although this is a more centralized solution). But the pain point of this solution is that developers can only update checkpoints when a new version of software is released. In addition, light client users usually do not keep the Geth version they use always up to date. So hardcoded checkpoints used by users are generally stale. Therefore, it still needs to download a large amount of blockchain data during synchronization. + +Checkpoint oracle is a more flexible solution. In simple terms, this is a smart contract that is deployed on the blockchain. The smart contract records several designated trusted signers. Whenever enough trusted signers have issued their signatures for the same checkpoint, it can be considered that the checkpoint has been authenticated by the signers. Checkpoints authenticated by trusted signers can be considered correct. + +So this way, even without updating the software version, as long as the trusted signers regularly update the checkpoint in oracle on time, the light client can always use the latest and verified checkpoint for data synchronization. + +### Usage + +Checkpoint-admin is a command line tool designed for checkpoint oracle. Users can easily deploy contracts and update checkpoints through this tool. + +#### Install + +```shell +go get github.com/ethereum/go-ethereum/cmd/checkpoint-admin +``` + +#### Deploy + +Deploy checkpoint oracle contract. `--signers` indicates the specified trusted signer, and `--threshold` indicates the minimum number of signatures required by trusted signers to update a checkpoint. + +```shell +checkpoint-admin deploy --rpc --clef --signer --signers --threshold 1 +``` + +It is worth noting that checkpoint-admin only supports clef as a signer for transactions and plain text(checkpoint). For more clef usage, please see the clef [tutorial](https://geth.ethereum.org/docs/clef/tutorial) . + +#### Sign + +Checkpoint-admin provides two different modes of signing. You can automatically obtain the current stable checkpoint and sign it interactively, and you can also use the information provided by the command line flags to sign checkpoint offline. + +**Interactive mode** + +```shell +checkpoint-admin sign --clef --signer --rpc +``` + +*It is worth noting that the connected Geth node can be a fullnode or a light client. If it is fullnode, you must enable the LES protocol. E.G. add `--light.serv 50` to the startup command line flags*. + +**Offline mode** + +```shell +checkpoint-admin sign --clef --signer --index --hash --oracle +``` + +*CHECKPOINT_HASH is obtained based on this [calculation method](https://github.com/ethereum/go-ethereum/blob/master/params/config.go#L251).* + +#### Publish + +Collect enough signatures from different trusted signers for the same checkpoint and submit them to oracle to update the "authenticated" checkpoint in the contract. + +```shell +checkpoint-admin publish --clef --rpc --signer --index --signatures +``` + +#### Status query + +Check the latest status of checkpoint oracle. + +```shell +checkpoint-admin status --rpc +``` + +### Enable checkpoint oracle in your private network + +Currently, only the Ethereum mainnet and the default supported test networks (ropsten, rinkeby, goerli) activate this feature. If you want to activate this feature in your private network, you can overwrite the relevant checkpoint oracle settings through the configuration file after deploying the oracle contract. + +* Get your node configuration file `geth dumpconfig OTHER_COMMAND_LINE_OPTIONS > config.toml` +* Edit the configuration file and add the following information + +```toml +[Eth.CheckpointOracle] +Address = CHECKPOINT_ORACLE_ADDRESS +Signers = [TRUSTED_SIGNER_1, ..., TRUSTED_SIGNER_N] +Threshold = THRESHOLD +``` + +* Start geth with the modified configuration file + +*In the private network, all fullnodes and light clients need to be started using the same checkpoint oracle settings.* \ No newline at end of file From 5dd0cd12ec05bd2ced2ab1317168b0b991344fc2 Mon Sep 17 00:00:00 2001 From: Alex Willmer Date: Wed, 18 Mar 2020 09:52:07 +0000 Subject: [PATCH 030/103] go.mod: update duktape to fix sprintf warnings (#20777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This revision of go-duktype fixes the following warning ``` duk_logging.c: In function ‘duk__logger_prototype_log_shared’: duk_logging.c:184:64: warning: ‘Z’ directive writing 1 byte into a region of size between 0 and 9 [-Wformat-overflow=] 184 | sprintf((char *) date_buf, "%04d-%02d-%02dT%02d:%02d:%02d.%03dZ", | ^ In file included from /usr/include/stdio.h:867, from duk_logging.c:5: /usr/include/x86_64-linux-gnu/bits/stdio2.h:36:10: note: ‘__builtin___sprintf_chk’ output between 25 and 85 bytes into a destination of size 32 36 | return __builtin___sprintf_chk (__s, __USE_FORTIFY_LEVEL - 1, | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 37 | __bos (__s), __fmt, __va_arg_pack ()); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 791d80c981..6f2511d29d 100644 --- a/go.mod +++ b/go.mod @@ -65,7 +65,7 @@ require ( golang.org/x/text v0.3.2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce - gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 + gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 gopkg.in/urfave/cli.v1 v1.20.0 gotest.tools v2.2.0+incompatible // indirect ) diff --git a/go.sum b/go.sum index 0000392cca..2a823e15cf 100644 --- a/go.sum +++ b/go.sum @@ -225,6 +225,8 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772 h1:hhsSf/5z74Ck/DJYc+R8zpq8KGm7uJvpdLRQED/IedA= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20190213234257-ec84240a7772/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9 h1:ITeyKbRetrVzqR3U1eY+ywgp7IBspGd1U/bkwd1gWu4= +gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200316214253-d7b0ff38cac9/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0 h1:NdAVW6RYxDif9DhDHaAortIu956m2c0v+09AZBPTbE0= From 20a092fb9fac68a1835b7a98e2e3a0e96a138ef6 Mon Sep 17 00:00:00 2001 From: meowsbits <45600330+meowsbits@users.noreply.github.com> Date: Wed, 18 Mar 2020 06:55:30 -0500 Subject: [PATCH 031/103] core/rawdb: fix freezer table test error check Fixes: Condition is always 'false' because 'err' is always 'nil' --- core/rawdb/freezer_table_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/rawdb/freezer_table_test.go b/core/rawdb/freezer_table_test.go index e4a22b3768..dd1ff744a6 100644 --- a/core/rawdb/freezer_table_test.go +++ b/core/rawdb/freezer_table_test.go @@ -196,10 +196,8 @@ func TestFreezerRepairDanglingHeadLarge(t *testing.T) { f.Append(uint64(x), data) } // The last item should be there - if _, err = f.Retrieve(f.items - 1); err == nil { - if err != nil { - t.Fatal(err) - } + if _, err = f.Retrieve(f.items - 1); err != nil { + t.Fatal(err) } f.Close() } From 6283391c9939d71934520200dd6ef9e77920d3ab Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 18 Mar 2020 20:15:49 +0800 Subject: [PATCH 032/103] core/rawdb: improve table database (#20703) This PR fixes issues in TableDatabase. TableDatabase is a wrapper of underlying ethdb.Database with an additional prefix. The prefix is applied to all entries it maintains. However when we try to retrieve entries from it we don't handle the key properly. In theory the prefix should be truncated and only user key is returned. But we don't do it in some cases, e.g. the iterator and batch replayer created from it. So this PR is the fix to these issues. --- core/rawdb/table.go | 76 ++++++++++++++++++++- core/rawdb/table_test.go | 143 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 core/rawdb/table_test.go diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 6610b7f5a2..092341fbfe 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -113,13 +113,21 @@ func (t *table) NewIterator() ethdb.Iterator { // database content starting at a particular initial key (or after, if it does // not exist). func (t *table) NewIteratorWithStart(start []byte) ethdb.Iterator { - return t.db.NewIteratorWithStart(start) + iter := t.db.NewIteratorWithStart(append([]byte(t.prefix), start...)) + return &tableIterator{ + iter: iter, + prefix: t.prefix, + } } // NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset // of database content with a particular key prefix. func (t *table) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator { - return t.db.NewIteratorWithPrefix(append([]byte(t.prefix), prefix...)) + iter := t.db.NewIteratorWithPrefix(append([]byte(t.prefix), prefix...)) + return &tableIterator{ + iter: iter, + prefix: t.prefix, + } } // Stat returns a particular internal stat of the database. @@ -198,7 +206,69 @@ func (b *tableBatch) Reset() { b.batch.Reset() } +// tableReplayer is a wrapper around a batch replayer which truncates +// the added prefix. +type tableReplayer struct { + w ethdb.KeyValueWriter + prefix string +} + +// Put implements the interface KeyValueWriter. +func (r *tableReplayer) Put(key []byte, value []byte) error { + trimmed := key[len(r.prefix):] + return r.w.Put(trimmed, value) +} + +// Delete implements the interface KeyValueWriter. +func (r *tableReplayer) Delete(key []byte) error { + trimmed := key[len(r.prefix):] + return r.w.Delete(trimmed) +} + // Replay replays the batch contents. func (b *tableBatch) Replay(w ethdb.KeyValueWriter) error { - return b.batch.Replay(w) + return b.batch.Replay(&tableReplayer{w: w, prefix: b.prefix}) +} + +// tableIterator is a wrapper around a database iterator that prefixes each key access +// with a pre-configured string. +type tableIterator struct { + iter ethdb.Iterator + prefix string +} + +// Next moves the iterator to the next key/value pair. It returns whether the +// iterator is exhausted. +func (iter *tableIterator) Next() bool { + return iter.iter.Next() +} + +// Error returns any accumulated error. Exhausting all the key/value pairs +// is not considered to be an error. +func (iter *tableIterator) Error() error { + return iter.iter.Error() +} + +// Key returns the key of the current key/value pair, or nil if done. The caller +// should not modify the contents of the returned slice, and its contents may +// change on the next call to Next. +func (iter *tableIterator) Key() []byte { + key := iter.iter.Key() + if key == nil { + return nil + } + return key[len(iter.prefix):] +} + +// Value returns the value of the current key/value pair, or nil if done. The +// caller should not modify the contents of the returned slice, and its contents +// may change on the next call to Next. +func (iter *tableIterator) Value() []byte { + return iter.iter.Value() +} + +// Release releases associated resources. Release should always succeed and can +// be called multiple times without causing error. +func (iter *tableIterator) Release() { + iter.iter.Release() } diff --git a/core/rawdb/table_test.go b/core/rawdb/table_test.go new file mode 100644 index 0000000000..977eb4bf05 --- /dev/null +++ b/core/rawdb/table_test.go @@ -0,0 +1,143 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rawdb + +import ( + "bytes" + "testing" +) + +func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } +func TestEmptyPrefixTableDatabase(t *testing.T) { testTableDatabase(t, "") } + +type testReplayer struct { + puts [][]byte + dels [][]byte +} + +func (r *testReplayer) Put(key []byte, value []byte) error { + r.puts = append(r.puts, key) + return nil +} + +func (r *testReplayer) Delete(key []byte) error { + r.dels = append(r.dels, key) + return nil +} + +func testTableDatabase(t *testing.T, prefix string) { + db := NewTable(NewMemoryDatabase(), prefix) + + var entries = []struct { + key []byte + value []byte + }{ + {[]byte{0x01, 0x02}, []byte{0x0a, 0x0b}}, + {[]byte{0x03, 0x04}, []byte{0x0c, 0x0d}}, + {[]byte{0x05, 0x06}, []byte{0x0e, 0x0f}}, + + {[]byte{0xff, 0xff, 0x01}, []byte{0x1a, 0x1b}}, + {[]byte{0xff, 0xff, 0x02}, []byte{0x1c, 0x1d}}, + {[]byte{0xff, 0xff, 0x03}, []byte{0x1e, 0x1f}}, + } + + // Test Put/Get operation + for _, entry := range entries { + db.Put(entry.key, entry.value) + } + for _, entry := range entries { + got, err := db.Get(entry.key) + if err != nil { + t.Fatalf("Failed to get value: %v", err) + } + if !bytes.Equal(got, entry.value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) + } + } + + // Test batch operation + db = NewTable(NewMemoryDatabase(), prefix) + batch := db.NewBatch() + for _, entry := range entries { + batch.Put(entry.key, entry.value) + } + batch.Write() + for _, entry := range entries { + got, err := db.Get(entry.key) + if err != nil { + t.Fatalf("Failed to get value: %v", err) + } + if !bytes.Equal(got, entry.value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entry.value, got) + } + } + + // Test batch replayer + r := &testReplayer{} + batch.Replay(r) + for index, entry := range entries { + got := r.puts[index] + if !bytes.Equal(got, entry.key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entry.key, got) + } + } + + // Test iterators + iter := db.NewIterator() + var index int + for iter.Next() { + key, value := iter.Key(), iter.Value() + if !bytes.Equal(key, entries[index].key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) + } + if !bytes.Equal(value, entries[index].value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) + } + index += 1 + } + iter.Release() + + // Test iterators with prefix + iter = db.NewIteratorWithPrefix([]byte{0xff, 0xff}) + index = 3 + for iter.Next() { + key, value := iter.Key(), iter.Value() + if !bytes.Equal(key, entries[index].key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) + } + if !bytes.Equal(value, entries[index].value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) + } + index += 1 + } + iter.Release() + + // Test iterators with start point + iter = db.NewIteratorWithStart([]byte{0xff, 0xff, 0x02}) + index = 4 + for iter.Next() { + key, value := iter.Key(), iter.Value() + if !bytes.Equal(key, entries[index].key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) + } + if !bytes.Equal(value, entries[index].value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) + } + index += 1 + } + iter.Release() +} From dc6e98d2a8a084b0535f729587fb134e1715b3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 18 Mar 2020 14:17:12 +0200 Subject: [PATCH 033/103] eth: when triggering a sync, check the head header TD, not block --- eth/handler.go | 4 ++-- eth/sync.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/eth/handler.go b/eth/handler.go index f103f1c37f..236e50729c 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -727,8 +727,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { // Schedule a sync if above ours. Note, this will not fire a sync for a gap of // a single block (as the true TD is below the propagated block), however this // scenario should easily be covered by the fetcher. - currentBlock := pm.blockchain.CurrentBlock() - if trueTD.Cmp(pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64())) > 0 { + currentHeader := pm.blockchain.CurrentHeader() + if trueTD.Cmp(pm.blockchain.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())) > 0 { go pm.synchronise(p) } } diff --git a/eth/sync.go b/eth/sync.go index d5c678a74a..0709706c91 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -189,8 +189,8 @@ func (pm *ProtocolManager) synchronise(peer *peer) { return } // Make sure the peer's TD is higher than our own - currentBlock := pm.blockchain.CurrentBlock() - td := pm.blockchain.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) + currentHeader := pm.blockchain.CurrentHeader() + td := pm.blockchain.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()) pHead, pTd := peer.Head() if pTd.Cmp(td) <= 0 { From e6ca1958d3a2d93321e380e04a0d91bccc2e44e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 18 Mar 2020 15:23:16 +0200 Subject: [PATCH 034/103] internal/web3ext: fix clique console apis to work on missing arguments --- internal/web3ext/web3ext.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index bc105ef37c..71f0a2ed5d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -74,7 +74,7 @@ web3._extend({ name: 'getSnapshot', call: 'clique_getSnapshot', params: 1, - inputFormatter: [web3._extend.utils.fromDecimal] + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'getSnapshotAtHash', @@ -85,7 +85,7 @@ web3._extend({ name: 'getSigners', call: 'clique_getSigners', params: 1, - inputFormatter: [web3._extend.utils.fromDecimal] + inputFormatter: [web3._extend.formatters.inputBlockNumberFormatter] }), new web3._extend.Method({ name: 'getSignersAtHash', From e943f07a858b2eb3a47fbee32cd2d5b6c19ed0da Mon Sep 17 00:00:00 2001 From: Guillaume Ballet Date: Fri, 20 Mar 2020 09:37:53 +0100 Subject: [PATCH 035/103] whisper/whisperv6: delete failing tests (#20788) These tests occasionally fail on Travis. --- whisper/whisperv6/peer_test.go | 548 --------------------------------- 1 file changed, 548 deletions(-) delete mode 100644 whisper/whisperv6/peer_test.go diff --git a/whisper/whisperv6/peer_test.go b/whisper/whisperv6/peer_test.go deleted file mode 100644 index 986f8667d1..0000000000 --- a/whisper/whisperv6/peer_test.go +++ /dev/null @@ -1,548 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package whisperv6 - -import ( - "bytes" - "crypto/ecdsa" - "fmt" - mrand "math/rand" - "sync" - "testing" - "time" - - "net" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/p2p" - "github.com/ethereum/go-ethereum/p2p/enode" - "github.com/ethereum/go-ethereum/p2p/nat" - "github.com/ethereum/go-ethereum/rlp" -) - -var keys = []string{ - "d49dcf37238dc8a7aac57dc61b9fee68f0a97f062968978b9fafa7d1033d03a9", - "73fd6143c48e80ed3c56ea159fe7494a0b6b393a392227b422f4c3e8f1b54f98", - "119dd32adb1daa7a4c7bf77f847fb28730785aa92947edf42fdd997b54de40dc", - "deeda8709dea935bb772248a3144dea449ffcc13e8e5a1fd4ef20ce4e9c87837", - "5bd208a079633befa349441bdfdc4d85ba9bd56081525008380a63ac38a407cf", - "1d27fb4912002d58a2a42a50c97edb05c1b3dffc665dbaa42df1fe8d3d95c9b5", - "15def52800c9d6b8ca6f3066b7767a76afc7b611786c1276165fbc61636afb68", - "51be6ab4b2dc89f251ff2ace10f3c1cc65d6855f3e083f91f6ff8efdfd28b48c", - "ef1ef7441bf3c6419b162f05da6037474664f198b58db7315a6f4de52414b4a0", - "09bdf6985aabc696dc1fbeb5381aebd7a6421727343872eb2fadfc6d82486fd9", - "15d811bf2e01f99a224cdc91d0cf76cea08e8c67905c16fee9725c9be71185c4", - "2f83e45cf1baaea779789f755b7da72d8857aeebff19362dd9af31d3c9d14620", - "73f04e34ac6532b19c2aae8f8e52f38df1ac8f5cd10369f92325b9b0494b0590", - "1e2e07b69e5025537fb73770f483dc8d64f84ae3403775ef61cd36e3faf162c1", - "8963d9bbb3911aac6d30388c786756b1c423c4fbbc95d1f96ddbddf39809e43a", - "0422da85abc48249270b45d8de38a4cc3c02032ede1fcf0864a51092d58a2f1f", - "8ae5c15b0e8c7cade201fdc149831aa9b11ff626a7ffd27188886cc108ad0fa8", - "acd8f5a71d4aecfcb9ad00d32aa4bcf2a602939b6a9dd071bab443154184f805", - "a285a922125a7481600782ad69debfbcdb0316c1e97c267aff29ef50001ec045", - "28fd4eee78c6cd4bf78f39f8ab30c32c67c24a6223baa40e6f9c9a0e1de7cef5", - "c5cca0c9e6f043b288c6f1aef448ab59132dab3e453671af5d0752961f013fc7", - "46df99b051838cb6f8d1b73f232af516886bd8c4d0ee07af9a0a033c391380fd", - "c6a06a53cbaadbb432884f36155c8f3244e244881b5ee3e92e974cfa166d793f", - "783b90c75c63dc72e2f8d11b6f1b4de54d63825330ec76ee8db34f06b38ea211", - "9450038f10ca2c097a8013e5121b36b422b95b04892232f930a29292d9935611", - "e215e6246ed1cfdcf7310d4d8cdbe370f0d6a8371e4eb1089e2ae05c0e1bc10f", - "487110939ed9d64ebbc1f300adeab358bc58875faf4ca64990fbd7fe03b78f2b", - "824a70ea76ac81366da1d4f4ac39de851c8ac49dca456bb3f0a186ceefa269a5", - "ba8f34fa40945560d1006a328fe70c42e35cc3d1017e72d26864cd0d1b150f15", - "30a5dfcfd144997f428901ea88a43c8d176b19c79dde54cc58eea001aa3d246c", - "de59f7183aca39aa245ce66a05245fecfc7e2c75884184b52b27734a4a58efa2", - "92629e2ff5f0cb4f5f08fffe0f64492024d36f045b901efb271674b801095c5a", - "7184c1701569e3a4c4d2ddce691edd983b81e42e09196d332e1ae2f1e062cff4", -} - -type TestData struct { - counter [NumNodes]int - mutex sync.RWMutex -} - -type TestNode struct { - shh *Whisper - id *ecdsa.PrivateKey - server *p2p.Server - filerID string -} - -const NumNodes = 8 // must not exceed the number of keys (32) - -var result TestData -var nodes [NumNodes]*TestNode -var sharedKey = hexutil.MustDecode("0x03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31") -var wrongKey = hexutil.MustDecode("0xf91156714d7ec88d3edc1c652c2181dbb3044e8771c683f3b30d33c12b986b11") -var sharedTopic = TopicType{0xF, 0x1, 0x2, 0} -var wrongTopic = TopicType{0, 0, 0, 0} -var expectedMessage = []byte("per aspera ad astra") -var unexpectedMessage = []byte("per rectum ad astra") -var masterBloomFilter []byte -var masterPow = 0.00000001 -var round = 1 -var debugMode = false -var prevTime time.Time -var cntPrev int - -func TestSimulation(t *testing.T) { - // create a chain of whisper nodes, - // installs the filters with shared (predefined) parameters - initialize(t) - - // each node sends one random (not decryptable) message - for i := 0; i < NumNodes; i++ { - sendMsg(t, false, i) - } - - // node #0 sends one expected (decryptable) message - sendMsg(t, true, 0) - - // check if each node have received and decrypted exactly one message - checkPropagation(t, true) - - // check if Status message was correctly decoded - checkBloomFilterExchange(t) - checkPowExchange(t) - - // send new pow and bloom exchange messages - resetParams(t) - - // node #1 sends one expected (decryptable) message - sendMsg(t, true, 1) - - // check if each node (except node #0) have received and decrypted exactly one message - checkPropagation(t, false) - - // check if corresponding protocol-level messages were correctly decoded - checkPowExchangeForNodeZero(t) - checkBloomFilterExchange(t) - - stopServers() -} - -func resetParams(t *testing.T) { - // change pow only for node zero - masterPow = 7777777.0 - nodes[0].shh.SetMinimumPoW(masterPow) - - // change bloom for all nodes - masterBloomFilter = TopicToBloom(sharedTopic) - for i := 0; i < NumNodes; i++ { - nodes[i].shh.SetBloomFilter(masterBloomFilter) - } - - round++ -} - -func initBloom(t *testing.T) { - masterBloomFilter = make([]byte, BloomFilterSize) - _, err := mrand.Read(masterBloomFilter) - if err != nil { - t.Fatalf("rand failed: %s.", err) - } - - msgBloom := TopicToBloom(sharedTopic) - masterBloomFilter = addBloom(masterBloomFilter, msgBloom) - for i := 0; i < 32; i++ { - masterBloomFilter[i] = 0xFF - } - - if !BloomFilterMatch(masterBloomFilter, msgBloom) { - t.Fatalf("bloom mismatch on initBloom.") - } -} - -func initialize(t *testing.T) { - initBloom(t) - - var err error - - for i := 0; i < NumNodes; i++ { - var node TestNode - b := make([]byte, BloomFilterSize) - copy(b, masterBloomFilter) - node.shh = New(&DefaultConfig) - node.shh.SetMinimumPoW(masterPow) - node.shh.SetBloomFilter(b) - if !bytes.Equal(node.shh.BloomFilter(), masterBloomFilter) { - t.Fatalf("bloom mismatch on init.") - } - node.shh.Start(nil) - topics := make([]TopicType, 0) - topics = append(topics, sharedTopic) - f := Filter{KeySym: sharedKey} - f.Topics = [][]byte{topics[0][:]} - node.filerID, err = node.shh.Subscribe(&f) - if err != nil { - t.Fatalf("failed to install the filter: %s.", err) - } - node.id, err = crypto.HexToECDSA(keys[i]) - if err != nil { - t.Fatalf("failed convert the key: %s.", keys[i]) - } - name := common.MakeName("whisper-go", "2.0") - - node.server = &p2p.Server{ - Config: p2p.Config{ - PrivateKey: node.id, - MaxPeers: NumNodes/2 + 1, - Name: name, - Protocols: node.shh.Protocols(), - ListenAddr: "127.0.0.1:0", - NAT: nat.Any(), - }, - } - - startServer(t, node.server) - nodes[i] = &node - } - - for i := 0; i < NumNodes; i++ { - for j := 0; j < i; j++ { - peerNodeId := nodes[j].id - address, _ := net.ResolveTCPAddr("tcp", nodes[j].server.ListenAddr) - peer := enode.NewV4(&peerNodeId.PublicKey, address.IP, address.Port, address.Port) - nodes[i].server.AddPeer(peer) - } - } -} - -func startServer(t *testing.T, s *p2p.Server) { - err := s.Start() - if err != nil { - t.Fatalf("failed to start the first server. err: %v", err) - } -} - -func stopServers() { - for i := 0; i < NumNodes; i++ { - n := nodes[i] - if n != nil { - n.shh.Unsubscribe(n.filerID) - n.shh.Stop() - n.server.Stop() - } - } -} - -func checkPropagation(t *testing.T, includingNodeZero bool) { - if t.Failed() { - return - } - - prevTime = time.Now() - // (cycle * iterations) should not exceed 50 seconds, since TTL=50 - const cycle = 200 // time in milliseconds - const iterations = 250 - - first := 0 - if !includingNodeZero { - first = 1 - } - - for j := 0; j < iterations; j++ { - for i := first; i < NumNodes; i++ { - f := nodes[i].shh.GetFilter(nodes[i].filerID) - if f == nil { - t.Fatalf("failed to get filterId %s from node %d, round %d.", nodes[i].filerID, i, round) - } - - mail := f.Retrieve() - validateMail(t, i, mail) - - if isTestComplete() { - checkTestStatus() - return - } - } - - checkTestStatus() - time.Sleep(cycle * time.Millisecond) - } - - if !includingNodeZero { - f := nodes[0].shh.GetFilter(nodes[0].filerID) - if f != nil { - t.Fatalf("node zero received a message with low PoW.") - } - } - - t.Fatalf("Test was not complete (%d round): timeout %d seconds. nodes=%v", round, iterations*cycle/1000, nodes) -} - -func validateMail(t *testing.T, index int, mail []*ReceivedMessage) { - var cnt int - for _, m := range mail { - if bytes.Equal(m.Payload, expectedMessage) { - cnt++ - } - } - - if cnt == 0 { - // no messages received yet: nothing is wrong - return - } - if cnt > 1 { - t.Fatalf("node %d received %d.", index, cnt) - } - - if cnt == 1 { - result.mutex.Lock() - defer result.mutex.Unlock() - result.counter[index] += cnt - if result.counter[index] > 1 { - t.Fatalf("node %d accumulated %d.", index, result.counter[index]) - } - } -} - -func checkTestStatus() { - var cnt int - var arr [NumNodes]int - - for i := 0; i < NumNodes; i++ { - arr[i] = nodes[i].server.PeerCount() - envelopes := nodes[i].shh.Envelopes() - if len(envelopes) >= NumNodes { - cnt++ - } - } - - if debugMode { - if cntPrev != cnt { - fmt.Printf(" %v \t number of nodes that have received all msgs: %d, number of peers per node: %v \n", - time.Since(prevTime), cnt, arr) - prevTime = time.Now() - cntPrev = cnt - } - } -} - -func isTestComplete() bool { - result.mutex.RLock() - defer result.mutex.RUnlock() - - for i := 0; i < NumNodes; i++ { - if result.counter[i] < 1 { - return false - } - } - - for i := 0; i < NumNodes; i++ { - envelopes := nodes[i].shh.Envelopes() - if len(envelopes) < NumNodes+1 { - return false - } - } - - return true -} - -func sendMsg(t *testing.T, expected bool, id int) { - if t.Failed() { - return - } - - opt := MessageParams{KeySym: sharedKey, Topic: sharedTopic, Payload: expectedMessage, PoW: 0.00000001, WorkTime: 1} - if !expected { - opt.KeySym = wrongKey - opt.Topic = wrongTopic - opt.Payload = unexpectedMessage - opt.Payload[0] = byte(id) - } - - msg, err := NewSentMessage(&opt) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - envelope, err := msg.Wrap(&opt) - if err != nil { - t.Fatalf("failed to seal message: %s", err) - } - - err = nodes[id].shh.Send(envelope) - if err != nil { - t.Fatalf("failed to send message: %s", err) - } -} - -func TestPeerBasic(t *testing.T) { - InitSingleTest() - - params, err := generateMessageParams() - if err != nil { - t.Fatalf("failed generateMessageParams with seed %d.", seed) - } - - params.PoW = 0.001 - msg, err := NewSentMessage(params) - if err != nil { - t.Fatalf("failed to create new message with seed %d: %s.", seed, err) - } - env, err := msg.Wrap(params) - if err != nil { - t.Fatalf("failed Wrap with seed %d.", seed) - } - - p := newPeer(nil, nil, nil) - p.mark(env) - if !p.marked(env) { - t.Fatalf("failed mark with seed %d.", seed) - } -} - -func checkPowExchangeForNodeZero(t *testing.T) { - const iterations = 200 - for j := 0; j < iterations; j++ { - lastCycle := (j == iterations-1) - ok := checkPowExchangeForNodeZeroOnce(t, lastCycle) - if ok { - break - } - time.Sleep(50 * time.Millisecond) - } -} - -func checkPowExchangeForNodeZeroOnce(t *testing.T, mustPass bool) bool { - cnt := 0 - for i, node := range nodes { - for peer := range node.shh.peers { - if peer.peer.ID() == nodes[0].server.Self().ID() { - cnt++ - if peer.powRequirement != masterPow { - if mustPass { - t.Fatalf("node %d: failed to set the new pow requirement for node zero.", i) - } else { - return false - } - } - } - } - } - if cnt == 0 { - t.Fatalf("looking for node zero: no matching peers found.") - } - return true -} - -func checkPowExchange(t *testing.T) { - for i, node := range nodes { - for peer := range node.shh.peers { - if peer.peer.ID() != nodes[0].server.Self().ID() { - if peer.powRequirement != masterPow { - t.Fatalf("node %d: failed to exchange pow requirement in round %d; expected %f, got %f", - i, round, masterPow, peer.powRequirement) - } - } - } - } -} - -func checkBloomFilterExchangeOnce(t *testing.T, mustPass bool) bool { - for i, node := range nodes { - for peer := range node.shh.peers { - peer.bloomMu.Lock() - equals := bytes.Equal(peer.bloomFilter, masterBloomFilter) - peer.bloomMu.Unlock() - if !equals { - if mustPass { - t.Fatalf("node %d: failed to exchange bloom filter requirement in round %d. \n%x expected \n%x got", - i, round, masterBloomFilter, peer.bloomFilter) - } else { - return false - } - } - } - } - - return true -} - -func checkBloomFilterExchange(t *testing.T) { - const iterations = 200 - for j := 0; j < iterations; j++ { - lastCycle := (j == iterations-1) - ok := checkBloomFilterExchangeOnce(t, lastCycle) - if ok { - break - } - time.Sleep(50 * time.Millisecond) - } -} - -//two generic whisper node handshake -func TestPeerHandshakeWithTwoFullNode(t *testing.T) { - w1 := Whisper{} - p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), false}}) - err := p1.handshake() - if err != nil { - t.Fatal() - } -} - -//two generic whisper node handshake. one don't send light flag -func TestHandshakeWithOldVersionWithoutLightModeFlag(t *testing.T) { - w1 := Whisper{} - p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize)}}) - err := p1.handshake() - if err != nil { - t.Fatal() - } -} - -//two light nodes handshake. restriction disabled -func TestTwoLightPeerHandshakeRestrictionOff(t *testing.T) { - w1 := Whisper{} - w1.settings.Store(restrictConnectionBetweenLightClientsIdx, false) - w1.SetLightClientMode(true) - p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}}) - err := p1.handshake() - if err != nil { - t.FailNow() - } -} - -//two light nodes handshake. restriction enabled -func TestTwoLightPeerHandshakeError(t *testing.T) { - w1 := Whisper{} - w1.settings.Store(restrictConnectionBetweenLightClientsIdx, true) - w1.SetLightClientMode(true) - p1 := newPeer(&w1, p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}), &rwStub{[]interface{}{ProtocolVersion, uint64(123), make([]byte, BloomFilterSize), true}}) - err := p1.handshake() - if err == nil { - t.FailNow() - } -} - -type rwStub struct { - payload []interface{} -} - -func (stub *rwStub) ReadMsg() (p2p.Msg, error) { - size, r, err := rlp.EncodeToReader(stub.payload) - if err != nil { - return p2p.Msg{}, err - } - return p2p.Msg{Code: statusCode, Size: uint32(size), Payload: r}, nil -} - -func (stub *rwStub) WriteMsg(m p2p.Msg) error { - return nil -} From 93ffb85b3d3874d08beb2127e70a278444eaec93 Mon Sep 17 00:00:00 2001 From: meowsbits <45600330+meowsbits@users.noreply.github.com> Date: Sat, 21 Mar 2020 09:28:27 -0500 Subject: [PATCH 036/103] rpc: dont log an error if user configures --rpcapi=rpc... (#20776) This just prevents a false negative ERROR warning when, for some unknown reason, a user attempts to turn on the module rpc even though it's already going to be on. --- rpc/endpoints.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rpc/endpoints.go b/rpc/endpoints.go index ed295adfc0..09f389d71b 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -22,8 +22,9 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// checkModuleAvailability check that all names given in modules are actually -// available API services. +// checkModuleAvailability checks that all names given in modules are actually +// available API services. It assumes that the MetadataApi module ("rpc") is always available; +// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. func checkModuleAvailability(modules []string, apis []API) (bad, available []string) { availableSet := make(map[string]struct{}) for _, api := range apis { @@ -33,7 +34,7 @@ func checkModuleAvailability(modules []string, apis []API) (bad, available []str } } for _, name := range modules { - if _, ok := availableSet[name]; !ok { + if _, ok := availableSet[name]; !ok && name != MetadataApi { bad = append(bad, name) } } From 074efe6c8dfe879adb26f60a8a9554fdf9da1907 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Fri, 6 Mar 2020 13:05:44 +0100 Subject: [PATCH 037/103] core: fix two snapshot iterator flaws, decollide snap storage prefix * core/state/snapshot/iterator: fix two disk iterator flaws * core/rawdb: change SnapshotStoragePrefix to avoid prefix collision with preimagePrefix --- core/blockchain_test.go | 123 ++++++++++++++++++++++++++++++++ core/rawdb/schema.go | 2 +- core/state/journal.go | 6 +- core/state/snapshot/iterator.go | 18 +++-- core/state/statedb.go | 12 ++-- 5 files changed, 149 insertions(+), 12 deletions(-) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index b6b497ecee..f15ede449c 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -2758,3 +2758,126 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) { } } } + +// TestInitThenFailCreateContract tests a pretty notorious case that happened +// on mainnet over blocks 7338108, 7338110 and 7338115. +// - Block 7338108: address e771789f5cccac282f23bb7add5690e1f6ca467c is initiated +// with 0.001 ether (thus created but no code) +// - Block 7338110: a CREATE2 is attempted. The CREATE2 would deploy code on +// the same address e771789f5cccac282f23bb7add5690e1f6ca467c. However, the +// deployment fails due to OOG during initcode execution +// - Block 7338115: another tx checks the balance of +// e771789f5cccac282f23bb7add5690e1f6ca467c, and the snapshotter returned it as +// zero. +// +// The problem being that the snapshotter maintains a destructset, and adds items +// to the destructset in case something is created "onto" an existing item. +// We need to either roll back the snapDestructs, or not place it into snapDestructs +// in the first place. +// +func TestInitThenFailCreateContract(t *testing.T) { + var ( + // Generate a canonical chain to act as the main dataset + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + // A sender who makes transactions, has some funds + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000) + bb = common.HexToAddress("0x000000000000000000000000000000000000bbbb") + ) + + // The bb-code needs to CREATE2 the aa contract. It consists of + // both initcode and deployment code + // initcode: + // 1. If blocknum < 1, error out (e.g invalid opcode) + // 2. else, return a snippet of code + initCode := []byte{ + byte(vm.PUSH1), 0x1, // y (2) + byte(vm.NUMBER), // x (number) + byte(vm.GT), // x > y? + byte(vm.PUSH1), byte(0x8), + byte(vm.JUMPI), // jump to label if number > 2 + byte(0xFE), // illegal opcode + byte(vm.JUMPDEST), + byte(vm.PUSH1), 0x2, // size + byte(vm.PUSH1), 0x0, // offset + byte(vm.RETURN), // return 2 bytes of zero-code + } + if l := len(initCode); l > 32 { + t.Fatalf("init code is too long for a pushx, need a more elaborate deployer") + } + bbCode := []byte{ + // Push initcode onto stack + byte(vm.PUSH1) + byte(len(initCode)-1)} + bbCode = append(bbCode, initCode...) + bbCode = append(bbCode, []byte{ + byte(vm.PUSH1), 0x0, // memory start on stack + byte(vm.MSTORE), + byte(vm.PUSH1), 0x00, // salt + byte(vm.PUSH1), byte(len(initCode)), // size + byte(vm.PUSH1), byte(32 - len(initCode)), // offset + byte(vm.PUSH1), 0x00, // endowment + byte(vm.CREATE2), + }...) + + initHash := crypto.Keccak256Hash(initCode) + aa := crypto.CreateAddress2(bb, [32]byte{}, initHash[:]) + t.Logf("Destination address: %x\n", aa) + + gspec := &Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + address: {Balance: funds}, + // The address aa has some funds + aa: {Balance: big.NewInt(100000)}, + // The contract BB tries to create code onto AA + bb: { + Code: bbCode, + Balance: big.NewInt(1), + }, + }, + } + genesis := gspec.MustCommit(db) + nonce := uint64(0) + blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 4, func(i int, b *BlockGen) { + b.SetCoinbase(common.Address{1}) + // One transaction to BB + tx, _ := types.SignTx(types.NewTransaction(nonce, bb, + big.NewInt(0), 100000, big.NewInt(1), nil), types.HomesteadSigner{}, key) + b.AddTx(tx) + nonce++ + }) + + // Import the canonical chain + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{ + //Debug: true, + //Tracer: vm.NewJSONLogger(nil, os.Stdout), + }, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + statedb, _ := chain.State() + if got, exp := statedb.GetBalance(aa), big.NewInt(100000); got.Cmp(exp) != 0 { + t.Fatalf("Genesis err, got %v exp %v", got, exp) + } + // First block tries to create, but fails + { + block := blocks[0] + if _, err := chain.InsertChain([]*types.Block{blocks[0]}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + statedb, _ = chain.State() + if got, exp := statedb.GetBalance(aa), big.NewInt(100000); got.Cmp(exp) != 0 { + t.Fatalf("block %d: got %v exp %v", block.NumberU64(), got, exp) + } + } + // Import the rest of the blocks + for _, block := range blocks[1:] { + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", block.NumberU64(), err) + } + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index dc8faca32d..2c20df200b 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -59,7 +59,7 @@ var ( txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits SnapshotAccountPrefix = []byte("a") // SnapshotAccountPrefix + account hash -> account trie value - SnapshotStoragePrefix = []byte("s") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value + SnapshotStoragePrefix = []byte("o") // SnapshotStoragePrefix + account hash + storage hash -> storage trie value preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage configPrefix = []byte("ethereum-config-") // config prefix for the db diff --git a/core/state/journal.go b/core/state/journal.go index c0bd2b9242..f242dac5af 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -90,7 +90,8 @@ type ( account *common.Address } resetObjectChange struct { - prev *stateObject + prev *stateObject + prevdestruct bool } suicideChange struct { account *common.Address @@ -142,6 +143,9 @@ func (ch createObjectChange) dirtied() *common.Address { func (ch resetObjectChange) revert(s *StateDB) { s.setStateObject(ch.prev) + if !ch.prevdestruct && s.snap != nil { + delete(s.snapDestructs, ch.prev.addrHash) + } } func (ch resetObjectChange) dirtied() *common.Address { diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index f0b1eafa99..e062298fa4 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -148,9 +148,10 @@ type diskAccountIterator struct { // AccountIterator creates an account iterator over a disk layer. func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { + // TODO: Fix seek position, or remove seek parameter return &diskAccountIterator{ layer: dl, - it: dl.diskdb.NewIteratorWithPrefix(append(rawdb.SnapshotAccountPrefix, seek[:]...)), + it: dl.diskdb.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix), } } @@ -160,11 +161,16 @@ func (it *diskAccountIterator) Next() bool { if it.it == nil { return false } - // Try to advance the iterator and release it if we reahed the end - if !it.it.Next() || !bytes.HasPrefix(it.it.Key(), rawdb.SnapshotAccountPrefix) { - it.it.Release() - it.it = nil - return false + // Try to advance the iterator and release it if we reached the end + for { + if !it.it.Next() || !bytes.HasPrefix(it.it.Key(), rawdb.SnapshotAccountPrefix) { + it.it.Release() + it.it = nil + return false + } + if len(it.it.Key()) == len(rawdb.SnapshotAccountPrefix)+common.HashLength { + break + } } return true } diff --git a/core/state/statedb.go b/core/state/statedb.go index 038005685b..4f5c1703ed 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -569,12 +569,19 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject { func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! + var prevdestruct bool + if s.snap != nil && prev != nil { + _, prevdestruct = s.snapDestructs[prev.addrHash] + if !prevdestruct { + s.snapDestructs[prev.addrHash] = struct{}{} + } + } newobj = newObject(s, addr, Account{}) newobj.setNonce(0) // sets the object to dirty if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { - s.journal.append(resetObjectChange{prev: prev}) + s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) } s.setStateObject(newobj) return newobj, prev @@ -595,9 +602,6 @@ func (s *StateDB) CreateAccount(addr common.Address) { if prev != nil { newObj.setBalance(prev.data.Balance) } - if s.snap != nil && prev != nil { - s.snapDestructs[prev.addrHash] = struct{}{} - } } func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) error { From a75c0610b7aad63de01cefeeaf3426b332e718eb Mon Sep 17 00:00:00 2001 From: meowsbits <45600330+meowsbits@users.noreply.github.com> Date: Mon, 23 Mar 2020 09:05:15 -0500 Subject: [PATCH 038/103] core/blockchain: simplify atomic store after writeBlockWithState (#20798) Signed-off-by: meows --- core/blockchain.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index de0d4f3993..1da899ab43 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1740,11 +1740,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er // Write the block to the chain and get the status. substart = time.Now() status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false) + atomic.StoreUint32(&followupInterrupt, 1) if err != nil { - atomic.StoreUint32(&followupInterrupt, 1) return it.index, err } - atomic.StoreUint32(&followupInterrupt, 1) // Update the metrics touched during block commit accountCommitTimer.Update(statedb.AccountCommits) // Account commits are complete, we can mark them From 0734c4b82050fd8114bb18514191cb5c7ccf5259 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 23 Mar 2020 16:26:56 +0100 Subject: [PATCH 039/103] node, cmd/clef: report actual port used for http rpc (#20789) --- cmd/clef/main.go | 4 ++-- node/node.go | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/clef/main.go b/cmd/clef/main.go index b2c8812ab2..dd898871d5 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -545,12 +545,12 @@ func signer(c *cli.Context) error { if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } - extapiURL = fmt.Sprintf("http://%s", httpEndpoint) + extapiURL = fmt.Sprintf("http://%v/", listener.Addr()) log.Info("HTTP endpoint opened", "url", extapiURL) defer func() { listener.Close() - log.Info("HTTP endpoint closed", "url", httpEndpoint) + log.Info("HTTP endpoint closed", "url", extapiURL) }() } if !c.GlobalBool(utils.IPCDisabledFlag.Name) { diff --git a/node/node.go b/node/node.go index 1bdd5af7d2..7d8f9b07b9 100644 --- a/node/node.go +++ b/node/node.go @@ -368,7 +368,9 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors if err != nil { return err } - n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%s", endpoint), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) + n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), + "cors", strings.Join(cors, ","), + "vhosts", strings.Join(vhosts, ",")) // All listeners booted successfully n.httpEndpoint = endpoint n.httpListener = listener @@ -380,10 +382,10 @@ func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors // stopHTTP terminates the HTTP RPC endpoint. func (n *Node) stopHTTP() { if n.httpListener != nil { + url := fmt.Sprintf("http://%v/", n.httpListener.Addr()) n.httpListener.Close() n.httpListener = nil - - n.log.Info("HTTP endpoint closed", "url", fmt.Sprintf("http://%s", n.httpEndpoint)) + n.log.Info("HTTP endpoint closed", "url", url) } if n.httpHandler != nil { n.httpHandler.Stop() From 39f502329fac4640cfb71959c3496f19ea88bc85 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 23 Mar 2020 18:21:23 +0100 Subject: [PATCH 040/103] internal/ethapi: don't set sender-balance to maxuint, fixes #16999 (#20783) Prior to this change, eth_call changed the balance of the sender account in the EVM environment to 2^256 wei to cover the gas cost of the call execution. We've had this behavior for a long time even though it's super confusing. This commit sets the default call gasprice to zero instead of updating the balance, which is better because it makes eth_call semantics less surprising. Removing the built-in balance assignment also makes balance overrides work as expected. --- eth/api_backend.go | 2 -- internal/ethapi/api.go | 6 +----- les/api_backend.go | 2 -- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/eth/api_backend.go b/eth/api_backend.go index 0ad2d57fd2..a82aee4eb7 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -191,7 +190,6 @@ func (b *EthAPIBackend) GetTd(blockHash common.Hash) *big.Int { } func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - state.SetBalance(msg.From(), math.MaxBig256) vmError := func() error { return nil } context := core.NewEVMContext(msg, header, b.eth.BlockChain(), nil) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index cb4d255daf..68afa83c76 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -47,10 +47,6 @@ import ( "github.com/tyler-smith/go-bip39" ) -const ( - defaultGasPrice = params.GWei -) - // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -808,7 +804,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) gas = globalGasCap.Uint64() } - gasPrice := new(big.Int).SetUint64(defaultGasPrice) + gasPrice := new(big.Int) if args.GasPrice != nil { gasPrice = args.GasPrice.ToInt() } diff --git a/les/api_backend.go b/les/api_backend.go index 5627c34d6a..756beaf6a7 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -23,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" @@ -168,7 +167,6 @@ func (b *LesApiBackend) GetTd(hash common.Hash) *big.Int { } func (b *LesApiBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) { - state.SetBalance(msg.From(), math.MaxBig256) context := core.NewEVMContext(msg, header, b.eth.blockchain, nil) return vm.NewEVM(context, state, b.eth.chainConfig, vm.Config{}), state.Error, nil } From 42e02ac03bfc4e01496d15314e94f46260d98f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 26 Mar 2020 11:24:01 +0200 Subject: [PATCH 041/103] metrics: disable CPU stats (gosigar) on iOS --- metrics/cpu.go | 12 ------------ metrics/cpu_disabled.go | 23 +++++++++++++++++++++++ metrics/cpu_enabled.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 metrics/cpu_disabled.go create mode 100644 metrics/cpu_enabled.go diff --git a/metrics/cpu.go b/metrics/cpu.go index 3278d81616..72ece16e07 100644 --- a/metrics/cpu.go +++ b/metrics/cpu.go @@ -16,21 +16,9 @@ package metrics -import "github.com/elastic/gosigar" - // CPUStats is the system and process CPU stats. type CPUStats struct { GlobalTime int64 // Time spent by the CPU working on all processes GlobalWait int64 // Time spent by waiting on disk for all processes LocalTime int64 // Time spent by the CPU working on this process } - -// ReadCPUStats retrieves the current CPU stats. -func ReadCPUStats(stats *CPUStats) { - global := gosigar.Cpu{} - global.Get() - - stats.GlobalTime = int64(global.User + global.Nice + global.Sys) - stats.GlobalWait = int64(global.Wait) - stats.LocalTime = getProcessCPUTime() -} diff --git a/metrics/cpu_disabled.go b/metrics/cpu_disabled.go new file mode 100644 index 0000000000..6c3428993f --- /dev/null +++ b/metrics/cpu_disabled.go @@ -0,0 +1,23 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build ios + +package metrics + +// ReadCPUStats retrieves the current CPU stats. Internally this uses `gosigar`, +// which is not supported on the platforms in this file. +func ReadCPUStats(stats *CPUStats) {} diff --git a/metrics/cpu_enabled.go b/metrics/cpu_enabled.go new file mode 100644 index 0000000000..99d44e4002 --- /dev/null +++ b/metrics/cpu_enabled.go @@ -0,0 +1,31 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build !ios + +package metrics + +import "github.com/elastic/gosigar" + +// ReadCPUStats retrieves the current CPU stats. +func ReadCPUStats(stats *CPUStats) { + global := gosigar.Cpu{} + global.Get() + + stats.GlobalTime = int64(global.User + global.Nice + global.Sys) + stats.GlobalWait = int64(global.Wait) + stats.LocalTime = getProcessCPUTime() +} From 1583e7d2744af796aa85159217e80ca146097302 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 26 Mar 2020 12:51:50 +0100 Subject: [PATCH 042/103] cmd/devp2p: tweak DNS TTLs (#20801) * cmd/devp2p: tweak DNS TTLs * cmd/devp2p: bump treeNodeTTL to four weeks --- cmd/devp2p/dnscmd.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/devp2p/dnscmd.go b/cmd/devp2p/dnscmd.go index 7c9ccd31f4..8ca31504d3 100644 --- a/cmd/devp2p/dnscmd.go +++ b/cmd/devp2p/dnscmd.go @@ -97,8 +97,8 @@ var ( ) const ( - rootTTL = 1 - treeNodeTTL = 2147483647 + rootTTL = 30 * 60 // 30 min + treeNodeTTL = 4 * 7 * 24 * 60 * 60 // 4 weeks ) // dnsSync performs dnsSyncCommand. From 87a411b839dc770460cd152cf2e7e6d40729709a Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 26 Mar 2020 16:39:56 +0100 Subject: [PATCH 043/103] cmd/devp2p: lower route53 change limit again (#20819) --- cmd/devp2p/dns_route53.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index 71118be543..d83446a73f 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -32,9 +32,11 @@ import ( "gopkg.in/urfave/cli.v1" ) -// The Route53 limits change sets to this size. DNS changes need to be split -// up into multiple batches to work around the limit. -const route53ChangeLimit = 30000 +// Route53 limits change sets to 32k of 'RDATA size'. DNS changes need to be split up into +// multiple batches to work around the limit. Unfortunately I cannot find any +// documentation explaining how the RDATA size of a change set is computed and the best we +// can do is estimate it. For this reason, our internal limit is much lower than 32k. +const route53ChangeLimit = 20000 var ( route53AccessKeyFlag = cli.StringFlag{ From d3c1e654f06a38cb1d78cab0503d872e4ca56bec Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Thu, 26 Mar 2020 23:55:33 +0100 Subject: [PATCH 044/103] cmd/devp2p: be very correct about route53 change splitting (#20820) Turns out the way RDATA limits work is documented after all, I just didn't search right. The trick to make it work is to count UPSERTs twice. This also adds an additional check to ensure TTL changes are applied on existing records. --- cmd/devp2p/dns_route53.go | 41 ++++++++++++++++++++++++---------- cmd/devp2p/dns_route53_test.go | 16 +++++++++++-- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/cmd/devp2p/dns_route53.go b/cmd/devp2p/dns_route53.go index d83446a73f..c5f99529b8 100644 --- a/cmd/devp2p/dns_route53.go +++ b/cmd/devp2p/dns_route53.go @@ -32,11 +32,13 @@ import ( "gopkg.in/urfave/cli.v1" ) -// Route53 limits change sets to 32k of 'RDATA size'. DNS changes need to be split up into -// multiple batches to work around the limit. Unfortunately I cannot find any -// documentation explaining how the RDATA size of a change set is computed and the best we -// can do is estimate it. For this reason, our internal limit is much lower than 32k. -const route53ChangeLimit = 20000 +const ( + // Route53 limits change sets to 32k of 'RDATA size'. Change sets are also limited to + // 1000 items. UPSERTs count double. + // https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests-changeresourcerecordsets + route53ChangeSizeLimit = 32000 + route53ChangeCountLimit = 1000 +) var ( route53AccessKeyFlag = cli.StringFlag{ @@ -104,7 +106,7 @@ func (c *route53Client) deploy(name string, t *dnsdisc.Tree) error { } // Submit change batches. - batches := splitChanges(changes, route53ChangeLimit) + batches := splitChanges(changes, route53ChangeSizeLimit, route53ChangeCountLimit) for i, changes := range batches { log.Info(fmt.Sprintf("Submitting %d changes to Route53", len(changes))) batch := new(route53.ChangeBatch) @@ -178,7 +180,7 @@ func (c *route53Client) computeChanges(name string, records map[string]string, e // Entry is unknown, push a new one log.Info(fmt.Sprintf("Creating %s = %q", path, val)) changes = append(changes, newTXTChange("CREATE", path, ttl, splitTXT(val))) - } else if prevValue != val { + } else if prevValue != val || prevRecords.ttl != ttl { // Entry already exists, only change its content. log.Info(fmt.Sprintf("Updating %s from %q to %q", path, prevValue, val)) changes = append(changes, newTXTChange("UPSERT", path, ttl, splitTXT(val))) @@ -214,18 +216,26 @@ func sortChanges(changes []*route53.Change) { // splitChanges splits up DNS changes such that each change batch // is smaller than the given RDATA limit. -func splitChanges(changes []*route53.Change, limit int) [][]*route53.Change { - var batches [][]*route53.Change - var batchSize int +func splitChanges(changes []*route53.Change, sizeLimit, countLimit int) [][]*route53.Change { + var ( + batches [][]*route53.Change + batchSize int + batchCount int + ) for _, ch := range changes { // Start new batch if this change pushes the current one over the limit. - size := changeSize(ch) - if len(batches) == 0 || batchSize+size > limit { + count := changeCount(ch) + size := changeSize(ch) * count + overSize := batchSize+size > sizeLimit + overCount := batchCount+count > countLimit + if len(batches) == 0 || overSize || overCount { batches = append(batches, nil) batchSize = 0 + batchCount = 0 } batches[len(batches)-1] = append(batches[len(batches)-1], ch) batchSize += size + batchCount += count } return batches } @@ -241,6 +251,13 @@ func changeSize(ch *route53.Change) int { return size } +func changeCount(ch *route53.Change) int { + if *ch.Action == "UPSERT" { + return 2 + } + return 1 +} + // collectRecords collects all TXT records below the given name. func (c *route53Client) collectRecords(name string) (map[string]recordSet, error) { log.Info(fmt.Sprintf("Retrieving existing TXT records on %s (%s)", name, c.zoneID)) diff --git a/cmd/devp2p/dns_route53_test.go b/cmd/devp2p/dns_route53_test.go index f6ab6c1762..a2ef3791f6 100644 --- a/cmd/devp2p/dns_route53_test.go +++ b/cmd/devp2p/dns_route53_test.go @@ -140,11 +140,23 @@ func TestRoute53ChangeSort(t *testing.T) { t.Fatalf("wrong changes (got %d, want %d)", len(changes), len(wantChanges)) } + // Check splitting according to size. wantSplit := [][]*route53.Change{ wantChanges[:4], - wantChanges[4:8], + wantChanges[4:6], + wantChanges[6:], } - split := splitChanges(changes, 600) + split := splitChanges(changes, 600, 4000) + if !reflect.DeepEqual(split, wantSplit) { + t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) + } + + // Check splitting according to count. + wantSplit = [][]*route53.Change{ + wantChanges[:5], + wantChanges[5:], + } + split = splitChanges(changes, 10000, 6) if !reflect.DeepEqual(split, wantSplit) { t.Fatalf("wrong split batches: got %d, want %d", len(split), len(wantSplit)) } From d7851e635936dc71f649b8836d0afa2e8764edd6 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Fri, 27 Mar 2020 13:52:53 +0100 Subject: [PATCH 045/103] graphql, node, rpc: fix typos in comments (#20824) --- graphql/service.go | 2 +- node/node.go | 6 +++--- rpc/endpoints.go | 1 - rpc/http.go | 4 ++-- rpc/subscription.go | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/graphql/service.go b/graphql/service.go index f640756806..21892402db 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -35,7 +35,7 @@ type Service struct { cors []string // Allowed CORS domains vhosts []string // Recognised vhosts timeouts rpc.HTTPTimeouts // Timeout settings for HTTP requests. - backend ethapi.Backend // The backend that queries will operate onn. + backend ethapi.Backend // The backend that queries will operate on. handler http.Handler // The `http.Handler` used to answer queries. listener net.Listener // The listening socket. } diff --git a/node/node.go b/node/node.go index 7d8f9b07b9..39e15e0a74 100644 --- a/node/node.go +++ b/node/node.go @@ -158,7 +158,7 @@ func (n *Node) Register(constructor ServiceConstructor) error { return nil } -// Start create a live P2P node and starts running it. +// Start creates a live P2P node and starts running it. func (n *Node) Start() error { n.lock.Lock() defer n.lock.Unlock() @@ -235,7 +235,7 @@ func (n *Node) Start() error { // Mark the service started for potential cleanup started = append(started, kind) } - // Lastly start the configured RPC interfaces + // Lastly, start the configured RPC interfaces if err := n.startRPC(services); err != nil { for _, service := range services { service.Stop() @@ -274,7 +274,7 @@ func (n *Node) openDataDir() error { return nil } -// startRPC is a helper method to start all the various RPC endpoint during node +// startRPC is a helper method to start all the various RPC endpoints during node // startup. It's not meant to be called at any time afterwards as it makes certain // assumptions about the state of the node. func (n *Node) startRPC(services map[reflect.Type]Service) error { diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 09f389d71b..07aaa44122 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -103,7 +103,6 @@ func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins [] } go NewWSServer(wsOrigins, handler).Serve(listener) return listener, handler, err - } // StartIPCEndpoint starts an IPC endpoint. diff --git a/rpc/http.go b/rpc/http.go index 40810c7b44..1cf6d90393 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -250,8 +250,8 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), code) return } - // All checks passed, create a codec that reads direct from the request body - // until EOF, write the response to w, and order the server to process a + // All checks passed, create a codec that reads directly from the request body + // until EOF, writes the response to w, and orders the server to process a // single request. ctx := r.Context() ctx = context.WithValue(ctx, "remote", r.RemoteAddr) diff --git a/rpc/subscription.go b/rpc/subscription.go index 8992bfc5e1..f9d60dcb98 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -153,7 +153,7 @@ func (n *Notifier) takeSubscription() *Subscription { return n.sub } -// acticate is called after the subscription ID was sent to client. Notifications are +// activate is called after the subscription ID was sent to client. Notifications are // buffered before activation. This prevents notifications being sent to the client before // the subscription ID is sent to the client. func (n *Notifier) activate() error { @@ -179,7 +179,7 @@ func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { }) } -// A Subscription is created by a notifier and tight to that notifier. The client can use +// A Subscription is created by a notifier and tied to that notifier. The client can use // this subscription to wait for an unsubscribe request for the client, see Err(). type Subscription struct { ID ID From d6c5f2417c03e802f312d86fc40f33b53803c3eb Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 27 Mar 2020 14:03:20 +0100 Subject: [PATCH 046/103] eth: improve shutdown synchronization (#20695) * eth: improve shutdown synchronization Most goroutines started by eth.Ethereum didn't have any shutdown sync at all, which lead to weird error messages when quitting the client. This change improves the clean shutdown path by stopping all internal components in dependency order and waiting for them to actually be stopped before shutdown is considered done. In particular, we now stop everything related to peers before stopping 'resident' parts such as core.BlockChain. * eth: rewrite sync controller * eth: remove sync start debug message * eth: notify chainSyncer about new peers after handshake * eth: move downloader.Cancel call into chainSyncer * eth: make post-sync block broadcast synchronous * eth: add comments * core: change blockchain stop message * eth: change closeBloomHandler channel type --- core/blockchain.go | 2 +- eth/backend.go | 44 +++++------ eth/bloombits.go | 2 +- eth/handler.go | 95 +++++++++++----------- eth/helper_test.go | 15 +--- eth/protocol_test.go | 2 +- eth/sync.go | 182 +++++++++++++++++++++++++++++++------------ eth/sync_test.go | 9 ++- 8 files changed, 213 insertions(+), 138 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 1da899ab43..d6f732194e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -897,7 +897,7 @@ func (bc *BlockChain) Stop() { log.Error("Dangling trie nodes after full cleanup") } } - log.Info("Blockchain manager stopped") + log.Info("Blockchain stopped") } func (bc *BlockChain) procFutureBlocks() { diff --git a/eth/backend.go b/eth/backend.go index ed79340f59..589773f81a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -67,9 +67,6 @@ type LesServer interface { type Ethereum struct { config *Config - // Channel for shutting down the service - shutdownChan chan bool - // Handlers txPool *core.TxPool blockchain *core.BlockChain @@ -84,8 +81,9 @@ type Ethereum struct { engine consensus.Engine accountManager *accounts.Manager - bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests - bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports + bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests + bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports + closeBloomHandler chan struct{} APIBackend *EthAPIBackend @@ -145,17 +143,17 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { log.Info("Initialised chain configuration", "config", chainConfig) eth := &Ethereum{ - config: config, - chainDb: chainDb, - eventMux: ctx.EventMux, - accountManager: ctx.AccountManager, - engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), - shutdownChan: make(chan bool), - networkID: config.NetworkId, - gasPrice: config.Miner.GasPrice, - etherbase: config.Miner.Etherbase, - bloomRequests: make(chan chan *bloombits.Retrieval), - bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), + config: config, + chainDb: chainDb, + eventMux: ctx.EventMux, + accountManager: ctx.AccountManager, + engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb), + closeBloomHandler: make(chan struct{}), + networkID: config.NetworkId, + gasPrice: config.Miner.GasPrice, + etherbase: config.Miner.Etherbase, + bloomRequests: make(chan chan *bloombits.Retrieval), + bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, params.BloomConfirms), } bcVersion := rawdb.ReadDatabaseVersion(chainDb) @@ -557,18 +555,20 @@ func (s *Ethereum) Start(srvr *p2p.Server) error { // Stop implements node.Service, terminating all internal goroutines used by the // Ethereum protocol. func (s *Ethereum) Stop() error { - s.bloomIndexer.Close() - s.blockchain.Stop() - s.engine.Close() + // Stop all the peer-related stuff first. s.protocolManager.Stop() if s.lesServer != nil { s.lesServer.Stop() } + + // Then stop everything else. + s.bloomIndexer.Close() + close(s.closeBloomHandler) s.txPool.Stop() s.miner.Stop() - s.eventMux.Stop() - + s.blockchain.Stop() + s.engine.Close() s.chainDb.Close() - close(s.shutdownChan) + s.eventMux.Stop() return nil } diff --git a/eth/bloombits.go b/eth/bloombits.go index 9a31997d60..35522b9bfa 100644 --- a/eth/bloombits.go +++ b/eth/bloombits.go @@ -54,7 +54,7 @@ func (eth *Ethereum) startBloomHandlers(sectionSize uint64) { go func() { for { select { - case <-eth.shutdownChan: + case <-eth.closeBloomHandler: return case request := <-eth.bloomRequests: diff --git a/eth/handler.go b/eth/handler.go index 236e50729c..9a02f1f20f 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -87,14 +87,12 @@ type ProtocolManager struct { whitelist map[uint64]common.Hash // channels for fetcher, syncer, txsyncLoop - newPeerCh chan *peer - txsyncCh chan *txsync - quitSync chan struct{} - noMorePeers chan struct{} + txsyncCh chan *txsync + quitSync chan struct{} - // wait group is used for graceful shutdowns during downloading - // and processing - wg sync.WaitGroup + chainSync *chainSyncer + wg sync.WaitGroup + peerWG sync.WaitGroup // Test fields or hooks broadcastTxAnnouncesOnly bool // Testing field, disable transaction propagation @@ -105,18 +103,17 @@ type ProtocolManager struct { func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCheckpoint, mode downloader.SyncMode, networkID uint64, mux *event.TypeMux, txpool txPool, engine consensus.Engine, blockchain *core.BlockChain, chaindb ethdb.Database, cacheLimit int, whitelist map[uint64]common.Hash) (*ProtocolManager, error) { // Create the protocol manager with the base fields manager := &ProtocolManager{ - networkID: networkID, - forkFilter: forkid.NewFilter(blockchain), - eventMux: mux, - txpool: txpool, - blockchain: blockchain, - peers: newPeerSet(), - whitelist: whitelist, - newPeerCh: make(chan *peer), - noMorePeers: make(chan struct{}), - txsyncCh: make(chan *txsync), - quitSync: make(chan struct{}), + networkID: networkID, + forkFilter: forkid.NewFilter(blockchain), + eventMux: mux, + txpool: txpool, + blockchain: blockchain, + peers: newPeerSet(), + whitelist: whitelist, + txsyncCh: make(chan *txsync), + quitSync: make(chan struct{}), } + if mode == downloader.FullSync { // The database seems empty as the current block is the genesis. Yet the fast // block is ahead, so fast sync was enabled for this node at a certain point. @@ -140,6 +137,7 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh manager.fastSync = uint32(1) } } + // If we have trusted checkpoints, enforce them on the chain if checkpoint != nil { manager.checkpointNumber = (checkpoint.SectionIndex+1)*params.CHTFrequency - 1 @@ -199,6 +197,8 @@ func NewProtocolManager(config *params.ChainConfig, checkpoint *params.TrustedCh } manager.txFetcher = fetcher.NewTxFetcher(txpool.Has, txpool.AddRemotes, fetchTx) + manager.chainSync = newChainSyncer(manager) + return manager, nil } @@ -213,15 +213,7 @@ func (pm *ProtocolManager) makeProtocol(version uint) p2p.Protocol { Version: version, Length: length, Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := pm.newPeer(int(version), p, rw, pm.txpool.Get) - select { - case pm.newPeerCh <- peer: - pm.wg.Add(1) - defer pm.wg.Done() - return pm.handle(peer) - case <-pm.quitSync: - return p2p.DiscQuitting - } + return pm.runPeer(pm.newPeer(int(version), p, rw, pm.txpool.Get)) }, NodeInfo: func() interface{} { return pm.NodeInfo() @@ -260,40 +252,37 @@ func (pm *ProtocolManager) Start(maxPeers int) { pm.maxPeers = maxPeers // broadcast transactions + pm.wg.Add(1) pm.txsCh = make(chan core.NewTxsEvent, txChanSize) pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh) go pm.txBroadcastLoop() // broadcast mined blocks + pm.wg.Add(1) pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{}) go pm.minedBroadcastLoop() // start sync handlers - go pm.syncer() + pm.wg.Add(2) + go pm.chainSync.loop() go pm.txsyncLoop64() // TODO(karalabe): Legacy initial tx echange, drop with eth/64. } func (pm *ProtocolManager) Stop() { - log.Info("Stopping Ethereum protocol") - pm.txsSub.Unsubscribe() // quits txBroadcastLoop pm.minedBlockSub.Unsubscribe() // quits blockBroadcastLoop - // Quit the sync loop. - // After this send has completed, no new peers will be accepted. - pm.noMorePeers <- struct{}{} - - // Quit fetcher, txsyncLoop. + // Quit chainSync and txsync64. + // After this is done, no new peers will be accepted. close(pm.quitSync) + pm.wg.Wait() // Disconnect existing sessions. // This also closes the gate for any new registrations on the peer set. // sessions which are already established but not added to pm.peers yet // will exit when they try to register. pm.peers.Close() - - // Wait for all peer handler goroutines and the loops to come down. - pm.wg.Wait() + pm.peerWG.Wait() log.Info("Ethereum protocol stopped") } @@ -302,6 +291,15 @@ func (pm *ProtocolManager) newPeer(pv int, p *p2p.Peer, rw p2p.MsgReadWriter, ge return newPeer(pv, p, rw, getPooledTx) } +func (pm *ProtocolManager) runPeer(p *peer) error { + if !pm.chainSync.handlePeerEvent(p) { + return p2p.DiscQuitting + } + pm.peerWG.Add(1) + defer pm.peerWG.Done() + return pm.handle(p) +} + // handle is the callback invoked to manage the life cycle of an eth peer. When // this function terminates, the peer is disconnected. func (pm *ProtocolManager) handle(p *peer) error { @@ -323,6 +321,7 @@ func (pm *ProtocolManager) handle(p *peer) error { p.Log().Debug("Ethereum handshake failed", "err", err) return err } + // Register the peer locally if err := pm.peers.Register(p); err != nil { p.Log().Error("Ethereum peer registration failed", "err", err) @@ -334,6 +333,8 @@ func (pm *ProtocolManager) handle(p *peer) error { if err := pm.downloader.RegisterPeer(p.id, p.version, p); err != nil { return err } + pm.chainSync.handlePeerEvent(p) + // Propagate existing transactions. new transactions appearing // after this will be sent via broadcasts. pm.syncTransactions(p) @@ -723,14 +724,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error { // Update the peer's total difficulty if better than the previous if _, td := p.Head(); trueTD.Cmp(td) > 0 { p.SetHead(trueHead, trueTD) - - // Schedule a sync if above ours. Note, this will not fire a sync for a gap of - // a single block (as the true TD is below the propagated block), however this - // scenario should easily be covered by the fetcher. - currentHeader := pm.blockchain.CurrentHeader() - if trueTD.Cmp(pm.blockchain.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64())) > 0 { - go pm.synchronise(p) - } + pm.chainSync.handlePeerEvent(p) } case msg.Code == NewPooledTransactionHashesMsg && p.version >= eth65: @@ -883,9 +877,10 @@ func (pm *ProtocolManager) BroadcastTransactions(txs types.Transactions, propaga } } -// Mined broadcast loop +// minedBroadcastLoop sends mined blocks to connected peers. func (pm *ProtocolManager) minedBroadcastLoop() { - // automatically stops if unsubscribe + defer pm.wg.Done() + for obj := range pm.minedBlockSub.Chan() { if ev, ok := obj.Data.(core.NewMinedBlockEvent); ok { pm.BroadcastBlock(ev.Block, true) // First propagate block to peers @@ -894,7 +889,10 @@ func (pm *ProtocolManager) minedBroadcastLoop() { } } +// txBroadcastLoop announces new transactions to connected peers. func (pm *ProtocolManager) txBroadcastLoop() { + defer pm.wg.Done() + for { select { case event := <-pm.txsCh: @@ -906,7 +904,6 @@ func (pm *ProtocolManager) txBroadcastLoop() { pm.BroadcastTransactions(event.Txs, true) // First propagate transactions to peers pm.BroadcastTransactions(event.Txs, false) // Only then announce to the rest - // Err() channel will be closed when unsubscribing. case <-pm.txsSub.Err(): return } diff --git a/eth/helper_test.go b/eth/helper_test.go index bec37e16cb..3338af71d5 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -170,23 +170,14 @@ func newTestPeer(name string, version int, pm *ProtocolManager, shake bool) (*te // Create a message pipe to communicate through app, net := p2p.MsgPipe() - // Generate a random id and create the peer + // Start the peer on a new thread var id enode.ID rand.Read(id[:]) - peer := pm.newPeer(version, p2p.NewPeer(id, name, nil), net, pm.txpool.Get) - - // Start the peer on a new thread errc := make(chan error, 1) - go func() { - select { - case pm.newPeerCh <- peer: - errc <- pm.handle(peer) - case <-pm.quitSync: - errc <- p2p.DiscQuitting - } - }() + go func() { errc <- pm.runPeer(peer) }() tp := &testPeer{app: app, net: net, peer: peer} + // Execute any implicitly requested handshakes and return if shake { var ( diff --git a/eth/protocol_test.go b/eth/protocol_test.go index 4bbfe9bd3c..a313e4e6cf 100644 --- a/eth/protocol_test.go +++ b/eth/protocol_test.go @@ -385,7 +385,7 @@ func testSyncTransaction(t *testing.T, propagtion bool) { go pmFetcher.handle(pmFetcher.newPeer(65, p2p.NewPeer(enode.ID{}, "fetcher", nil), io1, pmFetcher.txpool.Get)) time.Sleep(250 * time.Millisecond) - pmFetcher.synchronise(pmFetcher.peers.BestPeer()) + pmFetcher.doSync(peerToSyncOp(downloader.FullSync, pmFetcher.peers.BestPeer())) atomic.StoreUint32(&pmFetcher.acceptTxs, 1) newTxs := make(chan core.NewTxsEvent, 1024) diff --git a/eth/sync.go b/eth/sync.go index 0709706c91..d689200dce 100644 --- a/eth/sync.go +++ b/eth/sync.go @@ -17,6 +17,7 @@ package eth import ( + "math/big" "math/rand" "sync/atomic" "time" @@ -30,9 +31,9 @@ import ( const ( forceSyncCycle = 10 * time.Second // Time interval to force syncs, even if few peers are available - minDesiredPeerCount = 5 // Amount of peers desired to start syncing + defaultMinSyncPeers = 5 // Amount of peers desired to start syncing - // This is the target size for the packs of transactions sent by txsyncLoop. + // This is the target size for the packs of transactions sent by txsyncLoop64. // A pack can get larger than this if a single transactions exceeds this size. txsyncPackSize = 100 * 1024 ) @@ -81,12 +82,15 @@ func (pm *ProtocolManager) syncTransactions(p *peer) { // transactions. In order to minimise egress bandwidth usage, we send // the transactions in small packs to one peer at a time. func (pm *ProtocolManager) txsyncLoop64() { + defer pm.wg.Done() + var ( pending = make(map[enode.ID]*txsync) sending = false // whether a send is active pack = new(txsync) // the pack that is being sent done = make(chan error, 1) // result of the send ) + // send starts a sending a pack of transactions from the sync. send := func(s *txsync) { if s.p.version >= eth65 { @@ -149,73 +153,148 @@ func (pm *ProtocolManager) txsyncLoop64() { } } -// syncer is responsible for periodically synchronising with the network, both -// downloading hashes and blocks as well as handling the announcement handler. -func (pm *ProtocolManager) syncer() { - // Start and ensure cleanup of sync mechanisms - pm.blockFetcher.Start() - pm.txFetcher.Start() - defer pm.blockFetcher.Stop() - defer pm.txFetcher.Stop() - defer pm.downloader.Terminate() +// chainSyncer coordinates blockchain sync components. +type chainSyncer struct { + pm *ProtocolManager + force *time.Timer + forced bool // true when force timer fired + peerEventCh chan struct{} + doneCh chan error // non-nil when sync is running +} - // Wait for different events to fire synchronisation operations - forceSync := time.NewTicker(forceSyncCycle) - defer forceSync.Stop() +// chainSyncOp is a scheduled sync operation. +type chainSyncOp struct { + mode downloader.SyncMode + peer *peer + td *big.Int + head common.Hash +} + +// newChainSyncer creates a chainSyncer. +func newChainSyncer(pm *ProtocolManager) *chainSyncer { + return &chainSyncer{ + pm: pm, + peerEventCh: make(chan struct{}), + } +} + +// handlePeerEvent notifies the syncer about a change in the peer set. +// This is called for new peers and every time a peer announces a new +// chain head. +func (cs *chainSyncer) handlePeerEvent(p *peer) bool { + select { + case cs.peerEventCh <- struct{}{}: + return true + case <-cs.pm.quitSync: + return false + } +} + +// loop runs in its own goroutine and launches the sync when necessary. +func (cs *chainSyncer) loop() { + defer cs.pm.wg.Done() + + cs.pm.blockFetcher.Start() + cs.pm.txFetcher.Start() + defer cs.pm.blockFetcher.Stop() + defer cs.pm.txFetcher.Stop() + defer cs.pm.downloader.Terminate() + + // The force timer lowers the peer count threshold down to one when it fires. + // This ensures we'll always start sync even if there aren't enough peers. + cs.force = time.NewTimer(forceSyncCycle) + defer cs.force.Stop() for { + if op := cs.nextSyncOp(); op != nil { + cs.startSync(op) + } + select { - case <-pm.newPeerCh: - // Make sure we have peers to select from, then sync - if pm.peers.Len() < minDesiredPeerCount { - break + case <-cs.peerEventCh: + // Peer information changed, recheck. + case <-cs.doneCh: + cs.doneCh = nil + cs.force.Reset(forceSyncCycle) + cs.forced = false + case <-cs.force.C: + cs.forced = true + + case <-cs.pm.quitSync: + if cs.doneCh != nil { + cs.pm.downloader.Cancel() + <-cs.doneCh } - go pm.synchronise(pm.peers.BestPeer()) - - case <-forceSync.C: - // Force a sync even if not enough peers are present - go pm.synchronise(pm.peers.BestPeer()) - - case <-pm.noMorePeers: return } } } -// synchronise tries to sync up our local block chain with a remote peer. -func (pm *ProtocolManager) synchronise(peer *peer) { - // Short circuit if no peers are available - if peer == nil { - return +// nextSyncOp determines whether sync is required at this time. +func (cs *chainSyncer) nextSyncOp() *chainSyncOp { + if cs.doneCh != nil { + return nil // Sync already running. } - // Make sure the peer's TD is higher than our own - currentHeader := pm.blockchain.CurrentHeader() - td := pm.blockchain.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()) - pHead, pTd := peer.Head() - if pTd.Cmp(td) <= 0 { - return + // Ensure we're at mininum peer count. + minPeers := defaultMinSyncPeers + if cs.forced { + minPeers = 1 + } else if minPeers > cs.pm.maxPeers { + minPeers = cs.pm.maxPeers } - // Otherwise try to sync with the downloader - mode := downloader.FullSync - if atomic.LoadUint32(&pm.fastSync) == 1 { - // Fast sync was explicitly requested, and explicitly granted - mode = downloader.FastSync + if cs.pm.peers.Len() < minPeers { + return nil } - if mode == downloader.FastSync { - // Make sure the peer's total difficulty we are synchronizing is higher. - if pm.blockchain.GetTdByHash(pm.blockchain.CurrentFastBlock().Hash()).Cmp(pTd) >= 0 { - return - } + + // We have enough peers, check TD. + peer := cs.pm.peers.BestPeer() + if peer == nil { + return nil } - // Run the sync cycle, and disable fast sync if we've went past the pivot block - if err := pm.downloader.Synchronise(peer.id, pHead, pTd, mode); err != nil { - return + mode, ourTD := cs.modeAndLocalHead() + op := peerToSyncOp(mode, peer) + if op.td.Cmp(ourTD) <= 0 { + return nil // We're in sync. + } + return op +} + +func peerToSyncOp(mode downloader.SyncMode, p *peer) *chainSyncOp { + peerHead, peerTD := p.Head() + return &chainSyncOp{mode: mode, peer: p, td: peerTD, head: peerHead} +} + +func (cs *chainSyncer) modeAndLocalHead() (downloader.SyncMode, *big.Int) { + if atomic.LoadUint32(&cs.pm.fastSync) == 1 { + block := cs.pm.blockchain.CurrentFastBlock() + td := cs.pm.blockchain.GetTdByHash(block.Hash()) + return downloader.FastSync, td + } else { + head := cs.pm.blockchain.CurrentHeader() + td := cs.pm.blockchain.GetTd(head.Hash(), head.Number.Uint64()) + return downloader.FullSync, td + } +} + +// startSync launches doSync in a new goroutine. +func (cs *chainSyncer) startSync(op *chainSyncOp) { + cs.doneCh = make(chan error, 1) + go func() { cs.doneCh <- cs.pm.doSync(op) }() +} + +// doSync synchronizes the local blockchain with a remote peer. +func (pm *ProtocolManager) doSync(op *chainSyncOp) error { + // Run the sync cycle, and disable fast sync if we're past the pivot block + err := pm.downloader.Synchronise(op.peer.id, op.head, op.td, op.mode) + if err != nil { + return err } if atomic.LoadUint32(&pm.fastSync) == 1 { log.Info("Fast sync complete, auto disabling") atomic.StoreUint32(&pm.fastSync, 0) } + // If we've successfully finished a sync cycle and passed any required checkpoint, // enable accepting transactions from the network. head := pm.blockchain.CurrentBlock() @@ -226,6 +305,7 @@ func (pm *ProtocolManager) synchronise(peer *peer) { atomic.StoreUint32(&pm.acceptTxs, 1) } } + if head.NumberU64() > 0 { // We've completed a sync cycle, notify all peers of new state. This path is // essential in star-topology networks where a gateway node needs to notify @@ -233,6 +313,8 @@ func (pm *ProtocolManager) synchronise(peer *peer) { // scenario will most often crop up in private and hackathon networks with // degenerate connectivity, but it should be healthy for the mainnet too to // more reliably update peers or the local TD state. - go pm.BroadcastBlock(head, false) + pm.BroadcastBlock(head, false) } + + return nil } diff --git a/eth/sync_test.go b/eth/sync_test.go index d02bc57108..ac1e5fad1b 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -33,6 +33,8 @@ func TestFastSyncDisabling65(t *testing.T) { testFastSyncDisabling(t, 65) } // Tests that fast sync gets disabled as soon as a real block is successfully // imported into the blockchain. func testFastSyncDisabling(t *testing.T, protocol int) { + t.Parallel() + // Create a pristine protocol manager, check that fast sync is left enabled pmEmpty, _ := newTestProtocolManagerMust(t, downloader.FastSync, 0, nil, nil) if atomic.LoadUint32(&pmEmpty.fastSync) == 0 { @@ -43,14 +45,17 @@ func testFastSyncDisabling(t *testing.T, protocol int) { if atomic.LoadUint32(&pmFull.fastSync) == 1 { t.Fatalf("fast sync not disabled on non-empty blockchain") } + // Sync up the two peers io1, io2 := p2p.MsgPipe() - go pmFull.handle(pmFull.newPeer(protocol, p2p.NewPeer(enode.ID{}, "empty", nil), io2, pmFull.txpool.Get)) go pmEmpty.handle(pmEmpty.newPeer(protocol, p2p.NewPeer(enode.ID{}, "full", nil), io1, pmEmpty.txpool.Get)) time.Sleep(250 * time.Millisecond) - pmEmpty.synchronise(pmEmpty.peers.BestPeer()) + op := peerToSyncOp(downloader.FastSync, pmEmpty.peers.BestPeer()) + if err := pmEmpty.doSync(op); err != nil { + t.Fatal("sync failed:", err) + } // Check that fast sync was disabled if atomic.LoadUint32(&pmEmpty.fastSync) == 1 { From 62cd943c7bfefd810ed8175703f79224f8a2f77b Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 27 Mar 2020 23:21:58 +0800 Subject: [PATCH 047/103] les: fix dead lock (#20828) --- les/peer.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/les/peer.go b/les/peer.go index ff29831959..d308fd249e 100644 --- a/les/peer.go +++ b/les/peer.go @@ -531,8 +531,6 @@ func (p *serverPeer) getTxRelayCost(amount, size int) uint64 { // HasBlock checks if the peer has a given block func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bool { p.lock.RLock() - defer p.lock.RUnlock() - head := p.headInfo.Number var since, recent uint64 if hasState { @@ -543,6 +541,7 @@ func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bo recent = p.chainRecent } hasBlock := p.hasBlock + p.lock.RUnlock() return head >= number && number >= since && (recent == 0 || number+recent+4 > head) && hasBlock != nil && hasBlock(hash, number, hasState) } From 5d7e5b00be3eb04a334366d78b6ab742772c8e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ha=20=C4=90ANG?= Date: Fri, 27 Mar 2020 22:33:14 +0700 Subject: [PATCH 048/103] eth/filters: fix typo on unindexedLogs function's comment (#20827) --- eth/filters/filter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 2184092071..17635837af 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -210,7 +210,7 @@ func (f *Filter) indexedLogs(ctx context.Context, end uint64) ([]*types.Log, err } } -// indexedLogs returns the logs matching the filter criteria based on raw block +// unindexedLogs returns the logs matching the filter criteria based on raw block // iteration and bloom matching. func (f *Filter) unindexedLogs(ctx context.Context, end uint64) ([]*types.Log, error) { var logs []*types.Log From 55a73f556a67f496fbc78943929d91ce9832761e Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Mon, 30 Mar 2020 10:46:05 +0200 Subject: [PATCH 049/103] core: bump txpool tx max size to 128KB --- core/tx_pool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index e2137ca4f4..e4e3c41cee 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -48,7 +48,7 @@ const ( // non-trivial consequences: larger transactions are significantly harder and // more expensive to propagate; larger transactions also take more resources // to validate whether they fit into the pool or not. - txMaxSize = 2 * txSlotSize // 64KB, don't bump without EIP-2464 support + txMaxSize = 4 * txSlotSize // 128KB ) var ( From 76eed9e50d65e014acf0828588ed8f6ce1777f8d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 31 Mar 2020 10:25:41 +0200 Subject: [PATCH 050/103] snapshotter/tests: verify snapdb post-state against trie (#20812) * core/state/snapshot: basic trie-to-hash implementation * tests: validate snapshot after test * core/state/snapshot: fix review concerns --- core/blockchain.go | 9 +++ core/state/snapshot/conversion.go | 114 ++++++++++++++++++++++++++++++ tests/block_test_util.go | 14 ++++ 3 files changed, 137 insertions(+) create mode 100644 core/state/snapshot/conversion.go diff --git a/core/blockchain.go b/core/blockchain.go index d6f732194e..0d1c27f95e 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -520,6 +520,15 @@ func (bc *BlockChain) CurrentBlock() *types.Block { return bc.currentBlock.Load().(*types.Block) } +// Snapshot returns the blockchain snapshot tree. This method is mainly used for +// testing, to make it possible to verify the snapshot after execution. +// +// Warning: There are no guarantees about the safety of using the returned 'snap' if the +// blockchain is simultaneously importing blocks, so take care. +func (bc *BlockChain) Snapshot() *snapshot.Tree { + return bc.snaps +} + // CurrentFastBlock retrieves the current fast-sync head block of the canonical // chain. The block is retrieved from the blockchain's internal cache. func (bc *BlockChain) CurrentFastBlock() *types.Block { diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go new file mode 100644 index 0000000000..d9c86e5163 --- /dev/null +++ b/core/state/snapshot/conversion.go @@ -0,0 +1,114 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package snapshot + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" +) + +// conversionAccount is used for converting between full and slim format. When +// doing this, we can consider 'balance' as a byte array, as it has already +// been converted from big.Int into an rlp-byteslice. +type conversionAccount struct { + Nonce uint64 + Balance []byte + Root []byte + CodeHash []byte +} + +// SlimToFull converts data on the 'slim RLP' format into the full RLP-format +func SlimToFull(data []byte) ([]byte, error) { + acc := &conversionAccount{} + if err := rlp.DecodeBytes(data, acc); err != nil { + return nil, err + } + if len(acc.Root) == 0 { + acc.Root = emptyRoot[:] + } + if len(acc.CodeHash) == 0 { + acc.CodeHash = emptyCode[:] + } + fullData, err := rlp.EncodeToBytes(acc) + if err != nil { + return nil, err + } + return fullData, nil +} + +// trieKV represents a trie key-value pair +type trieKV struct { + key common.Hash + value []byte +} + +type trieGeneratorFn func(in chan (trieKV), out chan (common.Hash)) + +// GenerateTrieRoot takes an account iterator and reproduces the root hash. +func GenerateTrieRoot(it AccountIterator) common.Hash { + return generateTrieRoot(it, stdGenerate) +} + +func generateTrieRoot(it AccountIterator, generatorFn trieGeneratorFn) common.Hash { + var ( + in = make(chan trieKV) // chan to pass leaves + out = make(chan common.Hash) // chan to collect result + wg sync.WaitGroup + ) + wg.Add(1) + go func() { + generatorFn(in, out) + wg.Done() + }() + // Feed leaves + start := time.Now() + logged := time.Now() + accounts := 0 + for it.Next() { + slimData := it.Account() + fullData, _ := SlimToFull(slimData) + l := trieKV{it.Hash(), fullData} + in <- l + if time.Since(logged) > 8*time.Second { + log.Info("Generating trie hash from snapshot", + "at", l.key, "accounts", accounts, "elapsed", time.Since(start)) + logged = time.Now() + } + accounts++ + } + close(in) + result := <-out + log.Info("Generated trie hash from snapshot", "accounts", accounts, "elapsed", time.Since(start)) + wg.Wait() + return result +} + +// stdGenerate is a very basic hexary trie builder which uses the same Trie +// as the rest of geth, with no enhancements or optimizations +func stdGenerate(in chan (trieKV), out chan (common.Hash)) { + t, _ := trie.New(common.Hash{}, trie.NewDatabase(memorydb.New())) + for leaf := range in { + t.TryUpdate(leaf.key[:], leaf.value) + } + out <- t.Hash() +} diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 1ae986e3ca..37f63f538a 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/state/snapshot" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" @@ -144,6 +145,19 @@ func (t *BlockTest) Run(snapshotter bool) error { if err = t.validatePostState(newDB); err != nil { return fmt.Errorf("post state validation failed: %v", err) } + // Cross-check the snapshot-to-hash against the trie hash + if snapshotter { + snapTree := chain.Snapshot() + root := chain.CurrentBlock().Root() + it, err := snapTree.AccountIterator(root, common.Hash{}) + if err != nil { + return fmt.Errorf("Could not create iterator for root %x: %v", root, err) + } + generatedRoot := snapshot.GenerateTrieRoot(it) + if generatedRoot != root { + return fmt.Errorf("Snapshot corruption, got %d exp %d", generatedRoot, root) + } + } return t.validateImportedHeaders(chain, validBlocks) } From 8f05cfa1227c3d013ec86ec24c336731768ef22b Mon Sep 17 00:00:00 2001 From: Hanjiang Yu <42531996+de1acr0ix@users.noreply.github.com> Date: Tue, 31 Mar 2020 16:44:04 +0800 Subject: [PATCH 051/103] cmd, consensus: add option to disable mmap for DAG caches/datasets (#20484) * cmd, consensus: add option to disable mmap for DAG caches/datasets * consensus: add benchmarks for mmap with/with lock --- cmd/geth/main.go | 2 + cmd/geth/retesteth.go | 12 +++--- cmd/geth/usage.go | 2 + cmd/utils/flags.go | 28 +++++++++++--- consensus/ethash/algorithm_test.go | 27 +++++++++++++- consensus/ethash/ethash.go | 59 +++++++++++++++++------------- eth/backend.go | 14 ++++--- eth/config.go | 12 +++--- 8 files changed, 108 insertions(+), 48 deletions(-) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3615a91660..e276e86c6a 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -74,9 +74,11 @@ var ( utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, + utils.EthashCachesLockMmapFlag, utils.EthashDatasetDirFlag, utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsOnDiskFlag, + utils.EthashDatasetsLockMmapFlag, utils.TxPoolLocalsFlag, utils.TxPoolNoLocalsFlag, utils.TxPoolJournalFlag, diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 629c2ea7f2..05331d12dd 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -387,11 +387,13 @@ func (api *RetestethAPI) SetChainParams(ctx context.Context, chainParams ChainPa inner = ethash.NewFaker() case "Ethash": inner = ethash.New(ethash.Config{ - CacheDir: "ethash", - CachesInMem: 2, - CachesOnDisk: 3, - DatasetsInMem: 1, - DatasetsOnDisk: 2, + CacheDir: "ethash", + CachesInMem: 2, + CachesOnDisk: 3, + CachesLockMmap: false, + DatasetsInMem: 1, + DatasetsOnDisk: 2, + DatasetsLockMmap: false, }, nil, false) default: return false, fmt.Errorf("unrecognised seal engine: %s", chainParams.SealEngine) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index f2f3b5756b..51e9f91848 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -109,9 +109,11 @@ var AppHelpFlagGroups = []flagGroup{ utils.EthashCacheDirFlag, utils.EthashCachesInMemoryFlag, utils.EthashCachesOnDiskFlag, + utils.EthashCachesLockMmapFlag, utils.EthashDatasetDirFlag, utils.EthashDatasetsInMemoryFlag, utils.EthashDatasetsOnDiskFlag, + utils.EthashDatasetsLockMmapFlag, }, }, { diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cbbe530701..dbefc9d935 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -305,6 +305,10 @@ var ( Usage: "Number of recent ethash caches to keep on disk (16MB each)", Value: eth.DefaultConfig.Ethash.CachesOnDisk, } + EthashCachesLockMmapFlag = cli.BoolFlag{ + Name: "ethash.cacheslockmmap", + Usage: "Lock memory maps of recent ethash caches", + } EthashDatasetDirFlag = DirectoryFlag{ Name: "ethash.dagdir", Usage: "Directory to store the ethash mining DAGs", @@ -320,6 +324,10 @@ var ( Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)", Value: eth.DefaultConfig.Ethash.DatasetsOnDisk, } + EthashDatasetsLockMmapFlag = cli.BoolFlag{ + Name: "ethash.dagslockmmap", + Usage: "Lock memory maps for recent ethash mining DAGs", + } // Transaction pool settings TxPoolLocalsFlag = cli.StringFlag{ Name: "txpool.locals", @@ -1306,12 +1314,18 @@ func setEthash(ctx *cli.Context, cfg *eth.Config) { if ctx.GlobalIsSet(EthashCachesOnDiskFlag.Name) { cfg.Ethash.CachesOnDisk = ctx.GlobalInt(EthashCachesOnDiskFlag.Name) } + if ctx.GlobalIsSet(EthashCachesLockMmapFlag.Name) { + cfg.Ethash.CachesLockMmap = ctx.GlobalBool(EthashCachesLockMmapFlag.Name) + } if ctx.GlobalIsSet(EthashDatasetsInMemoryFlag.Name) { cfg.Ethash.DatasetsInMem = ctx.GlobalInt(EthashDatasetsInMemoryFlag.Name) } if ctx.GlobalIsSet(EthashDatasetsOnDiskFlag.Name) { cfg.Ethash.DatasetsOnDisk = ctx.GlobalInt(EthashDatasetsOnDiskFlag.Name) } + if ctx.GlobalIsSet(EthashDatasetsLockMmapFlag.Name) { + cfg.Ethash.DatasetsLockMmap = ctx.GlobalBool(EthashDatasetsLockMmapFlag.Name) + } } func setMiner(ctx *cli.Context, cfg *miner.Config) { @@ -1721,12 +1735,14 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai engine = ethash.NewFaker() if !ctx.GlobalBool(FakePoWFlag.Name) { engine = ethash.New(ethash.Config{ - CacheDir: stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir), - CachesInMem: eth.DefaultConfig.Ethash.CachesInMem, - CachesOnDisk: eth.DefaultConfig.Ethash.CachesOnDisk, - DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir), - DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem, - DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk, + CacheDir: stack.ResolvePath(eth.DefaultConfig.Ethash.CacheDir), + CachesInMem: eth.DefaultConfig.Ethash.CachesInMem, + CachesOnDisk: eth.DefaultConfig.Ethash.CachesOnDisk, + CachesLockMmap: eth.DefaultConfig.Ethash.CachesLockMmap, + DatasetDir: stack.ResolvePath(eth.DefaultConfig.Ethash.DatasetDir), + DatasetsInMem: eth.DefaultConfig.Ethash.DatasetsInMem, + DatasetsOnDisk: eth.DefaultConfig.Ethash.DatasetsOnDisk, + DatasetsLockMmap: eth.DefaultConfig.Ethash.DatasetsLockMmap, }, nil, false) } } diff --git a/consensus/ethash/algorithm_test.go b/consensus/ethash/algorithm_test.go index f4f65047be..51fb6b124d 100644 --- a/consensus/ethash/algorithm_test.go +++ b/consensus/ethash/algorithm_test.go @@ -729,7 +729,7 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { go func(idx int) { defer pend.Done() - ethash := New(Config{cachedir, 0, 1, "", 0, 0, ModeNormal, nil}, nil, false) + ethash := New(Config{cachedir, 0, 1, false, "", 0, 0, false, ModeNormal, nil}, nil, false) defer ethash.Close() if err := ethash.VerifySeal(nil, block.Header()); err != nil { t.Errorf("proc %d: block verification failed: %v", idx, err) @@ -787,3 +787,28 @@ func BenchmarkHashimotoFullSmall(b *testing.B) { hashimotoFull(dataset, hash, 0) } } + +func benchmarkHashimotoFullMmap(b *testing.B, name string, lock bool) { + b.Run(name, func(b *testing.B) { + tmpdir, err := ioutil.TempDir("", "ethash-test") + if err != nil { + b.Fatal(err) + } + defer os.RemoveAll(tmpdir) + + d := &dataset{epoch: 0} + d.generate(tmpdir, 1, lock, false) + var hash [common.HashLength]byte + b.ResetTimer() + for i := 0; i < b.N; i++ { + binary.PutVarint(hash[:], int64(i)) + hashimotoFull(d.dataset, hash[:], 0) + } + }) +} + +// Benchmarks the full verification performance for mmap +func BenchmarkHashimotoFullMmap(b *testing.B) { + benchmarkHashimotoFullMmap(b, "WithLock", true) + benchmarkHashimotoFullMmap(b, "WithoutLock", false) +} diff --git a/consensus/ethash/ethash.go b/consensus/ethash/ethash.go index 4bf78b0a1d..31bdceb4c3 100644 --- a/consensus/ethash/ethash.go +++ b/consensus/ethash/ethash.go @@ -48,7 +48,7 @@ var ( two256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) // sharedEthash is a full instance that can be shared between multiple users. - sharedEthash = New(Config{"", 3, 0, "", 1, 0, ModeNormal, nil}, nil, false) + sharedEthash = New(Config{"", 3, 0, false, "", 1, 0, false, ModeNormal, nil}, nil, false) // algorithmRevision is the data structure version used for file naming. algorithmRevision = 23 @@ -65,7 +65,7 @@ func isLittleEndian() bool { } // memoryMap tries to memory map a file of uint32s for read only access. -func memoryMap(path string) (*os.File, mmap.MMap, []uint32, error) { +func memoryMap(path string, lock bool) (*os.File, mmap.MMap, []uint32, error) { file, err := os.OpenFile(path, os.O_RDONLY, 0644) if err != nil { return nil, nil, nil, err @@ -82,6 +82,13 @@ func memoryMap(path string) (*os.File, mmap.MMap, []uint32, error) { return nil, nil, nil, ErrInvalidDumpMagic } } + if lock { + if err := mem.Lock(); err != nil { + mem.Unmap() + file.Close() + return nil, nil, nil, err + } + } return file, mem, buffer[len(dumpMagic):], err } @@ -107,7 +114,7 @@ func memoryMapFile(file *os.File, write bool) (mmap.MMap, []uint32, error) { // memoryMapAndGenerate tries to memory map a temporary file of uint32s for write // access, fill it with the data from a generator and then move it into the final // path requested. -func memoryMapAndGenerate(path string, size uint64, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) { +func memoryMapAndGenerate(path string, size uint64, lock bool, generator func(buffer []uint32)) (*os.File, mmap.MMap, []uint32, error) { // Ensure the data folder exists if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return nil, nil, nil, err @@ -142,7 +149,7 @@ func memoryMapAndGenerate(path string, size uint64, generator func(buffer []uint if err := os.Rename(temp, path); err != nil { return nil, nil, nil, err } - return memoryMap(path) + return memoryMap(path, lock) } // lru tracks caches or datasets by their last use time, keeping at most N of them. @@ -213,7 +220,7 @@ func newCache(epoch uint64) interface{} { } // generate ensures that the cache content is generated before use. -func (c *cache) generate(dir string, limit int, test bool) { +func (c *cache) generate(dir string, limit int, lock bool, test bool) { c.once.Do(func() { size := cacheSize(c.epoch*epochLength + 1) seed := seedHash(c.epoch*epochLength + 1) @@ -240,7 +247,7 @@ func (c *cache) generate(dir string, limit int, test bool) { // Try to load the file from disk and memory map it var err error - c.dump, c.mmap, c.cache, err = memoryMap(path) + c.dump, c.mmap, c.cache, err = memoryMap(path, lock) if err == nil { logger.Debug("Loaded old ethash cache from disk") return @@ -248,7 +255,7 @@ func (c *cache) generate(dir string, limit int, test bool) { logger.Debug("Failed to load old ethash cache", "err", err) // No previous cache available, create a new cache file to fill - c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) }) + c.dump, c.mmap, c.cache, err = memoryMapAndGenerate(path, size, lock, func(buffer []uint32) { generateCache(buffer, c.epoch, seed) }) if err != nil { logger.Error("Failed to generate mapped ethash cache", "err", err) @@ -290,7 +297,7 @@ func newDataset(epoch uint64) interface{} { } // generate ensures that the dataset content is generated before use. -func (d *dataset) generate(dir string, limit int, test bool) { +func (d *dataset) generate(dir string, limit int, lock bool, test bool) { d.once.Do(func() { // Mark the dataset generated after we're done. This is needed for remote defer atomic.StoreUint32(&d.done, 1) @@ -326,7 +333,7 @@ func (d *dataset) generate(dir string, limit int, test bool) { // Try to load the file from disk and memory map it var err error - d.dump, d.mmap, d.dataset, err = memoryMap(path) + d.dump, d.mmap, d.dataset, err = memoryMap(path, lock) if err == nil { logger.Debug("Loaded old ethash dataset from disk") return @@ -337,7 +344,7 @@ func (d *dataset) generate(dir string, limit int, test bool) { cache := make([]uint32, csize/4) generateCache(cache, d.epoch, seed) - d.dump, d.mmap, d.dataset, err = memoryMapAndGenerate(path, dsize, func(buffer []uint32) { generateDataset(buffer, d.epoch, cache) }) + d.dump, d.mmap, d.dataset, err = memoryMapAndGenerate(path, dsize, lock, func(buffer []uint32) { generateDataset(buffer, d.epoch, cache) }) if err != nil { logger.Error("Failed to generate mapped ethash dataset", "err", err) @@ -372,13 +379,13 @@ func (d *dataset) finalizer() { // MakeCache generates a new ethash cache and optionally stores it to disk. func MakeCache(block uint64, dir string) { c := cache{epoch: block / epochLength} - c.generate(dir, math.MaxInt32, false) + c.generate(dir, math.MaxInt32, false, false) } // MakeDataset generates a new ethash dataset and optionally stores it to disk. func MakeDataset(block uint64, dir string) { d := dataset{epoch: block / epochLength} - d.generate(dir, math.MaxInt32, false) + d.generate(dir, math.MaxInt32, false, false) } // Mode defines the type and amount of PoW verification an ethash engine makes. @@ -394,13 +401,15 @@ const ( // Config are the configuration parameters of the ethash. type Config struct { - CacheDir string - CachesInMem int - CachesOnDisk int - DatasetDir string - DatasetsInMem int - DatasetsOnDisk int - PowMode Mode + CacheDir string + CachesInMem int + CachesOnDisk int + CachesLockMmap bool + DatasetDir string + DatasetsInMem int + DatasetsOnDisk int + DatasetsLockMmap bool + PowMode Mode Log log.Logger `toml:"-"` } @@ -549,12 +558,12 @@ func (ethash *Ethash) cache(block uint64) *cache { current := currentI.(*cache) // Wait for generation finish. - current.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.PowMode == ModeTest) + current.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.CachesLockMmap, ethash.config.PowMode == ModeTest) // If we need a new future cache, now's a good time to regenerate it. if futureI != nil { future := futureI.(*cache) - go future.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.PowMode == ModeTest) + go future.generate(ethash.config.CacheDir, ethash.config.CachesOnDisk, ethash.config.CachesLockMmap, ethash.config.PowMode == ModeTest) } return current } @@ -574,20 +583,20 @@ func (ethash *Ethash) dataset(block uint64, async bool) *dataset { // If async is specified, generate everything in a background thread if async && !current.generated() { go func() { - current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) + current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) if futureI != nil { future := futureI.(*dataset) - future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) + future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) } }() } else { // Either blocking generation was requested, or already done - current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) + current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) if futureI != nil { future := futureI.(*dataset) - go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest) + go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.DatasetsLockMmap, ethash.config.PowMode == ModeTest) } } return current diff --git a/eth/backend.go b/eth/backend.go index 589773f81a..98eee91e1f 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -266,12 +266,14 @@ func CreateConsensusEngine(ctx *node.ServiceContext, chainConfig *params.ChainCo return ethash.NewShared() default: engine := ethash.New(ethash.Config{ - CacheDir: ctx.ResolvePath(config.CacheDir), - CachesInMem: config.CachesInMem, - CachesOnDisk: config.CachesOnDisk, - DatasetDir: config.DatasetDir, - DatasetsInMem: config.DatasetsInMem, - DatasetsOnDisk: config.DatasetsOnDisk, + CacheDir: ctx.ResolvePath(config.CacheDir), + CachesInMem: config.CachesInMem, + CachesOnDisk: config.CachesOnDisk, + CachesLockMmap: config.CachesLockMmap, + DatasetDir: config.DatasetDir, + DatasetsInMem: config.DatasetsInMem, + DatasetsOnDisk: config.DatasetsOnDisk, + DatasetsLockMmap: config.DatasetsLockMmap, }, notify, noverify) engine.SetThreads(-1) // Disable CPU mining return engine diff --git a/eth/config.go b/eth/config.go index 160ce8aa59..20480f742c 100644 --- a/eth/config.go +++ b/eth/config.go @@ -37,11 +37,13 @@ import ( var DefaultConfig = Config{ SyncMode: downloader.FastSync, Ethash: ethash.Config{ - CacheDir: "ethash", - CachesInMem: 2, - CachesOnDisk: 3, - DatasetsInMem: 1, - DatasetsOnDisk: 2, + CacheDir: "ethash", + CachesInMem: 2, + CachesOnDisk: 3, + CachesLockMmap: false, + DatasetsInMem: 1, + DatasetsOnDisk: 2, + DatasetsLockMmap: false, }, NetworkId: 1, LightPeers: 100, From c56f4fa808c218e7e69837596d31aa9e444666a8 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 31 Mar 2020 12:03:48 +0200 Subject: [PATCH 052/103] cmd/clef: add newaccount command (#20782) * cmd/clef: add newaccount command * cmd/clef: document clef_New, update API versioning * Update cmd/clef/intapi_changelog.md Co-Authored-By: ligi * Update signer/core/uiapi.go Co-Authored-By: ligi Co-authored-by: ligi --- cmd/clef/intapi_changelog.md | 11 +++++++++ cmd/clef/main.go | 48 ++++++++++++++++++++++++++++++++++-- signer/core/api.go | 14 ++++++++--- signer/core/uiapi.go | 10 ++++++++ 4 files changed, 78 insertions(+), 5 deletions(-) diff --git a/cmd/clef/intapi_changelog.md b/cmd/clef/intapi_changelog.md index 38424f06b9..f7e6993cf5 100644 --- a/cmd/clef/intapi_changelog.md +++ b/cmd/clef/intapi_changelog.md @@ -10,6 +10,17 @@ TL;DR: Given a version number MAJOR.MINOR.PATCH, increment the: Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format. +### 7.0.1 + +Added `clef_New` to the internal API calleable from a UI. + +> `New` creates a new password protected Account. The private key is protected with +> the given password. Users are responsible to backup the private key that is stored +> in the keystore location that was specified when this API was created. +> This method is the same as New on the external API, the difference being that +> this implementation does not ask for confirmation, since it's initiated by +> the user + ### 7.0.0 - The `message` field was renamed to `messages` in all data signing request methods to better reflect that it's a list, not a value. diff --git a/cmd/clef/main.go b/cmd/clef/main.go index dd898871d5..f4533a6e85 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -187,6 +187,21 @@ The setpw command stores a password for a given address (keyfile). Description: ` The delpw command removes a password for a given address (keyfile). `} + newAccountCommand = cli.Command{ + Action: utils.MigrateFlags(newAccount), + Name: "newaccount", + Usage: "Create a new account", + ArgsUsage: "", + Flags: []cli.Flag{ + logLevelFlag, + keystoreFlag, + utils.LightKDFFlag, + }, + Description: ` +The newaccount command creates a new keystore-backed account. It is a convenience-method +which can be used in lieu of an external UI.`, + } + gendocCommand = cli.Command{ Action: GenDoc, Name: "gendoc", @@ -222,7 +237,12 @@ func init() { advancedMode, } app.Action = signer - app.Commands = []cli.Command{initCommand, attestCommand, setCredentialCommand, delCredentialCommand, gendocCommand} + app.Commands = []cli.Command{initCommand, + attestCommand, + setCredentialCommand, + delCredentialCommand, + newAccountCommand, + gendocCommand} cli.CommandHelpTemplate = utils.OriginCommandHelpTemplate } @@ -382,6 +402,31 @@ func removeCredential(ctx *cli.Context) error { return nil } +func newAccount(c *cli.Context) error { + if err := initialize(c); err != nil { + return err + } + // The newaccount is meant for users using the CLI, since 'real' external + // UIs can use the UI-api instead. So we'll just use the native CLI UI here. + var ( + ui = core.NewCommandlineUI() + pwStorage storage.Storage = &storage.NoStorage{} + ksLoc = c.GlobalString(keystoreFlag.Name) + lightKdf = c.GlobalBool(utils.LightKDFFlag.Name) + ) + log.Info("Starting clef", "keystore", ksLoc, "light-kdf", lightKdf) + am := core.StartClefAccountManager(ksLoc, true, lightKdf, "") + // This gives is us access to the external API + apiImpl := core.NewSignerAPI(am, 0, true, ui, nil, false, pwStorage) + // This gives us access to the internal API + internalApi := core.NewUIServerAPI(apiImpl) + addr, err := internalApi.New(context.Background()) + if err == nil { + fmt.Printf("Generated account %v\n", addr.String()) + } + return err +} + func initialize(c *cli.Context) error { // Set up the logger to print everything logOutput := os.Stdout @@ -457,7 +502,6 @@ func signer(c *cli.Context) error { api core.ExternalAPI pwStorage storage.Storage = &storage.NoStorage{} ) - configDir := c.GlobalString(configdirFlag.Name) if stretchedKey, err := readMasterKey(c, ui); err != nil { log.Warn("Failed to open master, rules disabled", "err", err) diff --git a/signer/core/api.go b/signer/core/api.go index c10bf578d2..7e6ece997f 100644 --- a/signer/core/api.go +++ b/signer/core/api.go @@ -43,7 +43,7 @@ const ( // ExternalAPIVersion -- see extapi_changelog.md ExternalAPIVersion = "6.0.0" // InternalAPIVersion -- see intapi_changelog.md - InternalAPIVersion = "7.0.0" + InternalAPIVersion = "7.0.1" ) // ExternalAPI defines the external API through which signing requests are made. @@ -395,8 +395,7 @@ func (api *SignerAPI) List(ctx context.Context) ([]common.Address, error) { // the given password. Users are responsible to backup the private key that is stored // in the keystore location thas was specified when this API was created. func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { - be := api.am.Backends(keystore.KeyStoreType) - if len(be) == 0 { + if be := api.am.Backends(keystore.KeyStoreType); len(be) == 0 { return common.Address{}, errors.New("password based accounts not supported") } if resp, err := api.UI.ApproveNewAccount(&NewAccountRequest{MetadataFromContext(ctx)}); err != nil { @@ -404,7 +403,16 @@ func (api *SignerAPI) New(ctx context.Context) (common.Address, error) { } else if !resp.Approved { return common.Address{}, ErrRequestDenied } + return api.newAccount() +} +// newAccount is the internal method to create a new account. It should be used +// _after_ user-approval has been obtained +func (api *SignerAPI) newAccount() (common.Address, error) { + be := api.am.Backends(keystore.KeyStoreType) + if len(be) == 0 { + return common.Address{}, errors.New("password based accounts not supported") + } // Three retries to get a valid password for i := 0; i < 3; i++ { resp, err := api.UI.OnInputRequired(UserInputRequest{ diff --git a/signer/core/uiapi.go b/signer/core/uiapi.go index 36735d37ee..25a587de56 100644 --- a/signer/core/uiapi.go +++ b/signer/core/uiapi.go @@ -195,6 +195,16 @@ func (api *UIServerAPI) Import(ctx context.Context, keyJSON json.RawMessage, old return be[0].(*keystore.KeyStore).Import(keyJSON, oldPassphrase, newPassphrase) } +// New creates a new password protected Account. The private key is protected with +// the given password. Users are responsible to backup the private key that is stored +// in the keystore location that was specified when this API was created. +// This method is the same as New on the external API, the difference being that +// this implementation does not ask for confirmation, since it's initiated by +// the user +func (api *UIServerAPI) New(ctx context.Context) (common.Address, error) { + return api.extApi.newAccount() +} + // Other methods to be added, not yet implemented are: // - Ruleset interaction: add rules, attest rulefiles // - Store metadata about accounts, e.g. naming of accounts From 03fe9de2cb87716dbabfecfb7e9bf1083d901e38 Mon Sep 17 00:00:00 2001 From: Wenbiao Zheng Date: Tue, 31 Mar 2020 18:08:44 +0800 Subject: [PATCH 053/103] eth: add debug_accountRange API (#19645) This new API allows reading accounts and their content by address range. Co-authored-by: Martin Holst Swende Co-authored-by: Felix Lange --- core/state/dump.go | 48 +++++++++++++++++++---- eth/api.go | 88 ++++++++++++++++------------------------- eth/api_test.go | 98 +++++++++++++--------------------------------- 3 files changed, 102 insertions(+), 132 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index a742cf85f5..ded9298ee2 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -27,7 +27,7 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -// DumpAccount represents an account in the state +// DumpAccount represents an account in the state. type DumpAccount struct { Balance string `json:"balance"` Nonce uint64 `json:"nonce"` @@ -40,17 +40,24 @@ type DumpAccount struct { } -// Dump represents the full dump in a collected format, as one large map +// Dump represents the full dump in a collected format, as one large map. type Dump struct { Root string `json:"root"` Accounts map[common.Address]DumpAccount `json:"accounts"` } -// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively +// iterativeDump is a 'collector'-implementation which dump output line-by-line iteratively. type iterativeDump struct { *json.Encoder } +// IteratorDump is an implementation for iterating over data. +type IteratorDump struct { + Root string `json:"root"` + Accounts map[common.Address]DumpAccount `json:"accounts"` + Next []byte `json:"next,omitempty"` // nil if no more accounts +} + // Collector interface which the state trie calls during iteration type collector interface { onRoot(common.Hash) @@ -64,6 +71,13 @@ func (d *Dump) onRoot(root common.Hash) { func (d *Dump) onAccount(addr common.Address, account DumpAccount) { d.Accounts[addr] = account } +func (d *IteratorDump) onRoot(root common.Hash) { + d.Root = fmt.Sprintf("%x", root) +} + +func (d *IteratorDump) onAccount(addr common.Address, account DumpAccount) { + d.Accounts[addr] = account +} func (d iterativeDump) onAccount(addr common.Address, account DumpAccount) { dumpAccount := &DumpAccount{ @@ -88,11 +102,13 @@ func (d iterativeDump) onRoot(root common.Hash) { }{root}) } -func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool) { +func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) (nextKey []byte) { emptyAddress := (common.Address{}) missingPreimages := 0 c.onRoot(s.trie.Hash()) - it := trie.NewIterator(s.trie.NodeIterator(nil)) + + var count int + it := trie.NewIterator(s.trie.NodeIterator(start)) for it.Next() { var data Account if err := rlp.DecodeBytes(it.Value, &data); err != nil { @@ -130,10 +146,19 @@ func (s *StateDB) dump(c collector, excludeCode, excludeStorage, excludeMissingP } } c.onAccount(addr, account) + count++ + if maxResults > 0 && count >= maxResults { + if it.Next() { + nextKey = it.Key + } + break + } } if missingPreimages > 0 { log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages) } + + return nextKey } // RawDump returns the entire state an a single large object @@ -141,7 +166,7 @@ func (s *StateDB) RawDump(excludeCode, excludeStorage, excludeMissingPreimages b dump := &Dump{ Accounts: make(map[common.Address]DumpAccount), } - s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages) + s.dump(dump, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) return *dump } @@ -157,5 +182,14 @@ func (s *StateDB) Dump(excludeCode, excludeStorage, excludeMissingPreimages bool // IterativeDump dumps out accounts as json-objects, delimited by linebreaks on stdout func (s *StateDB) IterativeDump(excludeCode, excludeStorage, excludeMissingPreimages bool, output *json.Encoder) { - s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages) + s.dump(iterativeDump{output}, excludeCode, excludeStorage, excludeMissingPreimages, nil, 0) +} + +// IteratorDump dumps out a batch of accounts starts with the given start key +func (s *StateDB) IteratorDump(excludeCode, excludeStorage, excludeMissingPreimages bool, start []byte, maxResults int) IteratorDump { + iterator := &IteratorDump{ + Accounts: make(map[common.Address]DumpAccount), + } + iterator.Next = s.dump(iterator, excludeCode, excludeStorage, excludeMissingPreimages, start, maxResults) + return *iterator } diff --git a/eth/api.go b/eth/api.go index a874582e19..b3415f923c 100644 --- a/eth/api.go +++ b/eth/api.go @@ -351,70 +351,50 @@ func (api *PrivateDebugAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, return results, nil } -// AccountRangeResult returns a mapping from the hash of an account addresses -// to its preimage. It will return the JSON null if no preimage is found. -// Since a query can return a limited amount of results, a "next" field is -// also present for paging. -type AccountRangeResult struct { - Accounts map[common.Hash]*common.Address `json:"accounts"` - Next common.Hash `json:"next"` -} - -func accountRange(st state.Trie, start *common.Hash, maxResults int) (AccountRangeResult, error) { - if start == nil { - start = &common.Hash{0} - } - it := trie.NewIterator(st.NodeIterator(start.Bytes())) - result := AccountRangeResult{Accounts: make(map[common.Hash]*common.Address), Next: common.Hash{}} - - if maxResults > AccountRangeMaxResults { - maxResults = AccountRangeMaxResults - } - - for i := 0; i < maxResults && it.Next(); i++ { - if preimage := st.GetKey(it.Key); preimage != nil { - addr := &common.Address{} - addr.SetBytes(preimage) - result.Accounts[common.BytesToHash(it.Key)] = addr - } else { - result.Accounts[common.BytesToHash(it.Key)] = nil - } - } - - if it.Next() { - result.Next = common.BytesToHash(it.Key) - } - - return result, nil -} - // AccountRangeMaxResults is the maximum number of results to be returned per call const AccountRangeMaxResults = 256 -// AccountRange enumerates all accounts in the latest state -func (api *PrivateDebugAPI) AccountRange(ctx context.Context, start *common.Hash, maxResults int) (AccountRangeResult, error) { - var statedb *state.StateDB +// AccountRangeAt enumerates all accounts in the given block and start point in paging request +func (api *PublicDebugAPI) AccountRange(blockNrOrHash rpc.BlockNumberOrHash, start []byte, maxResults int, nocode, nostorage, incompletes bool) (state.IteratorDump, error) { + var stateDb *state.StateDB var err error - block := api.eth.blockchain.CurrentBlock() - if len(block.Transactions()) == 0 { - statedb, err = api.computeStateDB(block, defaultTraceReexec) - if err != nil { - return AccountRangeResult{}, err + if number, ok := blockNrOrHash.Number(); ok { + if number == rpc.PendingBlockNumber { + // If we're dumping the pending state, we need to request + // both the pending block as well as the pending state from + // the miner and operate on those + _, stateDb = api.eth.miner.Pending() + } else { + var block *types.Block + if number == rpc.LatestBlockNumber { + block = api.eth.blockchain.CurrentBlock() + } else { + block = api.eth.blockchain.GetBlockByNumber(uint64(number)) + } + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block #%d not found", number) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) + if err != nil { + return state.IteratorDump{}, err + } } - } else { - _, _, statedb, err = api.computeTxEnv(block.Hash(), len(block.Transactions())-1, 0) + } else if hash, ok := blockNrOrHash.Hash(); ok { + block := api.eth.blockchain.GetBlockByHash(hash) + if block == nil { + return state.IteratorDump{}, fmt.Errorf("block %s not found", hash.Hex()) + } + stateDb, err = api.eth.BlockChain().StateAt(block.Root()) if err != nil { - return AccountRangeResult{}, err + return state.IteratorDump{}, err } } - trie, err := statedb.Database().OpenTrie(block.Header().Root) - if err != nil { - return AccountRangeResult{}, err + if maxResults > AccountRangeMaxResults || maxResults <= 0 { + maxResults = AccountRangeMaxResults } - - return accountRange(trie, start, maxResults) + return stateDb.IteratorDump(nocode, nostorage, incompletes, start, maxResults), nil } // StorageRangeResult is the result of a debug_storageRangeAt API call. @@ -431,7 +411,7 @@ type storageEntry struct { } // StorageRangeAt returns the storage at the given block height and transaction index. -func (api *PrivateDebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { +func (api *PrivateDebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0) if err != nil { return StorageRangeResult{}, err diff --git a/eth/api_test.go b/eth/api_test.go index ab846db3ea..42f71e261e 100644 --- a/eth/api_test.go +++ b/eth/api_test.go @@ -33,29 +33,24 @@ import ( var dumper = spew.ConfigState{Indent: " "} -func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start *common.Hash, requestedNum int, expectedNum int) AccountRangeResult { - result, err := accountRange(*trie, start, requestedNum) - if err != nil { - t.Fatal(err) - } +func accountRangeTest(t *testing.T, trie *state.Trie, statedb *state.StateDB, start common.Hash, requestedNum int, expectedNum int) state.IteratorDump { + result := statedb.IteratorDump(true, true, false, start.Bytes(), requestedNum) if len(result.Accounts) != expectedNum { - t.Fatalf("expected %d results. Got %d", expectedNum, len(result.Accounts)) + t.Fatalf("expected %d results, got %d", expectedNum, len(result.Accounts)) } - - for _, address := range result.Accounts { - if address == nil { - t.Fatalf("null address returned") + for address := range result.Accounts { + if address == (common.Address{}) { + t.Fatalf("empty address returned") } - if !statedb.Exist(*address) { + if !statedb.Exist(address) { t.Fatalf("account not found in state %s", address.Hex()) } } - return result } -type resultHash []*common.Hash +type resultHash []common.Hash func (h resultHash) Len() int { return len(h) } func (h resultHash) Swap(i, j int) { h[i], h[j] = h[j], h[i] } @@ -80,7 +75,6 @@ func TestAccountRange(t *testing.T) { m[addr] = true } } - state.Commit(true) root := state.IntermediateRoot(true) @@ -88,68 +82,40 @@ func TestAccountRange(t *testing.T) { if err != nil { t.Fatal(err) } - - t.Logf("test getting number of results less than max") - accountRangeTest(t, &trie, state, &common.Hash{0x0}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) - - t.Logf("test getting number of results greater than max %d", AccountRangeMaxResults) - accountRangeTest(t, &trie, state, &common.Hash{0x0}, AccountRangeMaxResults*2, AccountRangeMaxResults) - - t.Logf("test with empty 'start' hash") - accountRangeTest(t, &trie, state, nil, AccountRangeMaxResults, AccountRangeMaxResults) - - t.Logf("test pagination") - + accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults/2, AccountRangeMaxResults/2) // test pagination - firstResult := accountRangeTest(t, &trie, state, &common.Hash{0x0}, AccountRangeMaxResults, AccountRangeMaxResults) - - t.Logf("test pagination 2") - secondResult := accountRangeTest(t, &trie, state, &firstResult.Next, AccountRangeMaxResults, AccountRangeMaxResults) + firstResult := accountRangeTest(t, &trie, state, common.Hash{}, AccountRangeMaxResults, AccountRangeMaxResults) + secondResult := accountRangeTest(t, &trie, state, common.BytesToHash(firstResult.Next), AccountRangeMaxResults, AccountRangeMaxResults) hList := make(resultHash, 0) - for h1, addr1 := range firstResult.Accounts { - h := &common.Hash{} - h.SetBytes(h1.Bytes()) - hList = append(hList, h) - for h2, addr2 := range secondResult.Accounts { - // Make sure that the hashes aren't the same - if bytes.Equal(h1.Bytes(), h2.Bytes()) { - t.Fatalf("pagination test failed: results should not overlap") - } - - // If either address is nil, then it makes no sense to compare - // them as they might be two different accounts. - if addr1 == nil || addr2 == nil { - continue - } - - // Since the two hashes are different, they should not have - // the same preimage, but let's check anyway in case there - // is a bug in the (hash, addr) map generation code. - if bytes.Equal(addr1.Bytes(), addr2.Bytes()) { - t.Fatalf("pagination test failed: addresses should not repeat") - } + for addr1 := range firstResult.Accounts { + // If address is empty, then it makes no sense to compare + // them as they might be two different accounts. + if addr1 == (common.Address{}) { + continue } + if _, duplicate := secondResult.Accounts[addr1]; duplicate { + t.Fatalf("pagination test failed: results should not overlap") + } + hList = append(hList, crypto.Keccak256Hash(addr1.Bytes())) } - // Test to see if it's possible to recover from the middle of the previous // set and get an even split between the first and second sets. - t.Logf("test random access pagination") sort.Sort(hList) middleH := hList[AccountRangeMaxResults/2] middleResult := accountRangeTest(t, &trie, state, middleH, AccountRangeMaxResults, AccountRangeMaxResults) - innone, infirst, insecond := 0, 0, 0 + missing, infirst, insecond := 0, 0, 0 for h := range middleResult.Accounts { if _, ok := firstResult.Accounts[h]; ok { infirst++ } else if _, ok := secondResult.Accounts[h]; ok { insecond++ } else { - innone++ + missing++ } } - if innone != 0 { - t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", innone) + if missing != 0 { + t.Fatalf("%d hashes in the 'middle' set were neither in the first not the second set", missing) } if infirst != AccountRangeMaxResults/2 { t.Fatalf("Imbalance in the number of first-test results: %d != %d", infirst, AccountRangeMaxResults/2) @@ -164,20 +130,10 @@ func TestEmptyAccountRange(t *testing.T) { statedb = state.NewDatabase(rawdb.NewMemoryDatabase()) state, _ = state.New(common.Hash{}, statedb, nil) ) - state.Commit(true) - root := state.IntermediateRoot(true) - - trie, err := statedb.OpenTrie(root) - if err != nil { - t.Fatal(err) - } - - results, err := accountRange(trie, &common.Hash{0x0}, AccountRangeMaxResults) - if err != nil { - t.Fatalf("Empty results should not trigger an error: %v", err) - } - if results.Next != common.HexToHash("0") { + state.IntermediateRoot(true) + results := state.IteratorDump(true, true, true, (common.Hash{}).Bytes(), AccountRangeMaxResults) + if bytes.Equal(results.Next, (common.Hash{}).Bytes()) { t.Fatalf("Empty results should not return a second page") } if len(results.Accounts) != 0 { From 300c35b8542a2d9586aab91b152d3970346018e1 Mon Sep 17 00:00:00 2001 From: Adam Schmideg Date: Tue, 31 Mar 2020 12:09:45 +0200 Subject: [PATCH 054/103] travis: allow cocoapods deploy to fail (#20833) --- .travis.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 288c57959e..82684a701c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,17 @@ language: go go_import_path: github.com/ethereum/go-ethereum sudo: false jobs: + allow_failures: + - stage: build + os: osx + go: 1.13.x + env: + - azure-osx + - azure-ios + - cocoapods-ios + include: - # This builder only tests code linters on latest version of Go + # This builder only tests code linters on latest version of Go - stage: lint os: linux dist: xenial From 3b69c14f5d48d583f240cdbdc38818a47ec6761d Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 31 Mar 2020 12:10:34 +0200 Subject: [PATCH 055/103] whisper/whisperv6: decrease pow requirement in tests (#20815) --- whisper/whisperv6/message_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/whisper/whisperv6/message_test.go b/whisper/whisperv6/message_test.go index 0a5c1c8533..ece6d732cc 100644 --- a/whisper/whisperv6/message_test.go +++ b/whisper/whisperv6/message_test.go @@ -36,7 +36,7 @@ func generateMessageParams() (*MessageParams, error) { sz := mrand.Intn(400) var p MessageParams - p.PoW = 0.01 + p.PoW = 0.001 p.WorkTime = 1 p.TTL = uint32(mrand.Intn(1024)) p.Payload = make([]byte, sz) From 32d31c31afdd17a8ce71985c1a760caf0d2916e5 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 31 Mar 2020 15:01:16 +0200 Subject: [PATCH 056/103] metrics: improve TestTimerFunc (#20818) The test failed due to what appears to be fluctuations in time.Sleep, which is not the actual method under test. This change modifies it so we compare the metered Max to the actual time instead of the desired time. --- metrics/timer_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/metrics/timer_test.go b/metrics/timer_test.go index 330de609bc..903e8e8d49 100644 --- a/metrics/timer_test.go +++ b/metrics/timer_test.go @@ -45,10 +45,23 @@ func TestTimerStop(t *testing.T) { } func TestTimerFunc(t *testing.T) { - tm := NewTimer() - tm.Time(func() { time.Sleep(50e6) }) - if max := tm.Max(); 35e6 > max || max > 145e6 { - t.Errorf("tm.Max(): 35e6 > %v || %v > 145e6\n", max, max) + var ( + tm = NewTimer() + testStart = time.Now() + actualTime time.Duration + ) + tm.Time(func() { + time.Sleep(50 * time.Millisecond) + actualTime = time.Since(testStart) + }) + var ( + drift = time.Millisecond * 2 + measured = time.Duration(tm.Max()) + ceil = actualTime + drift + floor = actualTime - drift + ) + if measured > ceil || measured < floor { + t.Errorf("tm.Max(): %v > %v || %v > %v\n", measured, ceil, measured, floor) } } From f78ffc0545f577af9c8f94c5f940a1d5a6ea90bf Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 31 Mar 2020 23:17:24 +0800 Subject: [PATCH 057/103] les: create utilities as common package (#20509) * les: move execqueue into utilities package execqueue is a util for executing queued functions in a serial order which is used by both les server and les client. Move it to common package. * les: move randselect to utilities package weighted_random_selector is a helpful tool for randomly select items maintained in a set but based on the item weight. It's used anywhere is LES package, mainly by les client but will be used in les server with very high chance. So move it into a common package as the second step for les separation. * les: rename to utils --- les/distributor.go | 9 ++-- les/peer.go | 15 +++--- les/serverpool.go | 39 ++++++++------- les/{execqueue.go => utils/exec_queue.go} | 37 +++++++------- .../exec_queue_test.go} | 20 ++++---- .../weighted_select.go} | 50 +++++++++---------- .../weighted_select_test.go} | 10 ++-- 7 files changed, 90 insertions(+), 90 deletions(-) rename les/{execqueue.go => utils/exec_queue.go} (71%) rename les/{execqueue_test.go => utils/exec_queue_test.go} (83%) rename les/{randselect.go => utils/weighted_select.go} (82%) rename les/{randselect_test.go => utils/weighted_select_test.go} (93%) diff --git a/les/distributor.go b/les/distributor.go index 4d2be1b8f9..97d2ccdfea 100644 --- a/les/distributor.go +++ b/les/distributor.go @@ -22,6 +22,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/les/utils" ) // requestDistributor implements a mechanism that distributes requests to @@ -194,7 +195,7 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { elem := d.reqQueue.Front() var ( bestWait time.Duration - sel *weightedRandomSelect + sel *utils.WeightedRandomSelect ) d.peerLock.RLock() @@ -219,9 +220,9 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { wait, bufRemain := peer.waitBefore(cost) if wait == 0 { if sel == nil { - sel = newWeightedRandomSelect() + sel = utils.NewWeightedRandomSelect() } - sel.update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1}) + sel.Update(selectPeerItem{peer: peer, req: req, weight: int64(bufRemain*1000000) + 1}) } else { if bestWait == 0 || wait < bestWait { bestWait = wait @@ -239,7 +240,7 @@ func (d *requestDistributor) nextRequest() (distPeer, *distReq, time.Duration) { } if sel != nil { - c := sel.choose().(selectPeerItem) + c := sel.Choose().(selectPeerItem) return c.peer, c.req, 0 } return nil, nil, bestWait diff --git a/les/peer.go b/les/peer.go index d308fd249e..c2f4235eb4 100644 --- a/les/peer.go +++ b/les/peer.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" + "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/enode" @@ -135,7 +136,7 @@ type peerCommons struct { headInfo blockInfo // Latest block information. // Background task queue for caching peer tasks and executing in order. - sendQueue *execQueue + sendQueue *utils.ExecQueue // Flow control agreement. fcParams flowcontrol.ServerParams // The config for token bucket. @@ -153,13 +154,13 @@ func (p *peerCommons) isFrozen() bool { // canQueue returns an indicator whether the peer can queue a operation. func (p *peerCommons) canQueue() bool { - return p.sendQueue.canQueue() && !p.isFrozen() + return p.sendQueue.CanQueue() && !p.isFrozen() } // queueSend caches a peer operation in the background task queue. // Please ensure to check `canQueue` before call this function func (p *peerCommons) queueSend(f func()) bool { - return p.sendQueue.queue(f) + return p.sendQueue.Queue(f) } // mustQueueSend starts a for loop and retry the caching if failed. @@ -337,7 +338,7 @@ func (p *peerCommons) handshake(td *big.Int, head common.Hash, headNum uint64, g // close closes the channel and notifies all background routines to exit. func (p *peerCommons) close() { close(p.closeCh) - p.sendQueue.quit() + p.sendQueue.Quit() } // serverPeer represents each node to which the client is connected. @@ -375,7 +376,7 @@ func newServerPeer(version int, network uint64, trusted bool, p *p2p.Peer, rw p2 id: peerIdToString(p.ID()), version: version, network: network, - sendQueue: newExecQueue(100), + sendQueue: utils.NewExecQueue(100), closeCh: make(chan struct{}), }, trusted: trusted, @@ -407,7 +408,7 @@ func (p *serverPeer) rejectUpdate(size uint64) bool { // frozen. func (p *serverPeer) freeze() { if atomic.CompareAndSwapUint32(&p.frozen, 0, 1) { - p.sendQueue.clear() + p.sendQueue.Clear() } } @@ -652,7 +653,7 @@ func newClientPeer(version int, network uint64, p *p2p.Peer, rw p2p.MsgReadWrite id: peerIdToString(p.ID()), version: version, network: network, - sendQueue: newExecQueue(100), + sendQueue: utils.NewExecQueue(100), closeCh: make(chan struct{}), }, errCh: make(chan error, 1), diff --git a/les/serverpool.go b/les/serverpool.go index ec99a2d982..d1c53295a7 100644 --- a/les/serverpool.go +++ b/les/serverpool.go @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discv5" @@ -129,7 +130,7 @@ type serverPool struct { adjustStats chan poolStatAdjust knownQueue, newQueue poolEntryQueue - knownSelect, newSelect *weightedRandomSelect + knownSelect, newSelect *utils.WeightedRandomSelect knownSelected, newSelected int fastDiscover bool connCh chan *connReq @@ -152,8 +153,8 @@ func newServerPool(db ethdb.Database, ulcServers []string) *serverPool { disconnCh: make(chan *disconnReq), registerCh: make(chan *registerReq), closeCh: make(chan struct{}), - knownSelect: newWeightedRandomSelect(), - newSelect: newWeightedRandomSelect(), + knownSelect: utils.NewWeightedRandomSelect(), + newSelect: utils.NewWeightedRandomSelect(), fastDiscover: true, trustedNodes: parseTrustedNodes(ulcServers), } @@ -402,8 +403,8 @@ func (pool *serverPool) eventLoop() { entry.lastConnected = addr entry.addr = make(map[string]*poolEntryAddress) entry.addr[addr.strKey()] = addr - entry.addrSelect = *newWeightedRandomSelect() - entry.addrSelect.update(addr) + entry.addrSelect = *utils.NewWeightedRandomSelect() + entry.addrSelect.Update(addr) req.result <- entry } @@ -459,7 +460,7 @@ func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry { entry = &poolEntry{ node: node, addr: make(map[string]*poolEntryAddress), - addrSelect: *newWeightedRandomSelect(), + addrSelect: *utils.NewWeightedRandomSelect(), shortRetry: shortRetryCnt, } pool.entries[node.ID()] = entry @@ -477,7 +478,7 @@ func (pool *serverPool) findOrNewNode(node *enode.Node) *poolEntry { entry.addr[addr.strKey()] = addr } addr.lastSeen = now - entry.addrSelect.update(addr) + entry.addrSelect.Update(addr) if !entry.known { pool.newQueue.setLatest(entry) } @@ -505,7 +506,7 @@ func (pool *serverPool) loadNodes() { pool.entries[e.node.ID()] = e if pool.trustedNodes[e.node.ID()] == nil { pool.knownQueue.setLatest(e) - pool.knownSelect.update((*knownEntry)(e)) + pool.knownSelect.Update((*knownEntry)(e)) } } } @@ -556,8 +557,8 @@ func (pool *serverPool) saveNodes() { // Note that it is called by the new/known queues from which the entry has already // been removed so removing it from the queues is not necessary. func (pool *serverPool) removeEntry(entry *poolEntry) { - pool.newSelect.remove((*discoveredEntry)(entry)) - pool.knownSelect.remove((*knownEntry)(entry)) + pool.newSelect.Remove((*discoveredEntry)(entry)) + pool.knownSelect.Remove((*knownEntry)(entry)) entry.removed = true delete(pool.entries, entry.node.ID()) } @@ -586,8 +587,8 @@ func (pool *serverPool) setRetryDial(entry *poolEntry) { // updateCheckDial is called when an entry can potentially be dialed again. It updates // its selection weights and checks if new dials can/should be made. func (pool *serverPool) updateCheckDial(entry *poolEntry) { - pool.newSelect.update((*discoveredEntry)(entry)) - pool.knownSelect.update((*knownEntry)(entry)) + pool.newSelect.Update((*discoveredEntry)(entry)) + pool.knownSelect.Update((*knownEntry)(entry)) pool.checkDial() } @@ -596,7 +597,7 @@ func (pool *serverPool) updateCheckDial(entry *poolEntry) { func (pool *serverPool) checkDial() { fillWithKnownSelects := !pool.fastDiscover for pool.knownSelected < targetKnownSelect { - entry := pool.knownSelect.choose() + entry := pool.knownSelect.Choose() if entry == nil { fillWithKnownSelects = false break @@ -604,7 +605,7 @@ func (pool *serverPool) checkDial() { pool.dial((*poolEntry)(entry.(*knownEntry)), true) } for pool.knownSelected+pool.newSelected < targetServerCount { - entry := pool.newSelect.choose() + entry := pool.newSelect.Choose() if entry == nil { break } @@ -615,7 +616,7 @@ func (pool *serverPool) checkDial() { // is over, we probably won't find more in the near future so select more // known entries if possible for pool.knownSelected < targetServerCount { - entry := pool.knownSelect.choose() + entry := pool.knownSelect.Choose() if entry == nil { break } @@ -636,7 +637,7 @@ func (pool *serverPool) dial(entry *poolEntry, knownSelected bool) { } else { pool.newSelected++ } - addr := entry.addrSelect.choose().(*poolEntryAddress) + addr := entry.addrSelect.Choose().(*poolEntryAddress) log.Debug("Dialing new peer", "lesaddr", entry.node.ID().String()+"@"+addr.strKey(), "set", len(entry.addr), "known", knownSelected) entry.dialed = addr go func() { @@ -684,7 +685,7 @@ type poolEntry struct { addr map[string]*poolEntryAddress node *enode.Node lastConnected, dialed *poolEntryAddress - addrSelect weightedRandomSelect + addrSelect utils.WeightedRandomSelect lastDiscovered mclock.AbsTime known, knownSelected, trusted bool @@ -734,8 +735,8 @@ func (e *poolEntry) DecodeRLP(s *rlp.Stream) error { e.node = enode.NewV4(pubkey, entry.IP, int(entry.Port), int(entry.Port)) e.addr = make(map[string]*poolEntryAddress) e.addr[addr.strKey()] = addr - e.addrSelect = *newWeightedRandomSelect() - e.addrSelect.update(addr) + e.addrSelect = *utils.NewWeightedRandomSelect() + e.addrSelect.Update(addr) e.lastConnected = addr e.connectStats = entry.CStat e.delayStats = entry.DStat diff --git a/les/execqueue.go b/les/utils/exec_queue.go similarity index 71% rename from les/execqueue.go rename to les/utils/exec_queue.go index e0c88a990f..a8f9b84acb 100644 --- a/les/execqueue.go +++ b/les/utils/exec_queue.go @@ -14,35 +14,35 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package utils import "sync" -// execQueue implements a queue that executes function calls in a single thread, +// ExecQueue implements a queue that executes function calls in a single thread, // in the same order as they have been queued. -type execQueue struct { +type ExecQueue struct { mu sync.Mutex cond *sync.Cond funcs []func() closeWait chan struct{} } -// newExecQueue creates a new execution queue. -func newExecQueue(capacity int) *execQueue { - q := &execQueue{funcs: make([]func(), 0, capacity)} +// NewExecQueue creates a new execution Queue. +func NewExecQueue(capacity int) *ExecQueue { + q := &ExecQueue{funcs: make([]func(), 0, capacity)} q.cond = sync.NewCond(&q.mu) go q.loop() return q } -func (q *execQueue) loop() { +func (q *ExecQueue) loop() { for f := q.waitNext(false); f != nil; f = q.waitNext(true) { f() } close(q.closeWait) } -func (q *execQueue) waitNext(drop bool) (f func()) { +func (q *ExecQueue) waitNext(drop bool) (f func()) { q.mu.Lock() if drop && len(q.funcs) > 0 { // Remove the function that just executed. We do this here instead of when @@ -60,20 +60,20 @@ func (q *execQueue) waitNext(drop bool) (f func()) { return f } -func (q *execQueue) isClosed() bool { +func (q *ExecQueue) isClosed() bool { return q.closeWait != nil } -// canQueue returns true if more function calls can be added to the execution queue. -func (q *execQueue) canQueue() bool { +// CanQueue returns true if more function calls can be added to the execution Queue. +func (q *ExecQueue) CanQueue() bool { q.mu.Lock() ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) q.mu.Unlock() return ok } -// queue adds a function call to the execution queue. Returns true if successful. -func (q *execQueue) queue(f func()) bool { +// Queue adds a function call to the execution Queue. Returns true if successful. +func (q *ExecQueue) Queue(f func()) bool { q.mu.Lock() ok := !q.isClosed() && len(q.funcs) < cap(q.funcs) if ok { @@ -84,16 +84,17 @@ func (q *execQueue) queue(f func()) bool { return ok } -// clear drops all queued functions -func (q *execQueue) clear() { +// Clear drops all queued functions. +func (q *ExecQueue) Clear() { q.mu.Lock() q.funcs = q.funcs[:0] q.mu.Unlock() } -// quit stops the exec queue. -// quit waits for the current execution to finish before returning. -func (q *execQueue) quit() { +// Quit stops the exec Queue. +// +// Quit waits for the current execution to finish before returning. +func (q *ExecQueue) Quit() { q.mu.Lock() if !q.isClosed() { q.closeWait = make(chan struct{}) diff --git a/les/execqueue_test.go b/les/utils/exec_queue_test.go similarity index 83% rename from les/execqueue_test.go rename to les/utils/exec_queue_test.go index cd45b03f22..98601c4486 100644 --- a/les/execqueue_test.go +++ b/les/utils/exec_queue_test.go @@ -14,21 +14,19 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package utils -import ( - "testing" -) +import "testing" func TestExecQueue(t *testing.T) { var ( N = 10000 - q = newExecQueue(N) + q = NewExecQueue(N) counter int execd = make(chan int) testexit = make(chan struct{}) ) - defer q.quit() + defer q.Quit() defer close(testexit) check := func(state string, wantOK bool) { @@ -40,11 +38,11 @@ func TestExecQueue(t *testing.T) { case <-testexit: } } - if q.canQueue() != wantOK { - t.Fatalf("canQueue() == %t for %s", !wantOK, state) + if q.CanQueue() != wantOK { + t.Fatalf("CanQueue() == %t for %s", !wantOK, state) } - if q.queue(qf) != wantOK { - t.Fatalf("canQueue() == %t for %s", !wantOK, state) + if q.Queue(qf) != wantOK { + t.Fatalf("Queue() == %t for %s", !wantOK, state) } } @@ -57,6 +55,6 @@ func TestExecQueue(t *testing.T) { t.Fatal("execution out of order") } } - q.quit() + q.Quit() check("closed queue", false) } diff --git a/les/randselect.go b/les/utils/weighted_select.go similarity index 82% rename from les/randselect.go rename to les/utils/weighted_select.go index 8efe0c94d3..fbf1f37d62 100644 --- a/les/randselect.go +++ b/les/utils/weighted_select.go @@ -14,43 +14,30 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package utils -import ( - "math/rand" -) +import "math/rand" // wrsItem interface should be implemented by any entries that are to be selected from -// a weightedRandomSelect set. Note that recalculating monotonously decreasing item -// weights on-demand (without constantly calling update) is allowed +// a WeightedRandomSelect set. Note that recalculating monotonously decreasing item +// weights on-demand (without constantly calling Update) is allowed type wrsItem interface { Weight() int64 } -// weightedRandomSelect is capable of weighted random selection from a set of items -type weightedRandomSelect struct { +// WeightedRandomSelect is capable of weighted random selection from a set of items +type WeightedRandomSelect struct { root *wrsNode idx map[wrsItem]int } -// newWeightedRandomSelect returns a new weightedRandomSelect structure -func newWeightedRandomSelect() *weightedRandomSelect { - return &weightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)} -} - -// update updates an item's weight, adds it if it was non-existent or removes it if -// the new weight is zero. Note that explicitly updating decreasing weights is not necessary. -func (w *weightedRandomSelect) update(item wrsItem) { - w.setWeight(item, item.Weight()) -} - -// remove removes an item from the set -func (w *weightedRandomSelect) remove(item wrsItem) { - w.setWeight(item, 0) +// NewWeightedRandomSelect returns a new WeightedRandomSelect structure +func NewWeightedRandomSelect() *WeightedRandomSelect { + return &WeightedRandomSelect{root: &wrsNode{maxItems: wrsBranches}, idx: make(map[wrsItem]int)} } // setWeight sets an item's weight to a specific value (removes it if zero) -func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) { +func (w *WeightedRandomSelect) setWeight(item wrsItem, weight int64) { idx, ok := w.idx[item] if ok { w.root.setWeight(idx, weight) @@ -71,11 +58,22 @@ func (w *weightedRandomSelect) setWeight(item wrsItem, weight int64) { } } -// choose randomly selects an item from the set, with a chance proportional to its +// Update updates an item's weight, adds it if it was non-existent or removes it if +// the new weight is zero. Note that explicitly updating decreasing weights is not necessary. +func (w *WeightedRandomSelect) Update(item wrsItem) { + w.setWeight(item, item.Weight()) +} + +// Remove removes an item from the set +func (w *WeightedRandomSelect) Remove(item wrsItem) { + w.setWeight(item, 0) +} + +// Choose randomly selects an item from the set, with a chance proportional to its // current weight. If the weight of the chosen element has been decreased since the // last stored value, returns it with a newWeight/oldWeight chance, otherwise just // updates its weight and selects another one -func (w *weightedRandomSelect) choose() wrsItem { +func (w *WeightedRandomSelect) Choose() wrsItem { for { if w.root.sumWeight == 0 { return nil @@ -154,7 +152,7 @@ func (n *wrsNode) setWeight(idx int, weight int64) int64 { return diff } -// choose recursively selects an item from the tree and returns it along with its weight +// Choose recursively selects an item from the tree and returns it along with its weight func (n *wrsNode) choose(val int64) (wrsItem, int64) { for i, w := range n.weights { if val < w { diff --git a/les/randselect_test.go b/les/utils/weighted_select_test.go similarity index 93% rename from les/randselect_test.go rename to les/utils/weighted_select_test.go index 9ae7726ddd..e1969e1a61 100644 --- a/les/randselect_test.go +++ b/les/utils/weighted_select_test.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package les +package utils import ( "math/rand" @@ -36,15 +36,15 @@ func (t *testWrsItem) Weight() int64 { func TestWeightedRandomSelect(t *testing.T) { testFn := func(cnt int) { - s := newWeightedRandomSelect() + s := NewWeightedRandomSelect() w := -1 list := make([]testWrsItem, cnt) for i := range list { list[i] = testWrsItem{idx: i, widx: &w} - s.update(&list[i]) + s.Update(&list[i]) } w = rand.Intn(cnt) - c := s.choose() + c := s.Choose() if c == nil { t.Errorf("expected item, got nil") } else { @@ -53,7 +53,7 @@ func TestWeightedRandomSelect(t *testing.T) { } } w = -2 - if s.choose() != nil { + if s.Choose() != nil { t.Errorf("expected nil, got item") } } From f0be151349cb1440d119d3c033936e22bcb0724a Mon Sep 17 00:00:00 2001 From: Jeff Wentworth Date: Wed, 1 Apr 2020 01:14:42 +0900 Subject: [PATCH 058/103] README: update private network genesis spec with istanbul (#20841) * add istanbul and muirGlacier to genesis states in README * remove muirGlacier, relocate istanbul --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 72479b5a1e..0c744fc202 100644 --- a/README.md +++ b/README.md @@ -217,7 +217,8 @@ aware of and agree upon. This consists of a small JSON file (e.g. call it `genes "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, - "petersburgBlock": 0 + "petersburgBlock": 0, + "istanbulBlock": 0 }, "alloc": {}, "coinbase": "0x0000000000000000000000000000000000000000", From a5a9feab21cf5af36f2790bc4c5d928e4c7b7608 Mon Sep 17 00:00:00 2001 From: ucwong Date: Wed, 1 Apr 2020 17:35:26 +0800 Subject: [PATCH 059/103] whisper: fix whisper go routine leak with sync wait group (#20844) --- whisper/whisperv6/peer.go | 7 +++++++ whisper/whisperv6/whisper.go | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/whisper/whisperv6/peer.go b/whisper/whisperv6/peer.go index 29d8bdf17e..68fa7c8cba 100644 --- a/whisper/whisperv6/peer.go +++ b/whisper/whisperv6/peer.go @@ -44,6 +44,8 @@ type Peer struct { known mapset.Set // Messages already known by the peer to avoid wasting bandwidth quit chan struct{} + + wg sync.WaitGroup } // newPeer creates a new whisper peer object, but does not run the handshake itself. @@ -64,6 +66,7 @@ func newPeer(host *Whisper, remote *p2p.Peer, rw p2p.MsgReadWriter) *Peer { // start initiates the peer updater, periodically broadcasting the whisper packets // into the network. func (peer *Peer) start() { + peer.wg.Add(1) go peer.update() log.Trace("start", "peer", peer.ID()) } @@ -71,6 +74,7 @@ func (peer *Peer) start() { // stop terminates the peer updater, stopping message forwarding to it. func (peer *Peer) stop() { close(peer.quit) + peer.wg.Wait() log.Trace("stop", "peer", peer.ID()) } @@ -81,7 +85,9 @@ func (peer *Peer) handshake() error { errc := make(chan error, 1) isLightNode := peer.host.LightClientMode() isRestrictedLightNodeConnection := peer.host.LightClientModeConnectionRestricted() + peer.wg.Add(1) go func() { + defer peer.wg.Done() pow := peer.host.MinPow() powConverted := math.Float64bits(pow) bloom := peer.host.BloomFilter() @@ -144,6 +150,7 @@ func (peer *Peer) handshake() error { // update executes periodic operations on the peer, including message transmission // and expiration. func (peer *Peer) update() { + defer peer.wg.Done() // Start the tickers for the updates expire := time.NewTicker(expirationCycle) defer expire.Stop() diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index a7787ca69f..e9c872a990 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -88,6 +88,8 @@ type Whisper struct { stats Statistics // Statistics of whisper node mailServer MailServer // MailServer interface + + wg sync.WaitGroup } // New creates a Whisper client ready to communicate through the Ethereum P2P network. @@ -243,8 +245,10 @@ func (whisper *Whisper) SetBloomFilter(bloom []byte) error { whisper.settings.Store(bloomFilterIdx, b) whisper.notifyPeersAboutBloomFilterChange(b) + whisper.wg.Add(1) go func() { // allow some time before all the peers have processed the notification + defer whisper.wg.Done() time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) whisper.settings.Store(bloomFilterToleranceIdx, b) }() @@ -261,7 +265,9 @@ func (whisper *Whisper) SetMinimumPoW(val float64) error { whisper.settings.Store(minPowIdx, val) whisper.notifyPeersAboutPowRequirementChange(val) + whisper.wg.Add(1) go func() { + defer whisper.wg.Done() // allow some time before all the peers have processed the notification time.Sleep(time.Duration(whisper.syncAllowance) * time.Second) whisper.settings.Store(minPowToleranceIdx, val) @@ -626,10 +632,12 @@ func (whisper *Whisper) Send(envelope *Envelope) error { // of the Whisper protocol. func (whisper *Whisper) Start(*p2p.Server) error { log.Info("started whisper v." + ProtocolVersionStr) + whisper.wg.Add(1) go whisper.update() numCPU := runtime.NumCPU() for i := 0; i < numCPU; i++ { + whisper.wg.Add(1) go whisper.processQueue() } @@ -640,6 +648,7 @@ func (whisper *Whisper) Start(*p2p.Server) error { // of the Whisper protocol. func (whisper *Whisper) Stop() error { close(whisper.quit) + whisper.wg.Wait() log.Info("whisper stopped") return nil } @@ -874,6 +883,7 @@ func (whisper *Whisper) checkOverflow() { // processQueue delivers the messages to the watchers during the lifetime of the whisper node. func (whisper *Whisper) processQueue() { + defer whisper.wg.Done() var e *Envelope for { select { @@ -892,6 +902,7 @@ func (whisper *Whisper) processQueue() { // update loops until the lifetime of the whisper node, updating its internal // state by expiring stale messages from the pool. func (whisper *Whisper) update() { + defer whisper.wg.Done() // Start a ticker to check for expirations expire := time.NewTicker(expirationCycle) From d56dc038d2a89c8b11d5ca5630c32114ccaef836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Wed, 1 Apr 2020 12:40:07 +0200 Subject: [PATCH 060/103] cmd/evm: Rework execution stats (#20792) - Dump stats also for --bench flag. - From memory stats only show number and size of allocations. This is what `test -bench` shows. I doubt others like number of GC runs are any useful, but can be added if requested. - Now the mem stats are for single execution in case of --bench. --- cmd/evm/runner.go | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index 0a9c19f5bc..639f0c0ac9 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -70,14 +70,13 @@ func readGenesis(genesisPath string) *core.Genesis { return genesis } -func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, uint64, time.Duration, error) { - var ( - output []byte - gasLeft uint64 - execTime time.Duration - err error - ) +type execStats struct { + time time.Duration // The execution time. + allocs int64 // The number of heap allocations during execution. + bytesAllocated int64 // The cumulative number of bytes allocated during execution. +} +func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []byte, gasLeft uint64, stats execStats, err error) { if bench { result := testing.Benchmark(func(b *testing.B) { for i := 0; i < b.N; i++ { @@ -87,14 +86,21 @@ func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) ([]byte, uin // Get the average execution time from the benchmarking result. // There are other useful stats here that could be reported. - execTime = time.Duration(result.NsPerOp()) + stats.time = time.Duration(result.NsPerOp()) + stats.allocs = result.AllocsPerOp() + stats.bytesAllocated = result.AllocedBytesPerOp() } else { + var memStatsBefore, memStatsAfter goruntime.MemStats + goruntime.ReadMemStats(&memStatsBefore) startTime := time.Now() output, gasLeft, err = execFunc() - execTime = time.Since(startTime) + stats.time = time.Since(startTime) + goruntime.ReadMemStats(&memStatsAfter) + stats.allocs = int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs) + stats.bytesAllocated = int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc) } - return output, gasLeft, execTime, err + return output, gasLeft, stats, err } func runCmd(ctx *cli.Context) error { @@ -256,7 +262,8 @@ func runCmd(ctx *cli.Context) error { } } - output, leftOverGas, execTime, err := timedExec(ctx.GlobalBool(BenchFlag.Name), execFunc) + bench := ctx.GlobalBool(BenchFlag.Name) + output, leftOverGas, stats, err := timedExec(bench, execFunc) if ctx.GlobalBool(DumpFlag.Name) { statedb.Commit(true) @@ -286,17 +293,12 @@ func runCmd(ctx *cli.Context) error { vm.WriteLogs(os.Stderr, statedb.Logs()) } - if ctx.GlobalBool(StatDumpFlag.Name) { - var mem goruntime.MemStats - goruntime.ReadMemStats(&mem) - fmt.Fprintf(os.Stderr, `evm execution time: %v -heap objects: %d -allocations: %d -total allocations: %d -GC calls: %d -Gas used: %d - -`, execTime, mem.HeapObjects, mem.Alloc, mem.TotalAlloc, mem.NumGC, initialGas-leftOverGas) + if bench || ctx.GlobalBool(StatDumpFlag.Name) { + fmt.Fprintf(os.Stderr, `EVM gas used: %d +execution time: %v +allocations: %d +allocated bytes: %d +`, initialGas-leftOverGas, stats.time, stats.allocs, stats.bytesAllocated) } if tracer == nil { fmt.Printf("0x%x\n", output) From 1e2e1b41f8cf0c51f53dd064a4caa75faee14065 Mon Sep 17 00:00:00 2001 From: ucwong Date: Wed, 1 Apr 2020 22:12:01 +0800 Subject: [PATCH 061/103] cmd/devp2p, cmd/wnode, whisper: add missing calls to Timer.Stop (#20843) --- cmd/devp2p/crawl.go | 1 + cmd/wnode/main.go | 1 + whisper/whisperv6/whisper.go | 1 + 3 files changed, 3 insertions(+) diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go index 92aaad72a3..7fefbd7a1c 100644 --- a/cmd/devp2p/crawl.go +++ b/cmd/devp2p/crawl.go @@ -63,6 +63,7 @@ func (c *crawler) run(timeout time.Duration) nodeSet { doneCh = make(chan enode.Iterator, len(c.iters)) liveIters = len(c.iters) ) + defer timeoutTimer.Stop() for _, it := range c.iters { go c.runIterator(doneCh, it) } diff --git a/cmd/wnode/main.go b/cmd/wnode/main.go index 968f1fd49f..a94ed947d0 100644 --- a/cmd/wnode/main.go +++ b/cmd/wnode/main.go @@ -599,6 +599,7 @@ func messageLoop() { } ticker := time.NewTicker(time.Millisecond * 50) + defer ticker.Stop() for { select { diff --git a/whisper/whisperv6/whisper.go b/whisper/whisperv6/whisper.go index e9c872a990..377406b360 100644 --- a/whisper/whisperv6/whisper.go +++ b/whisper/whisperv6/whisper.go @@ -905,6 +905,7 @@ func (whisper *Whisper) update() { defer whisper.wg.Done() // Start a ticker to check for expirations expire := time.NewTicker(expirationCycle) + defer expire.Stop() // Repeat updates until termination is requested for { From bf35e27ea76ea50515e3f345a9ac05ec558ffabf Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 00:00:33 +0800 Subject: [PATCH 062/103] p2p/server: add UDP port mapping goroutine to wait group (#20846) --- p2p/server.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/p2p/server.go b/p2p/server.go index c87b7758df..1fe5f39789 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -562,7 +562,11 @@ func (srv *Server) setupDiscovery() error { srv.log.Debug("UDP listener up", "addr", realaddr) if srv.NAT != nil { if !realaddr.IP.IsLoopback() { - go nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") + srv.loopWG.Add(1) + go func() { + nat.Map(srv.NAT, srv.quit, "udp", realaddr.Port, realaddr.Port, "ethereum discovery") + srv.loopWG.Done() + }() } } srv.localnode.SetFallbackUDP(realaddr.Port) From f15849cf00ddc21137886e582cc064e80ee8c47e Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 1 Apr 2020 18:46:53 +0200 Subject: [PATCH 063/103] accounts/abi faster unpacking of int256 (#20850) --- accounts/abi/unpack.go | 22 +++++++++------------- accounts/abi/unpack_test.go | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 2a5db3b315..b2ce26cac2 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -27,13 +27,9 @@ import ( var ( // MaxUint256 is the maximum value that can be represented by a uint256 - MaxUint256 = big.NewInt(0).Add( - big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil), - big.NewInt(-1)) + MaxUint256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1) // MaxInt256 is the maximum value that can be represented by a int256 - MaxInt256 = big.NewInt(0).Add( - big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil), - big.NewInt(-1)) + MaxInt256 = new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 255), common.Big1) ) // ReadInteger reads the integer based on its kind and returns the appropriate value @@ -56,17 +52,17 @@ func ReadInteger(typ byte, kind reflect.Kind, b []byte) interface{} { case reflect.Int64: return int64(binary.BigEndian.Uint64(b[len(b)-8:])) default: - // the only case lefts for integer is int256/uint256. - // big.SetBytes can't tell if a number is negative, positive on itself. - // On EVM, if the returned number > max int256, it is negative. + // the only case left for integer is int256/uint256. ret := new(big.Int).SetBytes(b) if typ == UintTy { return ret } - - if ret.Cmp(MaxInt256) > 0 { - ret.Add(MaxUint256, big.NewInt(0).Neg(ret)) - ret.Add(ret, big.NewInt(1)) + // big.SetBytes can't tell if a number is negative or positive in itself. + // On EVM, if the returned number > max int256, it is negative. + // A number is > max int256 if the bit at position 255 is set. + if ret.Bit(255) == 1 { + ret.Add(MaxUint256, new(big.Int).Neg(ret)) + ret.Add(ret, common.Big1) ret.Neg(ret) } return ret diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index dfea8db671..ab343042b5 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -1009,7 +1009,7 @@ func TestUnpackTuple(t *testing.T) { t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.A) } if v.B.Cmp(big.NewInt(-1)) != 0 { - t.Errorf("unexpected value unpacked: want %x, got %x", v.B, -1) + t.Errorf("unexpected value unpacked: want %x, got %x", -1, v.B) } } From c87cdd30532f5512c8335fd13204f57e11263427 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 16:11:16 +0800 Subject: [PATCH 064/103] p2p/discv5: add missing Timer.Stop calls (#20853) --- p2p/discv5/net.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index dd2ec3e929..8a548eeb4c 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -357,6 +357,8 @@ func (net *Network) loop() { bucketRefreshTimer = time.NewTimer(bucketRefreshInterval) refreshDone chan struct{} // closed when the 'refresh' lookup has ended ) + defer refreshTimer.Stop() + defer bucketRefreshTimer.Stop() // Tracking the next ticket to register. var ( @@ -393,11 +395,13 @@ func (net *Network) loop() { searchInfo = make(map[Topic]topicSearchInfo) activeSearchCount int ) + defer topicRegisterLookupTick.Stop() topicSearchLookupDone := make(chan topicSearchResult, 100) topicSearch := make(chan Topic, 100) <-topicRegisterLookupTick.C statsDump := time.NewTicker(10 * time.Second) + defer statsDump.Stop() loop: for { From ad4b60efdd5f1827872f110a6c8e39e60e45fb4f Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 16:40:38 +0800 Subject: [PATCH 065/103] miner/worker: add missing timer.Stop call (#20857) --- miner/worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/miner/worker.go b/miner/worker.go index d3cd10ed26..59a47a1220 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -297,6 +297,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { ) timer := time.NewTimer(0) + defer timer.Stop() <-timer.C // discard the initial tick // commit aborts in-flight transaction execution with given signal and resubmits a new one. From 228a2970566261df7f86764ca94cb6a670500064 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Thu, 2 Apr 2020 12:27:44 +0200 Subject: [PATCH 066/103] cmd/geth: fix bad genesis test (#20860) --- cmd/geth/genesis_test.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/cmd/geth/genesis_test.go b/cmd/geth/genesis_test.go index e75b542cbb..9ac2dded9a 100644 --- a/cmd/geth/genesis_test.go +++ b/cmd/geth/genesis_test.go @@ -28,22 +28,6 @@ var customGenesisTests = []struct { query string result string }{ - // Plain genesis file without anything extra - { - genesis: `{ - "alloc" : {}, - "coinbase" : "0x0000000000000000000000000000000000000000", - "difficulty" : "0x20000", - "extraData" : "", - "gasLimit" : "0x2fefd8", - "nonce" : "0x0000000000000042", - "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp" : "0x00" - }`, - query: "eth.getBlock(0).nonce", - result: "0x0000000000000042", - }, // Genesis file with an empty chain configuration (ensure missing fields work) { genesis: `{ @@ -52,14 +36,14 @@ var customGenesisTests = []struct { "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", - "nonce" : "0x0000000000000042", + "nonce" : "0x0000000000001338", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", "config" : {} }`, query: "eth.getBlock(0).nonce", - result: "0x0000000000000042", + result: "0x0000000000001338", }, // Genesis file with specific chain configurations { @@ -69,7 +53,7 @@ var customGenesisTests = []struct { "difficulty" : "0x20000", "extraData" : "", "gasLimit" : "0x2fefd8", - "nonce" : "0x0000000000000042", + "nonce" : "0x0000000000001339", "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp" : "0x00", @@ -80,7 +64,7 @@ var customGenesisTests = []struct { } }`, query: "eth.getBlock(0).nonce", - result: "0x0000000000000042", + result: "0x0000000000001339", }, } @@ -97,10 +81,10 @@ func TestCustomGenesis(t *testing.T) { if err := ioutil.WriteFile(json, []byte(tt.genesis), 0600); err != nil { t.Fatalf("test %d: failed to write genesis file: %v", i, err) } - runGeth(t, "--datadir", datadir, "init", json).WaitExit() + runGeth(t, "--nousb", "--datadir", datadir, "init", json).WaitExit() // Query the custom genesis block - geth := runGeth(t, + geth := runGeth(t, "--nousb", "--datadir", datadir, "--maxpeers", "0", "--port", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--exec", tt.query, "console") From 47f7c736cb4872be272f978b9bfade33bff3f0f1 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 18:31:50 +0800 Subject: [PATCH 067/103] eth/filters: add missing Ticker.Stop call (#20862) --- eth/filters/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/eth/filters/api.go b/eth/filters/api.go index 46c9f44bb0..30d7b71c31 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -78,6 +78,7 @@ func NewPublicFilterAPI(backend Backend, lightMode bool) *PublicFilterAPI { // Tt is started when the api is created. func (api *PublicFilterAPI) timeoutLoop() { ticker := time.NewTicker(5 * time.Minute) + defer ticker.Stop() for { <-ticker.C api.filtersMu.Lock() From 66ed58bfcc2658e2ef60521c04701fb8e0455bcb Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 18:32:45 +0800 Subject: [PATCH 068/103] eth/fetcher: add missing timer.Stop calls (#20861) --- eth/fetcher/block_fetcher.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/eth/fetcher/block_fetcher.go b/eth/fetcher/block_fetcher.go index b6cab05deb..7690a53862 100644 --- a/eth/fetcher/block_fetcher.go +++ b/eth/fetcher/block_fetcher.go @@ -302,6 +302,8 @@ func (f *BlockFetcher) loop() { // Iterate the block fetching until a quit is requested fetchTimer := time.NewTimer(0) completeTimer := time.NewTimer(0) + defer fetchTimer.Stop() + defer completeTimer.Stop() for { // Clean up any expired block fetches From 4d891f23b5a03ab8a8f64dd1683018942a5c7b0a Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 21:54:59 +0800 Subject: [PATCH 069/103] les: add missing Ticker.Stop call (#20864) --- les/costtracker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/les/costtracker.go b/les/costtracker.go index 81da045660..0558779bc5 100644 --- a/les/costtracker.go +++ b/les/costtracker.go @@ -269,6 +269,7 @@ func (ct *costTracker) gfLoop() { log.Debug("global cost factor saved", "value", factor) } saveTicker := time.NewTicker(time.Minute * 10) + defer saveTicker.Stop() for { select { From 0893ee6d519cb5cffb56c480020051d25728a734 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 21:56:25 +0800 Subject: [PATCH 070/103] event: add missing timer.Stop call in TestFeed (#20868) --- event/feed_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/event/feed_test.go b/event/feed_test.go index be8876932c..93badaaee6 100644 --- a/event/feed_test.go +++ b/event/feed_test.go @@ -86,6 +86,7 @@ func TestFeed(t *testing.T) { subchan := make(chan int) sub := feed.Subscribe(subchan) timeout := time.NewTimer(2 * time.Second) + defer timeout.Stop() subscribed.Done() select { From 53e034ce0be31155bf0b982ac02fbffc0924fc79 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 22:01:18 +0800 Subject: [PATCH 071/103] metrics: add missing calls to Ticker.Stop in tests (#20866) --- metrics/meter_test.go | 1 + metrics/sample_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/metrics/meter_test.go b/metrics/meter_test.go index 0302c947d8..28472253e8 100644 --- a/metrics/meter_test.go +++ b/metrics/meter_test.go @@ -26,6 +26,7 @@ func TestMeterDecay(t *testing.T) { ticker: time.NewTicker(time.Millisecond), meters: make(map[*StandardMeter]struct{}), } + defer ma.ticker.Stop() m := newStandardMeter() ma.meters[m] = struct{}{} go ma.tick() diff --git a/metrics/sample_test.go b/metrics/sample_test.go index 3250d88857..c9168d3e82 100644 --- a/metrics/sample_test.go +++ b/metrics/sample_test.go @@ -346,6 +346,7 @@ func TestUniformSampleConcurrentUpdateCount(t *testing.T) { quit := make(chan struct{}) go func() { t := time.NewTicker(10 * time.Millisecond) + defer t.Stop() for { select { case <-t.C: From 37d6357806f49071cad5ab2043545b05eebdb7ee Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 22:02:10 +0800 Subject: [PATCH 072/103] ethstats: add missing Ticker.Stop call (#20867) --- ethstats/ethstats.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ethstats/ethstats.go b/ethstats/ethstats.go index f9284722cf..defca45c89 100644 --- a/ethstats/ethstats.go +++ b/ethstats/ethstats.go @@ -240,6 +240,7 @@ func (s *Service) loop() { } // Keep sending status updates until the connection breaks fullReport := time.NewTicker(15 * time.Second) + defer fullReport.Stop() for err == nil { select { From 0c359e4b9a3f3418245ed8ef131e2ea4e4bab792 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 22:03:40 +0800 Subject: [PATCH 073/103] p2p/discv5, p2p/testing: add missing Timer.Stop calls in tests (#20869) --- p2p/discv5/sim_test.go | 3 ++- p2p/testing/protocolsession.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/p2p/discv5/sim_test.go b/p2p/discv5/sim_test.go index aac26d9c4e..3d1e610d3a 100644 --- a/p2p/discv5/sim_test.go +++ b/p2p/discv5/sim_test.go @@ -43,6 +43,7 @@ func TestSimRandomResolve(t *testing.T) { // A new node joins every 10s. launcher := time.NewTicker(10 * time.Second) + defer launcher.Stop() go func() { for range launcher.C { net := sim.launchNode(false) @@ -55,7 +56,6 @@ func TestSimRandomResolve(t *testing.T) { }() time.Sleep(3 * time.Hour) - launcher.Stop() sim.shutdown() sim.printStats() } @@ -196,6 +196,7 @@ func randomResolves(t *testing.T, s *simulation, net *Network) { } timer := time.NewTimer(randtime()) + defer timer.Stop() for { select { case <-timer.C: diff --git a/p2p/testing/protocolsession.go b/p2p/testing/protocolsession.go index e3a3915a81..58dac93c36 100644 --- a/p2p/testing/protocolsession.go +++ b/p2p/testing/protocolsession.go @@ -242,6 +242,7 @@ func (s *ProtocolSession) testExchange(e Exchange) error { t = 2000 * time.Millisecond } alarm := time.NewTimer(t) + defer alarm.Stop() select { case err := <-errc: return err From f98cabad7cea07a96f80c7b68efc10ff08d483f4 Mon Sep 17 00:00:00 2001 From: ucwong Date: Thu, 2 Apr 2020 22:04:45 +0800 Subject: [PATCH 074/103] core: add missing Timer.Stop call in TestLogReorgs (#20870) --- core/blockchain_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/blockchain_test.go b/core/blockchain_test.go index f15ede449c..665d20ec9c 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -973,6 +973,7 @@ func TestLogReorgs(t *testing.T) { t.Fatalf("failed to insert forked chain: %v", err) } timeout := time.NewTimer(1 * time.Second) + defer timeout.Stop() select { case <-done: case <-timeout.C: From f7b29ec942d91aee420d89d976ed94257398764e Mon Sep 17 00:00:00 2001 From: ucwong Date: Fri, 3 Apr 2020 04:08:45 +0800 Subject: [PATCH 075/103] rpc: add missing timer.Stop calls in websocket tests (#20863) --- rpc/websocket_test.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/rpc/websocket_test.go b/rpc/websocket_test.go index f2a8438d7c..f54fc3cd54 100644 --- a/rpc/websocket_test.go +++ b/rpc/websocket_test.go @@ -142,6 +142,7 @@ func TestClientWebsocketPing(t *testing.T) { // Wait for the subscription result. timeout := time.NewTimer(5 * time.Second) + defer timeout.Stop() for { select { case err := <-sub.Err(): @@ -227,9 +228,11 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- // Write messages. var ( - sendResponse <-chan time.Time - wantPong string + wantPong string + timer = time.NewTimer(0) ) + defer timer.Stop() + <-timer.C for { select { case _, open := <-sendPing: @@ -246,11 +249,10 @@ func wsPingTestHandler(t *testing.T, conn *websocket.Conn, shutdown, sendPing <- t.Errorf("got pong with wrong data %q", data) } wantPong = "" - sendResponse = time.NewTimer(200 * time.Millisecond).C - case <-sendResponse: + timer.Reset(200 * time.Millisecond) + case <-timer.C: t.Logf("server sending response") conn.WriteMessage(websocket.TextMessage, []byte(subNotify)) - sendResponse = nil case <-shutdown: conn.Close() return From 462ddce5b2a47835d057e3950ee5feb9d6c7bc10 Mon Sep 17 00:00:00 2001 From: Luke Champine Date: Fri, 3 Apr 2020 05:57:24 -0400 Subject: [PATCH 076/103] crypto/ecies: improve concatKDF (#20836) This removes a bunch of weird code around the counter overflow check in concatKDF and makes it actually work for different hash output sizes. The overflow check worked as follows: concatKDF applies the hash function N times, where N is roundup(kdLen, hashsize) / hashsize. N should not overflow 32 bits because that would lead to a repetition in the KDF output. A couple issues with the overflow check: - It used the hash.BlockSize, which is wrong because the block size is about the input of the hash function. Luckily, all standard hash functions have a block size that's greater than the output size, so concatKDF didn't crash, it just generated too much key material. - The check used big.Int to compare against 2^32-1. - The calculation could still overflow before reaching the check. The new code in concatKDF doesn't check for overflow. Instead, there is a new check on ECIESParams which ensures that params.KeyLen is < 512. This removes any possibility of overflow. There are a couple of miscellaneous improvements bundled in with this change: - The key buffer is pre-allocated instead of appending the hash output to an initially empty slice. - The code that uses concatKDF to derive keys is now shared between Encrypt and Decrypt. - There was a redundant invocation of IsOnCurve in Decrypt. This is now removed because elliptic.Unmarshal already checks whether the input is a valid curve point since Go 1.5. Co-authored-by: Felix Lange --- crypto/ecies/ecies.go | 147 +++++++++++++------------------------ crypto/ecies/ecies_test.go | 38 +++++++--- crypto/ecies/params.go | 19 +++++ 3 files changed, 94 insertions(+), 110 deletions(-) diff --git a/crypto/ecies/ecies.go b/crypto/ecies/ecies.go index 1474181482..64b5a99d03 100644 --- a/crypto/ecies/ecies.go +++ b/crypto/ecies/ecies.go @@ -35,6 +35,7 @@ import ( "crypto/elliptic" "crypto/hmac" "crypto/subtle" + "encoding/binary" "fmt" "hash" "io" @@ -44,7 +45,6 @@ import ( var ( ErrImport = fmt.Errorf("ecies: failed to import key") ErrInvalidCurve = fmt.Errorf("ecies: invalid elliptic curve") - ErrInvalidParams = fmt.Errorf("ecies: invalid ECIES parameters") ErrInvalidPublicKey = fmt.Errorf("ecies: invalid public key") ErrSharedKeyIsPointAtInfinity = fmt.Errorf("ecies: shared key is point at infinity") ErrSharedKeyTooBig = fmt.Errorf("ecies: shared key params are too big") @@ -138,57 +138,39 @@ func (prv *PrivateKey) GenerateShared(pub *PublicKey, skLen, macLen int) (sk []b } var ( - ErrKeyDataTooLong = fmt.Errorf("ecies: can't supply requested key data") ErrSharedTooLong = fmt.Errorf("ecies: shared secret is too long") ErrInvalidMessage = fmt.Errorf("ecies: invalid message") ) -var ( - big2To32 = new(big.Int).Exp(big.NewInt(2), big.NewInt(32), nil) - big2To32M1 = new(big.Int).Sub(big2To32, big.NewInt(1)) -) - -func incCounter(ctr []byte) { - if ctr[3]++; ctr[3] != 0 { - return - } - if ctr[2]++; ctr[2] != 0 { - return - } - if ctr[1]++; ctr[1] != 0 { - return - } - if ctr[0]++; ctr[0] != 0 { - return - } -} - // NIST SP 800-56 Concatenation Key Derivation Function (see section 5.8.1). -func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) (k []byte, err error) { - if s1 == nil { - s1 = make([]byte, 0) - } - - reps := ((kdLen + 7) * 8) / (hash.BlockSize() * 8) - if big.NewInt(int64(reps)).Cmp(big2To32M1) > 0 { - fmt.Println(big2To32M1) - return nil, ErrKeyDataTooLong - } - - counter := []byte{0, 0, 0, 1} - k = make([]byte, 0) - - for i := 0; i <= reps; i++ { - hash.Write(counter) +func concatKDF(hash hash.Hash, z, s1 []byte, kdLen int) []byte { + counterBytes := make([]byte, 4) + k := make([]byte, 0, roundup(kdLen, hash.Size())) + for counter := uint32(1); len(k) < kdLen; counter++ { + binary.BigEndian.PutUint32(counterBytes, counter) + hash.Reset() + hash.Write(counterBytes) hash.Write(z) hash.Write(s1) - k = append(k, hash.Sum(nil)...) - hash.Reset() - incCounter(counter) + k = hash.Sum(k) } + return k[:kdLen] +} - k = k[:kdLen] - return +// roundup rounds size up to the next multiple of blocksize. +func roundup(size, blocksize int) int { + return size + blocksize - (size % blocksize) +} + +// deriveKeys creates the encryption and MAC keys using concatKDF. +func deriveKeys(hash hash.Hash, z, s1 []byte, keyLen int) (Ke, Km []byte) { + K := concatKDF(hash, z, s1, 2*keyLen) + Ke = K[:keyLen] + Km = K[keyLen:] + hash.Reset() + hash.Write(Km) + Km = hash.Sum(Km[:0]) + return Ke, Km } // messageTag computes the MAC of a message (called the tag) as per @@ -209,7 +191,6 @@ func generateIV(params *ECIESParams, rand io.Reader) (iv []byte, err error) { } // symEncrypt carries out CTR encryption using the block cipher specified in the -// parameters. func symEncrypt(rand io.Reader, params *ECIESParams, key, m []byte) (ct []byte, err error) { c, err := params.Cipher(key) if err != nil { @@ -249,36 +230,27 @@ func symDecrypt(params *ECIESParams, key, ct []byte) (m []byte, err error) { // ciphertext. s1 is fed into key derivation, s2 is fed into the MAC. If the // shared information parameters aren't being used, they should be nil. func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err error) { - params := pub.Params - if params == nil { - if params = ParamsFromCurve(pub.Curve); params == nil { - err = ErrUnsupportedECIESParameters - return - } + params, err := pubkeyParams(pub) + if err != nil { + return nil, err } + R, err := GenerateKey(rand, pub.Curve, params) if err != nil { - return + return nil, err + } + + z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) + if err != nil { + return nil, err } hash := params.Hash() - z, err := R.GenerateShared(pub, params.KeyLen, params.KeyLen) - if err != nil { - return - } - K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - if err != nil { - return - } - Ke := K[:params.KeyLen] - Km := K[params.KeyLen:] - hash.Write(Km) - Km = hash.Sum(nil) - hash.Reset() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) em, err := symEncrypt(rand, params, Ke, m) if err != nil || len(em) <= params.BlockSize { - return + return nil, err } d := messageTag(params.Hash, Km, em, s2) @@ -288,7 +260,7 @@ func Encrypt(rand io.Reader, pub *PublicKey, m, s1, s2 []byte) (ct []byte, err e copy(ct, Rb) copy(ct[len(Rb):], em) copy(ct[len(Rb)+len(em):], d) - return + return ct, nil } // Decrypt decrypts an ECIES ciphertext. @@ -296,13 +268,11 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { if len(c) == 0 { return nil, ErrInvalidMessage } - params := prv.PublicKey.Params - if params == nil { - if params = ParamsFromCurve(prv.PublicKey.Curve); params == nil { - err = ErrUnsupportedECIESParameters - return - } + params, err := pubkeyParams(&prv.PublicKey) + if err != nil { + return nil, err } + hash := params.Hash() var ( @@ -316,12 +286,10 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { case 2, 3, 4: rLen = (prv.PublicKey.Curve.Params().BitSize + 7) / 4 if len(c) < (rLen + hLen + 1) { - err = ErrInvalidMessage - return + return nil, ErrInvalidMessage } default: - err = ErrInvalidPublicKey - return + return nil, ErrInvalidPublicKey } mStart = rLen @@ -331,36 +299,19 @@ func (prv *PrivateKey) Decrypt(c, s1, s2 []byte) (m []byte, err error) { R.Curve = prv.PublicKey.Curve R.X, R.Y = elliptic.Unmarshal(R.Curve, c[:rLen]) if R.X == nil { - err = ErrInvalidPublicKey - return - } - if !R.Curve.IsOnCurve(R.X, R.Y) { - err = ErrInvalidCurve - return + return nil, ErrInvalidPublicKey } z, err := prv.GenerateShared(R, params.KeyLen, params.KeyLen) if err != nil { - return + return nil, err } - - K, err := concatKDF(hash, z, s1, params.KeyLen+params.KeyLen) - if err != nil { - return - } - - Ke := K[:params.KeyLen] - Km := K[params.KeyLen:] - hash.Write(Km) - Km = hash.Sum(nil) - hash.Reset() + Ke, Km := deriveKeys(hash, z, s1, params.KeyLen) d := messageTag(params.Hash, Km, c[mStart:mEnd], s2) if subtle.ConstantTimeCompare(c[mEnd:], d) != 1 { - err = ErrInvalidMessage - return + return nil, ErrInvalidMessage } - m, err = symDecrypt(params, Ke, c[mStart:mEnd]) - return + return symDecrypt(params, Ke, c[mStart:mEnd]) } diff --git a/crypto/ecies/ecies_test.go b/crypto/ecies/ecies_test.go index b465f076f4..0a6aeb2b51 100644 --- a/crypto/ecies/ecies_test.go +++ b/crypto/ecies/ecies_test.go @@ -42,17 +42,23 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -// Ensure the KDF generates appropriately sized keys. func TestKDF(t *testing.T) { - msg := []byte("Hello, world") - h := sha256.New() - - k, err := concatKDF(h, msg, nil, 64) - if err != nil { - t.Fatal(err) + tests := []struct { + length int + output []byte + }{ + {6, decode("858b192fa2ed")}, + {32, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0")}, + {48, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955")}, + {64, decode("858b192fa2ed4395e2bf88dd8d5770d67dc284ee539f12da8bceaa45d06ebae0700f1ab918a5f0413b8140f9940d6955f3467fd6672cce1024c5b1effccc0f61")}, } - if len(k) != 64 { - t.Fatalf("KDF: generated key is the wrong size (%d instead of 64\n", len(k)) + + for _, test := range tests { + h := sha256.New() + k := concatKDF(h, []byte("input"), nil, test.length) + if !bytes.Equal(k, test.output) { + t.Fatalf("KDF: generated key %x does not match expected output %x", k, test.output) + } } } @@ -293,8 +299,8 @@ func TestParamSelection(t *testing.T) { func testParamSelection(t *testing.T, c testCase) { params := ParamsFromCurve(c.Curve) - if params == nil && c.Expected != nil { - t.Fatalf("%s (%s)\n", ErrInvalidParams.Error(), c.Name) + if params == nil { + t.Fatal("ParamsFromCurve returned nil") } else if params != nil && !cmpParams(params, c.Expected) { t.Fatalf("ecies: parameters should be invalid (%s)\n", c.Name) } @@ -401,7 +407,7 @@ func TestSharedKeyStatic(t *testing.T) { t.Fatal(ErrBadSharedKeys) } - sk, _ := hex.DecodeString("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") + sk := decode("167ccc13ac5e8a26b131c3446030c60fbfac6aa8e31149d0869f93626a4cdf62") if !bytes.Equal(sk1, sk) { t.Fatalf("shared secret mismatch: want: %x have: %x", sk, sk1) } @@ -414,3 +420,11 @@ func hexKey(prv string) *PrivateKey { } return ImportECDSA(key) } + +func decode(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} diff --git a/crypto/ecies/params.go b/crypto/ecies/params.go index 6312daf5a1..0bd3877ddd 100644 --- a/crypto/ecies/params.go +++ b/crypto/ecies/params.go @@ -49,8 +49,14 @@ var ( DefaultCurve = ethcrypto.S256() ErrUnsupportedECDHAlgorithm = fmt.Errorf("ecies: unsupported ECDH algorithm") ErrUnsupportedECIESParameters = fmt.Errorf("ecies: unsupported ECIES parameters") + ErrInvalidKeyLen = fmt.Errorf("ecies: invalid key size (> %d) in ECIESParams", maxKeyLen) ) +// KeyLen is limited to prevent overflow of the counter +// in concatKDF. While the theoretical limit is much higher, +// no known cipher uses keys larger than 512 bytes. +const maxKeyLen = 512 + type ECIESParams struct { Hash func() hash.Hash // hash function hashAlgo crypto.Hash @@ -115,3 +121,16 @@ func AddParamsForCurve(curve elliptic.Curve, params *ECIESParams) { func ParamsFromCurve(curve elliptic.Curve) (params *ECIESParams) { return paramsFromCurve[curve] } + +func pubkeyParams(key *PublicKey) (*ECIESParams, error) { + params := key.Params + if params == nil { + if params = ParamsFromCurve(key.Curve); params == nil { + return nil, ErrUnsupportedECIESParameters + } + } + if params.KeyLen > maxKeyLen { + return nil, ErrInvalidKeyLen + } + return params, nil +} From be9172a7ac5cf8a6919a36213531c51ecb5cc6ef Mon Sep 17 00:00:00 2001 From: gary rong Date: Fri, 3 Apr 2020 18:36:44 +0800 Subject: [PATCH 077/103] rpc: metrics for JSON-RPC method calls (#20847) This adds a couple of metrics for tracking the timing and frequency of method calls: - rpc/requests gauge counts all requests - rpc/success gauge counts requests which return err == nil - rpc/failure gauge counts requests which return err != nil - rpc/duration/all timer tracks timing of all requests - rpc/duration// tracks per-method timing --- rpc/handler.go | 16 +++++++++++++++- rpc/metrics.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 rpc/metrics.go diff --git a/rpc/handler.go b/rpc/handler.go index ab32cf47e4..c8571ad795 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -327,8 +327,22 @@ func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage if err != nil { return msg.errorResponse(&invalidParamsError{err.Error()}) } + start := time.Now() + answer := h.runMethod(cp.ctx, msg, callb, args) - return h.runMethod(cp.ctx, msg, callb, args) + // Collect the statistics for RPC calls if metrics is enabled. + // We only care about pure rpc call. Filter out subscription. + if callb != h.unsubscribeCb { + rpcRequestGauge.Inc(1) + if answer.Error != nil { + failedReqeustGauge.Inc(1) + } else { + successfulRequestGauge.Inc(1) + } + rpcServingTimer.UpdateSince(start) + newRPCServingTimer(msg.Method, answer.Error == nil).UpdateSince(start) + } + return answer } // handleSubscribe processes *_subscribe method calls. diff --git a/rpc/metrics.go b/rpc/metrics.go new file mode 100644 index 0000000000..7fb6fc0a17 --- /dev/null +++ b/rpc/metrics.go @@ -0,0 +1,39 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/metrics" +) + +var ( + rpcRequestGauge = metrics.NewRegisteredGauge("rpc/requests", nil) + successfulRequestGauge = metrics.NewRegisteredGauge("rpc/success", nil) + failedReqeustGauge = metrics.NewRegisteredGauge("rpc/failure", nil) + rpcServingTimer = metrics.NewRegisteredTimer("rpc/duration/all", nil) +) + +func newRPCServingTimer(method string, valid bool) metrics.Timer { + flag := "success" + if !valid { + flag = "failure" + } + m := fmt.Sprintf("rpc/duration/%s/%s", method, flag) + return metrics.GetOrRegisterTimer(m, nil) +} From 98eab2dbe7f2c0b4d6ae73ec61d1e5b2e600e0db Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 3 Apr 2020 14:11:04 +0200 Subject: [PATCH 078/103] mobile: use bind.NewKeyedTransactor instead of duplicating (#20888) It's better to reuse the existing code to create a keyed transactor than to rewrite the logic again. --- mobile/bind.go | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/mobile/bind.go b/mobile/bind.go index 48e9921b44..dc3f32b152 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -19,7 +19,6 @@ package geth import ( - "errors" "math/big" "strings" @@ -28,7 +27,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" ) // Signer is an interface defining the callback when a contract requires a @@ -82,28 +80,14 @@ func NewTransactOpts() *TransactOpts { return new(TransactOpts) } -// NewKeyedTransactor is a utility method to easily create a transaction signer +// NewKeyedTransactOpts is a utility method to easily create a transaction signer // from a single private key. func NewKeyedTransactOpts(keyJson []byte, passphrase string) (*TransactOpts, error) { key, err := keystore.DecryptKey(keyJson, passphrase) if err != nil { return nil, err } - keyAddr := crypto.PubkeyToAddress(key.PrivateKey.PublicKey) - opts := bind.TransactOpts{ - From: keyAddr, - Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { - if address != keyAddr { - return nil, errors.New("not authorized to sign this account") - } - signature, err := crypto.Sign(signer.Hash(tx).Bytes(), key.PrivateKey) - if err != nil { - return nil, err - } - return tx.WithSignature(signer, signature) - }, - } - return &TransactOpts{opts}, nil + return &TransactOpts{*bind.NewKeyedTransactor(key.PrivateKey)}, nil } func (opts *TransactOpts) GetFrom() *Address { return &Address{opts.opts.From} } From be6078ad831dea01121510dfc9ab1f264a3a189c Mon Sep 17 00:00:00 2001 From: Boqin Qin Date: Sat, 4 Apr 2020 02:07:22 +0800 Subject: [PATCH 079/103] all: fix a bunch of inconsequential goroutine leaks (#20667) The leaks were mostly in unit tests, and could all be resolved by adding suitably-sized channel buffers or by restructuring the test to not send on a channel after an error has occurred. There is an unavoidable goroutine leak in Console.Interactive: when we receive a signal, the line reader cannot be unblocked and will get stuck. This leak is now documented and I've tried to make it slightly less bad by adding a one-element buffer to the output channels of the line-reading loop. Should the reader eventually awake from its blocked state (i.e. when stdin is closed), at least it won't get stuck trying to send to the interpreter loop which has quit long ago. Co-authored-by: Felix Lange --- common/prque/lazyqueue_test.go | 19 +++++--- console/console.go | 85 +++++++++++++++++++--------------- event/event_test.go | 1 + miner/worker_test.go | 43 ++++++----------- rpc/client_test.go | 5 +- rpc/subscription_test.go | 78 ++++++++++++++++++------------- 6 files changed, 124 insertions(+), 107 deletions(-) diff --git a/common/prque/lazyqueue_test.go b/common/prque/lazyqueue_test.go index 0bd4fc6597..be9491e24e 100644 --- a/common/prque/lazyqueue_test.go +++ b/common/prque/lazyqueue_test.go @@ -74,17 +74,22 @@ func TestLazyQueue(t *testing.T) { q.Push(&items[i]) } - var lock sync.Mutex - stopCh := make(chan chan struct{}) + var ( + lock sync.Mutex + wg sync.WaitGroup + stopCh = make(chan chan struct{}) + ) + defer wg.Wait() + wg.Add(1) go func() { + defer wg.Done() for { select { case <-clock.After(testQueueRefresh): lock.Lock() q.Refresh() lock.Unlock() - case stop := <-stopCh: - close(stop) + case <-stopCh: return } } @@ -104,6 +109,8 @@ func TestLazyQueue(t *testing.T) { if rand.Intn(100) == 0 { p := q.PopItem().(*lazyItem) if p.p != maxPri { + lock.Unlock() + close(stopCh) t.Fatalf("incorrect item (best known priority %d, popped %d)", maxPri, p.p) } q.Push(p) @@ -113,7 +120,5 @@ func TestLazyQueue(t *testing.T) { clock.WaitForTimers(1) } - stop := make(chan struct{}) - stopCh <- stop - <-stop + close(stopCh) } diff --git a/console/console.go b/console/console.go index a2f12d8ede..e2b4835e46 100644 --- a/console/console.go +++ b/console/console.go @@ -340,62 +340,61 @@ func (c *Console) Evaluate(statement string) { // the configured user prompter. func (c *Console) Interactive() { var ( - prompt = c.prompt // Current prompt line (used for multi-line inputs) - indents = 0 // Current number of input indents (used for multi-line inputs) - input = "" // Current user input - scheduler = make(chan string) // Channel to send the next prompt on and receive the input + prompt = c.prompt // the current prompt line (used for multi-line inputs) + indents = 0 // the current number of input indents (used for multi-line inputs) + input = "" // the current user input + inputLine = make(chan string, 1) // receives user input + inputErr = make(chan error, 1) // receives liner errors + requestLine = make(chan string) // requests a line of input + interrupt = make(chan os.Signal, 1) ) - // Start a goroutine to listen for prompt requests and send back inputs - go func() { - for { - // Read the next user input - line, err := c.prompter.PromptInput(<-scheduler) - if err != nil { - // In case of an error, either clear the prompt or fail - if err == liner.ErrPromptAborted { // ctrl-C - prompt, indents, input = c.prompt, 0, "" - scheduler <- "" - continue - } - close(scheduler) - return - } - // User input retrieved, send for interpretation and loop - scheduler <- line - } - }() - // Monitor Ctrl-C too in case the input is empty and we need to bail - abort := make(chan os.Signal, 1) - signal.Notify(abort, syscall.SIGINT, syscall.SIGTERM) - // Start sending prompts to the user and reading back inputs + // Monitor Ctrl-C. While liner does turn on the relevant terminal mode bits to avoid + // the signal, a signal can still be received for unsupported terminals. Unfortunately + // there is no way to cancel the line reader when this happens. The readLines + // goroutine will be leaked in this case. + signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM) + defer signal.Stop(interrupt) + + // The line reader runs in a separate goroutine. + go c.readLines(inputLine, inputErr, requestLine) + defer close(requestLine) + for { - // Send the next prompt, triggering an input read and process the result - scheduler <- prompt + // Send the next prompt, triggering an input read. + requestLine <- prompt + select { - case <-abort: - // User forcefully quite the console + case <-interrupt: fmt.Fprintln(c.printer, "caught interrupt, exiting") return - case line, ok := <-scheduler: - // User input was returned by the prompter, handle special cases - if !ok || (indents <= 0 && exit.MatchString(line)) { + case err := <-inputErr: + if err == liner.ErrPromptAborted && indents > 0 { + // When prompting for multi-line input, the first Ctrl-C resets + // the multi-line state. + prompt, indents, input = c.prompt, 0, "" + continue + } + return + + case line := <-inputLine: + // User input was returned by the prompter, handle special cases. + if indents <= 0 && exit.MatchString(line) { return } if onlyWhitespace.MatchString(line) { continue } - // Append the line to the input and check for multi-line interpretation + // Append the line to the input and check for multi-line interpretation. input += line + "\n" - indents = countIndents(input) if indents <= 0 { prompt = c.prompt } else { prompt = strings.Repeat(".", indents*3) + " " } - // If all the needed lines are present, save the command and run + // If all the needed lines are present, save the command and run it. if indents <= 0 { if len(input) > 0 && input[0] != ' ' && !passwordRegexp.MatchString(input) { if command := strings.TrimSpace(input); len(c.history) == 0 || command != c.history[len(c.history)-1] { @@ -412,6 +411,18 @@ func (c *Console) Interactive() { } } +// readLines runs in its own goroutine, prompting for input. +func (c *Console) readLines(input chan<- string, errc chan<- error, prompt <-chan string) { + for p := range prompt { + line, err := c.prompter.PromptInput(p) + if err != nil { + errc <- err + } else { + input <- line + } + } +} + // countIndents returns the number of identations for the given input. // In case of invalid input such as var a = } the result can be negative. func countIndents(input string) int { diff --git a/event/event_test.go b/event/event_test.go index 2be357ba2d..cc9fa5d7c8 100644 --- a/event/event_test.go +++ b/event/event_test.go @@ -203,6 +203,7 @@ func BenchmarkPostConcurrent(b *testing.B) { // for comparison func BenchmarkChanSend(b *testing.B) { c := make(chan interface{}) + defer close(c) closed := make(chan struct{}) go func() { for range c { diff --git a/miner/worker_test.go b/miner/worker_test.go index 78e09a1767..86bb7db646 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,7 +17,6 @@ package miner import ( - "fmt" "math/big" "math/rand" "sync/atomic" @@ -210,49 +209,37 @@ func testGenerateBlockAndImport(t *testing.T, isClique bool) { w, b := newTestWorker(t, chainConfig, engine, db, 0) defer w.close() + // This test chain imports the mined blocks. db2 := rawdb.NewMemoryDatabase() b.genesis.MustCommit(db2) chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{}, nil) defer chain.Stop() - var ( - loopErr = make(chan error) - newBlock = make(chan struct{}) - subscribe = make(chan struct{}) - ) - listenNewBlock := func() { - sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) - defer sub.Unsubscribe() - - subscribe <- struct{}{} - for item := range sub.Chan() { - block := item.Data.(core.NewMinedBlockEvent).Block - _, err := chain.InsertChain([]*types.Block{block}) - if err != nil { - loopErr <- fmt.Errorf("failed to insert new mined block:%d, error:%v", block.NumberU64(), err) - } - newBlock <- struct{}{} - } - } - // Ignore empty commit here for less noise + // Ignore empty commit here for less noise. w.skipSealHook = func(task *task) bool { return len(task.receipts) == 0 } - go listenNewBlock() - <-subscribe // Ensure the subscription is created - w.start() // Start mining! + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Start mining! + w.start() for i := 0; i < 5; i++ { b.txPool.AddLocal(b.newRandomTx(true)) b.txPool.AddLocal(b.newRandomTx(false)) w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) w.postSideBlock(core.ChainSideEvent{Block: b.newRandomUncle()}) + select { - case e := <-loopErr: - t.Fatal(e) - case <-newBlock: - case <-time.NewTimer(3 * time.Second).C: // Worker needs 1s to include new changes. + case ev := <-sub.Chan(): + block := ev.Data.(core.NewMinedBlockEvent).Block + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. t.Fatalf("timeout") } } diff --git a/rpc/client_test.go b/rpc/client_test.go index 1d87d96671..8a996aeda5 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -413,15 +413,14 @@ func TestClientHTTP(t *testing.T) { // Launch concurrent requests. var ( results = make([]echoResult, 100) - errc = make(chan error) + errc = make(chan error, len(results)) wantResult = echoResult{"a", 1, new(echoArgs)} ) defer client.Close() for i := range results { i := i go func() { - errc <- client.Call(&results[i], "test_echo", - wantResult.String, wantResult.Int, wantResult.Args) + errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) }() } diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go index c3a918a832..54a053dba8 100644 --- a/rpc/subscription_test.go +++ b/rpc/subscription_test.go @@ -125,22 +125,25 @@ func TestSubscriptions(t *testing.T) { // This test checks that unsubscribing works. func TestServerUnsubscribe(t *testing.T) { + p1, p2 := net.Pipe() + defer p2.Close() + // Start the server. server := newTestServer() - service := ¬ificationTestService{unsubscribed: make(chan string)} + service := ¬ificationTestService{unsubscribed: make(chan string, 1)} server.RegisterName("nftest2", service) - p1, p2 := net.Pipe() go server.ServeCodec(NewCodec(p1), 0) - p2.SetDeadline(time.Now().Add(10 * time.Second)) - // Subscribe. + p2.SetDeadline(time.Now().Add(10 * time.Second)) p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`)) // Handle received messages. - resps := make(chan subConfirmation) - notifications := make(chan subscriptionResult) - errors := make(chan error) + var ( + resps = make(chan subConfirmation) + notifications = make(chan subscriptionResult) + errors = make(chan error, 1) + ) go waitForMessages(json.NewDecoder(p2), resps, notifications, errors) // Receive the subscription ID. @@ -173,34 +176,45 @@ type subConfirmation struct { subid ID } +// waitForMessages reads RPC messages from 'in' and dispatches them into the given channels. +// It stops if there is an error. func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) { for { - var msg jsonrpcMessage - if err := in.Decode(&msg); err != nil { - errors <- fmt.Errorf("decode error: %v", err) - return - } - switch { - case msg.isNotification(): - var res subscriptionResult - if err := json.Unmarshal(msg.Params, &res); err != nil { - errors <- fmt.Errorf("invalid subscription result: %v", err) - } else { - notifications <- res - } - case msg.isResponse(): - var c subConfirmation - if msg.Error != nil { - errors <- msg.Error - } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { - errors <- fmt.Errorf("invalid response: %v", err) - } else { - json.Unmarshal(msg.ID, &c.reqid) - successes <- c - } - default: - errors <- fmt.Errorf("unrecognized message: %v", msg) + resp, notification, err := readAndValidateMessage(in) + if err != nil { + errors <- err return + } else if resp != nil { + successes <- *resp + } else { + notifications <- *notification } } } + +func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionResult, error) { + var msg jsonrpcMessage + if err := in.Decode(&msg); err != nil { + return nil, nil, fmt.Errorf("decode error: %v", err) + } + switch { + case msg.isNotification(): + var res subscriptionResult + if err := json.Unmarshal(msg.Params, &res); err != nil { + return nil, nil, fmt.Errorf("invalid subscription result: %v", err) + } + return nil, &res, nil + case msg.isResponse(): + var c subConfirmation + if msg.Error != nil { + return nil, nil, msg.Error + } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { + return nil, nil, fmt.Errorf("invalid response: %v", err) + } else { + json.Unmarshal(msg.ID, &c.reqid) + return &c, nil, nil + } + default: + return nil, nil, fmt.Errorf("unrecognized message: %v", msg) + } +} From 3cf7d2e9a6605bef8140b01483d65da874e11a8a Mon Sep 17 00:00:00 2001 From: William Morriss Date: Fri, 3 Apr 2020 11:10:53 -0700 Subject: [PATCH 080/103] internal/ethapi: add CallArgs.ToMessage method (#20854) ToMessage is used to convert between ethapi.CallArgs and types.Message. It reduces the length of the DoCall method by about half by abstracting out the conversion between the CallArgs and the Message. This should improve the code's maintainability and reusability. --- internal/ethapi/api.go | 69 ++++++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 68afa83c76..7b9f9dafaf 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -739,6 +739,42 @@ type CallArgs struct { Data *hexutil.Bytes `json:"data"` } +// ToMessage converts CallArgs to the Message type used by the core evm +func (args *CallArgs) ToMessage(globalGasCap *big.Int) types.Message { + // Set sender address or use zero address if none specified. + var addr common.Address + if args.From != nil { + addr = *args.From + } + + // Set default gas & gas price if none were set + gas := uint64(math.MaxUint64 / 2) + if args.Gas != nil { + gas = uint64(*args.Gas) + } + if globalGasCap != nil && globalGasCap.Uint64() < gas { + log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) + gas = globalGasCap.Uint64() + } + gasPrice := new(big.Int) + if args.GasPrice != nil { + gasPrice = args.GasPrice.ToInt() + } + + value := new(big.Int) + if args.Value != nil { + value = args.Value.ToInt() + } + + var data []byte + if args.Data != nil { + data = []byte(*args.Data) + } + + msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) + return msg +} + // account indicates the overriding fields of account during the execution of // a message call. // Note, state and stateDiff can't be specified at the same time. If state is @@ -761,12 +797,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo return nil, 0, false, err } - // Set sender address or use zero address if none specified. - var addr common.Address - if args.From != nil { - addr = *args.From - } - // Override the fields of specified contracts before execution. for addr, account := range overrides { // Override account nonce. @@ -795,32 +825,6 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo } } } - // Set default gas & gas price if none were set - gas := uint64(math.MaxUint64 / 2) - if args.Gas != nil { - gas = uint64(*args.Gas) - } - if globalGasCap != nil && globalGasCap.Uint64() < gas { - log.Warn("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap) - gas = globalGasCap.Uint64() - } - gasPrice := new(big.Int) - if args.GasPrice != nil { - gasPrice = args.GasPrice.ToInt() - } - - value := new(big.Int) - if args.Value != nil { - value = args.Value.ToInt() - } - - var data []byte - if args.Data != nil { - data = []byte(*args.Data) - } - - // Create new call message - msg := types.NewMessage(addr, args.To, 0, value, gas, gasPrice, data, false) // Setup context so it may be cancelled the call has completed // or, in case of unmetered gas, setup a context with a timeout. @@ -835,6 +839,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo defer cancel() // Get a new instance of the EVM. + msg := args.ToMessage(globalGasCap) evm, vmError, err := b.GetEVM(ctx, msg, state, header) if err != nil { return nil, 0, false, err From f0b5eb09eb30545b11ad5c0179517fc2159ad02d Mon Sep 17 00:00:00 2001 From: gary rong Date: Tue, 7 Apr 2020 14:16:21 +0800 Subject: [PATCH 081/103] eth, les: fix flaky tests (#20897) * les: fix flaky test * eth: fix flaky test --- eth/handler_test.go | 2 +- les/peer_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/handler_test.go b/eth/handler_test.go index 5914f4c643..e5449c3420 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -615,7 +615,7 @@ func testBroadcastBlock(t *testing.T, totalPeers, broadcastExpected int) { case <-doneCh: received++ - case <-time.After(100 * time.Millisecond): + case <-time.After(time.Second): if received != broadcastExpected { t.Errorf("broadcast count mismatch: have %d, want %d", received, broadcastExpected) } diff --git a/les/peer_test.go b/les/peer_test.go index 59a2ad7009..8309e3557e 100644 --- a/les/peer_test.go +++ b/les/peer_test.go @@ -140,7 +140,7 @@ func TestHandshake(t *testing.T) { if err != nil { t.Fatalf("handshake failed, %v", err) } - case <-time.NewTimer(100 * time.Millisecond).C: + case <-time.After(time.Second): t.Fatalf("timeout") } } From 0bec6a43f68ec5eb608303c346985d38c7c7e1fb Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 7 Apr 2020 10:23:57 +0200 Subject: [PATCH 082/103] cmd/geth: enable metrics for geth import command (#20738) * cmd/geth: enable metrics for geth import command * cmd/geth: enable metrics-flags for import command --- cmd/geth/chaincmd.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index c5ae550e34..3e3085fd83 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/trie" "gopkg.in/urfave/cli.v1" ) @@ -82,6 +83,14 @@ The dumpgenesis command dumps the genesis block configuration in JSON format to utils.SnapshotFlag, utils.CacheDatabaseFlag, utils.CacheGCFlag, + utils.MetricsEnabledFlag, + utils.MetricsEnabledExpensiveFlag, + utils.MetricsEnableInfluxDBFlag, + utils.MetricsInfluxDBEndpointFlag, + utils.MetricsInfluxDBDatabaseFlag, + utils.MetricsInfluxDBUsernameFlag, + utils.MetricsInfluxDBPasswordFlag, + utils.MetricsInfluxDBTagsFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -255,6 +264,10 @@ func importChain(ctx *cli.Context) error { if len(ctx.Args()) < 1 { utils.Fatalf("This command requires an argument.") } + // Start metrics export if enabled + utils.SetupMetrics(ctx) + // Start system runtime metrics collection + go metrics.CollectProcessMetrics(3 * time.Second) stack := makeFullNode(ctx) defer stack.Close() From 8dc89415511ec0d5f8b0128e9ef9fb624ddb5d56 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 7 Apr 2020 11:45:21 +0200 Subject: [PATCH 083/103] core/vm: use a callcontext struct (#20761) * core/vm: use a callcontext struct * core/vm: fix tests * core/vm/runtime: benchmark * core/vm: make intpool push inlineable, unexpose callcontext --- core/vm/eips.go | 10 +- core/vm/instructions.go | 546 ++++++++++++++++---------------- core/vm/instructions_test.go | 16 +- core/vm/interpreter.go | 28 +- core/vm/intpool.go | 11 + core/vm/jump_table.go | 2 +- core/vm/runtime/runtime_test.go | 28 ++ 7 files changed, 349 insertions(+), 292 deletions(-) diff --git a/core/vm/eips.go b/core/vm/eips.go index 062d40779e..8bf697e1bc 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -60,9 +60,9 @@ func enable1884(jt *JumpTable) { } } -func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(contract.Address())) - stack.push(balance) +func opSelfBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + balance := interpreter.intPool.get().Set(interpreter.evm.StateDB.GetBalance(callContext.contract.Address())) + callContext.stack.push(balance) return nil, nil } @@ -80,9 +80,9 @@ func enable1344(jt *JumpTable) { } // opChainID implements CHAINID opcode -func opChainID(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opChainID(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { chainId := interpreter.intPool.get().Set(interpreter.evm.chainConfig.ChainID) - stack.push(chainId) + callContext.stack.push(chainId) return nil, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index d65664b67d..9ac4cd47cd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -37,48 +37,48 @@ var ( errInvalidJump = errors.New("evm: invalid jump destination") ) -func opAdd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opAdd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() math.U256(y.Add(x, y)) - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opSub(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opSub(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() math.U256(y.Sub(x, y)) - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opMul(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(math.U256(x.Mul(x, y))) +func opMul(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.pop() + callContext.stack.push(math.U256(x.Mul(x, y))) - interpreter.intPool.put(y) + interpreter.intPool.putOne(y) return nil, nil } -func opDiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opDiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() if y.Sign() != 0 { math.U256(y.Div(x, y)) } else { y.SetUint64(0) } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opSdiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) +func opSdiv(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop()) res := interpreter.intPool.getZero() if y.Sign() == 0 || x.Sign() == 0 { - stack.push(res) + callContext.stack.push(res) } else { if x.Sign() != y.Sign() { res.Div(x.Abs(x), y.Abs(y)) @@ -86,29 +86,29 @@ func opSdiv(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory } else { res.Div(x.Abs(x), y.Abs(y)) } - stack.push(math.U256(res)) + callContext.stack.push(math.U256(res)) } interpreter.intPool.put(x, y) return nil, nil } -func opMod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() +func opMod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.pop() if y.Sign() == 0 { - stack.push(x.SetUint64(0)) + callContext.stack.push(x.SetUint64(0)) } else { - stack.push(math.U256(x.Mod(x, y))) + callContext.stack.push(math.U256(x.Mod(x, y))) } - interpreter.intPool.put(y) + interpreter.intPool.putOne(y) return nil, nil } -func opSmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := math.S256(stack.pop()), math.S256(stack.pop()) +func opSmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := math.S256(callContext.stack.pop()), math.S256(callContext.stack.pop()) res := interpreter.intPool.getZero() if y.Sign() == 0 { - stack.push(res) + callContext.stack.push(res) } else { if x.Sign() < 0 { res.Mod(x.Abs(x), y.Abs(y)) @@ -116,38 +116,38 @@ func opSmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory } else { res.Mod(x.Abs(x), y.Abs(y)) } - stack.push(math.U256(res)) + callContext.stack.push(math.U256(res)) } interpreter.intPool.put(x, y) return nil, nil } -func opExp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - base, exponent := stack.pop(), stack.pop() +func opExp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + base, exponent := callContext.stack.pop(), callContext.stack.pop() // some shortcuts cmpToOne := exponent.Cmp(big1) if cmpToOne < 0 { // Exponent is zero // x ^ 0 == 1 - stack.push(base.SetUint64(1)) + callContext.stack.push(base.SetUint64(1)) } else if base.Sign() == 0 { // 0 ^ y, if y != 0, == 0 - stack.push(base.SetUint64(0)) + callContext.stack.push(base.SetUint64(0)) } else if cmpToOne == 0 { // Exponent is one // x ^ 1 == x - stack.push(base) + callContext.stack.push(base) } else { - stack.push(math.Exp(base, exponent)) - interpreter.intPool.put(base) + callContext.stack.push(math.Exp(base, exponent)) + interpreter.intPool.putOne(base) } - interpreter.intPool.put(exponent) + interpreter.intPool.putOne(exponent) return nil, nil } -func opSignExtend(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - back := stack.pop() +func opSignExtend(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + back := callContext.stack.pop() if back.Cmp(big.NewInt(31)) < 0 { bit := uint(back.Uint64()*8 + 7) - num := stack.pop() + num := callContext.stack.pop() mask := back.Lsh(common.Big1, bit) mask.Sub(mask, common.Big1) if num.Bit(int(bit)) > 0 { @@ -156,43 +156,43 @@ func opSignExtend(pc *uint64, interpreter *EVMInterpreter, contract *Contract, m num.And(num, mask) } - stack.push(math.U256(num)) + callContext.stack.push(math.U256(num)) } - interpreter.intPool.put(back) + interpreter.intPool.putOne(back) return nil, nil } -func opNot(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() +func opNot(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() math.U256(x.Not(x)) return nil, nil } -func opLt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opLt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() if x.Cmp(y) < 0 { y.SetUint64(1) } else { y.SetUint64(0) } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opGt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opGt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() if x.Cmp(y) > 0 { y.SetUint64(1) } else { y.SetUint64(0) } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opSlt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opSlt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() xSign := x.Cmp(tt255) ySign := y.Cmp(tt255) @@ -211,12 +211,12 @@ func opSlt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * y.SetUint64(0) } } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opSgt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opSgt(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() xSign := x.Cmp(tt255) ySign := y.Cmp(tt255) @@ -235,23 +235,23 @@ func opSgt(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * y.SetUint64(0) } } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opEq(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opEq(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() if x.Cmp(y) == 0 { y.SetUint64(1) } else { y.SetUint64(0) } - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opIszero(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x := stack.peek() +func opIszero(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x := callContext.stack.peek() if x.Sign() > 0 { x.SetUint64(0) } else { @@ -260,63 +260,63 @@ func opIszero(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor return nil, nil } -func opAnd(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.pop() - stack.push(x.And(x, y)) +func opAnd(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.pop() + callContext.stack.push(x.And(x, y)) - interpreter.intPool.put(y) + interpreter.intPool.putOne(y) return nil, nil } -func opOr(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opOr(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() y.Or(x, y) - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opXor(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y := stack.pop(), stack.peek() +func opXor(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y := callContext.stack.pop(), callContext.stack.peek() y.Xor(x, y) - interpreter.intPool.put(x) + interpreter.intPool.putOne(x) return nil, nil } -func opByte(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - th, val := stack.pop(), stack.peek() +func opByte(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + th, val := callContext.stack.pop(), callContext.stack.peek() if th.Cmp(common.Big32) < 0 { b := math.Byte(val, 32, int(th.Int64())) val.SetUint64(uint64(b)) } else { val.SetUint64(0) } - interpreter.intPool.put(th) + interpreter.intPool.putOne(th) return nil, nil } -func opAddmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() +func opAddmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() if z.Cmp(bigZero) > 0 { x.Add(x, y) x.Mod(x, z) - stack.push(math.U256(x)) + callContext.stack.push(math.U256(x)) } else { - stack.push(x.SetUint64(0)) + callContext.stack.push(x.SetUint64(0)) } interpreter.intPool.put(y, z) return nil, nil } -func opMulmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - x, y, z := stack.pop(), stack.pop(), stack.pop() +func opMulmod(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + x, y, z := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() if z.Cmp(bigZero) > 0 { x.Mul(x, y) x.Mod(x, z) - stack.push(math.U256(x)) + callContext.stack.push(math.U256(x)) } else { - stack.push(x.SetUint64(0)) + callContext.stack.push(x.SetUint64(0)) } interpreter.intPool.put(y, z) return nil, nil @@ -325,10 +325,10 @@ func opMulmod(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor // opSHL implements Shift Left // The SHL instruction (shift left) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the left by arg1 number of bits. -func opSHL(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHL(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool + shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek()) + defer interpreter.intPool.putOne(shift) // First operand back into the pool if shift.Cmp(common.Big256) >= 0 { value.SetUint64(0) @@ -343,10 +343,10 @@ func opSHL(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * // opSHR implements Logical Shift Right // The SHR instruction (logical shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with zero fill. -func opSHR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSHR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, second operand is left in the stack; accumulate result into it, and no need to push it afterwards - shift, value := math.U256(stack.pop()), math.U256(stack.peek()) - defer interpreter.intPool.put(shift) // First operand back into the pool + shift, value := math.U256(callContext.stack.pop()), math.U256(callContext.stack.peek()) + defer interpreter.intPool.putOne(shift) // First operand back into the pool if shift.Cmp(common.Big256) >= 0 { value.SetUint64(0) @@ -361,10 +361,10 @@ func opSHR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * // opSAR implements Arithmetic Shift Right // The SAR instruction (arithmetic shift right) pops 2 values from the stack, first arg1 and then arg2, // and pushes on the stack arg2 shifted to the right by arg1 number of bits with sign extension. -func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opSAR(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Note, S256 returns (potentially) a new bigint, so we're popping, not peeking this one - shift, value := math.U256(stack.pop()), math.S256(stack.pop()) - defer interpreter.intPool.put(shift) // First operand back into the pool + shift, value := math.U256(callContext.stack.pop()), math.S256(callContext.stack.pop()) + defer interpreter.intPool.putOne(shift) // First operand back into the pool if shift.Cmp(common.Big256) >= 0 { if value.Sign() >= 0 { @@ -372,19 +372,19 @@ func opSAR(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory * } else { value.SetInt64(-1) } - stack.push(math.U256(value)) + callContext.stack.push(math.U256(value)) return nil, nil } n := uint(shift.Uint64()) value.Rsh(value, n) - stack.push(math.U256(value)) + callContext.stack.push(math.U256(value)) return nil, nil } -func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - data := memory.GetPtr(offset.Int64(), size.Int64()) +func opSha3(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + data := callContext.memory.GetPtr(offset.Int64(), size.Int64()) if interpreter.hasher == nil { interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) @@ -398,70 +398,70 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory if evm.vmConfig.EnablePreimageRecording { evm.StateDB.AddPreimage(interpreter.hasherBuf, data) } - stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) + callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.hasherBuf[:])) interpreter.intPool.put(offset, size) return nil, nil } -func opAddress(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Address().Bytes())) +func opAddress(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Address().Bytes())) return nil, nil } -func opBalance(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() +func opBalance(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() slot.Set(interpreter.evm.StateDB.GetBalance(common.BigToAddress(slot))) return nil, nil } -func opOrigin(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes())) +func opOrigin(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil } -func opCaller(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(contract.Caller().Bytes())) +func opCaller(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetBytes(callContext.contract.Caller().Bytes())) return nil, nil } -func opCallValue(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(contract.value)) +func opCallValue(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().Set(callContext.contract.value)) return nil, nil } -func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(getDataBig(contract.Input, stack.pop(), big32))) +func opCallDataLoad(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetBytes(getDataBig(callContext.contract.Input, callContext.stack.pop(), big32))) return nil, nil } -func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(len(contract.Input)))) +func opCallDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Input)))) return nil, nil } -func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(contract.Input, dataOffset, length)) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), getDataBig(callContext.contract.Input, dataOffset, length)) interpreter.intPool.put(memOffset, dataOffset, length) return nil, nil } -func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData)))) +func opReturnDataSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetUint64(uint64(len(interpreter.returnData)))) return nil, nil } -func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - dataOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + dataOffset = callContext.stack.pop() + length = callContext.stack.pop() end = interpreter.intPool.get().Add(dataOffset, length) ) @@ -470,47 +470,47 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contrac if !end.IsUint64() || uint64(len(interpreter.returnData)) < end.Uint64() { return nil, errReturnDataOutOfBounds } - memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), interpreter.returnData[dataOffset.Uint64():end.Uint64()]) return nil, nil } -func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() +func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(common.BigToAddress(slot)))) return nil, nil } -func opCodeSize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - l := interpreter.intPool.get().SetInt64(int64(len(contract.Code))) - stack.push(l) +func opCodeSize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + l := interpreter.intPool.get().SetInt64(int64(len(callContext.contract.Code))) + callContext.stack.push(l) return nil, nil } -func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - memOffset = stack.pop() - codeOffset = stack.pop() - length = stack.pop() + memOffset = callContext.stack.pop() + codeOffset = callContext.stack.pop() + length = callContext.stack.pop() ) - codeCopy := getDataBig(contract.Code, codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + codeCopy := getDataBig(callContext.contract.Code, codeOffset, length) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil } -func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - addr = common.BigToAddress(stack.pop()) - memOffset = stack.pop() - codeOffset = stack.pop() - length = stack.pop() + addr = common.BigToAddress(callContext.stack.pop()) + memOffset = callContext.stack.pop() + codeOffset = callContext.stack.pop() + length = callContext.stack.pop() ) codeCopy := getDataBig(interpreter.evm.StateDB.GetCode(addr), codeOffset, length) - memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) + callContext.memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) interpreter.intPool.put(memOffset, codeOffset, length) return nil, nil @@ -542,8 +542,8 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, contract *Contract, // // (6) Caller tries to get the code hash for an account which is marked as deleted, // this account should be regarded as a non-existent account and zero should be returned. -func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - slot := stack.peek() +func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + slot := callContext.stack.peek() address := common.BigToAddress(slot) if interpreter.evm.StateDB.Empty(address) { slot.SetUint64(0) @@ -553,108 +553,108 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, return nil, nil } -func opGasprice(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice)) +func opGasprice(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().Set(interpreter.evm.GasPrice)) return nil, nil } -func opBlockhash(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - num := stack.pop() +func opBlockhash(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + num := callContext.stack.pop() n := interpreter.intPool.get().Sub(interpreter.evm.BlockNumber, common.Big257) if num.Cmp(n) > 0 && num.Cmp(interpreter.evm.BlockNumber) < 0 { - stack.push(interpreter.evm.GetHash(num.Uint64()).Big()) + callContext.stack.push(interpreter.evm.GetHash(num.Uint64()).Big()) } else { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } interpreter.intPool.put(num, n) return nil, nil } -func opCoinbase(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes())) +func opCoinbase(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetBytes(interpreter.evm.Coinbase.Bytes())) return nil, nil } -func opTimestamp(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time))) +func opTimestamp(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Time))) return nil, nil } -func opNumber(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber))) +func opNumber(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.BlockNumber))) return nil, nil } -func opDifficulty(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Difficulty))) +func opDifficulty(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(math.U256(interpreter.intPool.get().Set(interpreter.evm.Difficulty))) return nil, nil } -func opGasLimit(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(math.U256(interpreter.intPool.get().SetUint64(interpreter.evm.GasLimit))) +func opGasLimit(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(math.U256(interpreter.intPool.get().SetUint64(interpreter.evm.GasLimit))) return nil, nil } -func opPop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - interpreter.intPool.put(stack.pop()) +func opPop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + interpreter.intPool.putOne(callContext.stack.pop()) return nil, nil } -func opMload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - v := stack.peek() +func opMload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + v := callContext.stack.peek() offset := v.Int64() - v.SetBytes(memory.GetPtr(offset, 32)) + v.SetBytes(callContext.memory.GetPtr(offset, 32)) return nil, nil } -func opMstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opMstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // pop value of the stack - mStart, val := stack.pop(), stack.pop() - memory.Set32(mStart.Uint64(), val) + mStart, val := callContext.stack.pop(), callContext.stack.pop() + callContext.memory.Set32(mStart.Uint64(), val) interpreter.intPool.put(mStart, val) return nil, nil } -func opMstore8(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - off, val := stack.pop().Int64(), stack.pop().Int64() - memory.store[off] = byte(val & 0xff) +func opMstore8(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + off, val := callContext.stack.pop().Int64(), callContext.stack.pop().Int64() + callContext.memory.store[off] = byte(val & 0xff) return nil, nil } -func opSload(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := stack.peek() - val := interpreter.evm.StateDB.GetState(contract.Address(), common.BigToHash(loc)) +func opSload(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := callContext.stack.peek() + val := interpreter.evm.StateDB.GetState(callContext.contract.Address(), common.BigToHash(loc)) loc.SetBytes(val.Bytes()) return nil, nil } -func opSstore(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - loc := common.BigToHash(stack.pop()) - val := stack.pop() - interpreter.evm.StateDB.SetState(contract.Address(), loc, common.BigToHash(val)) +func opSstore(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + loc := common.BigToHash(callContext.stack.pop()) + val := callContext.stack.pop() + interpreter.evm.StateDB.SetState(callContext.contract.Address(), loc, common.BigToHash(val)) - interpreter.intPool.put(val) + interpreter.intPool.putOne(val) return nil, nil } -func opJump(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos := stack.pop() - if !contract.validJumpdest(pos) { +func opJump(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos := callContext.stack.pop() + if !callContext.contract.validJumpdest(pos) { return nil, errInvalidJump } *pc = pos.Uint64() - interpreter.intPool.put(pos) + interpreter.intPool.putOne(pos) return nil, nil } -func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - pos, cond := stack.pop(), stack.pop() +func opJumpi(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + pos, cond := callContext.stack.pop(), callContext.stack.pop() if cond.Sign() != 0 { - if !contract.validJumpdest(pos) { + if !callContext.contract.validJumpdest(pos) { return nil, errInvalidJump } *pc = pos.Uint64() @@ -666,50 +666,50 @@ func opJumpi(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory return nil, nil } -func opJumpdest(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opJumpdest(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opPc(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(*pc)) +func opPc(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetUint64(*pc)) return nil, nil } -func opMsize(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetInt64(int64(memory.Len()))) +func opMsize(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetInt64(int64(callContext.memory.Len()))) return nil, nil } -func opGas(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.push(interpreter.intPool.get().SetUint64(contract.Gas)) +func opGas(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.push(interpreter.intPool.get().SetUint64(callContext.contract.Gas)) return nil, nil } -func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCreate(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - value = stack.pop() - offset, size = stack.pop(), stack.pop() - input = memory.GetCopy(offset.Int64(), size.Int64()) - gas = contract.Gas + value = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + input = callContext.memory.GetCopy(offset.Int64(), size.Int64()) + gas = callContext.contract.Gas ) if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create(contract, input, gas, value) + callContext.contract.UseGas(gas) + res, addr, returnGas, suberr := interpreter.evm.Create(callContext.contract, input, gas, value) // Push item on the stack based on the returned error. If the ruleset is // homestead we must check for CodeStoreOutOfGasError (homestead only // rule) and treat as an error, if the ruleset is frontier we must // ignore this error and pretend the operation was successful. if interpreter.evm.chainRules.IsHomestead && suberr == ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else if suberr != nil && suberr != ErrCodeStoreOutOfGas { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(value, offset, size) if suberr == errExecutionReverted { @@ -718,26 +718,26 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memor return nil, nil } -func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCreate2(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - endowment = stack.pop() - offset, size = stack.pop(), stack.pop() - salt = stack.pop() - input = memory.GetCopy(offset.Int64(), size.Int64()) - gas = contract.Gas + endowment = callContext.stack.pop() + offset, size = callContext.stack.pop(), callContext.stack.pop() + salt = callContext.stack.pop() + input = callContext.memory.GetCopy(offset.Int64(), size.Int64()) + gas = callContext.contract.Gas ) // Apply EIP150 gas -= gas / 64 - contract.UseGas(gas) - res, addr, returnGas, suberr := interpreter.evm.Create2(contract, input, gas, endowment, salt) + callContext.contract.UseGas(gas) + res, addr, returnGas, suberr := interpreter.evm.Create2(callContext.contract, input, gas, endowment, salt) // Push item on the stack based on the returned error. if suberr != nil { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) + callContext.stack.push(interpreter.intPool.get().SetBytes(addr.Bytes())) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(endowment, offset, size, salt) if suberr == errExecutionReverted { @@ -746,139 +746,139 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo return nil, nil } -func opCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + interpreter.intPool.putOne(callContext.stack.pop()) gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() toAddr := common.BigToAddress(addr) value = math.U256(value) // Get the arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) if value.Sign() != 0 { gas += params.CallStipend } - ret, returnGas, err := interpreter.evm.Call(contract, toAddr, args, gas, value) + ret, returnGas, err := interpreter.evm.Call(callContext.contract, toAddr, args, gas, value) if err != nil { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + callContext.stack.push(interpreter.intPool.get().SetUint64(1)) } if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opCallCode(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opCallCode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + interpreter.intPool.putOne(callContext.stack.pop()) gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, value, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + addr, value, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() toAddr := common.BigToAddress(addr) value = math.U256(value) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) if value.Sign() != 0 { gas += params.CallStipend } - ret, returnGas, err := interpreter.evm.CallCode(contract, toAddr, args, gas, value) + ret, returnGas, err := interpreter.evm.CallCode(callContext.contract, toAddr, args, gas, value) if err != nil { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + callContext.stack.push(interpreter.intPool.get().SetUint64(1)) } if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(addr, value, inOffset, inSize, retOffset, retSize) return ret, nil } -func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opDelegateCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + interpreter.intPool.putOne(callContext.stack.pop()) gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() toAddr := common.BigToAddress(addr) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) - ret, returnGas, err := interpreter.evm.DelegateCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.DelegateCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + callContext.stack.push(interpreter.intPool.get().SetUint64(1)) } if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opStaticCall(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStaticCall(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. - interpreter.intPool.put(stack.pop()) + interpreter.intPool.putOne(callContext.stack.pop()) gas := interpreter.evm.callGasTemp // Pop other call parameters. - addr, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + addr, inOffset, inSize, retOffset, retSize := callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop(), callContext.stack.pop() toAddr := common.BigToAddress(addr) // Get arguments from the memory. - args := memory.GetPtr(inOffset.Int64(), inSize.Int64()) + args := callContext.memory.GetPtr(inOffset.Int64(), inSize.Int64()) - ret, returnGas, err := interpreter.evm.StaticCall(contract, toAddr, args, gas) + ret, returnGas, err := interpreter.evm.StaticCall(callContext.contract, toAddr, args, gas) if err != nil { - stack.push(interpreter.intPool.getZero()) + callContext.stack.push(interpreter.intPool.getZero()) } else { - stack.push(interpreter.intPool.get().SetUint64(1)) + callContext.stack.push(interpreter.intPool.get().SetUint64(1)) } if err == nil || err == errExecutionReverted { - memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + callContext.memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) } - contract.Gas += returnGas + callContext.contract.Gas += returnGas interpreter.intPool.put(addr, inOffset, inSize, retOffset, retSize) return ret, nil } -func opReturn(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opReturn(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(offset.Int64(), size.Int64()) interpreter.intPool.put(offset, size) return ret, nil } -func opRevert(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - offset, size := stack.pop(), stack.pop() - ret := memory.GetPtr(offset.Int64(), size.Int64()) +func opRevert(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + offset, size := callContext.stack.pop(), callContext.stack.pop() + ret := callContext.memory.GetPtr(offset.Int64(), size.Int64()) interpreter.intPool.put(offset, size) return ret, nil } -func opStop(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opStop(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { return nil, nil } -func opSuicide(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - balance := interpreter.evm.StateDB.GetBalance(contract.Address()) - interpreter.evm.StateDB.AddBalance(common.BigToAddress(stack.pop()), balance) +func opSuicide(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + balance := interpreter.evm.StateDB.GetBalance(callContext.contract.Address()) + interpreter.evm.StateDB.AddBalance(common.BigToAddress(callContext.stack.pop()), balance) - interpreter.evm.StateDB.Suicide(contract.Address()) + interpreter.evm.StateDB.Suicide(callContext.contract.Address()) return nil, nil } @@ -886,16 +886,16 @@ func opSuicide(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memo // make log instruction function func makeLog(size int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { topics := make([]common.Hash, size) - mStart, mSize := stack.pop(), stack.pop() + mStart, mSize := callContext.stack.pop(), callContext.stack.pop() for i := 0; i < size; i++ { - topics[i] = common.BigToHash(stack.pop()) + topics[i] = common.BigToHash(callContext.stack.pop()) } - d := memory.GetCopy(mStart.Int64(), mSize.Int64()) + d := callContext.memory.GetCopy(mStart.Int64(), mSize.Int64()) interpreter.evm.StateDB.AddLog(&types.Log{ - Address: contract.Address(), + Address: callContext.contract.Address(), Topics: topics, Data: d, // This is a non-consensus field, but assigned here because @@ -909,24 +909,24 @@ func makeLog(size int) executionFunc { } // opPush1 is a specialized version of pushN -func opPush1(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { +func opPush1(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { var ( - codeLen = uint64(len(contract.Code)) + codeLen = uint64(len(callContext.contract.Code)) integer = interpreter.intPool.get() ) *pc += 1 if *pc < codeLen { - stack.push(integer.SetUint64(uint64(contract.Code[*pc]))) + callContext.stack.push(integer.SetUint64(uint64(callContext.contract.Code[*pc]))) } else { - stack.push(integer.SetUint64(0)) + callContext.stack.push(integer.SetUint64(0)) } return nil, nil } // make push instruction function func makePush(size uint64, pushByteSize int) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - codeLen := len(contract.Code) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + codeLen := len(callContext.contract.Code) startMin := codeLen if int(*pc+1) < startMin { @@ -939,7 +939,7 @@ func makePush(size uint64, pushByteSize int) executionFunc { } integer := interpreter.intPool.get() - stack.push(integer.SetBytes(common.RightPadBytes(contract.Code[startMin:endMin], pushByteSize))) + callContext.stack.push(integer.SetBytes(common.RightPadBytes(callContext.contract.Code[startMin:endMin], pushByteSize))) *pc += size return nil, nil @@ -948,8 +948,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { // make dup instruction function func makeDup(size int64) executionFunc { - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.dup(interpreter.intPool, int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.dup(interpreter.intPool, int(size)) return nil, nil } } @@ -958,8 +958,8 @@ func makeDup(size int64) executionFunc { func makeSwap(size int64) executionFunc { // switch n + 1 otherwise n would be swapped with n size++ - return func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) { - stack.swap(int(size)) + return func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) { + callContext.stack.swap(int(size)) return nil, nil } } diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index bdc62d1777..d21dbf1423 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -109,7 +109,7 @@ func testTwoOperandOp(t *testing.T, tests []TwoOperandTestcase, opFn executionFu expected := new(big.Int).SetBytes(common.Hex2Bytes(test.Expected)) stack.push(x) stack.push(y) - opFn(&pc, evmInterpreter, nil, nil, stack) + opFn(&pc, evmInterpreter, &callCtx{nil, stack, nil}) actual := stack.pop() if actual.Cmp(expected) != 0 { @@ -223,7 +223,7 @@ func getResult(args []*twoOperandParams, opFn executionFunc) []TwoOperandTestcas y := new(big.Int).SetBytes(common.Hex2Bytes(param.y)) stack.push(x) stack.push(y) - opFn(&pc, interpreter, nil, nil, stack) + opFn(&pc, interpreter, &callCtx{nil, stack, nil}) actual := stack.pop() result[i] = TwoOperandTestcase{param.x, param.y, fmt.Sprintf("%064x", actual)} } @@ -260,7 +260,7 @@ func TestJsonTestcases(t *testing.T) { } } -func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error), args ...string) { +func opBenchmark(bench *testing.B, op executionFunc, args ...string) { var ( env = NewEVM(Context{}, nil, params.TestChainConfig, Config{}) stack = newstack() @@ -281,7 +281,7 @@ func opBenchmark(bench *testing.B, op func(pc *uint64, interpreter *EVMInterpret a := new(big.Int).SetBytes(arg) stack.push(a) } - op(&pc, evmInterpreter, nil, nil, stack) + op(&pc, evmInterpreter, &callCtx{nil, stack, nil}) stack.pop() } poolOfIntPools.put(evmInterpreter.intPool) @@ -509,12 +509,12 @@ func TestOpMstore(t *testing.T) { pc := uint64(0) v := "abcdef00000000000000abba000000000deaf000000c0de00100000000133700" stack.pushN(new(big.Int).SetBytes(common.Hex2Bytes(v)), big.NewInt(0)) - opMstore(&pc, evmInterpreter, nil, mem, stack) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) if got := common.Bytes2Hex(mem.GetCopy(0, 32)); got != v { t.Fatalf("Mstore fail, got %v, expected %v", got, v) } stack.pushN(big.NewInt(0x1), big.NewInt(0)) - opMstore(&pc, evmInterpreter, nil, mem, stack) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) if common.Bytes2Hex(mem.GetCopy(0, 32)) != "0000000000000000000000000000000000000000000000000000000000000001" { t.Fatalf("Mstore failed to overwrite previous value") } @@ -539,7 +539,7 @@ func BenchmarkOpMstore(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(value, memStart) - opMstore(&pc, evmInterpreter, nil, mem, stack) + opMstore(&pc, evmInterpreter, &callCtx{mem, stack, nil}) } poolOfIntPools.put(evmInterpreter.intPool) } @@ -560,7 +560,7 @@ func BenchmarkOpSHA3(bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { stack.pushN(big.NewInt(32), start) - opSha3(&pc, evmInterpreter, nil, mem, stack) + opSha3(&pc, evmInterpreter, &callCtx{mem, stack, nil}) } poolOfIntPools.put(evmInterpreter.intPool) } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index fe06492de3..5d213acc83 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -63,6 +63,14 @@ type Interpreter interface { CanRun([]byte) bool } +// callCtx contains the things that are per-call, such as stack and memory, +// but not transients like pc and gas +type callCtx struct { + memory *Memory + stack *Stack + contract *Contract +} + // keccakState wraps sha3.state. In addition to the usual hash methods, it also supports // Read to get a variable amount of data from the hash state. Read is faster than Sum // because it doesn't copy the internal state, but also modifies the internal state. @@ -160,9 +168,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } var ( - op OpCode // current opcode - mem = NewMemory() // bound memory - stack = newstack() // local stack + op OpCode // current opcode + mem = NewMemory() // bound memory + stack = newstack() // local stack + callContext = &callCtx{ + memory: mem, + stack: stack, + contract: contract, + } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC // to be uint256. Practically much less so feasible. @@ -194,7 +207,12 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // explicit STOP, RETURN or SELFDESTRUCT is executed, an error occurred during // the execution of one of the operations or until the done flag is set by the // parent context. - for atomic.LoadInt32(&in.evm.abort) == 0 { + steps := 0 + for { + steps++ + if steps%1000 == 0 && atomic.LoadInt32(&in.evm.abort) != 0 { + break + } if in.cfg.Debug { // Capture pre-execution values for tracing. logged, pcCopy, gasCopy = false, pc, contract.Gas @@ -267,7 +285,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( } // execute the operation - res, err = operation.execute(&pc, in, contract, mem, stack) + res, err = operation.execute(&pc, in, callContext) // verifyPool is a build flag. Pool verification makes sure the integrity // of the integer pool by comparing values to a default value. if verifyPool { diff --git a/core/vm/intpool.go b/core/vm/intpool.go index 917a78d560..eed074b073 100644 --- a/core/vm/intpool.go +++ b/core/vm/intpool.go @@ -53,6 +53,17 @@ func (p *intPool) getZero() *big.Int { return new(big.Int) } +// putOne returns an allocated big int to the pool to be later reused by get calls. +// Note, the values as saved as is; neither put nor get zeroes the ints out! +// As opposed to 'put' with variadic args, this method becomes inlined by the +// go compiler +func (p *intPool) putOne(i *big.Int) { + if len(p.pool.data) > poolLimit { + return + } + p.pool.push(i) +} + // put returns an allocated big int to the pool to be later reused by get calls. // Note, the values as saved as is; neither put nor get zeroes the ints out! func (p *intPool) put(is ...*big.Int) { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index b26b55284c..c1e7c88dbf 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -23,7 +23,7 @@ import ( ) type ( - executionFunc func(pc *uint64, interpreter *EVMInterpreter, contract *Contract, memory *Memory, stack *Stack) ([]byte, error) + executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 memorySizeFunc func(*Stack) (size uint64, overflow bool) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index fb07d69d09..decde46ea6 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -316,3 +316,31 @@ func TestBlockhash(t *testing.T) { t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got) } } + +// BenchmarkSimpleLoop test a pretty simple loop which loops +// 1M (1 048 575) times. +// Takes about 200 ms +func BenchmarkSimpleLoop(b *testing.B) { + // 0xfffff = 1048575 loops + code := []byte{ + byte(vm.PUSH3), 0x0f, 0xff, 0xff, + byte(vm.JUMPDEST), // [ count ] + byte(vm.PUSH1), 1, // [count, 1] + byte(vm.SWAP1), // [1, count] + byte(vm.SUB), // [ count -1 ] + byte(vm.DUP1), // [ count -1 , count-1] + byte(vm.PUSH1), 4, // [count-1, count -1, label] + byte(vm.JUMPI), // [ 0 ] + byte(vm.STOP), + } + //tracer := vm.NewJSONLogger(nil, os.Stdout) + //Execute(code, nil, &Config{ + // EVMConfig: vm.Config{ + // Debug: true, + // Tracer: tracer, + // }}) + + for i := 0; i < b.N; i++ { + Execute(code, nil, nil) + } +} From 094996b8c96047564c541e4ffe50bd9b6a470e96 Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Tue, 7 Apr 2020 12:15:28 +0200 Subject: [PATCH 084/103] docs/audits: add discv5 protocol audits from LA and C53 (#20898) --- .../2019-10-15_Discv5_audit_LeastAuthority.pdf | Bin 0 -> 332622 bytes docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf | Bin 0 -> 86510 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf create mode 100644 docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf diff --git a/docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf b/docs/audits/2019-10-15_Discv5_audit_LeastAuthority.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5d834e8273a7ac28fd4f02aee765193f9512d6f8 GIT binary patch literal 332622 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
?zIrZsDQLhI$-M-`IEYU5h|B{dzv=_H@_^ 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�C6O&*o>b44Knygv-0~9>0Eb|G=YRNt+ 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 literal 0 HcmV?d00001 diff --git a/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf b/docs/audits/2020-01-24_DiscV5_audit_Cure53.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f490e1d0ec53c3c731cc70854886d62d08584daf GIT binary patch literal 86510 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 Date: Tue, 7 Apr 2020 12:16:35 +0200 Subject: [PATCH 085/103] .github: change gitter reference to discord link in issue template (#20896) --- .github/ISSUE_TEMPLATE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 4e638166ff..59285e456d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,7 +2,7 @@ Hi there, Please note that this is an issue tracker reserved for bug reports and feature requests. -For general questions please use the gitter channel or the Ethereum stack exchange at https://ethereum.stackexchange.com. +For general questions please use [discord](https://discord.gg/nthXNEv) or the Ethereum stack exchange at https://ethereum.stackexchange.com. #### System information From 671f22be3888aa9d056e0798508c42420638cb1f Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Tue, 7 Apr 2020 14:37:24 +0200 Subject: [PATCH 086/103] couple of fixes to docs in clef (#20900) --- cmd/clef/docs/setup.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/clef/docs/setup.md b/cmd/clef/docs/setup.md index 33d2b0381f..a25f87ce64 100644 --- a/cmd/clef/docs/setup.md +++ b/cmd/clef/docs/setup.md @@ -34,7 +34,7 @@ There are two ways that this can be achieved: integrated via Qubes or integrated #### 1. Qubes Integrated -Qubes provdes a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request +Qubes provides a facility for inter-qubes communication via `qrexec`. A qube can request to make a cross-qube RPC request to another qube. The OS then asks the user if the call is permitted. ![Example](qubes/qrexec-example.png) @@ -48,7 +48,7 @@ This is how [Split GPG](https://www.qubes-os.org/doc/split-gpg/) is implemented. ![Clef via qrexec](qubes/clef_qubes_qrexec.png) -On the `target` qubes, we need to define the rpc service. +On the `target` qubes, we need to define the RPC service. [qubes.Clefsign](qubes/qubes.Clefsign): @@ -135,11 +135,11 @@ $ cat newaccnt.json $ cat newaccnt.json| qrexec-client-vm debian-work qubes.Clefsign ``` -This should pop up first a dialog to allow the IPC call: +A dialog should pop up first to allow the IPC call: ![one](qubes/qubes_newaccount-1.png) -Followed by a GTK-dialog to approve the operation +Followed by a GTK-dialog to approve the operation: ![two](qubes/qubes_newaccount-2.png) @@ -169,7 +169,7 @@ However, it comes with a couple of drawbacks: - The `Origin` header must be forwarded - Information about the remote ip must be added as a `X-Forwarded-For`. However, Clef cannot always trust an `XFF` header, since malicious clients may lie about `XFF` in order to fool the http server into believing it comes from another address. -- Even with a policy in place to allow rpc-calls between `caller` and `target`, there will be several popups: +- Even with a policy in place to allow RPC calls between `caller` and `target`, there will be several popups: - One qubes-specific where the user specifies the `target` vm - One clef-specific to approve the transaction @@ -177,7 +177,7 @@ However, it comes with a couple of drawbacks: #### 2. Network integrated The second way to set up Clef on a qubes system is to allow networking, and have Clef listen to a port which is accessible -form other qubes. +from other qubes. ![Clef via http](qubes/clef_qubes_http.png) @@ -193,6 +193,6 @@ to your computer. Over this new network interface, you can SSH into the device. Running Clef off a USB armory means that you can use the armory as a very versatile offline computer, which only ever connects to a local network between your computer and the device itself. -Needless to say, the while this model should be fairly secure against remote attacks, an attacker with physical access +Needless to say, while this model should be fairly secure against remote attacks, an attacker with physical access to the USB Armory would trivially be able to extract the contents of the device filesystem. From b7394d79428d395a6fca0567659cdcd4d808bee2 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 8 Apr 2020 09:57:23 +0200 Subject: [PATCH 087/103] p2p/discover: add initial discovery v5 implementation (#20750) This adds an implementation of the current discovery v5 spec. There is full integration with cmd/devp2p and enode.Iterator in this version. In theory we could enable the new protocol as a replacement of discovery v4 at any time. In practice, there will likely be a few more changes to the spec and implementation before this can happen. --- cmd/devp2p/crawl.go | 9 +- cmd/devp2p/discv4cmd.go | 105 ++-- cmd/devp2p/discv5cmd.go | 123 +++++ cmd/devp2p/main.go | 1 + p2p/discover/common.go | 34 +- p2p/discover/lookup.go | 2 +- p2p/discover/node.go | 7 +- p2p/discover/table.go | 4 + p2p/discover/table_util_test.go | 33 +- p2p/discover/v4_lookup_test.go | 54 +- p2p/discover/v4_udp.go | 11 +- p2p/discover/v4_udp_test.go | 32 +- p2p/discover/v5_encoding.go | 659 ++++++++++++++++++++++++ p2p/discover/v5_encoding_test.go | 373 ++++++++++++++ p2p/discover/v5_session.go | 123 +++++ p2p/discover/v5_udp.go | 832 +++++++++++++++++++++++++++++++ p2p/discover/v5_udp_test.go | 622 +++++++++++++++++++++++ p2p/enode/nodedb.go | 21 + p2p/enode/nodedb_test.go | 11 + 19 files changed, 2976 insertions(+), 80 deletions(-) create mode 100644 cmd/devp2p/discv5cmd.go create mode 100644 p2p/discover/v5_encoding.go create mode 100644 p2p/discover/v5_encoding_test.go create mode 100644 p2p/discover/v5_session.go create mode 100644 p2p/discover/v5_udp.go create mode 100644 p2p/discover/v5_udp_test.go diff --git a/cmd/devp2p/crawl.go b/cmd/devp2p/crawl.go index 7fefbd7a1c..9259b4894c 100644 --- a/cmd/devp2p/crawl.go +++ b/cmd/devp2p/crawl.go @@ -20,14 +20,13 @@ import ( "time" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/p2p/discover" "github.com/ethereum/go-ethereum/p2p/enode" ) type crawler struct { input nodeSet output nodeSet - disc *discover.UDPv4 + disc resolver iters []enode.Iterator inputIter enode.Iterator ch chan *enode.Node @@ -37,7 +36,11 @@ type crawler struct { revalidateInterval time.Duration } -func newCrawler(input nodeSet, disc *discover.UDPv4, iters ...enode.Iterator) *crawler { +type resolver interface { + RequestENR(*enode.Node) (*enode.Node, error) +} + +func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler { c := &crawler{ input: input, output: make(nodeSet, len(input)), diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 9525bec668..8580c61216 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -81,6 +81,18 @@ var ( Name: "bootnodes", Usage: "Comma separated nodes used for bootstrapping", } + nodekeyFlag = cli.StringFlag{ + Name: "nodekey", + Usage: "Hex-encoded node key", + } + nodedbFlag = cli.StringFlag{ + Name: "nodedb", + Usage: "Nodes database location", + } + listenAddrFlag = cli.StringFlag{ + Name: "addr", + Usage: "Listening address", + } crawlTimeoutFlag = cli.DurationFlag{ Name: "timeout", Usage: "Time limit for the crawl.", @@ -172,6 +184,62 @@ func discv4Crawl(ctx *cli.Context) error { return nil } +// startV4 starts an ephemeral discovery V4 node. +func startV4(ctx *cli.Context) *discover.UDPv4 { + ln, config := makeDiscoveryConfig(ctx) + socket := listen(ln, ctx.String(listenAddrFlag.Name)) + disc, err := discover.ListenV4(socket, ln, config) + if err != nil { + exit(err) + } + return disc +} + +func makeDiscoveryConfig(ctx *cli.Context) (*enode.LocalNode, discover.Config) { + var cfg discover.Config + + if ctx.IsSet(nodekeyFlag.Name) { + key, err := crypto.HexToECDSA(ctx.String(nodekeyFlag.Name)) + if err != nil { + exit(fmt.Errorf("-%s: %v", nodekeyFlag.Name, err)) + } + cfg.PrivateKey = key + } else { + cfg.PrivateKey, _ = crypto.GenerateKey() + } + + if commandHasFlag(ctx, bootnodesFlag) { + bn, err := parseBootnodes(ctx) + if err != nil { + exit(err) + } + cfg.Bootnodes = bn + } + + dbpath := ctx.String(nodedbFlag.Name) + db, err := enode.OpenDB(dbpath) + if err != nil { + exit(err) + } + ln := enode.NewLocalNode(db, cfg.PrivateKey) + return ln, cfg +} + +func listen(ln *enode.LocalNode, addr string) *net.UDPConn { + if addr == "" { + addr = "0.0.0.0:0" + } + socket, err := net.ListenPacket("udp4", addr) + if err != nil { + exit(err) + } + usocket := socket.(*net.UDPConn) + uaddr := socket.LocalAddr().(*net.UDPAddr) + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + ln.SetFallbackUDP(uaddr.Port) + return usocket +} + func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { s := params.RinkebyBootnodes if ctx.IsSet(bootnodesFlag.Name) { @@ -187,40 +255,3 @@ func parseBootnodes(ctx *cli.Context) ([]*enode.Node, error) { } return nodes, nil } - -// startV4 starts an ephemeral discovery V4 node. -func startV4(ctx *cli.Context) *discover.UDPv4 { - socket, ln, cfg, err := listen() - if err != nil { - exit(err) - } - if commandHasFlag(ctx, bootnodesFlag) { - bn, err := parseBootnodes(ctx) - if err != nil { - exit(err) - } - cfg.Bootnodes = bn - } - disc, err := discover.ListenV4(socket, ln, cfg) - if err != nil { - exit(err) - } - return disc -} - -func listen() (*net.UDPConn, *enode.LocalNode, discover.Config, error) { - var cfg discover.Config - cfg.PrivateKey, _ = crypto.GenerateKey() - db, _ := enode.OpenDB("") - ln := enode.NewLocalNode(db, cfg.PrivateKey) - - socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{0, 0, 0, 0}}) - if err != nil { - db.Close() - return nil, nil, cfg, err - } - addr := socket.LocalAddr().(*net.UDPAddr) - ln.SetFallbackIP(net.IP{127, 0, 0, 1}) - ln.SetFallbackUDP(addr.Port) - return socket, ln, cfg, nil -} diff --git a/cmd/devp2p/discv5cmd.go b/cmd/devp2p/discv5cmd.go new file mode 100644 index 0000000000..f871821ea2 --- /dev/null +++ b/cmd/devp2p/discv5cmd.go @@ -0,0 +1,123 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package main + +import ( + "fmt" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/p2p/discover" + "gopkg.in/urfave/cli.v1" +) + +var ( + discv5Command = cli.Command{ + Name: "discv5", + Usage: "Node Discovery v5 tools", + Subcommands: []cli.Command{ + discv5PingCommand, + discv5ResolveCommand, + discv5CrawlCommand, + discv5ListenCommand, + }, + } + discv5PingCommand = cli.Command{ + Name: "ping", + Usage: "Sends ping to a node", + Action: discv5Ping, + } + discv5ResolveCommand = cli.Command{ + Name: "resolve", + Usage: "Finds a node in the DHT", + Action: discv5Resolve, + Flags: []cli.Flag{bootnodesFlag}, + } + discv5CrawlCommand = cli.Command{ + Name: "crawl", + Usage: "Updates a nodes.json file with random nodes found in the DHT", + Action: discv5Crawl, + Flags: []cli.Flag{bootnodesFlag, crawlTimeoutFlag}, + } + discv5ListenCommand = cli.Command{ + Name: "listen", + Usage: "Runs a node", + Action: discv5Listen, + Flags: []cli.Flag{ + bootnodesFlag, + nodekeyFlag, + nodedbFlag, + listenAddrFlag, + }, + } +) + +func discv5Ping(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Ping(n)) + return nil +} + +func discv5Resolve(ctx *cli.Context) error { + n := getNodeArg(ctx) + disc := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Resolve(n)) + return nil +} + +func discv5Crawl(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return fmt.Errorf("need nodes file as argument") + } + nodesFile := ctx.Args().First() + var inputSet nodeSet + if common.FileExist(nodesFile) { + inputSet = loadNodesJSON(nodesFile) + } + + disc := startV5(ctx) + defer disc.Close() + c := newCrawler(inputSet, disc, disc.RandomNodes()) + c.revalidateInterval = 10 * time.Minute + output := c.run(ctx.Duration(crawlTimeoutFlag.Name)) + writeNodesJSON(nodesFile, output) + return nil +} + +func discv5Listen(ctx *cli.Context) error { + disc := startV5(ctx) + defer disc.Close() + + fmt.Println(disc.Self()) + select {} +} + +// startV5 starts an ephemeral discovery v5 node. +func startV5(ctx *cli.Context) *discover.UDPv5 { + ln, config := makeDiscoveryConfig(ctx) + socket := listen(ln, ctx.String(listenAddrFlag.Name)) + disc, err := discover.ListenV5(socket, ln, config) + if err != nil { + exit(err) + } + return disc +} diff --git a/cmd/devp2p/main.go b/cmd/devp2p/main.go index b895941f25..19aec77ed4 100644 --- a/cmd/devp2p/main.go +++ b/cmd/devp2p/main.go @@ -59,6 +59,7 @@ func init() { app.Commands = []cli.Command{ enrdumpCommand, discv4Command, + discv5Command, dnsCommand, nodesetCommand, } diff --git a/p2p/discover/common.go b/p2p/discover/common.go index cef6a9fc4f..3708bfb72c 100644 --- a/p2p/discover/common.go +++ b/p2p/discover/common.go @@ -20,8 +20,10 @@ import ( "crypto/ecdsa" "net" + "github.com/ethereum/go-ethereum/common/mclock" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethereum/go-ethereum/p2p/netutil" ) @@ -39,10 +41,25 @@ type Config struct { PrivateKey *ecdsa.PrivateKey // These settings are optional: - NetRestrict *netutil.Netlist // network whitelist - Bootnodes []*enode.Node // list of bootstrap nodes - Unhandled chan<- ReadPacket // unhandled packets are sent on this channel - Log log.Logger // if set, log messages go here + NetRestrict *netutil.Netlist // network whitelist + Bootnodes []*enode.Node // list of bootstrap nodes + Unhandled chan<- ReadPacket // unhandled packets are sent on this channel + Log log.Logger // if set, log messages go here + ValidSchemes enr.IdentityScheme // allowed identity schemes + Clock mclock.Clock +} + +func (cfg Config) withDefaults() Config { + if cfg.Log == nil { + cfg.Log = log.Root() + } + if cfg.ValidSchemes == nil { + cfg.ValidSchemes = enode.ValidSchemes + } + if cfg.Clock == nil { + cfg.Clock = mclock.System{} + } + return cfg } // ListenUDP starts listening for discovery packets on the given UDP socket. @@ -51,8 +68,15 @@ func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { } // ReadPacket is a packet that couldn't be handled. Those packets are sent to the unhandled -// channel if configured. This is exported for internal use, do not use this type. +// channel if configured. type ReadPacket struct { Data []byte Addr *net.UDPAddr } + +func min(x, y int) int { + if x > y { + return y + } + return x +} diff --git a/p2p/discover/lookup.go b/p2p/discover/lookup.go index ab825fb05d..40b271e6d9 100644 --- a/p2p/discover/lookup.go +++ b/p2p/discover/lookup.go @@ -150,7 +150,7 @@ func (it *lookup) query(n *node, reply chan<- []*node) { } else if len(r) == 0 { fails++ it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails) - it.tab.log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "err", err) + it.tab.log.Trace("Findnode failed", "id", n.ID(), "failcount", fails, "results", len(r), "err", err) if fails >= maxFindnodeFailures { it.tab.log.Trace("Too many findnode failures, dropping", "id", n.ID(), "failcount", fails) it.tab.delete(n) diff --git a/p2p/discover/node.go b/p2p/discover/node.go index a7d9ce7368..230638b6d1 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -18,6 +18,7 @@ package discover import ( "crypto/ecdsa" + "crypto/elliptic" "errors" "math/big" "net" @@ -45,13 +46,13 @@ func encodePubkey(key *ecdsa.PublicKey) encPubkey { return e } -func decodePubkey(e encPubkey) (*ecdsa.PublicKey, error) { - p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)} +func decodePubkey(curve elliptic.Curve, e encPubkey) (*ecdsa.PublicKey, error) { + p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)} half := len(e) / 2 p.X.SetBytes(e[:half]) p.Y.SetBytes(e[half:]) if !p.Curve.IsOnCurve(p.X, p.Y) { - return nil, errors.New("invalid secp256k1 curve point") + return nil, errors.New("invalid curve point") } return p, nil } diff --git a/p2p/discover/table.go b/p2p/discover/table.go index e5a5793e35..6d48ab00cd 100644 --- a/p2p/discover/table.go +++ b/p2p/discover/table.go @@ -424,6 +424,10 @@ func (tab *Table) len() (n int) { // bucket returns the bucket for the given node ID hash. func (tab *Table) bucket(id enode.ID) *bucket { d := enode.LogDist(tab.self().ID(), id) + return tab.bucketAtDistance(d) +} + +func (tab *Table) bucketAtDistance(d int) *bucket { if d <= bucketMinDistance { return tab.buckets[0] } diff --git a/p2p/discover/table_util_test.go b/p2p/discover/table_util_test.go index e35e48c5e6..44b62e751b 100644 --- a/p2p/discover/table_util_test.go +++ b/p2p/discover/table_util_test.go @@ -24,7 +24,6 @@ import ( "fmt" "math/rand" "net" - "reflect" "sort" "sync" @@ -56,6 +55,23 @@ func nodeAtDistance(base enode.ID, ld int, ip net.IP) *node { return wrapNode(enode.SignNull(&r, idAtDistance(base, ld))) } +// nodesAtDistance creates n nodes for which enode.LogDist(base, node.ID()) == ld. +func nodesAtDistance(base enode.ID, ld int, n int) []*enode.Node { + results := make([]*enode.Node, n) + for i := range results { + results[i] = unwrapNode(nodeAtDistance(base, ld, intIP(i))) + } + return results +} + +func nodesToRecords(nodes []*enode.Node) []*enr.Record { + records := make([]*enr.Record, len(nodes)) + for i := range nodes { + records[i] = nodes[i].Record() + } + return records +} + // idAtDistance returns a random hash such that enode.LogDist(a, b) == n func idAtDistance(a enode.ID, n int) (b enode.ID) { if n == 0 { @@ -173,9 +189,16 @@ func hasDuplicates(slice []*node) bool { } func checkNodesEqual(got, want []*enode.Node) error { - if reflect.DeepEqual(got, want) { - return nil + if len(got) == len(want) { + for i := range got { + if !nodeEqual(got[i], want[i]) { + goto NotEqual + } + return nil + } } + +NotEqual: output := new(bytes.Buffer) fmt.Fprintf(output, "got %d nodes:\n", len(got)) for _, n := range got { @@ -188,6 +211,10 @@ func checkNodesEqual(got, want []*enode.Node) error { return errors.New(output.String()) } +func nodeEqual(n1 *enode.Node, n2 *enode.Node) bool { + return n1.ID() == n2.ID() && n1.IP().Equal(n2.IP()) +} + func sortByID(nodes []*enode.Node) { sort.Slice(nodes, func(i, j int) bool { return string(nodes[i].ID().Bytes()) < string(nodes[j].ID().Bytes()) diff --git a/p2p/discover/v4_lookup_test.go b/p2p/discover/v4_lookup_test.go index 9b4042c5a2..83480d35e8 100644 --- a/p2p/discover/v4_lookup_test.go +++ b/p2p/discover/v4_lookup_test.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" ) func TestUDPv4_Lookup(t *testing.T) { @@ -32,7 +33,7 @@ func TestUDPv4_Lookup(t *testing.T) { test := newUDPTest(t) // Lookup on empty table returns no nodes. - targetKey, _ := decodePubkey(lookupTestnet.target) + targetKey, _ := decodePubkey(crypto.S256(), lookupTestnet.target) if results := test.udp.LookupPubkey(targetKey); len(results) > 0 { t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) } @@ -59,15 +60,7 @@ func TestUDPv4_Lookup(t *testing.T) { if len(results) != bucketSize { t.Errorf("wrong number of results: got %d, want %d", len(results), bucketSize) } - if hasDuplicates(wrapNodes(results)) { - t.Errorf("result set contains duplicate entries") - } - if !sortedByDistanceTo(lookupTestnet.target.id(), wrapNodes(results)) { - t.Errorf("result set not sorted by distance to target") - } - if err := checkNodesEqual(results, lookupTestnet.closest(bucketSize)); err != nil { - t.Errorf("results aren't the closest %d nodes\n%v", bucketSize, err) - } + checkLookupResults(t, lookupTestnet, results) } func TestUDPv4_LookupIterator(t *testing.T) { @@ -156,6 +149,26 @@ func serveTestnet(test *udpTest, testnet *preminedTestnet) { } } +// checkLookupResults verifies that the results of a lookup are the closest nodes to +// the testnet's target. +func checkLookupResults(t *testing.T, tn *preminedTestnet, results []*enode.Node) { + t.Helper() + t.Logf("results:") + for _, e := range results { + t.Logf(" ld=%d, %x", enode.LogDist(tn.target.id(), e.ID()), e.ID().Bytes()) + } + if hasDuplicates(wrapNodes(results)) { + t.Errorf("result set contains duplicate entries") + } + if !sortedByDistanceTo(tn.target.id(), wrapNodes(results)) { + t.Errorf("result set not sorted by distance to target") + } + wantNodes := tn.closest(len(results)) + if err := checkNodesEqual(results, wantNodes); err != nil { + t.Error(err) + } +} + // This is the test network for the Lookup test. // The nodes were obtained by running lookupTestnet.mine with a random NodeID as target. var lookupTestnet = &preminedTestnet{ @@ -242,8 +255,12 @@ func (tn *preminedTestnet) nodes() []*enode.Node { func (tn *preminedTestnet) node(dist, index int) *enode.Node { key := tn.dists[dist][index] - ip := net.IP{127, byte(dist >> 8), byte(dist), byte(index)} - return enode.NewV4(&key.PublicKey, ip, 0, 5000) + rec := new(enr.Record) + rec.Set(enr.IP{127, byte(dist >> 8), byte(dist), byte(index)}) + rec.Set(enr.UDP(5000)) + enode.SignV4(rec, key) + n, _ := enode.New(enode.ValidSchemes, rec) + return n } func (tn *preminedTestnet) nodeByAddr(addr *net.UDPAddr) (*enode.Node, *ecdsa.PrivateKey) { @@ -261,6 +278,19 @@ func (tn *preminedTestnet) nodesAtDistance(dist int) []rpcNode { return result } +func (tn *preminedTestnet) neighborsAtDistance(base *enode.Node, distance uint, elems int) []*enode.Node { + nodes := nodesByDistance{target: base.ID()} + for d := range lookupTestnet.dists { + for i := range lookupTestnet.dists[d] { + n := lookupTestnet.node(d, i) + if uint(enode.LogDist(n.ID(), base.ID())) == distance { + nodes.push(wrapNode(n), elems) + } + } + } + return unwrapNodes(nodes.entries) +} + func (tn *preminedTestnet) closest(n int) (nodes []*enode.Node) { for d := range tn.dists { for i := range tn.dists[d] { diff --git a/p2p/discover/v4_udp.go b/p2p/discover/v4_udp.go index bfb66fcb19..6af05f93dd 100644 --- a/p2p/discover/v4_udp.go +++ b/p2p/discover/v4_udp.go @@ -47,6 +47,7 @@ var ( errTimeout = errors.New("RPC timeout") errClockWarp = errors.New("reply deadline too far in the future") errClosed = errors.New("socket closed") + errLowPort = errors.New("low port") ) const ( @@ -176,7 +177,7 @@ func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*node, error) { if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) { return nil, errors.New("not contained in netrestrict whitelist") } - key, err := decodePubkey(rn.ID) + key, err := decodePubkey(crypto.S256(), rn.ID) if err != nil { return nil, err } @@ -209,7 +210,7 @@ type UDPv4 struct { addReplyMatcher chan *replyMatcher gotreply chan reply closeCtx context.Context - cancelCloseCtx func() + cancelCloseCtx context.CancelFunc } // replyMatcher represents a pending reply. @@ -258,6 +259,7 @@ type reply struct { } func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { + cfg = cfg.withDefaults() closeCtx, cancel := context.WithCancel(context.Background()) t := &UDPv4{ conn: c, @@ -271,9 +273,6 @@ func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) { cancelCloseCtx: cancel, log: cfg.Log, } - if t.log == nil { - t.log = log.Root() - } tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log) if err != nil { @@ -812,7 +811,7 @@ func (req *pingV4) preverify(t *UDPv4, from *net.UDPAddr, fromID enode.ID, fromK if expired(req.Expiration) { return errExpired } - key, err := decodePubkey(fromKey) + key, err := decodePubkey(crypto.S256(), fromKey) if err != nil { return errors.New("invalid public key") } diff --git a/p2p/discover/v4_udp_test.go b/p2p/discover/v4_udp_test.go index b4e024e7ef..ea7194e43e 100644 --- a/p2p/discover/v4_udp_test.go +++ b/p2p/discover/v4_udp_test.go @@ -41,10 +41,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -func init() { - spew.Config.DisableMethods = true -} - // shared test variables var ( futureExp = uint64(time.Now().Add(10 * time.Hour).Unix()) @@ -117,9 +113,12 @@ func (test *udpTest) packetInFrom(wantError error, key *ecdsa.PrivateKey, addr * func (test *udpTest) waitPacketOut(validate interface{}) (closed bool) { test.t.Helper() - dgram, ok := test.pipe.receive() - if !ok { + dgram, err := test.pipe.receive() + if err == errClosed { return true + } else if err != nil { + test.t.Error("packet receive error:", err) + return false } p, _, hash, err := decodeV4(dgram.data) if err != nil { @@ -671,17 +670,30 @@ func (c *dgramPipe) LocalAddr() net.Addr { return &net.UDPAddr{IP: testLocal.IP, Port: int(testLocal.UDP)} } -func (c *dgramPipe) receive() (dgram, bool) { +func (c *dgramPipe) receive() (dgram, error) { c.mu.Lock() defer c.mu.Unlock() - for len(c.queue) == 0 && !c.closed { + + var timedOut bool + timer := time.AfterFunc(3*time.Second, func() { + c.mu.Lock() + timedOut = true + c.mu.Unlock() + c.cond.Broadcast() + }) + defer timer.Stop() + + for len(c.queue) == 0 && !c.closed && !timedOut { c.cond.Wait() } if c.closed { - return dgram{}, false + return dgram{}, errClosed + } + if timedOut { + return dgram{}, errTimeout } p := c.queue[0] copy(c.queue, c.queue[1:]) c.queue = c.queue[:len(c.queue)-1] - return p, true + return p, nil } diff --git a/p2p/discover/v5_encoding.go b/p2p/discover/v5_encoding.go new file mode 100644 index 0000000000..842234e790 --- /dev/null +++ b/p2p/discover/v5_encoding.go @@ -0,0 +1,659 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package discover + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/elliptic" + crand "crypto/rand" + "crypto/sha256" + "errors" + "fmt" + "hash" + "net" + "time" + + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/crypto/hkdf" +) + +// TODO concurrent WHOAREYOU tie-breaker +// TODO deal with WHOAREYOU amplification factor (min packet size?) +// TODO add counter to nonce +// TODO rehandshake after X packets + +// Discovery v5 packet types. +const ( + p_pingV5 byte = iota + 1 + p_pongV5 + p_findnodeV5 + p_nodesV5 + p_requestTicketV5 + p_ticketV5 + p_regtopicV5 + p_regconfirmationV5 + p_topicqueryV5 + p_unknownV5 = byte(255) // any non-decryptable packet + p_whoareyouV5 = byte(254) // the WHOAREYOU packet +) + +// Discovery v5 packet structures. +type ( + // unknownV5 represents any packet that can't be decrypted. + unknownV5 struct { + AuthTag []byte + } + + // WHOAREYOU contains the handshake challenge. + whoareyouV5 struct { + AuthTag []byte + IDNonce [32]byte // To be signed by recipient. + RecordSeq uint64 // ENR sequence number of recipient + + node *enode.Node + sent mclock.AbsTime + } + + // PING is sent during liveness checks. + pingV5 struct { + ReqID []byte + ENRSeq uint64 + } + + // PONG is the reply to PING. + pongV5 struct { + ReqID []byte + ENRSeq uint64 + ToIP net.IP // These fields should mirror the UDP envelope address of the ping + ToPort uint16 // packet, which provides a way to discover the the external address (after NAT). + } + + // FINDNODE is a query for nodes in the given bucket. + findnodeV5 struct { + ReqID []byte + Distance uint + } + + // NODES is the reply to FINDNODE and TOPICQUERY. + nodesV5 struct { + ReqID []byte + Total uint8 + Nodes []*enr.Record + } + + // REQUESTTICKET requests a ticket for a topic queue. + requestTicketV5 struct { + ReqID []byte + Topic []byte + } + + // TICKET is the response to REQUESTTICKET. + ticketV5 struct { + ReqID []byte + Ticket []byte + } + + // REGTOPIC registers the sender in a topic queue using a ticket. + regtopicV5 struct { + ReqID []byte + Ticket []byte + ENR *enr.Record + } + + // REGCONFIRMATION is the reply to REGTOPIC. + regconfirmationV5 struct { + ReqID []byte + Registered bool + } + + // TOPICQUERY asks for nodes with the given topic. + topicqueryV5 struct { + ReqID []byte + Topic []byte + } +) + +const ( + // Encryption/authentication parameters. + authSchemeName = "gcm" + aesKeySize = 16 + gcmNonceSize = 12 + idNoncePrefix = "discovery-id-nonce" + handshakeTimeout = time.Second +) + +var ( + errTooShort = errors.New("packet too short") + errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake") + errHandshakeNonceMismatch = errors.New("wrong nonce in auth response") + errInvalidAuthKey = errors.New("invalid ephemeral pubkey") + errUnknownAuthScheme = errors.New("unknown auth scheme in handshake") + errNoRecord = errors.New("expected ENR in handshake but none sent") + errInvalidNonceSig = errors.New("invalid ID nonce signature") + zeroNonce = make([]byte, gcmNonceSize) +) + +// wireCodec encodes and decodes discovery v5 packets. +type wireCodec struct { + sha256 hash.Hash + localnode *enode.LocalNode + privkey *ecdsa.PrivateKey + myChtagHash enode.ID + myWhoareyouMagic []byte + + sc *sessionCache +} + +type handshakeSecrets struct { + writeKey, readKey, authRespKey []byte +} + +type authHeader struct { + authHeaderList + isHandshake bool +} + +type authHeaderList struct { + Auth []byte // authentication info of packet + IDNonce [32]byte // IDNonce of WHOAREYOU + Scheme string // name of encryption/authentication scheme + EphemeralKey []byte // ephemeral public key + Response []byte // encrypted authResponse +} + +type authResponse struct { + Version uint + Signature []byte + Record *enr.Record `rlp:"nil"` // sender's record +} + +func (h *authHeader) DecodeRLP(r *rlp.Stream) error { + k, _, err := r.Kind() + if err != nil { + return err + } + if k == rlp.Byte || k == rlp.String { + return r.Decode(&h.Auth) + } + h.isHandshake = true + return r.Decode(&h.authHeaderList) +} + +// ephemeralKey decodes the ephemeral public key in the header. +func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey { + var key encPubkey + copy(key[:], h.EphemeralKey) + pubkey, _ := decodePubkey(curve, key) + return pubkey +} + +// newWireCodec creates a wire codec. +func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec { + c := &wireCodec{ + sha256: sha256.New(), + localnode: ln, + privkey: key, + sc: newSessionCache(1024, clock), + } + // Create magic strings for packet matching. + self := ln.ID() + c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU")) + copy(c.myChtagHash[:], c.sha256sum(self[:])) + return c +} + +// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The +// 'challenge' parameter should be the most recently received WHOAREYOU packet from that +// node. +func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) { + if packet.kind() == p_whoareyouV5 { + p := packet.(*whoareyouV5) + enc, err := c.encodeWhoareyou(id, p) + if err == nil { + c.sc.storeSentHandshake(id, addr, p) + } + return enc, nil, err + } + // Ensure calling code sets node if needed. + if challenge != nil && challenge.node == nil { + panic("BUG: missing challenge.node in encode") + } + writeKey := c.sc.writeKey(id, addr) + if writeKey != nil || challenge != nil { + return c.encodeEncrypted(id, addr, packet, writeKey, challenge) + } + return c.encodeRandom(id) +} + +// encodeRandom encodes a random packet. +func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) { + tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) + r := make([]byte, 44) // TODO randomize size + if _, err := crand.Read(r); err != nil { + return nil, nil, err + } + nonce := make([]byte, gcmNonceSize) + if _, err := crand.Read(nonce); err != nil { + return nil, nil, fmt.Errorf("can't get random data: %v", err) + } + b := new(bytes.Buffer) + b.Write(tag[:]) + rlp.Encode(b, nonce) + b.Write(r) + return b.Bytes(), nonce, nil +} + +// encodeWhoareyou encodes WHOAREYOU. +func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) { + // Sanity check node field to catch misbehaving callers. + if packet.RecordSeq > 0 && packet.node == nil { + panic("BUG: missing node in whoareyouV5 with non-zero seq") + } + b := new(bytes.Buffer) + b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU"))) + err := rlp.Encode(b, packet) + return b.Bytes(), err +} + +// encodeEncrypted encodes an encrypted packet. +func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) { + nonce := make([]byte, gcmNonceSize) + if _, err := crand.Read(nonce); err != nil { + return nil, nil, fmt.Errorf("can't get random data: %v", err) + } + + var headEnc []byte + if challenge == nil { + // Regular packet, use existing key and simply encode nonce. + headEnc, _ = rlp.EncodeToBytes(nonce) + } else { + // We're answering WHOAREYOU, generate new keys and encrypt with those. + header, sec, err := c.makeAuthHeader(nonce, challenge) + if err != nil { + return nil, nil, err + } + if headEnc, err = rlp.EncodeToBytes(header); err != nil { + return nil, nil, err + } + c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey) + writeKey = sec.writeKey + } + + // Encode the packet. + body := new(bytes.Buffer) + body.WriteByte(packet.kind()) + if err := rlp.Encode(body, packet); err != nil { + return nil, nil, err + } + tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID()) + headsize := len(tag) + len(headEnc) + headbuf := make([]byte, headsize) + copy(headbuf[:], tag[:]) + copy(headbuf[len(tag):], headEnc) + + // Encrypt the body. + enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:]) + return enc, nonce, err +} + +// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU. +func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) { + resp := &authResponse{Version: 5} + + // Add our record to response if it's newer than what remote + // side has. + ln := c.localnode.Node() + if challenge.RecordSeq < ln.Seq() { + resp.Record = ln.Record() + } + + // Create the ephemeral key. This needs to be first because the + // key is part of the ID nonce signature. + var remotePubkey = new(ecdsa.PublicKey) + if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil { + return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient") + } + ephkey, err := crypto.GenerateKey() + if err != nil { + return nil, nil, fmt.Errorf("can't generate ephemeral key") + } + ephpubkey := encodePubkey(&ephkey.PublicKey) + + // Add ID nonce signature to response. + idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:]) + if err != nil { + return nil, nil, fmt.Errorf("can't sign: %v", err) + } + resp.Signature = idsig + + // Create session keys. + sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge) + if sec == nil { + return nil, nil, fmt.Errorf("key derivation failed") + } + + // Encrypt the authentication response and assemble the auth header. + respRLP, err := rlp.EncodeToBytes(resp) + if err != nil { + return nil, nil, fmt.Errorf("can't encode auth response: %v", err) + } + respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil) + if err != nil { + return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err) + } + head := &authHeaderList{ + Auth: nonce, + Scheme: authSchemeName, + IDNonce: challenge.IDNonce, + EphemeralKey: ephpubkey[:], + Response: respEnc, + } + return head, sec, err +} + +// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement. +func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets { + eph := ecdh(priv, pub) + if eph == nil { + return nil + } + + info := []byte("discovery v5 key agreement") + info = append(info, n1[:]...) + info = append(info, n2[:]...) + kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info) + sec := handshakeSecrets{ + writeKey: make([]byte, aesKeySize), + readKey: make([]byte, aesKeySize), + authRespKey: make([]byte, aesKeySize), + } + kdf.Read(sec.writeKey) + kdf.Read(sec.readKey) + kdf.Read(sec.authRespKey) + for i := range eph { + eph[i] = 0 + } + return &sec +} + +// signIDNonce creates the ID nonce signature. +func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) { + idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey) + if err != nil { + return nil, fmt.Errorf("can't sign: %v", err) + } + return idsig[:len(idsig)-1], nil // remove recovery ID +} + +// idNonceHash computes the hash of id nonce with prefix. +func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte { + h := c.sha256reset() + h.Write([]byte(idNoncePrefix)) + h.Write(nonce) + h.Write(ephkey) + return h.Sum(nil) +} + +// decode decodes a discovery packet. +func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { + // Delete timed-out handshakes. This must happen before decoding to avoid + // processing the same handshake twice. + c.sc.handshakeGC() + + if len(input) < 32 { + return enode.ID{}, nil, nil, errTooShort + } + if bytes.HasPrefix(input, c.myWhoareyouMagic) { + p, err := c.decodeWhoareyou(input) + return enode.ID{}, nil, p, err + } + sender := xorTag(input[:32], c.myChtagHash) + p, n, err := c.decodeEncrypted(sender, addr, input) + return sender, n, p, err +} + +// decodeWhoareyou decode a WHOAREYOU packet. +func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) { + packet := new(whoareyouV5) + err := rlp.DecodeBytes(input[32:], packet) + return packet, err +} + +// decodeEncrypted decodes an encrypted discovery packet. +func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) { + // Decode packet header. + var head authHeader + r := bytes.NewReader(input[32:]) + err := rlp.Decode(r, &head) + if err != nil { + return nil, nil, err + } + + // Decrypt and process auth response. + readKey, node, err := c.decodeAuth(fromID, fromAddr, &head) + if err != nil { + return nil, nil, err + } + + // Decrypt and decode the packet body. + headsize := len(input) - r.Len() + bodyEnc := input[headsize:] + body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32]) + if err != nil { + if !head.isHandshake { + // Can't decrypt, start handshake. + return &unknownV5{AuthTag: head.Auth}, nil, nil + } + return nil, nil, fmt.Errorf("handshake failed: %v", err) + } + if len(body) == 0 { + return nil, nil, errTooShort + } + p, err := decodePacketBodyV5(body[0], body[1:]) + return p, node, err +} + +// decodeAuth processes an auth header. +func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) { + if !head.isHandshake { + return c.sc.readKey(fromID, fromAddr), nil, nil + } + + // Remote is attempting handshake. Verify against our last WHOAREYOU. + challenge := c.sc.getHandshake(fromID, fromAddr) + if challenge == nil { + return nil, nil, errUnexpectedHandshake + } + if head.IDNonce != challenge.IDNonce { + return nil, nil, errHandshakeNonceMismatch + } + sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge) + if err != nil { + return nil, n, err + } + // Swap keys to match remote. + sec.readKey, sec.writeKey = sec.writeKey, sec.readKey + c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey) + c.sc.deleteHandshake(fromID, fromAddr) + return sec.readKey, n, err +} + +// decodeAuthResp decodes and verifies an authentication response. +func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) { + // Decrypt / decode the response. + if head.Scheme != authSchemeName { + return nil, nil, errUnknownAuthScheme + } + ephkey := head.ephemeralKey(c.privkey.Curve) + if ephkey == nil { + return nil, nil, errInvalidAuthKey + } + sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge) + respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil) + if err != nil { + return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err) + } + var resp authResponse + if err := rlp.DecodeBytes(respPT, &resp); err != nil { + return nil, nil, fmt.Errorf("invalid auth response: %v", err) + } + + // Verify response node record. The remote node should include the record + // if we don't have one or if ours is older than the latest version. + node := challenge.node + if resp.Record != nil { + if node == nil || node.Seq() < resp.Record.Seq() { + n, err := enode.New(enode.ValidSchemes, resp.Record) + if err != nil { + return nil, nil, fmt.Errorf("invalid node record: %v", err) + } + if n.ID() != fromID { + return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID()) + } + node = n + } + } + if node == nil { + return nil, nil, errNoRecord + } + + // Verify ID nonce signature. + err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node) + if err != nil { + return nil, nil, err + } + return sec, node, nil +} + +// verifyIDSignature checks that signature over idnonce was made by the node with given record. +func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error { + switch idscheme := n.Record().IdentityScheme(); idscheme { + case "v4": + var pk ecdsa.PublicKey + n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid + if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) { + return errInvalidNonceSig + } + return nil + default: + return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) + } +} + +// decodePacketBody decodes the body of an encrypted discovery packet. +func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) { + var dec packetV5 + switch ptype { + case p_pingV5: + dec = new(pingV5) + case p_pongV5: + dec = new(pongV5) + case p_findnodeV5: + dec = new(findnodeV5) + case p_nodesV5: + dec = new(nodesV5) + case p_requestTicketV5: + dec = new(requestTicketV5) + case p_ticketV5: + dec = new(ticketV5) + case p_regtopicV5: + dec = new(regtopicV5) + case p_regconfirmationV5: + dec = new(regconfirmationV5) + case p_topicqueryV5: + dec = new(topicqueryV5) + default: + return nil, fmt.Errorf("unknown packet type %d", ptype) + } + if err := rlp.DecodeBytes(body, dec); err != nil { + return nil, err + } + return dec, nil +} + +// sha256reset returns the shared hash instance. +func (c *wireCodec) sha256reset() hash.Hash { + c.sha256.Reset() + return c.sha256 +} + +// sha256sum computes sha256 on the concatenation of inputs. +func (c *wireCodec) sha256sum(inputs ...[]byte) []byte { + c.sha256.Reset() + for _, b := range inputs { + c.sha256.Write(b) + } + return c.sha256.Sum(nil) +} + +func xorTag(a []byte, b enode.ID) enode.ID { + var r enode.ID + for i := range r { + r[i] = a[i] ^ b[i] + } + return r +} + +// ecdh creates a shared secret. +func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { + secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) + if secX == nil { + return nil + } + sec := make([]byte, 33) + sec[0] = 0x02 | byte(secY.Bit(0)) + math.ReadBits(secX, sec[1:]) + return sec +} + +// encryptGCM encrypts pt using AES-GCM with the given key and nonce. +func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + panic(fmt.Errorf("can't create block cipher: %v", err)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + panic(fmt.Errorf("can't create GCM: %v", err)) + } + return aesgcm.Seal(dest, nonce, pt, authData), nil +} + +// decryptGCM decrypts ct using AES-GCM with the given key and nonce. +func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("can't create block cipher: %v", err) + } + if len(nonce) != gcmNonceSize { + return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) + } + aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) + if err != nil { + return nil, fmt.Errorf("can't create GCM: %v", err) + } + pt := make([]byte, 0, len(ct)) + return aesgcm.Open(pt, nonce, ct, authData) +} diff --git a/p2p/discover/v5_encoding_test.go b/p2p/discover/v5_encoding_test.go new file mode 100644 index 0000000000..77e6bae6ae --- /dev/null +++ b/p2p/discover/v5_encoding_test.go @@ -0,0 +1,373 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package discover + +import ( + "bytes" + "crypto/ecdsa" + "encoding/hex" + "fmt" + "net" + "reflect" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +var ( + testKeyA, _ = crypto.HexToECDSA("eef77acb6c6a6eebc5b363a475ac583ec7eccdb42b6481424c60f59aa326547f") + testKeyB, _ = crypto.HexToECDSA("66fb62bfbd66b9177a138c1e5cddbe4f7c30c343e94e68df8769459cb1cde628") + testIDnonce = [32]byte{5, 6, 7, 8, 9, 10, 11, 12} +) + +func TestDeriveKeysV5(t *testing.T) { + t.Parallel() + + var ( + n1 = enode.ID{1} + n2 = enode.ID{2} + challenge = &whoareyouV5{} + db, _ = enode.OpenDB("") + ln = enode.NewLocalNode(db, testKeyA) + c = newWireCodec(ln, testKeyA, mclock.System{}) + ) + defer db.Close() + + sec1 := c.deriveKeys(n1, n2, testKeyA, &testKeyB.PublicKey, challenge) + sec2 := c.deriveKeys(n1, n2, testKeyB, &testKeyA.PublicKey, challenge) + if sec1 == nil || sec2 == nil { + t.Fatal("key agreement failed") + } + if !reflect.DeepEqual(sec1, sec2) { + t.Fatalf("keys not equal:\n %+v\n %+v", sec1, sec2) + } +} + +// This test checks the basic handshake flow where A talks to B and A has no secrets. +func TestHandshakeV5(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) + resp := net.nodeB.expectDecode(t, p_unknownV5, packet) + + // A <- B WHOAREYOU + challenge := &whoareyouV5{ + AuthTag: resp.(*unknownV5).AuthTag, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) + + // A -> B FINDNODE + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) + net.nodeB.expectDecode(t, p_findnodeV5, findnode) + if len(net.nodeB.c.sc.handshakes) > 0 { + t.Fatalf("node B didn't remove handshake from challenge map") + } + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) + net.nodeA.expectDecode(t, p_nodesV5, nodes) +} + +// This test checks that handshake attempts are removed within the timeout. +func TestHandshakeV5_timeout(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) + resp := net.nodeB.expectDecode(t, p_unknownV5, packet) + + // A <- B WHOAREYOU + challenge := &whoareyouV5{ + AuthTag: resp.(*unknownV5).AuthTag, + IDNonce: testIDnonce, + RecordSeq: 0, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) + + // A -> B FINDNODE after timeout + net.clock.Run(handshakeTimeout + 1) + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) + net.nodeB.expectDecodeErr(t, errUnexpectedHandshake, findnode) +} + +// This test checks handshake behavior when no record is sent in the auth response. +func TestHandshakeV5_norecord(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + // A -> B RANDOM PACKET + packet, _ := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) + resp := net.nodeB.expectDecode(t, p_unknownV5, packet) + + // A <- B WHOAREYOU + nodeA := net.nodeA.n() + if nodeA.Seq() == 0 { + t.Fatal("need non-zero sequence number") + } + challenge := &whoareyouV5{ + AuthTag: resp.(*unknownV5).AuthTag, + IDNonce: testIDnonce, + RecordSeq: nodeA.Seq(), + node: nodeA, + } + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) + + // A -> B FINDNODE + findnode, _ := net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) + net.nodeB.expectDecode(t, p_findnodeV5, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) + net.nodeA.expectDecode(t, p_nodesV5, nodes) +} + +// In this test, A tries to send FINDNODE with existing secrets but B doesn't know +// anything about A. +func TestHandshakeV5_rekey(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + initKeys := &handshakeSecrets{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeys.readKey, initKeys.writeKey) + + // A -> B FINDNODE (encrypted with zero keys) + findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{}) + net.nodeB.expectDecode(t, p_unknownV5, findnode) + + // A <- B WHOAREYOU + challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) + + // Check that new keys haven't been stored yet. + if s := net.nodeA.c.sc.session(net.nodeB.id(), net.nodeB.addr()); !bytes.Equal(s.writeKey, initKeys.writeKey) || !bytes.Equal(s.readKey, initKeys.readKey) { + t.Fatal("node A stored keys too early") + } + if s := net.nodeB.c.sc.session(net.nodeA.id(), net.nodeA.addr()); s != nil { + t.Fatal("node B stored keys too early") + } + + // A -> B FINDNODE encrypted with new keys + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) + net.nodeB.expectDecode(t, p_findnodeV5, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) + net.nodeA.expectDecode(t, p_nodesV5, nodes) +} + +// In this test A and B have different keys before the handshake. +func TestHandshakeV5_rekey2(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + initKeysA := &handshakeSecrets{ + readKey: []byte("BBBBBBBBBBBBBBBB"), + writeKey: []byte("AAAAAAAAAAAAAAAA"), + } + initKeysB := &handshakeSecrets{ + readKey: []byte("CCCCCCCCCCCCCCCC"), + writeKey: []byte("DDDDDDDDDDDDDDDD"), + } + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), initKeysA.readKey, initKeysA.writeKey) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), initKeysB.readKey, initKeysA.writeKey) + + // A -> B FINDNODE encrypted with initKeysA + findnode, authTag := net.nodeA.encode(t, net.nodeB, &findnodeV5{Distance: 3}) + net.nodeB.expectDecode(t, p_unknownV5, findnode) + + // A <- B WHOAREYOU + challenge := &whoareyouV5{AuthTag: authTag, IDNonce: testIDnonce} + whoareyou, _ := net.nodeB.encode(t, net.nodeA, challenge) + net.nodeA.expectDecode(t, p_whoareyouV5, whoareyou) + + // A -> B FINDNODE encrypted with new keys + findnode, _ = net.nodeA.encodeWithChallenge(t, net.nodeB, challenge, &findnodeV5{}) + net.nodeB.expectDecode(t, p_findnodeV5, findnode) + + // A <- B NODES + nodes, _ := net.nodeB.encode(t, net.nodeA, &nodesV5{Total: 1}) + net.nodeA.expectDecode(t, p_nodesV5, nodes) +} + +// This test checks some malformed packets. +func TestDecodeErrorsV5(t *testing.T) { + t.Parallel() + net := newHandshakeTest() + defer net.close() + + net.nodeA.expectDecodeErr(t, errTooShort, []byte{}) + // TODO some more tests would be nice :) +} + +// This benchmark checks performance of authHeader decoding, verification and key derivation. +func BenchmarkV5_DecodeAuthSecp256k1(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + var ( + idA = net.nodeA.id() + addrA = net.nodeA.addr() + challenge = &whoareyouV5{AuthTag: []byte("authresp"), RecordSeq: 0, node: net.nodeB.n()} + nonce = make([]byte, gcmNonceSize) + ) + header, _, _ := net.nodeA.c.makeAuthHeader(nonce, challenge) + challenge.node = nil // force ENR signature verification in decoder + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _, err := net.nodeB.c.decodeAuthResp(idA, addrA, header, challenge) + if err != nil { + b.Fatal(err) + } + } +} + +// This benchmark checks how long it takes to decode an encrypted ping packet. +func BenchmarkV5_DecodePing(b *testing.B) { + net := newHandshakeTest() + defer net.close() + + r := []byte{233, 203, 93, 195, 86, 47, 177, 186, 227, 43, 2, 141, 244, 230, 120, 17} + w := []byte{79, 145, 252, 171, 167, 216, 252, 161, 208, 190, 176, 106, 214, 39, 178, 134} + net.nodeA.c.sc.storeNewSession(net.nodeB.id(), net.nodeB.addr(), r, w) + net.nodeB.c.sc.storeNewSession(net.nodeA.id(), net.nodeA.addr(), w, r) + addrB := net.nodeA.addr() + ping := &pingV5{ReqID: []byte("reqid"), ENRSeq: 5} + enc, _, err := net.nodeA.c.encode(net.nodeB.id(), addrB, ping, nil) + if err != nil { + b.Fatalf("can't encode: %v", err) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, _, p, _ := net.nodeB.c.decode(enc, addrB) + if _, ok := p.(*pingV5); !ok { + b.Fatalf("wrong packet type %T", p) + } + } +} + +var pp = spew.NewDefaultConfig() + +type handshakeTest struct { + nodeA, nodeB handshakeTestNode + clock mclock.Simulated +} + +type handshakeTestNode struct { + ln *enode.LocalNode + c *wireCodec +} + +func newHandshakeTest() *handshakeTest { + t := new(handshakeTest) + t.nodeA.init(testKeyA, net.IP{127, 0, 0, 1}, &t.clock) + t.nodeB.init(testKeyB, net.IP{127, 0, 0, 1}, &t.clock) + return t +} + +func (t *handshakeTest) close() { + t.nodeA.ln.Database().Close() + t.nodeB.ln.Database().Close() +} + +func (n *handshakeTestNode) init(key *ecdsa.PrivateKey, ip net.IP, clock mclock.Clock) { + db, _ := enode.OpenDB("") + n.ln = enode.NewLocalNode(db, key) + n.ln.SetStaticIP(ip) + n.c = newWireCodec(n.ln, key, clock) +} + +func (n *handshakeTestNode) encode(t testing.TB, to handshakeTestNode, p packetV5) ([]byte, []byte) { + t.Helper() + return n.encodeWithChallenge(t, to, nil, p) +} + +func (n *handshakeTestNode) encodeWithChallenge(t testing.TB, to handshakeTestNode, c *whoareyouV5, p packetV5) ([]byte, []byte) { + t.Helper() + // Copy challenge and add destination node. This avoids sharing 'c' among the two codecs. + var challenge *whoareyouV5 + if c != nil { + challengeCopy := *c + challenge = &challengeCopy + challenge.node = to.n() + } + // Encode to destination. + enc, authTag, err := n.c.encode(to.id(), to.addr(), p, challenge) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) -> (%s) %s\n%s", n.ln.ID().TerminalString(), to.id().TerminalString(), p.name(), hex.Dump(enc)) + return enc, authTag +} + +func (n *handshakeTestNode) expectDecode(t *testing.T, ptype byte, p []byte) packetV5 { + t.Helper() + dec, err := n.decode(p) + if err != nil { + t.Fatal(fmt.Errorf("(%s) %v", n.ln.ID().TerminalString(), err)) + } + t.Logf("(%s) %#v", n.ln.ID().TerminalString(), pp.NewFormatter(dec)) + if dec.kind() != ptype { + t.Fatalf("expected packet type %d, got %d", ptype, dec.kind()) + } + return dec +} + +func (n *handshakeTestNode) expectDecodeErr(t *testing.T, wantErr error, p []byte) { + t.Helper() + if _, err := n.decode(p); !reflect.DeepEqual(err, wantErr) { + t.Fatal(fmt.Errorf("(%s) got err %q, want %q", n.ln.ID().TerminalString(), err, wantErr)) + } +} + +func (n *handshakeTestNode) decode(input []byte) (packetV5, error) { + _, _, p, err := n.c.decode(input, "127.0.0.1") + return p, err +} + +func (n *handshakeTestNode) n() *enode.Node { + return n.ln.Node() +} + +func (n *handshakeTestNode) addr() string { + return n.ln.Node().IP().String() +} + +func (n *handshakeTestNode) id() enode.ID { + return n.ln.ID() +} diff --git a/p2p/discover/v5_session.go b/p2p/discover/v5_session.go new file mode 100644 index 0000000000..8a0eeb6977 --- /dev/null +++ b/p2p/discover/v5_session.go @@ -0,0 +1,123 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package discover + +import ( + crand "crypto/rand" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/hashicorp/golang-lru/simplelru" +) + +// The sessionCache keeps negotiated encryption keys and +// state for in-progress handshakes in the Discovery v5 wire protocol. +type sessionCache struct { + sessions *simplelru.LRU + handshakes map[sessionID]*whoareyouV5 + clock mclock.Clock +} + +// sessionID identifies a session or handshake. +type sessionID struct { + id enode.ID + addr string +} + +// session contains session information +type session struct { + writeKey []byte + readKey []byte + nonceCounter uint32 +} + +func newSessionCache(maxItems int, clock mclock.Clock) *sessionCache { + cache, err := simplelru.NewLRU(maxItems, nil) + if err != nil { + panic("can't create session cache") + } + return &sessionCache{ + sessions: cache, + handshakes: make(map[sessionID]*whoareyouV5), + clock: clock, + } +} + +// nextNonce creates a nonce for encrypting a message to the given session. +func (sc *sessionCache) nextNonce(id enode.ID, addr string) []byte { + n := make([]byte, gcmNonceSize) + crand.Read(n) + return n +} + +// session returns the current session for the given node, if any. +func (sc *sessionCache) session(id enode.ID, addr string) *session { + item, ok := sc.sessions.Get(sessionID{id, addr}) + if !ok { + return nil + } + return item.(*session) +} + +// readKey returns the current read key for the given node. +func (sc *sessionCache) readKey(id enode.ID, addr string) []byte { + if s := sc.session(id, addr); s != nil { + return s.readKey + } + return nil +} + +// writeKey returns the current read key for the given node. +func (sc *sessionCache) writeKey(id enode.ID, addr string) []byte { + if s := sc.session(id, addr); s != nil { + return s.writeKey + } + return nil +} + +// storeNewSession stores new encryption keys in the cache. +func (sc *sessionCache) storeNewSession(id enode.ID, addr string, r, w []byte) { + sc.sessions.Add(sessionID{id, addr}, &session{ + readKey: r, writeKey: w, + }) +} + +// getHandshake gets the handshake challenge we previously sent to the given remote node. +func (sc *sessionCache) getHandshake(id enode.ID, addr string) *whoareyouV5 { + return sc.handshakes[sessionID{id, addr}] +} + +// storeSentHandshake stores the handshake challenge sent to the given remote node. +func (sc *sessionCache) storeSentHandshake(id enode.ID, addr string, challenge *whoareyouV5) { + challenge.sent = sc.clock.Now() + sc.handshakes[sessionID{id, addr}] = challenge +} + +// deleteHandshake deletes handshake data for the given node. +func (sc *sessionCache) deleteHandshake(id enode.ID, addr string) { + delete(sc.handshakes, sessionID{id, addr}) +} + +// handshakeGC deletes timed-out handshakes. +func (sc *sessionCache) handshakeGC() { + deadline := sc.clock.Now().Add(-handshakeTimeout) + for key, challenge := range sc.handshakes { + if challenge.sent < deadline { + delete(sc.handshakes, key) + } + } +} diff --git a/p2p/discover/v5_udp.go b/p2p/discover/v5_udp.go new file mode 100644 index 0000000000..e667be1690 --- /dev/null +++ b/p2p/discover/v5_udp.go @@ -0,0 +1,832 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package discover + +import ( + "bytes" + "context" + "crypto/ecdsa" + crand "crypto/rand" + "errors" + "fmt" + "io" + "math" + "net" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/p2p/netutil" +) + +const ( + lookupRequestLimit = 3 // max requests against a single node during lookup + findnodeResultLimit = 15 // applies in FINDNODE handler + totalNodesResponseLimit = 5 // applies in waitForNodes + nodesResponseItemLimit = 3 // applies in sendNodes + + respTimeoutV5 = 700 * time.Millisecond +) + +// codecV5 is implemented by wireCodec (and testCodec). +// +// The UDPv5 transport is split into two objects: the codec object deals with +// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns. +type codecV5 interface { + // encode encodes a packet. The 'challenge' parameter is non-nil for calls which got a + // WHOAREYOU response. + encode(fromID enode.ID, fromAddr string, p packetV5, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) + + // decode decodes a packet. It returns an *unknownV5 packet if decryption fails. + // The fromNode return value is non-nil when the input contains a handshake response. + decode(input []byte, fromAddr string) (fromID enode.ID, fromNode *enode.Node, p packetV5, err error) +} + +// packetV5 is implemented by all discv5 packet type structs. +type packetV5 interface { + // These methods provide information and set the request ID. + name() string + kind() byte + setreqid([]byte) + // handle should perform the appropriate action to handle the packet, i.e. this is the + // place to send the response. + handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) +} + +// UDPv5 is the implementation of protocol version 5. +type UDPv5 struct { + // static fields + conn UDPConn + tab *Table + netrestrict *netutil.Netlist + priv *ecdsa.PrivateKey + localNode *enode.LocalNode + db *enode.DB + log log.Logger + clock mclock.Clock + validSchemes enr.IdentityScheme + + // channels into dispatch + packetInCh chan ReadPacket + readNextCh chan struct{} + callCh chan *callV5 + callDoneCh chan *callV5 + respTimeoutCh chan *callTimeout + + // state of dispatch + codec codecV5 + activeCallByNode map[enode.ID]*callV5 + activeCallByAuth map[string]*callV5 + callQueue map[enode.ID][]*callV5 + + // shutdown stuff + closeOnce sync.Once + closeCtx context.Context + cancelCloseCtx context.CancelFunc + wg sync.WaitGroup +} + +// callV5 represents a remote procedure call against another node. +type callV5 struct { + node *enode.Node + packet packetV5 + responseType byte // expected packet type of response + reqid []byte + ch chan packetV5 // responses sent here + err chan error // errors sent here + + // Valid for active calls only: + authTag []byte // authTag of request packet + handshakeCount int // # times we attempted handshake for this call + challenge *whoareyouV5 // last sent handshake challenge + timeout mclock.Timer +} + +// callTimeout is the response timeout event of a call. +type callTimeout struct { + c *callV5 + timer mclock.Timer +} + +// ListenV5 listens on the given connection. +func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { + t, err := newUDPv5(conn, ln, cfg) + if err != nil { + return nil, err + } + go t.tab.loop() + t.wg.Add(2) + go t.readLoop() + go t.dispatch() + return t, nil +} + +// newUDPv5 creates a UDPv5 transport, but doesn't start any goroutines. +func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) { + closeCtx, cancelCloseCtx := context.WithCancel(context.Background()) + cfg = cfg.withDefaults() + t := &UDPv5{ + // static fields + conn: conn, + localNode: ln, + db: ln.Database(), + netrestrict: cfg.NetRestrict, + priv: cfg.PrivateKey, + log: cfg.Log, + validSchemes: cfg.ValidSchemes, + clock: cfg.Clock, + // channels into dispatch + packetInCh: make(chan ReadPacket, 1), + readNextCh: make(chan struct{}, 1), + callCh: make(chan *callV5), + callDoneCh: make(chan *callV5), + respTimeoutCh: make(chan *callTimeout), + // state of dispatch + codec: newWireCodec(ln, cfg.PrivateKey, cfg.Clock), + activeCallByNode: make(map[enode.ID]*callV5), + activeCallByAuth: make(map[string]*callV5), + callQueue: make(map[enode.ID][]*callV5), + // shutdown + closeCtx: closeCtx, + cancelCloseCtx: cancelCloseCtx, + } + tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log) + if err != nil { + return nil, err + } + t.tab = tab + return t, nil +} + +// Self returns the local node record. +func (t *UDPv5) Self() *enode.Node { + return t.localNode.Node() +} + +// Close shuts down packet processing. +func (t *UDPv5) Close() { + t.closeOnce.Do(func() { + t.cancelCloseCtx() + t.conn.Close() + t.wg.Wait() + t.tab.close() + }) +} + +// Ping sends a ping message to the given node. +func (t *UDPv5) Ping(n *enode.Node) error { + _, err := t.ping(n) + return err +} + +// Resolve searches for a specific node with the given ID and tries to get the most recent +// version of the node record for it. It returns n if the node could not be resolved. +func (t *UDPv5) Resolve(n *enode.Node) *enode.Node { + if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() { + n = intable + } + // Try asking directly. This works if the node is still responding on the endpoint we have. + if resp, err := t.RequestENR(n); err == nil { + return resp + } + // Otherwise do a network lookup. + result := t.Lookup(n.ID()) + for _, rn := range result { + if rn.ID() == n.ID() && rn.Seq() > n.Seq() { + return rn + } + } + return n +} + +func (t *UDPv5) RandomNodes() enode.Iterator { + if t.tab.len() == 0 { + // All nodes were dropped, refresh. The very first query will hit this + // case and run the bootstrapping logic. + <-t.tab.refresh() + } + + return newLookupIterator(t.closeCtx, t.newRandomLookup) +} + +// Lookup performs a recursive lookup for the given target. +// It returns the closest nodes to target. +func (t *UDPv5) Lookup(target enode.ID) []*enode.Node { + return t.newLookup(t.closeCtx, target).run() +} + +// lookupRandom looks up a random target. +// This is needed to satisfy the transport interface. +func (t *UDPv5) lookupRandom() []*enode.Node { + return t.newRandomLookup(t.closeCtx).run() +} + +// lookupSelf looks up our own node ID. +// This is needed to satisfy the transport interface. +func (t *UDPv5) lookupSelf() []*enode.Node { + return t.newLookup(t.closeCtx, t.Self().ID()).run() +} + +func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup { + var target enode.ID + crand.Read(target[:]) + return t.newLookup(ctx, target) +} + +func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup { + return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) { + return t.lookupWorker(n, target) + }) +} + +// lookupWorker performs FINDNODE calls against a single node during lookup. +func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) { + var ( + dists = lookupDistances(target, destNode.ID()) + nodes = nodesByDistance{target: target} + err error + ) + for i := 0; i < lookupRequestLimit && len(nodes.entries) < findnodeResultLimit; i++ { + var r []*enode.Node + r, err = t.findnode(unwrapNode(destNode), dists[i]) + if err == errClosed { + return nil, err + } + for _, n := range r { + if n.ID() != t.Self().ID() { + nodes.push(wrapNode(n), findnodeResultLimit) + } + } + } + return nodes.entries, err +} + +// lookupDistances computes the distance parameter for FINDNODE calls to dest. +// It chooses distances adjacent to logdist(target, dest), e.g. for a target +// with logdist(target, dest) = 255 the result is [255, 256, 254]. +func lookupDistances(target, dest enode.ID) (dists []int) { + td := enode.LogDist(target, dest) + dists = append(dists, td) + for i := 1; len(dists) < lookupRequestLimit; i++ { + if td+i < 256 { + dists = append(dists, td+i) + } + if td-i > 0 { + dists = append(dists, td-i) + } + } + return dists +} + +// ping calls PING on a node and waits for a PONG response. +func (t *UDPv5) ping(n *enode.Node) (uint64, error) { + resp := t.call(n, p_pongV5, &pingV5{ENRSeq: t.localNode.Node().Seq()}) + defer t.callDone(resp) + select { + case pong := <-resp.ch: + return pong.(*pongV5).ENRSeq, nil + case err := <-resp.err: + return 0, err + } +} + +// requestENR requests n's record. +func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) { + nodes, err := t.findnode(n, 0) + if err != nil { + return nil, err + } + if len(nodes) != 1 { + return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes)) + } + return nodes[0], nil +} + +// requestTicket calls REQUESTTICKET on a node and waits for a TICKET response. +func (t *UDPv5) requestTicket(n *enode.Node) ([]byte, error) { + resp := t.call(n, p_ticketV5, &pingV5{}) + defer t.callDone(resp) + select { + case response := <-resp.ch: + return response.(*ticketV5).Ticket, nil + case err := <-resp.err: + return nil, err + } +} + +// findnode calls FINDNODE on a node and waits for responses. +func (t *UDPv5) findnode(n *enode.Node, distance int) ([]*enode.Node, error) { + resp := t.call(n, p_nodesV5, &findnodeV5{Distance: uint(distance)}) + return t.waitForNodes(resp, distance) +} + +// waitForNodes waits for NODES responses to the given call. +func (t *UDPv5) waitForNodes(c *callV5, distance int) ([]*enode.Node, error) { + defer t.callDone(c) + + var ( + nodes []*enode.Node + seen = make(map[enode.ID]struct{}) + received, total = 0, -1 + ) + for { + select { + case responseP := <-c.ch: + response := responseP.(*nodesV5) + for _, record := range response.Nodes { + node, err := t.verifyResponseNode(c, record, distance, seen) + if err != nil { + t.log.Debug("Invalid record in "+response.name(), "id", c.node.ID(), "err", err) + continue + } + nodes = append(nodes, node) + } + if total == -1 { + total = min(int(response.Total), totalNodesResponseLimit) + } + if received++; received == total { + return nodes, nil + } + case err := <-c.err: + return nodes, err + } + } +} + +// verifyResponseNode checks validity of a record in a NODES response. +func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distance int, seen map[enode.ID]struct{}) (*enode.Node, error) { + node, err := enode.New(t.validSchemes, r) + if err != nil { + return nil, err + } + if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil { + return nil, err + } + if c.node.UDP() <= 1024 { + return nil, errLowPort + } + if distance != -1 { + if d := enode.LogDist(c.node.ID(), node.ID()); d != distance { + return nil, fmt.Errorf("wrong distance %d", d) + } + } + if _, ok := seen[node.ID()]; ok { + return nil, fmt.Errorf("duplicate record") + } + seen[node.ID()] = struct{}{} + return node, nil +} + +// call sends the given call and sets up a handler for response packets (of type c.responseType). +// Responses are dispatched to the call's response channel. +func (t *UDPv5) call(node *enode.Node, responseType byte, packet packetV5) *callV5 { + c := &callV5{ + node: node, + packet: packet, + responseType: responseType, + reqid: make([]byte, 8), + ch: make(chan packetV5, 1), + err: make(chan error, 1), + } + // Assign request ID. + crand.Read(c.reqid) + packet.setreqid(c.reqid) + // Send call to dispatch. + select { + case t.callCh <- c: + case <-t.closeCtx.Done(): + c.err <- errClosed + } + return c +} + +// callDone tells dispatch that the active call is done. +func (t *UDPv5) callDone(c *callV5) { + select { + case t.callDoneCh <- c: + case <-t.closeCtx.Done(): + } +} + +// dispatch runs in its own goroutine, handles incoming packets and deals with calls. +// +// For any destination node there is at most one 'active call', stored in the t.activeCall* +// maps. A call is made active when it is sent. The active call can be answered by a +// matching response, in which case c.ch receives the response; or by timing out, in which case +// c.err receives the error. When the function that created the call signals the active +// call is done through callDone, the next call from the call queue is started. +// +// Calls may also be answered by a WHOAREYOU packet referencing the call packet's authTag. +// When that happens the call is simply re-sent to complete the handshake. We allow one +// handshake attempt per call. +func (t *UDPv5) dispatch() { + defer t.wg.Done() + + // Arm first read. + t.readNextCh <- struct{}{} + + for { + select { + case c := <-t.callCh: + id := c.node.ID() + t.callQueue[id] = append(t.callQueue[id], c) + t.sendNextCall(id) + + case ct := <-t.respTimeoutCh: + active := t.activeCallByNode[ct.c.node.ID()] + if ct.c == active && ct.timer == active.timeout { + ct.c.err <- errTimeout + } + + case c := <-t.callDoneCh: + id := c.node.ID() + active := t.activeCallByNode[id] + if active != c { + panic("BUG: callDone for inactive call") + } + c.timeout.Stop() + delete(t.activeCallByAuth, string(c.authTag)) + delete(t.activeCallByNode, id) + t.sendNextCall(id) + + case p := <-t.packetInCh: + t.handlePacket(p.Data, p.Addr) + // Arm next read. + t.readNextCh <- struct{}{} + + case <-t.closeCtx.Done(): + close(t.readNextCh) + for id, queue := range t.callQueue { + for _, c := range queue { + c.err <- errClosed + } + delete(t.callQueue, id) + } + for id, c := range t.activeCallByNode { + c.err <- errClosed + delete(t.activeCallByNode, id) + delete(t.activeCallByAuth, string(c.authTag)) + } + return + } + } +} + +// startResponseTimeout sets the response timer for a call. +func (t *UDPv5) startResponseTimeout(c *callV5) { + if c.timeout != nil { + c.timeout.Stop() + } + var ( + timer mclock.Timer + done = make(chan struct{}) + ) + timer = t.clock.AfterFunc(respTimeoutV5, func() { + <-done + select { + case t.respTimeoutCh <- &callTimeout{c, timer}: + case <-t.closeCtx.Done(): + } + }) + c.timeout = timer + close(done) +} + +// sendNextCall sends the next call in the call queue if there is no active call. +func (t *UDPv5) sendNextCall(id enode.ID) { + queue := t.callQueue[id] + if len(queue) == 0 || t.activeCallByNode[id] != nil { + return + } + t.activeCallByNode[id] = queue[0] + t.sendCall(t.activeCallByNode[id]) + if len(queue) == 1 { + delete(t.callQueue, id) + } else { + copy(queue, queue[1:]) + t.callQueue[id] = queue[:len(queue)-1] + } +} + +// sendCall encodes and sends a request packet to the call's recipient node. +// This performs a handshake if needed. +func (t *UDPv5) sendCall(c *callV5) { + if len(c.authTag) > 0 { + // The call already has an authTag from a previous handshake attempt. Remove the + // entry for the authTag because we're about to generate a new authTag for this + // call. + delete(t.activeCallByAuth, string(c.authTag)) + } + + addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()} + newTag, _ := t.send(c.node.ID(), addr, c.packet, c.challenge) + c.authTag = newTag + t.activeCallByAuth[string(c.authTag)] = c + t.startResponseTimeout(c) +} + +// sendResponse sends a response packet to the given node. +// This doesn't trigger a handshake even if no keys are available. +func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet packetV5) error { + _, err := t.send(toID, toAddr, packet, nil) + return err +} + +// send sends a packet to the given node. +func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet packetV5, c *whoareyouV5) ([]byte, error) { + addr := toAddr.String() + enc, authTag, err := t.codec.encode(toID, addr, packet, c) + if err != nil { + t.log.Warn(">> "+packet.name(), "id", toID, "addr", addr, "err", err) + return authTag, err + } + _, err = t.conn.WriteToUDP(enc, toAddr) + t.log.Trace(">> "+packet.name(), "id", toID, "addr", addr) + return authTag, err +} + +// readLoop runs in its own goroutine and reads packets from the network. +func (t *UDPv5) readLoop() { + defer t.wg.Done() + + buf := make([]byte, maxPacketSize) + for range t.readNextCh { + nbytes, from, err := t.conn.ReadFromUDP(buf) + if netutil.IsTemporaryError(err) { + // Ignore temporary read errors. + t.log.Debug("Temporary UDP read error", "err", err) + continue + } else if err != nil { + // Shut down the loop for permament errors. + if err != io.EOF { + t.log.Debug("UDP read error", "err", err) + } + return + } + t.dispatchReadPacket(from, buf[:nbytes]) + } +} + +// dispatchReadPacket sends a packet into the dispatch loop. +func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool { + select { + case t.packetInCh <- ReadPacket{content, from}: + return true + case <-t.closeCtx.Done(): + return false + } +} + +// handlePacket decodes and processes an incoming packet from the network. +func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error { + addr := fromAddr.String() + fromID, fromNode, packet, err := t.codec.decode(rawpacket, addr) + if err != nil { + t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err) + return err + } + if fromNode != nil { + // Handshake succeeded, add to table. + t.tab.addSeenNode(wrapNode(fromNode)) + } + if packet.kind() != p_whoareyouV5 { + // WHOAREYOU logged separately to report the sender ID. + t.log.Trace("<< "+packet.name(), "id", fromID, "addr", addr) + } + packet.handle(t, fromID, fromAddr) + return nil +} + +// handleCallResponse dispatches a response packet to the call waiting for it. +func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, reqid []byte, p packetV5) { + ac := t.activeCallByNode[fromID] + if ac == nil || !bytes.Equal(reqid, ac.reqid) { + t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.name()), "id", fromID, "addr", fromAddr) + return + } + if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() { + t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.name()), "id", fromID, "addr", fromAddr) + return + } + if p.kind() != ac.responseType { + t.log.Debug(fmt.Sprintf("Wrong disv5 response type %s", p.name()), "id", fromID, "addr", fromAddr) + return + } + t.startResponseTimeout(ac) + ac.ch <- p +} + +// getNode looks for a node record in table and database. +func (t *UDPv5) getNode(id enode.ID) *enode.Node { + if n := t.tab.getNode(id); n != nil { + return n + } + if n := t.localNode.Database().Node(id); n != nil { + return n + } + return nil +} + +// UNKNOWN + +func (p *unknownV5) name() string { return "UNKNOWN/v5" } +func (p *unknownV5) kind() byte { return p_unknownV5 } +func (p *unknownV5) setreqid(id []byte) {} + +func (p *unknownV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + challenge := &whoareyouV5{AuthTag: p.AuthTag} + crand.Read(challenge.IDNonce[:]) + if n := t.getNode(fromID); n != nil { + challenge.node = n + challenge.RecordSeq = n.Seq() + } + t.sendResponse(fromID, fromAddr, challenge) +} + +// WHOAREYOU + +func (p *whoareyouV5) name() string { return "WHOAREYOU/v5" } +func (p *whoareyouV5) kind() byte { return p_whoareyouV5 } +func (p *whoareyouV5) setreqid(id []byte) {} + +func (p *whoareyouV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + c, err := p.matchWithCall(t, p.AuthTag) + if err != nil { + t.log.Debug("Invalid WHOAREYOU/v5", "addr", fromAddr, "err", err) + return + } + // Resend the call that was answered by WHOAREYOU. + t.log.Trace("<< "+p.name(), "id", c.node.ID(), "addr", fromAddr) + c.handshakeCount++ + c.challenge = p + p.node = c.node + t.sendCall(c) +} + +var ( + errChallengeNoCall = errors.New("no matching call") + errChallengeTwice = errors.New("second handshake") +) + +// matchWithCall checks whether the handshake attempt matches the active call. +func (p *whoareyouV5) matchWithCall(t *UDPv5, authTag []byte) (*callV5, error) { + c := t.activeCallByAuth[string(authTag)] + if c == nil { + return nil, errChallengeNoCall + } + if c.handshakeCount > 0 { + return nil, errChallengeTwice + } + return c, nil +} + +// PING + +func (p *pingV5) name() string { return "PING/v5" } +func (p *pingV5) kind() byte { return p_pingV5 } +func (p *pingV5) setreqid(id []byte) { p.ReqID = id } + +func (p *pingV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.sendResponse(fromID, fromAddr, &pongV5{ + ReqID: p.ReqID, + ToIP: fromAddr.IP, + ToPort: uint16(fromAddr.Port), + ENRSeq: t.localNode.Node().Seq(), + }) +} + +// PONG + +func (p *pongV5) name() string { return "PONG/v5" } +func (p *pongV5) kind() byte { return p_pongV5 } +func (p *pongV5) setreqid(id []byte) { p.ReqID = id } + +func (p *pongV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)}) + t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +} + +// FINDNODE + +func (p *findnodeV5) name() string { return "FINDNODE/v5" } +func (p *findnodeV5) kind() byte { return p_findnodeV5 } +func (p *findnodeV5) setreqid(id []byte) { p.ReqID = id } + +func (p *findnodeV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + if p.Distance == 0 { + t.sendNodes(fromID, fromAddr, p.ReqID, []*enode.Node{t.Self()}) + return + } + if p.Distance > 256 { + p.Distance = 256 + } + // Get bucket entries. + t.tab.mutex.Lock() + nodes := unwrapNodes(t.tab.bucketAtDistance(int(p.Distance)).entries) + t.tab.mutex.Unlock() + if len(nodes) > findnodeResultLimit { + nodes = nodes[:findnodeResultLimit] + } + t.sendNodes(fromID, fromAddr, p.ReqID, nodes) +} + +// sendNodes sends the given records in one or more NODES packets. +func (t *UDPv5) sendNodes(toID enode.ID, toAddr *net.UDPAddr, reqid []byte, nodes []*enode.Node) { + // TODO livenessChecks > 1 + // TODO CheckRelayIP + total := uint8(math.Ceil(float64(len(nodes)) / 3)) + resp := &nodesV5{ReqID: reqid, Total: total, Nodes: make([]*enr.Record, 3)} + sent := false + for len(nodes) > 0 { + items := min(nodesResponseItemLimit, len(nodes)) + resp.Nodes = resp.Nodes[:items] + for i := 0; i < items; i++ { + resp.Nodes[i] = nodes[i].Record() + } + t.sendResponse(toID, toAddr, resp) + nodes = nodes[items:] + sent = true + } + // Ensure at least one response is sent. + if !sent { + resp.Total = 1 + resp.Nodes = nil + t.sendResponse(toID, toAddr, resp) + } +} + +// NODES + +func (p *nodesV5) name() string { return "NODES/v5" } +func (p *nodesV5) kind() byte { return p_nodesV5 } +func (p *nodesV5) setreqid(id []byte) { p.ReqID = id } + +func (p *nodesV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +} + +// REQUESTTICKET + +func (p *requestTicketV5) name() string { return "REQUESTTICKET/v5" } +func (p *requestTicketV5) kind() byte { return p_requestTicketV5 } +func (p *requestTicketV5) setreqid(id []byte) { p.ReqID = id } + +func (p *requestTicketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.sendResponse(fromID, fromAddr, &ticketV5{ReqID: p.ReqID}) +} + +// TICKET + +func (p *ticketV5) name() string { return "TICKET/v5" } +func (p *ticketV5) kind() byte { return p_ticketV5 } +func (p *ticketV5) setreqid(id []byte) { p.ReqID = id } + +func (p *ticketV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +} + +// REGTOPIC + +func (p *regtopicV5) name() string { return "REGTOPIC/v5" } +func (p *regtopicV5) kind() byte { return p_regtopicV5 } +func (p *regtopicV5) setreqid(id []byte) { p.ReqID = id } + +func (p *regtopicV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.sendResponse(fromID, fromAddr, ®confirmationV5{ReqID: p.ReqID, Registered: false}) +} + +// REGCONFIRMATION + +func (p *regconfirmationV5) name() string { return "REGCONFIRMATION/v5" } +func (p *regconfirmationV5) kind() byte { return p_regconfirmationV5 } +func (p *regconfirmationV5) setreqid(id []byte) { p.ReqID = id } + +func (p *regconfirmationV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { + t.handleCallResponse(fromID, fromAddr, p.ReqID, p) +} + +// TOPICQUERY + +func (p *topicqueryV5) name() string { return "TOPICQUERY/v5" } +func (p *topicqueryV5) kind() byte { return p_topicqueryV5 } +func (p *topicqueryV5) setreqid(id []byte) { p.ReqID = id } + +func (p *topicqueryV5) handle(t *UDPv5, fromID enode.ID, fromAddr *net.UDPAddr) { +} diff --git a/p2p/discover/v5_udp_test.go b/p2p/discover/v5_udp_test.go new file mode 100644 index 0000000000..15ea0402c2 --- /dev/null +++ b/p2p/discover/v5_udp_test.go @@ -0,0 +1,622 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package discover + +import ( + "bytes" + "crypto/ecdsa" + "encoding/binary" + "fmt" + "math/rand" + "net" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/internal/testlog" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +// Real sockets, real crypto: this test checks end-to-end connectivity for UDPv5. +func TestEndToEndV5(t *testing.T) { + t.Parallel() + + var nodes []*UDPv5 + for i := 0; i < 5; i++ { + var cfg Config + if len(nodes) > 0 { + bn := nodes[0].Self() + cfg.Bootnodes = []*enode.Node{bn} + } + node := startLocalhostV5(t, cfg) + nodes = append(nodes, node) + defer node.Close() + } + + last := nodes[len(nodes)-1] + target := nodes[rand.Intn(len(nodes)-2)].Self() + results := last.Lookup(target.ID()) + if len(results) == 0 || results[0].ID() != target.ID() { + t.Fatalf("lookup returned wrong results: %v", results) + } +} + +func startLocalhostV5(t *testing.T, cfg Config) *UDPv5 { + cfg.PrivateKey = newkey() + db, _ := enode.OpenDB("") + ln := enode.NewLocalNode(db, cfg.PrivateKey) + + // Prefix logs with node ID. + lprefix := fmt.Sprintf("(%s)", ln.ID().TerminalString()) + lfmt := log.TerminalFormat(false) + cfg.Log = testlog.Logger(t, log.LvlTrace) + cfg.Log.SetHandler(log.FuncHandler(func(r *log.Record) error { + t.Logf("%s %s", lprefix, lfmt.Format(r)) + return nil + })) + + // Listen. + socket, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IP{127, 0, 0, 1}}) + if err != nil { + t.Fatal(err) + } + realaddr := socket.LocalAddr().(*net.UDPAddr) + ln.SetStaticIP(realaddr.IP) + ln.Set(enr.UDP(realaddr.Port)) + udp, err := ListenV5(socket, ln, cfg) + if err != nil { + t.Fatal(err) + } + return udp +} + +// This test checks that incoming PING calls are handled correctly. +func TestUDPv5_pingHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + test.packetIn(&pingV5{ReqID: []byte("foo")}) + test.waitPacketOut(func(p *pongV5, addr *net.UDPAddr, authTag []byte) { + if !bytes.Equal(p.ReqID, []byte("foo")) { + t.Error("wrong request ID in response:", p.ReqID) + } + if p.ENRSeq != test.table.self().Seq() { + t.Error("wrong ENR sequence number in response:", p.ENRSeq) + } + }) +} + +// This test checks that incoming 'unknown' packets trigger the handshake. +func TestUDPv5_unknownPacket(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + authTag := [12]byte{1, 2, 3} + check := func(p *whoareyouV5, wantSeq uint64) { + t.Helper() + if !bytes.Equal(p.AuthTag, authTag[:]) { + t.Error("wrong token in WHOAREYOU:", p.AuthTag, authTag[:]) + } + if p.IDNonce == ([32]byte{}) { + t.Error("all zero ID nonce") + } + if p.RecordSeq != wantSeq { + t.Errorf("wrong record seq %d in WHOAREYOU, want %d", p.RecordSeq, wantSeq) + } + } + + // Unknown packet from unknown node. + test.packetIn(&unknownV5{AuthTag: authTag[:]}) + test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + check(p, 0) + }) + + // Make node known. + n := test.getNode(test.remotekey, test.remoteaddr).Node() + test.table.addSeenNode(wrapNode(n)) + + test.packetIn(&unknownV5{AuthTag: authTag[:]}) + test.waitPacketOut(func(p *whoareyouV5, addr *net.UDPAddr, _ []byte) { + check(p, n.Seq()) + }) +} + +// This test checks that incoming FINDNODE calls are handled correctly. +func TestUDPv5_findnodeHandling(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Create test nodes and insert them into the table. + nodes := nodesAtDistance(test.table.self().ID(), 253, 10) + fillTable(test.table, wrapNodes(nodes)) + + // Requesting with distance zero should return the node's own record. + test.packetIn(&findnodeV5{ReqID: []byte{0}, Distance: 0}) + test.expectNodes([]byte{0}, 1, []*enode.Node{test.udp.Self()}) + + // Requesting with distance > 256 caps it at 256. + test.packetIn(&findnodeV5{ReqID: []byte{1}, Distance: 4234098}) + test.expectNodes([]byte{1}, 1, nil) + + // This request gets no nodes because the corresponding bucket is empty. + test.packetIn(&findnodeV5{ReqID: []byte{2}, Distance: 254}) + test.expectNodes([]byte{2}, 1, nil) + + // This request gets all test nodes. + test.packetIn(&findnodeV5{ReqID: []byte{3}, Distance: 253}) + test.expectNodes([]byte{3}, 4, nodes) +} + +func (test *udpV5Test) expectNodes(wantReqID []byte, wantTotal uint8, wantNodes []*enode.Node) { + nodeSet := make(map[enode.ID]*enr.Record) + for _, n := range wantNodes { + nodeSet[n.ID()] = n.Record() + } + for { + test.waitPacketOut(func(p *nodesV5, addr *net.UDPAddr, authTag []byte) { + if len(p.Nodes) > 3 { + test.t.Fatalf("too many nodes in response") + } + if p.Total != wantTotal { + test.t.Fatalf("wrong total response count %d", p.Total) + } + if !bytes.Equal(p.ReqID, wantReqID) { + test.t.Fatalf("wrong request ID in response: %v", p.ReqID) + } + for _, record := range p.Nodes { + n, _ := enode.New(enode.ValidSchemesForTesting, record) + want := nodeSet[n.ID()] + if want == nil { + test.t.Fatalf("unexpected node in response: %v", n) + } + if !reflect.DeepEqual(record, want) { + test.t.Fatalf("wrong record in response: %v", n) + } + delete(nodeSet, n.ID()) + } + }) + if len(nodeSet) == 0 { + return + } + } +} + +// This test checks that outgoing PING calls work. +func TestUDPv5_pingCall(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + + // This ping times out. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) {}) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout, got %q", err) + } + + // This ping works. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetInFrom(test.remotekey, test.remoteaddr, &pongV5{ReqID: p.ReqID}) + }) + if err := <-done; err != nil { + t.Fatal(err) + } + + // This ping gets a reply from the wrong endpoint. + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + wrongAddr := &net.UDPAddr{IP: net.IP{33, 44, 55, 22}, Port: 10101} + test.packetInFrom(test.remotekey, wrongAddr, &pongV5{ReqID: p.ReqID}) + }) + if err := <-done; err != errTimeout { + t.Fatalf("want errTimeout for reply from wrong IP, got %q", err) + } +} + +// This test checks that outgoing FINDNODE calls work and multiple NODES +// replies are aggregated. +func TestUDPv5_findnodeCall(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Launch the request: + var ( + distance = 230 + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), distance, 8) + done = make(chan error, 1) + response []*enode.Node + ) + go func() { + var err error + response, err = test.udp.findnode(remote, distance) + done <- err + }() + + // Serve the responses: + test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { + if p.Distance != uint(distance) { + t.Fatalf("wrong bucket: %d", p.Distance) + } + test.packetIn(&nodesV5{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[:4]), + }) + test.packetIn(&nodesV5{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[4:]), + }) + }) + + // Check results: + if err := <-done; err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !reflect.DeepEqual(response, nodes) { + t.Fatalf("wrong nodes in response") + } + + // TODO: check invalid IPs + // TODO: check invalid/unsigned record +} + +// This test checks that pending calls are re-sent when a handshake happens. +func TestUDPv5_callResend(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 2) + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + + // Ping answered by WHOAREYOU. + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&whoareyouV5{AuthTag: authTag}) + }) + // Ping should be re-sent. + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&pongV5{ReqID: p.ReqID}) + }) + // Answer the other ping. + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&pongV5{ReqID: p.ReqID}) + }) + if err := <-done; err != nil { + t.Fatalf("unexpected ping error: %v", err) + } + if err := <-done; err != nil { + t.Fatalf("unexpected ping error: %v", err) + } +} + +// This test ensures we don't allow multiple rounds of WHOAREYOU for a single call. +func TestUDPv5_multipleHandshakeRounds(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + remote := test.getNode(test.remotekey, test.remoteaddr).Node() + done := make(chan error, 1) + go func() { + _, err := test.udp.ping(remote) + done <- err + }() + + // Ping answered by WHOAREYOU. + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&whoareyouV5{AuthTag: authTag}) + }) + // Ping answered by WHOAREYOU again. + test.waitPacketOut(func(p *pingV5, addr *net.UDPAddr, authTag []byte) { + test.packetIn(&whoareyouV5{AuthTag: authTag}) + }) + if err := <-done; err != errTimeout { + t.Fatalf("unexpected ping error: %q", err) + } +} + +// This test checks that calls with n replies may take up to n * respTimeout. +func TestUDPv5_callTimeoutReset(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + defer test.close() + + // Launch the request: + var ( + distance = 230 + remote = test.getNode(test.remotekey, test.remoteaddr).Node() + nodes = nodesAtDistance(remote.ID(), distance, 8) + done = make(chan error, 1) + ) + go func() { + _, err := test.udp.findnode(remote, distance) + done <- err + }() + + // Serve two responses, slowly. + test.waitPacketOut(func(p *findnodeV5, addr *net.UDPAddr, authTag []byte) { + time.Sleep(respTimeout - 50*time.Millisecond) + test.packetIn(&nodesV5{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[:4]), + }) + + time.Sleep(respTimeout - 50*time.Millisecond) + test.packetIn(&nodesV5{ + ReqID: p.ReqID, + Total: 2, + Nodes: nodesToRecords(nodes[4:]), + }) + }) + if err := <-done; err != nil { + t.Fatalf("unexpected error: %q", err) + } +} + +// This test checks that lookup works. +func TestUDPv5_lookup(t *testing.T) { + t.Parallel() + test := newUDPV5Test(t) + + // Lookup on empty table returns no nodes. + if results := test.udp.Lookup(lookupTestnet.target.id()); len(results) > 0 { + t.Fatalf("lookup on empty table returned %d results: %#v", len(results), results) + } + + // Ensure the tester knows all nodes in lookupTestnet by IP. + for d, nn := range lookupTestnet.dists { + for i, key := range nn { + n := lookupTestnet.node(d, i) + test.getNode(key, &net.UDPAddr{IP: n.IP(), Port: n.UDP()}) + } + } + + // Seed table with initial node. + fillTable(test.table, []*node{wrapNode(lookupTestnet.node(256, 0))}) + + // Start the lookup. + resultC := make(chan []*enode.Node, 1) + go func() { + resultC <- test.udp.Lookup(lookupTestnet.target.id()) + test.close() + }() + + // Answer lookup packets. + for done := false; !done; { + done = test.waitPacketOut(func(p packetV5, to *net.UDPAddr, authTag []byte) { + recipient, key := lookupTestnet.nodeByAddr(to) + switch p := p.(type) { + case *pingV5: + test.packetInFrom(key, to, &pongV5{ReqID: p.ReqID}) + case *findnodeV5: + nodes := lookupTestnet.neighborsAtDistance(recipient, p.Distance, 3) + response := &nodesV5{ReqID: p.ReqID, Total: 1, Nodes: nodesToRecords(nodes)} + test.packetInFrom(key, to, response) + } + }) + } + + // Verify result nodes. + checkLookupResults(t, lookupTestnet, <-resultC) +} + +// udpV5Test is the framework for all tests above. +// It runs the UDPv5 transport on a virtual socket and allows testing outgoing packets. +type udpV5Test struct { + t *testing.T + pipe *dgramPipe + table *Table + db *enode.DB + udp *UDPv5 + localkey, remotekey *ecdsa.PrivateKey + remoteaddr *net.UDPAddr + nodesByID map[enode.ID]*enode.LocalNode + nodesByIP map[string]*enode.LocalNode +} + +type testCodec struct { + test *udpV5Test + id enode.ID + ctr uint64 +} + +type testCodecFrame struct { + NodeID enode.ID + AuthTag []byte + Ptype byte + Packet rlp.RawValue +} + +func (c *testCodec) encode(toID enode.ID, addr string, p packetV5, _ *whoareyouV5) ([]byte, []byte, error) { + c.ctr++ + authTag := make([]byte, 8) + binary.BigEndian.PutUint64(authTag, c.ctr) + penc, _ := rlp.EncodeToBytes(p) + frame, err := rlp.EncodeToBytes(testCodecFrame{c.id, authTag, p.kind(), penc}) + return frame, authTag, err +} + +func (c *testCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) { + frame, p, err := c.decodeFrame(input) + if err != nil { + return enode.ID{}, nil, nil, err + } + if p.kind() == p_whoareyouV5 { + frame.NodeID = enode.ID{} // match wireCodec behavior + } + return frame.NodeID, nil, p, nil +} + +func (c *testCodec) decodeFrame(input []byte) (frame testCodecFrame, p packetV5, err error) { + if err = rlp.DecodeBytes(input, &frame); err != nil { + return frame, nil, fmt.Errorf("invalid frame: %v", err) + } + switch frame.Ptype { + case p_unknownV5: + dec := new(unknownV5) + err = rlp.DecodeBytes(frame.Packet, &dec) + p = dec + case p_whoareyouV5: + dec := new(whoareyouV5) + err = rlp.DecodeBytes(frame.Packet, &dec) + p = dec + default: + p, err = decodePacketBodyV5(frame.Ptype, frame.Packet) + } + return frame, p, err +} + +func newUDPV5Test(t *testing.T) *udpV5Test { + test := &udpV5Test{ + t: t, + pipe: newpipe(), + localkey: newkey(), + remotekey: newkey(), + remoteaddr: &net.UDPAddr{IP: net.IP{10, 0, 1, 99}, Port: 30303}, + nodesByID: make(map[enode.ID]*enode.LocalNode), + nodesByIP: make(map[string]*enode.LocalNode), + } + test.db, _ = enode.OpenDB("") + ln := enode.NewLocalNode(test.db, test.localkey) + ln.SetStaticIP(net.IP{10, 0, 0, 1}) + ln.Set(enr.UDP(30303)) + test.udp, _ = ListenV5(test.pipe, ln, Config{ + PrivateKey: test.localkey, + Log: testlog.Logger(t, log.LvlTrace), + ValidSchemes: enode.ValidSchemesForTesting, + }) + test.udp.codec = &testCodec{test: test, id: ln.ID()} + test.table = test.udp.tab + test.nodesByID[ln.ID()] = ln + // Wait for initial refresh so the table doesn't send unexpected findnode. + <-test.table.initDone + return test +} + +// handles a packet as if it had been sent to the transport. +func (test *udpV5Test) packetIn(packet packetV5) { + test.t.Helper() + test.packetInFrom(test.remotekey, test.remoteaddr, packet) +} + +// handles a packet as if it had been sent to the transport by the key/endpoint. +func (test *udpV5Test) packetInFrom(key *ecdsa.PrivateKey, addr *net.UDPAddr, packet packetV5) { + test.t.Helper() + + ln := test.getNode(key, addr) + codec := &testCodec{test: test, id: ln.ID()} + enc, _, err := codec.encode(test.udp.Self().ID(), addr.String(), packet, nil) + if err != nil { + test.t.Errorf("%s encode error: %v", packet.name(), err) + } + if test.udp.dispatchReadPacket(addr, enc) { + <-test.udp.readNextCh // unblock UDPv5.dispatch + } +} + +// getNode ensures the test knows about a node at the given endpoint. +func (test *udpV5Test) getNode(key *ecdsa.PrivateKey, addr *net.UDPAddr) *enode.LocalNode { + id := encodePubkey(&key.PublicKey).id() + ln := test.nodesByID[id] + if ln == nil { + db, _ := enode.OpenDB("") + ln = enode.NewLocalNode(db, key) + ln.SetStaticIP(addr.IP) + ln.Set(enr.UDP(addr.Port)) + test.nodesByID[id] = ln + } + test.nodesByIP[string(addr.IP)] = ln + return ln +} + +func (test *udpV5Test) waitPacketOut(validate interface{}) (closed bool) { + test.t.Helper() + fn := reflect.ValueOf(validate) + exptype := fn.Type().In(0) + + dgram, err := test.pipe.receive() + if err == errClosed { + return true + } + if err == errTimeout { + test.t.Fatalf("timed out waiting for %v", exptype) + return false + } + ln := test.nodesByIP[string(dgram.to.IP)] + if ln == nil { + test.t.Fatalf("attempt to send to non-existing node %v", &dgram.to) + return false + } + codec := &testCodec{test: test, id: ln.ID()} + frame, p, err := codec.decodeFrame(dgram.data) + if err != nil { + test.t.Errorf("sent packet decode error: %v", err) + return false + } + if !reflect.TypeOf(p).AssignableTo(exptype) { + test.t.Errorf("sent packet type mismatch, got: %v, want: %v", reflect.TypeOf(p), exptype) + return false + } + fn.Call([]reflect.Value{reflect.ValueOf(p), reflect.ValueOf(&dgram.to), reflect.ValueOf(frame.AuthTag)}) + return false +} + +func (test *udpV5Test) close() { + test.t.Helper() + + test.udp.Close() + test.db.Close() + for id, n := range test.nodesByID { + if id != test.udp.Self().ID() { + n.Database().Close() + } + } + if len(test.pipe.queue) != 0 { + test.t.Fatalf("%d unmatched UDP packets in queue", len(test.pipe.queue)) + } +} diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index 44332640c7..bd066ce857 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -41,6 +41,7 @@ const ( dbNodePrefix = "n:" // Identifier to prefix node entries with dbLocalPrefix = "local:" dbDiscoverRoot = "v4" + dbDiscv5Root = "v5" // These fields are stored per ID and IP, the full key is "n::v4::findfail". // Use nodeItemKey to create those keys. @@ -172,6 +173,16 @@ func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) { return id, ip, field } +func v5Key(id ID, ip net.IP, field string) []byte { + return bytes.Join([][]byte{ + []byte(dbNodePrefix), + id[:], + []byte(dbDiscv5Root), + ip.To16(), + []byte(field), + }, []byte{':'}) +} + // localItemKey returns the key of a local node item. func localItemKey(id ID, field string) []byte { key := append([]byte(dbLocalPrefix), id[:]...) @@ -378,6 +389,16 @@ func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error { return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails)) } +// FindFailsV5 retrieves the discv5 findnode failure counter. +func (db *DB) FindFailsV5(id ID, ip net.IP) int { + return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails))) +} + +// UpdateFindFailsV5 stores the discv5 findnode failure counter. +func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error { + return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails)) +} + // LocalSeq retrieves the local record sequence counter. func (db *DB) localSeq(id ID) uint64 { return db.fetchUint64(localItemKey(id, dbLocalSeq)) diff --git a/p2p/enode/nodedb_test.go b/p2p/enode/nodedb_test.go index 2adb14145d..d2b187896f 100644 --- a/p2p/enode/nodedb_test.go +++ b/p2p/enode/nodedb_test.go @@ -462,3 +462,14 @@ func TestDBExpiration(t *testing.T) { } } } + +// This test checks that expiration works when discovery v5 data is present +// in the database. +func TestDBExpireV5(t *testing.T) { + db, _ := OpenDB("") + defer db.Close() + + ip := net.IP{127, 0, 0, 1} + db.UpdateFindFailsV5(ID{}, ip, 4) + db.expireNodes() +} From c8e9a91672ce27d3c82a579a14d840bd6a04049f Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 8 Apr 2020 10:07:29 +0200 Subject: [PATCH 088/103] build: upgrade to golangci-lint 1.24.0 (#20901) * accounts/scwallet: remove unnecessary uses of fmt.Sprintf * cmd/puppeth: remove unnecessary uses of fmt.Sprintf * p2p/discv5: remove unnecessary use of fmt.Sprintf * whisper/mailserver: remove unnecessary uses of fmt.Sprintf * core: goimports -w tx_pool_test.go * eth/downloader: goimports -w downloader_test.go * build: upgrade to golangci-lint 1.24.0 --- accounts/scwallet/wallet.go | 6 +++--- build/checksums.txt | 32 ++++++++++++++++--------------- build/ci.go | 2 +- cmd/puppeth/module_wallet.go | 4 ++-- core/tx_pool_test.go | 8 ++++++-- eth/downloader/downloader_test.go | 14 ++++++++------ p2p/discv5/net.go | 2 +- whisper/mailserver/mailserver.go | 8 ++++---- 8 files changed, 42 insertions(+), 34 deletions(-) diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go index dd9266cb31..57804dfd11 100644 --- a/accounts/scwallet/wallet.go +++ b/accounts/scwallet/wallet.go @@ -312,15 +312,15 @@ func (w *Wallet) Status() (string, error) { } switch { case !w.session.verified && status.PinRetryCount == 0 && status.PukRetryCount == 0: - return fmt.Sprintf("Bricked, waiting for full wipe"), nil + return "Bricked, waiting for full wipe", nil case !w.session.verified && status.PinRetryCount == 0: return fmt.Sprintf("Blocked, waiting for PUK (%d attempts left) and new PIN", status.PukRetryCount), nil case !w.session.verified: return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil case !status.Initialized: - return fmt.Sprintf("Empty, waiting for initialization"), nil + return "Empty, waiting for initialization", nil default: - return fmt.Sprintf("Online"), nil + return "Online", nil } } diff --git a/build/checksums.txt b/build/checksums.txt index 553d1bbefe..394d32f4e9 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -2,18 +2,20 @@ b13bf04633d4d8cf53226ebeaace8d4d2fd07ae6fa676d0844a688339debec34 go1.13.8.src.tar.gz -478994633b0f5121a7a8d4f368078093e21014fdc7fb2c0ceeae63668c13c5b6 golangci-lint-1.22.2-freebsd-amd64.tar.gz -fcf80824c21567eb0871055711bf9bdca91cf9a081122e2a45f1d11fed754600 golangci-lint-1.22.2-darwin-amd64.tar.gz -cda85c72fc128b2ea0ae05baea7b91172c63aea34064829f65285f1dd536f1e0 golangci-lint-1.22.2-windows-386.zip -94f04899f620aadc9c1524e5482e415efdbd993fa2b2918c4fec2798f030ac1c golangci-lint-1.22.2-linux-armv7.tar.gz -0e72a87d71edde00b6e37e84a99841833ad55fee83e20d21130a7a622b2860bb golangci-lint-1.22.2-freebsd-386.tar.gz -86def2f31fe8fd7c05674104ed2a4bef3e44b7132b93c6ad2f52f198b3d01801 golangci-lint-1.22.2-linux-s390x.tar.gz -b0df4546d36be94e8107733ba290b98dd9b7e41a42d3fb202e87fc7e4ee800c3 golangci-lint-1.22.2-freebsd-armv6.tar.gz -3d45958dcf6a8d195086d2fced1a21db42a90815dfd156d180efa62dbdda6724 golangci-lint-1.22.2-darwin-386.tar.gz -7ee29f35c74fab017a454237990c74d984ce3855960f2c10509238992bb781f9 golangci-lint-1.22.2-linux-arm64.tar.gz -52086ac52a502b68578e58e35d3964f127c16d7a90b9ffcb399a004d055ded51 golangci-lint-1.22.2-linux-386.tar.gz -c2e4df1fab2ae53762f9baac6041503eeeaa968ce38ea41779f7cb526751c667 golangci-lint-1.22.2-windows-amd64.zip -109d38cdc89f271392f5a138d6782657157f9f496fd4801956efa2d0428e0cbe golangci-lint-1.22.2-linux-amd64.tar.gz -f08aae4868d4828c8f07deb0dcd941a1da695b97e58d15e9f3d1d07dcc7a0c84 golangci-lint-1.22.2-linux-armv6.tar.gz -37af03d9c144d527cb15c46a07e6a22d3f62b5491e34ad6f3bfe6bb0b0b597d4 golangci-lint-1.22.2-linux-ppc64le.tar.gz -251a1081d53944f1d5f86216d752837b23079f90605c9d1cc628da1ffcd2e749 golangci-lint-1.22.2-freebsd-armv7.tar.gz +aeaa5498682246b87d0b77ece283897348ea03d98e816760a074058bfca60b2a golangci-lint-1.24.0-windows-amd64.zip +7e854a70d449fe77b7a91583ec88c8603eb3bf96c45d52797dc4ba3f2f278dbe golangci-lint-1.24.0-darwin-386.tar.gz +835101fae192c3a2e7a51cb19d5ac3e1a40b0e311955e89bc21d61de78635979 golangci-lint-1.24.0-linux-armv6.tar.gz +a041a6e6a61c9ff3dbe58673af13ea00c76bcd462abede0ade645808e97cdd6d golangci-lint-1.24.0-windows-386.zip +7cc73eb9ca02b7a766c72b913f8080401862b10e7bb90c09b085415a81f21609 golangci-lint-1.24.0-freebsd-armv6.tar.gz +537bb2186987b5e68ad4e8829230557f26087c3028eb736dea1662a851bad73d golangci-lint-1.24.0-linux-armv7.tar.gz +8cb1bc1e63d8f0d9b71fcb10b38887e1646a6b8a120ded2e0cd7c3284528f633 golangci-lint-1.24.0-linux-mips64.tar.gz +095d3f8bf7fc431739861574d0b58d411a617df2ed5698ce5ae5ecc66d23d44d golangci-lint-1.24.0-freebsd-armv7.tar.gz +e245df27cec3827aef9e7afbac59e92816978ee3b64f84f7b88562ff4b2ac225 golangci-lint-1.24.0-linux-arm64.tar.gz +35d6d5927e19f0577cf527f0e4441dbb37701d87e8cf729c98a510fce397fbf7 golangci-lint-1.24.0-linux-ppc64le.tar.gz +a1ed66353b8ceb575d78db3051491bce3ac1560e469a9bc87e8554486fec7dfe golangci-lint-1.24.0-freebsd-386.tar.gz +241ca454102e909de04957ff8a5754c757cefa255758b3e1fba8a4533d19d179 golangci-lint-1.24.0-linux-amd64.tar.gz +ff488423db01a0ec8ffbe4e1d65ef1be6a8d5e6d7930cf380ce8aaf714125470 golangci-lint-1.24.0-linux-386.tar.gz +f05af56f15ebbcf77663a8955d1e39009b584ce8ea4c5583669369d80353a113 golangci-lint-1.24.0-darwin-amd64.tar.gz +b0096796c0ffcd6c350a2ec006100e7ef5f0597b43a204349d4f997273fb32a7 golangci-lint-1.24.0-freebsd-amd64.tar.gz +c9c2867380e85628813f1f7d1c3cfc6c6f7931e89bea86f567ff451b8cdb6654 golangci-lint-1.24.0-linux-mips64le.tar.gz +2feb97fa61c934aa3eba9bc104ab5dd8fb946791d58e64060e8857e800eeae0b golangci-lint-1.24.0-linux-s390x.tar.gz diff --git a/build/ci.go b/build/ci.go index 8c89aa83ae..ff0451d1b2 100644 --- a/build/ci.go +++ b/build/ci.go @@ -356,7 +356,7 @@ func doLint(cmdline []string) { // downloadLinter downloads and unpacks golangci-lint. func downloadLinter(cachedir string) string { - const version = "1.22.2" + const version = "1.24.0" csdb := build.MustLoadChecksums("build/checksums.txt") base := fmt.Sprintf("golangci-lint-%s-%s-%s", version, runtime.GOOS, runtime.GOARCH) diff --git a/cmd/puppeth/module_wallet.go b/cmd/puppeth/module_wallet.go index ebaa5b6ae1..e3e0862eeb 100644 --- a/cmd/puppeth/module_wallet.go +++ b/cmd/puppeth/module_wallet.go @@ -182,11 +182,11 @@ func checkWallet(client *sshClient, network string) (*walletInfos, error) { // Run a sanity check to see if the devp2p and RPC ports are reachable nodePort := infos.portmap[infos.envvars["NODE_PORT"]] if err = checkPort(client.server, nodePort); err != nil { - log.Warn(fmt.Sprintf("Wallet devp2p port seems unreachable"), "server", client.server, "port", nodePort, "err", err) + log.Warn("Wallet devp2p port seems unreachable", "server", client.server, "port", nodePort, "err", err) } rpcPort := infos.portmap["8545/tcp"] if err = checkPort(client.server, rpcPort); err != nil { - log.Warn(fmt.Sprintf("Wallet RPC port seems unreachable"), "server", client.server, "port", rpcPort, "err", err) + log.Warn("Wallet RPC port seems unreachable", "server", client.server, "port", rpcPort, "err", err) } // Assemble and return the useful infos stats := &walletInfos{ diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index a56151eba6..6ca60a855a 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -857,8 +857,12 @@ func testTransactionQueueGlobalLimiting(t *testing.T, nolocals bool) { // // This logic should not hold for local transactions, unless the local tracking // mechanism is disabled. -func TestTransactionQueueTimeLimiting(t *testing.T) { testTransactionQueueTimeLimiting(t, false) } -func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) { testTransactionQueueTimeLimiting(t, true) } +func TestTransactionQueueTimeLimiting(t *testing.T) { + testTransactionQueueTimeLimiting(t, false) +} +func TestTransactionQueueTimeLimitingNoLocals(t *testing.T) { + testTransactionQueueTimeLimiting(t, true) +} func testTransactionQueueTimeLimiting(t *testing.T, nolocals bool) { // Reduce the eviction interval to a testable amount diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index b23043b1c0..f359b703b5 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -481,12 +481,14 @@ func assertOwnForkedChain(t *testing.T, tester *downloadTester, common int, leng // Tests that simple synchronization against a canonical chain works correctly. // In this test common ancestor lookup should be short circuited and not require // binary searching. -func TestCanonicalSynchronisation62(t *testing.T) { testCanonicalSynchronisation(t, 62, FullSync) } -func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) } -func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } -func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } -func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } -func TestCanonicalSynchronisation64Light(t *testing.T) { testCanonicalSynchronisation(t, 64, LightSync) } +func TestCanonicalSynchronisation62(t *testing.T) { testCanonicalSynchronisation(t, 62, FullSync) } +func TestCanonicalSynchronisation63Full(t *testing.T) { testCanonicalSynchronisation(t, 63, FullSync) } +func TestCanonicalSynchronisation63Fast(t *testing.T) { testCanonicalSynchronisation(t, 63, FastSync) } +func TestCanonicalSynchronisation64Full(t *testing.T) { testCanonicalSynchronisation(t, 64, FullSync) } +func TestCanonicalSynchronisation64Fast(t *testing.T) { testCanonicalSynchronisation(t, 64, FastSync) } +func TestCanonicalSynchronisation64Light(t *testing.T) { + testCanonicalSynchronisation(t, 64, LightSync) +} func testCanonicalSynchronisation(t *testing.T, protocol int, mode SyncMode) { t.Parallel() diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 8a548eeb4c..c912cba7d1 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -641,7 +641,7 @@ loop: } log.Trace("loop stopped") - log.Debug(fmt.Sprintf("shutting down")) + log.Debug("shutting down") if net.conn != nil { net.conn.Close() } diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go index d7af4baae3..d2c3f116a1 100644 --- a/whisper/mailserver/mailserver.go +++ b/whisper/mailserver/mailserver.go @@ -169,7 +169,7 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) f := whisper.Filter{KeySym: s.key} decrypted := request.Open(&f) if decrypted == nil { - log.Warn(fmt.Sprintf("Failed to decrypt p2p request")) + log.Warn("Failed to decrypt p2p request") return false, 0, 0, nil } @@ -181,19 +181,19 @@ func (s *WMailServer) validateRequest(peerID []byte, request *whisper.Envelope) // if you want to check the signature, you can do it here. e.g.: // if !bytes.Equal(peerID, src) { if src == nil { - log.Warn(fmt.Sprintf("Wrong signature of p2p request")) + log.Warn("Wrong signature of p2p request") return false, 0, 0, nil } var bloom []byte payloadSize := len(decrypted.Payload) if payloadSize < 8 { - log.Warn(fmt.Sprintf("Undersized p2p request")) + log.Warn("Undersized p2p request") return false, 0, 0, nil } else if payloadSize == 8 { bloom = whisper.MakeFullNodeBloom() } else if payloadSize < 8+whisper.BloomFilterSize { - log.Warn(fmt.Sprintf("Undersized bloom filter in p2p request")) + log.Warn("Undersized bloom filter in p2p request") return false, 0, 0, nil } else { bloom = decrypted.Payload[8 : 8+whisper.BloomFilterSize] From 6975172d01292e29c9404eb3322c4629802fe02c Mon Sep 17 00:00:00 2001 From: ucwong Date: Wed, 8 Apr 2020 17:26:16 +0800 Subject: [PATCH 089/103] whisper/mailserver : recover corrupt db files before opening (#20891) * whisper/mailserver : recover db file when openfile corrupted * whisper/mailserver : fix db -> s.db * whisper/mailserver : common/errors for dbfile --- whisper/mailserver/mailserver.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/whisper/mailserver/mailserver.go b/whisper/mailserver/mailserver.go index d2c3f116a1..7312bbe23d 100644 --- a/whisper/mailserver/mailserver.go +++ b/whisper/mailserver/mailserver.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" whisper "github.com/ethereum/go-ethereum/whisper/whisperv6" "github.com/syndtr/goleveldb/leveldb" + "github.com/syndtr/goleveldb/leveldb/errors" "github.com/syndtr/goleveldb/leveldb/opt" "github.com/syndtr/goleveldb/leveldb/util" ) @@ -70,6 +71,9 @@ func (s *WMailServer) Init(shh *whisper.Whisper, path string, password string, p } s.db, err = leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 32}) + if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted { + s.db, err = leveldb.RecoverFile(path, nil) + } if err != nil { return fmt.Errorf("open DB file: %s", err) } From 5065cdefffbd401e97dd89075081485c4fa4b774 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 8 Apr 2020 12:00:10 +0200 Subject: [PATCH 090/103] accounts/abi/bind: Refactored topics (#20851) * accounts/abi/bind: refactored topics * accounts/abi/bind: use store function to remove code duplication * accounts/abi/bind: removed unused type defs * accounts/abi/bind: error on tuples in topics * Cosmetic changes to restart travis build Co-authored-by: Guillaume Ballet --- accounts/abi/argument.go | 2 +- accounts/abi/bind/topics.go | 157 +++++++------------------------ accounts/abi/bind/topics_test.go | 16 ++++ accounts/abi/unpack.go | 8 +- 4 files changed, 55 insertions(+), 128 deletions(-) diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index f8ec11b9fa..7f7f505865 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -292,7 +292,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) { retval := make([]interface{}, 0, arguments.LengthNonIndexed()) virtualArgs := 0 for index, arg := range arguments.NonIndexed() { - marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data) + marshalledValue, err := ToGoType((index+virtualArgs)*32, arg.Type, data) if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index c908c92582..7d844c3ae0 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/bind/topics.go @@ -103,151 +103,62 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) { return topics, nil } -// Big batch of reflect types for topic reconstruction. -var ( - reflectHash = reflect.TypeOf(common.Hash{}) - reflectAddress = reflect.TypeOf(common.Address{}) - reflectBigInt = reflect.TypeOf(new(big.Int)) -) - // parseTopics converts the indexed topic fields into actual log field values. -// -// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 -// hashes as the topic value! func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { - // Sanity check that the fields and topics match up - if len(fields) != len(topics) { - return errors.New("topic/field count mismatch") - } - // Iterate over all the fields and reconstruct them from topics - for _, arg := range fields { - if !arg.Indexed { - return errors.New("non-indexed field in topic reconstruction") - } - field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name)) - - // Try to parse the topic back into the fields based on primitive types - switch field.Kind() { - case reflect.Bool: - if topics[0][common.HashLength-1] == 1 { - field.Set(reflect.ValueOf(true)) - } - case reflect.Int8: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(int8(num.Int64()))) - - case reflect.Int16: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(int16(num.Int64()))) - - case reflect.Int32: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(int32(num.Int64()))) - - case reflect.Int64: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(num.Int64())) - - case reflect.Uint8: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(uint8(num.Uint64()))) - - case reflect.Uint16: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(uint16(num.Uint64()))) - - case reflect.Uint32: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(uint32(num.Uint64()))) - - case reflect.Uint64: - num := new(big.Int).SetBytes(topics[0][:]) - field.Set(reflect.ValueOf(num.Uint64())) - - default: - // Ran out of plain primitive types, try custom types - - switch field.Type() { - case reflectHash: // Also covers all dynamic types - field.Set(reflect.ValueOf(topics[0])) - - case reflectAddress: - var addr common.Address - copy(addr[:], topics[0][common.HashLength-common.AddressLength:]) - field.Set(reflect.ValueOf(addr)) - - case reflectBigInt: - num := new(big.Int).SetBytes(topics[0][:]) - if arg.Type.T == abi.IntTy { - if num.Cmp(abi.MaxInt256) > 0 { - num.Add(abi.MaxUint256, big.NewInt(0).Neg(num)) - num.Add(num, big.NewInt(1)) - num.Neg(num) - } - } - field.Set(reflect.ValueOf(num)) - - default: - // Ran out of custom types, try the crazies - switch { - // static byte array - case arg.Type.T == abi.FixedBytesTy: - reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size])) - default: - return fmt.Errorf("unsupported indexed type: %v", arg.Type) - } - } - } - topics = topics[1:] - } - return nil + return parseTopicWithSetter(fields, topics, + func(arg abi.Argument, reconstr interface{}) { + field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name)) + field.Set(reflect.ValueOf(reconstr)) + }) } // parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error { + return parseTopicWithSetter(fields, topics, + func(arg abi.Argument, reconstr interface{}) { + out[arg.Name] = reconstr + }) +} + +// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the +// provided set function. +// +// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256 +// hashes as the topic value! +func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter func(abi.Argument, interface{})) error { // Sanity check that the fields and topics match up if len(fields) != len(topics) { return errors.New("topic/field count mismatch") } // Iterate over all the fields and reconstruct them from topics - for _, arg := range fields { + for i, arg := range fields { if !arg.Indexed { return errors.New("non-indexed field in topic reconstruction") } - + var reconstr interface{} switch arg.Type.T { - case abi.BoolTy: - out[arg.Name] = topics[0][common.HashLength-1] == 1 - case abi.IntTy, abi.UintTy: - out[arg.Name] = abi.ReadInteger(arg.Type.T, arg.Type.Kind, topics[0].Bytes()) - case abi.AddressTy: - var addr common.Address - copy(addr[:], topics[0][common.HashLength-common.AddressLength:]) - out[arg.Name] = addr - case abi.HashTy: - out[arg.Name] = topics[0] - case abi.FixedBytesTy: - array, err := abi.ReadFixedBytes(arg.Type, topics[0].Bytes()) - if err != nil { - return err - } - out[arg.Name] = array + case abi.TupleTy: + return errors.New("tuple type in topic reconstruction") case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy: // Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash // whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash - out[arg.Name] = topics[0] + reconstr = topics[i] case abi.FunctionTy: - if garbage := binary.BigEndian.Uint64(topics[0][0:8]); garbage != 0 { - return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[0].Bytes()) + if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 { + return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes()) } var tmp [24]byte - copy(tmp[:], topics[0][8:32]) - out[arg.Name] = tmp - default: // Not handling tuples - return fmt.Errorf("unsupported indexed type: %v", arg.Type) + copy(tmp[:], topics[i][8:32]) + reconstr = tmp + default: + var err error + reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes()) + if err != nil { + return err + } } - - topics = topics[1:] + // Use the setter function to store the value + setter(arg, reconstr) } return nil diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/bind/topics_test.go index c62f5bab32..df5c8f7e88 100644 --- a/accounts/abi/bind/topics_test.go +++ b/accounts/abi/bind/topics_test.go @@ -84,6 +84,7 @@ func setupTopicsTests() []topicTest { bytesType, _ := abi.NewType("bytes5", "", nil) int8Type, _ := abi.NewType("int8", "", nil) int256Type, _ := abi.NewType("int256", "", nil) + tupleType, _ := abi.NewType("tuple(int256,int8)", "", nil) tests := []topicTest{ { @@ -145,6 +146,21 @@ func setupTopicsTests() []topicTest { }, wantErr: false, }, + { + name: "tuple(int256, int8)", + args: args{ + createObj: func() interface{} { return nil }, + resultObj: func() interface{} { return nil }, + resultMap: func() map[string]interface{} { return make(map[string]interface{}) }, + fields: abi.Arguments{abi.Argument{ + Name: "tupletype", + Type: tupleType, + Indexed: true, + }}, + topics: []common.Hash{}, + }, + wantErr: true, + }, } return tests diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index b2ce26cac2..01aac707f1 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -140,7 +140,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) elemSize := getTypeSize(*t.Elem) for i, j := start, 0; j < size; i, j = i+elemSize, j+1 { - inter, err := toGoType(i, *t.Elem, output) + inter, err := ToGoType(i, *t.Elem, output) if err != nil { return nil, err } @@ -157,7 +157,7 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { retval := reflect.New(t.Type).Elem() virtualArgs := 0 for index, elem := range t.TupleElems { - marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output) + marshalledValue, err := ToGoType((index+virtualArgs)*32, *elem, output) if elem.T == ArrayTy && !isDynamicType(*elem) { // If we have a static array, like [3]uint256, these are coded as // just like uint256,uint256,uint256. @@ -183,9 +183,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) { return retval.Interface(), nil } -// toGoType parses the output bytes and recursively assigns the value of these bytes +// ToGoType parses the output bytes and recursively assigns the value of these bytes // into a go type with accordance with the ABI spec. -func toGoType(index int, t Type, output []byte) (interface{}, error) { +func ToGoType(index int, t Type, output []byte) (interface{}, error) { if index+32 > len(output) { return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32) } From 07d909ff3283fb50a1312a1919787768e54a1c81 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 8 Apr 2020 13:33:12 +0200 Subject: [PATCH 091/103] node: allow websocket and HTTP on the same port (#20810) This change makes it possible to run geth with JSON-RPC over HTTP and WebSocket on the same TCP port. The default port for WebSocket is still 8546. geth --rpc --rpcport 8545 --ws --wsport 8545 This also removes a lot of deprecated API surface from package rpc. The rpc package is now purely about serving JSON-RPC and no longer provides a way to start an HTTP server. --- cmd/clef/main.go | 9 ++- cmd/geth/retesteth.go | 10 ++- graphql/service.go | 14 +++- node/api.go | 2 +- node/endpoints.go | 99 ++++++++++++++++++++++++++ node/node.go | 69 +++++++++++++++--- node/node_test.go | 58 +++++++++++++++ node/rpcstack.go | 159 ++++++++++++++++++++++++++++++++++++++++++ node/rpcstack_test.go | 38 ++++++++++ rpc/endpoints.go | 83 ---------------------- rpc/gzip.go | 66 ------------------ rpc/http.go | 97 -------------------------- rpc/websocket.go | 7 -- 13 files changed, 443 insertions(+), 268 deletions(-) create mode 100644 node/endpoints.go create mode 100644 node/rpcstack.go create mode 100644 node/rpcstack_test.go delete mode 100644 rpc/gzip.go diff --git a/cmd/clef/main.go b/cmd/clef/main.go index f4533a6e85..801c7e9efd 100644 --- a/cmd/clef/main.go +++ b/cmd/clef/main.go @@ -583,9 +583,16 @@ func signer(c *cli.Context) error { vhosts := splitAndTrim(c.GlobalString(utils.RPCVirtualHostsFlag.Name)) cors := splitAndTrim(c.GlobalString(utils.RPCCORSDomainFlag.Name)) + srv := rpc.NewServer() + err := node.RegisterApisFromWhitelist(rpcAPI, []string{"account"}, srv, false) + if err != nil { + utils.Fatalf("Could not register API: %w", err) + } + handler := node.NewHTTPHandlerStack(srv, cors, vhosts) + // start http server httpEndpoint := fmt.Sprintf("%s:%d", c.GlobalString(utils.RPCListenAddrFlag.Name), c.Int(rpcPortFlag.Name)) - listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"account"}, cors, vhosts, rpc.DefaultHTTPTimeouts) + listener, err := node.StartHTTPEndpoint(httpEndpoint, rpc.DefaultHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } diff --git a/cmd/geth/retesteth.go b/cmd/geth/retesteth.go index 05331d12dd..eccc8cd670 100644 --- a/cmd/geth/retesteth.go +++ b/cmd/geth/retesteth.go @@ -890,6 +890,14 @@ func retesteth(ctx *cli.Context) error { vhosts := splitAndTrim(ctx.GlobalString(utils.RPCVirtualHostsFlag.Name)) cors := splitAndTrim(ctx.GlobalString(utils.RPCCORSDomainFlag.Name)) + // register apis and create handler stack + srv := rpc.NewServer() + err := node.RegisterApisFromWhitelist(rpcAPI, []string{"test", "eth", "debug", "web3"}, srv, false) + if err != nil { + utils.Fatalf("Could not register RPC apis: %w", err) + } + handler := node.NewHTTPHandlerStack(srv, cors, vhosts) + // start http server var RetestethHTTPTimeouts = rpc.HTTPTimeouts{ ReadTimeout: 120 * time.Second, @@ -897,7 +905,7 @@ func retesteth(ctx *cli.Context) error { IdleTimeout: 120 * time.Second, } httpEndpoint := fmt.Sprintf("%s:%d", ctx.GlobalString(utils.RPCListenAddrFlag.Name), ctx.Int(rpcPortFlag.Name)) - listener, _, err := rpc.StartHTTPEndpoint(httpEndpoint, rpcAPI, []string{"test", "eth", "debug", "web3"}, cors, vhosts, RetestethHTTPTimeouts) + listener, err := node.StartHTTPEndpoint(httpEndpoint, RetestethHTTPTimeouts, handler) if err != nil { utils.Fatalf("Could not start RPC api: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index 21892402db..a206053024 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" "github.com/graph-gophers/graphql-go" @@ -68,7 +69,18 @@ func (s *Service) Start(server *p2p.Server) error { if s.listener, err = net.Listen("tcp", s.endpoint); err != nil { return err } - go rpc.NewHTTPServer(s.cors, s.vhosts, s.timeouts, s.handler).Serve(s.listener) + // create handler stack and wrap the graphql handler + handler := node.NewHTTPHandlerStack(s.handler, s.cors, s.vhosts) + // make sure timeout values are meaningful + node.CheckTimeouts(&s.timeouts) + // create http server + httpSrv := &http.Server{ + Handler: handler, + ReadTimeout: s.timeouts.ReadTimeout, + WriteTimeout: s.timeouts.WriteTimeout, + IdleTimeout: s.timeouts.IdleTimeout, + } + go httpSrv.Serve(s.listener) log.Info("GraphQL endpoint opened", "url", fmt.Sprintf("http://%s", s.endpoint)) return nil } diff --git a/node/api.go b/node/api.go index 66cd1dde33..1a73d1321d 100644 --- a/node/api.go +++ b/node/api.go @@ -186,7 +186,7 @@ func (api *PrivateAdminAPI) StartRPC(host *string, port *int, cors *string, apis } } - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts); err != nil { + if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, *port), api.node.rpcAPIs, modules, allowedOrigins, allowedVHosts, api.node.config.HTTPTimeouts, api.node.config.WSOrigins); err != nil { return false, err } return true, nil diff --git a/node/endpoints.go b/node/endpoints.go new file mode 100644 index 0000000000..8cd6b4d1c8 --- /dev/null +++ b/node/endpoints.go @@ -0,0 +1,99 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node + +import ( + "net" + "net/http" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" +) + +// StartHTTPEndpoint starts the HTTP RPC endpoint. +func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.Handler) (net.Listener, error) { + // start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, err + } + // make sure timeout values are meaningful + CheckTimeouts(&timeouts) + // Bundle and start the HTTP server + httpSrv := &http.Server{ + Handler: handler, + ReadTimeout: timeouts.ReadTimeout, + WriteTimeout: timeouts.WriteTimeout, + IdleTimeout: timeouts.IdleTimeout, + } + go httpSrv.Serve(listener) + return listener, err +} + +// startWSEndpoint starts a websocket endpoint. +func startWSEndpoint(endpoint string, handler http.Handler) (net.Listener, error) { + // start the HTTP listener + var ( + listener net.Listener + err error + ) + if listener, err = net.Listen("tcp", endpoint); err != nil { + return nil, err + } + wsSrv := &http.Server{Handler: handler} + go wsSrv.Serve(listener) + return listener, err +} + +// checkModuleAvailability checks that all names given in modules are actually +// available API services. It assumes that the MetadataApi module ("rpc") is always available; +// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. +func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available []string) { + availableSet := make(map[string]struct{}) + for _, api := range apis { + if _, ok := availableSet[api.Namespace]; !ok { + availableSet[api.Namespace] = struct{}{} + available = append(available, api.Namespace) + } + } + for _, name := range modules { + if _, ok := availableSet[name]; !ok && name != rpc.MetadataApi { + bad = append(bad, name) + } + } + return bad, available +} + +// CheckTimeouts ensures that timeout values are meaningful +func CheckTimeouts(timeouts *rpc.HTTPTimeouts) { + if timeouts.ReadTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", rpc.DefaultHTTPTimeouts.ReadTimeout) + timeouts.ReadTimeout = rpc.DefaultHTTPTimeouts.ReadTimeout + } + if timeouts.WriteTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", rpc.DefaultHTTPTimeouts.WriteTimeout) + timeouts.WriteTimeout = rpc.DefaultHTTPTimeouts.WriteTimeout + } + if timeouts.IdleTimeout < time.Second { + log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", rpc.DefaultHTTPTimeouts.IdleTimeout) + timeouts.IdleTimeout = rpc.DefaultHTTPTimeouts.IdleTimeout + } +} diff --git a/node/node.go b/node/node.go index 39e15e0a74..1d14317fc1 100644 --- a/node/node.go +++ b/node/node.go @@ -291,17 +291,21 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() return err } - if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts); err != nil { + if err := n.startHTTP(n.httpEndpoint, apis, n.config.HTTPModules, n.config.HTTPCors, n.config.HTTPVirtualHosts, n.config.HTTPTimeouts, n.config.WSOrigins); err != nil { n.stopIPC() n.stopInProc() return err } - if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { - n.stopHTTP() - n.stopIPC() - n.stopInProc() - return err + // if endpoints are not the same, start separate servers + if n.httpEndpoint != n.wsEndpoint { + if err := n.startWS(n.wsEndpoint, apis, n.config.WSModules, n.config.WSOrigins, n.config.WSExposeAll); err != nil { + n.stopHTTP() + n.stopIPC() + n.stopInProc() + return err + } } + // All API endpoints started successfully n.rpcAPIs = apis return nil @@ -359,22 +363,36 @@ func (n *Node) stopIPC() { } // startHTTP initializes and starts the HTTP RPC endpoint. -func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts) error { +func (n *Node) startHTTP(endpoint string, apis []rpc.API, modules []string, cors []string, vhosts []string, timeouts rpc.HTTPTimeouts, wsOrigins []string) error { // Short circuit if the HTTP endpoint isn't being exposed if endpoint == "" { return nil } - listener, handler, err := rpc.StartHTTPEndpoint(endpoint, apis, modules, cors, vhosts, timeouts) + // register apis and create handler stack + srv := rpc.NewServer() + err := RegisterApisFromWhitelist(apis, modules, srv, false) + if err != nil { + return err + } + handler := NewHTTPHandlerStack(srv, cors, vhosts) + // wrap handler in websocket handler only if websocket port is the same as http rpc + if n.httpEndpoint == n.wsEndpoint { + handler = NewWebsocketUpgradeHandler(handler, srv.WebsocketHandler(wsOrigins)) + } + listener, err := StartHTTPEndpoint(endpoint, timeouts, handler) if err != nil { return err } n.log.Info("HTTP endpoint opened", "url", fmt.Sprintf("http://%v/", listener.Addr()), "cors", strings.Join(cors, ","), "vhosts", strings.Join(vhosts, ",")) + if n.httpEndpoint == n.wsEndpoint { + n.log.Info("WebSocket endpoint opened", "url", fmt.Sprintf("ws://%v", listener.Addr())) + } // All listeners booted successfully n.httpEndpoint = endpoint n.httpListener = listener - n.httpHandler = handler + n.httpHandler = srv return nil } @@ -399,7 +417,14 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig if endpoint == "" { return nil } - listener, handler, err := rpc.StartWSEndpoint(endpoint, apis, modules, wsOrigins, exposeAll) + + srv := rpc.NewServer() + handler := srv.WebsocketHandler(wsOrigins) + err := RegisterApisFromWhitelist(apis, modules, srv, exposeAll) + if err != nil { + return err + } + listener, err := startWSEndpoint(endpoint, handler) if err != nil { return err } @@ -407,7 +432,7 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrig // All listeners booted successfully n.wsEndpoint = endpoint n.wsListener = listener - n.wsHandler = handler + n.wsHandler = srv return nil } @@ -664,3 +689,25 @@ func (n *Node) apis() []rpc.API { }, } } + +// RegisterApisFromWhitelist checks the given modules' availability, generates a whitelist based on the allowed modules, +// and then registers all of the APIs exposed by the services. +func RegisterApisFromWhitelist(apis []rpc.API, modules []string, srv *rpc.Server, exposeAll bool) error { + if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { + log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) + } + // Generate the whitelist based on the allowed modules + whitelist := make(map[string]bool) + for _, module := range modules { + whitelist[module] = true + } + // Register all the APIs exposed by the services + for _, api := range apis { + if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { + if err := srv.RegisterName(api.Namespace, api.Service); err != nil { + return err + } + } + } + return nil +} diff --git a/node/node_test.go b/node/node_test.go index c464771cd8..e246731fef 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -19,6 +19,7 @@ package node import ( "errors" "io/ioutil" + "net/http" "os" "reflect" "testing" @@ -27,6 +28,8 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/rpc" + + "github.com/stretchr/testify/assert" ) var ( @@ -597,3 +600,58 @@ func TestAPIGather(t *testing.T) { } } } + +func TestWebsocketHTTPOnSamePort_WebsocketRequest(t *testing.T) { + node := startHTTP(t) + defer node.stopHTTP() + + wsReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) + if err != nil { + t.Error("could not issue new http request ", err) + } + wsReq.Header.Set("Connection", "upgrade") + wsReq.Header.Set("Upgrade", "websocket") + wsReq.Header.Set("Sec-WebSocket-Version", "13") + wsReq.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") + + resp := doHTTPRequest(t, wsReq) + assert.Equal(t, "websocket", resp.Header.Get("Upgrade")) +} + +func TestWebsocketHTTPOnSamePort_HTTPRequest(t *testing.T) { + node := startHTTP(t) + defer node.stopHTTP() + + httpReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:7453", nil) + if err != nil { + t.Error("could not issue new http request ", err) + } + httpReq.Header.Set("Accept-Encoding", "gzip") + + resp := doHTTPRequest(t, httpReq) + assert.Equal(t, "gzip", resp.Header.Get("Content-Encoding")) +} + +func startHTTP(t *testing.T) *Node { + conf := &Config{HTTPPort: 7453, WSPort: 7453} + node, err := New(conf) + if err != nil { + t.Error("could not create a new node ", err) + } + + err = node.startHTTP("127.0.0.1:7453", []rpc.API{}, []string{}, []string{}, []string{}, rpc.HTTPTimeouts{}, []string{}) + if err != nil { + t.Error("could not start http service on node ", err) + } + + return node +} + +func doHTTPRequest(t *testing.T, req *http.Request) *http.Response { + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + t.Error("could not issue a GET request to the given endpoint", err) + } + return resp +} diff --git a/node/rpcstack.go b/node/rpcstack.go new file mode 100644 index 0000000000..793061968d --- /dev/null +++ b/node/rpcstack.go @@ -0,0 +1,159 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package node + +import ( + "compress/gzip" + "io" + "io/ioutil" + "net" + "net/http" + "strings" + "sync" + + "github.com/ethereum/go-ethereum/log" + "github.com/rs/cors" +) + +// NewHTTPHandlerStack returns wrapped http-related handlers +func NewHTTPHandlerStack(srv http.Handler, cors []string, vhosts []string) http.Handler { + // Wrap the CORS-handler within a host-handler + handler := newCorsHandler(srv, cors) + handler = newVHostHandler(vhosts, handler) + return newGzipHandler(handler) +} + +func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { + // disable CORS support if user has not specified a custom CORS configuration + if len(allowedOrigins) == 0 { + return srv + } + c := cors.New(cors.Options{ + AllowedOrigins: allowedOrigins, + AllowedMethods: []string{http.MethodPost, http.MethodGet}, + MaxAge: 600, + AllowedHeaders: []string{"*"}, + }) + return c.Handler(srv) +} + +// virtualHostHandler is a handler which validates the Host-header of incoming requests. +// Using virtual hosts can help prevent DNS rebinding attacks, where a 'random' domain name points to +// the service ip address (but without CORS headers). By verifying the targeted virtual host, we can +// ensure that it's a destination that the node operator has defined. +type virtualHostHandler struct { + vhosts map[string]struct{} + next http.Handler +} + +func newVHostHandler(vhosts []string, next http.Handler) http.Handler { + vhostMap := make(map[string]struct{}) + for _, allowedHost := range vhosts { + vhostMap[strings.ToLower(allowedHost)] = struct{}{} + } + return &virtualHostHandler{vhostMap, next} +} + +// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler +func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + // if r.Host is not set, we can continue serving since a browser would set the Host header + if r.Host == "" { + h.next.ServeHTTP(w, r) + return + } + host, _, err := net.SplitHostPort(r.Host) + if err != nil { + // Either invalid (too many colons) or no port specified + host = r.Host + } + if ipAddr := net.ParseIP(host); ipAddr != nil { + // It's an IP address, we can serve that + h.next.ServeHTTP(w, r) + return + + } + // Not an IP address, but a hostname. Need to validate + if _, exist := h.vhosts["*"]; exist { + h.next.ServeHTTP(w, r) + return + } + if _, exist := h.vhosts[host]; exist { + h.next.ServeHTTP(w, r) + return + } + http.Error(w, "invalid host specified", http.StatusForbidden) +} + +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(ioutil.Discard) + return w + }, +} + +type gzipResponseWriter struct { + io.Writer + http.ResponseWriter +} + +func (w *gzipResponseWriter) WriteHeader(status int) { + w.Header().Del("Content-Length") + w.ResponseWriter.WriteHeader(status) +} + +func (w *gzipResponseWriter) Write(b []byte) (int, error) { + return w.Writer.Write(b) +} + +func newGzipHandler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { + next.ServeHTTP(w, r) + return + } + + w.Header().Set("Content-Encoding", "gzip") + + gz := gzPool.Get().(*gzip.Writer) + defer gzPool.Put(gz) + + gz.Reset(w) + defer gz.Close() + + next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) + }) +} + +// NewWebsocketUpgradeHandler returns a websocket handler that serves an incoming request only if it contains an upgrade +// request to the websocket protocol. If not, serves the the request with the http handler. +func NewWebsocketUpgradeHandler(h http.Handler, ws http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if isWebsocket(r) { + ws.ServeHTTP(w, r) + log.Debug("serving websocket request") + return + } + + h.ServeHTTP(w, r) + }) +} + +// isWebsocket checks the header of an http request for a websocket upgrade request. +func isWebsocket(r *http.Request) bool { + return strings.ToLower(r.Header.Get("Upgrade")) == "websocket" && + strings.ToLower(r.Header.Get("Connection")) == "upgrade" +} diff --git a/node/rpcstack_test.go b/node/rpcstack_test.go new file mode 100644 index 0000000000..9db03181c9 --- /dev/null +++ b/node/rpcstack_test.go @@ -0,0 +1,38 @@ +package node + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/assert" +) + +func TestNewWebsocketUpgradeHandler_websocket(t *testing.T) { + srv := rpc.NewServer() + + handler := NewWebsocketUpgradeHandler(nil, srv.WebsocketHandler([]string{})) + ts := httptest.NewServer(handler) + defer ts.Close() + + responses := make(chan *http.Response) + go func(responses chan *http.Response) { + client := &http.Client{} + + req, _ := http.NewRequest(http.MethodGet, ts.URL, nil) + req.Header.Set("Connection", "upgrade") + req.Header.Set("Upgrade", "websocket") + req.Header.Set("Sec-WebSocket-Version", "13") + req.Header.Set("Sec-Websocket-Key", "SGVsbG8sIHdvcmxkIQ==") + + resp, err := client.Do(req) + if err != nil { + t.Error("could not issue a GET request to the test http server", err) + } + responses <- resp + }(responses) + + response := <-responses + assert.Equal(t, "websocket", response.Header.Get("Upgrade")) +} diff --git a/rpc/endpoints.go b/rpc/endpoints.go index 07aaa44122..9fc0705172 100644 --- a/rpc/endpoints.go +++ b/rpc/endpoints.go @@ -22,89 +22,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// checkModuleAvailability checks that all names given in modules are actually -// available API services. It assumes that the MetadataApi module ("rpc") is always available; -// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints. -func checkModuleAvailability(modules []string, apis []API) (bad, available []string) { - availableSet := make(map[string]struct{}) - for _, api := range apis { - if _, ok := availableSet[api.Namespace]; !ok { - availableSet[api.Namespace] = struct{}{} - available = append(available, api.Namespace) - } - } - for _, name := range modules { - if _, ok := availableSet[name]; !ok && name != MetadataApi { - bad = append(bad, name) - } - } - return bad, available -} - -// StartHTTPEndpoint starts the HTTP RPC endpoint, configured with cors/vhosts/modules. -func StartHTTPEndpoint(endpoint string, apis []API, modules []string, cors []string, vhosts []string, timeouts HTTPTimeouts) (net.Listener, *Server, error) { - if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { - log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available) - } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := NewServer() - for _, api := range apis { - if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return nil, nil, err - } - log.Debug("HTTP registered", "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, nil, err - } - go NewHTTPServer(cors, vhosts, timeouts, handler).Serve(listener) - return listener, handler, err -} - -// StartWSEndpoint starts a websocket endpoint. -func StartWSEndpoint(endpoint string, apis []API, modules []string, wsOrigins []string, exposeAll bool) (net.Listener, *Server, error) { - if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 { - log.Error("Unavailable modules in WS API list", "unavailable", bad, "available", available) - } - // Generate the whitelist based on the allowed modules - whitelist := make(map[string]bool) - for _, module := range modules { - whitelist[module] = true - } - // Register all the APIs exposed by the services - handler := NewServer() - for _, api := range apis { - if exposeAll || whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) { - if err := handler.RegisterName(api.Namespace, api.Service); err != nil { - return nil, nil, err - } - log.Debug("WebSocket registered", "service", api.Service, "namespace", api.Namespace) - } - } - // All APIs registered, start the HTTP listener - var ( - listener net.Listener - err error - ) - if listener, err = net.Listen("tcp", endpoint); err != nil { - return nil, nil, err - } - go NewWSServer(wsOrigins, handler).Serve(listener) - return listener, handler, err -} - // StartIPCEndpoint starts an IPC endpoint. func StartIPCEndpoint(ipcEndpoint string, apis []API) (net.Listener, *Server, error) { // Register all the APIs exposed by the services. diff --git a/rpc/gzip.go b/rpc/gzip.go deleted file mode 100644 index a14fd09d54..0000000000 --- a/rpc/gzip.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rpc - -import ( - "compress/gzip" - "io" - "io/ioutil" - "net/http" - "strings" - "sync" -) - -var gzPool = sync.Pool{ - New: func() interface{} { - w := gzip.NewWriter(ioutil.Discard) - return w - }, -} - -type gzipResponseWriter struct { - io.Writer - http.ResponseWriter -} - -func (w *gzipResponseWriter) WriteHeader(status int) { - w.Header().Del("Content-Length") - w.ResponseWriter.WriteHeader(status) -} - -func (w *gzipResponseWriter) Write(b []byte) (int, error) { - return w.Writer.Write(b) -} - -func newGzipHandler(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { - next.ServeHTTP(w, r) - return - } - - w.Header().Set("Content-Encoding", "gzip") - - gz := gzPool.Get().(*gzip.Writer) - defer gzPool.Put(gz) - - gz.Reset(w) - defer gz.Close() - - next.ServeHTTP(&gzipResponseWriter{ResponseWriter: w, Writer: gz}, r) - }) -} diff --git a/rpc/http.go b/rpc/http.go index 1cf6d90393..b3ce0a5b5e 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -25,14 +25,9 @@ import ( "io" "io/ioutil" "mime" - "net" "net/http" - "strings" "sync" "time" - - "github.com/ethereum/go-ethereum/log" - "github.com/rs/cors" ) const ( @@ -209,37 +204,6 @@ func (t *httpServerConn) RemoteAddr() string { // SetWriteDeadline does nothing and always returns nil. func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } -// NewHTTPServer creates a new HTTP RPC server around an API provider. -// -// Deprecated: Server implements http.Handler -func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv http.Handler) *http.Server { - // Wrap the CORS-handler within a host-handler - handler := newCorsHandler(srv, cors) - handler = newVHostHandler(vhosts, handler) - handler = newGzipHandler(handler) - - // Make sure timeout values are meaningful - if timeouts.ReadTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout) - timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout - } - if timeouts.WriteTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout) - timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout - } - if timeouts.IdleTimeout < time.Second { - log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout) - timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout - } - // Bundle and start the HTTP server - return &http.Server{ - Handler: handler, - ReadTimeout: timeouts.ReadTimeout, - WriteTimeout: timeouts.WriteTimeout, - IdleTimeout: timeouts.IdleTimeout, - } -} - // ServeHTTP serves JSON-RPC requests over HTTP. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Permit dumb empty requests for remote health-checks (AWS) @@ -296,64 +260,3 @@ func validateRequest(r *http.Request) (int, error) { err := fmt.Errorf("invalid content type, only %s is supported", contentType) return http.StatusUnsupportedMediaType, err } - -func newCorsHandler(srv http.Handler, allowedOrigins []string) http.Handler { - // disable CORS support if user has not specified a custom CORS configuration - if len(allowedOrigins) == 0 { - return srv - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - return c.Handler(srv) -} - -// virtualHostHandler is a handler which validates the Host-header of incoming requests. -// The virtualHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, -// since they do in-domain requests against the RPC api. Instead, we can see on the Host-header -// which domain was used, and validate that against a whitelist. -type virtualHostHandler struct { - vhosts map[string]struct{} - next http.Handler -} - -// ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler -func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - // if r.Host is not set, we can continue serving since a browser would set the Host header - if r.Host == "" { - h.next.ServeHTTP(w, r) - return - } - host, _, err := net.SplitHostPort(r.Host) - if err != nil { - // Either invalid (too many colons) or no port specified - host = r.Host - } - if ipAddr := net.ParseIP(host); ipAddr != nil { - // It's an IP address, we can serve that - h.next.ServeHTTP(w, r) - return - - } - // Not an IP address, but a hostname. Need to validate - if _, exist := h.vhosts["*"]; exist { - h.next.ServeHTTP(w, r) - return - } - if _, exist := h.vhosts[host]; exist { - h.next.ServeHTTP(w, r) - return - } - http.Error(w, "invalid host specified", http.StatusForbidden) -} - -func newVHostHandler(vhosts []string, next http.Handler) http.Handler { - vhostMap := make(map[string]struct{}) - for _, allowedHost := range vhosts { - vhostMap[strings.ToLower(allowedHost)] = struct{}{} - } - return &virtualHostHandler{vhostMap, next} -} diff --git a/rpc/websocket.go b/rpc/websocket.go index b7ec56c6a6..6e37b8522d 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -38,13 +38,6 @@ const ( var wsBufferPool = new(sync.Pool) -// NewWSServer creates a new websocket RPC server around an API provider. -// -// Deprecated: use Server.WebsocketHandler -func NewWSServer(allowedOrigins []string, srv *Server) *http.Server { - return &http.Server{Handler: srv.WebsocketHandler(allowedOrigins)} -} - // WebsocketHandler returns a handler that serves JSON-RPC to WebSocket connections. // // allowedOrigins should be a comma-separated list of allowed origin URLs. From fe9ffa5953a28d570478ac61238218a195fe0ccb Mon Sep 17 00:00:00 2001 From: Adam Schmideg Date: Wed, 8 Apr 2020 16:01:11 +0200 Subject: [PATCH 092/103] crypto: improve error messages in LoadECDSA (#20718) This improves error messages when the file is too short or too long. Also rewrite the test for SaveECDSA because LoadECDSA has its own test now. Co-authored-by: Felix Lange --- cmd/geth/accountcmd_test.go | 31 +++++++++++++ crypto/crypto.go | 56 ++++++++++++++++++++---- crypto/crypto_test.go | 87 +++++++++++++++++++++++++++---------- 3 files changed, 144 insertions(+), 30 deletions(-) diff --git a/cmd/geth/accountcmd_test.go b/cmd/geth/accountcmd_test.go index 186db3c6be..3ee4278e5f 100644 --- a/cmd/geth/accountcmd_test.go +++ b/cmd/geth/accountcmd_test.go @@ -88,6 +88,37 @@ Path of the secret key file: .*UTC--.+--[0-9a-f]{40} `) } +func TestAccountImport(t *testing.T) { + tests := []struct{ key, output string }{ + { + key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", + output: "Address: {fcad0b19bb29d4674531d6f115237e16afce377c}\n", + }, + { + key: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef1", + output: "Fatal: Failed to load the private key: invalid character '1' at end of key file\n", + }, + } + for _, test := range tests { + importAccountWithExpect(t, test.key, test.output) + } +} + +func importAccountWithExpect(t *testing.T, key string, expected string) { + dir := tmpdir(t) + keyfile := filepath.Join(dir, "key.prv") + if err := ioutil.WriteFile(keyfile, []byte(key), 0600); err != nil { + t.Error(err) + } + passwordFile := filepath.Join(dir, "password.txt") + if err := ioutil.WriteFile(passwordFile, []byte("foobar"), 0600); err != nil { + t.Error(err) + } + geth := runGeth(t, "account", "import", keyfile, "-password", passwordFile) + defer geth.ExpectExit() + geth.Expect(expected) +} + func TestAccountNewBadRepeat(t *testing.T) { geth := runGeth(t, "account", "new", "--lightkdf") defer geth.ExpectExit() diff --git a/crypto/crypto.go b/crypto/crypto.go index 2869b4c191..1f43ad15e8 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -17,6 +17,7 @@ package crypto import ( + "bufio" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -158,29 +159,67 @@ func FromECDSAPub(pub *ecdsa.PublicKey) []byte { // HexToECDSA parses a secp256k1 private key. func HexToECDSA(hexkey string) (*ecdsa.PrivateKey, error) { b, err := hex.DecodeString(hexkey) - if err != nil { - return nil, errors.New("invalid hex string") + if byteErr, ok := err.(hex.InvalidByteError); ok { + return nil, fmt.Errorf("invalid hex character %q in private key", byte(byteErr)) + } else if err != nil { + return nil, errors.New("invalid hex data for private key") } return ToECDSA(b) } // LoadECDSA loads a secp256k1 private key from the given file. func LoadECDSA(file string) (*ecdsa.PrivateKey, error) { - buf := make([]byte, 64) fd, err := os.Open(file) if err != nil { return nil, err } defer fd.Close() - if _, err := io.ReadFull(fd, buf); err != nil { + + r := bufio.NewReader(fd) + buf := make([]byte, 64) + n, err := readASCII(buf, r) + if err != nil { + return nil, err + } else if n != len(buf) { + return nil, fmt.Errorf("key file too short, want 64 hex characters") + } + if err := checkKeyFileEnd(r); err != nil { return nil, err } - key, err := hex.DecodeString(string(buf)) - if err != nil { - return nil, err + return HexToECDSA(string(buf)) +} + +// readASCII reads into 'buf', stopping when the buffer is full or +// when a non-printable control character is encountered. +func readASCII(buf []byte, r *bufio.Reader) (n int, err error) { + for ; n < len(buf); n++ { + buf[n], err = r.ReadByte() + switch { + case err == io.EOF || buf[n] < '!': + return n, nil + case err != nil: + return n, err + } + } + return n, nil +} + +// checkKeyFileEnd skips over additional newlines at the end of a key file. +func checkKeyFileEnd(r *bufio.Reader) error { + for i := 0; ; i++ { + b, err := r.ReadByte() + switch { + case err == io.EOF: + return nil + case err != nil: + return err + case b != '\n' && b != '\r': + return fmt.Errorf("invalid character %q at end of key file", b) + case i >= 2: + return errors.New("key file too long, want 64 hex characters") + } } - return ToECDSA(key) } // SaveECDSA saves a secp256k1 private key to the given file with @@ -190,6 +229,7 @@ func SaveECDSA(file string, key *ecdsa.PrivateKey) error { return ioutil.WriteFile(file, []byte(k), 0600) } +// GenerateKey generates a new private key. func GenerateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(S256(), rand.Reader) } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 177c19c0cf..f71ae8232a 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -139,39 +139,82 @@ func TestNewContractAddress(t *testing.T) { checkAddr(t, common.HexToAddress("c9ddedf451bc62ce88bf9292afb13df35b670699"), caddr2) } -func TestLoadECDSAFile(t *testing.T) { - keyBytes := common.FromHex(testPrivHex) - fileName0 := "test_key0" - fileName1 := "test_key1" - checkKey := func(k *ecdsa.PrivateKey) { - checkAddr(t, PubkeyToAddress(k.PublicKey), common.HexToAddress(testAddrHex)) - loadedKeyBytes := FromECDSA(k) - if !bytes.Equal(loadedKeyBytes, keyBytes) { - t.Fatalf("private key mismatch: want: %x have: %x", keyBytes, loadedKeyBytes) +func TestLoadECDSA(t *testing.T) { + tests := []struct { + input string + err string + }{ + // good + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\r"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\r\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\n"}, + {input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\r"}, + // bad + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde", + err: "key file too short, want 64 hex characters", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde\n", + err: "key file too short, want 64 hex characters", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeX", + err: "invalid hex character 'X' in private key", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdefX", + err: "invalid character 'X' at end of key file", + }, + { + input: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\n\n\n", + err: "key file too long, want 64 hex characters", + }, + } + + for _, test := range tests { + f, err := ioutil.TempFile("", "loadecdsa_test.*.txt") + if err != nil { + t.Fatal(err) + } + filename := f.Name() + f.WriteString(test.input) + f.Close() + + _, err = LoadECDSA(filename) + switch { + case err != nil && test.err == "": + t.Fatalf("unexpected error for input %q:\n %v", test.input, err) + case err != nil && err.Error() != test.err: + t.Fatalf("wrong error for input %q:\n %v", test.input, err) + case err == nil && test.err != "": + t.Fatalf("LoadECDSA did not return error for input %q", test.input) } } +} - ioutil.WriteFile(fileName0, []byte(testPrivHex), 0600) - defer os.Remove(fileName0) - - key0, err := LoadECDSA(fileName0) +func TestSaveECDSA(t *testing.T) { + f, err := ioutil.TempFile("", "saveecdsa_test.*.txt") if err != nil { t.Fatal(err) } - checkKey(key0) + file := f.Name() + f.Close() + defer os.Remove(file) - // again, this time with SaveECDSA instead of manual save: - err = SaveECDSA(fileName1, key0) + key, _ := HexToECDSA(testPrivHex) + if err := SaveECDSA(file, key); err != nil { + t.Fatal(err) + } + loaded, err := LoadECDSA(file) if err != nil { t.Fatal(err) } - defer os.Remove(fileName1) - - key1, err := LoadECDSA(fileName1) - if err != nil { - t.Fatal(err) + if !reflect.DeepEqual(key, loaded) { + t.Fatal("loaded key not equal to saved key") } - checkKey(key1) } func TestValidateSignatureValues(t *testing.T) { From 1bad8612229ef40998b263ff36b6ecee9a7859e5 Mon Sep 17 00:00:00 2001 From: rene <41963722+renaynay@users.noreply.github.com> Date: Wed, 8 Apr 2020 16:58:27 +0200 Subject: [PATCH 093/103] changed date of rpcstack.go since new file (#20904) --- node/rpcstack.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/rpcstack.go b/node/rpcstack.go index 793061968d..70aa0d4555 100644 --- a/node/rpcstack.go +++ b/node/rpcstack.go @@ -1,4 +1,4 @@ -// Copyright 2015 The go-ethereum Authors +// Copyright 2020 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify From 023b87b9d18b9fbcc659af37d64870c0ba216cdd Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Thu, 9 Apr 2020 10:54:57 +0200 Subject: [PATCH 094/103] accounts/abi/bind: fixed erroneous filtering of negative ints (#20865) * accounts/abi/bind: fixed erroneous packing of negative ints * accounts/abi/bind: added test cases for negative ints in topics * accounts/abi/bind: fixed genIntType for go 1.12 * accounts/abi: minor nitpick --- accounts/abi/bind/topics.go | 25 +++++++---- accounts/abi/bind/topics_test.go | 75 ++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index 7d844c3ae0..7b64f03347 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/bind/topics.go @@ -49,17 +49,13 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) { topic[common.HashLength-1] = 1 } case int8: - blob := big.NewInt(int64(rule)).Bytes() - copy(topic[common.HashLength-len(blob):], blob) + copy(topic[:], genIntType(int64(rule), 1)) case int16: - blob := big.NewInt(int64(rule)).Bytes() - copy(topic[common.HashLength-len(blob):], blob) + copy(topic[:], genIntType(int64(rule), 2)) case int32: - blob := big.NewInt(int64(rule)).Bytes() - copy(topic[common.HashLength-len(blob):], blob) + copy(topic[:], genIntType(int64(rule), 4)) case int64: - blob := big.NewInt(rule).Bytes() - copy(topic[common.HashLength-len(blob):], blob) + copy(topic[:], genIntType(rule, 8)) case uint8: blob := new(big.Int).SetUint64(uint64(rule)).Bytes() copy(topic[common.HashLength-len(blob):], blob) @@ -103,6 +99,19 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) { return topics, nil } +func genIntType(rule int64, size uint) []byte { + var topic [common.HashLength]byte + if rule < 0 { + // if a rule is negative, we need to put it into two's complement. + // extended to common.Hashlength bytes. + topic = [common.HashLength]byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255} + } + for i := uint(0); i < size; i++ { + topic[common.HashLength-i-1] = byte(rule >> (i * 8)) + } + return topic[:] +} + // parseTopics converts the indexed topic fields into actual log field values. func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error { return parseTopicWithSetter(fields, topics, diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/bind/topics_test.go index df5c8f7e88..627e43316e 100644 --- a/accounts/abi/bind/topics_test.go +++ b/accounts/abi/bind/topics_test.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" ) func TestMakeTopics(t *testing.T) { @@ -41,6 +42,80 @@ func TestMakeTopics(t *testing.T) { [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, false, }, + { + "support common hash types in topics", + args{[][]interface{}{{common.Hash{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{1, 2, 3, 4, 5}}}, + false, + }, + { + "support address types in topics", + args{[][]interface{}{{common.Address{1, 2, 3, 4, 5}}}}, + [][]common.Hash{{common.Hash{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5}}}, + false, + }, + { + "support *big.Int types in topics", + args{[][]interface{}{{big.NewInt(1).Lsh(big.NewInt(2), 254)}}}, + [][]common.Hash{{common.Hash{128}}}, + false, + }, + { + "support boolean types in topics", + args{[][]interface{}{ + {true}, + {false}, + }}, + [][]common.Hash{ + {common.Hash{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, 1}}, + {common.Hash{0}}, + }, + false, + }, + { + "support int/uint(8/16/32/64) types in topics", + args{[][]interface{}{ + {int8(-2)}, + {int16(-3)}, + {int32(-4)}, + {int64(-5)}, + {int8(1)}, + {int16(256)}, + {int32(65536)}, + {int64(4294967296)}, + {uint8(1)}, + {uint16(256)}, + {uint32(65536)}, + {uint64(4294967296)}, + }}, + [][]common.Hash{ + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252}}, + {common.Hash{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251}}, + {common.Hash{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, 1}}, + {common.Hash{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, 1, 0}}, + {common.Hash{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, 1, 0, 0}}, + {common.Hash{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, 1, 0, 0, 0, 0}}, + {common.Hash{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, 1}}, + {common.Hash{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, 1, 0}}, + {common.Hash{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, 1, 0, 0}}, + {common.Hash{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, 1, 0, 0, 0, 0}}, + }, + false, + }, + { + "support string types in topics", + args{[][]interface{}{{"hello world"}}}, + [][]common.Hash{{crypto.Keccak256Hash([]byte("hello world"))}}, + false, + }, + { + "support byte slice types in topics", + args{[][]interface{}{{[]byte{1, 2, 3}}}}, + [][]common.Hash{{crypto.Keccak256Hash([]byte{1, 2, 3})}}, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 15540ae9928441a38f8af47d8227347e6bada5ae Mon Sep 17 00:00:00 2001 From: Raw Pong Ghmoa <58883403+q9f@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:09:58 +0200 Subject: [PATCH 095/103] cmd: deprecate --testnet, use named networks instead (#20852) * cmd/utils: make goerli the default testnet * cmd/geth: explicitly rename testnet to ropsten * core: explicitly rename testnet to ropsten * params: explicitly rename testnet to ropsten * cmd: explicitly rename testnet to ropsten * miner: explicitly rename testnet to ropsten * mobile: allow for returning the goerli spec * tests: explicitly rename testnet to ropsten * docs: update readme to reflect changes to the default testnet * mobile: allow for configuring goerli and rinkeby nodes * cmd/geth: revert --testnet back to ropsten and mark as legacy * cmd/util: mark --testnet flag as deprecated * docs: update readme to properly reflect the 3 testnets * cmd/utils: add an explicit deprecation warning on startup * cmd/utils: swap goerli and ropsten in usage * cmd/geth: swap goerli and ropsten in usage * cmd/geth: if running a known preset, log it for convenience * docs: improve readme on usage of ropsten's testnet datadir * cmd/utils: check if legacy `testnet` datadir exists for ropsten * cmd/geth: check for legacy testnet path in console command * cmd/geth: use switch statement for complex conditions in main * cmd/geth: move known preset log statement to the very top * cmd/utils: create new ropsten configurations in the ropsten datadir * cmd/utils: makedatadir should check for existing testnet dir * cmd/geth: add legacy testnet flag to the copy db command * cmd/geth: add legacy testnet flag to the inspect command --- README.md | 40 ++++++++++++++++---------- cmd/devp2p/nodesetcmd.go | 2 +- cmd/geth/chaincmd.go | 7 +++-- cmd/geth/consolecmd.go | 13 +++++++-- cmd/geth/main.go | 27 ++++++++++++++++-- cmd/geth/usage.go | 5 ++-- cmd/utils/flags.go | 57 +++++++++++++++++++++++++------------- core/forkid/forkid_test.go | 4 +-- core/genesis.go | 16 +++++++---- core/genesis_alloc.go | 2 +- core/genesis_test.go | 16 +++++------ miner/stress_ethash.go | 2 +- mobile/geth.go | 20 +++++++++++-- mobile/params.go | 15 ++++++++-- params/bootnodes.go | 6 ++-- params/config.go | 18 ++++++------ tests/difficulty_test.go | 6 ++-- 17 files changed, 173 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 0c744fc202..fbcf3e4ce2 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ This command will: This tool is optional and if you leave it out you can always attach to an already running `geth` instance with `geth attach`. -### A Full node on the Ethereum test network +### A Full node on the Görli test network Transitioning towards developers, if you'd like to play around with creating Ethereum contracts, you almost certainly would like to do that without any real money involved until @@ -81,23 +81,24 @@ network, you want to join the **test** network with your node, which is fully eq the main network, but with play-Ether only. ```shell -$ geth --testnet console +$ geth --goerli console ``` The `console` subcommand has the exact same meaning as above and they are equally -useful on the testnet too. Please see above for their explanations if you've skipped here. +useful on the testnet too. Please, see above for their explanations if you've skipped here. -Specifying the `--testnet` flag, however, will reconfigure your `geth` instance a bit: +Specifying the `--goerli` flag, however, will reconfigure your `geth` instance a bit: + * Instead of connecting the main Ethereum network, the client will connect to the Görli + test network, which uses different P2P bootnodes, different network IDs and genesis + states. * Instead of using the default data directory (`~/.ethereum` on Linux for example), `geth` - will nest itself one level deeper into a `testnet` subfolder (`~/.ethereum/testnet` on + will nest itself one level deeper into a `goerli` subfolder (`~/.ethereum/goerli` on Linux). Note, on OSX and Linux this also means that attaching to a running testnet node requires the use of a custom endpoint since `geth attach` will try to attach to a - production node endpoint by default. E.g. - `geth attach /testnet/geth.ipc`. Windows users are not affected by + production node endpoint by default, e.g., + `geth attach /goerli/geth.ipc`. Windows users are not affected by this. - * Instead of connecting the main Ethereum network, the client will connect to the test - network, which uses different P2P bootnodes, different network IDs and genesis states. *Note: Although there are some internal protective measures to prevent transactions from crossing over between the main network and test network, you should make sure to always @@ -107,17 +108,26 @@ accounts available between them.* ### Full node on the Rinkeby test network -The above test network is a cross-client one based on the ethash proof-of-work consensus -algorithm. As such, it has certain extra overhead and is more susceptible to reorganization -attacks due to the network's low difficulty/security. Go Ethereum also supports connecting -to a proof-of-authority based test network called [*Rinkeby*](https://www.rinkeby.io) -(operated by members of the community). This network is lighter, more secure, but is only -supported by go-ethereum. +Go Ethereum also supports connecting to the older proof-of-authority based test network +called [*Rinkeby*](https://www.rinkeby.io) which is operated by members of the community. ```shell $ geth --rinkeby console ``` +### Full node on the Ropsten test network + +In addition to Görli and Rinkeby, Geth also supports the ancient Ropsten testnet. The +Ropsten test network is based on the Ethash proof-of-work consensus algorithm. As such, +it has certain extra overhead and is more susceptible to reorganization attacks due to the +network's low difficulty/security. + +```shell +$ geth --ropsten console +``` + +*Note: Older Geth configurations store the Ropsten database in the `testnet` subdirectory.* + ### Configuration As an alternative to passing the numerous flags to the `geth` binary, you can also pass a diff --git a/cmd/devp2p/nodesetcmd.go b/cmd/devp2p/nodesetcmd.go index de8e6d45ee..228d3319e3 100644 --- a/cmd/devp2p/nodesetcmd.go +++ b/cmd/devp2p/nodesetcmd.go @@ -164,7 +164,7 @@ func ethFilter(args []string) (nodeFilter, error) { case "goerli": filter = forkid.NewStaticFilter(params.GoerliChainConfig, params.GoerliGenesisHash) case "ropsten": - filter = forkid.NewStaticFilter(params.TestnetChainConfig, params.TestnetGenesisHash) + filter = forkid.NewStaticFilter(params.RopstenChainConfig, params.RopstenGenesisHash) default: return nil, fmt.Errorf("unknown network %q", args[0]) } diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 3e3085fd83..51eb54a0af 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -156,8 +156,10 @@ The export-preimages command export hash preimages to an RLP encoded stream`, utils.CacheFlag, utils.SyncModeFlag, utils.FakePoWFlag, - utils.TestnetFlag, + utils.RopstenFlag, utils.RinkebyFlag, + utils.GoerliFlag, + utils.LegacyTestnetFlag, }, Category: "BLOCKCHAIN COMMANDS", Description: ` @@ -203,9 +205,10 @@ Use "ethereum dump 0" to dump the genesis block.`, utils.DataDirFlag, utils.AncientFlag, utils.CacheFlag, - utils.TestnetFlag, + utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, + utils.LegacyTestnetFlag, utils.SyncModeFlag, }, Category: "BLOCKCHAIN COMMANDS", diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 83614f60e1..8773208adf 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -123,10 +123,19 @@ func remoteConsole(ctx *cli.Context) error { path = ctx.GlobalString(utils.DataDirFlag.Name) } if path != "" { - if ctx.GlobalBool(utils.TestnetFlag.Name) { - path = filepath.Join(path, "testnet") + if ctx.GlobalBool(utils.LegacyTestnetFlag.Name) || ctx.GlobalBool(utils.RopstenFlag.Name) { + // Maintain compatibility with older Geth configurations storing the + // Ropsten database in `testnet` instead of `ropsten`. + legacyPath := filepath.Join(path, "testnet") + if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { + path = legacyPath + } else { + path = filepath.Join(path, "ropsten") + } } else if ctx.GlobalBool(utils.RinkebyFlag.Name) { path = filepath.Join(path, "rinkeby") + } else if ctx.GlobalBool(utils.GoerliFlag.Name) { + path = filepath.Join(path, "goerli") } } endpoint = fmt.Sprintf("%s/geth.ipc", path) diff --git a/cmd/geth/main.go b/cmd/geth/main.go index e276e86c6a..0d734e5660 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -138,7 +138,8 @@ var ( utils.DNSDiscoveryFlag, utils.DeveloperFlag, utils.DeveloperPeriodFlag, - utils.TestnetFlag, + utils.LegacyTestnetFlag, + utils.RopstenFlag, utils.RinkebyFlag, utils.GoerliFlag, utils.VMEnableDebugFlag, @@ -258,10 +259,32 @@ func main() { // prepare manipulates memory cache allowance and setups metric system. // This function should be called before launching devp2p stack. func prepare(ctx *cli.Context) { + // If we're running a known preset, log it for convenience. + switch { + case ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name): + log.Info("Starting Geth on Ropsten testnet...") + log.Warn("The --testnet flag is ambiguous! Please specify one of --goerli, --rinkeby, or --ropsten.") + log.Warn("The generic --testnet flag is deprecated and will be removed in the future!") + + case ctx.GlobalIsSet(utils.RopstenFlag.Name): + log.Info("Starting Geth on Ropsten testnet...") + + case ctx.GlobalIsSet(utils.RinkebyFlag.Name): + log.Info("Starting Geth on Rinkeby testnet...") + + case ctx.GlobalIsSet(utils.GoerliFlag.Name): + log.Info("Starting Geth on Görli testnet...") + + case ctx.GlobalIsSet(utils.DeveloperFlag.Name): + log.Info("Starting Geth in ephemeral dev mode...") + + case !ctx.GlobalIsSet(utils.NetworkIdFlag.Name): + log.Info("Starting Geth on Ethereum mainnet...") + } // If we're a full node on mainnet without --cache specified, bump default cache allowance if ctx.GlobalString(utils.SyncModeFlag.Name) != "light" && !ctx.GlobalIsSet(utils.CacheFlag.Name) && !ctx.GlobalIsSet(utils.NetworkIdFlag.Name) { // Make sure we're not on any supported preconfigured testnet either - if !ctx.GlobalIsSet(utils.TestnetFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { + if !ctx.GlobalIsSet(utils.LegacyTestnetFlag.Name) && !ctx.GlobalIsSet(utils.RopstenFlag.Name) && !ctx.GlobalIsSet(utils.RinkebyFlag.Name) && !ctx.GlobalIsSet(utils.GoerliFlag.Name) && !ctx.GlobalIsSet(utils.DeveloperFlag.Name) { // Nope, we're really on mainnet. Bump that cache up! log.Info("Bumping default cache on mainnet", "provided", ctx.GlobalInt(utils.CacheFlag.Name), "updated", 4096) ctx.GlobalSet(utils.CacheFlag.Name, strconv.Itoa(4096)) diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index 51e9f91848..1601969d14 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -72,9 +72,9 @@ var AppHelpFlagGroups = []flagGroup{ utils.NoUSBFlag, utils.SmartCardDaemonPathFlag, utils.NetworkIdFlag, - utils.TestnetFlag, - utils.RinkebyFlag, utils.GoerliFlag, + utils.RinkebyFlag, + utils.RopstenFlag, utils.SyncModeFlag, utils.ExitWhenSyncedFlag, utils.GCModeFlag, @@ -245,6 +245,7 @@ var AppHelpFlagGroups = []flagGroup{ { Name: "DEPRECATED", Flags: []cli.Flag{ + utils.LegacyTestnetFlag, utils.LightLegacyServFlag, utils.LightLegacyPeersFlag, utils.MinerLegacyThreadsFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index dbefc9d935..00bbaf50e4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -162,20 +162,24 @@ var ( } NetworkIdFlag = cli.Uint64Flag{ Name: "networkid", - Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten, 4=Rinkeby)", + Usage: "Network identifier (integer, 1=Frontier, 3=Ropsten, 4=Rinkeby, 5=Görli)", Value: eth.DefaultConfig.NetworkId, } - TestnetFlag = cli.BoolFlag{ + LegacyTestnetFlag = cli.BoolFlag{ // TODO(q9f): Remove after Ropsten is discontinued. Name: "testnet", - Usage: "Ropsten network: pre-configured proof-of-work test network", + Usage: "Pre-configured test network (Deprecated: Please choose one of --goerli, --rinkeby, or --ropsten.)", + } + GoerliFlag = cli.BoolFlag{ + Name: "goerli", + Usage: "Görli network: pre-configured proof-of-authority test network", } RinkebyFlag = cli.BoolFlag{ Name: "rinkeby", Usage: "Rinkeby network: pre-configured proof-of-authority test network", } - GoerliFlag = cli.BoolFlag{ - Name: "goerli", - Usage: "Görli network: pre-configured proof-of-authority test network", + RopstenFlag = cli.BoolFlag{ + Name: "ropsten", + Usage: "Ropsten network: pre-configured proof-of-work test network", } DeveloperFlag = cli.BoolFlag{ Name: "dev", @@ -759,7 +763,6 @@ var ( Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", Value: "host=localhost", } - EWASMInterpreterFlag = cli.StringFlag{ Name: "vm.ewasm", Usage: "External ewasm configuration (default = built-in interpreter)", @@ -774,11 +777,17 @@ var ( // MakeDataDir retrieves the currently requested data directory, terminating // if none (or the empty string) is specified. If the node is starting a testnet, -// the a subdirectory of the specified datadir will be used. +// then a subdirectory of the specified datadir will be used. func MakeDataDir(ctx *cli.Context) string { if path := ctx.GlobalString(DataDirFlag.Name); path != "" { - if ctx.GlobalBool(TestnetFlag.Name) { - return filepath.Join(path, "testnet") + if ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name) { + // Maintain compatibility with older Geth configurations storing the + // Ropsten database in `testnet` instead of `ropsten`. + legacyPath := filepath.Join(path, "testnet") + if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { + return legacyPath + } + return filepath.Join(path, "ropsten") } if ctx.GlobalBool(RinkebyFlag.Name) { return filepath.Join(path, "rinkeby") @@ -836,8 +845,8 @@ func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) { } else { urls = splitAndTrim(ctx.GlobalString(BootnodesFlag.Name)) } - case ctx.GlobalBool(TestnetFlag.Name): - urls = params.TestnetBootnodes + case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + urls = params.RopstenBootnodes case ctx.GlobalBool(RinkebyFlag.Name): urls = params.RinkebyBootnodes case ctx.GlobalBool(GoerliFlag.Name): @@ -1240,8 +1249,16 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) case ctx.GlobalBool(DeveloperFlag.Name): cfg.DataDir = "" // unless explicitly requested, use memory databases - case ctx.GlobalBool(TestnetFlag.Name) && cfg.DataDir == node.DefaultDataDir(): - cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet") + case (ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name)) && cfg.DataDir == node.DefaultDataDir(): + // Maintain compatibility with older Geth configurations storing the + // Ropsten database in `testnet` instead of `ropsten`. + legacyPath := filepath.Join(node.DefaultDataDir(), "testnet") + if _, err := os.Stat(legacyPath); !os.IsNotExist(err) { + log.Warn("Using the deprecated `testnet` datadir. Future versions will store the Ropsten chain in `ropsten`.") + cfg.DataDir = legacyPath + } else { + cfg.DataDir = filepath.Join(node.DefaultDataDir(), "ropsten") + } case ctx.GlobalBool(RinkebyFlag.Name) && cfg.DataDir == node.DefaultDataDir(): cfg.DataDir = filepath.Join(node.DefaultDataDir(), "rinkeby") case ctx.GlobalBool(GoerliFlag.Name) && cfg.DataDir == node.DefaultDataDir(): @@ -1441,7 +1458,7 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) { // SetEthConfig applies eth-related command line flags to the config. func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Avoid conflicting network flags - CheckExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag, GoerliFlag) + CheckExclusive(ctx, DeveloperFlag, LegacyTestnetFlag, RopstenFlag, RinkebyFlag, GoerliFlag) CheckExclusive(ctx, LightLegacyServFlag, LightServeFlag, SyncModeFlag, "light") CheckExclusive(ctx, DeveloperFlag, ExternalSignerFlag) // Can't use both ephemeral unlocked and external signer @@ -1521,12 +1538,12 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { // Override any default configs for hard coded networks. switch { - case ctx.GlobalBool(TestnetFlag.Name): + case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 3 } - cfg.Genesis = core.DefaultTestnetGenesisBlock() - setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.TestnetGenesisHash]) + cfg.Genesis = core.DefaultRopstenGenesisBlock() + setDNSDiscoveryDefaults(cfg, params.KnownDNSNetworks[params.RopstenGenesisHash]) case ctx.GlobalBool(RinkebyFlag.Name): if !ctx.GlobalIsSet(NetworkIdFlag.Name) { cfg.NetworkId = 4 @@ -1708,8 +1725,8 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database { func MakeGenesis(ctx *cli.Context) *core.Genesis { var genesis *core.Genesis switch { - case ctx.GlobalBool(TestnetFlag.Name): - genesis = core.DefaultTestnetGenesisBlock() + case ctx.GlobalBool(LegacyTestnetFlag.Name) || ctx.GlobalBool(RopstenFlag.Name): + genesis = core.DefaultRopstenGenesisBlock() case ctx.GlobalBool(RinkebyFlag.Name): genesis = core.DefaultRinkebyGenesisBlock() case ctx.GlobalBool(GoerliFlag.Name): diff --git a/core/forkid/forkid_test.go b/core/forkid/forkid_test.go index f3364c3d69..da56530214 100644 --- a/core/forkid/forkid_test.go +++ b/core/forkid/forkid_test.go @@ -65,8 +65,8 @@ func TestCreation(t *testing.T) { }, // Ropsten test cases { - params.TestnetChainConfig, - params.TestnetGenesisHash, + params.RopstenChainConfig, + params.RopstenGenesisHash, []testcase{ {0, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Unsynced, last Frontier, Homestead and first Tangerine block {9, ID{Hash: checksumToBytes(0x30c7ddbc), Next: 10}}, // Last Tangerine block diff --git a/core/genesis.go b/core/genesis.go index 06d347f736..b31980f0a3 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -246,8 +246,12 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { return g.Config case ghash == params.MainnetGenesisHash: return params.MainnetChainConfig - case ghash == params.TestnetGenesisHash: - return params.TestnetChainConfig + case ghash == params.RopstenGenesisHash: + return params.RopstenChainConfig + case ghash == params.RinkebyGenesisHash: + return params.RinkebyChainConfig + case ghash == params.GoerliGenesisHash: + return params.GoerliChainConfig default: return params.AllEthashProtocolChanges } @@ -347,15 +351,15 @@ func DefaultGenesisBlock() *Genesis { } } -// DefaultTestnetGenesisBlock returns the Ropsten network genesis block. -func DefaultTestnetGenesisBlock() *Genesis { +// DefaultRopstenGenesisBlock returns the Ropsten network genesis block. +func DefaultRopstenGenesisBlock() *Genesis { return &Genesis{ - Config: params.TestnetChainConfig, + Config: params.RopstenChainConfig, Nonce: 66, ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"), GasLimit: 16777216, Difficulty: big.NewInt(1048576), - Alloc: decodePrealloc(testnetAllocData), + Alloc: decodePrealloc(ropstenAllocData), } } diff --git a/core/genesis_alloc.go b/core/genesis_alloc.go index 81477a6f6b..9d9417222c 100644 --- a/core/genesis_alloc.go +++ b/core/genesis_alloc.go @@ -22,6 +22,6 @@ package core // nolint: misspell const mainnetAllocData = "\xfa\x04]X\u0793\r\x83b\x011\x8e\u0189\x9agT\x06\x908'\x80t2\x80\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0793\x17bC\x0e\xa9\u00e2nWI\xaf\xdbp\xda_x\u077b\x8c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0793\x1d\x14\x80K9\x9cn\xf8\x0edWoev`\x80O\xec\v\x89\u3bb5sr@\xa0\x00\x00\u07932@5\x87\x94{\x9f\x15b*h\xd1\x04\xd5M3\xdb\xd1\u0349\x043\x87Oc,\xc6\x00\x00\u0793I~\x92\xcd\xc0\xe0\xb9c\xd7R\xb2)j\u02c7\u0682\x8b$\x89\n\x8fd\x9f\xe7\xc6\x18\x00\x00\u0793K\xfb\xe1Tk\xc6\xc6[\\~\xaaU0K8\xbb\xfe\xc6\u04c9lk\x93[\x8b\xbd@\x00\x00\u0793Z\x9c\x03\xf6\x9d\x17\xd6l\xbb\x8a\xd7!\x00\x8a\x9e\xbb\xb86\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u0793]\x0e\xe8\x15^\xc0\xa6\xffh\bU,\xa5\xf1k\xb5\xbe2:\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u0793v\"\xd8J#K\xb8\xb0x#\x0f\u03c4\xb6z\u9a2c\xae\x89%\xe1\xccQ\x99R\xf8\x00\x00\u0793{\x9f\xc3\x19\x05\xb4\x99K\x04\xc9\xe2\xcf\xdc^'pP?B\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u0793\u007fJ#\xca\x00\xcd\x04=%\u0088\x8c\x1a\xa5h\x8f\x81\xa3D\x89)\xf0\xa9[\xfb\xf7)\x00\x00\u0793\x869\u06bb\u3bac\x88{]\xc0\xe4>\x13\xbc\u0487\xd7l\x89\x10\xd0\xe3\xc8}n,\x00\x00\u0793\x89P\x86y\xab\xf8\xc7\x1b\xf6x\x16\x87\x12\x0e>j\x84XM\x89a\x94\x04\x9f0\xf7 \x00\x00\u0793\x8f\xc7\u02ed\xff\xbd\r\u007f\xe4O\x8d\xfd`\xa7\x9dr\x1a\x1c\x9c\x8965\u026d\xc5\u07a0\x00\x00\u0793\x95`\xa3\xdebxh\xf9\x1f\xa8\xbf\xe1\xc1\xb7\xaf\xaf\b\x18k\x89\x1cg\xf5\xf7\xba\xa0\xb0\x00\x00\u0793\x96\x97G\xf7\xa5\xb3\x06E\xfe\x00\xe4I\x01CZ\xce$\xcc7\x89\\(=A\x03\x94\x10\x00\x00\u0793\x9am}\xb3&g\x9bw\xc9\x03\x91\xa7Gm#\x8f;\xa3>\x89\n\xdaUGK\x814\x00\x00\u0793\x9e\xef\n\b\x86\x05n?i!\x18S\xb9\xb7E\u007f7\x82\u4262\xa8x\x06\x9b(\xe0\x00\x00\u0793\x9f\xdb\xf4N\x1fJcb\xb7i\u00daG_\x95\xa9l+\u01c9\x1e\x93\x12\x83\xcc\xc8P\x00\x00\u07d3\xa5y\u007fR\xc9\u054f\x18\x9f6\xb1\xd4]\x1b\xf6\x04\x1f/k\x8a\x01'\u0473F\x1a\xcd\x1a\x00\x00\u0793\xaaS\x81\xb2\x13\x8e\xbe\xff\xc1\x91\xd5\xd8\u00d1u;p\x98\u04895\xab\xb0\x9f\xfe\u07b6\x80\x00\u0793\xaa\xda%\xea\"\x86p\x9a\xbbB-A\x92?\u04c0\xcd\x04\u01c9#=\xf3)\x9far\x00\x00\u0793\xac\xbf\xb2\xf2ZT\x85\xc79\xefp\xa4N\xee\xeb|e\xa6o\x89\x05k\xc7^-c\x10\x00\x00\u07d3\xac\xc6\xf0\x82\xa4B\x82\x87d\xd1\x1fX\u0589J\xe4\b\xf0s\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u0793\xb2w\xb0\x99\xa8\xe8f\xca\x0e\xc6[\u02c7(O\xd1B\xa5\x82\x89j\xcb=\xf2~\x1f\x88\x00\x00\u0753\xbd\xd4\x01:\xa3\x1c\x04al+\xc9x_'\x88\xf9\x15g\x9b\x88\xb9\xf6]\x00\xf6<\x00\x00\u0793\xc2}c\xfd\xe2K\x92\xee\x8a\x1e~\xd5\xd2m\x8d\xc5\xc8;\x03\x89lk\x93[\x8b\xbd@\x00\x00\u0553\xc4\x0f\xe2\tT#P\x9b\x9f\u0677T21X\xaf#\x10\xf3\x80\u0793\xd7^\xd6\fwO\x8b:ZQs\xfb\x183\xadq\x05\xa2\u0649l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d3\u05cd\x89\xb3_G'\x16\xec\xea\xfe\xbf`\x05'\u04e1\xf9i\x8a\x05\xe0T\x9c\x962\xe1\xd8\x00\x00\u0793\xda\xe2{5\v\xae \xc5e!$\xaf]\x8b\\\xba\x00\x1e\xc1\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u0793\xdc\x01\xcb\xf4Ix\xa4.\x8d\xe8\xe46\xed\xf9B\x05\u03f6\xec\x89O\x0f\xeb\xbc\u068c\xb4\x00\x00\u07d3\u607c-\x10\xdbb\u0785\x84\x83$I\"P4\x8e\x90\xbf\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d3\xf4c\xe17\xdc\xf6%\xfb\U000fc8de\u0298\u04b9h\xcf\u007f\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\x01\x00\a9K\x8bue\xa1e\x8a\xf8\x8c\xe4cI\x915\u05b7\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x01\r\xf1\xdfK\xed#v\r-\x1c\x03x\x15\x86\xdd\xf7\x91\x8eT\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\x01\x0fJ\x98\u07e1\xd9y\x9b\xf5\u01d6\xfbU\x0e\xfb\xe7\xec\xd8w\x8a\x01\xb2\xf2\x92#b\x92\xc7\x00\x00\u07d4\x01\x15PW\x00/k\r\x18\xac\xb98\x8d;\xc8\x12\x9f\x8fz \x89H\xa4\xa9\x0f\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x01k`\xbbmg\x92\x8c)\xfd\x03\x13\xc6f\u068f\x16\x98\xd9\u0149lk\x93[\x8b\xbd@\x00\x00\u07d4\x01l\x85\xe1a;\x90\x0f\xa3W\xb8(;\x12\x0ee\xae\xfc\xdd\b\x89+]\x97\x84\xa9|\xd5\x00\x00\u07d4\x01\x84\x92H\x8b\xa1\xa2\x924\"G\xb3\x18U\xa5Y\x05\xfe\xf2i\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x01\x8f \xa2{'\xecD\x1a\xf7#\xfd\x90\x99\xf2\u02f7\x9dbc\x89uy*\x8a\xbd\xef|\x00\x00\u07d4\x01\x91\xebT~{\xf6\x97k\x9b\x1bWuFv\x1d\xe6V\"\xe2\x89lkLM\xa6\u077e\x00\x00\u07d4\x01\x9dp\x95y\xffK\xc0\x9f\xdc\xdd\xe41\xdc\x14G\xd2\xc2`\xbc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x01\xa2Z_Z\xf0\x16\x9b0\x86L;\xe4\xd7V<\xcdD\xf0\x9e\x89M\x85<\x8f\x89\b\x98\x00\x00\xe0\x94\x01\xa7\xd9\xfa}\x0e\xb1\x18\\g\xe5M\xa8<.u\xdbi\u37ca\x01\x9dJ\xdd\xd0\u063c\x96\x00\x00\u07d4\x01\xa8\x18\x13ZAB\x10\xc3|b\xb6%\xac\xa1\xa5F\x11\xac6\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\x01\xb1\xca\xe9\x1a;\x95Y\xaf\xb3<\xdcmh\x94B\xfd\xbf\xe07\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x01\xb5\xb5\xbcZ\x11\u007f\xa0\x8b4\xed\x1d\xb9D\x06\bYz\xc5H\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x01\xbb\xc1Og\xaf\x069\xaa\xb1D\x1ej\b\xd4\xceqb\t\x0f\x89F\xfc\xf6\x8f\xf8\xbe\x06\x00\x00\xe0\x94\x01\xd08\x15\xc6\x1fAkq\xa2a\n-\xab\xa5\x9f\xf6\xa6\xde[\x8a\x02\x05\xdf\xe5\v\x81\xc8.\x00\x00\u07d4\x01\u0559\xee\r_\x8c8\xab-9.,e\xb7L<\xe3\x18 \x89\x1b\xa5\xab\xf9\xe7y8\x00\x00\u07d4\x01\xe4\x05!\x12%0\u066c\x91\x11<\x06\xa0\x19\vmc\x85\v\x89Hz\x9a0E9D\x00\x00\u07d4\x01\xe6A]X{\x06T\x90\xf1\xed\u007f!\xd6\xe0\xf3\x86\xeegG\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x01\xe8d\xd3Tt\x1bB>oB\x85\x17$F\x8ct\xf5\xaa\x9c\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x01\xed_\xba\x8d.\xabg:\xec\x04-0\xe4\xe8\xa6\x11\xd8\xc5Z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x01\xfb\x8e\xc1$%\xa0O\x81>F\xc5L\x05t\x8c\xa6\xb2\x9a\xa9\x89\x0e\x15s\x03\x85F|\x00\x00\u07d4\x01\xff\x1e\xb1\u07adP\xa7\xf2\xf9c\x8f\xde\xe6\xec\xcf:{*\u0209 \x86\xac5\x10R`\x00\x00\u07d4\x02\x03b\u00ed\xe8x\u0290\u05b2\u0609\xa4\xccU\x10\xee\xd5\xf3\x898\x88\xe8\xb3\x11\xad\xb3\x80\x00\u07d4\x02\x03\xae\x01\xd4\xc4\x1c\xae\x18e\xe0K\x1f[S\xcd\xfa\xec\xae1\x896\x89\xcd\u03b2\x8c\xd7\x00\x00\u07d4\x02\b\x93a\xa3\xfetQ\xfb\x1f\x87\xf0\x1a-\x86fS\xdc\v\a\x89\x02*\xc7H2\xb5\x04\x00\x00\u07d4\x02\x1fi\x04=\xe8\x8cI\x17\xca\x10\xf1\x84(\x97\xee\xc0X\x9c|\x89kD\u03f8\x14\x87\xf4\x00\x00\u07d4\x02)\x0f\xb5\xf9\xa5\x17\xf8(E\xac\xde\xca\x0f\xc8F\x03\x9b\xe23\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x029\xb4\xf2\x1f\x8e\x05\xcd\x01Q++\xe7\xa0\xe1\x8am\x97F\a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x02Gr\x12\xff\xddu\xe5\x15VQ\xb7e\x06\xb1dfq\xa1\xeb\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x02J\t\x8a\xe7\x02\xbe\xf5@l\x9c\"\xb7\x8b\xd4\xeb,\u01e2\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x02K\xdd,{\xfdP\x0e\xe7@O\u007f\xb3\xe9\xfb1\xdd \xfb\u0449\t\xc2\x00vQ\xb2P\x00\x00\u07d4\x02Sg\x96\x03\x04\xbe\xee4Y\x11\x18\xe9\xac-\x13X\xd8\x02\x1a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x02V\x14\x9f[Pc\xbe\xa1N\x15f\x1f\xfbX\xf9\xb4Y\xa9W\x89&)\xf6n\fS\x00\x00\x00\u07d4\x02`=z;\xb2\x97\xc6|\x87~]4\xfb\u0579\x13\xd4\xc6:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x02a\xad:\x17*\xbf\x13\x15\xf0\xff\xec2p\x98j\x84\t\xcb%\x89\v\b!;\u03cf\xfe\x00\x00\u07d4\x02d2\xaf7\xdcQ\x13\xf1\xf4mH\nM\u0c80R#~\x89\x13I\xb7\x86\xe4\v\xfc\x00\x00\u07d4\x02f\xab\x1ck\x02\x16#\v\x93\x95D=_\xa7^hEh\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x02u\x1d\u018c\xb5\xbdsp'\xab\xf7\u0777s\x90\xcdw\xc1k\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x02w\x8e9\x0f\xa1u\x10\xa3B\x8a\xf2\x87\fBsT}8l\x8a\x03lw\x80\x18\x8b\xf0\xef\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x037|\x0eUkd\x01\x03(\x9aa\x89\u1baecI4g\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03IcM\u00a9\xe8\f?w!\xee+PF\xae\xaa\xed\xfb\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x03U\xbc\xac\xbd!D\x1e\x95\xad\xee\xdc0\xc1r\x18\u0224\b\u0389\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x03n\xef\xf5\xba\x90\xa6\x87\x9a\x14\xdf\xf4\xc5\x04;\x18\xca\x04`\u0249\x05k\xc7^-c\x10\x00\x00\xe0\x94\x03qKA\u04a6\xf7Q\x00\x8e\xf8\xddM+)\xae\u02b8\xf3n\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x03r\xe8RX.\t44J\x0f\xed!x0M\xf2]F(\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03r\xeeU\b\xbf\x81c\xed(N^\xef\x94\xceMsg\xe5\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x03}\xd0V\xe7\xfd\xbdd\x1d\xb5\xb6\xbe\xa2\xa8x\n\x83\xfa\u1009\a\x96\xe3\xea?\x8a\xb0\x00\x00\xe0\x94\x03\x83#\xb1\x84\xcf\xf7\xa8*\xe2\u1f67y?\xe41\x9c\xa0\xbf\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x03\x87y\xca-\xbef>c\xdb?\xe7V\x83\xea\x0e\xc6.#\x83\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\x03\x8eE\xea\xdd=\x88\xb8\u007f\xe4\u06b0fh\x05\"\xf0\xdf\xc8\xf9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x03\x92T\x9ar\u007f\x81eT)\u02d2\x8bR\x9f%\xdfM\x13\x85\x89\x01lC\xa0\xee\xa0t\x00\x00\u07d4\x03\x94\xb9\x0f\xad\xb8`O\x86\xf4?\xc1\xe3]1$\xb3*Y\x89\x89)j\xa1@'\x8ep\x00\x00\u0794\x03\x9ezN\xbc(N,\xcdB\xb1\xbd\xd6\v\xd6Q\x1c\x0fw\x06\x88\xf0\x15\xf2W6B\x00\x00\u07d4\x03\x9e\xf1\xceR\xfeyc\xf1f\u0562u\u0131\x06\x9f\xe3\xa82\x89\x15\xaf9\u4ab2t\x00\x00\u07d4\x03\xa2l\xfcL\x181op\u055e\x9e\x1ay\xee>\x8b\x96/L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x03\xaab(\x81#m\xd0\xf4\x94\f$\xc3$\xff\x8b{~!\x86\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4\x03\xafz\xd9\xd5\"<\xf7\xc8\xc1? \xdfg\xeb\xe5\xff\u017bA\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x03\xb0\xf1|\xd4F\x9d\xdc\u03f7\xdai~\x82\xa9\x1a_\x9ewt\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x03\xb4\x1bQ\xf4\x1d\xf2\r\xd2y\xba\xe1\x8c\x12w_w\xadw\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x03\xbe[F)\xae\xfb\xbc\xab\x9d\xe2m9Wl\xb7\xf6\x91\xd7d\x89\n\xdf0\xbap\u0217\x00\x00\u07d4\x03\xc6G\xa9\xf9)\xb0x\x1f\xe9\xae\x01\u02a3\xe1\x83\xe8vw~\x89\x18*\xb7\xc2\f\xe5$\x00\x00\u07d4\x03\xc9\x1d\x92\x946\x03\xe7R >\x054\x0eV`\x13\xb9\x00E\x89+|\xc2\xe9\xc3\"\\\x00\x00\u07d4\x03\xcbLOE\x16\xc4\xffy\xa1\xb6$O\xbfW.\x1c\u007f\xeay\x89\x94\x89#z\u06daP\x00\x00\u07d4\x03\u02d8\u05ec\xd8\x17\u079d\x88m\"\xfa\xb3\xf1\xb5}\x92\xa6\b\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x03\u031d-!\xf8k\x84\xac\x8c\xea\xf9q\u06e7\x8a\x90\xe6%p\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\x03\xd1rO\xd0\x0eT\xaa\xbc\xd2\xde*\x91\xe8F+\x10I\xdd:\x89\x8f\x1d\\\x1c\xae7@\x00\x00\u07d4\x03\xde\xdf\xcd\v<.\x17\xc7\x05\xda$\x87\x90\uf626\xbdWQ\x89Hz\x9a0E9D\x00\x00\u07d4\x03\u8c04SuW\xe7\t\xea\xe2\xe1\u1966\xbc\xe1\xef\x83\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x03\xeam&\u0400\xe5z\xee9&\xb1\x8e\x8e\xd7:N[(&\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x03\xeb<\xb8`\xf6\x02\x8d\xa5T\xd3D\xa2\xbbZP\n\xe8\xb8o\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x03\xeb\xc6?\xdaf`\xa4e\x04^#_\xben\\\xf1\x95s_\x89\a\xb0l\xe8\u007f\xddh\x00\x00\xe0\x94\x03\xefj\xd2\x0f\xf7\xbdO\x00+\xacX\xd4uD\u03c7\x9a\xe7(\x8a\x01u\xc7X\u0439n\\\x00\x00\u07d4\x03\xf7\xb9 \b\x81:\xe0\xa6v\xeb!(\x14\xaf\xab5\"\x10i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\x11p\xf5\x81\u0780\xe5\x8b*\x04\\\x8f|\x14\x93\xb0\x01\xb7\u02c90\xc8\xeca2\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\x04i\xe8\xc4@E\v\x0eQ&&\xfe\x81~gT\xa8\x15(0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04m'K\x1a\xf6\x15\xfbPZvJ\xd8\u0767p\xb1\xdb/=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x04}Z&\u05ed\x8f\x8ep`\x0fp\xa3\x98\u076a\x1c-\xb2o\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x04~\x87\xc8\xf7\xd1\xfc\xe3\xb0\x13S\xa8Xb\xa9H\xac\x04\x9f>\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x04\u007f\x9b\xf1R\x9d\xaf\x87\xd4\a\x17^o\x17\x1b^Y\xe9\xff>\x89#<\x8f\xe4'\x03\xe8\x00\x00\xe0\x94\x04\x85'2\xb4\xc6R\xf6\xc2\u53b3e\x87\xe6\nb\xda\x14\u06ca\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x04\x8a\x89p\xeaAE\xc6MU\x17\xb8\xde[F\xd0YZ\xad\x06\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x04\x9c]K\xc6\xf2]NEli{R\xa0x\x11\xcc\u045f\xb1\x89\x10D\x00\xa2G\x0eh\x00\x00\u07d4\x04\xa1\xca\xda\x1c\xc7Q\b/\xf8\u0692\x8e<\xfa\x00\b \xa9\xe9\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x04\xa8\n\xfa\xd5>\xf1\xf8Ae\xcf\xd8R\xb0\xfd\xf1\xb1\xc2K\xa8\x89\x03$\xe9d\xb3\xec\xa8\x00\x00\u07d4\x04\xaa\xfc\x8a\xe5\xceoI\x03\u021d\u007f\xac\x9c\xb1\x95\x12\"Gw\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x04\xbaK\xb8q@\x02,!Jo\xacB\xdbZ\x16\u0755@E\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x04\xba\x8a?\x03\xf0\x8b\x89P\x95\x99M\xdaa\x9e\u06ac\xee>z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\xc2\xc6K\xb5L>\xcc\xd0U\x85\xe1\x0e\xc6\xf9\x9a\f\xdb\x01\xa3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x04\xceE\xf6\x00\xdb\x18\xa9\u0405\x1b)\xd99>\xbd\xaa\xfe=\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x04\u05b8\xd4\u0686t\a\xbb\x99wI\u07bb\xcd\xc0\xb3XS\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x04\xd78\x96\xcfe\x93\xa6\x91\x97*\x13\xa6\xe4\x87\x1f\xf2\xc4+\x13\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x04\xd8*\xf9\xe0\x1a\x93m\x97\xf8\xf8Y@\xb9p\xf9\xd4\u06d96\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x04\xe5\xf5\xbc|\x92?\xd1\xe3\x175\xe7.\xf9h\xfdg\x11\fn\x89WU\x1d\xbc\x8ebL\x00\x00\u07d4\x04\xec\xa5\x01c\n\xbc\xe3R\x18\xb1t\x95k\x89\x1b\xa2^\xfb#\x8966\x9e\xd7t}&\x00\x00\u07d4\x05\x05\xa0\x8e\"\xa1\t\x01Z\"\xf6\x850STf*U1\u0549\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\x05\x14\x95L\xe8\x81\xc807\x03d\x00\x89lO\xd1\xee$nx\x00\x00\u07d4\x05\x1dBBv\xb2\x129fQ\x86\x13=e;\xb8\xb1\x86/\x89\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x05!\xbc:\x9f\x87\x11\xfe\xcb\x10\xf5\a\x97\xd7\x10\x83\xe3A\ub749\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x05#mL\x90\xd0e\xf9\u34c3X\xaa\xff\xd7w\xb8j\xecI\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x05*X\xe05\xf1\xfe\x9c\xdd\x16\x9b\xcf \x97\x03E\xd1+\x9cQ\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x05.\xab\x1fa\xb6\xd4U\x17(?A\xd1D\x18$\x87\x87I\u0409\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x053n\x9ar'(\xd9c\xe7\xa1\xcf'Y\xfd\x02tS\x0f\u02891\xa2D?\x88\x8ay\x80\x00\u07d4\x054q\u035aA\x92[9\x04\xa5\xa8\xff\xca6Y\xe04\xbe#\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4\x056\x1d\x8e\xb6\x94\x1dN\x90\xfb~\x14\x18\xa9Z2\xd5%w2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x05B:T\xc8\xd0\xf9p~pAs\xd9#\xb9F\xed\xc8\xe7\x00\x89\x06\xea\x03\u00bf\x8b\xa5\x80\x00\u07d4\x05D\f[\a;R\x9bH) \x9d\xff\x88\t\x0e\a\xc4\xf6\xf5\x89E\u04977\xe2/ \x00\x00\u07d4\x05Z\xb6X\xc6\xf0\xedO\x87^\xd6t.K\xc7)-\x1a\xbb\xf0\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\u07d4\x05[\xd0,\xaf\x19\xd6 +\xbc\u0703m\x18{\xd1\xc0\x1c\xf2a\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x05^\xacO\x1a\xd3\xf5\x8f\v\xd0$\u058e\xa6\r\xbe\x01\u01af\xb3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x05fQU\xccI\xcb\xf6\xaa\xbd\u056e\x92\xcb\xfa\xad\x82\xb8\xc0\xc1\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x05f\x86\a\x8f\xb6\xbc\xf9\xba\n\x8a\x8d\xc6:\x90o_\xea\xc0\xea\x89\x1b\x18\x1eK\xf24<\x00\x00\u07d4\x05iks\x91k\xd3\x03>\x05R\x1e2\x11\xdf\xec\x02n\x98\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x05k\x15F\x89O\x9a\x85\xe2\x03\xfb3m\xb5i\xb1l%\xe0O\x89\t.\xdb\t\xff\b\u0600\x00\u07d4\x05yI\xe1\xca\x05pF\x9eL\xe3\u0190\xaea:k\x01\xc5Y\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x05}\u049f-\x19\xaa=\xa4#'\xeaP\xbc\xe8o\xf5\xc9\x11\u0649\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x05\u007f\u007f\x81\xcdz@o\xc4Y\x94@\x8bPI\x91,Vdc\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x05\x91]N\"Zf\x81b\xae\xe7\xd6\xc2_\xcf\xc6\xed\x18\xdb\x03\x89\x03\x98\xc3ry%\x9e\x00\x00\u07d4\x05\x96\xa2}\xc3\xee\x11_\xce/\x94\xb4\x81\xbc z\x9e&\x15%\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x05\xa80rC\x02\xbc\x0fn\xbd\xaa\x1e\xbe\xee\xb4nl\xe0\v9\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\x05\xae\u007f\u053b\u0300\xca\x11\xa9\n\x1e\u01e3\x01\xf7\xcc\u0303\u06c91T\xc9r\x9d\x05x\x00\x00\u07d4\x05\xbbd\xa9\x16\xbef\xf4`\xf5\xe3\xb6C2\x11\r \x9e\x19\xae\x89\u3bb5sr@\xa0\x00\x00\xe0\x94\x05\xbfO\xcf\xe7r\xe4[\x82dC\x85.l5\x13P\xcer\xa2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\x05\xc6@\x04\xa9\xa8&\xe9N^N\xe2g\xfa*v2\xddNo\x8a\x03m\xc4.\xbf\xf9\v\u007f\x80\x00\xe0\x94\x05\xc76\xd3e\xaa7\xb5\xc0\xbe\x9c\x12\u022d\\\xd9\x03\xc3,\xf9\x8a\x01E^{\x80\n\x86\x88\x00\x00\xe0\x94\x05\xcbl;\x00r\xd3\x11ga\xb52\xb2\x18D;S\xe8\xf6\u014a\x1e\x02\xc3\xd7\xfc\xa9\xb6(\x00\x00\u07d4\x05\xd0\xf4\xd7(\xeb\xe8.\x84\xbfYu\x15\xadA\xb6\v\xf2\x8b9\x89\u3bb5sr@\xa0\x00\x00\u07d4\x05\u058d\xada\u04fb\u07f3\xf7y&\\IGJ\xff?\xcd0\x89\x02\"\xc5]\xc1Q\x9d\x80\x00\u07d4\x05\xe6q\xdeU\xaf\xec\x96K\aM\xe5t\xd5\x15\x8d]!\xb0\xa3\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x05\xe9{\tI,\u058fc\xb1+\x89.\xd1\xd1\x1d\x15,\x0e\u02897\b\xba\xed=h\x90\x00\x00\u07d4\x05\xf3c\x1fVd\xbd\xad]\x012\xc88\x8d6\xd7\u0612\t\x18\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x06\t\xd8:l\xe1\xff\u0276\x90\xf3\xe9\xa8\x1e\x98>\x8b\xdcM\x9d\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\u07d4\x06\x1e\xa4\x87|\u0409D\xebd\u0096n\x9d\xb8\xde\xdc\xfe\xc0k\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x06%\xd0`V\x96\x8b\x00\"\x06\xff\x91\x98\x01@$+\xfa\xa4\x99\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x06(\xbf\xbeU5x/\xb5\x88@k\xc9f`\xa4\x9b\x01\x1a\xf5\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\x061\u044b\xbb\xbd0\xd9\xe1s+\xf3n\xda\xe2\u0389\x01\xab\x80\x89\xa3\xf9\x88U\xec9\x90\x00\x00\u07d4\x061\xdc@\xd7NP\x95\xe3r\x9e\xdd\xf4\x95D\xec\xd49og\x89\b\xacr0H\x9e\x80\x00\x00\xe0\x94\x067Y\xdd\x1cN6.\xb1\x93\x98\x95\x1f\xf9\xf8\xfa\xd1\xd3\x10h\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x06_\xf5u\xfd\x9c\x16\xd3\xcbo\u058f\xfc\x8fH?\xc3.\xc85\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x06a\x8e\x9dWb\xdfb\x02\x86\x01\xa8\x1dD\x87\u05a0\xec\xb8\x0e\x89Hz\x9a0E9D\x00\x00\xe0\x94\x06fG\xcf\xc8]#\xd3v\x05W= \x8c\xa1T\xb2D\xd7l\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x06xeJ\xc6v\x1d\xb9\x04\xa2\xf7\xe8Y^\xc1\xea\xacsC\b\x89/\x98\xb2\x9c(\x18\xf8\x00\x00\u07d4\x06\x86\n\x93RYU\xffbI@\xfa\xdc\xff\xb8\xe1I\xfdY\x9c\x89lh\xcc\u041b\x02,\x00\x00\xe0\x94\x06\x8c\xe8\xbdn\x90*E\u02c3\xb5\x15A\xb4\x0f9\xc4F\x97\x12\x8a\x01\x1c\x0f\x9b\xadJF\xe0\x00\x00\u07d4\x06\x8e)\xb3\xf1\x91\xc8\x12\xa699\x18\xf7\x1a\xb93\xaehG\xf2\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x06\x8eeWf\xb9D\xfb&6\x19e\x87@\xb8P\xc9J\xfa1\x89\x01\xe8\u007f\x85\x80\x9d\xc0\x00\x00\u0794\x06\x96N-\x17\xe9\x18\x9f\x88\xa8 96\xb4\n\xc9nS<\x06\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x06\x99L\xd8:\xa2d\n\x97\xb2`\vA3\x9d\x1e\r>\xdel\x89\r\x8drkqw\xa8\x00\x00\u07d4\x06\x9e\u042bz\xa7}\xe5q\xf1a\x06\x05\x1d\x92\xaf\xe1\x95\xf2\u0409\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x06\xac&\xad\x92\u02c5\x9b\u0550]\xdc\xe4&j\xa0\xecP\xa9\u0149*\x03I\x19\u07ff\xbc\x00\x00\u07d4\x06\xb0\xc1\xe3\u007fZ^\u013b\xf5\b@T\x8f\x9d:\xc0(\x88\x97\x89\xd8\u0602\u148e}\x00\x00\u07d4\x06\xb0\xff\x83@s\xcc\xe1\xcb\xc9\xeaU~\xa8{`Yc\u8d09\x10CV\x1a\x88)0\x00\x00\xe0\x94\x06\xb1\x06d\x9a\xa8\xc4!\xdd\xcd\x1b\x8c2\xcd\x04\x18\xcf0\xda\x1f\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x06\xb5\xed\xe6\xfd\xf1\xd6\xe9\xa3G!7\x9a\xea\xa1|q=\xd8*\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x06\xcb\xfa\b\xcd\xd4\xfb\xa77\xba\xc4\a\xbe\x82$\xf4\xee\xf3X(\x89 +\xe5\xe88.\x8b\x80\x00\u07d4\x06\xd6\xcb0\x84\x81\xc36\xa6\xe1\xa2%\xa9\x12\xf6\xe65Y@\xa1\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x06\xdc\u007f\x18\xce\xe7\xed\xab[yS7\xb1\xdfj\x9e\x8b\u062eY\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x06\xf6\x8d\xe3\xd79\xdbA\x12\x1e\xac\xf7y\xaa\xda=\xe8v!\a\x89\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\x06\xf7\u070d\x1b\x94b\xce\xf6\xfe\xb13h\xa7\xe3\x97K\t\u007f\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\a\x01\xf9\xf1G\xecHhV\xf5\xe1\xb7\x1d\xe9\xf1\x17\xe9\x9e!\x05\x89\te\xdaq\u007f\u0578\x00\x00\u07d4\a\r]6L\xb7\xbb\xf8\"\xfc,\xa9\x1a5\xbd\xd4A\xb2\x15\u0549lk\x93[\x8b\xbd@\x00\x00\xe0\x94\a\x1d\xd9\r\x14\xd4\x1fO\xf7\xc4\x13\xc2B8\xd35\x9c\xd6\x1a\a\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\u07d4\a&\xc4.\x00\xf4T\x04\x83n\xb1\xe2\x80\xd0s\xe7\x05\x96\x87\xf5\x89X\x00>?\xb9G\xa3\x80\x00\xe0\x94\a'\xbe\n*\x00! H\xb5R\x0f\xbe\xfb\x95>\xbc\x9dT\xa0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\a)\xa8\xa4\xa5\xba#\xf5y\xd0\x02[\x1a\xd0\xf8\xa0\xd3\\\xdf\u048a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4\a)\xb4\xb4|\t\xeb\x16\x15\x84d\u022a\u007f\xd9i\vC\x889\x89lh\xcc\u041b\x02,\x00\x00\u0794\a4\xa0\xa8\x1c\x95b\xf4\xd9\xe9\xe1\n\x85\x03\xda\x15\xdbF\xd7n\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\a\xa7\xef[G\x00\x00\xe0\x94\ap\xc6\x1b\xe7\x87r#\f\xb5\xa3\xbb$)\xa7&\x14\xa0\xb36\x8a\x01n\u0899\xb7\x13A\x80\x00\u07d4\ar><0\xe8\xb71\xeeEj)\x1e\xe0\u7630 Jw\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\as\xee\xac\xc0P\xf7G \xb4\xa1\xbdW\x89[\x1c\xce\xebI]\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\a\x80\r/\x80h\xe4H\u01daOi\xb1\xf1^\xf6\x82\xaa\xe5\xf6\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\u07d4\a\xa8\xda\xde\xc1BW\x1a}S\xa4)pQxm\a,\xbaU\x89\x01;m\xa1\x13\x9b\u0680\x00\u07d4\a\xaf\x93\x8c\x127\xa2|\x900\tM\xcf$\aP$n=,\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\a\xb1\xa3\x06\xcbC\x12\xdffH,,\xaer\xd1\xe0a@\x0f\u034a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\a\xb7\xa5p3\xf8\xf1\x130\xe4f^\x18]#N\x83\xec\x14\v\x89\xea~\xe9*\f\x9a\v\x80\x00\u07d4\a\xbc,\xc8\xee\xdc\x01\x97\a\x00\xef\xc9\xc4\xfb6s^\x98\xcdq\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\a\xd4\x12\x17\xba\u0725\xe0\xe6\x03'\xd8E\xa3FO\x0f'\xf8J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\a\xd43N\u00c5\xe8\xaaT\xee\xda\xea\xdb0\x02/\f\u07e4\xab\x89\x8e\x91\xd5 \xf2\xeby\x00\x00\u07d4\a\xda\xe6\"c\r\x1168\x193\u04adk\"\xb89\xd8!\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\a\xdc+\xf8;\u01af\x19\xa8B\xff\xeaf\x1a\xf5\xb4\x1bg\xfd\xa1\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\a\u070c\x8b\x92z\xdb\xed\xfa\x8f]c\x9bCR5\x1f/6\u0489\x11\n\xed;U0\xdb\x00\x00\u07d4\a\xdd\xd0B,\x86\xefe\xbf\f\u007f\xc3E(b\xb1\"\x8b\b\xb8\x89o\xf5\u04aa\x8f\x9f\xcf\x00\x00\u07d4\a\xe1\x16,\xea\xe3\xcf!\xa3\xf6-\x10Y\x900.0\u007fN;\x89R\xf1\x03\xed\xb6k\xa8\x00\x00\u07d4\a\xe2\xb4\xcd\xee\xd9\u0407\xb1.Um\x9ew\f\x13\xc0\x99a_\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\a\xfe\xefT\xc16\x85\b)\xba\xdcKI\xc3\xf2\xa7<\x89\xfb\x9e\x89\x06hZ\xc1\xbf\xe3,\x00\x00\u07d4\b\x05FP\x8a=&\x82\u0239\x88O\x13c{\x88G\xb4M\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\t\bv\xba\xad\xfe\xe6\\=6;\xa5S\x12t\x8c\xfa\x87=\x89\\*\x997\x1c\xff\xe1\x00\x00\u07d4\b\x16o\x021?\xea\u12f0D\xe7\x87|\x80\x8bU\xb5\xbfX\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\b)\xd0\xf7\xbb|Dl\xfb\xb0\u07ad\xb29M\x9d\xb7$\x9a\x87\x89\x02,\xa3X|\xf4\xeb\x00\x00\u07d4\b0m\xe5\x19\x81\u7b21\x85hY\xb7\xc7xijki\xf9\x89\xadx\xeb\u016cb\x00\x00\x00\xe0\x94\b7S\x9b_jR*H,\xdc\u04e9\xbbpC\xaf9\xbd\u048a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\b8\xa7v\x8d\x9c*\u028b\xa2y\xad\xfe\xe4\xb1\xf4\x91\xe3&\xf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\bA\x16R\xc8qq6\t\xaf\x00b\xa8\xa1(\x1b\xf1\xbb\xcf\u0649K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\bM\x102Tu\x9b4<\xb2\xb9\xc2\xd8\xff\x9e\x1a\xc5\xf1E\x96\x8a\x01\x9b\xff/\xf5yh\xc0\x00\x00\u07d4\bPO\x05d?\xabY\x19\xf5\xee\xa5Y%\u05e3\xed}\x80z\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\b[J\xb7]\x83b\xd9\x14C\\\xed\xee\x1d\xaa+\x1e\xe1\xa2;\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\b[\xa6_\xeb\xe2>\xef\xc2\xc8\x02fj\xb1&#\x82\xcf\u0114\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\bt\x98\xc0FFh\xf3\x11P\xf4\xd3\u013c\u0765\"\x1b\xa1\x02\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\bw\uebabx\xd5\xc0\x0e\x83\xc3+-\x98\xfay\xadQH/\x89\x17\xd2-q\xdab&\x00\x00\u0794\b\x93j7\u07c5\xb3\xa1X\xca\xfd\x9d\xe0!\xf5\x817h\x13G\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\b\xa9\xa4N\x1fA\xde=\xbb\xa7\xa3c\xa3\xabA,\x12L\xd1^\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\b\xb7\xbd\u03d4MUp\x83\x8b\xe7\x04`$:\x86\x94HXX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xb8E6\xb7L\x8c\x01T=\xa8\x8b\x84\u05cb\xb9WG\xd8\"\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\b\xc2\xf26\xacJ\xdc\xd3\xfd\xa9\xfb\xc6\xe4S\"S\xf9\xda;\xec\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\b\xc8\x02\xf8wX4\x9f\xa0>k\xc2\xe2\xfd\a\x91\x19~\ua689lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xc9\U0007fd89\xfd\xf8\x04\xd7i\xf8!#6\x02\x15\xaf\xf9;\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\b\xca\u0215&A\xd8\xfcRn\xc1\xabO-\xf8&\xa5\xe7q\x0f\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\b\xcc\xdaP\xe4\xb2j\x0f\xfc\x0e\xf9.\x92\x051\a\x06\xbe\xc2\u01ca\x01Iul8W\xc6\x00\x00\x00\u07d4\b\u0406M\xc3/\x9a\xcb6\xbfN\xa4G\xe8\xddg&\x90j\x15\x89lnY\xe6|xT\x00\x00\u07d4\b\xd4&\u007f\xeb\x15\u0697\x00\xf7\xcc\xc3\xc8J\x89\x18\xbf\x17\xcf\u0789a\t=|,m8\x00\x00\xe0\x94\b\xd41\x1c\x9c\x1b\xba\xf8\u007f\xab\xe1\xa1\xd0\x14c\x82\x8d]\x98\u038a\x13\x0e\xe8\xe7\x17\x90D@\x00\x00\u07d4\b\xd5N\x83\xadHj\x93L\xfa\xea\u20e3>\xfd\"|\x0e\x99\x898S\x05\x83$^\xdc\x00\x00\u07d4\b\xd9~\xad\xfc\xb7\xb0d\xe1\xcc\xd9\u0217\x9f\xbe\xe5\xe7z\x97\x19\x89\x0el]\xa8\xd6z\xc1\x80\x00\u07d4\b\xda:z\x0fE!a\u03fc\xec1\x1b\xb6\x8e\xbf\xde\xe1~\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\b\xe3\x8e\xe0\xceH\xc9\xcad\\\x10\x19\xf7;SUX\x1cV\xe6\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\b\xef?\xa4\xc4<\xcd\xc5{\"\xa4\xb9\xb23\x1a\x82\xe58\x18\xf2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\t\td\x8c\x18\xa3\xce[\xaez\x04~\xc2\xf8h\xd2L\u0768\x1d\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\t\f\xd6{`\xe8\x1dT\xe7\xb5\xf6\a\x8f>\x02\x1b\xa6[\x9a\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\f\xeb\xef),>\xb0\x81\xa0_\u062a\xf7\u04db\xf0{\x89\u0509\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\t\x0f\xa96{\xdaW\xd0\xd3%:\n\x8f\xf7l\xe0\xb8\xe1\x9as\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\x14n\xa3\x88Qv\xf0w\x82\xe1\xfe0\xdc\xe3\xce$\u011e\x1f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\t!`_\x99\x16N;\xcc(\xf3\x1c\xae\xcex\x971\x82V\x1d\x89+\ai*\x90e\xa8\x00\x00\xe0\x94\t&\x1f\x9a\xcbE\x1c7\x88\x84O\f\x14Q\xa3[\xadP\x98\xe3\x8a\x01\u056d'P) `\x00\x00\xe0\x94\t'\"\x04\x92\x19K.\u069f\u013b\xe3\x8f%\u0581\xdf\xd3l\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\t*\xcbbK\b\xc0U\x10\x18\x9b\xbb\xe2\x1ee$\xd6D\u032d\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\t.\x81UX@-g\xf9\rk\xfem\xa0\xb2\xff\xfa\x91EZ\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\tP0\xe4\xb8&\x92\xdc\xf8\xb8\u0411$\x94\xb9\xb3x\xec\x93(\x89H\xa4zu\x80\x00\u07d4\t\x89\xc2\x00D\v\x87\x89\x91\xb6\x9d`\x95\xdf\xe6\x9e3\xa2.p\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\t\x90\xe8\x1c\u05c5Y\x9e\xa26\xbd\x19f\xcfRc\x02\xc3[\x9c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\t\x98\xd8'1\x15\xb5j\xf4%\xff\xc8>!\x8c\x1e\n\xfe\x89(\u01c8\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\t\xaeI\xe3\u007f\x12\x1d\xf5\xdc\x15\x8c\xfd\xe8\x06\xf1s\xa0k\f\u007f\x89\xd80\x9e&\xab\xa1\xd0\x00\x00\u07d4\t\xaf\xa7;\xc0G\xefF\xb9w\xfd\x97c\xf8r\x86\xa6\xbeh\u0189\x1b/\xb5\xe8\xf0jf\x00\x00\u07d4\t\xb4f\x86\x96\xf8j\b\x0f\x8b\xeb\xb9\x1d\xb8\xe6\xf8p\x15\x91Z\x89#\x8f\xf7\xb3O`\x01\x00\x00\xe0\x94\t\xb5\x9b\x86\x98\xa7\xfb\xd3\xd2\xf8\xc7:\x00\x89\x88\xde>@k+\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94\t\xb7\xa9\x88\xd1?\xf8\x91\x86so\x03\xfd\xf4au\xb5=\x16\xe0\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\t\xc1w\xf1\xaeD$\x11\u076c\xf1\x87\xd4m\xb9V\x14\x83`\xe7\x8a\x01\xe5.3l\xde\"\x18\x00\x00\xe0\x94\t\u020f\x91~Mj\xd4s\xfa\x12\u93a3\xc4G*^\xd6\u068a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\t\u0438\xcd\a|i\xd9\xf3-\x9c\xcaC\xb3\xc2\b\xa2\x1e\u050b\x89\b!\xd2!\xb5)\x1f\x80\x00\xe0\x94\t\xd6\xce\xfdu\xb0\u0133\xf8\xf1\u0587\xa5\"\xc9a#\xf1\xf59\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\t\xe47\xd4H\x86\x12(\xa22\xb6.\xe8\xd3ye\xa9\x04\ud70a\x04\x98\xcf@\x1d\xf8\x84.\x80\x00\u07d4\t\xee\x12\xb1\xb4+\x05\xaf\x9c\xf2\a\xd5\xfc\xac%[.\xc4\x11\xf2\x89\x031\xcd\xddG\xe0\xfe\x80\x00\u07d4\t\xf3\xf6\x01\xf6\x05D\x11@Xl\xe0eo\xa2J\xa5\xb1\u066e\x89Sswo\xe8\xc4T\x00\x00\u07d4\t\xf9W[\xe5}\x00G\x93\u01e4\ub137\x15\x87\xf9|\xbbj\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n\x06P\x86\x1fx^\xd8\xe4\xbf\x10\x05\xc4P\xbb\xd0n\xb4\x8f\xb6\x89\xa6A;y\x14N~\x00\x00\u07d4\n\x06\xfa\xd7\xdc\u05e4\x92\xcb\xc0S\xee\xab\xdei4\xb3\x9d\x867\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\n\a}\xb1?\xfe\xb0\x94\x84\xc2\x17p\x9dX\x86\xb8\xbf\x9cZ\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\n\x0e\u0366cow\x16\xef\x19saF\x87\xfd\x89\xa8 \xa7\x06\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\n)\xa8\xa4\xd5\xfd\x95\x00u\xff\xb3Mw\xaf\xeb-\x82;\u0589\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n*\u0795\xb2\xe8\xc6m\x8a\xe6\xf0\xbad\xcaW\u05c3\xbemD\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\n+O\xc5\xd8\x1a\xceg\xdcK\xba\x03\xf7\xb4UA=F\xfe=\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\n-\xcbzg\x17\x01\u06f8\xf4\x95r\x80\x88&Xs5l\x8e\x89\b?\x16\xce\b\xa0l\x00\x00\u07d4\n=\xe1U\xd5\xec\xd8\xe8\x1c\x1f\xf9\xbb\xf07\x83\x01\xf8\xd4\xc6#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\nG\xad\x90Y\xa2I\xfc\x93k&b5=\xa6\x90_u\u00b9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\nH)ov1p\x8c\x95\u04b7Iu\xbcJ\xb8\x8a\xc19*\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\nJ\x01\x19\x95\u0181\xbc\x99\x9f\xddyuN\x9a2J\xe3\xb3y\x8a\b\xc1\x9a\xb0n\xb8\x9a\xf6\x00\x00\u07d4\nX\xfd\xddq\x89\x8d\xe7s\xa7O\xda\xe4^{\xd8N\xf46F\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\n[y\xd8\xf2;d\x83\xdb\u2f6ab\xb1\x06L\xc7cf\xae\x89j\u0202\x10\tR\u01c0\x00\u07d4\ne.*\x8bw\xbd\x97\xa7\x90\xd0\xe9\x13a\u0248\x90\u06f0N\x8965\u026d\xc5\u07a0\x00\x00\u07d4\nn\xber;n\xd1\xf9\xa8ji\xdd\xdah\xdcGF\\+\x1b\x89@=-\xb5\x99\xd5\xe4\x00\x00\u07d4\nw\xe7\xf7+C{WO\x00\x12\x8b!\xf2\xac&Q3R\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\n\x91\u007f;\\\xb0\xb8\x83\x04\u007f\u0676Y=\xbc\xd5W\xf4S\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\n\x93\x1bD\x9e\xa8\xf1,\xdb\xd5\xe2\xc8\xccv\xba\xd2\xc2|\x069\x89\x01?\x9e\x8cy\xfe\x05\x80\x00\u0794\n\x98\x04\x13x\x03\xbahh\xd9:U\xf9\x98_\xcdT\x04Q\u4239\x8b\xc8)\xa6\xf9\x00\x00\u07d4\n\x9a\xb2c\x8b\x1c\xfdeM%\u06b0\x18\xa0\xae\xbd\u07c5\xfdU\x89\x01.\x8c\xb5\xfeLJ\x80\x00\u07d4\n\xb3f\xe6\xe7\u056b\xbc\xe6\xb4JC\x8di\xa1\u02bb\x90\xd13\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\n\xb4(\x1e\xbb1\x85\x90\xab\xb8\x9a\x81\xdf\a\xfa:\xf9\x04%\x8a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\n\xb5\x9d9\a\x02\xc9\xc0Y\xdb\x14\x8e\xb4\xf3\xfc\xfa}\x04\xc7\xe7\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\n\xbf\xb3\x9b\x11HmyW(f\x19[\xa2lc\vg\x84\u06ca\x19\xba\x877\xf9i(\xf0\x00\x00\u07d4\n\u029aV&\x91;\b\xcf\u0266m@P\x8d\xceR\xb6\x0f\x87\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\n\xd3\xe4M<\x00\x1f\xa2\x90\xb3\x93ap0TA\b\xacn\xb9\x89j\xbd\xa0\xbc0\xb2\u07c0\x00\u07d4\n\xec.Bn\xd6\xcc\f\xf3\xc2I\xc1\x89~\xacG\xa7\xfa\xa9\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\n\xf6_\x14xNU\xa6\xf9Vg\xfds%*\x1c\x94\a-*\x89\nv;\x8e\x02\xd4O\x80\x00\u07d4\n\xf6\xc8\xd59\xc9mP%\x9e\x1b\xa6q\x9e\x9c\x80`\xf3\x88\u008965\u026d\xc5\u07a0\x00\x00\u07d4\v\x069\x0f$7\xb2\x0e\u0123\xd3C\x1b2y\xc6X>^\u05c9\n\x84Jt$\xd9\xc8\x00\x00\u07d4\v\v8b\x11*\xee\u00e04\x92\xb1\xb0_D\x0e\xcaT%n\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\v\x0e\x05[(\xcb\xd0=\xc5\xffD\xaad\xf3\xdc\xe0O^c\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\x11\x9d\xf9\x9ck\x8d\xe5\x8a\x1e,?)zgD\xbfU\"w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\x14\x89\x19\x99\xa6\\\x9e\xf73\b\xef\xe3\x10\f\xa1\xb2\x0e\x81\x92\x89+^:\xf1k\x18\x80\x00\x00\u07d4\v!\x13PE4d*\x1d\xaf\x10.\xee\x10\xb9\xeb\xdev\xe2a\x89\x94,\xdd|\x95\xf2\xbd\x80\x00\xe0\x94\v(\x8aZ\x8bu\xf3\xdcA\x91\xeb\x04W\xe1\xc8=\xbd M%\x8a\x01\a\x14\xe7{\xb4:\xb4\x00\x00\u07d4\v6\x9e\x00.\x1bLy\x13\xfc\xf0\x0f-^\x19\u0141eG\x8f\x89\x03\u007fe\x16(\x8c4\x00\x00\u07d4\vC\xbd#\x91\x02U\x81\u0615l\xe4*\a%y\u02ff\xcb\x14\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u07d4\vP|\xf5SV\x8d\xaa\xf6U\x04\xaeN\xaa\x17\xa8\xea<\xdb\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v]f\xb1<\x87\xb3\x92\xe9M\x91\xd5\xf7l\rE\nU(C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v^ \x11\xeb\xc2Z\x00\u007f!6)`I\x8a\xfb\x8a\xf2\x80\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vd\x9d\xa3\xb9j\x10,\xdcm\xb6R\xa0\xc0}e\xb1\xe4C\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vi \xa6K6;\x8d]\x90\x80$\x94\xcfVKT|C\r\x89A\rXj \xa4\xc0\x00\x00\u07d4\vp\x11\x01\xa4\x10\x9f\x9c\xb3`\xdcW\xb7tBg=^Y\x83\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\vq\xf5T\x12$i\uf5ce/\x1f\xef\xd7\u02f4\x10\x98'r\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\xe0\x94\v{\xb3B\xf0\x1b\u0248\x8ej\x9a\xf4\xa8\x87\xcb\xf4\xc2\xdd,\xaf\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\v}3\x93q\xe5\xbeg'\xe6\xe31\xb5\x82\x1f\xa2K\u06ddZ\x89.\u007f\x81\x86\x82b\x01\x00\x00\u07d4\v\u007f\xc9\xdd\xf7\x05v\xf63\x06i\xea\xaaq\xb6\xa81\xe9\x95(\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\v\x80\xfcp(,\xbd\xd5\xfd\xe3[\xf7\x89\x84\xdb;\xdb\x12\x01\x88\x8968\x02\x1c\xec\u06b0\x00\x00\u07d4\v\x92M\xf0\a\xe9\xc0\x87\x84\x17\xcf\xe6;\x97n\xa1\xa3\x82\xa8\x97\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\v\x93\xfc\xa4\xa4\xf0\x9c\xac \xdb`\xe0e\xed\xcc\xcc\x11\u0976\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\v\x9d\xf8\x0f\xbe# \t\xda\xcf\n\xa8\xca\u0153v\xe2Gb\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xa6\xe4j\xf2Z\x13\xf5qi%Z4\xa4\xda\xc7\xce\x12\xbe\x04\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\v\xa8p[\xf5\\\xf2\x19\xc0\x95k^?\xc0\x1cDt\xa6\xcd\xc1\x89\x05%\xe0Y]Mk\x80\x00\u07d4\v\xafn\u0379\x1a\xcb6\x06\xa85|\v\xc4\xf4\\\xfd-~o\x8965\u026d\xc5\u07a0\x00\x00\u07d4\v\xb0_r$\xbbX\x04\x85eV\xc0~\xea\xdb\ud1fa\x8f|\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\v\xb0\xc1&\x82\xa2\xf1\\\x9bWA\xb28\\\xbeA\xf04\x06\x8e\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\v\xb2\\\xa7\u0448\xe7\x1eMi={\x17\a\x17\xd6\xf8\xf0\xa7\n\x89\x12C\x02\xa8/\xad\xd7\x00\x00\u07d4\v\xb2e\x0e\xa0\x1a\xcau[\xc0\xc0\x17\xb6K\x1a\xb5\xa6m\x82\xe3\x89Hz\x9a0E9D\x00\x00\u07d4\v\xb5Lr\xfdf\x10\xbf\xa463\x97\xe0 8K\x02+\fI\x89Hz\x9a0E9D\x00\x00\u07d4\v\xb7\x16\n\xba)7b\xf8sO>\x03&\xff\u0264\xca\xc1\x90\x8965\u026d\xc5\u07a0\x00\x00\u07d4\v\xc9\\\xb3-\xbbWL\x83/\xa8\x17J\x815m8\xbc\x92\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xd6}\xbd\xe0z\x85n\xbd\x89;^\xdcO:[\xe4 &\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xdb\xc5L\u023d\xbb\xb4\x02\xa0\x89\x11\xe2#*T`\u0386k\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\v\xddX\xb9n|\x91m\xd2\xfb05o*\xeb\xfa\xaf\x1d\x860\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\v\u1f39\x03C\xfa\xe501s\xf4a\xbd\x91JH9\x05l\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\v\xe1\xfd\xf6&\xeea\x89\x10-p\xd1;1\x01,\x95\xcd\x1c\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\v\xe2\xb9J\xd9P\xa2\xa6&@\xc3[\xfc\xcdlg\xda\xe4P\xf6\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\v\u681eC\a\xfeH\xd4\x12\xb8\u0461\xa8(M\xceHba\x8a\x04\x0f\xbf\xf8\\\x0180\x00\x00\u07d4\v\xef\xb5G\a\xf6\x1b,\x9f\xb0G\x15\xab\x02n\x1b\xb7 B\xbd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\v\xf0dB\x8f\x83bg\"\xa7\xb5\xb2j\x9a\xb2\x04!\xa7r>\x89\a?u\u0460\x85\xba\x00\x00\u07d4\v\xfb\xb6\x92]\xc7^R\xcf&\x84\"K\xbe\x05P\xfe\xa6\x85\u04c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\f\b\x80\x06\xc6K0\xc4\u076f\xbc6\xcb_\x05F\x9e\xb6(4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f s\xbaD\xd3\u077d\xb69\xc0N\x19\x109\xa7\x17\x16#\u007f\x89M\x85<\x8f\x89\b\x98\x00\x00\xe0\x94\f\",|A\u0270H\xef\xcc\xe0\xa22CCb\xe1-g;\x8a\x02\x1e\x83Yivw8\x00\x00\xe0\x94\f(\b\xb9Q\ud787-{2y\x0f\xccY\x94\xaeA\xff\u070a\x15\x99n[<\u05b3\xc0\x00\x00\u07d4\f(\x84~O\t\xdf\xce_\x9b%\xaf|NS\x0fY\u0200\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f-\\\x92\x058\xe9S\u02af$\xf0s\u007fUL\u0192wB\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f0\xca\xcc?r&\x9f\x8bO\x04\xcf\a=+\x05\xa8=\x9a\u0449lyt\x12?d\xa4\x00\x00\u07d4\f29\xe2\xe8A$-\xb9\x89\xa6\x15\x18\xc2\"G\xe8\xc5R\b\x89\x0eJ\xf6G\x174d\x00\x00\xe0\x94\fH\r\xe9\xf7F\x10\x02\x90\x8bI\xf6\x0f\xc6\x1e+b\xd3\x14\v\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\fH\xaeb\xd1S\x97\x88\xeb\xa0\x13\xd7^\xa6\vd\xee\xbaN\x80\x89w\xfb\xdcC\xe00\x99\x80\x00\u07d4\fU\x89\xa7\xa8\x9b\x9a\xd1[\x02u\x190AYH\xa8u\xfb\xef\x89\x06\u0519\xeclc8\x00\x00\u07d4\fg\x03=\xd8\xee\u007f\f\x8a\xe54\xd4*Q\xf7\xd9\xd4\xf7\x97\x8f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\fhE\xbfA\xd5\xee'<>\u6d70\u059fo\xd5\xea\xbb\xf7\x89\xa2\xa1\xb9h.X\t\x00\x00\xe0\x94\f\u007f\x86\x9f\x8e\x90\xd5?\xdc\x03\u8c81\x9b\x01k\x9d\x18\xeb&\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\f\x86\x92\xee\xff*S\xd6\xd1h\x8e\xd5j\x9d\u06fdh\u06bb\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f\x8ff\xc6\x01{\xce[ 4r\x04\xb6\x02\xb7C\xba\u05cd`\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\f\x8f\xd7w^T\xa6\xd9\u0263\xbf\x89\x0ev\x1fewi?\xf0\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\f\x92Z\xd5\xeb5,\x8e\xf7m\f\"-\x11[\a\x91\xb9b\xa1\x89\xacc]\u007f\xa3N0\x00\x00\u07d4\f\x96~0a\xb8zu>\x84P~\xb6\t\x86x,\x8f0\x13\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\f\xa1*\xb0\xb9fl\xf0\xce\xc6g\x1a\x15)/&SGj\xb2\x8a,x'\xc4-\"\xd0|\x00\x00\u07d4\f\xa6p\xeb,\x8b\x96\u02e3y!\u007fY)\u00b8\x92\xf3\x9e\xf6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\f\xae\x10\x8em\xb9\x9b\x9ecxv\xb0d\xc60>\u068ae\u0209\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\f\xbd\x92\x1d\xbe\x12\x15c\xb9\x8ahq\xfe\xcb\x14\xf1\xcc~\x88\u05c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\f\xbf\x87p\xf0\xd1\b.\\ \u016e\xad4\xe5\xfc\xa9\xaez\xe2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\f\xc6\u007f\x82s\xe1\xba\xe0\x86\u007f\xd4.\x8b\x81\x93\xd7&y\xdb\xf8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\f\u05a1A\x91\x8d\x12k\x10m\x9f.\xbfi\xe1\x02\xdeM2w\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\f\xda\x12\xbfr\xd4a\xbb\xc4y\xeb\x92\xe6I\x1d\x05~kZ\u044a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\f\u0716\v\x99\x8c\x14\x19\x98\x16\r\xc1y\xb3l\x15\u0484p\xed\x89\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\f\xfb\x17#5\xb1l\x87\xd5\x19\xcd\x14uS\r W\u007f^\x0e\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\r\x1f*Wq>\xbcn\x94\xde)\x84n\x88D\xd3vfWc\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\r2e\xd3\u7f79=^\x8e\x8b\x1c\xa4\u007f!\ny>\u030e\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\r5@\x8f\"ef\x11o\xb8\xac\u06a9\xe2\xc9\u055bvh?\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\rU\x1e\xc1\xa2\x13<\x98\x1d_\u01a8\xc8\x17?\x9e|OG\xaf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r]\x98V\\d|\xa5\xf1w\xa2\xad\xb9\xd3\x02/\xac(\u007f!\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\re\x80\x14\xa1\x99\x06\x1c\xf6\xb3\x943\x14\x03\x03\xc2\x0f\xfdNZ\x8a\x01\xbc\x85\xdc*\x89\xbb \x00\x00\u07d4\rg\x87\x06\xd07\x18\u007f>\"\xe6\xf6\x9b\x99\xa5\x92\xd1\x1e\xbcY\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\ri\x10\f9\\\xe6\xc5\xea\xad\xf9]\x05\xd8r\x83~\xde\xdd!\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\rt~\u559b\xf7\x9dW8\x1do\xe3\xa2@l\xd0\xd8\xce'\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\r\x80#\x92\x9d\x91r4\xae@Q+\x1a\xab\xb5\xe8\xa4Q'q\x89\b\x05\xe9\x9f\xdc\xc5\xd0\x00\x00\xe0\x94\r\x8a\xab\x8ft\xea\x86,\xdfvh\x05\x00\x9d?>B\xd8\xd0\v\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\r\x8c@\xa7\x9e\x18\x99O\xf9\x9e\xc2Q\xee\x10\u0408\u00d1.\x80\x89\x066d\xfc\u04bb\xc4\x00\x00\u07d4\r\x8e\xd7\xd0\xd1V83\x0e\xd7\xe4\xea\u032b\x8aE\x8dus~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r\x92X/\u06e0^\xab\xc3\xe5\x158\xc5m\xb8\x817\x85\xb3(\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\r\x94C\xa7\x94h\xa5\xbb\xf7\xc1\xe5\xb9\x15\xb3d\x87\xf9\x16\x1f\x19\x84m\x10\x1431\x8a\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\r\xbdA|7+\x8b\r\x01\xbc\xd9Dpk\xd3.`\xae(\u0449\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\r\xc1\x00\xb1\a\x01\x1c\u007f\xc0\xa13\x96\x12\xa1l\xce\xc3(R\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\r\u03dd\x8c\x98\x04E\x9fd|\x14\x13\x8e\xd5\x0f\xadV;AT\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4\r\xcf\xe87\xea\x1c\xf2\x8ce\xfc\xce\u00fe\xf1\xf8NY\xd1P\xc0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\r\xd4\xe6t\xbb\xad\xb1\xb0\u0702D\x98q=\xce;QV\xda)\x89\t79SM(h\x00\x00\u07d4\r\xfb\u0501pP\xd9\x1d\x9db\\\x02\x05<\xf6\x1a>\xe2\x85r\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\x0e\x02N\u007f\x02\x9cj\xaf:\x8b\x91\x0f^\b\bs\xb8W\x95\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0e\tdl\x99\xafC\x8e\x99\xfa'L\xb2\xf9\xc8V\xcbe\xf76\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x0e\f\x9d\x00^\xa0\x16\u0095\xcdy\\\xc9!>\x87\xfe\xbc3\xeb\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\x0e\rf3\xdb\x1e\f\u007f#Jm\xf1c\xa1\x0e\n\xb3\x9c \x0f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x0e\x11\xd7z\x89w\xfa\xc3\r&\x84E\xe51\x14\x9b1T\x1a$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\x12=}\xa6\xd1\xe6\xfa\xc2\u072d\xd2p)$\v\xb3\x90R\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0e\x18\x01\xe7\vbb\x86\x1b\x114\u033c9\x1fV\x8a\xfc\x92\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x0e \x94\xac\x16T\xa4k\xa1\xc4\u04e4\v\xb8\xc1}\xa7\U000d6209\x13h?\u007f<\x15\xd8\x00\x00\u07d4\x0e!\xaf\x1b\x8d\xbf'\xfc\xf6?7\xe0G\xb8z\x82\\\xbe|'\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x0e.PJ-\x11\"\xb5\xa9\xfe\xee\\\xb1E\x1b\xf4\u00ac\xe8{\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x0e/\x8e(\xa6\x81\xf7|X;\xd0\xec\xde\x16cK\xdd~\x00\u0349\x05'8\xf6Y\xbc\xa2\x00\x00\u07d4\x0e2\x02\x19\x83\x8e\x85\x9b/\x9f\x18\xb7.=@s\xcaP\xb3}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e3\xfc\xbb\xc0\x03Q\v\xe3W\x85\xb5*\x9c]!k\xc0\x05\xf4\x89e\xea=\xb7UF`\x00\x00\u07d4\x0e6\x96\xcf\x1fB\x17\xb1c\u047c\x12\xa5\xeas\x0f\x1c2\xa1J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x0e9\x0fD\x05=\xdf\xce\xf0\xd6\b\xb3^M\x9c,\xbe\x98q\xbb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x0e:(\xc1\u07ef\xb0P[\xdc\xe1\x9f\xe0%\xf5\x06\xa6\xd0\x1c\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e=\xd7\xd4\xe4)\xfe90\xa6A@5\xf5+\xdcY\x9dxM\x89\x02,\xa3X|\xf4\xeb\x00\x00\u07d4\x0eGey\x03Rek\xc6Vh,$\xfc^\xf3\xe7j#\u01c9\x02\x86\xd7\xfc\f\xb4\xf5\x00\x00\u07d4\x0eI\x88\x00Dqw\xb8\u022f\xc3\xfd\xfa\u007fi\xf4\x05\x1b\xb6)\x89t\x05\xb6\x9b\x8d\xe5a\x00\x00\u07d4\x0ek\xaa\xa3\u07b9\x89\xf2\x89b\x00vf\x86\x18\xe9\xac3(e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x0el\xd6d\xad\x9c\x1e\xd6K\xf9\x87I\xf4\x06D\xb6&\xe3y,\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\xe0\x94\x0em\xfdU;.\x87=*\xec\x15\xbd_\xbb?\x84r\xd8\u04d4\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\x0en\xc3\x137bq\xdf\xf5T#\xabT\"\xcc:\x8b\x06\xb2+\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x0en\u0399\x11\x1c\xad\x19a\xc7H\xed=\xf5\x1e\xddi\u04a3\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x0e\x83\xb8PH\x1a\xb4MI\xe0\xa2)\xa2\xe4d\x90,iS\x9b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0e\x89\xed\xdd?\xa0\xd7\x1d\x8a\xb0\xff\x8d\xa5X\x06\x86\xe3\xd4\xf7O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\x90\x96\xd3C\xc0`\xdbX\x1a\x12\x01\x12\xb2x`~\xc6\xe5+\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x0e\x9cQ\x18d\xa1w\xf4\x9b\xe7\x82\x02w?`H\x9f\xe0NR\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x0e\xa2\xa2\x101+>\x86~\xe0\xd1\xcch,\xe1\xd6f\xf1\x8e\u054a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x0e\xb1\x89\xef,-Wb\xa9c\u05b7\xbd\xf9i\x8e\xa8\u7d0a\x89Hz\x9a0E9D\x00\x00\xe0\x94\x0e\xb5\xb6b\xa1\xc7\x18`\x8f\xd5/\f%\xf97\x880\x17\x85\x19\x8a\x01J7(\x1aa.t\x00\x00\xe0\x94\x0e\xc4f\x96\xff\xac\x1fX\x00_\xa8C\x98$\xf0\x8e\xed\x1d\xf8\x9b\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x0e\xc5\n\xa8#\xf4e\xb9FK\v\xc0\u0125w$\xa5U\xf5\u058a\f\x83\xd1Bj\u01f1\xf0\x00\x00\u07d4\x0e\xc50\x8b1(.!\x8f\xc9\xe7Y\xd4\xfe\xc5\xdb7\b\xce\u01096C\xaady\x86\x04\x00\x00\u07d4\x0e\xcc\xf6\x17\x84O\xd6\x1f\xbab\xcb\x0eD[z\u018b\xcc\x1f\xbe\x89\x14\xfeO\xe65e\xc6\x00\x00\u07d4\x0e\u04fb:N\xb5T\xcf\u0297\x94}WU\a\xcd\xfdm!\u0609\x1d\xb3 _\xcc#\u0540\x00\u07d4\x0e\xd7l,;]P\xff\x8f\xb5\v>\xea\xcdh\x15\x90\xbe\x1c-\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0e\u0680\xf4\xed\aJ\xeaiz\xed\xdf(;c\xdb\xca=\xc4\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4\x0e\xddKX\x0f\xf1\x0f\xe0lJ\x03\x11b9\xef\x96b+\xae5\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x0e\xe3\x91\xf0^\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x0f\x92\x9c\xf8\x95\xdb\x01z\xf7\x9f>\xad\"\x16\xb1\xbdi\xc3}\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x0f\xa0\x10\xce\fs\x1d;b\x8e6\xb9\x1fW\x13\x00\u477e\xab\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\x0f\xa5\xd8\u0173\xf2\x94\xef\u0515\xabi\xd7h\xf8\x18rP\x85H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x0f\xa6\u01f0\x97=\v\xae)@T\x0e$}6'\xe3|\xa3G\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x0f\xad\x05P|\u070f$\xb2\xbeL\xb7\xfa]\x92}\u06d1\x1b\x88\x89\xa2\xdf\x13\xf4A\xf0\t\x80\x00\u07d4\x0f\xb5\xd2\xc6s\xbf\xb1\xdd\xca\x14\x1b\x98\x94\xfdm?\x05\xdag \x89\x05k\xc7^-c\x10\x00\x00\u07d4\x0f\u0260\xe3AE\xfb\xfd\xd2\xc9\u04a4\x99\xb6\x17\u05e0)i\xb9\x89\t\xc2\x00vQ\xb2P\x00\x00\xe0\x94\x0f\xcf\xc4\x06P\b\xcf\xd3#0_b\x86\xb5zM\xd7\xee\xe2;\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x0f\xdde@#\x95\u07db\u045f\xeeE\a\xefSE\xf7E\x10L\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x0f\xecN\xe0\xd7\xca\x18\x02\x90\xb6\xbd \xf9\x99#B\xf6\x0f\xf6\x8d\x89\x12 \u007f\x0e\xdc\xe9q\x80\x00\u07d4\x0f\ue06c3\x1e\xfd\x8f\x81\x16\x1cW8+\xb4P{\xb9\xeb\xec\x89\x15\xaf\x88\r\x8c\u06c3\x00\x00\u07d4\x0f\xfe\xa0mq\x13\xfbj\xec(i\xf4\xa9\u07f0\x90\a\xfa\xce\xf4\x89\f8F\x81\xb1\xe1t\x00\x00\u07d4\x10\tq\x98\xb4\xe7\xee\x91\xff\x82\xcc/;\xd9_\xeds\xc5@\xc0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x10\vM\tw\xfc\xba\xd4\u07bd^d\xa0Iz\xea\xe5\x16\x8f\xab\x89\x11\f\x90s\xb5$Z\x00\x00\xe0\x94\x10\x1a\nd\xf9\xaf\xccD\x8a\x8a\x13\rM\xfc\xbe\xe8\x957\xd8T\x8a\x037\xfe_\xea\xf2\u0440\x00\x00\u07d4\x10,G}i\xaa\u06e9\xa0\xb0\xf6+tY\xe1\u007f\xbb\x1c\x15a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x101\xe0\xec\xb5I\x85\xae!\xaf\x17\x93\x95\r\xc8\x11\x88\x8f\xde|\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x104d\x14\xbe\xc6\xd3\xdc\xc4NP\xe5MT\u00b8\xc3sN>\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x108\x98X\xb8\x00\xe8\xc0\xec2\xf5\x1e\xd6\x1a5YF\xcc@\x9b\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x10Y\xcb\xc6>6\xc4>\x88\xf3\x00\b\xac\xa7\xce\x05\x8e\ua816\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\x10n\xd5\xc7\x19\xb5&\x14w\x89\x04%\xaeuQ\xdcY\xbd%\\\x8a\x02\x89jX\xc9[\xe5\x88\x00\x00\u07d4\x10q\x1c=\xda21x\x85\xf0\xa2\xfd\x8a\xe9.\x82\x06\x9b\r\v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x10sy\xd4\xc4gFO#[\xc1\x8eU\x93\x8a\xad>h\x8a\u05c9\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x10v!-Ou\x8c\x8e\xc7\x12\x1c\x1c}t%I&E\x92\x84\x8a\ai[Y\xb5\xc1{L\x00\x00\u07d4\x10x\xd7\xf6\x1b\x0eV\xc7N\xe6c[.\x18\x19\xef\x1e=\x87\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x10z\x03\xcf\bB\xdb\u07b0a\x8f\xb5\x87\xcai\x18\x9e\xc9/\xf5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x10\x80\xc1\xd85\x8a\x15\xbc\x84\xda\xc8%\x89\u0392\xb9\x81\x89t\xc1\xfa\xb8\xad\xb4T\x00\x00\u07d4\x10\xe1\xe37x\x85\xc4-}\xf2\x18R.\xe7vh\x87\xc0^j\x89\x10C\xc4<\xde\x1d9\x80\x00\u07d4\x10\u342d+\xa3=\x82\xb3s\x88\u041cED\u01b0\"]\xe5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x10\xf4\xbf\xf0\u02a5\x02|\nj-\xcf\xc9R\x82M\xe2\x94\t\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11\x00\x1b\x89\xed\x87>:\xae\xc1\x15V4\xb4h\x16C\x98c#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x11\x027\u03d1\x17\xe7g\x92/\u0121\xb7\x8dyd\u0682\xdf \x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x11\x11\xe5\xdb\xf4^o\x90mb\x86o\x17\b\x10\x17\x88\xdd\xd5q\x89F{\xe6S>\xc2\xe4\x00\x00\xe0\x94\x11\x17+'\x8d\xddD\xee\xa2\xfd\xf4\xcb\x1d\x16\x96#\x91\xc4S\u064a\xc6/=\x9b\xfdH\x95\xf0\x00\x00\u07d4\x11&4\xb4\xec0\xffxn\x02AY\xf7\x96\xa5y9\xea\x14N\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x110l}WX\x867x\x0f\xc9\xfd\xe8\xe9\x8e\xcb\x00\x8f\x01d\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x116\x12\xbc;\xa0\xeeH\x98\xb4\x9d\xd2\x023\x90_/E\x8fb\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x11A_\xaba\xe0\xdf\u0539\x06v\x14\x1aUz\x86\x9b\xa0\xbd\xe9\x89o\x05\xb5\x9d; \x00\x00\x00\u07d4\x11L\xbb\xbfo\xb5*\xc4\x14\xbe~\xc6\x1f{\xb7\x14\x95\xce\x1d\xfa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x11L\xfe\xfeP\x17\r\xd9z\xe0\x8f\nDTIx\u0159T\x8d\x89.\u0207\xe7\xa1J\x1c\x00\x00\u07d4\x11a\b\xc1 \x84a.\xed\xa7\xa9=\xdc\xf8\xd2`.'\x9e\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11d\u02aa\x8c\u0157z\xfe\x1f\xad\x8a}`(\xce-W)\x9b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x11gZ%UF\a\xa3\xb6\xc9*\x9e\xe8\xf3ou\xed\xd3\xe36\x89\b\xa9\xab\xa5W\xe3l\x00\x00\u07d4\x11j\t\xdff\xcb\x15\x0e\x97W\x8e)\u007f\xb0n\x13\x04\f\x89<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11o\xef^`\x16B\xc9\x18\u02c9\x16\x0f\xc2);\xa7\x1d\xa96\x89+|\xc2\xe9\xc3\"\\\x00\x00\u07d4\x11xP\x1f\xf9J\xdd\x1cX\x81\xfe\x88a6\xf6\xdf\xdb\xe6\x1a\x94\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x11y\xc6\r\xbd\x06\x8b\x15\v\aM\xa4\xbe#\x03; \u0185X\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\x11}\x9a\xa3\xc4\xd1;\xee\x12\xc7P\x0f\t\xf5\xdd\x1cf\xc4e\x04\x89\v*\xd3\x04\x90\xb2x\x00\x00\xe0\x94\x11}\xb867\u007f\xe1TU\xe0,.\xbd\xa4\v\x1c\xebU\x1b\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x11\x8c\x18\xb2\xdc\xe1p\xe8\xf4Eu;\xa5\xd7Q<\xb7cm-\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\x11\x8f\xbdu;\x97\x929Z\xefzMx\xd2c\xcd\u02ab\xd4\xf7\x8963\x03\"\xd5#\x8c\x00\x00\xe0\x94\x11\x92\x83x\xd2}U\xc5 \xce\xed\xf2L\xeb\x1e\x82-\x89\r\xf0\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x11\x9a\xa6M[}\x18\x1d\xae\x9d<\xb4I\x95\\\x89\xc1\xf9c\xfa\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\x11\xc05\x8a\xa6G\x9d\xe2\x18f\xfe!\a\x19$\xb6^p\xf8\xb9\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\xe0\x94\x11\xd2$z\"\x1ep\xc2\xd6m\x17\xee\x13\x8d8\xc5_\xfb\x86@\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x11\u05c4JG\x1e\xf8\x9a\x8d\x87uUX<\xee\xbd\x149\xea&\x8a\x02#i\u6e80\u0188\x00\x00\u07d4\x11\xdda\x85\u0668\xd7=\xdf\u06a7\x1e\x9bwtC\x1cM\xfe\u008965\u026d\xc5\u07a0\x00\x00\u07d4\x11\xe7\x99~\u0750E\x03\xd7}\xa6\x03\x8a\xb0\xa4\xc84\xbb\xd5c\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\x11\xec\x00\xf8I\xb61\x9c\xf5\x1a\xa8\u074ff\xb3U)\xc0\xbew\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x11\ufe22\x04Q\x16\x1bdJ\x8c\u03bb\xc1\xd3C\xa3\xbb\xcbR\x89\xadx\xeb\u016cb\x00\x00\x00\xe0\x94\x11\xfe\xfb]\xc1\xa4Y\x8a\xa7\x12d\fQwu\u07e1\xd9\x1f\x8c\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x12\x0f\x9d\xe6\xe0\xaf~\xc0*\a\xc6\t\u0284G\xf1W\xe64L\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x12\x10\xf8\v\u06c2l\x17Tb\xab\a\x16\xe6\x9eF\xc2J\xd0v\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x12\x13N\u007fk\x01{\xf4\x8e\x85Z9\x9c\xa5\x8e.\x89/\xa5\u020965\u026d\xc5\u07a0\x00\x00\u07d4\x12\x170t\x98\x01S\xae\xaaK\r\xcb\xc7\x13.\xad\xce\xc2\x1bd\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4\x12\x1f\x85[p\x14\x9a\xc84s\xb9po\xb4MG\x82\x8b\x98;\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x12'\xe1\nM\xbf\x9c\xac\xa3\x1b\x17\x80#\x9fUv\x15\xfc5\xc1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x12-\xcf\xd8\x1a\u0779}\x1a\x0eI%\u0135I\x80n\x9f;\xeb\x89R 5\xccn\x01!\x00\x00\u07d4\x12/V\x12%I\xd1h\xa5\xc5\xe2g\xf5&b\xe5\xc5\xcc\xe5\u0209\n\ad\a\xd3\xf7D\x00\x00\xe0\x94\x121o\xc7\xf1x\xea\xc2.\xb2\xb2Z\xed\xea\xdf=u\xd0\x01w\x8a\x04<3\xbe\x05\xf6\xbf\xb9\x80\x00\xe0\x94\x127Y\xf33\xe1>0i\xe2\x03KO\x059\x89\x18\x11\x9d6\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x12\\\xc5\xe4\xd5k+\xcc.\xe1\xc7\t\xfb\x9eh\xfb\x17t@\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x12c#\x88\xb2v^\xe4E+P\x16\x1d\x1f\xff\xd9\x1a\xb8\x1fJ\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\u07d4\x12h\x97\xa3\x11\xa1J\xd4;x\xe0\x92\x01\x00\xc4Bk\xfdk\u07494\xc7&\x89?-\x94\x80\x00\u07d4\x12m\x91\xf7\xad\x86\u07bb\x05W\xc6\x12\xca'n\xb7\xf9m\x00\xa1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x12}?\xc5\x00;\xf6<\r\x83\xe99W\x83e\x15\xfd'\x90E\x89\x06\x10\xc9\".nu\x00\x00\xe0\x94\x12}\xb1\xca\xdf\x1bw\x1c\xbdtu\xe1\xb2ri\x0fU\x8c\x85e\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x12\x84\xf0\xce\xe9\xd2\xff)\x89\xb6Ut\xd0o\xfd\x9a\xb0\xf7\xb8\x05\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x12\x8b\x90\x8f\xe7C\xa44 =\xe2\x94\xc4A\xc7\xe2\n\x86\xeag\x89&\xab\x14\xe0\xc0\xe1<\x00\x00\xe0\x94\x12\x93\u01cc}jD;\x9dt\xb0\xba^\xe7\xbbG\xfdA\x85\x88\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\x12\x96\xac\xde\xd1\xe0c\xaf9\xfe\x8b\xa0\xb4\xb6=\xf7\x89\xf7\x05\x17\x89\x05k\xf9\x1b\x1ae\xeb\x00\x00\u07d4\x12\xaa}\x86\xdd\xfb\xad0\x16\x92\xfe\xac\x8a\b\xf8A\xcb!\\7\x89\amA\xc6$\x94\x84\x00\x00\xe0\x94\x12\xaf\xbc\xba\x14'\xa6\xa3\x9e{\xa4\x84\x9fz\xb1\xc45\x8a\xc3\x1b\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x12\xb5\xe2\x89E\xbb)i\xf9\xc6Lc\xcc\x05\xb6\xf1\xf8\xd6\xf4\u054a\x01\xa2\x9e\x86\x91;t\x05\x00\x00\u0794\x12\u03cb\x0eFR\x13!\x1a[S\u07f0\xdd'\x1a(,\x12\u0248\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x12\xd2\a\x90\xb7\xd3\xdb\u060c\x81\xa2y\xb8\x12\x03\x9e\x8a`;\u0409V\xf9\x85\u04c6D\xb8\x00\x00\xe0\x94\x12\xd6\re\xb7\xd9\xfcH\x84\v\xe5\xf8\x91\xc7E\xcev\xeeP\x1e\x8a\x04\x85\xe58\x8d\fv\x84\x00\x00\u0794\x12\xd9\x1a\x92\xd7O\xc8a\xa7)dm\xb1\x92\xa1%\xb7\x9fSt\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\x12\u992d*\xd5t\x84\xddp\x05e\xbd\xdbFB;\u067d1\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x12\xf3,\n\x1f-\xaa\xb6v\xfei\xab\xd9\xe0\x185-L\xcdE\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x12\xf4`\xaedl\xd2x\x0f\xd3\\P\xa6\xafK\x9a\xcc\xfa\x85\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x12\xff\xc1\x12\x86\x05\xcb\f\x13p\x9ar\x90Po&\x90\x97q\x93\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x13\x03$F\xe7\xd6\x10\xaa\x00\xec\x8cV\u0275t\xd3l\xa1\xc0\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\x1cy,\x19}\x18\xbd\x04]p$\x93|\x1f\x84\xb6\x0fD8\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13\x1d\xf8\xd30\xeb|\xc7\x14}\nUWo\x05\u078d&\xa8\xb7\x89\n1\x06+\xee\xedp\x00\x00\u07d4\x13\x1f\xae\xd1%a\xbbz\xee\x04\xe5\x18Z\xf8\x02\xb1\xc3C\x8d\x9b\x89\v\xdf\x0e\u0733\x90\xc9\xc8V\b\xb7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13!\xcc\xf2\x979\xb9t\xe5\xa5\x16\xf1\x8f:\x846q\xe3\x96B\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13'\xd7Y\xd5n\n\xb8z\xf3~\xcfc\xfe\x01\xf3\x10\xbe\x10\n\x89#\xbc<\xdbh\xa1\x80\x00\x00\u07d4\x13)\xdd\x19\xcdK\xaa\x9f\xc6C\x10\xef\xec\xea\xb2!\x17%\x1f\x12\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x137\x1f\x92\xa5n\xa88\x1eC\x05\x9a\x95\x12\x8b\xdcMC\u0166\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x13O\x15\xe1\xe3\x9cSCY0\xaa\xed\xf3\xe0\xfeV\xfd\xe8C\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x13Ac\xbe\x9f\xbb\xe1\xc5in\xe2U\xe9\v\x13%C\x95\xc3\x18\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x13\\\xec\xd9U\xe5y\x83pv\x920\x15\x93\x03\u0671\x83\x9ff\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x13]\x17\x19\xbf\x03\xe3\xf8f1$y\xfe3\x81\x18\xcd8~p\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x13^\xb8\xc0\xe9\xe1\x01\xde\xed\xec\x11\xf2\xec\xdbf\xae\x1a\xae\x88g\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x13`\xe8}\xf2Li\xeemQ\xc7nsv\u007f\xfe\x19\xa2\x13\x1c\x89\x04\xfc\xc1\xa8\x90'\xf0\x00\x00\u07d4\x13l\x83K\xf1\x112m s\x95)[.X>\xa7\xf35r\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x13mKf+\xbd\x10\x80\xcf\xe4D[\x0f\xa2\x13\x86D5\xb7\xf1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13oI\a\u02b4\x1e'\bK\x98E\x06\x9f\xf2\xfd\f\x9a\xdey\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13t\xfa\xcd{?\x8dhd\x9d`\xd4U\x0e\xe6\x9f\xf0HA3\x89\x0e\x9e\xd6\xe1\x11r\xda\x00\x00\u07d4\x13|\xf3A\xe8Ql\x81X\x14\xeb\xcds\xe6V\x9a\xf1L\xf7\xbc\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x13\x84\x8bF\xeau\xbe\xb7\xea\xa8_Y\xd8f\xd7\u007f\xd2L\xf2\x1a\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x13\x9d51\u0252*\xd5bi\xf60\x9a\xa7\x89\xfb$\x85\xf9\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x13\x9eG\x97d\xb4\x99\xd6f \x8cJ\x8a\x04z\x97\x041c\u0749 w!*\xffm\xf0\x00\x00\u07d4\x13\xa5\xee\xcb80]\xf9Iq\xef-\x9e\x17\x9a\xe6\u03ba\xb37\x89\x11\u3ac3\x95\xc6\xe8\x00\x00\u07d4\x13\xac\xad\xa8\x98\n\xff\xc7PI!\xbe\x84\xebID\xc8\xfb\xb2\xbd\x89V\u04aa:\\\t\xa0\x00\x00\u07d4\x13\xb9\xb1\a\x15qL\t\xcf\xd6\x10\u03dc\x98F\x05\x1c\xb1\xd5\x13\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x13\xce3-\xffe\xa6\xab\x938\x97X\x8a\xa2>\x00\t\x80\xfa\x82\x89\x0e\x02\x056\xf0(\xf0\x00\x00\u07d4\x13\xd6z~%\xf2\xb1,\u06c5XP\t\xf8\xac\u011b\x96s\x01\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x13\xde\xe0>7\x99\x95-\a8\x84=K\xe8\xfc\n\x80?\xb2\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\xe0/\xb4H\xd6\xc8J\xe1}\xb3\x10\xad(m\x05a`\u0695\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x13\xe3!r\x8c\x9cWb\x80X\xe9?\xc8f\xa02\xdd\v\u0690\x89&\xbc\xca#\xfe.\xa2\x00\x00\u07d4\x13\xec\x81\"\x84\x02n@\x9b\xc0f\xdf\xeb\xf9\u0564\xa2\xbf\x80\x1e\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\x14\x01)\xea\xa7f\xb5\xa2\x9f[:\xf2WND\t\xf8\xf6\xd3\xf1\x8a\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u07d4\x14\x05\x18\xa3\x19K\xad\x13P\xb8\x94\x9ee\x05e\u07bem\xb3\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x14\x06\x85M\x14\x9e\b\x1a\xc0\x9c\xb4\xcaV\r\xa4c\xf3\x120Y\x89Hz\x9a0E9D\x00\x00\u07d4\x14\f\xa2\x8f\xf3;\x9ff\xd7\xf1\xfc\x00x\xf8\xc1\xee\xf6\x9a\x1b\xc0\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x14\x0f\xbaX\xdb\xc0H\x03\xd8L!0\xf0\x19x\xf9\xe0\xc71)\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\x14\x1a^9\xee/h\n`\x0f\xbfo\xa2\x97\u0790\xf3\"\\\u074a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x14%N\xa1&\xb5-\x01B\xda\n~\x18\x8c\xe2U\xd8\xc4qx\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\x14+\x87\xc5\x04?\xfbZ\x91\xdf\x18\xc2\xe1\t\xce\xd6\xfeJq\u06c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x14\x87\xf5\xa5$\u0288Q^\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4\x14\xa75 f6D\x04\xdbP\xf0\xd0\u05cduJ\"\x19\x8e\xf4\x89e\xea=\xb7UF`\x00\x00\u07d4\x14\xab\x16K;RL\x82\u05ab\xfb\xc0\u0783\x11&\xae\x8d\x13u\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x14\xb1`>\xc6+ \x02 3\xee\xc4\xd6\xd6eZ\xc2J\x01Z\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x14\xc6;\xa2\u0731\xddM\xf3=\u06b1\x1cO\x00\a\xfa\x96\xa6-\x8a\x03HA\xb6\x05z\xfa\xb0\x00\x00\xe0\x94\x14\xcd\u077c\x8b\t\xe6gZ\x9e\x9e\x05\t\x1c\xb9\"8\u00de\x1e\x8a\x01\x14x\xb7\xc3\n\xbc0\x00\x00\u07d4\x14\xd0\n\xad9\xa0\xa7\u045c\xa0SP\xf7\xb07'\xf0\x8d\xd8.\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x14\xee\xc0\x9b\xf0>5+\xd6\xff\x1b\x1e\x87k\xe6d\xce\xff\xd0\u03c9\x01\x16\xdc:\x89\x94\xb3\x00\x00\u07d4\x14\xf2!\x15\x95\x18x;\u0127\x06go\xc4\xf3\xc5\xee@X)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x14\xfc\xd19\x1e}s/Avl\xda\u0344\xfa\x1d\xeb\x9f\xfd\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x0e=\xbc\xbc\xfc\x84\xcc\xf8\x9bsBwc\xa5e\xc2>`\u0409\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\x15\x18b{\x885\x1f\xed\xe7\x96\xd3\xf3\b3d\xfb\u0508{\f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u0794\x15\"J\xd1\xc0\xfa\xceF\xf9\xf5V\xe4wJ0%\xad\x06\xbdR\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x15/+\xd2)\xdd\xf3\xcb\x0f\xda\xf4U\xc1\x83 \x9c\x0e\x1e9\xa2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x15/N\x86\x0e\xf3\xee\x80jP'w\xa1\xb8\xdb\xc9\x1a\x90vh\x89 \x86\xac5\x10R`\x00\x00\u07d4\x15<\b\xaa\x8b\x96\xa6\x11\xefc\xc0%>*C4\x82\x9eW\x9d\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\x15<\xf2\x84,\xb9\u0787l'o\xa6Gg\u0468\xec\xf5s\xbb\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15>\xf5\x8a\x1e.z>\xb6\xb4Y\xa8\n\xb2\xa5G\xc9A\x82\xa2\x8a\x14T+\xa1*3|\x00\x00\x00\u07d4\x15DY\xfa/!1\x8e44D\x97\x89\xd8&\xcd\xc1W\f\xe5\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15G\xb9\xbfz\xd6bt\xf3A8'#\x1b\xa4\x05\ue308\xc1\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\x15H\xb7p\xa5\x11\x8e\u0787\u06e2\xf6\x903\u007fam\u60eb\x89\x1c\x99V\x85\u0fc7\x00\x00\u07d4\x15R\x83P\xe0\xd9g\n.\xa2\u007f{J3\xb9\xc0\xf9b\x1d!\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x15[7y\xbbmV4./\u0681{[-\x81\xc7\xf4\x13'\x89\x02\xb8\xaa:\al\x9c\x00\x00\u07d4\x15e\xaf\x83~\xf3\xb0\xbdN+#V\x8dP#\xcd4\xb1d\x98\x89\x15Q\xe9rJ\u013a\x00\x00\u07d4\x15f\x91\x80\xde\u2558\x86\x9b\b\xa7!\xc7\xd2LL\x0e\xe6?\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x15r\xcd\xfa\xb7*\x01\u0396\x8ex\xf5\xb5D\x8d\xa2\x98S\xfb\u074a\x01\x12blI\x06\x0f\xa6\x00\x00\xe0\x94\x15uY\xad\xc5Wd\xccm\xf7\x93#\t%4\xe3\xd6dZf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x15x\xbd\xbc7\x1bM$8E3\x05V\xff\xf2\xd5\xefM\xffg\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x15~\xb3\xd3\x11;\u04f5\x97qM:\x95N\xdd\x01\x89\x82\xa5\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x84\xa2\xc0f\xb7\xa4U\xdb\u05ae(\a\xa73N\x83\xc3_\xa5\x89\a\f\x1c\xc7;\x00\xc8\x00\x00\u07d4\x15\x87F\x86\xb6s=\x10\xd7\x03\xc9\xf9\xbe\xc6\xc5.\xb8b\x8dg\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x15\x8a\ra\x92S\xbfD2\xb5\xcd\x02\u01f8b\xf7\u00b7V6\x89\a[\xac|[\x12\x18\x80\x00\u07d4\x15\x98\x12y\x82\xf2\xf8\xad;k\x8f\xc3\xcf'\xbfax\x01\xba+\x89\t`\xdbwh\x1e\x94\x00\x00\xe0\x94\x15\x9a\xdc\xe2z\xa1\vG#d)\xa3JZ\xc4,\xad[d\x16\x8a\x06\xbf\x90\xa9n\xdb\xfaq\x80\x00\u07d4\x15\xa0\xae\xc3\u007f\xf9\xff=T\t\xf2\xa4\xf0\xc1!*\xac\xcb\x02\x96\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x15\xaaS\r\xc3iX\xb4\xed\xb3\x8e\xeem\xd9\xe3\xc7}L\x91E\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15\xac\xb6\x15h\xecJ\xf7\xea(\x198a\x81\xb1\x16\xa6\xc5\xeep\x8a\x06\x90\x83l\n\xf5\xf5`\x00\x00\u07d4\x15\xb9o0\xc2;\x86d\xe7I\x06Q\x06k\x00\xc49\x1f\xbf\x84\x89\x16B\xe9\xdfHv)\x00\x00\u07d4\x15\xc7\xed\xb8\x11\x8e\xe2{4\"\x85\xebY&\xb4z\x85[\u01e5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x15\u0654hPz\xa0A?\xb6\r\xca*\xdc\u007fV\x9c\xb3kT\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x15\u06f4\x8c\x980\x97d\xf9\x9c\xed6\x92\xdc\xca5\xee0k\xac\x8a\x1f\u00c4+\xd1\xf0q\xc0\x00\x00\xe0\x94\x15\u072f\xcc+\xac\xe7\xb5[T\xc0\x1a\x1cQF&\xbfa\xeb\u060a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x15\u3d44\x05kb\xc9s\xcf^\xb0\x96\xf1s>T\xc1\\\x91\x892\xc7Z\x02#\xdd\xf3\x00\x00\u07d4\x15\xeb\xd1\xc7\xca\u04af\xf1\x92u\xc6W\xc4\xd8\b\xd0\x10\xef\xa0\xf5\x89\n\xdf0\xbap\u0217\x00\x00\u07d4\x15\xee\x0f\xc6>\xbf\x1b\x1f\u011d{\xb3\x8f\x88c\x82:.\x17\u0489g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x15\xf1\xb3R\x11\rh\x90\x1d\x8fg\xaa\xc4jl\xfa\xfe\x03\x14w\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x15\xf2\xb7\xb1d2\xeeP\xa5\xf5[A#/c4\xedX\xbd\xc0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x16\x01\x9aM\xaf\xabC\xf4\u067fAc\xfa\xe0\x84}\x84\x8a\xfc\xa2\x89\x01[\xc7\x019\xf7J\x00\x00\u07d4\x16\x02&\xef\xe7\xb5:\x8a\xf4b\xd1\x17\xa0\x10\x80\x89\xbd\xec\xc2\u0449\n\xdf0\xbap\u0217\x00\x00\u07d4\x16\f\xebo\x98\x0e\x041_S\xc4\xfc\x98\x8b+\xf6\x9e(M}\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\xe0\x94\x16\x1c\xafZ\x97*\u0383y\xa6\u0420J\xe6\xe1c\xfe!\xdf+\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x16\x1d&\xefgY\xba[\x9f \xfd\xcdf\xf1a2\xc3RA^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16!\x10\xf2\x9e\xac_}\x02\xb5C\xd8\xdc\u057bY\xa5\xe3;s\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x16+\xa5\x03'b\x14\xb5\t\xf9u\x86\xbd\x84!\x10\xd1\x03\xd5\x17\x8a\x01\xe7\xff\u0609\\\"h\x00\x00\u07d4\x16-v\xc2\xe6QJ:\xfbo\xe3\xd3\u02d3\xa3\\Z\xe7\x83\xf1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16;\xadJ\x12+E}d\xe8\x15\nA>\xaeM\a\x02>k\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u07d4\x16<\u023e\"vF\xcb\tq\x91Y\xf2\x8e\u041c]\xc0\xdc\xe0\x89Hz\x9a0E9D\x00\x00\u07d4\x16=\xcas\xd7\xd6\xea?>`b2*\x874\x18\f\vx\uf25ft \x03\xcb}\xfc\x00\x00\u07d4\x16Mz\xac>\xec\xba\uc86dQ\x91\xb7S\xf1s\xfe\x12\xec3\x89(VR\xb8\xa4hi\x00\x00\u07d4\x16Rl\x9e\u07d4>\xfaOm\x0f\v\xae\x81\xe1\x8b1\xc5@y\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x16S\x05\xb7\x872.%\xdcj\xd0\xce\xfelo3Fx\xd5i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16e\xab\x179\xd7\x11\x19\xeea2\xab\xbd\x92j'\x9f\xe6yH\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x16k\xf6\u06b2-\x84\x1bHl8\xe7\xbaj\xb3:\x14\x87\ud30a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x16v\x99\xf4\x8ax\xc6\x15Q%\x15s\x99X\x993\x12WO\a\x89\x02\x1d;\xd5^\x80<\x00\x00\u07d4\x16x\xc5\xf2\xa5\"92%\x19ca\x89OS\xccu/\xe2\xf3\x89h\xf3e\xae\xa1\xe4@\x00\x00\u07d4\x16|\xe7\xdee\xe8G\bYZRT\x97\xa3\xeb^ZfPs\x89\x1f1Gsfo\xc4\x00\x00\u07d4\x16~>:\xe2\x003HE\x93\x92\xf7\xdf\xceD\xaf|!\xadY\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x16\x80\xce\xc5\x02\x1e\xe90P\xf8\xae\x12rQ\x83\x9et\xc1\xf1\xfd\x8a\x02\xc6\x14a\xe5\xd7C\u0580\x00\u07d4\x16\x81j\xac\x0e\xde\r-<\xd4B\xday\xe0c\x88\x0f\x0f\x1dg\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x16\x8bP\x19\xb8\x18i\x16D\x83_\xe6\x9b\xf2)\xe1q\x12\xd5,\x8a\x05\xed\xe2\x0f\x01\xa4Y\x80\x00\x00\u07d4\x16\x8b\xde\xc8\x18\xea\xfcm)\x92\xe5\xefT\xaa\x0e\x16\x01\xe3\xc5a\x8967Pz0\xab\xeb\x00\x00\u07d4\x16\x8d0\xe5?\xa6\x81\t+R\xe9\xba\xe1Z\r\xcbA\xa8\u027b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x16\x9b\xbe\xfcA\xcf\xd7\xd7\u02f8\xdf\xc60 \xe9\xfb\x06\u0515F\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xa5\x8e\x98]\xcc\xd7\a\xa5\x94\u0453\xe7\u0327\x8b]\x02xI\x89I\xb9\u029aiC@\x00\x00\u07d4\x16\xa9\xe9\xb7:\u92c6M\x17(y\x8b\x87f\xdb\xc6\xea\x8d\x12\x893\xe7\xb4K\r\xb5\x04\x00\x00\u07d4\x16\xaaR\xcb\vUG#\xe7\x06\x0f!\xf3'\xb0\xa6\x83\x15\xfe\xa3\x89\r\x8drkqw\xa8\x00\x00\u07d4\x16\xab\xb8\xb0!\xa7\x10\xbd\u01ce\xa54\x94\xb2\x06\x14\xffN\xaf\xe8\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x16\xaf\xa7\x87\xfc\x9f\x94\xbd\xffiv\xb1\xa4/C\n\x8b\xf6\xfb\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xba\xe5\xd2N\xff\x91w\x8c\u064bM:\x1c\xc3\x16/D\xaaw\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\x16\xbc@!Z\xbb\u066e](\v\x95\xb8\x01\vE\x14\xff\x12\x92\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x16\xbeu\u9299Z9R\"\xd0\v\u05df\xf4\xb6\xe68\u144a\a\x9f\x90\\o\xd3N\x80\x00\x00\u07d4\x16\xc1\xbf[}\xc9\xc8<\x17\x9e\xfa\xcb\xcf.\xb1t\xe3V\x1c\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x16\u01f3\x1e\x8c7b\x82\xac\"qr\x8c1\xc9^5\xd9R\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xf3\x13\u03ca\xd0\x00\x91J\n\x17m\u01a44+y\xec%8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x16\xff\xac\x84\x03)@\xf0\x12\x1a\tf\x8b\x85\x8a~y\xff\xa3\xbb\x89\xd2J\xdan\x10\x87\x11\x00\x00\xe0\x94\x17\x03\xb4\xb2\x92\xb8\xa9\xde\xdd\xed\xe8\x1b\xb2]\x89\x17\x9fdF\xb6\x8a\x04+e\xa4U\xe8\xb1h\x00\x00\u07d4\x17\x04\x93\x11\x10\x1d\x81~\xfb\x1de\x91\x0ff6b\xa6\x99\u024c\x89lh\xcc\u041b\x02,\x00\x00\u07d4\x17\x04\xce\xfc\xfb\x131\xeczx8\x8b)9>\x85\xc1\xafy\x16\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17\n\x88\xa8\x99\u007f\x92\xd287\x0f\x1a\xff\xde\xe64pP\xb0\x13\x89\xa2\xacw5\x14\x880\x00\x00\u07d4\x17\x10\x8d\xab,P\xf9\x9d\xe1\x10\u1cf3\xb4\u0342\xf5\xdf(\xe7\x895 ;g\xbc\xca\xd0\x00\x00\xe0\x94\x17\x12[Y\xacQ\xce\xe0)\xe4\xbdx\xd7\xf5\x94}\x1e\xa4\x9b\xb2\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4\x17\x1a\u0660K\xed\u0238a\xe8\xedK\xdd\xf5qx\x13\xb1\xbbH\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17\x1c\xa0*\x8bmb\xbfL\xa4~\x90i\x14\a\x98a\x97,\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x17\"\xc4\xcb\xe7\n\x94\xb6U\x9dBP\x84\xca\xee\xd4\xd6\xe6n!\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x17X\vvotSR\\\xa4\u01a8\x8b\x01\xb5\x05p\xea\b\x8c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x17X\x9al\x00jT\xca\xd7\x01\x03\x12:\xae\n\x82\x13_\u07b4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x17Z\x18::#_\xfb\xb0;\xa85gRg\"\x94\x17\xa0\x91\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x17_\xee\xea*\xa4\xe0\xef\xda\x12\xe1X\x8d/H2\x90\xed\xe8\x1a\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x17e6\x1c.\xc2\xf86\x16\u0383c\xaa\xe2\x10%\xf2Vo@\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x17gR\\_Z\"\xed\x80\xe9\xd4\xd7q\x0f\x03b\u049e\xfa3\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x17v%`\xe8*\x93\xb3\xf5\"\xe0\xe5$\xad\xb8a,:tp\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x17}\xaex\xbc\x01\x13\xd8\u04dcD\x02\xf2\xa6A\xae*\x10Z\xb8\x89b\x92BV \xb4H\x00\x00\xe0\x94\x17\x84\x94\x8b\xf9\x98H\u021eDV8PM\u0598'\x1bY$\x8a\x01GLA\r\x87\xba\xee\x00\x00\u07d4\x17\x88\u069bW\xfd\x05\xed\xc4\xff\x99\xe7\xfe\xf3\x01Q\x9c\x8a\n\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\x8e\xafk\x85T\xc4]\xfd\xe1kx\xce\f\x15\u007f.\xe3\x13Q\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\x17\x96\x1dc;\xcf \xa7\xb0)\xa7\xd9K}\xf4\xda.\xc5B\u007f\x89\fo\xf0p\U000532c0\x00\u07d4\x17\x96\xbc\xc9{\x8a\xbcq\u007fKJ|k\x106\xea!\x82c\x9f\x89\x13A\xf9\x1c\xd8\xe3Q\x00\x00\u07d4\x17\x99=1*\xa1\x10iW\x86\x8fjU\xa5\xe8\xf1/w\xc8C\x89\x18e\xe8\x14\xf4\x14.\x80\x00\u07d4\x17\x9a\x82^\x0f\x1fn\x98S\tf\x84e\xcf\xfe\xd46\xf6\xae\xa9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x17\xb2\xd6\xcfe\xc6\xf4\xa3G\xdd\xc6W&U5M\x8aA+)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xb8\a\xaf\xa3\xdd\xd6G\xe7#T.{R\xfe\xe3\x95'\xf3\x06\x89\x15\xaf@\xff\xa7\xfc\x01\x00\x00\u07d4\x17\xc0G\x86W\xe1\xd3\xd1z\xaa3\x1d\xd4)\xce\u03d1\xf8\xae]\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\x17\xc0\xfe\xf6\x98l\xfb.@A\xf9\x97\x9d\x99@\xb6\x9d\xff=\xe2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x17\u0511\x8d\xfa\xc1]w\xc4\u007f\x9e\xd4\x00\xa8P\x19\rd\xf1Q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xd5!\xa8\xd9w\x90#\xf7\x16M#<;d \xff\xd2#\xed\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x17\xd91\xd4\xc5b\x94\u073ew\xc8e[\xe4i_\x00mJ<\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xdfIQ\x8ds\xb1)\xf0\xda6\xb1\u0274\f\xb6d \xfd\u01ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x17\xe4\xa0\xe5+\xac>\xe4N\xfe\tT\xe7S\u0538]dN\x05\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xe5\x84\xe8\x10\xe5gp,a\xd5]CK4\u0375\xee0\xf6\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x17\xe8.px\xdcO\xd9\xe8y\xfb\x8aPf\u007fS\xa5\xc5E\x91\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x17\xe8o;[0\xc0\xbaY\xf2\xb2\xe8XB[\xa8\x9f\n\x10\xb0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x17\xee\x9fT\xd4\xdd\xc8Mg\x0e\xff\x11\xe5Je\x9f\xd7/DU\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94\x17\xefJ\xcc\x1b\xf1G\xe3&t\x9d\x10\xe6w\xdc\xff\xd7o\x9e\x06\x8a\bwQ\xf4\xe0\xe1\xb50\x00\x00\u07d4\x17\xf1F2\xa7\xe2\x82\v\xe6\xe8\xf6\u07c25X(=\xad\xab-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x17\xf5#\xf1\x17\xbc\x9f\xe9x\xaaH\x1e\xb4\xf5V\x17\x117\x1b\u0209li\xf7>)\x13N\x00\x00\u07d4\x17\xfd\x9bU\x1a\x98\xcba\xc2\xe0\u007f\xbfA\xd3\xe8\u02650\u02e5\x89\x01v\x8c0\x81\x93\x04\x80\x00\u07d4\x18\x04x\xa6U\u05cd\x0f;\fO +aH[\xc4\x00/\u0549lk\x93[\x8b\xbd@\x00\x00\u07d4\x18\x13l\x9d\xf1g\xaa\x17\xb6\xf1\x8e\"\xa7\x02\u020fK\u0082E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x18\x15'\x9d\xff\x99R\xda;\xe8\xf7rI\xdb\xe2\"C7{\xe7\x8a\x01\x01|\xb7n{&d\x00\x00\u07d4\x18\x1f\xbb\xa8R\xa7\xf5\x01x\xb1\xc7\xf0>\xd9\xe5\x8dT\x16))\x89$\x1a\x9bOaz(\x00\x00\xe0\x94\x18'\x03\x9f\tW\x02\x94\b\x8f\xdd\xf0G\x16\\3\u65a4\x92\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\x18-\xb8R\x93\xf6\x06\u8248\xc3pL\xb3\xf0\xc0\xbb\xbf\xcaZ\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x18H\x00<%\xbf\u052a\x90\xe7\xfc\xb5\u05f1k\xcd\f\xff\xc0\u060965\u026d\xc5\u07a0\x00\x00\xe0\x94\x18JO\v\xebq\xff\xd5X\xa6\xb6\xe8\xf2(\xb7\x87\x96\xc4\xcf>\x8a\x02\x8a\x85t%Fo\x80\x00\x00\xe0\x94\x18M\x86\xf3Fj\xe6h;\x19r\x99\x82\xe7\xa7\u1903G\xb2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x18Q\xa0c\xcc\xdb0T\x90w\xf1\xd19\xe7-\xe7\x97\x11\x97\u0549lk\x93[\x8b\xbd@\x00\x00\u07d4\x18UF\xe8v\x8dPhs\x81\x8a\xc9u\x1c\x1f\x12\x11j;\xef\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x18X\xcf\x11\xae\xa7\x9fS\x98\xad+\xb2\"g\xb5\xa3\xc9R\xeat\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\xe0\x94\x18Z\u007f\u012c\xe3h\xd23\xe6 \xb2\xa4Y5f\x12\x92\xbd\xf2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x18d\xa3\u01f4\x81UD\x8cT\u020cp\x8f\x16g\tsm1\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x18j\xfd\xc0\x85\xf2\xa3\xdc\xe4a^\xdf\xfb\xad\xf7\x1a\x11x\x0fP\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x18k\x95\xf8\xe5\xef\xfd\xdc\xc9O\x1a1[\xf0)];\x1e\xa5\x88\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x18}\x9f\f\a\xf8\xebt\xfa\xaa\xd1^\xbc{\x80Dt\x17\xf7\x82\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x18\x95\xa0\xebJCrr/\xcb\u016f\xe6\x93o(\x9c\x88\xa4\x19\x891T\xc9r\x9d\x05x\x00\x00\u07d4\x18\x99\xf6\x9fe;\x05\xa5\xa6\xe8\x1fH\a\x11\u041b\xbf\x97X\x8c\x89i\xfb\x13=\xf7P\xac\x00\x00\u07d4\x18\xa6\xd2\xfcR\xbes\b@#\xc9\x18\x02\xf0[\xc2JK\xe0\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x18\xb0@|\xda\xd4\xceR`\x06#\xbd^\x1fj\x81\xaba\xf0&\x89\x11Q\xcc\xf0\xc6T\u0180\x00\u07d4\x18\xb8\xbc\xf9\x83!\xdaa\xfbN>\xac\xc1\xecT\x17'-\xc2~\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\x18\xc6r:gS)\x9c\xb9\x14G}\x04\xa3\xbd!\x8d\xf8\xc7u\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x18\xe1\x13\xd8\x17|i\x1aa\xbexXR\xfa[\xb4z\uef6f\x89Hz\x9a0E9D\x00\x00\xe0\x94\x18\xe4\xceGH;S\x04\n\u06eb5\x17,\x01\xefdPn\f\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\xe0\x94\x18\xe52C\x98\x1a\xab\xc8v}\xa1\fsD\x9f\x13\x91V\x0e\xaa\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x18\xfa\x86%\xc9\u0704>\x00\x15\x9e\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94\x193\xe34\xc4\x0f:\u02ed\f\v\x85\x11X i$\xbe\xca:\x8a\x01\x99^\xaf\x01\xb8\x96\x18\x80\x00\xe0\x94\x197\xc5\xc5\x15\x05uS\u033dF\u0546dU\xcef)\x02\x84\x8a\xd3\xc2\x1b\xce\xcc\xed\xa1\x00\x00\x00\u07d4\x19:\xc6Q\x83e\x18\x00\xe25\x80\xf8\xf0\xea\u04fbY~\xb8\xa4\x89\x02\xb6*\xbc\xfb\x91\n\x00\x00\u07d4\x19=7\xed4}\x1c/N55\r\x9aDK\xc5|\xa4\xdbC\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\x19@\u0713d\xa8R\x16_GAN'\xf5\x00$E\xa4\xf1C\x8a\x02L-\xffj<|H\x00\x00\u07d4\x19E\xfe7\u007f\xe6\u0537\x1e>y\x1fo\x17\xdb$<\x9b\x8b\x0f\x89vy\u7fb9\x886\x00\x00\u07d4\x19Jk\xb3\x02\xb8\xab\xa7\xa5\xb5y\u07d3\xe0\xdf\x15t\x96v%\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x19L\ubd12\x98\x82\xbf;K\xf9\x86L+\x1b\x0fb\u0083\xf9\x89\x1e\xf8aS\x1ft\xaa\x00\x00\u07d4\x19O\xf4J\xef\xc1{\xd2\x0e\xfdz LG\xd1b\f\x86\xdb]\x89\xa2\x99\th\u007fj\xa4\x00\x00\xe0\x94\x19O\xfex\xbb\xf5\xd2\r\u044a\x1f\x01\xdaU.\x00\xb7\xb1\x1d\xb1\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x19S1>*\xd7F#\x9c\xb2'\x0fH\xaf4\u063b\x9cDe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19W\x1a+\x8f\x81\u01bc\xf6j\xb3\xa1\x00\x83)V\x17\x15\x00\x03\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\xe0\x94\x19h}\xaa9\xc3h\x13\x9bn{\xe6\r\xc1u:\x9f\f\xbe\xa3\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x19l\x02!\nE\n\xb0\xb3cpe_qz\xa8{\xd1\xc0\x04\x89\x0e\x10\xac\xe1W\xdb\xc0\x00\x00\u07d4\x19n\x85\xdf~s+J\x8f\x0e\xd06#\xf4\u06dd\xb0\xb8\xfa1\x89\x01%\xb9/\\\xef$\x80\x00\u07d4\x19s+\xf9s\x05]\xbd\x91\xa4S:\u06a2\x14\x9a\x91\u04c3\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19vr\xfd9\xd6\xf2F\xcef\xa7\x90\xd1:\xa9\"\xd7\x0e\xa1\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x19y\x8c\xbd\xa7\x15\ua69b\x9dj\xab\x94,U\x12\x1e\x98\xbf\x91\x89A\rXj \xa4\xc0\x00\x00\u07d4\x19\x8b\xfc\xf1\xb0z\xe3\b\xfa,\x02\x06\x9a\xc9\xda\xfeq5\xfbG\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x19\x8e\xf1\xec2Z\x96\xcc5Lrf\xa08\xbe\x8b\\U\x8fg\x8a\x80\xd1\xe47>\u007f!\xda\x00\x00\xe0\x94\x19\x91\x8a\xa0\x9e}IN\x98\xff\xa5\xdbP5\b\x92\xf7\x15j\u018a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x19\xb3k\f\x87\xeafN\xd8\x03\x18\xdcw\xb6\x88\xdd\xe8}\x95\xa5\x89i\x9fI\x98\x020=\x00\x00\u07d4\x19\u07d4E\xa8\x1c\x1b=\x80J\xea\xebon NB6f?\x89\x02\x06\xd9NjI\x87\x80\x00\u07d4\x19\xe5\u07a37\n,tj\xae4\xa3|S\x1fA\xda&N\x83\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x19\xe7\xf3\xeb{\xf6\u007f5\x99 \x9e\xbe\b\xb6*\xd32\u007f\x8c\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\x19\xe9Nb\x00P\xaa\xd7f\xb9\xe1\xba\xd91#\x83\x12\u053fI\x89\x81\xe3-\xf9r\xab\xf0\x00\x00\u07d4\x19\xec\xf2\xab\xf4\f\x9e\x85{%/\xe1\xdb\xfd=L]\x8f\x81n\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x19\xf5\xca\xf4\xc4\x0ei\b\x81<\aE\xb0\xae\xa9Xm\x9d\xd91\x89#\xfe\xd9\xe1\xfa+`\x00\x00\u07d4\x19\xf6C\xe1\xa8\xfa\x04\xae\x16\x00`(\x13\x833\xa5\x9a\x96\u0787\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x19\xf9\x9f,\vF\u0389\x06\x87]\xc9\xf9\n\xe1\x04\xda\xe3U\x94\x89\xf4WZ]M\x16*\x00\x00\u07d4\x19\xff$O\xcf\xe3\xd4\xfa/O\u065f\x87\xe5[\xb3\x15\xb8\x1e\xb6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1a\x04\xce\xc4 \xadC\"\x15$mw\xfe\x17\x8d3\x9e\u0435\x95\x89\x11!a\x85\u009fp\x00\x00\xe0\x94\x1a\x04\xd58\x9e\xb0\x06\xf9\u0388\f0\xd1SS\xf8\xd1\x1cK1\x8a\x03\x9d\x84\xb2\x18m\xc9\x10\x00\x00\u07d4\x1a\bA\xb9*\u007fpuV\x9d\xc4b~kv\u02b0Z\u0791\x89Rf<\u02b1\xe1\xc0\x00\x00\xe0\x94\x1a\b]C\xec\x92AN\xa2{\x91O\xe7g\xb6\xd4k\x1e\xefD\x8a\x06A\xe8\xa15c\xd8\xf8\x00\x00\u07d4\x1a\t\xfd\xc2\u01e2\x0e#WK\x97\u019e\x93\u07bag\xd3r \x89lO\xd1\xee$nx\x00\x00\u07d4\x1a\n\x1d\u07f01\xe5\xc8\xcc\x1dF\xcf\x05\x84-P\xfd\xdcq0\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1a\x1c\x9a&\xe0\xe0$\x18\xa5\xcfh}\xa7Z'\\b,\x94@\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x1a \x1bC'\u03a7\xf3\x99\x04bF\xa3\xc8~n\x03\xa3\u0368\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a$4\xccwD\"\u050dS\u055c]V,\u0384\a\xc9K\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\x1a%\xe1\u017c~_P\xec\x16\xf8\x88_!\x0e\xa1\xb98\x80\x0e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x1a&\x94\xec\a\xcf^Mh\xba@\xf3\xe7\xa1LS\xf3\x03\x8cn\x8966\xcd\x06\xe2\xdb:\x80\x00\u07d4\x1a5 E5\x82\xc7\x18\xa2\x1cB7[\xc5\as%RS\xe1\x89*\xd3s\xcef\x8e\x98\x00\x00\xe0\x94\x1a7n\x1b-/Y\ai\xbb\x85\x8dEu2\rN\x14\x99p\x8a\x01\x06q%v9\x1d\x18\x00\x00\u07d4\x1a:3\x0eO\xcbi\xdb\xef^i\x01x;\xf5\x0f\xd1\xc1SB\x89\u3bb5sr@\xa0\x00\x00\u07d4\x1aN\u01a0\xae\u007fZ\x94'\xd2=\xb9rL\r\f\xff\xb2\xab/\x89\t\xb4\x1f\xbf\x9e\n\xec\x00\x00\u07d4\x1aP^b\xa7N\x87\xe5wG>O:\xfa\x16\xbe\xdd<\xfaR\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x1a^\xe53\xac\xbf\xb3\xa2\xd7m[hRw\xb7\x96\xc5j\x05+\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1adJP\xcb\u00ae\xe8#\xbd+\xf2C\xe8%\xbeMG\xdf\x02\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\x1apD\xe28?\x87\b0[I[\xd1\x17k\x92\xe7\xef\x04:\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1ay\xc7\xf4\x03\x9cg\xa3\x9du\x13\x88L\xdc\x0e,4\"$\x90\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1a\x89\x89\x9c\xbe\xbd\xbbd\xbb&\xa1\x95\xa6<\bI\x1f\u035e\xee\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1a\x8a\\\xe4\x14\u079c\xd1r\x93~7\xf2\u055c\xffq\xceW\xa0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1a\x95\xa8\xa8\b.FR\xe4\x17\r\xf9'\x1c\xb4\xbbC\x05\xf0\xb2\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x1a\x95\u0277Tk]\x17\x86\u00c5\x8f\xb1#dF\xbc\f\xa4\u0389j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1a\x98~?\x83\xdeu\xa4/\x1b\xde|\x99|\x19!{J_$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1a\x9ep/8]\xcd\x10^\x8b\x9f\xa4(\xee\xa2\x1cW\xffR\x8a\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1a\xa1\x02\x1fU\n\xf1X\xc7Gf\x8d\xd1;F1`\xf9Z@\x89O\xb0Y\x1b\x9b08\x00\x00\u07d4\x1a\xa2v\x99\xca\u068d\u00e7oy3\xaaf\xc7\x19\x19\x04\x0e\x88\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x1a\xa4\x02p\xd2\x1e\\\u0786\xb61m\x1a\xc3\xc53IKy\xed\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1a\xb5:\x11\xbc\xc6=\u07ea@\xa0+\x9e\x18d\x96\u037b\x8a\xff\x89l?*\xac\x80\f\x00\x00\x00\u07d4\x1a\xbcN%;\b\n\xebCy\x84\xab\x05\xbc\xa0\x97\x9a\xa4>\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a\xc0\x89\u00fcM\x82\xf0j \x05\x1a\x9ds-\xc0\xe74\xcba\x89%\xf6\x9dc\xa6\xce\x0e\x00\x00\xe0\x94\x1a\xd4V>\xa5xk\xe1\x15\x995\xab\xb0\xf1\u0547\x9c>sr\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1a\xd7- \xa7n\u007f\xcckv@X\xf4\x8dA}Io\xa6\u0349lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1a\xda\xf4\xab\xfa\x86}\xb1\u007f\x99\xafj\xbe\xbfpz<\xf5]\xf6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1a\xf6\x03C6\x0e\v-u%R\x107W \xdf!\xdb\\}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1a\xfc\u0145\x89l\xd0\xed\xe1)\xee-\xe5\xc1\x9e\xa8\x11T\vd\x89\xaf*\xba\f\x8e[\xef\x80\x00\u07d4\x1b\x05\xeajj\u022f|\xb6\xa8\xb9\x11\xa8\xcc\xe8\xfe\x1a*\xcf\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\v1\xaf\xffKm\xf3e:\x94\xd7\xc8yx\xae5\xf3J\xae\x89\x139\x10E?\xa9\x84\x00\x00\u07d4\x1b\r\ah\x17\xe8\u058e\xe2\xdfN\x1d\xa1\xc1\x14-\x19\x8cD5\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\x1b\x13\ro\xa5\x1d\\H\xec\x8d\x1dR\u070a\"{\xe8s\\\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b#\u02c6cUHq\xfb\xbe\r\x9e`9~\xfbo\xae\xdc>\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1b&9X\x8bU\xc3D\xb0#\xe8\xde_\xd4\b{\x1f\x04\x03a\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x1b9 \xd0\x01\xc4>r\xb2N|\xa4o\x0f\xd6\xe0\xc2\n_\xf2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1b<\xb8\x1eQ\x01\x1bT\x9dx\xbfr\v\r\x92J\xc7c\xa7\u008av\x95\xa9, \xd6\xfe\x00\x00\x00\u07d4\x1bC#,\xcdH\x80\xd6\xf4o\xa7Q\xa9l\xd8$s1XA\x89\x04V9\x18$O@\x00\x00\u07d4\x1bK\xbc\xb1\x81e!\x1b&[(\a\x16\xcb?\x1f!!v\xe8\x89\x19\x9a\xd3}\x03\xd0`\x80\x00\u07d4\x1bM\a\xac\u04c1\x83\xa6\x1b\xb2x=+{\x17\x8d\xd5\x02\xac\x8d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1bckzIo\x04MsYYn5:\x10F\x16Cok\x89\x13\x88\xea\x95\xc3?\x1d\x00\x00\u07d4\x1bd\x95\x89\x12@\xe6NYD\x93\xc2f!q\xdb^0\xce\x13\x89\tX\x87\u0595\xedX\x00\x00\u07d4\x1bf\x10\xfbh\xba\xd6\xed\x1c\xfa\xa0\xbb\xe3:$\xeb.\x96\xfa\xfb\x89\b=lz\xabc`\x00\x00\u07d4\x1by\x903\xefm\xc7\x12x\"\xf7EB\xbb\"\xdb\xfc\t\xa3\b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x1b~\xd9t\xb6\xe24\u0381$t\x98B\x9a[\u0520\xa2\xd19\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\x82o\xb3\xc0\x12\xb0\xd1Y\u253a[\x8aI\x9f\xf3\xc0\xe0<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\x8a\xa0\x16\f\u05df\x00_\x88Q\nqI\x13\xd7\n\u04fe3\x89\n\xef\xfb\x83\a\x9a\xd0\x00\x00\xe0\x94\x1b\x8b\xd6\xd2\xec\xa2\x01\x85\xa7\x8e}\x98\xe8\xe1\x85g\x8d\xacH0\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\x1b\x9b-\u0096\x0eL\xb9@\x8ft\x05\x82|\x9bY\a\x16\x12\xfd\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1b\xa9\"\x8d8\x87'\xf3\x89\x15\x0e\xa0;s\xc8-\xe8\xeb.\t\x8a\x01\x89t\xfb\xe1w\xc9(\x00\x00\u07d4\x1b\xa9\xf7\x99~S\x87\xb6\xb2\xaa\x015\xac$R\xfe6\xb4\xc2\r\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\x1b\xba\x03\xffkJ\u057f\x18\x18J\xcb!\xb1\x88\xa3\x99\xe9\xebJ\x89a\t=|,m8\x00\x00\u07d4\x1b\xbc\x19\x9eXg\x90\xbe\x87\xaf\xed\xc8I\xc0G&t\\]{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x1b\xbc`\xbc\xc8\x0e\\\xdc5\xc5Aj\x1f\n@\xa8=\xae\x86{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xc4L\x87a#\x1b\xa1\xf1\x1f_\xaa@\xfaf\x9a\x01>\x12\u0389\v\tR\xc4Z\xea\xad\x00\x00\u07d4\x1b\xcf4A\xa8f\xbd\xbe\x960\t\xce3\xc8\x1c\xbb\x02a\xb0,\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\x1b\u048c\xd5\u01ca\xeeQ5|\x95\xc1\xef\x925\xe7\xc1\x8b\xc8T\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xd8\xeb\xaavt\xbb\x18\u1458\xdb$OW\x03\x13\a_C\x89\b!\xab\rD\x14\x98\x00\x00\u07d4\x1b\xd9\t\xac\rJ\x11\x02\xec\x98\xdc\xf2\u0329j\n\xdc\u05e9Q\x89\x01\x16Q\xac>zu\x80\x00\u07d4\x1b\xe3T,6\x13hte\xf1Zp\xae\xeb\x81f+e\u0328\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1b\xeaM\xf5\x12/\xaf\u07b3`~\xdd\xda\x1e\xa4\xff\u06da\xbf*\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d4\x1b\xecM\x02\u0385\xfcH\xfe\xb6$\x89\x84\x1d\x85\xb1pXj\x9b\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\x1b\xf9t\u0650OE\u0381\xa8E\xe1\x1e\xf4\xcb\xcf'\xafq\x9e\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x1c\x04VI\xcdS\xdc#T\x1f\x8e\xd4\xd3A\x81(\b\xd5\u075c\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x1c\x12\x8b\xd6\u0365\xfc\xa2uu\xe4\xb4;2S\xc8\xc4\x17*\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\x13\u04c67\xb9\xa4|\xe7\x9d7\xa8oP\xfb@\x9c\x06\a(\x89Hz\x9a0E9D\x00\x00\u07d4\x1c \x10\xbdf-\xf4\x17\xf2\xa2q\x87\x9a\xfb\x13\xefL\x88\xa3\xae\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x1c%z\u0525Q\x05\xea;X\xed7K\x19\x8d\xa2f\xc8_c\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x1c.6\a\xe1'\xca\xca\x0f\xbd\\YH\xad\xad}\xd80\xb2\x85\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x1c5l\xfd\xb9_\xeb\xb7\x14c;(\xd5\xc12\u0744\xa9\xb46\x89\x01Z\xf1\u05cbX\xc4\x00\x00\u07d4\x1c5\xaa\xb6\x88\xa0\u034e\xf8.vT\x1b\xa7\xac9R\u007ft;\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x1c>\xf0]\xae\x9d\xcb\u0509\xf3\x02D\bf\x9d\xe2D\xc5*\x02\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x1cJ\xf0\xe8c\xd2el\x865\xbco\xfe\xc8\u0759(\x90\x8c\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c`\x19\x93x\x92\a\xf9e\xbb\x86\\\xbbL\xd6W\xcc\xe7o\xc0\x89\x05T\x1ap7P?\x00\x00\u07d4\x1cc\xfa\x9e,\xbb\xf21a\xda3\xa1\xda}\xf7\r\x1b\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1c\xb6\xb2\xd7\xcf\xc5Y\xb7\xf4\x1eoV\xab\x95\xc7\xc9X\xcd\x0eL\x89Hz\x9a0E9D\x00\x00\u07d4\x1c\xc1\xd3\xc1O\x0f\xb8d\x0e6rM\xc42)\xd2\xeaz\x1eH\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x1c\xc9\bv\x00A\t\xcdy\xa3\u07a8f\u02c4\n\xc3d\xba\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xd1\xf0\xa3\x14\u02f2\x00\xde\n\f\xb1\xef\x97\xe9 p\x9d\x97\u0089lk\x93[\x8b\xbd@\x00\x00\u0794\x1c\xdaA\x1b\xd5\x16;\xae\xca\x1eU\x85c`\x1c\xe7 \xe2N\xe1\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1c\xe8\x1d1\xa7\x920\"\xe1%\xbfH\xa3\xe06\x93\xb9\x8d\xc9\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xeb\xf0\x98]\u007fh\n\xaa\x91\\D\xccb\xed\xb4\x9e\xab&\x9e\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1c\xedg\x15\xf8b\xb1\xff\x86\x05\x82\x01\xfc\xceP\x82\xb3nb\xb2\x8a\x01j^`\xbe\xe2s\xb1\x00\x00\u07d4\x1c\xf0L\xb1C\x80\x05\x9e\xfd?#\x8be\u057e\xb8j\xfa\x14\u0609\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1c\xf1\x05\xab#\x02;ULX>\x86\u05d2\x11y\xee\x83\x16\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1c\xf2\xebz\x8c\xca\u00ad\xea\xef\x0e\xe8sG\xd55\u04f9@X\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1c\xfc\xf7Q\u007f\f\bE\x97 \x94+dz\u0452\xaa\x9c\x88(\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\x1d\t\xad$\x12i\x1c\u0141\xc1\xab6\xb6\xf9CL\xd4\xf0\x8bT\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\x1d\x15|Xv\xc5\xca\xd5S\xc9\x12\xca\xf6\xce-Rw\xe0\\s\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x1d&\x15\xf8\xb6\xcaP\x12\xb6c\xbd\u0414\xb0\xc5\x13|w\x8d\u07ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1d)\u01ea\xb4+ H\u04b2R%\u0518\u06e6z\x03\xfb\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0794\x1d4\x1f\xa5\xa3\xa1\xbd\x05\x1f}\xb8\a\xb6\xdb/\u01faO\x9bE\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1d4N\x96%g\xcb'\xe4M\xb9\xf2\xfa\u01f6\x8d\xf1\xc1\xe6\xf7\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x1d6h0c\xb7\xe9\xeb\x99F-\xab\xd5i\xbd\xdc\xe7\x16\x86\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1d7aky?\x94\x91\x188\xac\x8e\x19\xee\x94I\u07d2\x1e\u0109QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94\x1d9[0\xad\xda\x1c\xf2\x1f\t\x1aOJ{u3q\x18\x94A\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x1dEXn\xb8\x03\xca!\x90e\v\xf7H\xa2\xb1t1+\xb5\a\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1dW.\xdd-\x87\xca'\x1ag\x14\xc1Z;7v\x1d\u0320\x05\x89\x06\xeb\xd5*\x8d\xdd9\x00\x00\u07d4\x1dc0\x97\xa8R%\xa1\xffC!\xb1)\x88\xfd\xd5\\+8D\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x1di\xc8=(\xff\x04t\xce\xeb\xea\xcb:\xd2'\xa1D\xec\u78ca\x01(\xcc\x03\x92\nb\u0480\x00\u07d4\x1d\x96\xbc\u0544W\xbb\xf1\xd3\u00a4o\xfa\xf1m\xbf}\x83hY\x89\tIr\t\xd8F~\x80\x00\u07d4\x1d\x9ej\xaf\x80\x19\xa0_#\x0e]\xef\x05\xaf]\x88\x9b\xd4\xd0\xf2\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x1d\xab\x17.\xff\xa6\xfb\xeeSL\x94\xb1~yN\xda\xc5OU\xf8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x1d\xb9\xac\x9a\x9e\xae\xec\nR7W\x05\fq\xf4rx\xc7-P\x89Hz\x9a0E9D\x00\x00\u07d4\x1d\xbe\x8e\x1c+\x8a\x00\x9f\x85\xf1\xad<\xe8\r.\x055\x0e\u3709\aW\rn\x9e\xbb\xe4\x00\x00\u07d4\x1d\xc7\xf7\xda\xd8]\xf5?\x12q\x15$\x03\xf4\xe1\xe4\xfd\xb3\xaf\xa0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1d\u03bc\xb7em\xf5\u072a3h\xa0U\xd2/\x9e\xd6\xcd\xd9@\x89\x1b\x18\x1eK\xf24<\x00\x00\xe0\x94\x1d\xd7tA\x84J\xfe\x9c\xc1\x8f\x15\xd8\xc7{\xcc\xfbe^\xe04\x8a\x01\x06\xebEW\x99D\x88\x00\x00\u07d4\x1d\xde\xfe\xfd5\xab\x8fe\x8b$q\xe5G\x90\xbc\x17\xaf\x98\u07a4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1d\xee\xc0\x1a\xbe\\\r\x95-\xe9\x10l=\xc3\x069\xd8P\x05\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\x1d\xf6\x91\x16rg\x9b\xb0\xef5\t\x03\x8c\f'\xe3\x94\xfd\xfe0\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\x1d\xfa\xee\ar\x12\xf1\xbe\xaf\x0eo/\x18@Sz\xe1T\xad\x86\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x1e\x06\r\xc6\xc5\xf1\u02cc\xc7\xe1E.\x02\xee\x16u\b\xb5eB\x8a\x02\xb1O\x02\xc8d\xc7~\x00\x00\xe0\x94\x1e\x13\xecQ\x14,\ubde2`\x83A,<\xe3QD\xbaV\xa1\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x1e\x1aH(\x11\x9b\xe3\t\xbd\x88#nMH+PM\xc5W\x11\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\x1e\x1a\ud178leb\u02cf\xa1\xebo\x8f;\xc9\u072eny\x89\xf4\xd2\u0744%\x9b$\x00\x00\u07d4\x1e\x1ccQwj\xc3\x10\x919~\xcf\x16\x00-\x97\x9a\x1b-Q\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x1e\x1dz_$h\xb9N\xa8&\x98-\xbf!%yR*\xb7\xdf\n\u02ac\x9e\xee\xd3Y09\xe5\xacuy\x8a+\x14F\xddj\xef\xe4\x1c\x00\x00\u07d4\x1e{^M\x1fW+\xec\xf2\xc0\x0f\xc9\f\xb4v{Jn3\u0509\x06\x1f\xc6\x10u\x93\xe1\x00\x00\u07d4\x1e\x8eh\x9b\x02\x91|\xdc)$]\f\x9ch\xb0\x94\xb4\x1a\x9e\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xa34\xb5u\b\a\xeat\xaa\u016b\x86\x94\xec_(\xaaw\u03c9\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\x1e\xa4qU\x04\u01af\x10{\x01\x94\xf4\xf7\xb1\xcbo\xcc\xcdoK\x89 \x041\x97\xe0\xb0'\x00\x00\u07d4\x1e\xa4\x92\xbc\xe1\xad\x10~3\u007fK\u0527\xac\x9a{\xab\xcc\u036b\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x1e\xa6\xbf/\x15\xae\x9c\x1d\xbcd\u06a7\xf8\xeaM\r\x81\xaa\xd3\xeb\x89\u3bb5sr@\xa0\x00\x00\u07d4\x1e\xb4\xbfs\x15j\x82\xa0\xa6\x82 \x80\xc6\xed\xf4\x9cF\x9a\xf8\xb9\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\x1e\xba\xcbxD\xfd\xc3\"\xf8\x05\x90O\xbf\x19b\x80-\xb1S|\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x1e\xc4\xecKw\xbf\x19\u0411\xa8h\xe6\xf4\x91T\x18\x05A\xf9\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xd0n\xe5\x16b\xa8lcE\x88\xfbb\xdcC\xc8\xf2~|\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1e\u063b?\x06w\x8b\x03\x9e\x99a\xd8\x1c\xb7\x1as\xe6x|\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x1e\xda\bNye\x00\xba\x14\xc5\x12\x1c\r\x90\x84of\xe4\xbeb\x89\x1c\xfd\xd7F\x82\x16\xe8\x00\x00\u07d4\x1e\xeel\xbe\xe4\xfe\x96\xadaZ\x9c\xf5\x85zdy@\u07ccx\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4\x1e\xf2\u073f\xe0\xa5\x00A\x1d\x95n\xb8\u0213\x9c=l\xfef\x9d\x89*\x11)\u0413g \x00\x00\xe0\x94\x1e\xf5\xc9\xc76P\u03fb\xde\\\x88U1\xd4'\xc7\xc3\xfeUD\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x1f\x04\x12\xbf\xed\u0356N\x83}\t,q\xa5\xfc\xba\xf3\x01&\xe2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\x17O@\xa0Dr4\xe6fS\x91Mu\xbc\x00>V\x90\u0709\b\xacr0H\x9e\x80\x00\x00\u07d4\x1f!\x86\xde\xd2>\f\xf9R\x16\x94\xe4\xe1dY>i\n\x96\x85\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x1f*\xfc\n\xed\x11\xbf\xc7\x1ew\xa9\ae{6\xeav\xe3\xfb\x99\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\x1f9Y\xfc)\x11\x10\xe8\x822\xc3kvg\xfcx\xa3ya?\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x1f=\xa6\x8f\xe8~\xafC\xa8)\xabm~\u0166\xe0\t\xb2\x04\xfb\x89\x1e\x16\x01u\x8c,~\x00\x00\u07d4\x1fI\xb8m\r9EY\x06\x98\xa6\xaa\xf1g<7u\\\xa8\r\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\x1f_;4\xbd\x13K'\x81\xaf\xe5\xa0BJ\u0144l\xde\xfd\x11\x89\x05]\xe6\xa7y\xbb\xac\x00\x00\u07d4\x1fo\x0004\x97R\x06\x1c\x96\a+\xc3\xd6\xeb5I \x8dk\x89\x01K\x8d\xe1\xeb\x88\u06c0\x00\u07d4\x1f}\x8e\x86\xd6\xee\xb0%E\xaa\xd9\x0e\x912{\xd3i\xd7\xd2\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\x81\x16\xbd\n\xf5W\x0e\xaf\fV\u011cz\xb5\xe3zX\x04X\x89lk\x93[\x8b\xbd@\x00\x00\u0794\x1f\x88\xf8\xa13\x8f\xc7\xc1\tv\xab\xcd?\xb8\u04c5T\xb5\uc708\xb9\xf6]\x00\xf6<\x00\x00\u07d4\x1f\x9c2hE\x8d\xa3\x01\xa2\xbeZ\xb0\x82W\xf7{\xb5\xa9\x8a\xa4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x1f\xa21\x9f\xed\x8c-F*\xdf.\x17\xfe\xecjo0Qn\x95\x89\x06\xca\xe3\x06!\xd4r\x00\x00\u07d4\x1f\xb4c\xa08\x99\x83\xdf}Y?{\xddmxI\u007f\xed\x88y\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x1f\xb7\xbd1\r\x95\xf2\xa6\u067a\xaf\x8a\x8aC\n\x9a\x04E:\x8b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x1f\xcc|\xe6\xa8HX\x95\xa3\x19\x9e\x16H\x1fr\xe1\xf7b\xde\xfe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x1f\xcf\xd1\xd5\u007f\x87\"\x90V\f\xb6-`\x0e\x1d\xef\xbe\xfc\xcc\x1c\x89P\xc5\xe7a\xa4D\b\x00\x00\u0794\x1f\u0496\xbe\x03\xads|\x92\xf9\u0186\x9e\x8d\x80\xa7\x1cW\x14\xaa\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x1f\xdd\xd8_\u024b\xe9\xc4\x04Ya\xf4\x0f\x93\x80^\xccEI\xe5\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4 \x01\xbe\xf7{f\xf5\x1e\x15\x99\xb0/\xb1\x10\x19J\x00\x99\xb7\x8d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4 \x02d\xa0\x9f\x8ch\xe3\xe6b\x97\x95(\x0fV%O\x86@\u0409\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \x03qy\a\xa7%`\xf40\u007f\x1b\xee\xccT6\xf4=!\xe7\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4 \r\xfc\vq\xe3Y\xb2\xb4eD\n6\xa6\xcd\xc3Rw0\a\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4 \x13L\xbf\xf8\x8b\xfa\xdcFkR\xec\ua9d8W\x89\x1d\x83\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4 \x14&\x1f\x01\b\x9fSyV0\xba\x9d\xd2O\x9a4\xc2\xd9B\x89Hz\x9a0E9D\x00\x00\u07d4 \x16\x89]\xf3,\x8e\xd5G\x82iF\x84#\xae\xa7\xb7\xfb\xceP\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \x18\x1cKA\xf6\xf9r\xb6iX!_\x19\xf5p\xc1]\xdf\xf1\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4 \x18d\xa8\xf7\x84\xc2'{\v|\x9e\xe74\xf7\xb3w\xea\xb6H\x89\xf2(\x14\x00\xd1\xd5\xec\x00\x00\u07d4 \xb8\x1a\xe59&\xac\xe9\xf7\xd7AZ\x05\f\x03\x1dX_ \x89\x12\u007f\x19\xe8>\xb3H\x00\x00\xe0\x94 \x1d\x9e\xc1\xbc\v\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4 \xa1RV\xd5\f\xe0X\xbf\x0e\xacC\xaaS:\xa1n\u0273\x80\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4 \xa2\x9cPy\xe2k?\x181\x8b\xb2\xe5\x0e\x8e\x8b4n[\xe8\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4 \xa8\x16\x80\xe4e\xf8\x87\x90\xf0\aO`\xb4\xf3_]\x1ej\xa5\x89Ea\x80'\x8f\fw\x80\x00\u07d4 \xb9\xa9\u6f48\x80\u0659J\xe0\r\u0439(*\v\xea\xb8\x16\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4 \u0084\xba\x10\xa2\b0\xfc=i\x9e\xc9}-\xfa'\xe1\xb9^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4 \xd1A\u007f\x99\xc5i\u3fb0\x95\x85e0\xfe\x12\xd0\xfc\uaa89@\x15\xf9K\x11\x83i\x80\x00\u07d4 \u074f\u02f4n\xa4o\u3066\x8b\x8c\xa0\xea[\xe2\x1f\u9949lk\x93[\x8b\xbd@\x00\x00\xe0\x94 \xff>\u078c\xad\xb5\xc3{H\xcb\x14X\x0f\xb6^#\t\n{\x8a\b\xe4\xd3\x16\x82v\x86@\x00\x00\xe0\x94!\x008\x1d`\xa5\xb5J\xdc\t\u0456\x83\xa8\xf6\u057bK\xfb\u02ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94!\x18\xc1\x16\xab\f\xdfo\xd1\x1dT\xa40\x93\a\xb4w\xc3\xfc\x0f\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94!\x1b)\xce\xfcy\xae\x97gD\xfd\xeb\u03bd<\xbb2\xc5\x13\x03\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4! l\xe2.\xa4\x80\xe8Y@\xd3\x13\x14\xe0\xd6ONM:\x04\x8965\u026d\xc5\u07a0\x00\x00\u07d4!2\xc0Qj.\x17\x17J\xc5G\xc4;{\x00 \xd1\xebLY\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94!@\x8bMz,\x0en\xcaAC\xf2\xca\u037b\u033a\x12\x1b\u060a\x04<3\xc1\x93ud\x80\x00\x00\u07d4!Kt9U\xa5\x12\xden\r\x88j\x8c\xbd\x02\x82\xbe\xe6\u04a2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!L\x89\u017d\x8e}\"\xbcWK\xb3^H\x95\x02\x11\xc6\xf7v\x89\x01\x06T\xf2X\xfd5\x80\x00\xe0\x94!Ti\x14\xdf\u04ef*\xddA\xb0\xff>\x83\xff\xdat\x14\xe1\xe0\x8a\x01C\x95\xe78ZP.\x00\x00\u07d4!X.\x99\xe5\x02\xcb\xf3\xd3\xc2;\xdf\xfbv\xe9\x01\xacmV\xb2\x89\x05k\xc7^-c\x10\x00\x00\u07d4!Y$\b\x13\xa70\x95\xa7\xeb\xf7\u00f3t>\x80(\xae_\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!`\xb4\xc0,\xac\n\x81\u0791\b\xdeCE\x90\xa8\xbf\xe6\x875\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94!nA\x86N\xf9\x8f\x06\r\xa0\x8e\xca\xe1\x9a\xd1\x16j\x17\xd06\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4!\x84o/\xdfZA\xed\x8d\xf3n^\xd8TM\xf7Y\x88\xec\xe3\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94!\xa6\xdbe'F{\xc6\xda\xd5K\xc1n\x9f\xe2\x95;g\x94\xed\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4!\xa6\xfe\xb6\xab\x11\xc7f\xfd\xd9w\xf8\xdfA!\x15_G\xa1\xc0\x89\x03\x19\xcf8\xf1\x00X\x00\x00\u07d4!\xb1\x82\xf2\xda+8D\x93\xcf_5\xf8=\x9d\x1e\xe1O*!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!\xbf\xe1\xb4\\\xac\xdebt\xfd\x86\b\u0661x\xbf>\xebn\u0709l\xee\x06\u077e\x15\xec\x00\x00\u07d4!\xc0s\x80HOl\xbc\x87$\xad2\xbc\x86L;Z\xd5\x00\xb7\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94!\u00e8\xbb\xa2g\xc8\u0322{\x1a\x9a\xfa\xba\xd8o`z\xf7\b\x8a\x01\xe4\xa3lI\u06580\x00\x00\u07d4!\xcem[\x90\x18\xce\xc0J\u0596yD\xbe\xa3\x9e\x800\xb6\xb8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4!\xd0'\x05\xf3\xf6I\x05\xd8\x0e\xd9\x14y\x13\xea\x8cs\a\u0595\x89I\xed\xb1\xc0\x98\x876\x00\x00\u07d4!\xd1?\f@$\xe9g\xd9G\a\x91\xb5\x0f\"\xde:\xfe\xcf\x1b\x89\xf1Z\xd3^.1\xe5\x00\x00\xe0\x94!\xdb\u06c1z\r\x84\x04\u01bd\xd6\x15\x047N\x9cC\xc9!\x0e\x8a\x02\x1e\x18\xb9\xe9\xabE\xe4\x80\x00\xe0\x94!\xdf\x1e\xc2KNK\xfey\xb0\xc0\x95\u03ba\xe1\x98\xf2\x91\xfb\u044a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94!\xdf-\u036ft\xb2\xbf\x804\x04\xddM\xe6\xa3^\xab\xec\x1b\xbd\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4!\xe2\x19\u021c\xa8\xac\x14\xaeL\xbaa0\xee\xb7}\x9em9b\x89*\u035f\xaa\xa08\xee\x00\x00\u07d4!\xe5\u04ba\xe9\x95\xcc\xfd\b\xa5\xc1k\xb5$\xe1\xf60D\x8f\x82\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4!\xe5\xd7s 0L \x1c\x1eS\xb2a\xa1#\u0421\x06>\x81\x89\x04\xb6\xfa\x9d3\xddF\x00\x00\xe0\x94!\xea\xe6\xfe\xff\xa9\xfb\xf4\u0347OG9\xac\xe50\u033eY7\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4!\xec\xb2\u07e6Wy\xc7Y-\x04\x1c\xd2\x10Z\x81\xf4\xfdNF\x8965\u026d\xc5\u07a0\x00\x00\u07d4!\uff20\x9b5\x80\xb9\x8es\xf5\xb2\xf7\xf4\xdc\v\xf0,R\x9c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4!\xfd\v\xad\xe5\xf4\xeftt\xd0X\xb7\xf3\xd8T\xcb\x13\x00RN\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94!\xfdG\xc5%`\x12\x19\x8f\xa5\xab\xf11\xc0mj\xa1\x96_u\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4!\xfdl]\x97\xf9\xc6\x00\xb7h!\xdd\xd4\xe7v5\x0f\xce+\xe0\x89lj\u04c2\xd4\xfba\x00\x00\u07d4\"\r\u018d\xf0\x19\xb6\xb0\u033f\xfbxKZZ\xb4\xb1]@`\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\"\x0e+\x92\xc0\xf6\xc9\x02\xb5\x13\xd9\xf1\xe6\xfa\xb6\xa8\xb0\xde\xf3\u05c9+^:\xf1k\x18\x80\x00\x00\u07d4\"V\x1cY1\x14560\x9c\x17\xe82X{b\\9\v\x9a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\"W\xfc\xa1jn\\*d|<)\xf3l\xe2)\xab\x93\xb1~\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\"]5\xfa\xed\xb3\x91\u01fc-\xb7\xfa\x90q\x16\x04\x05\x99m\x00\x89\t\x18T\xfc\x18bc\x00\x00\u07d4\"_\x9e\xb3\xfbo\xf3\xe9\xe3\xc8D~\x14\xa6n\x8dO7y\xf6\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\"r\x18n\xf2}\xcb\xe2\xf5\xfc70P\xfd\xae\u007f*\xce#\x16\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4\"s\xba\u05fcNHv\"\xd1u\xefzf\x98\x8bj\x93\xc4\xee\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\"v&K\xec\x85&\xc0\xc0\xf2pgz\xba\xf4\xf0\xe4A\xe1g\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\"\x82B\xf83n\xec\xd8$.\x1f\x00\x0fA\x93~q\xdf\xfb\xbf\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\"\x84*\xb80\xdaP\x99\x13\xf8\x1d\xd1\xf0O\x10\xaf\x9e\xdd\x1cU\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\"\x94O\xbc\xa9\xb5yc\bN\xb8M\xf7\xc8_\xb9\xbc\u07f8V\x89\xfc\x11\x8f\uf43a8\x80\x00\u07d4\"\x9c\xc4q\x1bbu^\xa2\x96DZ\u00f7\u007f\xc63\x82\x1c\xf2\x89\x02#\xe8\xb0R\x192\x80\x00\u0794\"\x9eC\r\xe2\xb7OD&Q\xdd\u0377\x01v\xbc\x05L\xadT\x88\xbb\xf9\x81\xbcJ\xaa\x80\x00\u07d4\"\x9fO\x1a*OT\atP[G\a\xa8\x1d\xe4D\x10%[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\"\x9f\xf8\v\xf5p\x80\t\xa9\xf79\xe0\xf8\xb5`\x91@\x16\u0566\x89\x12\x11\xec\xb5m\x13H\x80\x00\u07d4\"\xa2X\x12\xabV\xdc\xc4#\x17^\xd1\u062d\xac\xce3\xcd\x18\x10\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94\"\xb9j\xb2\xca\xd5]\xb1\x00\xb50\x01\xf9\xe4\xdb7\x81\x04\xc8\a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\"\xbd\xff\xc2@\xa8\x8f\xf7C\x1a\xf3\xbf\xf5\x0e\x14\xda7\xd5\x18>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\"\xce4\x91Y\xee\xb1D\xef\x06\xff&6X\x8a\xefy\xf6(2\x89\n1\x06+\xee\xedp\x00\x00\u07d4\"\xdbU\x9f,<\x14u\xa2\xe6\xff\xe8:YyY\x91\x96\xa7\xfa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\"\xe1QX\xb5\xee>\x86\xeb\x032\xe3\u6a6cl\u0675^\u0349\b\xacr0H\x9e\x80\x00\x00\u07d4\"\xe2H\x8e-\xa2jI\xae\x84\xc0\x1b\xd5K!\xf2\x94x\x91\u0189]\u0212\xaa\x111\xc8\x00\x00\u07d4\"\xe5\x12\x14\x9a\x18\xd3i\xb7\x86\xc9\xed\xab\xaf\x1d\x89N\xe0.g\x14a\\\x00\x00\u07d4\"\xeb}\xb0\xbaV\xb0\xf8\xb8\x16\u0332\x06\xe6\x15\xd9)\x18[\r\x89\x04])s~\"\xf2\x00\x00\u07d4\"\xee\xd3'\xf8\xeb\x1d\x138\xa3\xcb{\x0f\x8aK\xaaY\a\u0355\x89\x01E]_Hw\b\x80\x00\xe0\x94\"\xf0\x04\u07cd\xe9\xe6\xeb\xf5#\u032c\xe4W\xac\xcb&\xf9r\x81\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794\"\xf2\xdc\xffZ\u05cc>\xb6\x85\v\\\xb9Q\x12{e\x95\"\u623e -j\x0e\xda\x00\x00\u07d4\"\xf3\xc7y\xddy\x02>\xa9*x\xb6\\\x1a\x17\x80\xf6-\\J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\"\xfe\x88M\x907)\x1bMR\xe6(Z\xe6\x8d\xea\v\xe9\xff\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\x06\u07d3\x1a\x94\rX\xc0\x16e\xfaM\b\x00\x80,\x02\xed\xfe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94#\t\xd3@\x91D[22Y\v\xd7\x0fO\x10\x02[,\x95\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4#\x12\x00F\xf6\x83!\x02\xa7R\xa7fVi\x1c\x86>\x17\u5709\x11\xe0\xe4\xf8\xa5\v\xd4\x00\x00\u07d4#\x1a\x15\xac\xc1\x99\u021f\xa9\xcb\"D\x1c\xc7\x030\xbd\xcc\xe6\x17\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4#\x1d\x94\x15]\xbc\xfe*\x93\xa3\x19\xb6\x17\x1fc\xb2\v\u04b6\xfa\x89\xcf\x14{\xb9\x06\xe2\xf8\x00\x00\u07d4#(2\xcdYw\xe0\nL0\xd0\x16?.$\xf0\x88\xa6\xcb\t\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4#,m\x03\xb5\xb6\xe6q\x1e\xff\xf1\x90\xe4\x9c(\xee\xf3l\x82\xb0\x89Hz\x9a0E9D\x00\x00\xe0\x94#,\xb1\xcdI\x99<\x14J?\x88\xb3a\x1e#5i\xa8k\u058a\x03L`lB\u042c`\x00\x00\u07d4#,\xe7\x82Pb%\xfd\x98`\xa2\xed\xc1Jz0Gsm\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4#/R]U\x85\x9b}N`\x8d H\u007f\xaa\xdb\x00)15\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94#4\u0150\u01e4\x87i\x100E\u0176SL\x8a4i\xf4J\x8a\x03\xb1\x99\a=\xf7-\xc0\x00\x00\u07d4#7n\u02bftl\xe53!\xcfB\xc8fI\xb9+g\xb2\xff\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#7\x8fB\x92m\x01\x84\xb7\x93\xb0\xc8'\xa6\xdd>=3O\u0349\x03\t'\xf7L\x9d\xe0\x00\x00\u07d4#8B\xb1\xd0i/\xd1\x11@\xcfZ\u0364\xbf\x960\xba\xe5\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#9\xe9I(p\xaf\xea%7\xf3\x89\xac/\x83\x83\x02\xa3<\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#;\xdd\xdd]\xa9HR\xf4\xad\xe8\xd2\x12\x88V\x82\xd9\ak\u0189\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#OF\xba\xb7?\xe4]1\xbf\x87\xf0\xa1\xe0Fa\x99\xf2\ubb09\x1aJ\xba\"\\ t\x00\x00\u07d4#U\x1fV\x97_\xe9+1\xfaF\x9cI\xeaf\xeefb\xf4\x1e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4#V\x95B\xc9}V`\x18\xc9\a\xac\xfc\xf3\x91\xd1@g\xe8~\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94#_\xa6l\x02^\xf5T\x00p\xeb\xcf\r7-\x81w\xc4g\xab\x8a\a\x12\x9e\x1c\xdf7>\xe0\x00\x00\xe0\x94#r\xc4\xc1\u0253\x9fz\xafl\xfa\xc0@\x90\xf0\x04t\x84\n\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4#s\f5z\x91\x02nD\xb1\xd0\xe2\xfc*Q\xd0q\xd8\xd7{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#v\xad\xa9\x033\xb1\u0441\bL\x97\xe6E\xe8\x10\xaa[v\xf1\x89(\xa8WBTf\xf8\x00\x00\u07d4#x\xfdC\x82Q\x1e\x96\x8e\u0452\x10g7\xd3$\xf4T\xb55\x8965\u026d\xc5\u07a0\x00\x00\u07d4#\x82\xa9\u050e\xc8>\xa3e(\x90\xfd\x0e\u7710{[-\xc1\x89\a?u\u0460\x85\xba\x00\x00\u07d4#\x83\xc2\"\xe6~\x96\x91\x90\xd3!\x9e\xf1M\xa3xP\xe2lU\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\x8akv5%/RDHl\n\xf0\xa7: s\x85\xe09\x89JD\x91\xbdm\xcd(\x00\x00\u07d4#\x9as>k\x85Z\u0152\xd6c\x15a\x86\xa8\xa1t\xd2D\x9e\x89X\xbe7X\xb2A\xf6\x00\x00\xe0\x94#\xab\t\xe7?\x87\xaa\x0f;\xe0\x13\x9d\xf0\xc8\xebk\xe5cO\x95\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94#\xab\xd9\xe9>yW\xe5\xb66\xbeey\x05\x1c\x15\xe5\xce\v\x0e\x8a\x03\xa3\xc8\xf7\xcb\xf4,8\x00\x00\u07d4#\xb1\u0111\u007f\xbd\x93\xee=H8\x93\x06\x95s\x84\xa5Il\xbf\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94#\xba8d\xdaX=\xabV\xf4 \x87<7g\x96\x90\xe0/\x00\x8a\x02\x13BR\r_\xec \x00\x00\u07d4#\xc5Z\xebW9\x87o\n\xc8\xd7\xeb\xea\x13\xber\x96\x85\xf0\x00\x89Hz\x9a0E9D\x00\x00\u07d4#\u025b\xa0\x87D\x8e\x19\xc9p\x1d\xf6n\f\xabR6\x831\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xcc\xc3\u01ac\xd8\\.F\fO\xfd\xd8+\xc7]\xc8I\xea\x14\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4#\xcd%\x98\xa2\x0e\x14\x9e\xad*\u0593yWn\xce\xdb`\u3389lk\x93[\x8b\xbd@\x00\x00\u07d4#\u07cfH\xee\x00\x92V\xeay~\x1f\xa3i\xbe\xeb\xcfk\xc6c\x89|\xd3\xfa\xc2m\x19\x81\x80\x00\u07d4#\xe2\u01a8\xbe\x8e\n\u03e5\xc4\xdf^6\x05\x8b\xb7\u02ecZ\x81\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xeaf\x9e5d\x81\x9a\x83\xb0\xc2l\x00\xa1m\x9e\x82olF\x89M\x8dl\xa9h\xca\x13\x00\x00\u07d4#\xebo\xd8Vq\xa9\x06:\xb7g\x8e\xbe&Z \xf6\x1a\x02\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4#\xf9\xec\xf3\xe5\xdd\u0723\x88\x15\xd3\xe5\x9e\xd3K[\x90\xb4\xa3S\x89\v\x17\x81\xa3\xf0\xbb \x00\x00\u07d4#\xfa~\xb5\x1aH\"\x95\x98\xf9~v+\xe0\x86\x96R\xdf\xfcf\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94$\x03\x05rs\x13\xd0\x1esT,w_\xf5\x9d\x11\xcd5\xf8\x19\x8a\x01A\x88Vf\x80\u007f\\\x80\x00\u07d4$\x04k\x91\u069ba\xb6)\u02cb\x8e\xc0\xc3Q\xa0~\a\x03\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4$\x0eU\x9e'J\xae\xf0\xc2X\x99\x8c\x97\x9fg\x1d\x11s\xb8\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94$\x13aU\x9f\xee\xf8\x0e\xf170!S\xbd\x9e\xd2\xf2]\xb3\xef\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94$;;\xcaj)\x93Y\xe8\x86\xce3\xa3\x03A\xfa\xfeMW=\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4$<\x84\xd1$ W\f\xc4\xef;\xab\xa1\xc9Y\u0083$\x95 \x89\u007f\x1fi\x93\xa8S\x04\x00\x00\xe0\x94$CJ>2\xe5N\xcf'/\xe3G\v_oQ/gU \x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4$HYo\x91\xc0\x9b\xaa0\xbc\x96\x10j-7\xb5p^](\x89lk\x93[\x8b\xbd@\x00\x00\u0794$Xn\xc5E\x175\xee\xaa\xebG\r\xc8sj\xaeu/\x82\xe5\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4$X\xd6U_\xf9\x8a\x12\x9c\xce@7\x95=\x00 n\xffB\x87\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4$b\x91\x16[Y3-\xf5\xf1\x8c\xe5\u0248V\xfa\xe9X\x97\u0589\\(=A\x03\x94\x10\x00\x00\u07d4$g\u01a5\u0196\xed\xe9\xa1\xe5B\xbf\x1a\xd0k\xccK\x06\xac\xa0\x89\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4$v\xb2\xbbu\x1c\xe7H\xe1\xa4\xc4\xff{#\v\xe0\xc1]\"E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4$z\n\x11\xc5\u007f\x03\x83\xb9I\xdeT\vf\xde\xe6\x86\x04\xb0\xa1\x899\xfb\xae\x8d\x04-\xd0\x00\x00\u07d4$\x87\xc3\u013e\x86\xa2r=\x91|\x06\xb4XU\x01p\xc3\xed\xba\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\x89\xac\x12i4\xd4\u05a9M\xf0\x87C\xda{v\x91\xe9y\x8e\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\x9d\xb2\x9d\xbc\x19\xd1#]\xa7)\x8a\x04\b\x1c1WB\u9b09a\xac\xff\x81\xa7\x8a\xd4\x00\x00\u07d4$\xa4\xeb6\xa7\xe4\x98\xc3o\x99\x97\\\x1a\x8dr\x9f\u05b3\x05\u05c9\r\xfcx!\x0e\xb2\xc8\x00\x00\u07d4$\xa7P\xea\xe5\x87G\x11\x11m\xd7\xd4{q\x86\u0399\r1\x03\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4$\xaa\x11Q\xbbv_\xa3\xa8\x9c\xa5\x0e\xb6\xe1\xb1\xc7\x06A\u007f\u0509\xa8\r$g~\xfe\xf0\x00\x00\u0794$\xac\xa0\x8d[\xe8^\xbb\x9f12\xdf\xc1\xb6 \x82N\xdf\xed\xf9\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4$\xb2\xbe\x11\x8b\x16\u0632\x17Gi\xd1{L\xf8O\a\u0294m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4$\xb8\xb4F\u07bd\x19G\x95]\u0404\xf2\xc5D\x933F\u04ed\x89\xeaim\x90@9\xbd\x80\x00\u07d4$\xb9^\xbe\xf7\x95\x00\xba\xa0\xed\xa7.w\xf8wA]\xf7\\3\x891T\xc9r\x9d\x05x\x00\x00\u07d4$\xb9\xe6dOk\xa4\xcd\xe1&'\r\x81\xf6\xab`\xf2\x86\xdf\xf4\x89\a?u\u0460\x85\xba\x00\x00\u07d4$\xbdY\x04\x05\x90\x91\xd2\xf9\xe1-j&\xa0\x10\xca\"\xab\x14\xe8\x89e\xea=\xb7UF`\x00\x00\u07d4$\xc0\u020bT\xa3TG\t\x82\x8a\xb4\xab\x06\x84\x05Y\xf6\xc5\u2250\xf54`\x8ar\x88\x00\x00\u07d4$\xc1\x17\xd1\u04b3\xa9z\xb1\x1aFy\u025awJ\x9e\xad\xe8\u044965\u026d\xc5\u07a0\x00\x00\u07d4$\xcf\xf0\xe93j\x9f\x80\xf9\xb1\u02d6\x8c\xafk\x1d\x1cI2\xa4\x89\n\xdaUGK\x814\x00\x00\u07d4$\u06aa\xdd\xf7\xb0k\xbc\ua6c0Y\x00\x85\xa8\x85gh+N\x89\x11K \x15\u04bb\xd0\x00\x00\u07d4$\xdc\xc2K\xd9\xc7!\f\xea\u03f3\r\xa9\x8a\xe0JM{\x8a\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4$\xf7E\r\xdb\xf1\x8b\x02\x0f\xeb\x1a 2\xd9\xd5Kc>\xdf7\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4$\xfcs\xd2\a\x93\t\x8e\t\u076bW\x98Pb$\xfa\x1e\x18P\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4$\xfd\x9al\x87L/\xab?\xf3n\x9a\xfb\xf8\xce\r2\xc7\u0792\x89Hz\x9a0E9D\x00\x00\u07d4%\n@\xce\xf3 #\x97\xf2@F\x95H\xbe\xb5bj\xf4\xf2<\x89\x05\x03\xb2\x03\xe9\xfb\xa2\x00\x00\u07d4%\niC\av\xf64w\x03\xf9R\x97\x83\x95Za\x97\xb6\x82\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4%\x0e\xb7\xc6o\x86\x9d\xdfI\u0685\xf39>\x98\f\x02\x9a\xa44\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%\x10j\xb6u]\xf8mkc\xa1\x87p;\f\xfe\xa0\u5520\x89\x01|@Z\xd4\x1d\xb4\x00\x00\xe0\x94%\x18_2Z\xcf-dP\x06\x98\xf6\\v\x9d\xdfh0\x16\x02\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4%\x1c\x12r,hy\"y\x92\xa3\x04\xeb5v\xcd\x18CN\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\x1eh8\xf7\xce\u0173\x83\xc1\xd9\x01F4\x12t\xda\xf8\xe5\x02\x89\a\xff\x1c\xcbua\xdf\x00\x00\u07d4%%\x9d\x97Z!\xd8:\xe3\x0e3\xf8\x00\xf5?7\u07e0\x198\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4%({\x81_\\\x828\ns\xb0\xb1?\xba\xf9\x82\xbe$\xc4\u04c9\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94%+eU\xaf\u0700\xf2\xd9m\x97-\x17\u06c4\xeaZ\xd5!\xac\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4%8S)6\x81<\x91\xe6S(O\x01|\x80\u00f8\xf8\xa3o\x89l\x87T\xc8\xf3\f\b\x00\x00\xe0\x94%>2\xb7N\xa4I\n\xb9&\x06\xfd\xa0\xaa%{\xf2=\u02cb\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94%?\x1et*,\uc1b0\u05f3\x06\xe5\xea\xcbl\xcb/\x85T\x8a\x04>^\xde\x1f\x87\x8c \x00\x00\u07d4%A1J\v@\x8e\x95\xa6\x94DIwq*Pq5\x91\xab\x89X\x9e\x1a]\xf4\u05f5\x00\x00\u07d4%L\x1e\xccc\f(w\u0780\x95\xf0\xa8\u06e1\xe8\xbf\x1fU\f\x89\\(=A\x03\x94\x10\x00\x00\u07d4%Z\xbc\x8d\b\xa0\x96\xa8\x8f=j\xb5_\xbcsR\xbd\u0739\u0389\x04t6\x821>\u0780\x00\u07d4%[\xdddt\u0302b\xf2j\"\u00cfE\x94\x0e\x1c\ue99b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%`\xb0\x9b\x89\xa4\xaehI\xedZ<\x99XBf1qDf\x89\\(=A\x03\x94\x10\x00\x00\u07d4%a\xa18\xdc\xf8;\xd8\x13\xe0\xe7\xf1\bd+\xe3\xde=o\x05\x8964\xf4\x84\x17@\x1a\x00\x00\u0794%a\xec\x0f7\x92\x18\xfe^\xd4\xe0(\xa3\xf7D\xaaAuLr\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u0794%b\x92\xa1\x91\xbd\xda4\xc4\xdakk\u0591G\xbfu\u2a6b\x88\xc2\xff.\r\xfb\x03\x80\x00\u07d4%i~\xf2\f\u032ap\xd3-7o\x82r\xd9\xc1\a\f=x\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4%o\xa1P\u0307\xb5\x05j\a\xd0\x04\xef\xc8E$s\x9eb\xb5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4%r\x1c\x87\xb0\xdc!7|r\x00\xe5$\xb1J\"\xf0\xafi\xfb\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4%\x899\xbb\xf0\f\x9d\xe9\xafS8\xf5\xd7\x14\xab\xf6\xd0\xc1\xc6q\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94%\x90\x12hp\xe0\xbd\xe8\xa6c\xab\x04\nr\xa5W=\x8dA\u008a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4%\x9e\xc4\xd2e\xf3\xabSk|p\xfa\x97\xac\xa1Bi,\x13\xfc\x89\x01\x1b\x1b[\xea\x89\xf8\x00\x00\xe0\x94%\xa5\x00\xee\xeczf*\x84\x15R\xb5\x16\x8bp{\r\xe2\x1e\x9e\x8a\x02\x1f/o\x0f\xc3\xc6\x10\x00\x00\xe0\x94%\xa5\xa4M8\xa2\xf4Lj\x9d\xb9\u037ck\x1e.\x97\xab\xb5\t\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4%\xa7L*\xc7]\u023a\xa8\xb3\x1a\x9c|\xb4\xb7\x82\x9b$V\u0689lk\x93[\x8b\xbd@\x00\x00\xe0\x94%\xad\xb8\xf9o9I,\x9b\xb4|^\u0708bNF\aV\x97\x8a\x05\xa9\x94\v\xc5hyP\x00\x00\u07d4%\xae\xe6\x8d\t\xaf\xb7\x1d\x88\x17\xf3\xf1\x84\xecV/x\x97\xb74\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\xb0S;\x81\xd0*a{\x92)\xc7\xec]o/g.[Z\x8965\u026d\xc5\u07a0\x00\x00\u07d4%\xb7\x8c\x9f\xad\x85\xb43C\xf0\xbf\xcd\x0f\xac\x11\u0254\x9c\xa5\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4%\xbcI\xef(\x8c\xd1e\xe5%\xc6a\xa8\x12\u03c4\xfb\xec\x8f3\x89\x12Y!\xae\xbd\xa9\xd0\x00\x00\u07d4%\xbd\xfa>\xe2o8Ia{#\x00bX\x8a\x97\xe3\xca\xe7\x01\x8965\xe6\x19\xbb\x04\xd4\x00\x00\u07d4%\xc1\xa3~\xe5\xf0\x82e\xa1\xe1\r=\x90\xd5G)U\xf9x\x06\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4%\xc6\xe7O\xf1\xd9(\u07d8\x13z\xf4\u07c40\xdf$\xf0|\u05c9\x15$VU\xb1\x02X\x00\x00\xe0\x94%\xcf\xc4\xe2\\5\xc1;i\xf7\xe7}\xbf\xb0\x8b\xafXuk\x8d\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94%\xda\u0515\xa1\x1a\x86\xb9\xee\xec\xe1\xee\xec\x80^W\xf1W\xfa\xff\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94%\xe07\xf0\n\x18'\v\xa5\xec4 \"\x9d\xdb\n,\u33e2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4%\xe6a\xc99\x86:\xcc\x04No\x17\xb5i\x8c\xce7\x9e\xc3\u0309JD\x91\xbdm\xcd(\x00\x00\u07d4&\x04\x8f\xe8M\x9b\x01\nb\xe71b~I\xbc.\xb7?@\x8f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4&\x06\u00f3\xb4\xca\x1b\t\x14\x98`,\xb1\x97\x8b\xf3\xb9R!\xc0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4&\n#\x0eDe\a~\v\x14\xeeDB\xa4\x82\u0570\xc9\x14\xbf\x89Z\xf6\x06\xa0k[\x11\x80\x00\u07d4&\r\xf8\x94:\x8c\x9a]\xbayE2\u007f\xd7\xe0\x83|\x11\xad\a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4&\x14\xf4-]\xa8D7ux\xe6\xb4H\xdc$0[\xef+\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\x15\x10\x0e\xa7\xe2[\xba\x9b\xcat`X\xaf\xbb\xb4\xff\xbeBD\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4&\x15u\xe9\xcfY\xc8\"o\xa7\xaa\xf9\x1d\xe8o\xb7\x0fZ\u00ee\x89\x10C\xa4CjR?\x00\x00\xe0\x94&\x1e\x0f\xa6LQ\x13te\xee\xcf[\x90\xf1\x97\xf7\x93\u007f\xdb\x05\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4&*\x8b\xfd}\x9d\xc5\xdd:\u05c1a\xb6\xbbV\b$76U\x89?j\x83\x84\a+v\x00\x00\xe0\x94&*\xedK\xc0\xf4\xa4\xb2\xc6\xfb5y>\x83ZI\x18\x9c\xdf\xec\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94&-\xc16L\xcfm\xf8\\C&\x8e\xe1\x82UM\xaei.)\x8a\x01\v /\xect\xce\xd8\x00\x00\u07d4&8\x140\x9d\xe4\xe65\xcfX^\r6Tw\xfc@\xe6l\xf7\x89\a\xea(2uw\b\x00\x00\u07d4&9\xee\xe9\x87<\xee\xc2o\u0314T\xb5H\xb9\xe7\xc5J\xa6\\\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94&>W\xda\xcb\xe0\x14\x9f\x82\xfee\xa2fH\x98\x86o\xf5\xb4c\x8a\b\v\xfb\xef\xcb_\v\xc0\x00\x00\u07d4>\x19\xc0m_\x14z\xa5\x97$\x8e\xb4l\xf7\xbe\xfad\xa5\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4&L\xc8\bj\x87\x10\xf9\x1b!r\t\x05\x91,\u05d6J\xe8h\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94&S\x83\u058bR\xd04\x16\x1b\xfa\xb0\x1a\xe1\xb0G\x94/\xbc2\x8a\x04rq\xde\xe2\rt\\\x00\x00\u07d4&Y\xfa\xcb\x1e\x83CeS\xb5\xb4)\x89\xad\xb8\a_\x99S\xed\x89\x01\x97evw\x1a^\x00\x00\xe0\x94&o-\xa7\xf0\b^\xf3\xf3\xfa\t\xba\xee#+\x93\xc7D\xdb.\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4&qH\xfdr\xc5Ob\nY/\xb9'\x991\x9c\xc4S+\\\x89\x169\u46fa\x16(\x00\x00\xe0\x94&xJ\u0791\u0228:\x8e9e\x8c\x8d\x82wA<\u0319T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4&z~n\x82\xe1\xb9\x1dQ\xde\u0776D\xf0\xe9m\xbb\x1f\u007f~\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4&\x80q=@\x80\x8e*P\xed\x011P\xa2\xa6\x94\xb9j\u007f\x1d\x89a\t=|,m8\x00\x00\u07d4&\x97\xb39\x81;\f-\x96K$q\xeb\x1c`oN\u02d6\x16\x89>\x8e\xf7\x95\u0610\xc8\x00\x00\u07d4&\xa6\x8e\xab\x90Z\x8b=\xce\x00\xe3\x170\x82%\u06b1\xb9\xf6\xb8\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4&\xb1\x1d\x06e\x88\xcet\xa5r\xa8Zc(s\x92\x12\xaa\x8b@\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xba\xbfB\xb2g\xfd\xcf8a\xfd\xd4#j^GHH\xb3X\x8965\u026d\xc5\u07a0\x00\x00\u07d4&\xc0\x05Kp\r:|-\xcb\xe2uh\x9dOL\xad\x16\xa35\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xc2\xff\xc3\x0e\xfd\xc5'>v\x18:\x16\xc2i\x8dnS\x12\x86\x89*\x11)\u0413g \x00\x00\u07d4&\u025f\x88I\u0240+\x83\xc8a!\u007f\xd0z\x9e\x84\u0377\x9d\x89\x10CV\x1a\x88)0\x00\x00\u07d4&\xcf\xff\xd0R\x15+\xb3\xf9W\xb4x\xd5\xf9\x8b#:|+\x92\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4&\u0521h\x91\xf5)\"x\x92\x17\xfc\u0606\xf7\xfc\xe2\x96\xd4\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4&\xd4\xec\x17\xd5\u03b2\u0214\xbd\u015d\nji]\xad+C\u0309\x9f\x1fxv\x1d4\x1a\x00\x00\u07d4&\xe8\x01\xb6,\x82q\x91\xddh\xd3\x1a\x01\x19\x90\x94\u007f\xd0\xeb\xe0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4&\xe9\xe2\xadr\x97\x02bd\x17\xef%\xde\r\xc8\x00\xf7\xa7y\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4&\xf9\xf7\xce\xfd~9K\x9d9$A+\xf2\u0083\x1f\xaf\x1f\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94&\xfe\x17L\xbfRfP\xe0\xcd\x00\x9b\xd6\x12e\x02\u038ehM\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\xe0\x94&\xff\nQ\xe7\xce\u0384\x00'ix\xdb\xd6#n\xf1b\xc0\xe6\x8a\x15.\x18V'T\nP\x00\x00\u07d4'\x10\x1a\x0fV\u04da\x88\u0168O\x9b2L\xdd\xe3>\\\xb6\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'\x14L\xa9\xa7w\x1a\x83j\xd5\x0f\x80?d\xd8i\xb2\xae+ \x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4'\x14i\x13V:\xa7E\xe2X\x840\xd94\x8e\x86\xea|5\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4'\x1d=H\x1c\xb8\x8evq\xad!iI\xb66^\x060=\xe0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4' \xf9\xcaBn\xf2\xf2\xcb\xd2\xfe\xcd9\x92\fO\x1a\x89\xe1m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'*\x13\x1aZejz:\xca5\u023d \"\"\xa7Y\"X\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4'D\xffgFA!\xe3Z\xfc)\"\x17qd\xfa/\xcb\x02g\x89\x05k\xc7^-c\x10\x00\x00\u07d4'J=w\x1a=p\x97\x96\xfb\xc4\xd5\xf4\x8f\xce/\xe3\x8cy\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4'Mi\x17\x0f\xe7\x14\x14\x01\x88+\x88j\xc4a\x8cj\xe4\x0e\u06c93\xc5I\x901r\f\x00\x00\u07d4'R\x1d\xeb;n\xf1An\xa4\u01c1\xa2\xe5\u05f3n\xe8\x1ca\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'Xu\xffO\xbb\f\xf3\xa40!1'H\u007fv\b\xd0L\xba\x89\x1b\x1c\x01\x0evmX\x00\x00\u07d4'j\x00n0(\xec\xd4L\xdbb\xba\nw\u0394\xeb\xd9\xf1\x0f\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4'k\x05!\xb0\xe6\x8b'}\xf0\xbb2\xf3\xfdH2cP\xbf\xb2\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4'o\xd7\xd2O\x8f\x88?Zz()[\xf1qQ\u01e8K\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'p\xf1N\xfb\x16]\u07bay\xc1\v\xb0\xaf1\xc3\x1eY3L\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4'vw\xab\xa1\xe5,;S\xbf\xa2\a\x1dN\x85\x9a\n\xf7\xe8\xe1\x8965\u026d\xc5\u07a0\x00\x00\u07d4'\x82Ff\xd2x\xd7\x04#\xf0=\xfe\x1d\u01e3\xf0/C\u2d4966\xc2^f\xec\xe7\x00\x00\u07d4'\x83\f_`#\xaf\xaa\xf7\x97Egl J\x0f\xac\u0360\xba\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94'\x84\x90?\x1d|\x1b\\\xd9\x01\xf8\x87]\x14\xa7\x9b<\xbe*V\x8a\x04\xbd\xa7\xe9\xd7J\xd5P\x00\x00\u07d4'\x8c\v\xdec\x0e\u00d3\xb1\xe7&\u007f\xc9\xd7\xd9p\x19\xe4\x14[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4'\x98q\x10\"\x1a\x88\b&\xad\xb2\xe7\xab^\xcax\xc6\xe3\x1a\xec\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94'\xac\a;\xe7\x9c\xe6W\xa9:\xa6\x93\xeeC\xbf\x0f\xa4\x1f\xef\x04\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4'\xb1iN\xaf\xa1e\xeb\xd7\xcc{\u025et\x81J\x95\x14\x19\u0709+^:\xf1k\x18\x80\x00\x00\u07d4'\xb6(\x16\xe1\xe3\xb8\u045by\xd1Q=]\xfa\x85[\f:*\x89\x05j\xf5\xc1\xfdiP\x80\x00\u07d4'\xbf\x94<\x163\xfe2\xf8\xbc\xcc\xdbc\x02\xb4\a\xa5rND\x892\xf8Lm\xf4\b\xc0\x80\x00\u07d4'\xbf\x9fD\xba}\x05\xc35@\u00e5;\xb0,\xbb\xff\xe7\xc3\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4'\xc2\xd7\xcaPM\xaa=\x90f\xdc\t\x13}\xc4/:\xaa\xb4R\x89 \x86\xac5\x10R`\x00\x00\u07d4'\xd1X\xac=>\x11\t\xabnW\x0e\x90\xe8]8\x92\xcdv\x80\x89\x05k\xc7^-c\x10\x00\x00\u07d4'\xe69\x89\xca\x1e\x90;\xc6 \xcf\x1b\x9c?g\xb9\xe2\xaee\x81\x89Hz\x9a0E9D\x00\x00\xe0\x94'\xf0<\xf1\xab\xc5\xe1\xb5\x1d\xbcDK(\x9eT,\x9d\u07f0\xe6\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4'\xfc\x85\xa4\x9c\xff\x90\xdb\xcf\xda\u071d\xdd@\u05b9\xa2!\nl\x89\x05k\xc7^-c\x10\x00\x00\u07d4(\x05A^\x1d\u007f\xde\xc6\xde\u07f8\x9eR\x1d\x10Y-t<\x10\x89\x05k\xc7^-c\x10\x00\x00\u07d4(\a>\xfc\x17\xd0\\\xab1\x95\xc2\xdb3+a\x98Gw\xa6\x12\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x12P\xa2\x91!'\nN\xe5\u05cd$\xfe\xaf\xe8,p\xba:\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x13\xd2c\xfc_\xf2G\x9e\x97\x05\x95\u05b6\xb5`\xf8\xd6\xd6\u0449lk\x93[\x8b\xbd@\x00\x00\u07d4(.\x80\xa5T\x87ZVy\x9f\xa0\xa9\u007fU\x10\u7557LN\x8965\u026d\xc5\u07a0\x00\x00\u07d4(3\x96\xce<\xac9\x8b\xcb\xe7\"\u007f2>x\xff\x96\u0407g\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4(4\x9f~\xf9t\xeaU\xfe6\xa1X;4\xce\xc3\xc4Pe\xf0\x89\f\xb63\u051eeY\x00\x00\u07d4(6\x120F\xb2\x84\xe5\xef\x10+\xfd\"\xb1v^P\x81\x16\xad\x89\x16S\xfb\xb5\xc4'\xe4\x00\x00\u07d4(<#\x14(<\x92\u0530d\xf0\xae\xf9\xbbRF\xa7\x00\u007f9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4(>\x11 7I\xb1\xfaO2\xfe\xbbq\xe4\x9d\x13Y\x198*\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94(>bR\xb4\xef\xcfFT9\x1a\xcbu\xf9\x03\u015bx\xc5\xfb\x8a\x02\x8a\x85t%Fo\x80\x00\x00\xe0\x94(Q\x0en\xff\x1f\xc8)\xb6WoC(\xbc98\xecze\x80\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4(X\xac\xac\xaf!\xea\x81\u02b7Y\x8f\xdb\xd8kE.\x9e\x8e\x15\x89$\x1a\x9bOaz(\x00\x00\u07d4(Z\xe5\x1b\x95\x00\u014dT\x13e\xd9ui\xf1K\xb2\xa3p\x9b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(f\xb8\x1d\xec\xb0.\xe7\n\xe2P\xce\xe5\xcd\xc7{Y\u05f6y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(i\x06\xb6\xbdIr\xe3\xc7\x16U\xe0K\xaf6&\f|\xb1S\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4(k\x18ma\xea\x1f\u05cd\x990\xfe\x12\xb0e7\xb0\\=Q\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94(t\xf3\xe2\x98]_{@f'\xe1{\xaaw+\x01\xab\u031e\x8a\x01F\x05\x04\x10v_8\x00\x00\xe0\x94(|\xf9\u0410.\xf8\x19\xa7\xa5\xf1ID[\xf1w^\xe8\xc4|\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4(\x81\x8e\x18\xb6\x10\x00\x13!\xb3\x1d\xf6\xfe}(\x15\u036d\xc9\xf5\x8965\u026d\xc5\u07a0\x00\x00\u07d4(\x86\x83$3~\x11\xba\x10l\xb4\x81\u0696/:\x84S\x80\x8d\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94(\x90K\xb7\xc40)C\xb7\t\xb1Myp\xe4+\x83$\u184a\x02\x1f\x97\x84j\a-~\x00\x00\u07d4(\x95\xe8\t\x99\xd4\x06\xadY.+&'7\xd3_}\xb4\xb6\x99\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4(\x96r\x80!N!\x8a\x12\f]\xda7\x04\x1b\x11\x1e\xa3mt\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4(\xa3\xda\t\xa8\x19H\x19\xae\x19\x9f.m\x9d\x13\x04\x81~(\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4(\xab\x16_\xfbi\xed\xa0\xc5I\xae8\xe9\x82o_\u007f\x92\xf8S\x89FM\xf6\xd7\xc8DY\x00\x00\u07d4(\xb7u\x85\xcb=U\xa1\x99\xab)\x1d:\x18\u018f\u8684\x8a\x89j@v\xcfy\x95\xa0\x00\x00\xe0\x94(\xd4\xeb\xf4\x1e=\x95\xf9\xbb\x9a\x89u#\\\x1d\x009>\x80\x00\u07d4)\nV\xd4\x1fn\x9e\xfb\xdc\xea\x03B\u0dd2\x9a\x8c\xdf\xcb\x05\x89\x12\xa5\xf5\x81h\xee`\x00\x00\u07d4)\x15bK\xcbg\x917\xb8\xda\xe9\xabW\xd1\x1bI\x05\xea\xeeK\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4)\x1e\xfe\x00\x81\xdc\xe8\xc1G\x99\xf7\xb2\xa46\x19\xc0\u00f3\xfc\x1f\x89A\rXj \xa4\xc0\x00\x00\u07d4)\x1f\x92\x9c\xa5\x9bT\xf8D>=Mu\xd9]\xee$<\xefx\x89\x1b\x1a\b\x927\a=\x00\x00\xe0\x94))\x8c\xcb\xdf\xf6\x89\xf8\u007f\xe4\x1a\xa6\xe9\x8f\u07f5=\xea\xf3z\x8a\x041\\2\xd7\x1a\x9e`\x00\x00\u07d4)/\"\x8b\n\x94t\x8c\x8e\xeca-$o\x98\x93c\xe0\x8f\b\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4)3\x84\xc4+o\x8f)\x05\xceR\xb7 \\\"t7la+\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4)4\xc0\xdf{\xbc\x17+l\x18k\vrTz\u038b\xf7TT\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4)<#\x06\xdf6\x04\xaeO\xda\r z\xbasog\xde\a\x92\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94)I\xfd\x1d\xef\\v\xa2\x86\xb3\x87$$\x80\x9a\a\xdb9f\xf3\x8a\x01\x1b\xd9\x06\u06a0\xc9C\x80\x00\u07d4)OIK?.\x14\xa3\xf8\xab\x00\x00\x00\u07d4)U\xc3W\xfd\x8fu\xd5\x15\x9a=\xfai\u0178z5\x9d\ua309lk\x93[\x8b\xbd@\x00\x00\u07d4)a\xfb9\x1ca\x95|\xb5\xc9\xe4\a\u0762\x938\u04f9,\x80\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4)h\x1d\x99\x12\xdd\xd0~\xaa\xbb\x88\xd0]\x90\xf7f\xe8bA}\x8965\u026d\xc5\u07a0\x00\x00\u07d4)kq\xc0\x01X\x19\xc2B\xa7\x86\x1eo\xf7\xed\xed\x8a_q\xe3\x89lh\xcc\u041b\x02,\x00\x00\u07d4)mf\xb5!W\x1aNA\x03\xa7\xf5b\xc5\x11\xe6\xaas-\x81\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4)o\x00\xde\x1d\u00fb\x01\xd4z\x8c\xcd\x1e]\x1d\u0661\xebw\x91\x8965\u026d\xc5\u07a0\x00\x00\u07d4)s\x85\xe8\x864FV\x85\xc21\xa3\x14\xa0\xd5\xdc\xd1F\xaf\x01\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4)v=\xd6\u069a|\x16\x11s\x88\x83!\ub9b6<\x8f\xb8E\x89\x11\xc7\xea\x16.x \x00\x00\u07d4)yt\x11t\xa8\xc1\xea\v\u007f\x9e\xdfe\x81w\x85\x94\x17\xf5\x12\x89\x19\x01\x96l\x84\x96\x83\x80\x00\u07d4)z\x88\x92\x1b_\xca\x10\u5edd\xed`\x02T7\xae\"\x16\x94\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94)}]\xbe\"//\xb5%1\xac\xbd\v\x01=\xc4F\xacsh\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4)\x82N\x94\xccCH\xbc\x962y\xdc\xdfG9\x17\x152L\u04c9i*\xe8\x89p\x81\xd0\x00\x00\u07d4)\x82\xd7j\x15\xf8G\xddA\xf1\x92*\xf3h\xfeg\x8d\x0eh\x1e\x89\x05k\xc7^-c\x10\x00\x00\u07d4)\x88\x87\xba\xb5|[\xa4\xf0aR)\xd7R_\xa1\x13\xb7\ua249\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94)\x8e\xc7kD\r\x88\a\xb3\xf7\x8b_\x90\x97\x9b\xeeB\xedC\u06ca\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4)\x93h`\x90B\xa8X\xd1\xec\xdf\x1f\xc0\xad\xa5\xea\xce\xca)\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4)\x9e\v\xcaU\xe0i\u0785\x04\xe8\x9a\xcan\xca!\u04ca\x9a]\x89\x03\x027\x9b\xf2\xca.\x00\x00\u07d4)\xac+E\x84T\xa3l~\x96\xc7:\x86g\"*\x12$,q\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94)\xad\u03c3\xb6\xb2\n\u01a44\xab\xb1\x99<\xbd\x05\xc6\x0e\xa2\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94)\xae\xf4\x8d\xe8\xc9\xfb\xadK\x9eL\xa9pyzU3\xebr-\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4)\xb3\xf5a\xeezn%\x94\x1e\x98\xa52[x\xad\u01d7\x85\xf3\x89\x05k\xc7^-c\x10\x00\x00\u07d4)\xbd\xc4\xf2\x8d\xe0\x18\x0fC<&\x94\xebt\xf5PL\xe9C7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4)\u0300M\x92+\xe9\x1fY\t\xf3H\xb0\xaa\xa5\xd2\x1b`x0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94)\xda>5\xb2;\xb1\xf7/\x8e\"X\xcf\u007fU3Y\xd2K\xac\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94)\xe6y\x90\xe1\xb6\xd5.\x10U\xff\xe0I\xc51\x95\xa8\x15B\u03ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4)\uab82v\x17b\xf4\xd2\xdbS\xa9\u018b\x0fk\vmNf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4)\xeb~\xef\xda\xe9\xfe\xb4I\xc6?\xf5\xf2y\xd6u\x10\xeb\x14\"\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4)\xf0\xed\xc6\x038\xe7\x11 \x85\xa1\xd1\x14\u068cB\u038fU\u0589\xa0Z\u007f\x0f\xd8%x\x00\x00\u07d4)\xf8\xfb\xa4\xc3\ar\xb0W\xed\xbb\xe6*\xe7B\f9\x05r\xe1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94)\xf9(l\x0es\x8d\x17!\xa6\x91\u01b9Z\xb3\u0667\x97\xed\xe8\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4*\b^%\xb6Hb\xf5\xe6\x8dv\x8e+\x0fz\x85)\x85\x8e\xee\x89k\x88:\xcdWf\xcd\x00\x00\u07d4**\xb6\xb7Lz\xf1\xd9Gk\xb5\xbc\xb4RG\x97\xbe\xdc5R\x8965\u026d\xc5\u07a0\x00\x00\u07d4*9\x19\nO\u0783\u07f3\xdd\xcbL_\xbb\x83\xaclIu\\\x8965\u026d\xc5\u07a0\x00\x00\u07d4*@\r\xff\x85\x94\xder(\xb4\xfd\x15\xc3#\"\xb7[\xb8}\xa8\x89\x051\xa1\u007f`z-\x00\x00\xe0\x94*D\xa7!\x8f\xe4Me\xa1\xb4\xb7\xa7\u0671\xc2\xc5,\x8c>4\x8a\r-\x06\xc3\x05\xa1\xebW\x80\x00\u07d4*F\xd3Swqv\xff\x8e\x83\xff\xa8\x00\x1fOp\xf9s:\xa5\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4*Y_\x16\xee\xe4\xcb\f\x17\u0662\xd99\xb3\xc1\x0flgrC\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4*Y\xe4~\xa5\xd8\xf0\xe7\xc0(\xa3\xe8\xe0\x93\xa4\x9c\x1bP\xb9\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4*[\xa9\xe3L\u054d\xa5L\x9a'\x12f:;\xe2t\xc8\xe4{\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94*^:@\xd2\xcd\x03%vm\xe7:=g\x18\x96\xb3b\xc7;\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94*cY\x0e\xfe\x99\x86\xc3\xfe\xe0\x9b\n\n3\x8b\x15\xbe\xd9\x1f!\x8a\x01^\x1cN\x05\xee&\xd0\x00\x00\u07d4*gf\n\x13h\xef\xcdbn\xf3k+\x1b`\x19\x80\x94\x1c\x05\x89\a?u\u0460\x85\xba\x00\x00\u07d4*t+\x89\x10\x94\x1e\t2\x83\n\x1d\x96\x92\xcf\u0484\x94\xcf@\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4*tl\xd4@'\xaf>\xbd7\xc3x\xc8^\xf7\xf7T\xab_(\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4*\x81\xd2|\xb6\xd4w\x0f\xf4\xf3\u0123\xba\x18\xe5\xe5\u007f\aQ|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4*\x91\xa9\xfe\xd4\x1b}\x0e\\\xd2\xd81X\xd3\xe8\xa4\x1a\x9a-q\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94*\x9cW\xfe{k\x13\x8a\x92\rgo{\x1a%\x10\x80\xff\xb9\x8a4\xf0\x86\xf3\xb3;h@\x00\x00\u07d4+p\x1d\x16\xc0\xd3\xcc\x1eL\xd8TE\xe6\xad\x02\ue92c\x01-\x89 \x86\xac5\x10R`\x00\x00\xe0\x94+q|\xd42\xa3#\xa4e\x909\x84\x8d;\x87\xde&\xfc\x95F\x8ai\xe1\r\xe7fv\u0400\x00\x00\u07d4+t\xc3s\xd0K\xfb\x0f\xd6\n\x18\xa0\x1a\x88\xfb\xe8Gp\u5309\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4+w\xa4\u060c\rV\xa3\xdb\xe3\xba\xe0J\x05\xf4\xfc\u0477W\xe1\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94+\x84\x88\xbd-<\x19z=&\x15\x18\x15\xb5\xa7\x98\xd2qh\u070a\x01j\x1f\x9f_\xd7\xd9`\x00\x00\u07d4+\x8a\r\xee\\\xb0\xe1\xe9~\x15\xcf\xcan\x19\xad!\xf9\x95\ufb49\x1bUC\x8d\x9a$\x9b\x00\x00\xe0\x94+\x8f\xe4\x16n#\xd1\x19c\xc0\x93+\x8a\u078e\x01E\xea\ap\x8a\t(\x96R\x9b\xad\u0708\x00\x00\xe0\x94+\x99\xb4.OBa\x9e\xe3k\xaa~J\xf2\xd6^\xac\xfc\xba5\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4+\xab\x0f\xbe(\u0544 \xb5 6w\n\x12\xf9\x95*\xeai\x11\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4+\xad\xe9\x1d\x15E\x17b\x0f\u05349\xac\x97\x15zA\x02\xa9\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4+\xaf\x8dn\"\x11t\x12H \xeeI+\x94Y\xecO\xad\xaf\xbb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4+\xaf\xbf\x9e\x9e\xd2\xc2\x19\xf7\xf2y\x13t\xe7\xd0\\\xb0gw\xe7\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\xe0\x94+\xb3f\xb9\xed\xcb\r\xa6\x80\xf0\xe1\v;n(t\x81\x90\xd6\u00ca\x01:b\u05f5v@d\x00\x00\xe0\x94+\xb6\xf5x\xad\xfb\u7ca1\x16\xb3UO\xac\xf9\x96\x98\x13\xc3\x19\x8a\x01\x91'\xa19\x1e\xa2\xa0\x00\x00\u07d4+\xbeb\xea\xc8\f\xa7\xf4\xd6\xfd\xee~}\x8e(\xb6:\xcfw\x0e\x89\x81\xe3-\xf9r\xab\xf0\x00\x00\u07d4+\xbeg*\x18WP\x8fc\x0f*^\xdbV=\x9e\x9d\xe9(\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4+\xc4)\xd6\x18\xa6jL\xf8-\xbb-\x82N\x93V\xef\xfa\x12j\x89lj\xccg\u05f1\xd4\x00\x00\u07d4+\xd2R\xe0\xd72\xff\x1d|x\xf0\xa0.l\xb2T#\xcf\x1b\x1a\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4+\xdd\x03\xbe\xbb\xee';l\xa1\x05\x9b4\x99\x9a[\xbda\xbby\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,\x04\x11\\>R\x96\x1b\r\xc0\xb0\xbf1\xfb\xa4ToYf\xfd\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94,\x06\u0752+aQJ\xaf\xed\xd8D\x88\xc0\u008em\xcf\x0e\x99\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94,\f\xc3\xf9QH,\u0222\x92X\x15hN\xb9\xf9N\x06\x02\x00\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4,\x0e\xe14\u0633aE\xb4{\xee\u7bcd'8\xdb\xda\b\xe8\x89\n\xe5os\x0em\x84\x00\x00\u07d4,\x0f[\x9d\xf46%y\x8e~\x03\xc1\xa5\xfdjm\t\x1a\xf8+\x89\x01\xb0\xfc\xaa\xb2\x000\x00\x00\u07d4,\x12\x8c\x95\xd9W!Q\x01\xf0C\u074f\u0142EmA\x01m\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4,\x18\x00\xf3_\xa0->\xb6\xff[%(_^J\xdd\x13\xb3\x8d\x891\"\u04ed\xaf\xde\x10\x00\x00\u07d4,\x1c\x19\x11N=m\xe2xQHK\x8d'\x15\xe5\x0f\x8a\x10e\x89\x05k\xc7^-c\x10\x00\x00\u07d4,\x1c\xc6\xe1\x8c\x15$\x88\xba\x11\xc2\xcc\x1b\xce\xfa-\xf3\x06\xab\u0449Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94,\x1d\xf8\xa7oH\xf6\xb5K\u03dc\xafV\xf0\xee\x1c\xf5z\xb3=\x8a\x02$\u007fu\x00\x89\xdaX\x00\x00\u07d4,!G\x94z\xe3?\xb0\x98\xb4\x89\xa5\xc1k\xff\xf9\xab\xcdN*\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4,#OP\\\xa8\xdc\xc7}\x9b~\x01\xd2W\xc3\x18\xcc\x199m\x89\x05k\xc7^-c\x10\x00\x00\u07d4,$(\xe4\xa6it\xed\xc8\"\xd5\xdb\xfb$\x1b'(\aQX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,-\x15\xff9V\x1c\x1br\xed\xa1\xcc\x02\u007f\xfe\xf27C\xa1D\x89\u0500\xed\x9e\xf3+@\x00\x00\u07d4,-\xb2\x8c3\t7^\xea1\x82\x1b\x84\xd4\b\x93\x0e\xfa\x1a\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4,Z-\n\xbd\xa0;\xbe!W\x81\xb4\xff)l\x8ca\xbd\xba\xf6\x89\x01\xa8\xe5oH\xc0\"\x80\x00\u07d4,[}{\x19Z7\x1b\xf9\xab\u0774/\xe0O/\x1d\x9a\x99\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4,]\xf8ffj\x19K&\u03bb@~J\x1f\xd7> \x8d^\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94,`?\xf0\xfe\x93alCW>\xf2y\xbf\xea@\x88\x8dj\xe7\x8a\x01\x00\xf4\xb6\xd6gW\x90\x00\x00\xe0\x94,hF\xa1\xaa\x99\x9a\"F\xa2\x87\x05`\x00\xbaM\u02e8\xe6=\x8a\x02\x1f/o\x0f\xc3\xc6\x10\x00\x00\u0794,j\xfc\xd4\x03|\x1e\xd1O\xa7O\xf6u\x8e\tE\xa1\x85\xa8\xe8\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4,ki\x9d\x9e\xad4\x9f\x06\u007fEq\x1a\aJd\x1d\xb6\xa8\x97\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,o\\\x12L\u01c9\xf8\xbb9\x8e?\x88\x97Q\xbcK`-\x9e\x89\x01Y\xf2\v\xed\x00\xf0\x00\x00\u07d4,\x83\xae\xb0/\xcf\x06}e\xa4p\x82\xfd\x97x3\xab\x1c\uc449\b'8#%\x8a\xc0\x00\x00\xe0\x94,\x89\xf5\xfd\xca=\x15T\t\xb68\xb9\x8at.U\xebFR\xb7\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4,\x96HI\xb1\xf6\x9c\xc7\u03a4D%8\xed\x87\xfd\xf1l\xfc\x8f\x89lk\x93[\x8b\xbd@\x00\x00\u0794,\x9f\xa7,\x95\xf3}\b\xe9\xa3`\t\u7930\u007f)\xba\xd4\x1a\x88\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4,\xafk\xf4\xec}Z\x19\xc5\xe0\x89z^\xeb\x01\x1d\xce\xceB\x10\x89\a\x93H5\xa01\x16\x00\x00\u07d4,\xb4\xc3\xc1k\xb1\xc5^|kz\x19\xb1'\xa1\xac\x93\x90\xcc\t\x89\xb8'\x94\xa9$O\f\x80\x00\xe0\x94,\xb5IZPS6\xc2FT\x10\xd1\xca\xe0\x95\xb8\xe1\xba\\\u074a\x04<3\xc1\x93ud\x80\x00\x00\u07d4,\xb6\x15\a:@\xdc\u06d9\xfa\xa8HW.\x98{;\x05n\xfb\x89+X\xad\u06c9\xa2X\x00\x00\u07d4,\xbam]\r\xc2\x04\xea\x8a%\xad\xa2\xe2oVu\xbd_/\u0709H#\xef}\u06da\xf3\x80\x00\u07d4,\xbb\fs\u07d1\xb9\x17@\xb6i;wJ}\x05\x17~\x8eX\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4,\xcbfIM\n\xf6\x89\xab\xf9H=6]x$D\xe7\u07ad\x8965\u026d\xc5\u07a0\x00\x00\u07d4,\xcc\x1f\x1c\xb5\xf4\xa8\x00.\x18k \x88]\x9d\xbc\x03\f\b\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,\u03c0\xe2\x18\x98\x12^\xb4\xe8\a\u0342\xe0\x9b\x9d(Y/n\x89lk\x93[\x8b\xbd@\x00\x00\u07d4,\u0456\x94\u0452j\x0f\xa9\x18\x9e\u07ba\xfcg\x1c\xf1\xb2\u02a5\x8965\u026d\xc5\u07a0\x00\x00\u07d4,\u04d34\xac~\xacyrW\xab\xe3sa\x95\xf5\xb4\xb5\xce\x0f\x89\x05kGx^7&\x00\x00\u07d4,\u05de\xb5 '\xb1,\x18\x82\x8e>\xaa\xb2\x96\x9b\xfc\u0487\xe9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4,\xd8xfV\x8d\xd8\x1a\xd4}\x9d:\u0404nZePss\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4,\xdb9De\x06\x16\xe4|\xb1\x82\xe0`2/\xa1Hyx\u0389b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4,\xe1\x1a\x92\xfa\xd0$\xff+>\x87\xe3\xb5B\xe6\xc6\r\xcb\u0656\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4-\x03&\xb2?\x04\t\xc0\xc0\xe9#hc\xa13\aZ\x94\xba\x18\x89\vg\x9b\xe7[\xe6\xae\x00\x00\u07d4-\r\xecQ\xa6\xe8s0\xa6\xa8\xfa*\x0fe\u060dJ\xbc\xdfs\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4-#vkok\x05s}\xad\x80\xa4\x19\xc4\x0e\xdaMw\x10>\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4-+\x03#Y\xb3c\x96O\xc1\x1aQ\x82c\xbf\xd0T1\xe8g\x89\b\x1c\x1d\xf7b\x9ep\x00\x00\u07d4-4\x80\xbf\be\aJr\xc7u\x9e\xe5\x13{Mp\xc5\x1c\xe9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4-5\xa9\xdfbu\u007f\u007f\xfa\xd1\x04\x9a\xfb\x06\xcaJ\xfcFLQ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4-@U\x8b\x06\xf9\n9#\x14U\x92\x12;gt\xe4n1\xf4\x8965\u026d\xc5\u07a0\x00\x00\u07d4-Bi\x12\xd0Y\xfa\xd9t\v.9\n.\xea\xc0To\xf0\x1b\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4-S-\xf4\xc69\x11\xd1\u0391\xf6\xd1\xfc\xbf\xf7\x96\x0fx\xa8\x85\x89Z\x85\x96\x8aXx\u0680\x00\u07d4-S\x91\xe98\xb3HX\u03d6[\x84\x051\xd5\xef\xdaA\v\t\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94-[B\xfcY\xeb\xda\r\xfdf\xae\x91K\u008c\x1b\nn\xf8:\x8a+\u0235\x9f\xdc\xd86c\x80\x00\u07d4-]s5\xac\xb06+G\u07e3\xa8\xa4\xd3\xf5\x94\x95D\u04c0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-a\xbf\xc5hs\x92<+\x00\t]\xc3\xea\xa0\xf5\x90\u062e\x0f\x8a\x04ef\xdf\xf8\xceU`\x00\x00\u07d4-e\x11\xfdz8\x00\xb2hT\xc7\xec9\xc0\u0735\xf4\xc4\xe8\xe8\x89\x15\xad\u077a/\x9ew\x00\x00\u07d4-}\\@\u076f\xc4P\xb0Jt\xa4\u06bc+\xb5\xd6e\x00.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4-\x89\xa8\x00jO\x13z \xdc+\xecF\xfe.\xb3\x12\xea\x96T\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4-\x8cR2\x9f8\u04a2\xfa\x9c\xba\xf5\u0143\xda\xf1I\v\xb1\x1c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4-\x8e\x06\x18\x92\xa5\xdc\xce!\x96j\xe1\xbb\a\x88\xfd>\x8b\xa0Y\x89\r\x8e\\\xe6\x17\xf2\xd5\x00\x00\u07d4-\x8e[\xb8\xd3R\x16\x95\xc7~|\x83N\x02\x91\xbf\xac\xeet\b\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4-\x90\xb4\x15\xa3\x8e.\x19\xcd\xd0/\U000ed069z\xf7\xcb\xf6r\x89\x05\xf3\xc7\xf6A1\xe4\x00\x00\u07d4-\x9b\xado\x1e\xe0*p\xf1\xf1=\xef\\\u0332z\x9a'@1\x89a\t=|,m8\x00\x00\u07d4-\x9c_\xec\u04b4O\xbbj\x1e\xc72\xea\x05\x9fO\x1f\x9d+\\\x896\xca2f\x1d\x1a\xa7\x00\x00\xe0\x94-\xa6\x17iP\t\xccW\xd2j\u0510\xb3*]\xfb\xeb\x93N^\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4-\xa7k|9\xb4 \u323a,\x10 \xb0\x85k\x02pd\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4-\u01ddn\u007fU\xbc\xe2\xe2\xd0\xc0*\xd0|\uca3bR\x93T\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\xe0\x94-\xca\x0eD\x9a\xb6F\xdb\xdf\u04d3\xa9fb\x96\v\u02b5\xae\x1e\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4-\xd3%\xfd\xff\xb9{\x19\x99R\x84\xaf\xa5\xab\xdbWJ\x1d\xf1j\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4-\xd5x\xf7@}\xfb\xd5H\xd0^\x95\xcc\u00dcHT)bj\x89\u3bb5sr@\xa0\x00\x00\u07d4-\xd8\xee\xef\x87\x19J\xbc,\xe7X]\xa1\xe3[|\xeax\f\xb7\x8965\xc6 G9\u0640\x00\u07d4-\xdf@\x90Wi\xbc\xc4&\xcb,)8\xff\xe0w\xe1\u8758\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4-\xe0\x96D\x00\u0082\xbd\u05ca\x91\x9ck\xf7|k_yay\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-\xe3\x1a\xfd\x18\x9a\x13\xa7o\xf6\xfes\xea\xd9\xf7K\xb5\u0126)\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4-\xec\x982\x9d\x1f\x96\u00e5\x9c\xaay\x81uTR\xd4\xdaI\u0549\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94-\ue422\x8f\x19-gj\x87s#+V\xf1\x8f#\x9e/\xad\x8a\x03\xef\xa7\xe7G\xb6\u046d\x00\x00\xe0\x94.\b\x80\xa3E\x96#\a \xf0Z\xc8\xf0e\xaf\x86\x81\u0736\u008a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4.\fW\xb4qP\xf9Z\xa6\xa7\xe1j\xb9\xb1\xcb\xf5C(\x97\x9a\x89\x05k\xc7^-c\x10\x00\x00\u07d4.\x10\x91\v\xa6\xe0\xbc\x17\xe0UUf\x14\u02c7\t\x0fM~[\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94.$\xb5\x97\x87;\xb1A\xbd\xb27\xea\x8aZ\xb7Gy\x9a\xf0-\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4.(\x10\xde\xe4J\xe4\xdf\xf3\xd8cB\xab\x12fW\xd6S\xc36\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4.,\xbdz\xd8%G\xb4\xf5\xff\x8b:\xb5o\x94*dE\xa3\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4.-~\xa6k\x9fG\xd8\xccR\xc0\x1cR\xb6\u147c}G\x86\x89\xd8\xd4`,&\xbfl\x00\x00\u07d4.C\x93H\u07caBw\xb2*v\x84W\xd1\x15\x8e\x97\xc4\t\x04\x89*\x1e\x9f\xf2o\xbfA\x00\x00\xe0\x94.F\xfc\xeej;\xb1E\xb5\x94\xa2C\xa3\x91?\xce]\xado\xba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794.G\xf2\x87\xf4\x98#7\x13\x85\r1&\x82<\xc6}\xce\xe2U\x88\u029d\x9e\xa5X\xb4\x00\x00\u07d4.N\u1b99j\xa0\xa1\xd9$(\xd0fR\xa6\xbe\xa6\xd2\xd1]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.R\x91+\xc1\x0e\xa3\x9dT\xe2\x93\xf7\xae\u05b9\x9a\x0fLs\xbe\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4.a\x9fW\xab\xc1\u91ea\x93j\xe3\xa2&Ib\xe7\xeb-\x9a\x89(\xfb\x9b\x8a\x8aSP\x00\x00\u07d4.d\xa8\xd7\x11\x11\xa2/L]\xe1\xe09\xb36\xf6\x8d9\x8a|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.i3T=O,\xc0\vSP\xbd\x80h\xba\x92C\u05be\xb0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94.~\x05\xe2\x9e\u0767\xe4\xae%\xc5\x175C\xef\xd7\x1fm=\x80\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4.\u007fFU \xec5\xcc#\u058eue\x1b\xb6h\x95D\xa1\x96\x898\xec[r\x1a\x1a&\x80\x00\u07d4.\x8e\xb3\nqn_\xe1\\t#>\x03\x9b\xfb\x11\x06\xe8\x1d\x12\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94.\x98$\xb5\xc12\x11\x1b\xca$\xdd\xfb\xa7\xe5u\xa5\xcdr\x96\xc1\x8a\x03\xa4\x84Qnm\u007f\xfe\x00\x00\u07d4.\xa5\xfe\xe6?3z7nK\x91\x8e\xa8!H\xf9MH\xa6&\x89e\x0f\x8e\r\u0493\xc5\x00\x00\u07d4.\xafN*F\xb7\x89\xcc\u0088\xc8\xd1\xd9)N?\xb0\x858\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4.\xaf\xf9\xf8\xf8\x110d\u04d5z\xc6\xd6\xe1\x1e\xeeB\xc8\x19]\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4.\xba\fn\xe5\xa1\x14\\\x1cW9\x84\x96:`]\x88\nz \x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4.\xc9X\"\xeb\x88{\xc1\x13\xb4q*M\xfd\u007f\x13\xb0\x97\xb5\xe7\x8965\u026d\xc5\u07a0\x00\x00\u07d4.\xcaj<]\x9fD\x9d\tV\xbdC\xfa{M{\xe8CYX\x89lk\xdaip\x9c\xc2\x00\x00\xe0\x94.\xca\xc5\x04\xb23\x86n\xb5\xa4\xa9\x9e{\u0490\x13Y\xe4;=\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4.\xeb\xf5\x942\xb5(\x92\xf98\v\xd1@\xaa\x99\xdc\xf8\xad\f\x0f\x89\b=lz\xabc`\x00\x00\u07d4.\xee\xd5\x04q\xa1\xa2\xbfS\xee0\xb1#.n\x9d\x80\xef\x86m\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4.\xefk\x14\x17\u05f1\x0e\xcf\xc1\x9b\x12:\x8a\x89\xe7>RlX\x89 \x86\xac5\x10R`\x00\x00\u07d4.\xf8i\xf05\vW\xd54x\xd7\x01\xe3\xfe\xe5)\xbc\x91\x1cu\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4.\xf9\xe4eqj\xca\u03f8\xc8%/\xa8\xe7\xbcyi\xeb\xf6\u4255\x9e\xb1\xc0\xe4\xae \x00\x00\xe0\x94.\xfcLd}\xacj\xca\xc3Uw\xad\"\x17X\xfe\xf6ao\xaa\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4/\x13eu&\xb1w\xca\xd5G\u00d0\x8c\x84\x0e\xffd{E\u0649?v\x84\x9c\xf1\xee,\x80\x00\u07d4/\x18}ZpMZ3\x8c[(v\xa0\x90\xdc\xe9d(N)\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94/%#\u0303O\x00\x86\x05$\x02bb\x96gQ\x86\xa8\u508a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4/(*\xbb\xb6\u0523\xc3\xcd;\\\xa8\x12\xf7d>\x800_\x06\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4/+\xba\x1b\x17\x96\x82\x1avo\xced\xb8O(\xech\xf1Z\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4/1]\x90\x16\xe8\xee_Sf\x81 /\x90\x84\xb02TMM\x898<\xd1+\x9e\x86<\x00\x00\u07d4/M\xa7SC\x0f\xc0\x9es\xac\xbc\xcd\xcd\xe9\xdad\u007f+]7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4/P\x80\xb8?~-\xc0\xa1\xdd\x11\xb0\x92\xad\x04+\xffx\x8fL\x89\xb4\xf8\xfby#\x1d+\x80\x00\u07d4/a\uf941\x9dp_+\x1eN\xe7T\xae\xb8\xa8\x19Pju\x89O%\x91\xf8\x96\xa6P\x00\x00\xe0\x94/f\xbf\xbf\"b\xef\u030d+\xd0DO\u0170ib\x98\xff\x1e\x8a\x02\x1a\xd95\xf7\x9fv\xd0\x00\x00\u07d4/m\xce\x130\u015e\xf9!`!TW-MK\xac\xbd\x04\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4/}2\x90\x85\x1b\xe5\u01b4\xb4?}Et2\x9fa\xa7\x92\u00c9\x05k\xc7^-c\x10\x00\x00\u07d4/\x858\x17\xaf\u04f8\xf3\xb8n\x9f`\xeew\xb5\xd9ws\xc0\xe3\x89N\xae\xeaD\xe3h\xb9\x00\x00\u07d4/\xa4\x91\xfbY \xa6WN\xbd(\x9f9\xc1\xb2C\r-\x9aj\x89lk\x93[\x8b\xbd@\x00\x00\u07d4/\xb5f\xc9K\xbb\xa4\xe3\xcbg\xcd\xda}_\xadq1S\x91\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4/\xbbPJ]\xc5'\xd3\xe3\xeb\x00\x85\xe2\xfc<}\xd58\xcbz\x89C\u00b1\x8a\xec<\n\x80\x00\u07d4/\xbc\x85y\x8aX5\x98\xb5\"\x16mn\x9d\xda\x12\x1db}\xbc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4/\xbc\xef3\x84\xd4 \xe4\xbfa\xa0f\x99\x90\xbcpT\U00065bc9lk\x93[\x8b\xbd@\x00\x00\xe0\x94/\xc8.\xf0v\x93#A&Oaz\f\x80\xddW\x1ej\xe99\x8a\x01\x84$\xf5\xf0\xb1\xb4\xe0\x00\x00\u07d4/\u075by\u07cd\xf50\xadc\xc2\x0eb\xafC\x1a\xe9\x92\x16\xb8\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4/\xe0\x02?W\"e\x0f:\x8a\xc0\x10\t\x12^t\xe3\xf8.\x9b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4/\xe0\xccBKS\xa3\x1f\t\x16\xbe\b\xec\x81\xc5\v\xf8\xea\xb0\xc1\x89 \x86\xac5\x10R`\x00\x00\u07d4/\xe1:\x8d\a\x85\u0787X\xa5\xe4\x18v\xc3n\x91l\xf7Pt\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4/\xea\x1b/\x83O\x02\xfcT3?\x8a\x80\x9f\x048\xe5\x87\n\xa9\x89\x01\x18T\xd0\xf9\xce\xe4\x00\x00\u07d4/\xee6\xa4\x9e\xe5\x0e\xcfqo\x10G\x91VFw\x9f\x8b\xa0?\x899B\"\xc4\u0686\xd7\x00\x00\u07d4/\xef\x81G\x8aK.\x80\x98\xdb_\xf3\x87\xba!S\xf4\xe2+y\x896'\xe8\xf7\x127<\x00\x00\u07d4/\xf1`\xc4Or\xa2\x99\xb5\xec-q\xe2\x8c\xe5Dm/\u02ef\x89\x13\x84\x00\xec\xa3d\xa0\x00\x00\u07d4/\xf1\xcaU\xfd\x9c\xec\x1b\x1f\xe9\U00029af7LQ<\x1e*\xaa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94/\xf5\u02b1,\r\x95\u007f\xd33\xf3\x82\xee\xb7Q\a\xa6L\xb8\xe8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94/\xf80\xcfU\xfb\x00\u0560\xe05\x14\xfe\xcdD1K\xd6\xd9\xf1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4/\xfe\x93\xec\x1aV6\xe9\xee4\xafp\xdf\xf5&\x82\xe6\xffpy\x89lk\x93[\x8b\xbd@\x00\x00\u07d40\x03y\x88p&q\xac\xbe\x89,\x03\xfeW\x88\xaa\x98\xaf(z\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d40$\x8dX\xe4\x14\xb2\x0f\xed:lH+Y\xd9\xd8\xf5\xa4\xb7\xe2\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4019\xbcYd\x03\xd5\u04d3\x1fwLf\u013aFtT\u06c9\\%\xe1J\xea(?\x00\x00\u079408\x00\x87xie\x14\x9e\x81B;\x15\xe3\x13\xba2\xc5\u01c3\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d40:0\xacB\x86\xae\x17\xcfH=\xad{\x87\fk\xd6M{J\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d40?\xba\xeb\xbeF\xb3[n[t\x94j_\x99\xbc\x15\x85\xca\xe7\x89/\x9a\xc0i_[\xba\x00\x00\u07d40ADZ3\xba\x15\x87A\x16\r\x9c4N\xb8\x8e\\0o\x94\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d40H\x01d\xbc\xd8It\xeb\xc0\xd9\f\x9b\x9a\xfa\xb6&\xcd\x1cs\x89+^:\xf1k\x18\x80\x00\x00\u07d40N\u019atTW!\xd71j\xefM\u03f4\x1a\u015e\xe2\xf0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x940Q\x182\x91\x8d\x804\xa7\xbe\xe7.\xf2\xbf\xeeD\x0e\u02fc\xf6\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d40Q?\u029f6\xfdx\x8c\xfe\xa7\xa3@\xe8m\xf9\x82\x94\xa2D\x89\x18;_\x03\xb1G\x9c\x00\x00\u07d40U\xef\xd2`)\xe0\xd1\x1b\x93\r\xf4\xf5;\x16,\x8c?\xd2\u0389\x1b\x1a\b\x927\a=\x00\x00\u07d40]&\xc1\v\xdc\x10?k\x9c!'.\xb7\xcb-\x91\b\xc4~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d40_x\xd6\x18\xb9\x90\xb4)[\xac\x8a-\xfa&(\x84\xf8\x04\xea\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x940d\x89\x9a\x96\x1a>\x1d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d40\x98\xb6]\xb9>\xca\xca\xf75\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d40\uc4d2$J!\b\u0247\xbc\\\xdd\xe0\ud7c3z\x81{\x89T\x99%\xf6\xc9\xc5%\x00\x00\xe0\x940\xed\x11\xb7{\xc1~^f\x94\u023c[nG\x98\xf6\x8d\x9c\xa7\x8a\x1eo\xb3B\x1f\xe0)\x9e\x00\x00\u07d40\xf7\xd0%\xd1o{\xee\x10U\x80Ho\x9fV\x1c{\xae?\xef\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x940\xfb\xe5\x88_\x9f\xcc\xe9\xea^\u06c2\xedJ\x11\x96\xdd%\x9a\xed\x8a\x01\x19\xe4\u007f!8\x1f@\x00\x00\u07d41\x04}p?c\xb94$\xfb\xbdn/\x1f\x9et\xde\x13\xe7\t\x89\x9a\x81f\xf7\u6ca7\x80\x00\u07d411?\xfdc[\xf2\xf32HA\xa8\x8c\a\xed\x14aD\xce\xeb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d41Y\xe9\fH\xa9\x15\x90J\xdf\u24b2/\xa5\xfd^ryk\x896\xaf\xe9\x8f&\x06\x10\x00\x00\u07d41]\xb7C\x9f\xa1\u0574#\xaf\xa7\xddq\x98\xc1\xcft\xc9\x18\xbc\x89 \x86\xac5\x10R`\x00\x00\u07d41^\xf2\xdab\x0f\xd30\xd1.\xe5]\xe5\xf3)\xa6\x96\xe0\xa9h\x89\b!\xab\rD\x14\x98\x00\x00\u07d41n\x92\xa9\x1b\xbd\xa6\x8b\x9e/\x98\xb3\xc0H\x93N<\xc0\xb4\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d41n\xb4\xe4}\xf7\x1bB\xe1mo\xe4h%\xb72{\xaf1$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d41q\x87~\x9d\x82\f\xc6\x18\xfc\t\x19\xb2\x9e\xfd3?\xdaI4\x8965\u026d\xc5\u07a0\x00\x00\u07d41|\xf4\xa2<\xb1\x91\xcd\xc5c\x12\u009d\x15\xe2\x10\xb3\xb9\xb7\x84\x89\a\xcef\xc5\x0e(@\x00\x00\u07d41\x8b.\xa5\xf0\xaa\xa8y\xc4\xd5\xe5H\xac\x9d\x92\xa0\xc6t\x87\xb7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d41\x8cv\xec\xfd\x8a\xf6\x8dpUSR\xe1\xf6\x01\xe3Y\x88\x04-\x89\x1b1\x19.h\xc7\xf0\x00\x00\u07d41\x8f\x1f\x8b\xd2 \xb0U\x8b\x95\xfb3\x10\x0f\xfd\xbbd\r|\xa6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d41\xaa;\x1e\xbe\x8cM\xbc\xb6\xa7\b\xb1\xd7H1\xe6\x0eIv`\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d41\xab\b\x89f\xec\xc7\"\x92X\xf6\t\x8f\xceh\xcf9\xb3\x84\x85\x8965\u026d\xc5\u07a0\x00\x00\xe0\x941\xadM\x99F\xef\t\xd8\xe9\x88\xd9F\xb1\"\u007f\x91A\x90\x176\x8a\x04\xd8S\xc8\xf8\x90\x89\x80\x00\x00\xe0\x941\xb4;\x01]\x00\x81d~h\x00\x00\u07d424\x86\xcad\xb3uGO\xb2\xb7Y\xa9\xe7\xa15\x85\x9b\xd9\xf6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d427I\xa3\xb9q\x95\x9eF\u0234\x82-\xca\xfa\xf7\xaa\xf9\xbdn\x89\x01\x16q\xa5\xb2Ep\x00\x00\u07d42:\xadA\xdfKo\xc8\xfe\u038c\x93\x95\x8a\xa9\x01\xfah\bC\x894\x95tD\xb8@\xe8\x00\x00\xe0\x942;<\xfe>\xe6+\xbd\xe2\xa2a\xe5<\xb3\xec\xc0X\x10\xf2\u018a\x02\ub3b1\xa1r\u0738\x00\x00\u07d42?\xca^\xd7\u007fi\x9f\x9d\x990\xf5\xce\xef\xf8\xe5oY\xf0<\x89Hz\x9a0E9D\x00\x00\u07d42H\\\x81\x87(\xc1\x97\xfe\xa4\x87\xfb\xb6\xe8)\x15\x9e\xba\x83p\x899!\xb4\x13\xbcN\xc0\x80\x00\xe0\x942P\xe3\xe8X\xc2j\xde\u032d\xf3jVc\xc2*\xa8LAp\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x942Y\xbd/\xdd\xfb\xbco\xba\u04f6\xe8t\xf0\xbb\xc0,\xda\x18\xb5\x8a\x02\x84`VI[\r\x18\x80\x00\u07d42uIo\xd4\u07491\xfdi\xfb\n\v\x04\xc4\xd1\xff\x87\x9e\xf5\x89\x18-~L\xfd\xa08\x00\x00\u07d42{\xb4\x9euOo\xb4\xf73\xc6\xe0o9\x89\xb4\xf6]K\xee\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d42\x82y\x1do\xd7\x13\xf1\xe9OK\xfdV^\xaax\xb3\xa0Y\x9d\x89Hz\x9a0E9D\x00\x00\u07d42\x83\xeb\u007f\x917\xdd9\xbe\xd5_\xfek\x8d\xc8E\xf3\xe1\xa0y\x89\x03\x97\n\xe9!Ux\x00\x00\u07d42\x86\t\x97\xd70\xb2\xd8;s$\x1a%\xd3f}Q\xc9\b\xef\x89\x1b\x1a\b\x927\a=\x00\x00\xe0\x942\x86\u047cez1,\x88G\xd9<\xb3\xcbyP\xf2\xb0\xc6\xe3\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x942\xa2\r\x02\x8e,b\x18\xb9\xd9[D\\w\x15$cj\"\xef\x8a\x02\x02\xfe\xfb\xf2\xd7\xc2\xf0\x00\x00\u07d42\xa7\x06\x91%\\\x9f\xc9y\x1aOu\u0238\x1f8\x8e\n%\x03\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d42\xb7\xfe\xeb\xc5\u015b\xf6^\x86\x1cL\v\xe4*v\x11\xa5T\x1a\x89w\u9aa8R\\\x10\x00\x00\xe0\x942\xba\x9a}\x04#\xe0:R_\xe2\xeb\xebf\x1d \x85w\x8b\u060a\x04<3\xc1\x93ud\x80\x00\x00\u07d42\xbb.\x96\x93\xe4\xe0\x854M/\r\xbdF\xa2\x83\u3807\xfd\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x942\xc2\xfd\u2daa\xbb\x80\u5ba2\xb9I\xa2\x17\xf3\xcb\t\"\x83\x8a\x010a`\xaf\xdf 7\x80\x00\u07d42\xd9P\xd5\xe9>\xa1\u0574\x8d\xb4qO\x86{\x03 \xb3\x1c\x0f\x897\b\xba\xed=h\x90\x00\x00\u07d42\u06f6qlT\xe81e\x82\x9aJ\xbb6uxI\xb6\xe4}\x8965\u026d\xc5\u07a0\x00\x00\u07d42\xebd\xbe\x1b]\xed\xe4\b\u01bd\xef\xben@\\\x16\xb7\xed\x02\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d42\xef\\\xdcg\x1d\xf5V*\x90\x1a\xee]\xb7\x16\xb9\xbev\xdc\xf6\x89lk\x93[\x8b\xbd@\x00\x00\u07d42\xf2\x9e\x87'\xa7LkC\x01\xe3\xff\xff\x06\x87\xc1\xb8p\xda\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d42\xfa\x0e\x86\xcd\b}\u058di1\x90\xf3-\x931\t\t\xedS\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d42\xfb\xee\xd6\xf6&\xfc\xdf\xd5\x1a\xca\xfbs\v\x9e\xef\xf6\x12\xf5d\x89lk\x93[\x8b\xbd@\x00\x00\u07943\x00\xfb\x14\x9a\xde\xd6[\u02e6\xc0N\x9c\u05b7\xa0;\x89;\xb1\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x943\x01\xd9\xca/;\xfe\x02by\xcdh\x19\xf7\x9a)=\x98\x15n\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\xe0\x943\b\xb04f\xc2z\x17\xdf\xe1\xaa\xfc\xeb\x81\xe1m)4Vo\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07943\x1a\x1c&\xcci\x94\xcd\xd3\xc1K\xec\xe2v\xff\xffK\x9d\xf7|\x88\xfaz\xed\xdfO\x06\x80\x00\xe0\x943&\xb8\x8d\xe8\x06\x18DT\xc4\v'\xf3\t\xd9\xddm\u03f9x\x8a\x03\xca\\f\u067cD0\x00\x00\xe0\x943)\xeb;\xafCE\xd6\x00\xce\xd4\x0en\x99ueo\x117B\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d432\r\xd9\x0f+\xaa\x11\r\xd34\x87*\x99\x8f\x14\x84&E<\x8965f3\xeb\xd8\xea\x00\x00\u07d436\xc3\xefn\x8bP\xee\x90\xe07\xb1d\xb7\xa8\xea_\xaa\xc6]\x89\x0e\u0223\xa7\x1c\"T\x00\x00\xe0\x9438\fo\xffZ\xcd&Q0\x96)\u06daq\xbf? \u017a\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d43:\xd1Yd\x01\xe0Z\xea-6\xcaG1\x8e\xf4\xcd,\xb3\u07c9\x9d\xc0\\\xce(\u00b8\x00\x00\u07d43C@\xeeK\x9c\u0701\xf8P\xa7Q\x16\xd5\x0e\u9d98%\xbf\x89lk\x93[\x8b\xbd@\x00\x00\u07d43H\x1e\x85n\xbe\u050e\xa7\b\xa2t&\xef(\xe8g\xf5|\u0449\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x943V[\xa9\xda,\x03\xe7x\xce\x12)O\b\x1d\xfe\x81\x06M$\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07943X\x1c\xee#0\x88\xc0\x86\r\x94N\f\xf1\u03ab\xb8&\x1c.\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d43XX\xf7I\xf1i\u02bc\xfeR\xb7\x96\xe3\xc1\x1e\xc4~\xa3\u0089\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x943^\"\x02[zw\u00e0t\u01cb\x8e=\xfe\a\x13A\x94n\x8a\x02'\xcas\n\xb3\xf6\xac\x00\x00\u07d43b\x9b\xd5/\x0e\x10{\xc0q\x17ld\xdf\x10\x8fdw}I\x89\x01\xcf\xddth!n\x80\x00\u07d43{;\u07c6\xd7\x13\xdb\xd0{]\xbf\xcc\x02+z{\x19F\xae\x89\xd7\xc1\x98q\x0ef\xb0\x00\x00\u07d43|\xfe\x11W\xa5\u0191 \x10\xddV\x153y\x17i\u00b6\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d43\xb36\xf5\xba^\xdb{\x1c\xcc~\xb1\xa0\u0644\xc1#\x1d\x0e\u0709lk\x93[\x8b\xbd@\x00\x00\u07d43\xc4\a\x13;\x84\xb3\xcaL=\xed\x1fFX\x90\f8\x10\x16$\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\xe0\x943\xd1r\xab\a\\Q\xdb\x1c\xd4\n\x8c\xa8\xdb\xff\r\x93\xb8C\xbb\x8a\x016x\x05\x10\xd1-\xe3\x80\x00\u07d43\xe9\xb7\x18#\x95.\x1ff\x95\x8c'\x8f\u008b\x11\x96\xa6\u0164\x89\x05k\xc7^-c\x10\x00\x00\u07d43\xeakxU\xe0[\a\xab\x80\u06b1\xe1M\xe9\xb6I\xe9\x9bl\x89\x1c\xd6\xfb\xadW\xdb\xd0\x00\x00\u07d43\xf1R#1\rD\u078bf6h_:L=\x9cVU\xa5\x89\r\x94b\xc6\xcbKZ\x00\x00\u07d43\xf4\xa6G\x1e\xb1\xbc\xa6\xa9\xf8[;Hr\xe1\aU\xc8+\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d43\xfbWzM!O\xe0\x10\xd3,\xca|>\xed\xa6?\x87\xce\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d43\xfdq\x8f\v\x91\xb5\xce\u020a]\xc1^\xec\xf0\xec\xef\xa4\xef=\x89\x17r$\xaa\x84Lr\x00\x00\u07d44\x14\x80\u030c\xb4v\xf8\xd0\x1f\xf3\b\x12\xe7\xc7\x0e\x05\xaf\xaf]\x89lk\x93[\x8b\xbd@\x00\x00\u07d44'-^ut1]\xca\u9afd1{\xac\x90(\x9dGe\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x9440\xa1c\x81\xf8i\xf6\xeaT#\x91XU\xe8\x00\x885%\xa9\x8a\x03\xca\\f\u067cD0\x00\x00\u07d441\x86%\x81\x8e\xc1?\x11\x83Z\xe9sS\xce7}oY\n\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d449<]\x91\xb9\xdeYr\x03\xe7[\xacC\t\xb5\xfa=(\u00c9\n\x84Jt$\xd9\xc8\x00\x00\u07d449\x99\x8b$|\xb4\xbf\x8b\xc8\nm+5'\xf1\xdf\xe9\xa6\u0489\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d44C}\x14ed\v\x13l\xb5\x84\x1c?\x93O\x9b\xa0\xb7\t}\x89\t`\xdbwh\x1e\x94\x00\x00\u07d44J\x8d\xb0\x86\xfa\xedN\xfc7\x13\x1b:\"\xb0x-\xadp\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x944fM\"\x0f\xa7\xf3yX\x02J32\u0584\xbc\xc6\xd4\u023d\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d44f\xf6~9cl\x01\xf4;:!\xa0\xe8R\x93%\xc0\x86$\x89-\xb1\x16vP\xac\xd8\x00\x00\u07d44\x856\x1e\xe6\xbf\x06\xefe\b\xcc\xd2=\x94d\x1f\x81M>/\x89lk\x93[\x8b\xbd@\x00\x00\u07d44\x85\xf6!%d3\xb9\x8aB\x00\xda\xd8W\xef\xe5Y7\uc609lk\x93[\x8b\xbd@\x00\x00\u07d44\x95\x8aF\xd3\x0e0\xb2s\xec\xc6\xe5\xd3X\xa2\x12\xe50~\x8c\x89lk\x93[\x8b\xbd@\x00\x00\u07d44\x97\xddf\xfd\x11\x80q\xa7\x8c,\xb3n@\xb6e\x1c\xc8%\x98\x89\x05\xf1\x01kPv\xd0\x00\x00\xe0\x944\x9a\x81k\x17\xab='\xbb\xc0\xae\x00Q\xf6\xa0p\xbe\x1f\xf2\x9d\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d44\x9d,\x91\x8f\u041e(\a1\x8ef\xceC)\t\x17k\xd5\v\x89<\xb7\x1fQ\xfcU\x80\x00\x00\u07d44\xa0C\x1f\xff^\xad\x92\u007f\xb6`\f\x1e\xa8\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d44\xff&\xeb`\xa8\u0469ZH\x9f\xae\x13n\xe9\x1dNX\bL\x89 \x86\xac5\x10R`\x00\x00\u07d44\xffX)R\xff$E\x8f{\x13\xd5\x1f\vO\x98p\"\xc1\xfe\x89\x98\x06\xde=\xa6\xe9x\x00\x00\u07d45\x10k\xa9N\x85c\u0533\xcb<\\i,\x10\xe6\x04\xb7\xce\u0609lk\x93[\x8b\xbd@\x00\x00\xe0\x945\x14_b\x03\x97\u019c\xb8\xe0\tb\x96\x1f\x0fH\x86d9\x89\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d45\x14t0\xc3\x10e\x00\u77e2\xf5\x02F.\x94p<#\xb1\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x945\x17\x87\x845\x05\xf8\xe4\xef\xf4ef\xcc\u695fM\x1c_\xe7\x8a\x01\xf5q\x89\x87fKH\x00\x00\xe0\x945\x1f\x16\xe5\xe0sZ\xf5gQ\xb0\xe2%\xb2B\x11q9@\x90\x8a\x02\xd4\xca\x05\xe2\xb4<\xa8\x00\x00\xe0\x945$\xa0\x00#N\xba\xaf\a\x89\xa14\xa2\xa4\x178<\xe5(*\x8a\x011yU\x94}\x8e,\x00\x00\u07d45&\xee\xce\x1ak\xdc>\xe7\xb4\x00\xfe\x93[HF?1\xbe\u05c9\x04w\x87\x9bm\x140\x00\x00\u07d45*x_J\x92\x162PL\xe5\xd0\x15\xf8\xd7FO\xa3\xdb\x14\xe7r\x92\x13\u03aa7\x8c\t^\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d45\xaf\x04\n\f\xc23zv\xaf(\x81T\xc7V\x1e\x1a#3I\x8965\u026d\xc5\u07a0\x00\x00\u07d45\xb0>\xa4$W6\xf5{\x85\xd2\xebyb\x8f\x03m\xdc\xd7\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d45\xbd$he\xfa\xb4\x90\xac\bz\xc1\xf1\xd4\xf2\xc1\r\f\xda\x03\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x945\xbff\x88R/5Fz\u007fu0#\x14\xc0+\xa1v\x80\x0e\x8a\x03\xafA\x82\x02\xd9T\xe0\x00\x00\u07d45\u022d\xc1\x11%C+;w\xac\xd6F%\xfeX\xeb\xee\x9df\x89lk\x93[\x8b\xbd@\x00\x00\u07d45\u0497\x0fI\xdc\xc8\x1e\xa9\xeep~\x9c\x8a\n\xb2\xa8\xbbtc\x89N\x10\x03\xb2\x8d\x92\x80\x00\x00\u07d45\xe0\x96\x12\r\xea\xa5\xc1\xec\xb1d^,\u02cbN\xdb\xd9)\x9a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d45\xea!c\xa3\x8c\u07da\x12?\x82\xa5\xec\x00%\x8d\xae\v\xc7g\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d45\xf1\xda\x12{\x837o\x1b\x88\xc8*3Y\xf6z^g\xddP\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d45\xf2\x94\x9c\xf7\x8b\xc2\x19\xbbO\x01\x90|\xf3\xb4\xb3\u04c6T\x82\x89\x0f\xb5\xc8l\x92\xe44\x00\x00\u07d45\xf5\x86\x01I\xe4\xbb\xc0K\x8a\u0172r\xbeU\xad\x1a\xcaX\xe0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d46\x02E\x8d\xa8omj\x9d\x9e\xb0=\xaf\x97\xfeV\x19\xd4B\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x057-\x93\xa9\x01\t\x88\x01\x8f\x9f1]\x03.\u0448\x0f\xa1\x89\x1b\x1b\xcfQ\x89j}\x00\x00\u07d46\x16\xd4H\x98_]2\xae\xfa\x8b\x93\xa9\x93\xe0\x94\xbd\x85I\x86\x89\v\"\u007fc\xbe\x81<\x00\x00\u07d46\x16\xfbF\xc8\x15x\xc9\xc8\xebM;\xf8\x80E\x1a\x887\x9d}\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d46\x1cu\x93\x16\x96\xbc=B}\x93\xe7lw\xfd\x13\xb2A\xf6\xf4\x89\x1d\xc5\xd8\xfc&m\xd6\x00\x00\u07d46\x1d\x9e\xd8\v[\xd2|\xf9\xf1\"o&u2X\xee_\x9b?\x89\xbfi\x14\xba}r\xc2\x00\x00\u07d46\x1f;\xa9\xed\x95kw\x0f%}6r\xfe\x1f\xf9\xf7\xb0$\f\x89 \x86\xac5\x10R`\x00\x00\u07d46\"|\u07e0\xfd;\x9d~jtF\x85\xf5\xbe\x9a\xa3f\xa7\xf0\x89\n\xc2s\x0e\xe9\xc6\xc1\x80\x00\u07d46/\xbc\xb1\x06b7\n\x06\x8f\xc2e&\x02\xa2Wy7\xcc\xe6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d460\xc5\xe5e\u03aa\x8a\x0f\x0f\xfe2\x87^\xae*l\xe6<\x19\x89\t7r+7t\xd0\x00\x00\u07d463\x9f\x84\xa5\u00b4L\xe5=\xfd\xb6\xd4\xf9}\xf7\x82\x12\xa7\u07c9\x11o\x18\xb8\x17\x15\xa0\x00\x00\u07d464:\xec\xa0{n\u054a\x0eb\xfaN\xcbI\x8a\x12O\xc9q\x89\x10CV\x1a\x88)0\x00\x00\u07d46au@4\x81\xe0\xab\x15\xbbQF\x15\u02f9\x89\xeb\u018f\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d46ro;\x88Z$\xf9)\x96\u0681b^\u022d\x16\xd8\xcb\xe6\x89S\xafu\u0441HW\x80\x00\xe0\x946s\x95C\x99\xf6\u07feg\x18\x18%\x9b\xb2x\xe2\xe9.\xe3\x15\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d46u\x8e\x04\x9c\u064b\u03a1\"w\xa6v\xf9)sb\x89\x00#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d46\u007fY\u0302yS)8NA\xe1(1\x15\xe7\x91\xf2j\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x81\x0f\xf9\xd2\x13\xa2q\xed\xa2\xb8\xaay\x8b\xe6T\xfaK\xbe\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d46\x8cT\x14\xb5k\x84U\x17\x1f\xbf\ab \xc1\u02e4\xb5\xca1\x89\x1e>\xf9\x11\xe8=r\x00\x00\xe0\x946\x90$k\xa3\xc8\x06y\xe2.\xacD\x12\xa1\xae\xfc\xe6\xd7\u0342\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d46\x92\x8bU\xbc\x86\x15\t\xd5\x1c\x8c\xf1\xd5F\xbf\xecn>\x90\xaf\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d46\x98\"\xf5W\x8b@\xdd\x1fDqpk\"\u0357\x13R\xdak\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d46\x9e\xf7a\x19_:7>$\xec\xe6\xcd\"R\x0f\xe0\xb9\xe8n\x89\x1c\xff\xaf\xc9M\xb2\b\x80\x00\u07d46\xa0\x8f\xd6\xfd\x1a\xc1|\xe1^\xd5~\xef\xb1*+\u2048\xbf\x89Hz\x9a0E9D\x00\x00\u07d46\xa0\xe6\x1e\x1b\xe4\u007f\xa8~0\xd3(\x88\xee\x030\x90\x1c\xa9\x91\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x946\xb2\xc8^:\xee\xeb\xb7\rc\u0124s\f\xe2\xe8\xe8\x8a6$\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x946\xbfC\xff5\u07d0\x90\x88$3l\x9b1\xce3\x06~/P\x8aIr\x15\x10\xc1\xc1\xe9H\x00\x00\u07d46\xbf\xe1\xfa;{p\xc1r\xeb\x04/h\x19\xa8\x97%\x95A>\x8965\u026d\xc5\u07a0\x00\x00\xe0\x946\xc5\x10\xbf\x8dnV\x9b\xf2\xf3}G&]\xbc\xb5\x02\xff+\u038a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x946\xd8]\xc3h1V\xe6;\xf8\x80\xa9\xfa\xb7x\x8c\xf8\x14:'\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d46\u07cf\x88<\x12s\xec\x8a\x17\x1fz3\xcf\xd6I\xb1\xfe`u\x89\fRHJ\xc4\x16\x89\x00\x00\xe0\x946\xe1Va\f\xd8\xffd\xe7\x80\u061d\x00T8\\\xa7gU\xaa\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d46\xfe\xc6,,B^!\x9b\x18D\x8a\xd7W\x00\x9d\x8cT\x02o\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d47\x00\xe3\x02t$\xd99\xdb\xde]B\xfbx\xf6\xc4\xdb\xec\x1a\x8f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d47\x02\xe7\x04\xcc!at9\xadN\xa2zW\x14\xf2\xfd\xa1\xe92\x8965\u026d\xc5\u07a0\x00\x00\u07d47\x035\fMo\xe374,\xdd\xc6[\xf1\xe28k\xf3\xf9\xb2\x89m\x81!\xa1\x94\xd1\x10\x00\x00\xe0\x947\b\xe5\x9d\xe6\xb4\x05P\x88x)\x02\xe0W\x9cr\x01\xa8\xbfP\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d47\x126~^U\xa9mZ\x19\x16\x8fn\xb2\xbc~\x99q\xf8i\x8965\u026d\xc5\u07a0\x00\x00\u07d47\x19Zc]\xccb\xf5jq\x80I\xd4~\x8f\x9f\x96\x83(\x91\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d47'4\x1f&\xc1 \x01\xe3x@^\xe3\x8b-\x84d\xecq@\x89lk\x93[\x8b\xbd@\x00\x00\u07d47.E:kb\x9f'g\x8c\u022e\xb5\xe5|\xe8^\xc0\xae\xf9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d474\xcb\x18t\x91\xed\xe7\x13\xae[;-\x12(J\xf4k\x81\x01\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d477!n\xe9\x1f\x17w2\xfbX\xfa@\x97&r\a\xe2\xcfU\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d47M;\xbb\x057Q\xf9\xf6\x8d\xdb\a\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d48r\xf4\x8d\xc5\xe3\xf8\x17\xbck*\xd2\xd00\xfc^\x04q\x19=\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d48~\xea\xfdk@\t\u07af\x8b\u0578Zr\x98:\x8d\xcc4\x87\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d48\x81\xde\xfa\xe1\xc0{<\xe0Lx\xab\xe2k\f\u070ds\xf0\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d48\x83\xbe\xcc\b\xb9\xbeh\xad;\b6\xaa\u00f6 \xdc\x00\x17\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d48\x85\xfe\xe6q\a\xdc:\xa9\x8a\x1d\x99:t\xdf\\\xd7T\xb9\x8dR\x9a\x89a\t=|,m8\x00\x00\u07d48\xe4m\xe4E<8\xe9A\xe7\x93\x0fC0O\x94\xbb{+\xe8\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d48\xe7\u06e8\xfdO\x1f\x85\r\xbc&I\xd8\xe8O\tR\xe3\xeb<\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d48\xe8\xa3\x1a\xf2\xd2e\xe3\x1a\x9f\xff-\x8fF(m\x12E\xa4g\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d48\xeao[Z{\x88AuQ\xb4\x12=\xc1'\xdf\xe94-\xa6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d48\xee\xc6\xe2\x17\xf4\xd4\x1a\xa9 \xe4$\xb9RQ\x97\x04\x1c\xd4\u0189\xf0\r%\xeb\x92.g\x00\x00\xe0\x948\xf3\x87\xe1\xa4\xedJs\x10n\xf2\xb4b\xe4t\xe2\xe3\x14:\u040a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d49\x11a\xb0\xe4<0 f\u898d,\xe7\xe1\x99\xec\xdb\x1dW\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x949\x15\uad6b.Yw\xd0u\xde\xc4}\x96\xb6\x8bK\\\xf5\x15\x8a\r\a\x01\x81\x85\x12\x0f@\x00\x00\u07d49\x1aw@\\\t\xa7+^\x846#z\xaa\xf9]h\xda\x17\t\x89\x02\xa9&J\xf3\u0479\x00\x00\u07d49\x1f \x17m\x126\rrMQG\n\x90p6uYJM\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d49$3\xd2\u0383\xd3\xfbJv\x02\u0323\xfa\xcaN\xc1@\xa4\xb0\x89\x02\xc3\xc4e\xcaX\xec\x00\x00\xe0\x949?x;\\\u06c6\"\x1b\xf0)O\xb7\x14\x95\x9c{E\x89\x9c\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d49?\xf4%^\\e\x8f.\u007f\x10\xec\xbd)%rg\x1b\xc2\u0489lk\x93[\x8b\xbd@\x00\x00\u07d49A2`\x0fAU\xe0\u007fME\xbc>\xb8\xd9\xfbr\xdc\u05c4\x89\x9fn\x92\xed\xea\a\xd4\x00\x00\u07d49Q\xe4\x8e<\x86\x9ekr\xa1C\xb6\xa4Ph\u0379\xd4f\u0409\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x949T\xbd\xfe\v\xf5\x87\u0195\xa3\x05\xd9$L=[\xdd\xda\u027b\x8a\x04\x10'\x83'\xf9\x85`\x80\x00\u07d49]m%U \xa8\xdb)\xab\xc4}\x83\xa5\u06ca\x1a}\xf0\x87\x89\x05k\xc7^-c\x10\x00\x00\u07d49ck%\x81\x1b\x17j\xbf\xcf\xee\xcad\xbc\x87E/\x1f\xdf\xf4\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d49i\xb4\xf7\x1b\xb8u\x1e\xdeC\xc0\x166:zaOv\x11\x8e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x949x/\xfe\x06\xacx\x82*<:\x8a\xfe0^P\xa5a\x88\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d49zn\xf8v:\x18\xf0\x0f\xac!~\x05\\\r0\x94\x10\x10\x11\x89lk\x93[\x8b\xbd@\x00\x00\u07d49|\u06cc\x80\xc6yP\xb1\x8deB)a\x0e\x93\xbf\xa6\xee\x1a\x89?\x95\xc8\xe0\x82\x15!\x00\x00\u07d49\x82O\x8b\xce\xd1v\xfd>\xa2.\u01a4\x93\xd0\xcc\xc3?\xc1G\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d49\x93l'\x19E\v\x94 \xcc%\"\u03d1\xdb\x01\xf2'\xc1\xc1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d49\x95\xe0\x96\xb0\x8aZrh\x00\xfc\xd1}\x9cd\xc6N\b\x8d+\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d49\x9a\xa6\xf5\xd0x\xcb\tp\x88+\u0259 \x06\xf8\xfb\xdf4q\x8965\u026d\xc5\u07a0\x00\x00\u07d49\xaa\x05\xe5m}28T!\u03d36\xe9\r=\x15\xa9\xf8Y\x89\x01h\u048e?\x00(\x00\x00\u07d49\xaa\xf0\x85M\xb6\xeb9\xbc{.C\x84jv\x17\x1c\x04E\u0789dI\xe8NG\xa8\xa8\x00\x00\u07d49\xb1\xc4q\xae\x94\xe1!dE.\x81\x1f\xbb\xe2\xb3\xcdru\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d49\xb2\x992t\x90\xd7/\x9a\x9e\xdf\xf1\x1b\x83\xaf\xd0\xe9\xd3\xc4P\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d49\xba\u018d\x94xY\xf5\x9e\x92&\b\x9c\x96\xd6.\x9f\xbe<\u0789\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x949\xbf\xd9xh\x9b\xec\x04\x8f\xc7v\xaa\x15$\u007f^\x1d|9\xa2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d49\xc7s6|\x88%\xd3YlhoB\xbf\r\x141\x9e?\x84\x89\a?u\u0460\x85\xba\x00\x00\u07d49\u05291@,\fy\xc4W\x18o$\u07c7)\u03d5p1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d49\xd6\xca\xca\"\xbc\xcdjr\xf8~\xe7\u05b5\x9e\v\xde!\xd7\x19\x89l\x87T\xc8\xf3\f\b\x00\x00\u07d49\xe0\xdbM`V\x8c\x80\v\x8cU\x00\x02l%\x94\xf5v\x89`\x8965\u026d\xc5\u07a0\x00\x00\xe0\x949\xeeO\xe0\x0f\xbc\xeddph\xd4\xf5|\x01\xcb\"\xa8\v\xcc\u044a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d49\xf1\x983\x1eK!\xc1\xb7`\xa3\x15_J\xb2\xfe\x00\xa7F\x19\x89lk\x93[\x8b\xbd@\x00\x00\u07d49\xf4Fc\xd9%a\t\x1b\x82\xa7\r\xcfY=u@\x05\x97:\x89\n\u05cb.\xdc!Y\x80\x00\u07d4:\x03U\x94\xc7GGmB\xd1\xee\x96l6\"L\xdd\"I\x93\x89\x13J\xf7Ei\xf9\xc5\x00\x00\u07d4:\x04W(G\xd3\x1e\x81\xf7v\\\xa5\xbf\xc9\xd5W\x15\x9f6\x83\x89\a6-\r\xab\xea\xfd\x80\x00\xe0\x94:\x06\xe3\xbb\x1e\xdc\xfd\fD\xc3\aM\xe0\xbb`k\x04\x98\x94\xa2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794:\x10\x88\x8b~\x14\x9c\xae',\x010,2}\n\xf0\x1a\v$\x88\xeb\xec!\xee\x1d\xa4\x00\x00\u07d4:1\b\xc1\u6023;3l!\x13\x134@\x9d\x97\xe5\xad\xec\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4:6\x8e\xfeJ\u05c6\xe2c\x95\xec\x9f\u01adi\x8c\xae)\xfe\x01\x89\"E\x89\x96u\xf9\xf4\x00\x00\u07d4:=\xd1\x04\xcd~\xb0O!\x93/\xd43\xeaz\xff\u04d3i\xf5\x89\x13aO#\xe2B&\x00\x00\u07d4:B\x97\xda\xc4.\x1eO\xb8\xcb1\xec\xddC\xaew<\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94:\x99`&m\xf6I cS\x8a\x99\xf4\x87\xc9P\xa3\xa5\uc78a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4:\x9b\x11\x10)\xce\x1f \xc9\x10\x9czt\xee\xee\xf3OO.\xb2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4:\x9eTA\xd4K$;\xe5[u\x02z\x1c\ub7ac\xf5\r\xf2\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94:\xa0z4\xa1\xaf\u0216}=\x13\x83\xb9kb\u03d6\xd5\xfa\x90\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94:\xa4,!\xb9\xb3\x1c>'\xcc\xd1~\t\x9a\xf6y\xcd\xf5i\a\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4:\xa9H\xea\x029wU\xef\xfb/\x9d\xc99-\xf1\x05\x8f~3\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4:\xad\xf9\x8ba\xe5\u0216\xe7\xd1\x00\xa39\x1d2P\"]a\u07c9\f\xafg\x007\x01h\x00\x00\u07d4:\xaeHr\xfd\x90\x93\xcb\xca\xd1@o\x1e\x80x\xba\xb5\x03Y\xe2\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4:\xbb\x8a\xdf\xc6\x04\xf4\x8dY\x84\x81\x1d\u007f\x1dR\xfe\xf6u\x82p\x89\xf2\x97\x19\xb6o\x11\f\x00\x00\u07d4:\xc2\xf0\xff\x16\x12\xe4\xa1\xc3F\xd53\x82\xab\xf6\u0622[\xaaS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4:\xc9\xdczCj\xe9\x8f\xd0\x1cz\x96!\xaa\x8e\x9d\v\x8bS\x1d\x89a\t=|,m8\x00\x00\xe0\x94:\xd0aI\xb2\x1cU\xff\x86|\xc3\xfb\x97@\u04bc\xc7\x10\x121\x8a)\xb7d2\xb9DQ \x00\x00\u07d4:\xd7\x02C\u060b\xf0@\x0fW\xc8\xc1\xfdW\x81\x18H\xaf\x16*\x89.\x9e\xe5\u00c6S\xf0\x00\x00\u07d4:\xd9\x15\xd5P\xb7#AV \xf5\xa9\xb5\xb8\x8a\x85\xf3\x82\xf05\x8965\u026d\xc5\u07a0\x00\x00\u07d4:\xe1`\xe3\xcd`\xae1\xb9\xd6t-h\xe1Nv\xbd\x96\xc5\x17\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4:\xe6+\xd2q\xa7`c\u007f\xady\xc3\x1c\x94\xffb\xb4\xcd\x12\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4:\xeaN\x82\xd2@\x02H\xf9\x98q\xa4\x1c\xa2W\x06\r:\"\x1b\x8965\u026d\xc5\u07a0\x00\x00\u07d4:\xf6[>(\x89ZJ\x00\x11S9\x1d\x1ei\xc3\x1f\xb9\xdb9\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4;\a\xdbZ5\u007fZ\xf2HL\xbc\x9dw\xd7;\x1f\xd0Q\x9f\u01c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4;\n\u032fK`|\xfea\xd1s4\xc2\x14\xb7\\\xde\xfd\xbd\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\x13c\x1a\x1b\x89\xcbVeH\x89\x9a\x1d`\x91\\\xdc\xc4 [\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\x15\x90\x99\aR\a\u0180vc\xb1\xf0\xf7\xed\xa5J\xc8\xcc\xe3\x89j\xc4\xe6[i\xf9-\x80\x00\u07d4;\x197\xd5\u74f8\x9bc\xfb\x8e\xb5\xf1\xb1\xc9\xcak\xa0\xfa\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\"\xda*\x02q\xc8\xef\xe1\x02S'scji\xb1\xc1~\t\x89\x1b6\xa6DJ>\x18\x00\x00\u07d4;\"\u07a3\xc2_\x1bY\u01fd'\xbb\x91\u04e3\xea\xec\xef9\x84\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94;#g\xf8IK_\xe1\x8dh<\x05]\x89\x99\x9c\x9f=\x1b4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4;,E\x99\x0e!GDQ\xcfOY\xf0\x19U\xb31\xc7\xd7\u0249lk\x93[\x8b\xbd@\x00\x00\xe0\x94;A\x00\xe3\ns\xb0\xc74\xb1\x8f\xfa\x84&\u045b\x191/\x1a\x8a\v\xb5\u046ap\n\xfd\x90\x00\x00\u07d4;B\xa6m\x97\x9fX(4tz\x8b`B\x8e\x9bN\xec\xcd#\x89!\xa1\u01d0\xfa\xdcX\x00\x00\u07d4;Gh\xfdq\xe2\xdb,\xbe\u007f\xa0PH<'\xb4\xeb\x93\x1d\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;Vj\x8a\xfa\u0456\x82\xdc,\xe8g\x9a<\xe4D\xa5\xb0\xfdO\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94;\\%\x1d\u007f\u05c9;\xa2\t\xfeT\x1c\xec\xd0\xce%:\x99\r\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4;^\x8b\x17w\xca\x18A\x896\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94;\x93\xb1a6\xf1\x1e\xaf\x10\x99l\x95\x99\r;'9\xcc\xea_\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4;\xabK\x01\xa7\xc8K\xa1?\uea70\xbb\x19\x1bw\xa3\xaa\u0723\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4;\xb55\x98\xcc \xe2\x05]\xc5S\xb0I@J\u0277\xdd\x1e\x83\x89!W\x1d\xf7|\x00\xbe\x00\x00\u07d4;\xbc\x13\xd0J\xcc\xc0pz\xeb\u072e\xf0\x87\u0438~\v^\u327e\xd1\xd0&=\x9f\x00\x00\x00\u07d4;\xc6\xe3\xeezV\u038f\x14\xa3u2Y\x0fcqk\x99f\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\xc8]ls[\x9c\xdaK\xba_H\xb2K\x13\xe7\x0600{\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4;\xd6$\xb5H\xcbe\x976\x90~\u062a<\fp^$\xb5u\x89lk\x93[\x8b\xbd@\x00\x00\u07d4;\u0660m\x1b\xd3lN\xdd'\xfc\r\x1f[\b\x8d\xda\xe3\xc7*\x89\x1b\x1azB\v\xa0\r\x00\x00\u0794;\u077c\x814\xf7}UY\u007f\xc9|&\xd2f\x98\t\x06\x04\ub23e -j\x0e\xda\x00\x00\xe0\x94;\xf8n\u0623\x15>\xc93xj\x02\xac\t\x03\x01\x85^Wk\x8a_J\x8c\x83u\xd1U@\x00\x00\u07d4;\xfb\u04c4|\x17\xa6\x1c\xf3\xf1{R\xf8\ub879`\xb3\U000df262\xa1]\tQ\x9b\xe0\x00\x00\u07d4<\x03\xbb\xc0#\xe1\xe9?\xa3\xa3\xa6\xe4(\xcf\f\xd8\xf9^\x1e\u0189Rf<\u02b1\xe1\xc0\x00\x00\u07d4<\f=\ufb1c\xeaz\xcc1\x9a\x96\xc3\v\x8e\x1f\xed\xabEt\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4<\x15\xb3Q\x1d\xf6\xf04.sH\u0309\xaf9\xa1h\xb7s\x0f\x8965\u026d\xc5\u07a0\x00\x00\u07d4<\x1f\x91\xf3\x01\xf4\xb5e\xbc\xa2GQ\xaa\x1fv\x13\"p\x9d\u0749a\t=|,m8\x00\x00\xe0\x94<(l\xfb0\x14n_\u05d0\xc2\xc8T\x15RW\x8d\xe34\u060a\x02)\x1b\x11\xaa0n\x8c\x00\x00\u07d4<2.a\x1f\u06c2\rG\xc6\xf8\xfcd\xb6\xfa\xd7L\xa9_^\x89\r%\x8e\xce\x1b\x13\x15\x00\x00\u07d4\xa5\xe5\xbfb\xbb\u0309\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4<\x86\x9c\tie#\xce\xd8$\xa0pAF\x05\xbbv#\x1f\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4<\x92V\x19\u02731DF?\x057\u06165\x87\x06\xc5 \xb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\x98YK\xf6\x8bW5\x1e\x88\x14\xae\x9em\xfd-%J\xa0o\x89\x10CV\x1a\x88)0\x00\x00\u07d4<\xad\xeb=>\xed?b1\x1dRU>p\xdfJ\xfc\xe5o#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94<\xae\xdbS\x19\xfe\x80eC\xc5nP!\xd3r\xf7\x1b\xe9\x06.\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4<\xaf\xaf^bPV\x15\x06\x8a\xf8\xeb\"\xa1:\u0629\xe5Pp\x89lf\x06E\xaaG\x18\x00\x00\u07d4<\xb1y\xcbH\x01\xa9\x9b\x95\u00f0\xc3$\xa2\xbd\xc1\x01\xa6S`\x89\x01h\u048e?\x00(\x00\x00\u07d4<\xb5a\u0386BK5\x98\x91\xe3d\xec\x92_\xfe\xff'}\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xcbq\xaah\x80\xcb\v\x84\x01-\x90\xe6\a@\xec\x06\xac\u05cf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\xce\xf8\x86yW9G\xe9I\x97y\x8a\x1e2~\b`:e\x89+\xc9\x16\u059f;\x02\x00\x00\xe0\x94<\xd1\xd9s\x1b\xd5H\xc1\xddo\u03a6\x1b\xebu\xd9\x17T\xf7\u04ca\x01\x16\x1d\x01\xb2\x15\xca\xe4\x80\x00\u07d4<\u04e6\xe95y\xc5mIAq\xfcS>z\x90\xe6\xf5\x94d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4<\u05b7Y<\xbe\xe7x0\xa8\xb1\x9d\b\x01\x95\x8f\xcdK\xc5z\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4<\xd7\xf7\xc7\xc257\x80\xcd\xe0\x81\xee\xecE\x82+%\xf2\x86\f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xe1\u0717\xfc\u05f7\xc4\u04e1\x8aI\xd6\xf2\xa5\xc1\xb1\xa9\x06\u05c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4<\xea0*G*\x94\x03y\xdd9\x8a$\xea\xfd\xba\u07c8\xady\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94<\xec\xa9k\xb1\xcd\xc2\x14\x02\x9c\xbc^\x18\x1d9\x8a\xb9M=A\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4<\xf4\x84RO\xbd\xfa\xda\xe2m\xc1\x85\xe3++c\x0f\xd2\xe7&\x89\x18TR\xcb*\x91\xc3\x00\x00\u07d4<\xf9\xa1\xd4e\xe7\x8bp9\xe3iDx\xe2b{6\xfc\xd1A\x89J`S*\xd5\x1b\xf0\x00\x00\u07d4<\xfb\xf0fVYpc\x9e\x13\r\xf2\xa7\xd1k\x0e\x14\xd6\t\x1c\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94=\th\x8d\x93\xad\a\xf3\xab\xe6\x8cr'#\xcdh\t\x90C^\x8a\x06ZL\xe9\x9fv\x9en\x00\x00\u07d4=1X{_\u0546\x98Ex\x87%\xa6c)\nI\xd3g\x8c\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4=?\xadI\xc9\xe5\xd2u\x9c\x8e\x8eZzM`\xa0\xdd\x13V\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4=WO\xcf\x00\xfa\xe1\u064c\u023f\x9d\u07e1\xb3\x95;\x97A\xbc\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4=Z\x8b+\x80\xbe\x8b5\xd8\xec\xf7\x89\xb5\xedz\au\xc5\al\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4=f\xcdK\xd6M\\\x8c\x1b^\xea(\x1e\x10m\x1cZ\xad#s\x89i\xc4\xf3\xa8\xa1\x10\xa6\x00\x00\u0794=j\xe0S\xfc\xbc1\x8do\xd0\xfb\xc3S\xb8\xbfT.h\r'\x88\xc6s\xce<@\x16\x00\x00\u07d4=o\xf8,\x93w\x05\x9f\xb3\r\x92\x15r?`\xc7u\u0211\xfe\x89\r\x8e\\\xe6\x17\xf2\xd5\x00\x00\u07d4=y\xa8S\xd7\x1b\xe0b\x1bD\xe2\x97Yel\xa0u\xfd\xf4\t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4=~\xa5\xbf\x03R\x81\x00\xed\x8a\xf8\xae\xd2e>\x92\x1bng%\x8965\u026d\xc5\u07a0\x00\x00\u07d4=\x81?\xf2\xb6\xedW\xb97\u06bf+8\x1d\x14\x8aA\x1f\xa0\x85\x89\x05k\xc7^-c\x10\x00\x00\u07d4=\x88\x143\xf0J}\r'\xf8ID\xe0\x8aQ-\xa3UR\x87\x89A\rXj \xa4\xc0\x00\x00\u07d4=\x89\xe5\x05\xcbF\xe2\x11\xa5?2\xf1g\xa8w\xbe\xc8\u007fK\n\x89\x01[5W\xf1\x93\u007f\x80\x00\xe0\x94=\x8d\a#r\x1es\xa6\xc0\xd8`\xaa\x05W\xab\xd1L\x1e\xe3b\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4=\x8f9\x88\x1b\x9e\xdf\xe9\x12'\xc3?\xa4\xcd\xd9\x1eg\x85D\xb0\x89\x04\xab\a\xbaC\xad\xa9\x80\x00\u07d4=\x9dk\xe5\u007f\xf8>\x06Y\x85fO\x12VD\x83\xf2\xe6\x00\xb2\x89n\xac\xe4?#\xbd\x80\x00\x00\u07d4=\xa3\x9c\xe3\xefJz9f\xb3.\xe7\xeaN\xbc#5\xa8\xf1\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4=\xaa\x01\u03b7\x0e\xaf\x95\x91\xfaR\x1b\xa4\xa2~\xa9\xfb\x8e\xdeJ\x89Zc\xd2\u027cvT\x00\x00\u07d4=\xb5\xfejh\xbd6\x12\xac\x15\xa9\x9aa\xe5U\x92\x8e\xec\xea\xf3\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4=\xb9\xed\u007f\x02L~&7/\xea\xcf+\x05\b\x03D^8\x10\x89E\xb1H\xb4\x99j0\x00\x00\u07d4=\xbf\r\xbf\xd7x\x90\x80\x053\xf0\x9d\xea\x83\x01\xb9\xf0%\u04a6\x8965\u026d\xc5\u07a0\x00\x00\u07d4=\xce\U0005c18b\x15\xd3N\xdaBn\xc7\xe0K\x18\xb6\x01p\x02\x89lh\xcc\u041b\x02,\x00\x00\xe0\x94=\xd1.Uj`76\xfe\xbaJo\xa8\xbdJ\xc4]f*\x04\x8a#u{\x91\x83\xe0x(\x00\x00\u07d4=\u078b\x15\xb3\u033a\xa5x\x01\x12\xc3\xd6t\xf3\x13\xbb\xa6\x80&\x89`\x1dQZ>O\x94\x00\x00\xe0\x94=\xde\xdb\xe4\x89#\xfb\xf9\xe56\xbf\x9f\xfb\aG\xc9\xcd\u04de\xef\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4=\xea\xe43'\x91?b\x80\x8f\xaa\x1bbv\xa2\xbdch\xea\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4=\xf7b\x04\x9e\u068a\u0192}\x90Lz\xf4/\x94\xe5Q\x96\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>\x04\r@\u02c0\xba\x01%\xf3\xb1_\xde\xfc\xc8?0\x05\xda\x1b\x898E$\xccp\xb7x\x00\x00\u07d4>\v\x8e\xd8n\xd6i\xe1'#\xafur\xfb\xac\xfe\x82\x9b\x1e\x16\x89QM\xe7\xf9\xb8\x12\xdc\x00\x00\xe0\x94>\f\xbejm\xcba\xf1\x10\xc4[\xa2\xaa6\x1d\u007f\xca\xd3\xdas\x8a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4>\x19KN\xce\xf8\xbbq\x1e\xa2\xff$\xfe\xc4\xe8{\xd02\xf7\u0449\x8b\x9d\xc1\xbc\x1a\x03j\x80\x00\xe0\x94>\x1b\"0\xaf\xbb\xd3\x10\xb4\x92jLwmZ\u705cf\x1d\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4>\x1cS0\x0eL\x16\x89\x12\x16<~\x99\xb9]\xa2h\xad(\n\x896b2\\\u044f\xe0\x00\x00\u07d4>\x1c\x96 c\xe0\xd5)YA\xf2\x10\u0723\xabS\x1e\xec\x88\t\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4>,\xa0\xd24\xba\xf6\a\xadFj\x1b\x85\xf4\xa6H\x8e\xf0\n\xe7\x89\x04\xda!\xa3H=V\x80\x00\u07d4>/&#^\x13zs$\xe4\xdc\x15K]\xf5\xafF\xea\x1aI\x89\x017\xaa\xd8\x03-\xb9\x00\x00\xe0\x94>1a\xf1\xea/\xbf\x12ny\xda\x18\x01\u0695\x12\xb3y\x88\u024a\nm\xd9\f\xaeQ\x14H\x00\x00\xe0\x94>6\xc1rS\xc1\x1c\xf3\x89t\xed\r\xb1\xb7Y\x16\r\xa67\x83\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4><\u04fe\xc0e\x91\xd64o%Kb\x1e\xb4\x1c\x89\x00\x8d1\x895\u07fe\u069f74\x00\x00\u07d4>E\xbdU\u06d0`\xec\xed\x92;\xb9\xcbs<\xb3W?\xb51\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4>M\x13\xc5Z\x84\xe4n\xd7\xe9\u02d0\xfd5^\x8a\u0651\u33c965\u026d\xc5\u07a0\x00\x00\u07d4>N\x92e\"<\x9782L\xf2\v\xd0`\x06\xd0\a>\u06cc\x89\a?u\u0460\x85\xba\x00\x00\xe0\x94>O\xbdf\x10\x15\xf6F\x1e\xd6s\\\xef\xef\x01\xf3\x14E\xde:\x8a\x03n4)\x98\xb8\xb0 \x00\x00\xe0\x94>S\xff!\a\xa8\u07be3(I:\x92\xa5\x86\xa7\xe1\xf4\x97X\x8a\x04\xe6\x9c*q\xa4\x05\xab\x00\x00\u07d4>Z9\xfd\xdap\xdf\x11&\xab\r\u011asx1\x1aSz\x1f\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\xe0\x94>Z\xbd\t\xceZ\xf7\xba\x84\x87\xc3Y\xe0\xf2\xa9:\x98k\v\x18\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4>\\\xb8\x92\x8cAx%\xc0:;\xfc\xc5!\x83\xe5\xc9\x1eB\u05c9\xe71\xd9\xc5,\x96/\x00\x00\u07d4>^\x93\xfbL\x9c\x9d\x12F\xf8\xf2G5\x8e\"\xc3\xc5\xd1{j\x89\b!\xab\rD\x14\x98\x00\x00\u07d4>a\x83P\xfa\x01ez\xb0\xef>\xba\xc8\xe3p\x12\xf8\xfc+o\x89\x98\x06\xde=\xa6\xe9x\x00\x00\u07d4>c\xce;$\xca(e\xb4\u0166\x87\xb7\xae\xa3Y~\xf6\xe5H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>f\xb8GiVj\xb6yE\xd5\xfa\x8175V\xbc\u00e1\xfa\x89\b=lz\xabc`\x00\x00\xe0\x94>v\xa6-\xb1\x87\xaat\xf68\x17S;0l\xea\xd0\xe8\u03be\x8a\x06\x9bZ\xfa\xc7P\xbb\x80\x00\x00\u07d4>z\x96k]\xc3W\xff\xb0~\x9f\xe0g\xc4W\x91\xfd\x8e0I\x89\x034-`\xdf\xf1\x96\x00\x00\xe0\x94>\x81w!u#~\xb4\xcb\xe0\xfe-\xca\xfd\xad\xff\xebj\x19\x99\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4>\x83I\xb6\u007fWED\x9fe\x93g\u066dG\x12\xdb[\x89Z\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4>\x83TO\x00\x82U%r\u01c2\xbe\xe5\xd2\x18\xf1\xef\x06J\x9d\x89\x05l\xd5_\xc6M\xfe\x00\x00\u07d4>\x84\xb3\\[\"ePpa\xd3\vo\x12\xda\x03?\xe6\xf8\xb9\x89a\t=|,m8\x00\x00\u07d4>\x86A\xd4\x87E\xba2/_\xd6\xcbP\x12N\xc4f\x88\u01e6\x9a\u007f\xae\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4>\x91N0\x18\xac\x00D\x93A\u011d\xa7\x1d\x04\xdf\xee\xedb!\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4>\x94\x10\u04f9\xa8~\xd5\xe4Q\xa6\xb9\x1b\xb8\x92?\xe9\x0f\xb2\xb5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4>\x94\xdfS\x13\xfaR\x05p\xef#+\xc31\x1d_b/\xf1\x83\x89lk\x93[\x8b\xbd@\x00\x00\u0794>\x9b4\xa5\u007f3u\xaeY\xc0\xa7^\x19\u0136A\"\x8d\x97\x00\x88\xf8i\x93)g~\x00\x00\u07d4>\xad\xa8\xc9/V\x06~\x1b\xb7<\xe3x\xdaV\xdc,\xdf\xd3e\x89w\xcd\xe9:\xeb\rH\x00\x00\xe0\x94>\xaf\by\xb5\xb6\xdb\x15\x9bX\x9f\x84W\x8bjt\xf6\xc1\x03W\x8a\x01\x898\xb6q\xfae\xa2\x80\x00\u07d4>\xaf1k\x87a]\x88\xf7\xad\xc7|X\xe7\x12\xedMw\x96k\x89\x05m\xbcL\xee$d\x80\x00\u07d4>\xb8\xb3;!\xd2<\u0686\xd8(\x88\x84\xabG\x0e\x16F\x91\xb5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4>\xb9\xef\x06\xd0\xc2Y\x04\x03\x19\x94~\x8czh\x12\xaa\x02S\u0609\t\r\x97/22<\x00\x00\u07d4>\u030e\x16h\xdd\xe9\x95\xdcW\x0f\xe4\x14\xf4B\x11\xc54\xa6\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4>\u03752\xe3\x97W\x96b\xb2\xa4aA\u73c25\x93j_\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4>\xeeo\x1e\x966\vv\x89\xb3\x06\x9a\xda\xf9\xaf\x8e\xb6\f\u404965\u026d\xc5\u07a0\x00\x00\xe0\x94?\b\u066d\x89O\x81>\x8e!H\xc1`\xd2K5:\x8et\xb0\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4?\f\x83\xaa\xc5qybsN\\\xea\xea\xec\u04db(\xad\x06\xbe\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94?\x10\x80\x02\x82\u0477\xdd\u01cf\xa9-\x820\aN\x1b\xf6\xae\xae\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4?\x123qO M\xe9\xdeN\xe9m\a;6\x8d\x81\x97\x98\x9f\x89\x02\x17\xc4\x10t\xe6\xbb\x00\x00\u07d4?\x17:\xa6\xed\xf4i\u0445\xe5\x9b\xd2j\xe4#k\x92\xb4\xd8\xe1\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4?\x1b\xc4 \xc5<\x00,\x9e\x90\x03|D\xfej\x8e\xf4\xdd\xc9b\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4?#a\b\xee\xc7\"\x89\xba\u00e6\\\u0483\xf9^\x04\x1d\x14L\x8964\xbf9\xab\x98x\x80\x00\u07d4?-\xa0\x93\xbb\x16\xeb\x06O\x8b\xfa\x9e0\xb9)\xd1_\x8e\x1cL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?-\xd5]\xb7\xea\xb0\xeb\xeee\xb3>\xd8 ,\x1e\x99.\x95\x8b\x89,s\xc97t,P\x00\x00\u07d4?/8\x14\x91y|\xc5\xc0\u0502\x96\xc1O\xd0\xcd\x00\xcd\xfa-\x89+\x95\xbd\xcc9\xb6\x10\x00\x00\u07d4?0\u04fc\x9f`\"2\xbcrB\x88\xcaF\xcd\v\a\x88\xf7\x15\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4?<\x8ea\xe5`L\xef\x06\x05\xd46\xdd\"\xac\u0346\"\x17\xfc\x89Hz\x9a0E9D\x00\x00\u07d4??F\xb7\\\xab\xe3{\xfa\u0307`(\x1fCA\xca\u007fF=\x89 \xacD\x825\xfa\xe8\x80\x00\u07d4?G)c\x19x\x83\xbb\xdaZ\x9b}\xfc\xb2-\xb1\x14@\xad1\x89\x1a\x19d<\xb1\xef\xf0\x80\x00\u07d4?L\xd19\x9f\x8a4\xed\u06da\x17\xa4q\xfc\x92+Xp\xaa\xfc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4?U\x1b\xa9<\xd5F\x93\xc1\x83\xfb\x9a\xd6\re\xe1`\x96s\u0249lk\x93[\x8b\xbd@\x00\x00\xe0\x94?bzv\x9ej\x95\x0e\xb8p\x17\xa7\u035c\xa2\bq\x13h1\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4?m\xd3e\x0e\xe4(\u0737u\x95S\xb0\x17\xa9j\x94(j\u0249Hz\x9a0E9D\x00\x00\u07d4?tr7\x80o\xed?\x82\x8ahR\xeb\bg\xf7\x90'\xaf\x89\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4?u\xaea\xcc\x1d\x80Be;[\xae\xc4D>\x05\x1c^z\xbd\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4?\xb7\u0457\xb3\xbaO\xe0E\xef\xc2=P\xa1E\x85\xf5X\u0672\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94?\xbc\x1eE\x18\xd74\x00\xc6\xd0F5\x949\xfbh\xea\x1aI\xf4\x8a\x03y\v\xb8U\x13v@\x00\x00\u07d4?\xbe\xd6\xe7\xe0\u029c\x84\xfb\xe9\xeb\u03ddN\xf9\xbbIB\x81e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?\u043bGy\x8c\xf4L\u07feM3=\xe67\xdfJ\x00\xe4\\\x89\x05lUy\xf7\"\x14\x00\x00\xe0\x94?\xe4\x0f\xbd\x91\x9a\xad(\x18\xdf\x01\xeeM\xf4lF\x84*\xc59\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4?\xe8\x01\xe6\x135\xc5\x14\r\xc7\xed\xa2\xefR\x04F\nP\x120\x89lk\x93[\x8b\xbd@\x00\x00\u07d4?\xf86\xb6\xf5{\x90\x1bD\f0\xe4\xdb\xd0e\xcf7\xd3\u050c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4?\xfc\xb8p\xd4\x02=%]Qg\u0625\a\xce\xfc6kh\xba\x89#4^\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4@s\xfaI\xb8q\x17\u02d0\x8c\xf1\xabQ-\xa7T\xa92\xd4w\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4@\x8ai\xa4\a\x15\xe1\xb3\x13\xe15N`\b\x00\xa1\xe6\xdc\x02\xa5\x89\x01\u7e11\u0312T\x00\x00\u07d4@\x9b\xd7P\x85\x82\x1c\x1d\xe7\f\xdc;\x11\xff\xc3\xd9#\xc7@\x10\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4@\x9dZ\x96.\xde\uefa1x\x01\x8c\x0f8\xb9\u0372\x13\xf2\x89\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4@\xa31\x19[\x97s%\u00aa(\xfa/B\xcb%\xec<%<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4@\xa7\xf7(g\xa7\u0706w\v\x16+uW\xa44\xedP\xcc\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d4@\xab\n>\x83\xd0\u022c\x93f\x91\x05 \xea\xb1w+\xac;\x1a\x894\xf1\f-\xc0^|\x00\x00\u07d4@\xabf\xfe!>\xa5l:\xfb\x12\xc7[\xe3?\x8e2\xfd\b]\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94@\xadt\xbc\v\xce*E\xe5/6\xc3\u07bb\x1b:\xda\x1bv\x19\x8a\x01p\x16-\xe1\t\xc6X\x00\x00\u07d4@\u03c9\x05\x91\xea\u484f\x81*)T\xcb)_c3'\xe6\x89\x02\x9b\xf76\xfcY\x1a\x00\x00\u07d4@\u03d0\xef[v\x8c]\xa5\x85\x00,\xcb\xe6avP\xd8\xe87\x8963\x03\"\xd5#\x8c\x00\x00\xe0\x94@\xd4]\x9dv%\xd1QV\xc92\xb7q\xca{\x05'\x13\tX\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4@\xdb\x1b\xa5\x85\xce4S\x1e\xde\xc5IHI9\x13\x81\xe6\xcc\u04c9a\t=|,m8\x00\x00\xe0\x94@\xdfI^\xcf?\x8bL\xef*l\x18\x99W$\x8f\u813c+\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4@\xe0\xdb\xf3\xef\uf404\xea\x1c\xd7\xe5\x03\xf4\v;J\x84C\xf6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4@\xe2D\n\xe1B\u02006j\x12\xc6\xd4\x10/K\x844\xb6*\x8965\u026d\xc5\u07a0\x00\x00\u07d4@\xe3\u0083\xf7\xe2M\xe0A\f\x12\x1b\xee`\xa5`\u007f>)\xa6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94@\xeaPD\xb2\x04\xb20v\xb1\xa5\x80;\xf1\xd3\f\x0f\x88\x87\x1a\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94@\xed\xdbD\x8di\x0e\xd7.\x05\xc2%\xd3O\xc85\x0f\xa1\xe4\u014a\x01{x\x83\xc0i\x16`\x00\x00\xe0\x94@\xf4\xf4\xc0ls,\xd3[\x11\x9b\x89;\x12~}\x9d\aq\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4A\x01\x0f\u023a\xf8C}\x17\xa0Ci\x80\x9a\x16\x8a\x17\xcaV\xfb\x89\x05k\xc7^-c\x10\x00\x00\u07d4A\x03)\x96q\xd4gc\x97\x8f\xa4\xaa\x19\xee4\xb1\xfc\x95'\x84\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4A\x03<\x1bm\x05\xe1\u0289\xb0\x94\x8f\xc6DS\xfb\xe8z\xb2^\x89Hz\x9a0E9D\x00\x00\u07d4A\t\x8a\x81E#\x17\xc1\x9e>\xef\v\xd1#\xbb\xe1x\xe9\xe9\u0289\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4A\x16\x10\xb1x\xd5a}\xfa\xb94\u0493\xf5\x12\xa9>\\\x10\xe1\x89\t79SM(h\x00\x00\u07d4A\x1c\x83\x1c\xc6\xf4O\x19e\xecWW\xabN[<\xa4\xcf\xfd\x1f\x89\x17\n\x0fP@\xe5\x04\x00\x00\xe0\x94A*h\xf6\xc6EU\x9c\xc9w\xfcId\x04z \x1d\x1b\xb0\xe2\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4A?K\x02f\x9c\xcf\xf6\x80k\xc8&\xfc\xb7\xde\xca;\x0e\xa9\xbc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4AE\x99\t.\x87\x9a\xe2Sr\xa8MsZ\xf5\xc4\xe5\x10\xcdm\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4AHV\x12\xd04F\xecL\x05\xe5$NV?\x1c\xba\xe0\xf1\x97\x894\x95tD\xb8@\xe8\x00\x00\u07d4A]\tj\xb0b\x93\x18?<\x03=%\xf6\xcfqx\xac;\u01c9\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4Af\xfc\b\u0285\xf7f\xfd\xe81F\x0e\x9d\xc9<\x0e!\xaal\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Ag\x84\xaf`\x960\xb0p\u051a\x8b\xcd\x12#\\d(\xa4\b\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Ag\xcdH\xe73A\x8e\x8f\x99\xff\xd14\x12\x1cJJ\xb2x\u0109\xc5S%\xcat\x15\xe0\x00\x00\u07d4Al\x86\xb7 \x83\xd1\xf8\x90}\x84\xef\xd2\xd2\u05c3\xdf\xfa>\xfb\x89lj\xccg\u05f1\xd4\x00\x00\u07d4AsA\x9d\\\x9fc)U\x1d\xc4\xd3\xd0\u03ac\x1bp\x1b\x86\x9e\x89\x04\xc5>\xcd\xc1\x8a`\x00\x00\u07d4At\xfa\x1b\xc1*;q\x83\u02eb\xb7z\vYU{\xa5\xf1\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4Axj\x10\xd4G\xf4\x84\xd32D\u0337\xfa\u034bB{[\x8c\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Az<\u0454\x96S\nmB\x04\u00f5\xa1|\xe0\xf2\a\xb1\xa5\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4A~N&\x88\xb1\xfdf\xd8!R\x9eF\xedOB\xf8\xb3\xdb=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94A\x9aq\xa3l\x11\xd1\x05\xe0\xf2\xae\xf5\xa3\xe5\x98\a\x8e\x85\xc8\v\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94A\x9b\xdes\x16\xcc\x1e\u0495\u0205\xac\xe3B\u01db\xf7\xee3\xea\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4A\xa2\xf2\xe6\xec\xb8c\x94\xec\x0e3\x8c\x0f\xc9~\x9cU\x83\xde\u0489l\xee\x06\u077e\x15\xec\x00\x00\u07d4A\xa8\u0083\x00\x81\xb1\x02\xdfn\x011e|\a\xabc[T\u0389lj\xccg\u05f1\xd4\x00\x00\u07d4A\xa8\xe26\xa3\x0emc\xc1\xffdM\x13*\xa2\\\x89S~\x01\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4A\xa9\xa4\x04\xfc\x9f[\xfe\xe4\x8e\xc2e\xb1%#3\x8e)\xa8\xbf\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4A\xad6\x9fu\x8f\xef8\xa1\x9a\xa3\x14\x93y\x83,\x81\x8e\xf2\xa0\x8966\x9e\xd7t}&\x00\x00\u07d4A\xb2\xd3O\xde\v\x10)&+Ar\xc8\x1c\x15\x90@[\x03\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4A\xb2\xdb\u05dd\u069b\x86Ojp0'T\x19\u00dd>\xfd;\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4A\xc3\xc26u4\xd1;\xa2\xb3?\x18\\\xdb\xe6\xacC\xc2\xfa1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4A\u02d8\x96D_p\xa1\n\x14!R\x96\xda\xf6\x14\xe3,\xf4\u0549g\x8a\x93 b\xe4\x18\x00\x00\u07d4A\xcey\x95\t5\xcf\xf5[\xf7\x8eL\xce\xc2\xfec\x17\x85\u06d5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4A\u04f71\xa3&\xe7hX\xba\xa5\xf4\xbd\x89\xb5{6\x93#C\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\xe0\x94A\xe4\xa2\x02u\xe3\x9b\xdc\xef\xebe\\\x03\"tKvQ@\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4A\xed-\x8ep\x81H,\x91\x9f\xc2=\x8f\x00\x91\xb3\xc8,F\x85\x89F:\x1ev[\u05ca\x00\x00\xe0\x94A\xf2~tK\u049d\xe2\xb0Y\x8f\x02\xa0\xbb\x9f\x98\xe6\x81\ua90a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\u07d4A\xf4\x89\xa1\xect{\u009c>_\x9d\x8d\xb9xw\xd4\u0474\xe9\x89\a?u\u0460\x85\xba\x00\x00\u07d4B\x0f\xb8n}+Q@\x1f\xc5\xe8\xc7 \x15\xde\xcbN\xf8\xfc.\x8965\u026d\xc5\u07a0\x00\x00\u07d4B\x16\x84\xba\xa9\xc0\xb4\xb5\xf5S8\xe6\xf6\xe7\xc8\xe1F\xd4\x1c\xb7\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4B9\x96Y\xac\xa6\xa5\xa8c\xea\"E\xc93\xfe\x9a5\xb7\x88\x0e\x89n\xce2\xc2l\x82p\x00\x00\xe0\x94B;\xcaG\xab\xc0\fpW\xe3\xad4\xfc\xa6>7_\xbd\x8bJ\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4B<1\a\xf4\xba\xceANI\x9cd9\nQ\xf7F\x15\xca^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B<\xc4YL\xf4\xab\xb66\x8d\xe5\x9f\u04b1#\a4a!C\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94BD\xf13\x11X\xb9\xce&\xbb\xe0\xb9#k\x92\x03\xca5\x144\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794BQw\xebt\xad\n\x9d\x9aWR\"\x81G\xeemcV\xa6\u6239\x8b\xc8)\xa6\xf9\x00\x00\u07d4BW%\xc0\xf0\x8f\b\x11\xf5\xf0\x06\xee\xc9\x1c\\\\\x12k\x12\xae\x89\b!\xab\rD\x14\x98\x00\x00\xe0\x94BX\xfdf/\xc4\xce2\x95\xf0\xd4\xed\x8f{\xb1D\x96\x00\xa0\xa9\x8a\x01lE.\xd6\b\x8a\xd8\x00\x00\xe0\x94B\\\x18\x16\x86\x8fww\xcc+\xa6\xc6\u048c\x9e\x1eylR\xb3\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4B\\3\x8a\x13%\xe3\xa1W\x8e\xfa)\x9eW\u0646\xebGO\x81\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94BbY\xb0\xa7Vp\x1a\x8bf5(R!V\xc0(\x8f\x0f$\x8a\x02\x18\xae\x19k\x8dO0\x00\x00\u07d4Bm\x15\xf4\a\xa0\x115\xb1:kr\xf8\xf2R\v51\xe3\x02\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Box\xf7\r\xb2Y\xac\x854\x14[)4\xf4\xef\x10\x98\xb5\u0609\x13\x84\x00\xec\xa3d\xa0\x00\x00\u07d4Bs-\x8e\xf4\x9f\xfd\xa0K\x19x\x0f\xd3\xc1\x84i\xfb7A\x06\x89\x17\v\x00\xe5\u4a7e\x00\x00\u07d4Bt\x17\xbd\x16\xb1\xb3\xd2-\xbb\x90-\x8f\x96W\x01o$\xa6\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Btj\xee\xa1O'\xbe\xff\f\r\xa6BS\xf1\xe7\x97\x18\x90\xa0\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4B{F*\xb8NP\x91\xf4\x8aF\xeb\f\u0712\xdd\xcb&\xe0x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B~GQ\u00fa\xbex\xcf\xf8\x83\b\x86\xfe\xbc\x10\xf9\x90\x8dt\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94B~\xc6h\xac\x94\x04\xe8\x95\u0306\x15\x11\xd1b\nI\x12\xbe\x98\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4B\x80\xa5\x8f\x8b\xb1\v\x94@\u0794\xf4+OY! \x82\x01\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4B\x8a\x1e\xe0\xed3\x1dyR\u033e\x1cyt\xb2\x85+\u0453\x8a\x89w\xb7JN\x8d\xe5e\x00\x00\u0794B\x9c\x06\xb4\x87\xe8Tj\xbd\xfc\x95\x8a%\xa3\xf0\xfb\xa5?o\x00\x88\xbbdJ\xf5B\x19\x80\x00\xe0\x94B\xa9\x8b\xf1`'\xceX\x9cN\xd2\xc9X1\xe2rB\x05\x06N\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4B\xc6\xed\xc5\x15\xd3UW\x80\x8d\x13\xcdD\xdc\xc4@\v%\x04\xe4\x89\n\xba\x14\u015b\xa72\x00\x00\u07d4B\xce\xcf\u0492\x10y\xc2\xd7\xdf?\b\xb0z\xa3\xbe\xee^!\x9a\x8965\u026d\xc5\u07a0\x00\x00\u07d4B\u04669\x9b0\x16\xa8Y\u007f\x8bd\t'\xb8\xaf\xbc\xe4\xb2\x15\x89\xa1\x8b\xce\xc3H\x88\x10\x00\x00\u07d4B\xd3I@\xed\xd2\xe7\x00]F\xe2\x18\x8eL\xfe\u0383\x11\xd7M\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4B\u04e5\xa9\x01\xf2\xf6\xbd\x93V\xf1\x12\xa7\x01\x80\xe5\xa1U\v`\x892$\xf4'#\xd4T\x00\x00\u07d4B\u05b2c\xd9\xe9\xf4\x11lA\x14$\xfc\x99Ux;\xa1\xc5\x1b\x81\x0f\xc4g\u057aM\xeaB\xf7\xa9\x88^i\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94C>\xb9J3\x90\x86\xed\x12\u067d\xe9\xcd\x1dE\x86\x03\xc9}\u058a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4CI\"Zb\xf7\n\xeaH\n\x02\x99\x15\xa0\x1eSy\xe6O\xa5\x89\x8c\xd6~#4\xc0\xd8\x00\x00\u07d4CT\"\x1eb\xdc\t\xe6@d6\x16:\x18^\xf0m\x11J\x81\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94CTC\xb8\x1d\xfd\xb9\xbd\x8cg\x87\xbc%\x18\xe2\xd4~W\xc1_\x8a\x01C\x8d\x93\x97\x88\x1e\xf2\x00\x00\u07d4Ca\u0504o\xaf\xb3w\xb6\xc0\xeeI\xa5\x96\xa7\x8d\xdf5\x16\xa3\x89\xc2\x12z\xf8X\xdap\x00\x00\xe0\x94Cd0\x9a\x9f\xa0p\x95`\x0fy\xed\xc6Q \xcd\xcd#\xdcd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Cg\xaeK\f\xe9d\xf4\xa5J\xfdK\\6\x84\x96\xdb\x16\x9e\x9a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Ct\x89(\xe8\xc3\xecD6\xa1\u0412\xfb\xe4:\xc7I\xbe\x12Q\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4Cv{\xf7\xfd*\xf9[r\xe91-\xa9D<\xb1h\x8eCC\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94Cy\x838\x8a\xb5\x9aO\xfc!_\x8e\x82iF\x10)\xc3\xf1\xc1\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4C\x89\x8cI\xa3MP\x9b\xfe\xd4\xf7`A\xee\x91\xca\xf3\xaaj\xa5\x89\x10CV\x1a\x88)0\x00\x00\u07d4C\x8c/T\xff\x8eb\x9b\xab6\xb1D+v\v\x12\xa8\x8f\x02\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\x98b\x8e\xa6c-9>\x92\x9c\xbd\x92\x84d\xc5h\xaaJ\f\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4C\x9d//Q\x10\xa4\u054b\x17W\x93P\x15@\x87@\xfe\xc7\xf8\x89\u03e5\xc5\x15\x0fL\x88\x80\x00\u07d4C\x9d\xee?vy\xff\x100s?\x93@\xc0\x96hkI9\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xb0y\xba\xf0ry\x99\xe6k\xf7C\u057c\xbfwl;\t\"\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xbc-M\xdc\xd6X;\xe2\u01fc\tK(\xfbr\xe6+\xa8;\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xc7\xeb\u0173\xe7\xaf\x16\xf4}\xc5az\xb1\x0e\x0f9\xb4\xaf\xbb\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4C\u02d6R\x81\x8coMg\x96\xb0\xe8\x94\t0ly\xdbcI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4C\xcc\b\xd0s*\xa5\x8a\xde\xf7a\x9b\xedFU\x8a\xd7wAs\x89\xf0\xe7\u0730\x12*\x8f\x00\x00\xe0\x94C\u0567\x1c\xe8\xb8\xf8\xae\x02\xb2\xea\xf8\xea\xf2\xca(@\xb9?\xb6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794C\xdb\u007f\xf9Z\bm(\ubff8/\xb8\xfb_#\n^\xbc\u0348\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4C\xe7\xec\x84cX\xd7\xd0\xf97\xad\x1c5\v\xa0i\u05ffr\xbf\x89\x06p\xaeb\x92\x14h\x00\x00\u07d4C\xf1o\x1eu\xc3\xc0j\x94x\xe8\u0157\xa4\n<\xb0\xbf\x04\u0309\x9d\xf7\u07e8\xf7`H\x00\x00\u07d4C\xf4p\xede\x9e)\x91\xc3u\x95~]\xde\u017d\x1d8\"1\x89\x05k\xc7^-c\x10\x00\x00\u07d4C\xf7\xe8n8\x1e\xc5\x1e\u0110m\x14v\u02e9z=\xb5\x84\xe4\x8965\u026d\xc5\u07a0\x00\x00\u07d4C\xff8t>\xd0\xcdC0\x8c\x06e\t\u030e~r\xc8b\xaa\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94C\xff\x88S\xe9\x8e\xd8@k\x95\x00\n\u0684\x83b\u05a09*\x8a\x04\xae\v\x1cM.\x84\xd0\x00\x00\u07d4D\t\x88f\xa6\x9bh\xc0\xb6\xbc\x16\x82)\xb9`5\x87\x05\x89g\x89\n1\x06+\xee\xedp\x00\x00\u07d4D\x19\xaca\x8d]\xea|\xdc`w o\xb0}\xbd\xd7\x1c\x17\x02\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4D\x1aR\x00\x16a\xfa\xc7\x18\xb2\u05f3Q\xb7\xc6\xfbR\x1az\xfd\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94D\x1a\u0282c\x13$\xac\xbf\xa2F\x8b\xda2[\xbdxG{\xbf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4D\x1f7\xe8\xa0)\xfd\x02H/(\x9cI\xb5\xd0m\x00\xe4\b\xa4\x89\x12\x11\xec\xb5m\x13H\x80\x00\u07d4D \xaa5F[\xe6\x17\xad$\x98\xf3p\xde\n<\xc4\xd20\xaf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4D#/\xf6m\xda\xd1\xfd\x84\x12f8\x006\xaf\xd7\xcf}\u007fB\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4D%\rGn\x06$\x84\xe9\b\n9g\xbf:Js*\xd7?\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4D)\xa2\x9f\xee\x19\x84Pg,\f\x1d\a1b%\v\xecdt\x896*\xaf\x82\x02\xf2P\x00\x00\u07d4D5RS\xb2wH\xe3\xf3O\xe9\xca\xe1\xfbq\x8c\x8f$\x95)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4D8\xe8\x80\xcb'f\xb0\xc1\u03ae\xc9\xd2A\x8f\u03b9R\xa0D\x89\a?\xa0s\x90?\b\x00\x00\u07d4DL\xafy\xb7\x138\ue6a7\xc73\xb0*\u02a7\xdc\x02YH\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4D\\\xb8\xde^=\xf5 \xb4\x99\xef\u0240\xf5+\xff@\xf5\\v\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Dj\x809\xce\u03dd\xceHy\xcb\xca\xf3I;\xf5E\xa8\x86\x10\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4Dt)\x9d\x0e\xe0\x90\u0710x\x9a\x14\x86H\x9c=\rd^m\x8965\u026d\xc5\u07a0\x00\x00\u07d4D\x8b\xf4\x10\xad\x9b\xbc/\xec\xc4P\x8d\x87\xa7\xfc.K\x85a\xad\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4D\x90\x1e\r\x0e\b\xac=^\x95\xb8\xec\x9d^\x0f\xf5\xf1.\x03\x93\x89\x16\xa1\xf9\xf5\xfd}\x96\x00\x00\xe0\x94D\x93\x12<\x02\x1e\xce;3\xb1\xa4R\xc9&\x8d\xe1@\a\xf9\u04ca\x01je\x02\xf1Z\x1eT\x00\x00\xe0\x94D\x9a\xc4\xfb\xe3\x83\xe3g8\x85^6JW\xf4q\xb2\xbf\xa11\x8a)\xb7d2\xb9DQ \x00\x00\u07d4D\xa0\x1f\xb0J\xc0\xdb,\xce]\xbe(\x1e\x1cF\xe2\x8b9\xd8x\x89lj\xccg\u05f1\xd4\x00\x00\u07d4D\xa6=\x18BE\x87\xb9\xb3\a\xbf\xc3\xc3d\xae\x10\xcd\x04\xc7\x13\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94D\xa8\x98\x9e20\x81!\xf7$f\x97\x8d\xb3\x95\xd1\xf7l:K\x8a\x01\x88P)\x9fB\xb0j\x00\x00\u07d4D\xc1\x11\v\x18\x87\x0e\xc8\x11x\xd9=!X8\xc5Q\u050ed\x89\n\xd6\xf9\x85\x93\xbd\x8f\x00\x00\u07d4D\xc1Ge\x12|\xde\x11\xfa\xb4l],\xf4\u0532\x89\x00#\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94D\xc5N\xaa\x8a\xc9@\xf9\xe8\x0f\x1et\xe8/\xc1O\x16v\x85j\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4D\xcdwSZ\x89?\xa7\xc4\xd5\xeb:$\x0ey\u0419\xa7--\x89,s\xc97t,P\x00\x00\u07d4D\u07faP\xb8)\xbe\xcc_O\x14\u0470J\xab3 \xa2\x95\xe5\x8965\u026d\xc5\u07a0\x00\x00\u07d4D\xe2\xfd\xc6y\xe6\xbe\xe0\x1e\x93\xefJ:\xb1\xbc\xce\x01*\xbc|\x89\x16=\x19I\x00\xc5E\x80\x00\xe0\x94D\xf6/*\xaa\xbc)\xad:k\x04\xe1\xffo\x9c\xe4R\xd1\xc1@\x8a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4D\xff\xf3{\xe0\x1a8\x88\u04f8\xb8\u1200\xa7\xdd\xef\xee\xea\u04c9\x0e\f[\xfc}\xae\x9a\x80\x00\u07d4E\x06\xfe\x19\xfaK\x00k\xaa9\x84R\x9d\x85\x16\xdb++P\xab\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\x1b6\x99G[\xed]y\x05\xf8\x90Z\xa3Eo\x1e\u05c8\xfc\x89\x8a\xc7#\x04\x89\xe8\x00\x00\x00\u0794E\x1bpp%\x9b\u06e2q\x00\xe3n#B\x8aS\xdf\xe3\x04\u9239\x8b\xc8)\xa6\xf9\x00\x00\u07d4E'+\x8fb\xe9\xf9\xfa\x8c\xe0D \u1ba3\xeb\xa9hn\xac\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94E+d\u06ce\xf7\xd6\u07c7\u01c8c\x9c\"\x90\xbe\x84\x82\xd5u\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4E>5\x9a3\x97\x94LZ'Z\xb1\xa2\xf7\n^Z?i\x89\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4EI\xb1Yy%_~e\xe9\x9b\rV\x04\u06d8\xdf\xca\u023f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4EKa\xb3D\xc0\xef\x96Qy#\x81U\xf2w\u00c2\x9d\v8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94EO\x01A\xd7!\xd3<\xbd\xc4\x10\x18\xbd\x01\x11\x9a\xa4xH\x18\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4ES3\x90\xe3@\xfe\r\xe3\xb3\xcf_\xb9\xfc\x8e\xa5R\xe2\x9eb\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4ES\x96\xa4\xbb\u067a\u8bdf\xb7\xc4\xd6MG\x1d\xb9\xc2E\x05\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4E[\x92\x96\x92\x1at\xd1\xfcAa\u007fC\xb80>o>\xd7l\x89\u3bb5sr@\xa0\x00\x00\u07d4E\\\xb8\xee9\xff\xbcu#1\xe5\xae\xfcX\x8e\xf0\xeeY4T\x8965F:x\r\xef\x80\x00\u07d4Ej\u0b24\x8e\xbc\xfa\xe1f\x06\x02PR_c\x96^v\x0f\x89\x10CV\x1a\x88)0\x00\x00\u07d4Eo\x8dtf\x82\xb2$g\x93I\x06M\x1b6\x8c|\x05\xb1v\x89\u0213\u041c\x8fQP\x00\x00\u07d4Ep)\xc4i\xc4T\x8d\x16\x8c\xec>e\x87.D(\xd4+g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Eq\xdeg+\x99\x04\xba\xd8t6\x92\xc2\x1cO\xdc\xeaL.\x01\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Ex\x1b\xbew\x14\xa1\xc8\xf7;\x1cty!\xdfO\x84'\x8bp\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E{\xce\xf3}\xd3\xd6\v-\xd0\x19\xe3\xfea\xd4k?\x1erR\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94E\x8e<\u025e\x94xD\xa1\x8ejB\x91\x8f\xef~\u007f_^\xb3\x8a\a\xb5?y\xe8\x88\xda\xc0\x00\x00\u07d4E\x93\x93\xd6:\x06>\xf3r\x1e\x16\xbd\x9f\xdeE\ue77dw\xfb\x89j\xba\u05a3\xc1S\x05\x00\x00\u07d4E\xa5p\xdc\xc2\t\f\x86\xa6\xb3\xea)\xa6\bc\xdd\xe4\x1f\x13\xb5\x89\f\x9a\x95\xee)\x86R\x00\x00\u07d4E\xa8 \xa0g/\x17\xdct\xa0\x81\x12\xbcd?\xd1\x16w6\u00c9\n\xd6\xc4;(\x15\xed\x80\x00\u07d4E\xb4q\x05\xfeB\xc4q-\xcen*!\xc0[\xff\xd5\xeaG\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\xbb\x82\x96R\u063f\xb5\x8b\x85'\xf0\xec\xb6!\u009e!.\u00c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\xc0\u045f\v\x8e\x05O\x9e\x8986\xd5\xec\xaey\x01\xaf(\x12\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4E\xc4\xec\xb4\xee\x89\x1e\xa9\x84\xa7\xc5\xce\xfd\x8d\xfb\x001\v(P\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4E\u028d\x95f\b\xf9\xe0\n/\x99t\x02\x86@\x88\x84ef\x8f\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\u0298b\x00;N@\xa3\x17\x1f\xb5\xca\xfa\x90(\xca\xc8\xde\x19\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4E\xd1\xc9\xee\xdf|\xabA\xa7y\x05{y9_T(\xd8\x05(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4E\u0535M7\xa8\xcfY\x98!#_\x06/\xa9\xd1p\xed\u8909\x11\x90g;_\u0690\x00\x00\xe0\x94E\xdb\x03\xbc\xcf\u05a5\xf4\xd0&k\x82\xa2*6\x87\x92\xc7}\x83\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4E\xe3\xa9>r\x14J\u0686\f\xbcV\xff\x85\x14Z\xda8\xc6\u0689WG=\x05\u06ba\xe8\x00\x00\u07d4E\u6378\u06fa\xba_\xc2\xcb3|b\xbc\xd0\xd6\x1b\x05\x91\x89\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94E\u6379L}\n\xb7\xacA\x85zq\xd6qG\x87\x0fNq\x8aT\xb4\v\x1f\x85+\xda\x00\x00\x00\u07d4E\xf4\xfc`\xf0\x8e\xac\xa1\x05\x98\xf03c)\x80\x1e<\x92\xcbF\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4F\rSU\xb2\xce\xebnb\x10}\x81\xe5\x12p\xb2k\xf4V \x89l\xb7\xe7Hg\xd5\xe6\x00\x00\xe0\x94F\"O2\xf4\xec\xe5\u0206p\x90\xd4@\x9dU\xe5\v\x18C-\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4F'\xc6\x06\x84&q\xab\u0782\x95\xee]\xd9L\u007fT\x954\xf4\x89\x0f\x89_\xbd\x872\xf4\x00\x00\u07d4F+g\x8bQ\xb5\x84\xf3\xedz\xda\a\v\\\u065c\v\xf7\xb8\u007f\x89\x05k\xc7^-c\x10\x00\x00\u07d4FM\x9c\x89\xcc\xe4\x84\xdf\x00\x02w\x19\x8e\xd8\a_\xa65r\u0449\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4FPNj!Z\xc8;\xcc\xf9V\xbe\xfc\x82\xabZg\x93q\u0209\x1c!(\x05\u00b4\xa5\x00\x00\xe0\x94FQ\xdcB\x0e\b\xc3);'\xd2Ix\x90\xebP\":\xe2\xf4\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4FS\x1e\x8b\x1b\xde\t\u007f\u07c4\x9dm\x11\x98\x85`\x8a\x00\x8d\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4Fb\x92\xf0\xe8\rC\xa7\x87t'u\x90\xa9\xebE\x96\x12\x14\xf4\x894\x95tD\xb8@\xe8\x00\x00\xe0\x94Fb\xa1v^\xe9!\x84-\u0708\x89\x8d\x1d\xc8bu\x97\xbd~\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Fe\xe4s\x96\xc7\u06d7\xeb*\x03\xd9\bc\xd5\u053a1\x9a\x94\x89 \x86\xac5\x10R`\x00\x00\u07d4Fo\xdak\x9bX\xc5S'P0j\x10\xa2\xa8\xc7h\x10;\a\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4Fq$\xae\u007fE/&\xb3\xd5t\xf6\b\x88\x94\xfa]\x1c\xfb;\x89\x92^\x06\xee\xc9r\xb0\x00\x00\u0794Fr*6\xa0\x1e\x84\x1d\x03\xf7\x80\x93^\x91}\x85\u0566z\xbd\x88\xce\xc7o\x0eqR\x00\x00\u07d4Fw\x9aVV\xff\x00\xd7>\xac:\xd0\u00cbl\x850\x94\xfb@\x89\f\x82S\xc9lj\xf0\x00\x00\u07d4Fw\xb0N\x03C\xa3!1\xfdj\xbb9\xb1\xb6\x15k\xba=[\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4F}Y\x88$\x9ahaG\x16e\x98@\xed\n\xe6\xf6\xf4W\xbc\x89\x15\x01\xa4\x8c\xef\xdf\xde\x00\x00\u07d4F~\x0e\xd5O;v\xae\x066\x17n\aB\b\x15\xa0!sn\x89lk\x93[\x8b\xbd@\x00\x00\u07d4F~\xa1\x04E\x82~\xf1\xe5\x02\xda\xf7k\x92\x8a \x9e\r@2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94F\u007f\xbfAD\x16\x00u\u007f\xe1X0\xc8\xcd_O\xfb\xbb\xd5`\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94F\x93Xp\x932\xc8+\x88~ \xbc\xdd\xd0\"\x0f\x8e\u06e7\u040a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4F\x97\xba\xaf\x9c\xcb`?\xd3\x040h\x9dCTE\xe9\u024b\xf5\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4F\xa3\v\x8a\x80\x891!tE\xc3\xf5\xa9>\x88,\x03E\xb4&\x89\r\x8d\xb5\xeb\u05f2c\x80\x00\u07d4F\xa40\xa2\u0528\x94\xa0\u062a?\xea\xc6\x156\x14\x15\xc3\xf8\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4F\xaaP\x18pg~\u007f\nPHv\xb4\xe8\x80\x1a\n\xd0\x1cF\x89+^:\xf1k\x18\x80\x00\x00\u07d4F\xbf\u0172\a\xeb \x13\xe2\xe6\x0fw_\xec\xd7\x18\x10\u0159\f\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4F\xc1\xaa\"D\xb9\u0229W\u028f\xacC\x1b\x05\x95\xa3\xb8h$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4F\xd8\x061(B\x03\xf6(\x8e\xcdNWX\xbb\x9dA\xd0]\xbe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\n\xc5\xd1\xf3\xef\xe2\x8f8\x02\xaf\x92[W\x1ec\x86\x8b9}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\x10\x10\xdaI/@\x18\x83;\b\x8d\x98r\x90\x1e\x06\x12\x91t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4G\x12T\x02e\xcb\xee\u00c4p\"\u015f\x1b1\x8dC@\n\x9e\x89\xbd\xbcA\xe04\x8b0\x00\x00\xe0\x94G\x14\u03e4\xf4k\u05bdps}u\x87\x81\x97\xe0\x8f\x88\xe61\x8a\x02\u007f>\u07f3Nn@\x00\x00\u07d4G H\xcc`\x9a\xeb$!e\uaa87\x05\x85\f\xf3\x12]\xe0\x8965\u026d\xc5\u07a0\x00\x00\u07d4G!\x92)\xe8\xcdVe\x9ae\u00a9C\xe2\u075a\x8fK\xfd\x89\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4G7\xd0B\xdcj\xe7>\xc7:\xe2Qz\u03a2\xfd\xd9d\x87\u014965\u026d\xc5\u07a0\x00\x00\u07d4GAX\xa1\xa9\xdci<\x13?e\xe4{\\:\xe2\xf7s\xa8o\x89\n\xdaUGK\x814\x00\x00\u07d4GE\xab\x18\x1a6\xaa\x8c\xbf\"\x89\xd0\xc4Qe\xbc~\xbe#\x81\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4GPf\xf9\xad&eQ\x96\xd5SS'\xbb\xeb\x9by)\xcb\x04\x89\xa4\xccy\x95c\u00c0\x00\x00\xe0\x94GR!\x8eT\xdeB?\x86\xc0P\x193\x91z\xea\b\xc8\xfe\u054a\x04<3\xc1\x93ud\x80\x00\x00\u07d4GZa\x93W-JNY\u05fe\t\u02d6\r\u074cS\x0e/\x89$,\xf7\x8c\xdf\a\xff\x80\x00\u07d4Gd\x8b\xed\x01\xf3\xcd2I\bNc]\x14\u06a9\xe7\xec<\x8a\x89\n\x84Jt$\xd9\xc8\x00\x00\u07d4Gh\x84\x10\xff%\xd6T\xd7.\xb2\xbc\x06\xe4\xad$\xf83\xb0\x94\x89\b\xb2\x8da\xf3\u04ec\x00\x00\u07d4GkU\x99\b\x9a?\xb6\xf2\x9clr\xe4\x9b.G@\ua00d\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4Gs\x0f_\x8e\xbf\x89\xacr\xef\x80\xe4l\x12\x19P8\xec\xdcI\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94G{$\xee\u80deO\u045d\x12P\xbd\vfEyJa\u028a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4G\x81\xa1\nM\xf5\uef02\xf4\xcf\xe1\a\xba\x1d\x8av@\xbdf\x89a\t=|,m8\x00\x00\u07d4G\x88Z\xba\xbe\xdfM\x92\x8e\x1c\x88\x83\xa6a\x9cl(\x11\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94G\xe2]\xf8\x82%8\xa8Yk(\xc67\x89kM\x14<5\x1d\x8a\x11\v\xe9\xeb$\xb8\x81P\x00\x00\u07d4G\xf4ik\xd4b\xb2\r\xa0\x9f\xb8>\xd2\x03\x98\x18\xd7v%\xb3\x89\b\x13\xcaV\x90m4\x00\x00\u07d4G\xfe\xf5\x85\x84FRH\xa0\x81\r`F>\xe9>Zn\xe8\u04c9\x0fX\xcd>\x12i\x16\x00\x00\u07d4G\xffo\xebC! `\xbb\x15\x03\u05e3\x97\xfc\b\xf4\xe7\x03R\x89lk\x93[\x8b\xbd@\x00\x00\u07d4G\xff\xf4,g\x85Q\xd1A\xebu\xa6\xee9\x81\x17\xdf>J\x8d\x89\x05k\xea\xe5\x1f\xd2\xd1\x00\x00\u07d4H\x01\x0e\xf3\xb8\xe9^?0\x8f0\xa8\xcb\u007fN\xb4\xbf`\xd9e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4H\n\xf5 v\x00\x9c\xa77\x81\xb7\x0eC\xb9Y\x16\xa6\"\x03\xab\x892\x19r\xf4\b=\x87\x80\x00\u07d4H\x0f1\xb9\x891\x1eA$\u01a7F_ZD\tM6\xf9\u04097\x90\xbb\x85Q7d\x00\x00\xe0\x94H\x11\x15)j\xb7\xdbRI/\xf7\xb6G\xd63)\xfb\\\xbck\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4H\x1e:\x91\xbf\xdc/\x1c\x84(\xa0\x11\x9d\x03\xa4\x16\x01A~\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4H(\xe4\xcb\xe3N\x15\x10\xaf\xb7,+\ueb0aE\x13\xea\xeb\u0649\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94H)\x82\xac\x1f\x1cm\x17!\xfe\xec\u0679\xc9l\xd9I\x80PU\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4H0,1\x1e\xf8\xe5\xdcfAX\xddX<\x81\x19Mn\rX\x89\xb6gl\xe0\xbc\xcb\\\x00\x00\u07d4H;\xa9\x904\xe9\x00\xe3\xae\xdfaI\x9d;+\xce9\xbe\xb7\xaa\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4HT\x8bK\xa6+\xcb/\r4\xa8\x8d\u019ah\x0eS\x9c\xf0F\x89\x05l\xf1\u02fbt2\x00\x00\u07d4Hc\x84\x979&Zc\xb0\xa2\xbf#jY\x13\xe6\xf9Y\xce\x15\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4He\x9d\x8f\x8c\x9a/\xd4Oh\u06a5]#\xa6\b\xfb\xe5\x00\u0709lk\x93[\x8b\xbd@\x00\x00\xe0\x94Hf\x9e\xb5\xa8\x01\u0637_\xb6\xaaX\xc3E\x1bpX\xc2C\xbf\x8a\x06\x8dB\xc18\u06b9\xf0\x00\x00\u07d4Hjl\x85\x83\xa8D\x84\xe3\xdfC\xa1#\x83\u007f\x8c~#\x17\u0409\x11\x87\xc5q\xab\x80E\x00\x00\u07d4Hz\xdf}p\xa6t\x0f\x8dQ\xcb\xddh\xbb?\x91\u0125\xceh\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4H~\x10\x85\x02\xb0\xb1\x89\uf70cm\xa4\xd0\xdbba\xee\xc6\xc0\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94H\x88\xfb%\xcdP\u06f9\xe0H\xf4\x1c\xa4}x\xb7\x8a'\xc7\u064a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u0794H\x934\u00b6\x95\xc8\xee\a\x94\xbd\x86B\x17\xfb\x9f\xd8\xf8\xb15\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4H\xa3\r\xe1\xc9\x19\xd3\xfd1\x80\xe9}_+*\x9d\xbd\x96M-\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4H\xbf\x14\u05f1\xfc\x84\xeb\xf3\xc9k\xe1/{\xce\x01\xaai\xb0>\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4H\xc2\ue465\aV\xd8\u039a\xbe\xebu\x89\xd2,o\xee]\xfb\x89\xae\x8ez\v\xb5u\xd0\x00\x00\u07d4H\xc5\u0197\v\x91a\xbb\x1c{z\xdf\xed\x9c\xde\u078a\x1b\xa8d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94H\xd2CKz}\xbb\xff\b\";c\x87\xb0]\xa2\xe5\t1&\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4H\xd4\xf2F\x8f\x96?\u05da\x00a\x98\xbbg\x89]-Z\xa4\u04c9K\xe4\xe7&{j\xe0\x00\x00\u07d4H\xe0\xcb\xd6\u007f\x18\xac\xdbzb\x91\xe1%M\xb3.\trs\u007f\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4H\xf6\n5HO\xe7y+\u030a{c\x93\xd0\u0761\xf6\xb7\x17\x89\xc3(\t>a\xee@\x00\x00\u07d4H\xf8\x83\xe5g\xb46\xa2{\xb5\xa3\x12M\xbc\x84\xde\xc7u\xa8\x00\x89)\xd7n\x86\x9d\u0340\x00\x00\xe0\x94I\x01E\xaf\xa8\xb5E\"\xbb!\xf3R\xf0m\xa5\xa7\x88\xfa\x8f\x1d\x8a\x01\xf4lb\x90\x1a\x03\xfb\x00\x00\u07d4I\t\xb3\x19\x98\xea\xd4\x14\xb8\xfb\x0e\x84k\xd5\xcb\xde995\xbe\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4I\x12\xd9\x02\x93\x16v\xff9\xfc4\xfe<<\xc8\xfb!\x82\xfaz\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4I\x13o\xe6\xe2\x8btS\xfc\xb1kk\xbb\u9aac\xba\x837\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94I\x15a\u06cbo\xaf\xb9\x00~b\xd0P\u0082\xe9,Kk\u020a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4I\x18]\xd7\xc262\xf4lu\x94s\ubb96`\b\xcd5\x98\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4I,\xb5\xf8a\xb1\x87\xf9\xdf!\xcdD\x85\xbe\xd9\vP\xff\xe2-\x89\x1b\x19\xe5\vD\x97|\x00\x00\u07d4I-\xe4j\xaf\x8f\x1dp\x8dY\u05da\xf1\xd0:\xd2\xcb`\x90/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I.p\xf0M\x18@\x8c\xb4\x1e%`70Pk5\xa2\x87k\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4I:g\xfe#\xde\xccc\xb1\r\xdau\xf3(v\x95\xa8\x1b\u056b\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4I=H\xbd\xa0\x15\xa9\xbf\xcf\x16\x03\x93n\xabh\x02L\xe5Q\xe0\x89\x018\xa3\x88\xa4<\x00\x00\x00\xe0\x94IBV\xe9\x9b\x0f\x9c\xd6\xe5\xeb\xca8\x99\x862R\x90\x01e\u020a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4IM\xecM^\xe8\x8a'q\xa8\x15\xf1\xeerd\x94/\xb5\x8b(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I[d\x1b\x1c\u07a3b\u00f4\u02fd\x0f\\\xc5\v\x1e\x17k\x9c\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94Ih\xa2\xce\xdbEuU\xa19)Z\xea(wnT\x00<\x87\x8a\x02#\x1a\xef\u0266b\x8f\x00\x00\u07d4Im6U4S\n_\xc1W|\nRA\u02c8\xc4\xdapr\x89a\t=|,m8\x00\x00\xe0\x94In1\x95\x92\xb3A\xea\xcc\xd7x\u0767\xc8\x19mT\xca\xc7u\x8a\x01\xf5q\x89\x87fKH\x00\x00\u07d4IoXC\xf6\xd2L\u064d%^L#\xd1\xe1\xf0#\"uE\x89_\x17\x9f\u0526\xee\t\x80\x00\xe0\x94Ip\u04ec\xf7+[\x1f2\xa7\x00<\xf1\x02\xc6N\xe0TyA\x8a\x1d\xa5jK\b5\xbf\x80\x00\x00\u07d4Iw\xa7\x93\x9d\t9h\x94U\xce&9\xd0\xeeZL\xd9\x10\xed\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4Iy\x19N\xc9\xe9}\xb9\xbe\xe84;|w\xd9\xd7\xf3\xf1\u071f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Iy4c\xe1h\x10\x83\u05ab\xd6\xe7%\u057b\xa7E\xdc\xcd\xe8\x89\x1d\x98\xe9LNG\x1f\x00\x00\u07d4I\x81\xc5\xfff\xccN\x96\x80%\x1f\xc4\xcd/\xf9\a\xcb2xe\x89(\xa8WBTf\xf8\x00\x00\u07d4I\x89\u007f\xe92\xbb\xb3\x15L\x95\u04fc\xe6\xd9;ms)\x04\u0749\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4I\x89\xe1\xab^|\xd0\aF\xb3\x93\x8e\xf0\xf0\xd0d\xa2\x02[\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4I\x8a\xbd\xeb\x14\xc2k{r4\xd7\x0f\u03ae\xf3a\xa7m\xffr\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4I\xa6E\xe0f}\xfd{2\xd0u\xcc$g\u074ch\t\a\u0109\a\x06\x01\x95\x8f\u02dc\x00\x00\xe0\x94I\xb7N\x16\x92e\xf0\x1a\x89\xecL\x90r\u0164\xcdr\xe4\xe85\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4I\xbd\xbc{\xa5\xab\xeb\xb68\x9e\x91\xa3(R \xd3E\x1b\xd2S\x8965\u026d\xc5\u07a0\x00\x00\u07d4I\xc9A\xe0\xe5\x01\x87&\xb7)\x0f\xc4s\xb4q\xd4\x1d\xae\x80\u0449\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94I\xc9w\x1f\xca\x19\u0579\xd2E\u0211\xf8\x15\x8f\xe4\x9fG\xa0b\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4I\xcf\x1eT\xbe61\x06\xb9 r\x9d-\v\xa4o\bg\x98\x9a\x89\x0e\x87?D\x13<\xb0\x00\x00\u07d4I\xd2\u008e\xe9\xbcT^\xaa\xf7\xfd\x14\xc2|@s\xb4\xbb_\x1a\x89O\xe9\xb8\x06\xb4\r\xaf\x00\x00\u07d4I\xdd\xee\x90.\x1d\f\x99\u0471\x1a\xf3\u030a\x96\xf7\x8eM\xcf\x1a\x89\n\u03a5\xe4\xc1\x8cS\x00\x00\u07d4I\xf0(9[Z\x86\xc9\xe0\u007fwxc\x0eL.=7:w\x89\x06\xa7JP8\u06d1\x80\x00\xe0\x94J\x19 5\xe2a\x9b$\xb0p\x9dVY\x0e\x91\x83\xcc\xf2\xc1\u064a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4J@S\xb3\x1d\x0e\xe5\u06ef\xb1\xd0k\u05ec\u007f\xf3\",G\u0589K\xe4\xe7&{j\xe0\x00\x00\u07d4JC\x01p\x15-\xe5\x17&3\u0742b\xd1\a\xa0\xaf\xd9j\x0f\x89\xabM\xcf9\x9a:`\x00\x00\u07d4JG\xfc>\x17\u007fVz\x1e8\x93\xe0\x00\xe3k\xba#R\n\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4JR\xba\xd2\x03W\"\x8f\xaa\x1e\x99k\xedy\f\x93gK\xa7\u0409Hz\x9a0E9D\x00\x00\u07d4JS\xdc\xdbV\xceL\xdc\xe9\xf8.\xc0\xeb\x13\xd6sR\xe7\u020b\x89\u3bb5sr@\xa0\x00\x00\u07d4J_\xae;\x03r\xc20\xc1%\xd6\xd4p\x14\x037\xab\x91VV\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4Jq\x90a\xf5(T\x95\xb3{\x9d~\xf8\xa5\x1b\a\xd6\u6b2c\x89\n\xd4\xc81j\v\f\x00\x00\u07d4Js8\x92\x98\x03\x1b\x88\x16\u0329FB\x1c\x19\x9e\x18\xb3C\u0589\"8h\xb8y\x14o\x00\x00\u07d4Js]\"G\x927m3\x13g\xc0\x93\xd3\x1c\x87\x944\x15\x82\x89f\xff\xcb\xfd^Z0\x00\x00\u07d4Jt\x94\xcc\xe4HU\u0300X(B\xbe\x95\x8a\r\x1c\x00r\ue242\x1a\xb0\xd4AI\x80\x00\x00\u07d4Ju\xc3\xd4\xfao\u033d]\u0567\x03\xc1Sy\xa1\xe7\x83\u9dc9b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94J\x81\xab\xe4\x98L|k\xefc\u0598 \xe5WC\xc6\x1f \x1c\x8a\x03d\x01\x00N\x9a\xa3G\x00\x00\u07d4J\x82iO\xa2\x9d\x9e!2\x02\xa1\xa2\t(]\xf6\xe7E\xc2\t\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4J\x83\\%\x82LG\xec\xbf\u01d49\xbf?\\4\x81\xaau\u0349K\xe4\xe7&{j\xe0\x00\x00\u07d4J\x91\x802C\x91Y\xbb1[g%\xb6\x83\r\xc86\x97s\x9f\x89\x12\xa3.\xf6x3L\x00\x00\u07d4J\x97\xe8\xfc\xf4c^\xa7\xfc^\x96\xeeQu.\u00c8qk`\x89\x1d\x99E\xab+\x03H\x00\x00\u07d4J\x9a&\xfd\n\x8b\xa1\x0f\x97}\xa4\xf7|1\x90\x8d\xabJ\x80\x16\x89a\t=|,m8\x00\x00\u07d4J\xa1H\xc2\xc34\x01\xe6j+Xnew\u0132\x92\xd3\xf2@\x89\v\xb8`\xb2\x85\xf7t\x00\x00\u07d4J\xa6\x93\xb1\"\xf3\x14H*G\xb1\x1c\xc7|h\xa4\x97\x87ab\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4J\xb2\xd3O\x04\x83O\xbftyd\x9c\xab\x92=,G%\xc5S\x89\xbe\xd1\xd0&=\x9f\x00\x00\x00\u07d4J\xc0vs\xe4/d\xc1\xa2^\xc2\xfa-\x86\xe5\xaa+4\xe09\x89lk\x93[\x8b\xbd@\x00\x00\u07d4J\u016c\xad\x00\v\x88w!L\xb1\xae\x00\xea\u0263}Y\xa0\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4J\u0250ZL\xb6\xab\x1c\xfdbTn\xe5\x91s\x00\xb8|O\u07897\b\xba\xed=h\x90\x00\x00\u07d4J\u03e9\xd9N\xdaf%\xc9\u07e5\xf9\xf4\xf5\xd1\a\xc4\x03\x1f\u07c9\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4J\xd0G\xfa\xe6~\xf1b\xfeh\xfe\xdb\xc2};e\xca\xf1\f6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4J\xd9]\x18\x8dddp\x9a\xdd%U\xfbM\x97\xfe\x1e\xbf1\x1f\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\u07d4J\xdb\xf4\xaa\xe0\xe3\xefD\xf7\xddM\x89\x85\u03ef\tn\u010e\x98\x89\b!\xab\rD\x14\x98\x00\x00\u07d4J\xe2\xa0M9\t\xefENTL\xcf\xd6\x14\xbf\xef\xa7\x10\x89\xae\x89\x18\x01\x15\x9d\xf1\xee\xf8\x00\x00\xe0\x94J\xe90\x82\xe4Q\x87\xc2a`\xe6g\x92\xf5\u007f\xad5Q\xc7:\x8a\x04\x96\x15 \xda\xff\x82(\x00\x00\u07d4J\xf0\xdb\a{\xb9\xba^D>!\xe1H\xe5\x9f7\x91\x05\u0152\x89 \x86\xac5\x10R`\x00\x00\u07d4K\x06\x19\xd9\u062a1:\x951\xac}\xbe\x04\xca\rjZ\u0476\x89lk\x93[\x8b\xbd@\x00\x00\u07d4K\v\u062c\xfc\xbcS\xa6\x01\v@\xd4\u040d\xdd-\x9dib-\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4K\x19\xeb\f5K\xc199`\xeb\x06\x06;\x83\x92o\rg\xb2\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4K)C|\x97\xb4\xa8D\xbeq\u0323\xb6H\xd4\xca\x0f\u075b\xa4\x89\b$q\x984\u03ec\x00\x00\u07d4K1\xbfA\xab\xc7\\\x9a\xe2\u034f\u007f5\x16;n+tPT\x89\x14\xb5P\xa0\x13\xc78\x00\x00\u07d4K:|\u00e7\u05f0\x0e\xd5(\"!\xa6\x02Y\xf2[\xf6S\x8a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94K:\xab3^\xbb\xfa\xa8p\xccM`^}.t\xc6h6\x9f\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4K\xcd\xc1\x8a`\x00\x00\u07d4K`\xa3\xe2S\xbf8\xc8\xd5f \x10\xbb\x93\xa4s\xc9e\xc3\xe5\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4Kt\xf5\xe5\x8e.\xdfv\xda\xf7\x01Q\x96J\v\x8f\x1d\xe0f<\x89\x11\x90\xaeID\xba\x12\x00\x00\u07d4Kv!f\xdd\x11\x18\xe8Ci\xf8\x04\xc7_\x9c\xd6W\xbfs\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Ky.)h>\xb5\x86\u353b3Rl`\x01\xb3\x97\x99\x9e\x89 \x86\xac5\x10R`\x00\x00\u07d4K\x90N\x93K\xd0\u030b p_\x87\x9e\x90[\x93\xea\f\xcc0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94K\x92\x06\xbakT\x9a\x1a\u007f\x96\x9e\x1d]\xba\x86u9\xd1\xfag\x8a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4K\x98N\xf2lWn\x81Z.\xae\xd2\xf5\x17\u007f\a\u06f1\xc4v\x89T\x91YV\xc4\t`\x00\x00\u07d4K\x9e\x06\x8f\xc4h\tv\xe6\x15\x04\x91)\x85\xfd\\\xe9K\xab\r\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4K\xa0\xd9\xe8\x96\x01w+IhG\xa2\xbbC@\x18g\x87\xd2e\x8965\u026d\xc5\u07a0\x00\x00\u07d4K\xa5:\xb5I\xe2\x01m\xfa\"<\x9e\u0563\x8f\xad\x91(\x8d\a\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94K\xa8\xe0\x11\u007f\xc0\xb6\xa3\xe5k$\xa3\xa5\x8f\xe6\xce\xf4B\xff\x98\x8a\x011\xbe\xb9%\xff\xd3 \x00\x00\u07d4K\xac\x84j\xf4\x16\x9f\x1d\x95C\x1b4\x1d\x88\x00\xb2!\x80\xaf\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4K\xb6\xd8k\x83\x14\xc2-\x8d7\xeaQm\x00\x19\xf1V\xaa\xe1-\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94K\xb9e\\\xfb*6\xea|cz{\x85\x9bJ1T\xe2n\xbe\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\xe0\x94K\xbc\xbf8\xb3\xc9\x01c\xa8K\x1c\u04a9;X\xb2\xa34\x8d\x87\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94K\xd6\xdd\f\xff#@\x0e\x170\xba{\x89E\x04W}\x14\xe7J\x8a+\xa0\xcc\xdd\xd0\xdfs\xb0\x00\x00\u07d4K\xe8b\x8a\x81T\x87N\x04\x8d\x80\xc1B\x18\x10\"\xb1\x80\xbc\xc1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4K\xe9\rA!)\u0564\xd0BCa\xd6d\x9dNG\xa6#\x16\x897\b\xba\xed=h\x90\x00\x00\xe0\x94K\xea(\x8e\xeaB\u0115^\xb9\xfa\xad*\x9f\xafG\x83\xcb\u076c\x8a\x06\x18\xbe\x16c\u012fI\x00\x00\u07d4K\xf4G\x97\x99\xef\x82\xee\xa2\tC7OV\xa1\xbfT\x00\x1e^\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4K\xf8\xbf\x1d5\xa211Wd\xfc\x80\x01\x80\x9a\x94\x92\x94\xfcI\x89\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\u07d4K\xf8\xe2oL'\x90\xdae3\xa2\xac\x9a\xba\xc3\u019a\x19\x943\x89\n\u05ce\xbcZ\xc6 \x00\x00\u0794L\n\xcaP\x8b<\xaf^\xe0(\xbcp}\xd1\xe8\x00\xb88\xf4S\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94L\v\x15\x15\xdf\xce\u05e1>\x13\xee\x12\xc0\xf5#\xaePO\x03+\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4L\x13\x98\f2\xdc\xf3\x92\vx\xa4\xa7\x903\x12\x90|\x1b\x12?\x89\x03A\x00\x15\xfa\xae\f\x00\x00\u07d4L\x15y\xaf3\x12\xe4\xf8\x8a\xe9\x95\xcc9W\xd2R\xce\v\xf0\xc8}[O\"4g.p\x89\x87\x86x2n\xac\x90\x00\x00\u07d4LB1y\x82i\x1d\x10\x89\x05k\xc7^-c\x10\x00\x00\u07d4LZ\xfe@\xf1\x8f\xfcH\u04e1\xae\xc4\x1f\u009d\xe1y\xf4\u0497\x89lk\x93[\x8b\xbd@\x00\x00\u07d4L[=\xc0\xe2\xb96\x0f\x91(\x9b\x1f\xe1<\xe1,\x0f\xbd\xa3\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Lfk\x86\xf1\xc5\ue324\x12\x85\xf5\xbd\xe4\xf7\x90R\b\x14\x06\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Lik\xe9\x9f:i\x04@\xc3CjY\xa7\xd7\xe97\u05ba\r\x89\xbb\x91%T\"c\x90\x00\x00\u07d4Lj$\x8f\xc9}p]\xefI\\\xa2\aY\x16\x9e\xf0\xd3dq\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4Lj\x9d\xc2\u02b1\n\xbb.|\x13p\x06\xf0\x8f\ucd77y\xe1\x89\x1b\r\x04 /G\xec\x00\x00\u07d4Lk\x93\xa3\xbe\xc1cIT\f\xbf\xca\xe9l\x96!\xd6dP\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Lu\x98\x13\xad\x13\x86\xbe\xd2\u007f\xfa\xe9\xe4\x81^60\u0323\x12\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Lv\f\xd9\xe1\x95\xeeO-k\xce%\x00\xff\x96\xda|C\ue44a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4Lv{e\xfd\x91\x16\x1fO\xbd\xccji\xe2\xf6\xadq\x1b\xb9\x18\x89'\b\x01\xd9F\xc9@\x00\x00\u07d4L~.+w\xad\f\xd6\xf4J\xcb(a\xf0\xfb\x8b(u\x0e\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4L\x85\xed6/$\xf6\xb9\xf0L\xdf\xcc\xd0\"\xaeSQG\u02f9\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4L\x93[\xb2Pw\x8b0\x9b==\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4L\xee\x90\x1bJ\u0231V\xc5\xe2\xf8\xa6\xf1\xbe\xf5r\xa7\xdc\xeb~\x8965\u026d\xc5\u07a0\x00\x00\u07d4L\xef\xbe#\x98\xe4}R\u73743L\x8bivu\U00053b89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4L\xf5S{\x85\x84/\x89\xcf\xee5\x9e\xaeP\x0f\xc4I\xd2\x11\x8f\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94M\bG\x1dh\x00z\xff*\xe2y\xbc^?\xe4\x15o\xbb\xe3\u078a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4M \x01\x10\x12@\b\xd5ov\x98\x12VB\f\x94jo\xf4\\\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4M$\xb7\xacG\xd2\xf2}\xe9\tt\xba=\xe5\xea\xd2\x03TK\u0349\x05k\xc7^-c\x10\x00\x00\u0794M)\xfcR:,\x16)S!!\u0699\x98\u9d6b\x9d\x1bE\x88\xdbD\xe0I\xbb,\x00\x00\u07d4M8\xd9\x0f\x83\xf4Q\\\x03\xccx2j\x15M5\x8b\u0602\xb7\x89\n\ad\a\xd3\xf7D\x00\x00\u07d4ML\xf5\x80t)a^0\xcd\xfa\xce\x1eZ\xaeM\xad0U\xe6\x89 \x86\xac5\x10R`\x00\x00\u07d4MW\xe7\x16\x87l\f\x95\xef^\xae\xbd5\xc8\xf4\x1b\x06\x9bk\xfe\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Mg\U000ab159\xfe\xf5\xfcA9\x99\xaa\x01\xfd\u007f\xcep\xb4=\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Mn\x8f\xe1\t\xcc\xd2\x15\x8eM\xb1\x14\x13/\xe7_\xec\u023e[\x89\x01[5W\xf1\x93\u007f\x80\x00\xe0\x94Mq\xa6\xeb=\u007f2~\x184'\x8e(\v\x03\x9e\xdd\xd3\x1c/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4M|\xfa\xa8L\xb31\x06\x80\n\x8c\x80/\xb8\xaaF8\x96\u0159\x89a\t=|,m8\x00\x00\u07d4M\x80\x10\x93\xc1\x9c\xa9\xb8\xf3B\xe3<\xc9\xc7{\xbdL\x83\x12\u03c9\x12\xb3\xe7\xfb\x95\u0364\x80\x00\u07d4M\x82\x88\x94u/o%\x17]\xaf!w\tD\x87\x95Ko\x9f\x89O!+\xc2\u011c\x83\x80\x00\xe0\x94M\x82\xd7p\f\x12;\xb9\x19A\x9b\xba\xf0Fy\x9ck\x0e,f\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4M\x83m\x9d;\x0e,\xbdM\xe0PYo\xaaI\f\xff\xb6\r]\x89\x10CV\x1a\x88)0\x00\x00\u07d4M\x86\x97\xaf\x0f\xbf,\xa3n\x87h\xf4\xaf\"\x135phZ`\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4M\x92y\x96 )\xa8\xbdEc\x977\xe9\x8bQ\x1e\xff\aL!\x89Hz\x9a0E9D\x00\x00\u07d4M\x93io\xa2HY\xf5\u0493\x9a\xeb\xfaT\xb4\xb5\x1a\xe1\xdc\u0309\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4M\x9cw\xd0u\f^o\xbc$\u007f/\u05d2thl\xb3S\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4M\xa5\xed\u0188\xb0\xcbb\xe1@=\x17\x00\xd9\u0739\x9f\xfe?\u04c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94M\xa8\x03\ai\x84K\xc3A\x86\xb8\\\xd4\xc74\x88I\xffI\xe9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4M\xb1\xc4:\x0f\x83M}\x04x\xb8\x96\ag\xec\x1a\xc4L\x9a\xeb\x89/Q\x810V'7\x00\x00\u07d4M\xb2\x12\x84\xbc\xd4\xf7\x87\xa7Ue\x00\xd6\xd7\xd8\xf3f#\xcf5\x89i(7Ow\xa3c\x00\x00\u07d4M\xc3\xda\x13\xb2\xb4\xaf\xd4O]\r1\x89\xf4D\xd4\xdd\xf9\x1b\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4M\u013f^u\x89\xc4{(7\x8du\x03\u03d6H\x80a\u06fd\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4M\xc9\u057bK\x19\xce\u0354\xf1\x9e\xc2] \x0e\xa7/%\xd7\xed\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94M\xcd\x11\x81X\x18\xae)\xb8]\x016sI\xa8\xa7\xfb\x12\xd0k\x8a\x01\xacB\x86\x10\x01\x91\xf0\x00\x00\u07d4M\xcfb\xa3\xde?\x06\x1d\xb9\x14\x98\xfda\x06\x0f\x1fc\x98\xffs\x89lj\xccg\u05f1\xd4\x00\x00\u07d4M\xd11\xc7J\x06\x8a7\xc9\n\xde\xd4\xf3\t\xc2@\x9fdx\u04c9\x15\xaf9\u4ab2t\x00\x00\xe0\x94M\u0767Xk\"7\xb0S\xa7\xf3(\x9c\xf4`\xdcW\xd3z\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4M\xe3\xfe4\xa6\xfb\xf64\xc0Q\x99\u007fG\xcc\u007fHy\x1fX$\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4M\xf1@\xbaye\x85\xddT\x891[\xcaK\xbah\n\u06f8\x18\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4N\x02\ay\xb5\xdd\xd3\xdf\"\x8a\x00\xcbH\xc2\xfc\x97\x9d\xa6\xae8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4N\v\xd3$s\xc4\xc5\x1b\xf2VT\xde\xf6\x9fy|k)\xa22\x89V\xc9]\xe8\xe8\xca\x1d\x00\x00\u07d4N\"%\xa1\xbbY\xbc\x88\xa21ft\xd33\xb9\xb0\xaf\xcafU\x89\bg\x0e\x9e\xc6Y\x8c\x00\x00\u07d4N#\x10\x19\x1e\xad\x8d;\xc6H\x98s\xa5\xf0\xc2\xeck\x87\u1f8965\u026d\xc5\u07a0\x00\x00\u07d4N#-S\xb3\u6f8f\x89Sa\xd3\x1c4\xd4v+\x12\xc8.\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4N+\xfaJFo\x82g\x1b\x80\x0e\xeeBj\xd0\f\a\x1b\xa1p\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4N>\xda\u0506M\xabd\xca\xe4\xc5Azvw@S\xdcd2\x89 \b\xfbG\x8c\xbf\xa9\x80\x00\u07d4NC\x18\xf5\xe1>\x82JT\xed\xfe0\xa7\xedO&\xcd=\xa5\x04\x89lk\x93[\x8b\xbd@\x00\x00\u07d4N[w\xf9\x06aY\xe6\x15\x93?-\xdatw\xfaNG\xd6H\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94Nf\x00\x80b\x89EJ\u03630\xa2\xa3U`\x10\u07ec\xad\xe6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Ns\xcf#y\xf1$\x86\x0fs\xd6\xd9\x1b\xf5\x9a\xcc\\\xfc\x84[\x89\x02,\xa3X|\xf4\xeb\x00\x00\xe0\x94Nz\xa6~\x12\x18>\xf9\xd7F\x8e\xa2\x8a\xd29\xc2\xee\xf7\x1bv\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\xe0\x94N{TGM\x01\xfe\xfd8\x8d\xfc\xd5;\x9ff&$A\x8a\x05\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94N\x89.\x80\x81\xbf6\xe4\x88\xfd\xdb;&0\xf3\xf1\xe8\xda0\u048a\x02\x8a\xba0u$Q\xfc\x00\x00\xe0\x94N\x8amcH\x9c\xcc\x10\xa5\u007f\x88_\x96\xeb\x04\xec\xbbT`$\x8a\x03\xea\xe3\x13\x0e\u0316\x90\x00\x00\u07d4N\x8eG\xae;\x1e\xf5\f\x9dT\xa3\x8e\x14 \x8c\x1a\xbd6\x03\u0089y(\xdb\x12vf\f\x00\x00\u0794N\x90\u03312X\xac\xaa\x9fO\xeb\xc0\xa3B\x92\xf9Y\x91\xe20\x88\xdbD\xe0I\xbb,\x00\x00\u07d4N\xa5n\x11\x12d\x1c\x03\x8d\x05e\xa9\u0096\xc4c\xaf\xef\xc1~\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\xe0\x94N\xa7\x0f\x041?\xaee\xc3\xff\"J\x05\\=-\xab(\xdd\u07ca\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4N\xb1EKW8\x05\u022c\xa3~\xde\xc7\x14\x9aA\xf6\x12\x02\xf4\x89\x10CV\x1a\x88)0\x00\x00\u07d4N\xb8{\xa8x\x8e\xba\r\xf8~[\x9b\xd5\n\x8eE6\x80\x91\xc1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4N\xbcV)\xf9\xa6\xa6k,\xf36:\u0109\\\x03H\u8fc7\x8967\tlK\xcci\x00\x00\u07d4N\xc7h)^\xea\xba\xfcB\x95\x84\x15\xe2+\xe2\x16\xcd\xe7v\x18\x89\x03;\x1d\xbc9\xc5H\x00\x00\u07d4N\xcc\x19\x94\x8d\xd9\u0347\xb4\xc7 \x1a\xb4\x8eu\x8f(\xe7\xccv\x89\x1b\x1d\xaba\u04ead\x00\x00\u07d4N\xd1M\x81\xb6\v#\xfb%\x05M\x89%\u07e5s\u072eah\x89\x12nr\xa6\x9aP\xd0\x00\x00\xe0\x94N\xe1<\rA \vF\u045d\xee\\K\xce\xc7\x1d\x82\xbb\x8e8\x8a\x01\xab\xee\x13\u033e\ufbc0\x00\u07d4N\xea\xd4\n\xad\x8cs\xef\b\xfc\x84\xbc\n\x92\xc9\t/j6\xbf\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4N\xeb\xe8\f\xb6\xf3\xaeY\x04\xf6\xf4\xb2\x8d\x90\u007f\x90q\x89\xfc\xab\x89lj\xccg\u05f1\xd4\x00\x00\u07d4N\xeb\xf1 ]\f\xc2\f\xeel\u007f\x8f\xf3\x11_V\u050f\xba&\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4N\xf1\xc2\x14c:\xd9\xc0p;N#t\xa2\xe3>>B\x92\x91\x89Hz\x9a0E9D\x00\x00\u07d4N\xfc\xd9\u01df\xb43L\xa6${\n3\xbd\x9c\xc32\b\xe2r\x89Hz\x9a0E9D\x00\x00\xe0\x94O\x06$k\x8dK\u0496a\xf4>\x93v\"\x01\u0486\x93Z\xb1\x8a\x01\x059O\xfcF6\x11\x00\x00\u07d4O\x15+/\xb8e\x9dCwn\xbb\x1e\x81g:\xa8Ai\xbe\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4O\x17\u007f\x9dV\x95=\xedq\xa5a\x1f93\"\xc3\x02y\x89\\\x89\rU\uf422\xda\x18\x00\x00\u07d4O\x1a-\xa5JLm\xa1\x9d\x14$\x12\xe5n\x81WA\xdb#%\x89\x05k\xc7^-c\x10\x00\x00\u07d4O#\xb6\xb8\x17\xff\xa5\xc6d\xac\xda\u05db\xb7\xb7&\xd3\n\xf0\xf9\x89_h\xe8\x13\x1e\u03c0\x00\x00\xe0\x94O&i\f\x99+z1*\xb1.\x13\x85\xd9J\xcdX(\x8e{\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4O+G\xe2wZ\x1f\xa7\x17\x8d\xad\x92\x98Z[\xbeI;\xa6\u0589\n\u05ce\xbcZ\xc6 \x00\x00\u07d4O:HT\x91\x11E\xea\x01\xc6D\x04K\xdb.Z\x96\n\x98/\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4O?,g0i\xac\x97\xc2\x026\a\x15)\x81\xf5\xcd`c\xa0\x89 \x86\xac5\x10R`\x00\x00\xe0\x94OJ\x9b\xe1\f\xd5\xd3\xfb]\xe4\x8c\x17\xbe)o\x89V\x90d[\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4OR\xadap\xd2[*.\x85\x0e\xad\xbbRA?\xf20>\u007f\x89\xa4\xccy\x95c\u00c0\x00\x00\u07d4OX\x01\xb1\xeb0\xb7\x12\u0620WZ\x9aq\xff\x96]O4\xeb\x89\x10CV\x1a\x88)0\x00\x00\u07d4O]\xf5\xb9CW\u0794\x86\x04\xc5\x1bx\x93\xcd\xdf`v\xba\xad\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\u07d4Od\xa8^\x8e\x9a@I\x8c\fu\xfc\xeb\x037\xfbI\b>^\x8965\u026d\xc5\u07a0\x00\x00\u07d4Og9m%S\xf9\x98x_pN\a\xa69\x19}\u0454\x8d\x89\x10DrR\x1b\xa78\x00\x00\u07d4OmG7\u05e9@8$\x87&H\x86i|\xf7c\u007f\x80\x15\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4Os0\toy\xed&N\xe0\x12\u007f]0\xd2\xf7?!\xcb\u007f\x04\x89\x04\x82\xfe&\f\xbc\xa9\x00\x00\u07d4O\xeeP\xc5\xf9\x88 k\t\xa5sF\x9f\xb1\u0434.\xbbm\u0389l\xee\x06\u077e\x15\xec\x00\x00\u07d4O\xf6v\xe2\u007fh\x1a\x98-\x8f\xd9\xd2\x0ed\x8b=\xce\x05\xe9E\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4O\xf6\u007f\xb8\u007fn\xfb\xa9'\x990\u03fd\x1bz4L\u057a\x8bN\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94PFf\u03891\x17^\x11\xa5\xed\x11\xc1\u072a\x06\xe5\u007fNf\x8a\x02\u007f>\u07f3Nn@\x00\x00\u0794PXM\x92\x06\xa4l\xe1\\0\x11\x17\xee(\xf1\\0\xe6\x0eu\x88\xb9\xf6]\x00\xf6<\x00\x00\xe0\x94PZ3\xa1\x864\xddH\x00i)\x13N\x00\x00\u07d4P\u0286\xb5\xeb\x1d\x01\x87M\xf8\xe5\xf3IE\u051cl\x1a\xb8H\x8965\u026d\xc5\u07a0\x00\x00\u07d4P\u0357\xe97\x8b\\\xf1\x8f\x179c#l\x99Q\xeft8\xa5\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4P\u073c'\xbc\xad\x98@\x93\xa2\x12\xa9\xb4\x17\x8e\xab\xe9\x01ua\x89\a\xe3by\v\\\xa4\x00\x00\u07d4P\xe10#\xbd\x9c\xa9j\xd4\xc5?\xdf\xd4\x10\xcbk\x1fB\v\u07c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94P\xe1\xc8\xec\x98A[\xefD&\x18p\x87\x99C{\x86\xe6\xc2\x05\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4P\xf8\xfaK\xb9\xe2g|\x99\nN\xe8\xcep\xdd\x15#%\x1eO\x89\x01i=#\x16Ok\x00\x00\u07d4P\xfb6\xc2q\a\xee,\xa9\xa3#n'F\u0321\x9a\xcekI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4P\xfe\xf2\x96\x95U\x88\u02aet\xc6.\xc3*#\xa4T\xe0\x9a\xb8\x89A\x1d\xff\xab\xc5\a8\x00\x00\u07d4Q\x02\xa4\xa4 w\xe1\x1cX\xdfGs\u3b14F#\xa6m\x9f\x89lp\x15\xfdR\xed@\x80\x00\u07d4Q\x03\x93w\xee\xd0\xc5s\xf9\x86\xc5\xe8\xa9_\xb9\x9aY\xe93\x0f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Q\x03\xbc\t\x93>\x99!\xfdS\xdcSo\x11\xf0]\rG\x10}\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94Q\x04\xec\xc0\xe30\xdd\x1f\x81\xb5\x8a\xc9\u06f1\xa9\xfb\xf8\x8a<\x85\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4Q\r\x81Y\u0314Wh\xc7E\a\x90\xba\a>\xc0\xd9\xf8\x9e0\x89\x8a\xc7#\x04\x89\xe8\x00\x00\x00\u07d4Q\x0e\xdaV\x01I\x9a\r^\x1a\x00k\xff\xfd\x836r\xf2\xe2g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x12dF\xab=\x802U~\x8e\xbaeY}u\xfa\u0701\\\x89\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\xe0\x94Q\x18U}`\r\x05\xc2\xfc\xbf8\x06\xff\xbd\x93\xd0 %\xd70\x8a\x02g\u04ebd#\xf5\x80\x00\x00\u07d4Q\x1e\x0e\xfb\x04\xacN?\xf2\xe6U\x0eI\x82\x95\xbf\xcdV\xff\u0549$=M\x18\"\x9c\xa2\x00\x00\u07d4Q!\x16\x81{\xa9\xaa\xf8C\xd1P|e\xa5\xead\n{\x9e\xec\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4Q&F\ri,q\u026fo\x05WM\x93\x99\x83h\xa27\x99\x89\x02\u0465\x1c~\x00P\x00\x00\u07d4Q'\u007f\xe7\xc8\x1e\xeb\xd2R\xa0=\xf6\x9ak\x9f2n'\"\a\x89\x03@.y\u02b4L\x80\x00\u07d4Q)oPD'\r\x17pvF\x12\x9c\x86\xaa\xd1d^\xad\xc1\x89H|r\xb3\x10\xd4d\x80\x00\xe0\x94Q+\x91\xbb\xfa\xa9\xe5\x81\xefh?\xc9\r\x9d\xb2*\x8fI\xf4\x8b\x8aA\xa5\"8m\x9b\x95\xc0\x00\x00\u07d4Q5\xfb\x87W`\f\xf4tTbR\xf7M\xc0tm\x06&,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4QF2\xef\xbdd,\x04\xdel\xa3B1]@\u0750\xa2\u06e6\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4QKu\x12\u026e^\xa6<\xbf\x11q[c\xf2\x1e\x18\u0496\xc1\x89lj\xccg\u05f1\xd4\x00\x00\u07d4QS\xa0\xc3\u0211(\x81\xbf\x1c5\x01\xbfd\xb4VI\xe4\x82\"\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94QVQ\xd6\xdbO\xaf\x9e\xcd\x10:\x92\x1b\xbb\xbej\xe9p\xfd\u050a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94Q_0\xbc\x90\xcd\xf4W~\xe4}e\u05c5\xfb\xe2\xe87\u01bc\x8a\x02'\x1b^\x01\x8b\xa0X\x00\x00\u07d4Q`\xeda.\x1bH\xe7??\xc1[\xc42\x1b\x8f#\xb8\xa2K\x89\x1e\x82kB(e\xd8\x00\x00\u07d4Qa\xfdI\xe8G\xf6tU\xf1\u023bz\xbb6\xe9\x85&\r\x03\x89A\rXj \xa4\xc0\x00\x00\u07d4QiT\x02_\xca&\b\xf4}\xa8\x1c!^\xed\xfd\x84J\t\xff\x89\x14\xb5P\xa0\x13\xc78\x00\x00\u07d4Qi\xc6\n\xeeL\xee\u0444\x9a\xb3mfL\xff\x97\x06\x1e\x8e\xa8\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4Q|uC\r\xe4\x01\xc3A\x03&\x86\x11'\x90\xf4mM6\x9e\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4Q|\xd7`\x8e]\r\x83\xa2kq\u007f6\x03\xda\xc2'}\u00e4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x86]\xb1H\x88\x19Q\xf5\x12Qq\x0e\x82\xb9\xbe\r~\xad\xb2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\x89\x1b,\xcd\xd2\xf5\xa4K*\x8b\u011a]\x9b\xcadw%\x1c\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4Q\x8c\xef'\xb1\x05\x82\xb6\xd1OiH=\u06a0\xdd<\x87\xbb\\\x89 \x86\xac5\x10R`\x00\x00\u07d4Q\xa6\xd6'\xf6j\x89#\u060d`\x94\xc4qS\x80\xd3\x05|\xb6\x89>s\xd2z5\x94\x1e\x00\x00\u07d4Q\xa8\xc2\x166\x02\xa3.\xe2L\xf4\xaa\x97\xfd\x9e\xa4\x14QiA\x89\x03h\xf7\xe6\xb8g,\x00\x00\u07d4Q\xb4u\x8e\x9e\x14P\xe7\xafBh\xc3\u01f1\xe7\xbdo\\uP\x8965\u026d\xc5\u07a0\x00\x00\u07d4Q\u028b\xd4\xdcdO\xacG\xafgUc\u0540J\r\xa2\x1e\xeb\x89*\xb7\xb2`\xff?\xd0\x00\x00\u07d4Q\xd2K\xc3so\x88\xddc\xb7\" &\x88f0\xb6\ub1cd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Q\u05cb\x17\x8dp~9n\x87\x10\x96\\OA\xb1\xa1\xd9\x17\x9d\x89\x05\xfe\xe2\"\x04\x1e4\x00\x00\u07d4Q\xe3/\x14\xf4\xca^(|\xda\xc0W\xa7y^\xa9\xe0C\x99S\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Q\xe4?\xe0\xd2\\x(`\xaf\x81\xea\x89\xddy<\x13\xf0\u02f1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4Q\xe7\xb5\\/\x98 \xee\xd78\x846\x1bPf\xa5\x9boE\u0189lk\x93[\x8b\xbd@\x00\x00\xe0\x94Q\xea\x1c\t4\xe3\xd0@\"\ud715\xa0\x87\xa1P\xefp^\x81\x8a\x01Tp\x81\xe7\"M \x00\x00\u07d4Q\xee\f\xca;\xcb\x10\xcd>\x987\"\xce\xd8I=\x92l\bf\x8965f3\xeb\xd8\xea\x00\x00\xe0\x94Q\xf4f:\xb4O\xf7\x93E\xf4'\xa0\xf6\xf8\xa6\u0225?\xf24\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Q\xf5^\xf4~dV\xa4\x18\xab2\xb9\"\x1e\xd2}\xbaf\b\xee\x89\u3bb5sr@\xa0\x00\x00\xe0\x94Q\xf9\xc42\xa4\xe5\x9a\xc8b\x82\u05ad\xabL.\xb8\x91\x91`\xeb\x8ap;[\x89\u00e6\xe7@\x00\x00\u07d4R\x0ff\xa0\xe2e\u007f\xf0\xacA\x95\xf2\xf0d\xcf/\xa4\xb2BP\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4R\x10#T\xa6\xac\xa9]\x8a.\x86\xd5\u07bd\xa6\xdei4`v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\x13\xf4Y\xe0x\xad:\xb9Z\t #\x9f\xcf\x163\xdc\x04\u0289\x8c\xf2\x18|*\xfb\x18\x80\x00\u07d4R\x15\x18;\x8f\x80\xa9\xbc\x03\xd2l\xe9\x12\a\x83*\r9\xe6 \x8965\u026d\xc5\u07a0\x00\x00\xe0\x94R!Cx\xb5@\x04\x05j|\xc0\x8c\x89\x13'y\x8a\u01b2H\x8a\x037\xfe_\xea\xf2\u0440\x00\x00\xe0\x94R##\xaa\xd7\x1d\xbc\x96\xd8Z\xf9\x0f\bK\x99\xc3\xf0\x9d\ucdca\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4R>\x14\r\xc8\x11\xb1\x86\xde\xe5\xd6\u020b\xf6\x8e\x90\xb8\xe0\x96\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R?mdi\x0f\xda\u0354(SY\x1b\xb0\xff \xd3em\x95\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4RO\xb2\x10R,^#\xbbg\u07ff\x8c&\xaaam\xa4\x99U\x8965b\xa6m4#\x80\x00\u07d4RU\xdci\x15ZE\xb9p\xc6\x04\xd3\x00G\xe2\xf50i\x0e\u007f\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4R`\xdcQ\xee\a\xbd\u06ab\xab\xb9\xeetK9<\u007fG\x93\xa6\x89\x01\xd8f_\xa5\xfaL\x00\x00\u07d4Rg\xf4\xd4\x12\x92\xf3p\x86<\x90\u05d3)i\x03\x846%\u01c9K\xe4\xe7&{j\xe0\x00\x00\u07d4Rk\xb53\xb7n \xc8\xee\x1e\xbf\x12?\x1e\x9f\xf4\x14\x8e@\xbe\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4Rl\xb0\x9c\u3b63g.\xec\x1d\xebF [\xe8\x9aKV>\x89\x85\xcaa[\xf9\xc0\x10\x00\x00\u07d4Rs\x8c\x90\xd8`\xe0L\xb1/I\x8d\x96\xfd\xb5\xbf6\xfc4\x0e\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4Rz\x8c\xa1&\x863\xa6\xc99\xc5\xde\x1b\x92\x9a\ue4ae\xac\x8d\x890\xca\x02O\x98{\x90\x00\x00\u07d4R\x81\x01\xceF\xb7 \xa2!M\u036ef\x18\xa51w\xff\xa3w\x89\x1b\x96\x12\xb9\xdc\x01\xae\x00\x00\xe0\x94R\x81s4s\xe0\r\x87\xf1\x1e\x99U\u5275\x9fJ\u008ez\x8a\x8b\xd6/\xf4\xee\xc5Y \x00\x00\u07d4R\x98\xab\x18*\x195\x9f\xfc\xec\xaf\xd7\u0475\xfa!-\xed\xe6\u0749\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4R\x9a\xa0\x02\u0196*:\x85E\x02\u007f\u0630_\"\xb5\xbf\x95d\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4R\x9e\x82O\xa0rX+@2h:\xc7\xee\xcc\x1c\x04\xb4\xca\xc1\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94R\xa5\xe4\xdeC\x93\xee\xcc\xf0X\x1a\xc1\x1bR\u0183\xc7n\xa1]\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4R\xb4%|\xf4\x1bn(\x87\x8dP\xd5{\x99\x91O\xfa\x89\x87:\x89\xd5\r\u026a,Aw\x00\x00\u07d4R\xb8\xa9Y&4\xf70\v|\\Y\xa34[\x83_\x01\xb9\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\xbd\u066fYx\x85\v\xc2A\x10q\x8b7#u\x9bC~Y\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4R\xcd @;\xa7\xed\xa6\xbc0z=c\xb5\x91\x1b\x81|\x12c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794R\u04c0Q\x1d\xf1\x9d^\u0080{\xbc\xb6vX\x1bg\xfd7\xa3\x88\xb9\xf6]\x00\xf6<\x00\x00\xe0\x94R\xe1s\x13P\xf9\x83\xcc,A\x89\x84/\xde\x06\x13\xfa\xd5\f\xe1\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\u07d4R\xe4g\x832\x9av\x93\x01\xb1u\x00\x9d4gh\xf4\xc8~\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4R\xf0X\xd4aG\xe9\x00m)\xbf,\t0J\xd1\xcd\xddn\x15\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4R\xf1T#2<$\xf1\x9a\xe2\xabg7\x17\"\x9d?t}\x9b\x897\xa04\xcb\xe8\xe3\xf3\x80\x00\u07d4R\xf8\xb5\t\xfe\xe1\xa8t\xabo\x9d\x876\u007f\xbe\xaf\x15\xac\x13\u007f\x8965\u026d\xc5\u07a0\x00\x00\u07d4R\xfbF\xac]\x00\xc3Q\x8b,:\x1c\x17}D/\x81eU_\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4S\x00w\xc9\xf7\xb9\a\xff\x9c\xec\fw\xa4\x1ap\xe9\x02\x9a\xddJ\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\x03\x19\xdb\n\x8f\x93\xe5\xbb}M\xbfH\x161O\xbe\xd86\x1b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\x04}\u022c\x90\x83\xd9\x06r\xe8\xb3G<\x10\f\xcd'\x83#\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4S\va\xe4/9Bm$\b\xd4\bR\xb9\xe3J\xb5\xeb\xeb\u0149\x0e~\xeb\xa3A\vt\x00\x00\u07d4S\x0f\xfa\u00fc4\x12\xe2\xec\x0e\xa4{y\x81\xc7p\xf5\xbb/5\x89\a?u\u0460\x85\xba\x00\x00\u07d4S\x17\xec\xb0#\x05,\xa7\xf5e+\xe2\xfa\x85L\xfeEc\xdfM\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4S\x19M\x8a\xfa>\x885\x02v~\xdb\xc3\x05\x86\xaf3\xb1\x14\u04c9lk\x93[\x8b\xbd@\x00\x00\u07d4S*}\xa0\xa5\xadt\aF\x8d;\xe8\xe0~i\xc7\xddd\xe8a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4S-2\xb0\x0f0[\xcc$\xdc\xefV\x81}b/4\xfb,$\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4S4DX@\x82\xeb\xa6T\xe1\xad0\xe1Is\\o{\xa9\"\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4S8\xefp\xea\xc9\u075a\xf5\xa0P;^\xfa\xd1\x03\x9eg\xe7%\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94S9oJ&\u00b4`D\x960lTB\xe7\xfc\xba'.6\x8a\x04?/\b\xd4\x0eZ\xfc\x00\x00\xe0\x94S:s\xa4\xa2\"\x8e\xee\x05\xc4\xff\xd7\x18\xbb\xf3\xf9\xc1\xb1)\xa7\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4S<\x06\x92\x8f\x19\u0429V\xcc(\x86k\xf6\xc8\xd8\xf4\x19\x1a\x94\x89\x0f\xd8\xc1C8\xe60\x00\x00\u07d4S@e6\x1c\xb8T\xfa\xc4+\xfb\\\x9f\xcd\xe0`J\xc9\x19\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4SC\u007f\xec\xf3J\xb9\xd45\xf4\u07b8\xca\x18\x15\x19\xe2Y 5\x89\n1\x06+\xee\xedp\x00\x00\u07d4SR\x01\xa0\xa1\xd74\"\x80\x1fU\xde\xd4\u07ee\xe4\xfb\xaan;\x89\x02&!\x1fy\x15B\x80\x00\xe0\x94S`\x81\x05\xceK\x9e\x11\xf8k\xf4\x97\xff\xca;x\x96{_\x96\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4SnM\x80)\xb7?Uy\u0723>p\xb2N\xba\x89\xe1\x1d~\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Sp\rS%MC\x0f\"x\x1aJv\xa4c\x93;]k\b\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94S\u007f\x9dM1\xefp\x83\x9d\x84\xb0\xd9\u0377+\x9a\xfe\xdb\xdf5\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\xe0\x94S\x81D\x85\x03\xc0\xc7\x02T+\x1d\xe7\xcc_\xb5\xf6\xab\x1c\xf6\xa5\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94S\x94.yI\xd6x\x8b\xb7\x80\xa7\xe8\xa0y'\x81\xb1aK\x84\x8a\x03]\xebFhO\x10\xc8\x00\x00\u07d4S\x95\xa4E]\x95\xd1x\xb4S*\xa4r[\x19?\xfeQ)a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94S\x98\x9e\xd30V?\xd5}\xfe\u027d4<7`\xb0y\x93\x90\x8a\x01P\x89N\x84\x9b9\x00\x00\x00\u07d4S\xa2Dg(\x95H\x0fJ+\x1c\xdf}\xa5\xe5\xa2B\xecM\xbc\x8965\u026d\xc5\u07a0\x00\x00\u07d4S\xa7\x14\xf9\x9f\xa0\x0f\xefu\x8e#\xa2\xe7F2m\xad$|\xa7\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4S\xaf2\xc2/\uf640?\x17\x8c\xf9\v\x80/\xb5q\xc6\x1c\xb9\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4S\xc0\xbb\u007f\u020e\xa4\"\xd2\xef~T\x0e-\x8f(\xb1\xbb\x81\x83\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94S\xc5\xfe\x01\x19\xe1\xe8Hd\f\xee0\xad\ua594\x0f*]\x8b\x8a\x04\x9a\xda_\xa8\xc1\f\x88\x00\x00\u07d4S\xc9\xec\xa4\ts\xf6;\xb5\x92{\xe0\xbcj\x8a\x8b\xe1\x95\x1ft\x89lk\x93[\x8b\xbd@\x00\x00\u07d4S\u0388\xe6lZ\xf2\U0009bf4fY*V\xa3\xd1_ l2\x89\a\xa2\x8c1\xcc6\x04\x00\x00\u07d4S\xce\xc6\u0200\x92\xf7V\xef\xe5o}\xb1\x12(\xa2\xdbE\xb1\"\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4S\xe3[\x12#\x1f\x19\xc3\xfdwL\x88\xfe\xc8\xcb\xee\xdf\x14\b\xb2\x89\x1b\xc1mgN\xc8\x00\x00\x00\u07d4S\xe4\xd9im\xcb?M{?p\u072aN\xec\xb7\x17\x82\xff\\\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4S\xfa\xf1e\xbe\x03\x1e\xc1\x830\xd9\xfc\xe5\xbd\x12\x81\xa1\xaf\b\u06c9\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4T\n\x18\x19\xbd|5\x86\x1ey\x18\x04\xe5\xfb\xb3\xbc\x97\u026b\xb1\x89N\xd7\xda\xc6B0 \x00\x00\xe0\x94T\f\a(\x02\x01N\xf0\xd5a4Z\xecH\x1e\x8e\x11\xcb5p\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94T\f\xf2=\xd9\\MU\x8a'\x9dw\x8d+75\xb3\x16A\x91\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4T\x10`\xfcX\xc7P\xc4\x05\x12\xf83i\xc0\xa63@\xc1\"\xb6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4T\x13\xc9\u007f\xfaJn*{\xba\x89a\u071f\u03850\xa7\x87\u05c965\u026d\xc5\u07a0\x00\x00\u07d4T\x1d\xb2\n\x80\xcf;\x17\xf1b\x1f\x1b?\xf7\x9b\x88/P\xde\xf3\x8965\u026d\xc5\u07a0\x00\x00\u07d4T.\x80\x96\xba\xfb\x88\x16&\x06\x00.\x8c\x8a>\u0458\x14\xae\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T1\v:\xa8\x87\x03\xa7%\u07e5}\xe6\xe6F\x93Qd\x80,\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4T1\xb1\u0447Q\xb9\x8f\xc9\u220a\xc7u\x9f\x155\xa2\xdbG\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T1\xcaB~ae\xa6D\xba\xe3&\xbd\tu\n\x17\x8ce\r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T5\xc6\xc1y3\x17\xd3,\xe1;\xbaLO\xfe\xb9s\xb7\x8a\u0709\r\x8ek\x1c\x12\x85\xef\x00\x00\xe0\x94T6)\xc9\\\xde\xf4(\xad7\xd4S\u02958\xa9\xf9\t\x00\xac\x8a\t(\x96R\x9b\xad\u0708\x00\x00\u07d4T9\x1bM\x17mGl\xea\x16N_\xb55\u0197\x00\xcb%5\x89\x05l\xd5_\xc6M\xfe\x00\x00\xe0\x94T:\x8c\x0e\xfb\x8b\xcd\x15\xc5C\u29a4\xf8\aYv1\xad\xef\x8a\x01?\x80\xe7\xe1O-D\x00\x00\u07d4T?\x8cgN$b\xd8\xd5\u06a0\xe8\x01\x95\xa8p\x8e\x11\xa2\x9e\x89\x03wX\x83;:z\x00\x00\xe0\x94TK[5\x1d\x1b\xc8.\x92\x97C\x99H\xcfHa\xda\u026e\x11\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4TM\xdaB\x1d\xc1\xebs\xbb$\xe3\xe5j$\x80\x13\xb8|\x0fD\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4TW\\1\x14u\x1e\x14o\xfe\u00c7nE\xf2\x0e\xe8AJ\u07ba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4T\xb4B\x9b\x18/\x03w\xbe~bi9\xc5\xdbd@\xf7]z\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4T\xbc\xb8\xe7\xf7<\xda=s\xf4\u04cb-\bG\xe6\x00\xba\r\xf8\x89:pAX\x82\xdf\x18\x00\x00\u07d4T\xc9>\x03\xa9\xb2\xe8\xe4\xc3g(5\xa9\xeev\xf9a[\xc1N\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4T\u0388'YV\xde\xf5\xf9E\x8e;\x95\xde\xca\xcdH@!\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4T\xdb^\x06\xb4\x81]1\xcbV\xa8q\x9b\xa3:\xf2\xd7>rR\x89$R\x1e*0\x17\xb8\x00\x00\xe0\x94T\xe0\x12\x83\u030b8E8\xdddgp\xb3W\xc9`\xd6\xca\u034a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4T\xecs\x00\xb8\x1a\xc8C3\xed\x1b\x03<\xd5\u05e39r\xe24\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4T\xfe\xbc\xce \xfez\x90\x98\xa7U\xbd\x90\x98\x86\x02\xa4\x8c\b\x9e\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4U\n\xad\xae\x12!\xb0z\xfe\xa3\x9f\xba.\xd6.\x05\u5df5\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4U\f0o\x81\xef]\x95\x80\xc0l\xb1\xab \x1b\x95\xc7H\xa6\x91\x89$\x17\xd4\xc4p\xbf\x14\x00\x00\xe0\x94U\x19\x99\xdd\xd2\x05V3'\xb9\xb50xZ\xcf\xf9\xbcs\xa4\xba\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4U\x1ew\x84w\x8e\xf8\xe0H\xe4\x95\xdfI\xf2aO\x84\xa4\xf1\u0709 \x86\xac5\x10R`\x00\x00\xe0\x94U)\x83\na\xc1\xf1<\x19~U\v\xed\xdf\u05bd\x19\\\x9d\x02\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4U)\x87\xf0e\x1b\x91[.\x1eS(\xc1!\x96\rK\xddj\xf4\x89a\t=|,m8\x00\x00\u07d4U;k\x1cW\x05\x0e\x88\xcf\f1\x06{\x8dL\xd1\xff\x80\xcb\t\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4U?7\xd9$fU\x0e\x9f\xd7u\xaet6-\xf00\x17\x912\x89lk\x93[\x8b\xbd@\x00\x00\u07d4UC6\xeeN\xa1U\xf9\xf2O\x87\xbc\xa9\xcar\xe2S\xe1,\u0489\x05k\xc7^-c\x10\x00\x00\u0794UC\xddm\x16\x9e\xec\x8a!;\xbfz\x8a\xf9\xff\xd1]O\xf7Y\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4UG\xfd\xb4\xae\x11\x95>\x01)+x\a\xfa\x92#\xd0\xe4`j\x89\x05]\x11}\xcb\x1d&\x00\x00\u07d4UR\xf4\xb3\xed>\x1d\xa7\x9a/x\xbb\x13\xe8\xaeZh\xa9\xdf;\x8965\u026d\xc5\u07a0\x00\x00\u07d4U\\\xa9\xf0\\\xc14\xabT\xae\x9b\xea\x1c?\xf8z\xa8Q\x98\u0289\x05k\xc7^-c\x10\x00\x00\xe0\x94U]\x8d<\xe1y\x8a\u0290'T\xf1d\xb8\xbe*\x022\x9cl\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4U]\xf1\x93\x90\xc1m\x01)\x87r\xba\xe8\xbc:\x11R\x19\x9c\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4U^\xbe\x84\u06a4+\xa2V\xeax\x91\x05\xce\u0136\x93\xf1/\x18\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94U\u007f^e\xe0\xda3\x99\x82\x19\xadN\x99W\x05E\xb2\xa9\xd5\x11\x8a\x02U\x9c\xbb\x98XB@\x00\x00\u07d4U\x83` h\x83\xdd\x1bmJYc\x9eV)\xd0\xf0\xc6u\u0409lk\x93[\x8b\xbd@\x00\x00\u07d4U\x84B0P\xe3\xc2\x05\x1f\v\xbd\x8fD\xbdm\xbc'\xec\xb6,\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4U\x85)CI)p\xf8\xd6)\xa1Sf\xcd\xda\x06\xa9OE\x13\x89lk\x93[\x8b\xbd@\x00\x00\u0794U\x86d\x86\xec\x16\x8fy\xdb\xe0\u1af1\x88d\u0649\x91\xae,\x88\xdfn\xb0\xb2\xd3\xca\x00\x00\u07d4U\x8cTd\x9a\x8an\x94r+\xd6\xd2\x1d\x14qOqx\x054\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\x91\x940O\x14\xb1\xb9:\xfeDO\x06$\xe0S\xc2:\x00\t\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4U\x93\xc9\u0536ds\x0f\xd9<\xa6\x01Q\xc2\\.\xae\xd9<;\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4U\x97\x06\xc32\xd2\ay\xc4_\x8am\x04ji\x91Y\xb7I!\x89\x14\x9bD.\x85\xa3\u03c0\x00\u07d4U\x98\xb3\xa7\x9aH\xf3+\x1f_\xc9\x15\xb8{d]\x80]\x1a\xfe\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4U\xa3\xdfW\xb7\xaa\xec\x16\xa1b\xfdS\x16\xf3[\xec\b(!\u03c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4U\xa4\xca\xc0\u02cbX-\x9f\xef8\xc5\xc9\xff\xf9\xbdS\t=\x1f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4U\xa6\x1b\x10\x94\x80\xb5\xb2\xc4\xfc\xfd\xef\x92\xd9\x05\x84\x16\f\r5\x89\x02lVM+S\xf6\x00\x00\u07d4U\xaa]1>\xbb\bM\xa0\xe7\x80\x10\x91\u2792\xc5\xde\u00ea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\xab\x99\xb0\xe0\xe5]{\xb8t\xb7\xcf\xe84\xdec\x1c\x97\xec#\x897\xe9\x8c\xe3h\x99\xe4\x00\x00\u07d4U\xaf\t/\x94\xbajy\x91\x8b\f\xf99\xea\xb3\xf0\x1b?Q\u01c9\b \xd5\xe3\x95v\x12\x00\x00\u07d4U\xc5dfAf\xa1\xed\xf3\x91>\x01i\xf1\xcdE\x1f\xdb]\f\x89\x82\x17\xeaIP\x8el\x00\x00\xe0\x94U\xcaj\xbey\xea$\x97\xf4o\u06f804`\x10\xfeF\x9c\xbe\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4U\xca\xffK\xba\x04\xd2 \u0265\xd2\x01\x86r\xec\x85\xe3\x1e\xf8>\x89lk\x93[\x8b\xbd@\x00\x00\u07d4U\xd0W\xbc\xc0K\xd0\xf4\xaf\x96BQ:\xa5\t\v\xb3\xff\x93\xfe\x89;\xfeE,\x8e\xddL\x00\x00\u07d4U\xd4.\xb4\x95\xbfF\xa64\x99{_.\xa3b\x81I\x18\u2c09\x05\xc0\xd2e\xb5\xb2\xa8\x00\x00\u07d4U\u069d\xcd\xcaa\xcb\xfe\x1f\x13<{\xce\xfc\x86{\x9c\x81\"\xf9\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4U\xe2 \x87bb\xc2\x18\xafOVxG\x98\xc7\xe5]\xa0\x9e\x91\x89\a=\x99\xc1VE\xd3\x00\x00\u07d4U\xfd\b\u0440d\xbd ,\x0e\xc3\xd2\xcc\xe0\xce\v\x9d\x16\x9cM\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4V\x00s\nU\xf6\xb2\x0e\xbd$\x81\x1f\xaa=\xe9m\x16b\xab\xab\x89e\xea=\xb7UF`\x00\x00\u07d4V\x03$\x1e\xb8\xf0\x8fr\x1e4\x8c\x9d\x9a\xd9/H\u342a$\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4V\x056yJ\x9e+\x00I\xd1\x023\xc4\x1a\xdc_A\x8a&J\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\aY\x00Y\xa9\xfe\xc1\x88\x11I\xa4K6\x94\x9a\xef\x85\xd5`\x89lk\x93[\x8b\xbd@\x00\x00\u07d4V\v\xec\xdfR\xb7\x1f=\x88'\xd9'a\x0f\x1a\x98\x0f3qo\x89\x17GMp_V\u0400\x00\xe0\x94V\r\xa3~\x95m\x86/\x81\xa7_\u0540\xa7\x13\\\x1b$cR\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94V\x0f\xc0\x8d\a\x9f\x04~\xd8\xd7\xdfuU\x1a\xa55\x01\xf5p\x13\x8a\x01\x9b\xff/\xf5yh\xc0\x00\x00\u07d4V\x1b\xe9)\x9b>k>c\xb7\x9b\t\x16\x9d\x1a\x94\x8a\xe6\xdb\x01\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94V \xe3\xedy-/\x185\xfe_UA}Q\x11F\fj\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4V \xf4m\x14Q\xc25=bC\xa5\u0534'\x13\v\xe2\xd4\a\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94V!\x05\xe8+\t\x975\xdeI\xf6&\x92\u0307\xcd8\xa8\xed\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94V*\x8d\u02fe\xee\xf7\xb3`h]'0;\u059e\tJ\xcc\xf6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4V+\xce\u04ca\xb2\xabl\b\x0f;\x05A\xb8Enp\x82K?\x89\"\xca5\x87\xcfN\xb0\x00\x00\xe0\x94V+\xe9Z\xba\x17\xc57\x1f\u2e82\x87\x99\xb1\xf5]!w\u058a\b\x16\xd3~\x87\xb9\xd1\xe0\x00\x00\u07d4V/\x16\u05da\xbf\xce\u00d4>4\xb2\x0f\x05\xf9{\xdf\u0366\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4V7=\xaa\xb4c\x16\xfd~\x15v\xc6\x1ej\xff\xcbeY\xdd\u05c9\v\xacq]\x14l\x9e\x00\x00\u07d4V9v8\xbb<\xeb\xf1\xf6 byK^\xb9B\xf9\x16\x17\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4V:\x03\xab\x9cV\xb6\x00\xf6\xd2[f\f!\xe1c5Qzu\x8965\u026d\xc5\u07a0\x00\x00\u07d4V<\xb8\x80<\x1d2\xa2['\xb6A\x14\x85+\xd0M\x9c \u0349\v\x14\x9e\xad\n\xd9\xd8\x00\x00\u07d4VXc\x91\x04\fW\xee\xc6\xf5\xaf\xfd\x8c\u052b\xde\x10\xb5\n\u0309\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Vl\x10\xd68\u8e0bG\xd6\xe6\xa4\x14Iz\xfd\xd0\x06\x00\u0509\x05k9Bc\xa4\f\x00\x00\u07d4Vl(\xe3L8\b\xd9vo\xe8B\x1e\xbfO+\x1cO}w\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4V\x8d\xf3\x18Vi\x9b\xb5\xac\xfc\x1f\xe1\u0580\u07d9`\xcaCY\x89J\xcfUR\xf3\xb2I\x80\x00\u07d4V\x91\xdd/gE\xf2\x0e\"\xd2\xe1\u0479U\xaa)\x03\xd6VV\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4V\xa1\xd6\r@\xf5\u007f0\x8e\xeb\xf0\x87\xde\xe3\xb3\u007f\x1e|,\xba\x89>\u072e\xc8-\x06\xf8\x00\x00\u07d4V\xac \xd6;\xd8\x03Y\\\xec\x03m\xa7\xed\x1d\xc6n\n\x9e\a\x89\x03w*S\xcc\xdce\x80\x00\u07d4V\xb6\xc2=\xd2\uc434r\x8f;\xb2\xe7d\xc3\xc5\f\x85\xf1D\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\xdf\x05\xba\xd4l?\x00\xaeGn\xcf\x01{\xb8\xc8w8?\xf1\x89\n\xb1]\xaa\xefp@\x00\x00\u07d4V\xee\x19\u007fK\xbf\x9f\x1b\x06b\xe4\x1c+\xbd\x9a\xa1\xf7\x99\xe8F\x8965\u026d\xc5\u07a0\x00\x00\u07d4V\xf4\x93\xa3\xd1\b\xaa\xa2\u044d\x98\x92/\x8e\xfe\x16b\u03f7=\x89m\x81!\xa1\x94\xd1\x10\x00\x00\u07d4V\xfc\x1a{\xad@G#|\xe1\x16\x14b\x96#\x8e\a\x8f\x93\xad\x89\t\xa6?\b\xeac\x88\x00\x00\u07d4V\xfe\xbf\x9e\x10\x03\xaf\x15\xb1\xbdI\a\xec\b\x9aJ\x1b\x91\xd2h\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4W\x17\u0313\x01Q\x1dJ\x81\xb9\xf5\x83\x14\x8b\xee\xd3\xd3\u0303\t\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4W\x17\xf2\xd8\xf1\x8f\xfc\xc0\xe5\xfe$}:B\x19\x03|:d\x9c\x89\u063beI\xb0+\xb8\x00\x00\u07d4W\x19P\xea,\x90\xc1B}\x93\x9da\xb4\xf2\xdeL\xf1\u03ff\xb0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4W\x19\xf4\x9br\r\xa6\x88V\xf4\xb9\xe7\b\xf2VE\xbd\xbcKA\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4W*\xc1\xab\xa0\xde#\xaeA\xa7\xca\xe1\xdc\bB\u062b\xfc\x10;\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94W-\xd8\xcd?\xe3\x99\xd1\xd0\xec(\x121\xb7\xce\xfc \xb9\u4eca\x023\xc8\xfeBp>\x80\x00\x00\xe0\x94WI!\x83\x8c\xc7}l\x98\xb1}\x90::\xe0\xee\r\xa9[\u040a\vS(\x17\x8a\xd0\xf2\xa0\x00\x00\u07d4WJ\xd95S\x90\u421e\xf4*\xcd\x13\x8b*'\xe7\x8c\x00\xae\x89Tg\xb72\xa9\x134\x00\x00\u07d4WM\xe1\xb3\xf3\x8d\x91XF\xae7\x18VJZ\xda \xc2\xf3\xed\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94W\\\x00\u0081\x82\x10\u0085U\xa0\xff)\x01\x02\x89\xd3\xf8#\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94Ws\xb6\x02g!\xa1\xdd\x04\xb7\x82\x8c\xd6+Y\x1b\xfb4SL\x8a\x05\xb7\xacES\xdez\xe0\x00\x00\xe0\x94WwD\x1c\x83\xe0?\v\xe8\xdd4\v\xdechP\x84|b\v\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4Wx\xff\u071b\x94\u0165\x9e\"N\xb9e\xb6\u0790\xf2\"\xd1p\x89\x12-\u007f\xf3f\x03\xfc\x00\x00\u07d4Wz\xee\xe8\u053c\b\xfc\x97\xab\x15n\xd5\u007f\xb9p\x92Sf\xbe\x89\x12\r\xf1\x14rX\xbf\x00\x00\u07d4W{-\a\xe9\xcfRJ\x18\u04c9\x15Vak\x96\x06g\x00\x00\u07d4W\xd5\xfd\x0e=0I3\x0f\xfc\xdc\xd0 Ei\x17e{\xa2\u0689k\xf2\x01\x95\xf5T\xd4\x00\x00\u07d4W\u0754q\xcb\xfa&'\t\xf5\U00106f37t\xc5\xf5'\xb8\xf8\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4W\xdf#\xbe\xbd\xc6^\xb7_\ub732\xfa\xd1\xc0si++\xaf\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4X\x00\u03410\x83\x9e\x94I]-\x84\x15\xa8\xea,\x90\xe0\xc5\u02c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94X\x03\xe6\x8b4\xda\x12\x1a\xef\b\xb6\x02\xba\u06ef\xb4\xd1$\x81\u028a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\xe0\x94X\x16\xc2hww\xb6\xd7\u04a2C-Y\xa4\x1f\xa0Y\xe3\xa4\x06\x8a\x1cO\xe4:\xdb\n^\x90\x00\x00\u07d4X\x1a:\xf2\x97\xef\xa4Cj)\xaf\x00r\x92\x9a\xbf\x98&\xf5\x8b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\x1b\x9f\xd6\xea\xe3r\xf3P\x1fB\xeb\x96\x19\xee\xc8 \xb7\x8a\x84\x8a\x04+\xe2\xc0\f\xa5;\x8d\x80\x00\u07d4X\x1b\xdf\x1b\xb2v\xdb\u0746\xae\xdc\xdb9z\x01\xef\xc0\xe0\f[\x8965\u026d\xc5\u07a0\x00\x00\u07d4X\x1f4\xb5#\xe5\xb4\x1c\t\xc8|)\x8e)\x9c\xbc\x0e)\xd0f\x89=X3\xaa\xfd9u\x80\x00\xe0\x94X$\xa7\xe2(8'q40\x8c_KP\u06b6^C\xbb1\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4X+pf\x9c\x97\xaa\xb7\u0581H\xd8\xd4\xe9\x04\x11\xe2\x81\rV\x8965f3\xeb\xd8\xea\x00\x00\u07d4X.|\xc4o\x1d{Nn\x9d\x95\x86\x8b\xfd7\x05s\x17\x8fL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4X>\x83\xbaU\xe6~\x13\xe0\xe7o\x83\x92\xd8s\xcd!\xfb\xf7\x98\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Xi\xfb\x86}q\xf18\u007f\x86;i\x8d\t\xfd\xfb\x87\u011b\\\x89\u01bb\xf8X\xb3\x16\b\x00\x00\u07d4X}hI\xb1h\xf6\xc33+z\xba\xe7\xeblB\xc3\u007fH\xbf\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4X\x87\xdcj3\xdf\xedZ\xc1\xed\xef\xe3^\xf9\x1a!b1\xac\x96\x89\r\x8drkqw\xa8\x00\x00\xe0\x94X\x8e\u0650\xa2\xaf\xf4J\x94\x10]X\xc3\x05%w5\xc8h\xac\x8a\x03h\xc8b:\x8bM\x10\x00\x00\u07d4X\xae-\xdc_L\x8a\u0697\xe0l\x00\x86\x17\x17g\xc4#\xf5\u05c9WG=\x05\u06ba\xe8\x00\x00\u07d4X\xae\xd6gJ\xff\xd9\xf6B3'*W\x8d\xd98k\x99\xc2c\x89\xb8Pz\x82\a( \x00\x00\xe0\x94X\xb8\b\xa6[Q\xe63\x89i\xaf\xb9^\xc7\a5\xe4Q\xd5&\x8a\bxK\xc1\xb9\x83z8\x00\x00\u07d4X\xb8\xae\x8fc\xef5\xed\ab\xf0\xb6#=J\xc1Nd\xb6M\x89lk\x93[\x8b\xbd@\x00\x00\u07d4X\xba\x15ie\x0e[\xbb\xb2\x1d5\xd3\xe1u\xc0\u05b0\xc6Q\xa9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4X\xc5U\xbc)<\xdb\x16\xc66.\xd9z\xe9U\v\x92\xea\x18\x0e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4X\xc6P\xce\xd4\v\xb6VA\xb8\xe8\xa9$\xa09\xde\xf4hT\u07c9\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4X\xc9\aT\xd2\xf2\n\x1c\xb1\xdd3\x06%\xe0KE\xfaa\x9d\\\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\xe2\xf1\x12#\xfc\x827\xf6\x9d\x99\xc6(\x9c\x14\x8c\x06\x04\xf7B\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4X\xe5T\xaf=\x87b\x96 \xdaa\xd58\xc7\xf5\xb4\xb5LJ\xfe\x89FP\x9diE4r\x80\x00\u07d4X\xe5\xc9\xe3D\xc8\x06e\r\xac\xfc\x90M3\xed\xbaQ\a\xb0\u0789\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4X\xe6a\u043as\xd6\xcf$\t\x9aUb\xb8\b\xf7\xb3g;h\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94X\xf0[&%`P<\xa7a\xc6\x18\x90\xa4\x03_Lsr\x80\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4X\xfb\x94sd\xe7iWe6\x1e\xbb\x1e\x80\x1f\xfb\x8b\x95\xe6\u0409\n\u05ce\xbcZ\xc6 \x00\x00\u07d4Y\x01\x81\xd4E\x00{\u0407Z\xaf\x06\x1c\x8dQ\x159\x00\x83j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Y\x02\xe4J\xf7i\xa8rF\xa2\x1e\a\x9c\b\xbf6\xb0n\xfe\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4Y\n\xcb\xda7)\f\r>\xc8O\xc2\x00\rv\x97\xf9\xa4\xb1]\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94Y\f\xcbY\x11\xcfx\xf6\xf6\"\xf55\xc4t7_J\x12\xcf\u03ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y\x10\x10m\xeb\u0491\xa1\u0340\xb0\xfb\xbb\x8d\x8d\x9e\x93\xa7\xcc\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Y\x16\x17I\xfe\xdc\xf1\xc7!\xf2 -\x13\xad\xe2\xab\xcfF\v=\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94Y\x1b\xef1q\xd1\u0155w\x17\xa4\xe9\x8d\x17\xeb\x14,!NV\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y <\xc3u\x99\xb6H1*|\xc9\xe0m\xac\xb5\x89\xa9\xaej\x89\b\x0fyq\xb6@\x0e\x80\x00\u07d4Y&\x81q\xb83\xe0\xaa\x13\xc5KR\xcc\xc0B.O\xa0:\ub262\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94Y'w&\x1e;\xd8R\u010e\u0295\xb3\xa4L[\u007f-B,\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4Y0Dg\x0f\xae\xff\x00\xa5[Z\xe0Q\xeb{\xe8p\xb1\x16\x94\x89\a?u\u0460\x85\xba\x00\x00\xe0\x94Y;E\xa1\x86J\xc5\xc7\xe8\xf0\u02ae\xba\r\x87<\xd5\xd1\x13\xb2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Y_^\xdajV\xf1N%\xe0\xc6\xf3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4Z\x1a3ib\xd6\xe0\xc601\u0303\u01a5\u01a6\xf4G\x8e\u02c965\u026d\xc5\u07a0\x00\x00\u07d4Z\x1d--\x1dR\x03\x04\xb6 \x88IW\x047\xeb0\x91\xbb\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4Z&s1\xfa\xcb&-\xaa\xec\xd9\xddc\xa9p\f_RY\u07c9\x05k\xc7^-c\x10\x00\x00\xe0\x94Z(WU9\x1e\x91NX\x02_\xaaH\xcch_O\xd4\xf5\xb8\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4Z)\x16\xb8\xd2\xe8\xcc\x12\xe2\a\xabFMC>#p\xd8#\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4Z+\x1c\x85:\xeb(\xc4U9\xafv\xa0\n\xc2\u0628$(\x96\x89\x01Z\xf1\u05cbX\xc4\x00\x00\u07d4Z-\xaa\xb2\\1\xa6\x1a\x92\xa4\xc8,\x99%\xa1\xd2\xefXX^\x89\f8\r\xa9\u01d5\f\x00\x00\u07d4Z0\xfe\xac7\xac\x9fr\u05f4\xaf\x0f+\xc79R\xc7O\xd5\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4ZTh\xfa\\\xa2&\xc7S.\xcf\x06\xe1\xbc\x1cE\"]~\u0249g\x8a\x93 b\xe4\x18\x00\x00\u07d4ZVR\x857JI\xee\xddPL\x95}Q\bt\xd0\x04U\xbc\x89\x05k\xc7^-c\x10\x00\x00\u07d4Z^\xe8\xe9\xbb\x0e\x8a\xb2\xfe\xcbK3\u0494x\xbeP\xbb\xd4K\x89*\x11)\u0413g \x00\x00\xe0\x94Z_\x85\b\xda\x0e\xbe\xbb\x90\xbe\x903\xbdM\x9e'A\x05\xae\x00\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4Z`q\xbc\xeb\xfc\xbaJ\xb5\u007fM\xb9o\u01e6\x8b\xec\xe2\xba[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z`\xc9$\x16(s\xfc~\xa4\xda\u007f\x97.5\x01g7`1\x89\x04\x87\xf2w\xa8\x85y\x80\x00\u07d4Zf\x86\xb0\xf1~\a\xed\xfcY\xb7Y\xc7}[\xef\x16M8y\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4Zp\x10o \xd6?\x87Re\xe4\x8e\r5\xf0\x0e\x17\xd0+\u0249\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794Zt\xbab\xe7\xc8\x1a4t\xe2}\x89O\xed3\xdd$\xad\x95\xfe\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94Zw5\x00}p\xb0hD\u0699\x01\xcd\xfa\xdb\x11\xa2X,/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4Z\x82\xf9l\u0537\xe2\xd9=\x10\xf3\x18]\xc8\xf4=Ku\xaai\x89lc?\xba\xb9\x8c\x04\x00\x00\u07d4Z\x87\xf04\xe6\xf6\x8fNt\xff\xe6\fd\x81\x946\x03l\xf7\u05c9\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94Z\x89\x11U\xf5\x0eB\aCt\xc79\xba\xad\xf7\xdf&Q\x15:\x8a\x01\x02\xdao\xd0\xf7:<\x00\x00\u07d4Z\x9c\x8bi\xfcaMiVI\x99\xb0\r\xcbB\xdbg\xf9~\x90\x89\xb9\xe6\x15\xab\xad:w\x80\x00\xe0\x94Z\xaf\x1c1%Jn\x00_\xba\u007fZ\xb0\xecy\xd7\xfc+c\x0e\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4Z\xb1\xa5aSH\x00\x1c|w]\xc7WHf\x9b\x8b\xe4\xde\x14\x89%jr\xfb)\xe6\x9c\x00\x00\xe1\x94Z\xbf\xec%\xf7L\u06047c\x1aw1\x90i2wcV\xf9\x8b\t\xd8<\xc0\u07e1\x11w\xff\x80\x00\u07d4Z\u0090\x8b\x0f9\x8c\r\xf5\xba\xc2\xcb\x13\xcas\x14\xfb\xa8\xfa=\x89\n\xd4\xc81j\v\f\x00\x00\xe0\x94Z\u025a\u05c1j\xe9\x02\x0f\xf8\xad\xf7\x9f\xa9\x86\x9b|\xeaf\x01\x8a\x04ri\x8bA;C \x00\x00\u07d4Z\xd1,^\xd4\xfa\x82~!P\u03e0\u058c\n\xa3{\x17i\xb8\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94Z\xd5\xe4 uV\x13\x88o5\xaaV\xac@>\xeb\xdf\xe4\xb0\u040a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4Z\xdew\xfd\x81\xc2\\\n\xf7\x13\xb1\a\x02v\x8c\x1e\xb2\xf9u\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4Z\xe6N\x85;\xa0\xa5\x12\x82\u02cd\xb5.Aa^|\x9fs?\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z\xed\x0el\xfe\x95\xf9\u0580\xc7dr\xa8\x1a+h\n\u007f\x93\xe2\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4Z\xef\x16\xa2&\xddh\a\x1f$\x83\xe1\xdaBY\x83\x19\xf6\x9b,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4Z\xf4j%\xac\t\xcbsakS\xb1O\xb4/\xf0\xa5\x1c\u0772\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4Z\xf7\xc0r\xb2\u016c\xd7\x1cv\xad\xdc\xceS\\\xf7\xf8\xf95\x85\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94Z\xfd\xa9@\\\x8e\x976QEt\u0692\x8d\xe6tV\x01\t\x18\x8a\x01E\xb8\xb0#\x9aF\x92\x00\x00\u07d4[\x06\xd1\xe6\x93\f\x10Ti+y\xe3\xdb\xe6\xec\xceS\x96d \x89\v\"\u007fc\xbe\x81<\x00\x00\u07d4[%\xca\xe8m\xca\xfa*`\xe7r61\xfc_\xa4\x9c\x1a\xd8}\x89\x87\fXQ\x0e\x85 \x00\x00\u07d4[(|~sB\x99\xe7'bo\x93\xfb\x11\x87\xa6\rPW\xfe\x89\x05|\xd94\xa9\x14\xcb\x00\x00\u07d4[)\f\x01\x96|\x81.M\xc4\xc9\v\x17L\x1b@\x15\xba\xe7\x1e\x89\b \xeb4\x8dR\xb9\x00\x00\u07d4[+d\xe9\xc0X\u30a8\xb2\x99\"N\xec\xaa\x16\xe0\x9c\x8d\x92\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\xe0\x94[./\x16\x18U.\xab\r\xb9\x8a\xddUc|)Q\xf1\xfb\x19\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4[0`\x8cg\x8e\x1a\xc4d\xa8\x99L;3\xe5\xcd\xf3Iq\x12\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4[36\x96\xe0L\xca\x16\x92\xe7\x19\x86W\x9c\x92\rk)\x16\xf9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94[C\rw\x96\x96\xa3e?\xc6\x0et\xfb\u02ec\xf6\xb9\u00ba\xf1\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4[Cse\xae:\x9a/\xf9|h\xe6\xf9\nv \x18\x8c}\x19\x89l\x87T\xc8\xf3\f\b\x00\x00\u07d4[I\xaf\xcduDx8\xf6\xe7\xce\u068d!w}O\xc1\xc3\xc0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4[L\f`\xf1\x0e\u0489K\xdbB\xd9\xdd\x1d!\x05\x87\x81\n\r\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4[N\xa1m\xb6\x80\x9b\x03R\u0536\xe8\x1c9\x13\xf7jQ\xbb2\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4[[\xe0\xd8\xc6rv\xba\xab\xd8\xed\xb3\rH\xeaud\v\x8b)\x89,\xb1\xf5_\xb7\xbe\x10\x00\x00\u07d4[]Qp)2\x15b\x11\x1bC\bm\v\x045\x91\x10\x9ap\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\xe0\x94[]\x8c\x8e\xedl\x85\xac!Va\xde\x02fv\x82?\xaa\n\f\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4[mU\xf6q)g@\\e\x91)\xf4\xb1\xde\t\xac\xf2\xcb{\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4[p\u011c\u024b=\xf3\xfb\xe2\xb1Y\u007f\\\x1bcG\xa3\x88\xb7\x894\x95tD\xb8@\xe8\x00\x00\u07d4[sn\xb1\x83Sb\x9b\u0796v\xda\xdd\x16P4\xce^\xcch\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4[u\x9f\xa1\x10\xa3\x1c\x88F\x9fT\xd4K\xa3\x03\xd5}\xd3\xe1\x0f\x89[F\xdd/\x0e\xa3\xb8\x00\x00\u07d4[w\x84\xca\xea\x01y\x9c\xa3\x02'\x82vg\xce |\\\xbcv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4[x\xec\xa2\u007f\xbd\xeao&\xbe\xfb\xa8\x97+)^x\x146K\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94[\x80\v\xfd\x1b>\u0525}\x87Z\xed&\xd4/\x1aw\b\xd7*\x8a\x01Z\x82\xd1\u057b\x88\xe0\x00\x00\u07d4[\x85\xe6\x0e*\xf0TO/\x01\xc6N 2\x90\x0e\xbd8\xa3\u01c9lk\x93[\x8b\xbd@\x00\x00\u07d4[\xa2\xc6\xc3]\xfa\xec)h&Y\x19\x04\xd5DFJ\xea\xbd^\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94[\xafmt\x96 \x80>\x83H\xaf7\x10\xe5\xc4\xfb\xf2\x0f\u0214\x8a\x01\x0f@\x02a]\xfe\x90\x00\x00\u07d4[\xc1\xf9U\a\xb1\x01\x86B\xe4\\\xd9\xc0\xe2'3\xb9\xb1\xa3&\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94[\xd25GG\u007fm\t\u05f2\xa0\x05\xc5\xeee\fQ\fV\u05ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4[\xd2J\xac6\x12\xb2\f`\x9e\xb4gy\xbf\x95i\x84\a\xc5|\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4[\u0586-Q}M\xe4U\x9dN\xec\n\x06\xca\xd0^/\x94n\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4[\xe0EQ*\x02n?\x1c\xeb\xfdZ~\xc0\xcf\xc3o-\xc1k\x89\x06\x81U\xa46v\xe0\x00\x00\xe0\x94[\xf9\xf2\"nZ\xea\xcf\x1d\x80\xae\nY\xc6\xe3\x808\xbc\x8d\xb5\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4[\xfa\xfe\x97\xb1\xdd\x1dq+\xe8mA\xdfy\x89SE\x87Z\x87\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\\\x0f.Q7\x8fk\r{\xabas1X\vn9\xad<\xa5\x8a\x02\bj\xc3Q\x05&\x00\x00\x00\u07d4\\)\xf9\xe9\xa5#\xc1\xf8f\x94H\xb5\\H\xcb\xd4|%\xe6\x10\x894F\xa0\xda\xd0L\xb0\x00\x00\xe0\x94\\0\x8b\xacHW\xd3;\xae\xa0t\xf3\x95m6!\xd9\xfa(\xe1\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\\1*V\u01c4\xb1\"\t\x9bvM\x05\x9c!\xec\xe9^\x84\u0289\x05&c\u032b\x1e\x1c\x00\x00\u07d4\\1\x99m\xca\xc0\x15\xf9\xbe\x98[a\x1fF\x870\xef$M\x90\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\\24W\xe1\x87v\x1a\x82v\xe3Y\xb7\xb7\xaf?;n=\xf6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\\<\x1cd[\x91uC\x11;>l\x1c\x05M\xa1\xfet+\x9a\x89+^:\xf1k\x18\x80\x00\x00\u0794\\=\x19D\x1d\x19l\xb4Cf \xfc\xad\u007f\xbby\xb2\x9ex\x88\xc6s\xce<@\x16\x00\x00\u07d4\\?V\u007f\xaf\xf7\xba\u0475\x12\x00\"\xe8\xcb\u02a8+I\x17\xb3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\\Ch\x91\x8a\xced\t\u01de\u0280\u036a\xe49\x1d+bN\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\\FA\x97y\x1c\x8a=\xa3\xc9%Co'z\xb1;\xf2\xfa\xa2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\\H\x81\x16\\\xb4+\xb8.\x979l\x8e\xf4J\xdb\xf1s\xfb\x99\x89\x05\xfe\xe2\"\x04\x1e4\x00\x00\xe0\x94\\H\x92\x90z\a \xdfo\xd3A>c\xffv}k9\x80#\x8a\x02\xcb\x00\x9f\u04f5y\x0f\x80\x00\u07d4\\O$\xe9\x94\ud3c5\x0e\xa7\x81\x8fG\x1c\x8f\xac;\xcf\x04R\x89]\x80h\x8d\x9e1\xc0\x00\x00\u07d4\\T\x19V\\:\xadNqN\a92\x8e5!\u024f\x05\u0309\x1c\x9fx\u0489>@\x00\x00\u07d4\\a6\xe2\x18\xde\na\xa17\xb2\xb3\x96-*a\x12\xb8\t\u05c9\x0f\xf3\u06f6_\xf4\x86\x80\x00\xe0\x94\\a\xaby\xb4\b\xdd2)\xf6bY7\x05\xd7/\x1e\x14{\xb8\x8a\x04\xd0$=4\x98\u0344\x00\x00\u07d4\\m\x04\x1d\xa7\xafD\x87\xb9\xdcH\xe8\xe1\xf6\af\u0425m\xbc\x89O\a\n\x00>\x9ct\x00\x00\u07d4\\o6\xaf\x90\xab\x1aeln\xc8\xc7\xd5!Q'b\xbb\xa3\xe1\x89lh\xcc\u041b\x02,\x00\x00\u07d4\\{\x9e\u01e2C\x8d\x1eD*\x86\x0f\x8a\x02\x1e\x18\x99\xf07z\xea\x00\x00\u07d4\\\xcc\xf1P\x8b\xfd5\xc2\x050\xaad%\x00\xc1\r\xeee\xea\xed\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\\\xcer\xd0h\xc7\xc3\xf5[\x1d(\x19T^w1|\xae\x82@\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\\\xd0\xe4u\xb5D!\xbd\xfc\f\x12\xea\x8e\b+\u05e5\xaf\nj\x89\x032\xca\x1bg\x94\f\x00\x00\u07d4\\\u0548\xa1N\xc6H\xcc\xf6G)\xf9\x16z\xa7\xbf\x8b\xe6\xeb=\x8965\u026d\xc5\u07a0\x00\x00\u07d4\\\u062f`\xdee\xf2M\xc3\xceW0\xba\x92e0\"\xdcYc\x89a\t=|,m8\x00\x00\u07d4\\\xdcG\b\xf1O@\xdc\xc1Zy_}\xc8\xcb\v\u007f\xaa\x9en\x89\x1d\x1c_>\xda \xc4\x00\x00\u07d4\\\u0d86,\u0391b\xe8~\bI\xe3\x87\xcb]\xf4\xf9\x11\x8c\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\\\xe2\xe7\u03aa\xa1\x8a\xf0\xf8\xaa\xfa\u007f\xba\xd7L\u021e<\xd46\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\\\xe4@h\xb8\xf4\xa3\xfey\x9ej\x83\x11\xdb\xfd\xed\xa2\x9d\xee\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u0794\\\xeb\xe3\v*\x95\xf4\xae\xfd\xa6ee\x1d\xc0\xcf~\xf5u\x81\x99\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\\\xf1\x8f\xa7\u0227\xc0\xa2\xb3\xd5\xef\u0459\x0fd\xdd\xc5i$,\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\\\xf4N\x10T\reqd#\xb1\xbc\xb5B\xd2\x1f\xf8:\x94\u034a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\\\xf8\xc0>\xb3\xe8r\xe5\x0f|\xfd\f/\x8d;?,\xb5\x18:\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\\\xfa\x8dV\x85ue\x8c\xa4\xc1\xa5\x93\xacL]\x0eD\xc6\aE\x89\x0f\xc6o\xae7F\xac\x00\x00\u07d4\\\xfa\x98w\xf7\x19\u01dd\x9eIJ\b\xd1\xe4\x1c\xf1\x03\xfc\x87\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4]\x1d\xc38{G\xb8E\x1eU\x10l\f\xc6}m\xc7+\u007f\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4]#\x1ap\xc1\xdf\xeb6\n\xbd\x97\xf6\x16\xe2\xd1\r9\xf3\u02b5\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4]$\xbd\xbc\x1cG\xf0\xeb\x83\xd1(\xca\xe4\x8a\xc3\xf4\xb5\x02bt\a\xda'/g\x81Jk\xec\u0509\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4]\x83\xb2\x1b\xd2q#`Ckg\xa5\x97\xee3x\xdb>z\xe4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94]\x87+\x12.\x99N\xf2|q\xd7\u07b4W\xbfeB\x9e\xcal\x8a\x01\xb1\xad\xed\x81\u04d4\x10\x80\x00\xe0\x94]\x8d1\xfa\xa8d\xe2!Y\xcdoQu\xcc\xec\xc5?\xa5Mr\x8a\x05\xb6\x96\xb7\r\xd5g\x10\x00\x00\xe0\x94]\x95\x8a\x9b\u0449\u0098_\x86\u014a\x8ci\xa7\xa7\x88\x06\xe8\u068a\x02(\xf1o\x86\x15x`\x00\x00\u07d4]\xa2\xa9\xa4\xc2\xc0\xa4\xa9$\xcb\xe0\xa5:\xb9\xd0\xc6'\xa1\u03e0\x89'\xbf8\xc6TM\xf5\x00\x00\u07d4]\xa4\u0288\x93\\'\xf5\\1\x10H\x84\x0eX\x9e\x04\xa8\xa0I\x89\x04V9\x18$O@\x00\x00\u07d4]\xa5G\x85\u027d0W\\\x89\u07b5\x9d A\xd2\n9\xe1{\x89j\xa2\t\xf0\xb9\x1de\x80\x00\xe0\x94]\xb6\x9f\xe9>o\xb6\xfb\xd4P\x96k\x97#\x8b\x11\n\xd8'\x9a\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4]\xb7\xbb\xa1\xf9W?$\x11]\x8c\x8cb\xe9\u0388\x95\x06\x8e\x9f\x89\x02\xb5\xaa\xd7,e \x00\x00\xe0\x94]\xb8D\x00W\x00i\xa9W<\xab\x04\xb4\u6d955\xe2\x02\xb8\x8a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4]\xc3m\xe55\x94P\xa1\xec\t\xcb\fD\xcf+\xb4+:\xe45\x89<\x94m\x89;3\x06\x00\x00\u07d4]\xc6\xf4_\xef&\xb0n3\x021?\x88M\xafH\xe2to\xb9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94]\u0376\xb8zP\xa9\xde\x02C\x80\x00\x00\u07d4^Q\xb8\xa3\xbb\t\xd3\x03\xea|\x86\x05\x15\x82\xfd`\x0f\xb3\xdc\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794^X\xe2U\xfc\x19\x87\n\x040_\xf2\xa0F1\xf2\xff)K\xb1\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4^ZD\x19t\xa8=t\u0187\xeb\xdcc?\xb1\xa4\x9e{\x1a\u05c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^eE\x8b\xe9d\xaeD\x9fqw7\x04\x97\x97f\xf8\x89\x87a\x89\x1c\xa7\xccs[o|\x00\x00\u07d4^g\u07c9i\x10\x1a\u06bd\x91\xac\xcdk\xb1\x99\x12t\xaf\x8d\xf2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4^n\x97G\xe1b\xf8\xb4\\en\x0fl\xaez\x84\xba\xc8\x0eN\x89lk\x93[\x8b\xbd@\x00\x00\u07d4^s\x1bU\xce\xd4R\xbb??\xe8q\xdd\xc3\xed~\xe6Q\n\x8f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^t\xed\x80\xe9eW\x88\xe1\xbb&\x97R1\x96g\xfeuNZ\x89\x03\t'\xf7L\x9d\xe0\x00\x00\u07d4^w.'\xf2\x88\x00\xc5\r\u0697;\xb3>\x10v.n\xea \x89a\t=|,m8\x00\x00\u07d4^{\x8cT\xdcW\xb0@ bq\x9d\xee~\xf5\xe3~\xa3]b\x89\x9b\xf9\x81\x0f\xd0\\\x84\x00\x00\u07d4^\u007fp7\x87uX\x9f\xc6j\x81\xd3\xf6S\xe9T\xf5U`\ub243\xf2\x89\x18\x1d\x84\xc8\x00\x00\xe0\x94^\x80n\x84W0\xf8\a>l\xc9\x01\x8e\xe9\x0f\\\x05\xf9\t\xa3\x8a\x02\x01\xe9m\xac\u03af \x00\x00\u07d4^\x8eM\xf1\x8c\xf0\xafw\tx\xa8\u07cd\xac\x90\x93\x15\x10\xa6y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4^\x90\xc8Xw\x19\x87V\xb06l\x0e\x17\xb2\x8eR\xb4FPZ\x89\x14JJ\x18\xef\xebh\x00\x00\u07d4^\x95\xfe_\xfc\xf9\x98\xf9\xf9\xac\x0e\x9a\x81\u06b8>\xadw\x00=\x89\x1dB\xc2\r2y\u007f\x00\x00\u07d4^\xad)\x03z\x12\x89dx\xb1)j\xb7\x14\xe9\u02d5B\x8c\x81\x89\x03\xe0C\a-@n\x00\x00\u07d4^\xb3q\xc4\a@lB{;}\xe2q\xad<\x1e\x04&\x95y\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4^\u037a\xea\xb9\x10o\xfe]{Q\x96\x96`\x9a\x05\xba\ub16d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4^\xd0\xd63\x85Y\xefD\xdcza\xed\xeb\x89?\xa5\xd8?\xa1\xb5\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\xe0\x94^\u04fb\xc0R@\xe0\u04d9\xebm\xdf\xe6\x0fb\xdeM\x95\t\xaf\x8a)\x14\xc0$u\xf9\xd6\xd3\x00\x00\u0594^\xd3\xf1\xeb\xe2\xaegV\xb5\xd8\xdc\x19\xca\xd0,A\x9a\xa5w\x8b\x80\u07d4^\xd5a\x15\xbde\x05\xa8\x82s\xdf\\V\x83\x94p\xd2J-\xb7\x89\x03\x8ee\x91\xeeVf\x80\x00\xe0\x94^\xf8\xc9a\x86\xb3y\x84\xcb\xfe\x04\u0158@n;\n\xc3\x17\x1f\x8a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4^\xfb\xdf\xe58\x99\x99c<&`Z[\xfc,\x1b\xb5\x95\x93\x93\x89\x03\xc0W\xc9\\\xd9\b\x00\x00\xe0\x94_\x13\x15F1Fm\xcb\x13S\u0210\x93*|\x97\xe0\x87\x8e\x90\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4_\x16z\xa2B\xbcL\x18\x9a\xde\xcb:\u0127\xc4R\xcf\x19/\u03c9lkLM\xa6\u077e\x00\x00\xe0\x94_\x1c\x8a\x04\xc9\rs[\x8a\x15)\t\xae\xaeco\xb0\xce\x16e\x8a\x01{x'a\x8cZ7\x00\x00\u07d4_#\xba\x1f7\xa9lE\xbcI\x02YS\x8aT\u008b\xa3\xb0\u0549A\rXj \xa4\xc0\x00\x00\u07d4_&\xcf4Y\x9b\xc3n\xa6{\x9ez\x9f\x9bC0\xc9\xd5B\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4_)\xc9\xdev]\xde%\x85*\xf0}3\xf2\xceF\x8f\xd2\t\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_/\a\xd2\u0597\xe8\xc5g\xfc\xfd\xfe\x02\x0fI\xf3`\xbe!9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_2\x1b=\xaa\xa2\x96\xca\xdf)C\x9f\x9d\xab\x06*K\xff\xed\u0589\x04p%\x90>\xa7\xae\x00\x00\u07d4_3:;#\x10vZ\r\x182\xb9\xbeL\n\x03pL\x1c\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4_4K\x01\xc7\x19\x1a2\xd0v*\xc1\x88\xf0\xec-\xd4`\x91\x1d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94_6>\n\xb7G\xe0-\x1b;f\xab\xb6\x9e\xa5<{\xafR:\x8a\x02w\x01s8\xa3\n\xe0\x00\x00\u07d4_7[\x86`\f@\u0328\xb2gkz\x1a\x1d\x16D\xc5\xf5,\x89\x04F\x18\xd7Lb?\x00\x00\u07d4_>\x1eg9\xb0\xc6\"\x00\xe0\n\x006\x91\xd9\xef\xb28\u061f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4_H?\xfb\x8fh\n\xed\xf2\xa3\x8fx3\xaf\xdc\xdeY\xb6\x1eK\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94_J\xceL\x1c\xc13\x91\xe0\x1f\x00\xb1\x98\xe1\xf2\v_\x91\xcb\xf5\x8a\x01\x0f\x0f\xa8\xb9\u04c1\x1a\x00\x00\xe0\x94_R\x12\x82\xe9\xb2x\u070c\x03Lr\xafS\xee)\xe5D=x\x8a\x01as-/\x8f:\xe0\x00\x00\u07d4_h\xa2L~\xb4\x11vgs{39?\xb3\xc2\x14\x8aS\xb6\x89\x02\xce\u0791\x8dE<\x00\x00\u07d4_p\x8e\xaf9\xd8#\x94lQ\xb3\xa3\u9df3\xc0\x03\xe2cA\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4_t.H~:\xb8\x1a\xf2\xf9J\xfd\xbe\x1b\x9b\x8f\\\u0301\xbc\x89u\xc4E\xd4\x11c\xe6\x00\x00\u07d4_t\xed\x0e$\xff\x80\u0672\u0124K\xaa\x99uB\x8c\u05b95\x89\xa1\x8b\xce\xc3H\x88\x10\x00\x00\u07d4_v\xf0\xa3\x06&\x9cx0k=e\r\xc3\xe9\xc3p\x84\xdba\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4_w\xa1\a\xab\x12&\xb3\xf9_\x10\ue0ee\xfcl]\xff>\u0709\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4_{;\xba\xc1m\xab\x83\x1aJ\x0f\xc5;\fT\x9d\xc3l1\u0289i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94_\x93\xff\x83't\xdbQ\x14\xc5[\xb4\xbfD\xcc\U000f53d0?\x8a(\xa9\xc9\x1a&4X)\x00\x00\u07d4_\x96\x16\xc4{Jg\xf4\x06\xb9Z\x14\xfeo\xc2h9o\x17!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4_\x98\x109\xfc\xf5\x02%\xe2\xad\xf7bu!\x12\xd1\xcc&\xb6\xe3\x89\x1b\x1aAj!S\xa5\x00\x00\u07d4_\x99\u070eI\xe6\x1dW\xda\xef`j\xcd\xd9\x1bMp\a2j\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4_\xa6\x1f\x15-\xe6\x125\x16\xc7Q$)y(_yj\u01d1\x89\v\x0f\x11\x97)c\xb0\x00\x00\u07d4_\xa7\xbf\xe0C\x88a'\xd4\x01\x1d\x83V\xa4~\x94yc\xac\xa8\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94_\xa8\xa5Nh\x17lO\xe2\xc0\x1c\xf6q\xc5\x15\xbf\xbd\xd5(\xa8\x8aE\xe1U\xfa\x01\x10\xfa@\x00\x00\u07d4_\xad\x96\x0fk,\x84V\x9c\x9fMG\xbf\x19\x85\xfc\xb2\xc6]\xa6\x8965f3\xeb\xd8\xea\x00\x00\u07d4_\xc6\xc1\x14&\xb4\xa1\xea\xe7\xe5\x1d\xd5\x12\xad\x10\x90\xc6\xf1\xa8[\x89\x93\xfe\\W\xd7\x10h\x00\x00\u07d4_\u0344Th\x96\xdd\b\x1d\xb1\xa3 \xbdM\x8c\x1d\xd1R\x8cL\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4_\u0368G\xaa\xf8\xd7\xfa\x8b\xca\b\x02\x9c\xa2\x84\x91f\xaa\x15\xa3\x89!\u02b8\x12Y\xa3\xbf\x00\x00\u07d4_\xd1\xc3\xe3\x17x'l\xb4.\xa7@\xf5\xea\xe9\xc6A\xdb\xc7\x01\x89\n\x84Jt$\xd9\xc8\x00\x00\u07d4_\xd3\xd6w~\xc2b\n\xe8:\x05R\x8e\xd4%\a-<\xa8\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4_\xd9s\xaf6j\xa5\x15|Te\x9b\u03f2|\xbf\xa5\xac\x15\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4_\xe7w\x03\x80\x8f\x82>l9\x93R\x10\x8b\xdb,R|\xb8|\x89j@v\xcfy\x95\xa0\x00\x00\xe0\x94_\xecI\xc6e\xe6N\xe8\x9d\xd4A\xeet\x05n\x1f\x01\xe9(p\x8a\x01V\x9b\x9es4t\xc0\x00\x00\u07d4_\xf3&\xcd`\xfd\x13k$^)\xe9\bzj\u04e6R\u007f\r\x89e\xea=\xb7UF`\x00\x00\u07d4_\xf9=\xe6\xee\x05L\xadE\x9b-^\xb0\xf6\x87\x03\x89\xdf\xcbt\x89\v\xed\x1d\x02c\xd9\xf0\x00\x00\u07d4`\x06\xe3m\x92\x9b\xf4]\x8f\x16#\x1b\x12j\x01\x1a\xe2\x83\xd9%\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4`!\xe8Z\x88\x14\xfc\xe1\xe8*A\xab\xd1\u04f2\xda\xd2\xfa\xef\xe0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4`8t\n\xe2\x8df\xba\x93\xb0\xbe\bH+2\x05\xa0\xf7\xa0{\x89\x11!a\x85\u009fp\x00\x00\u07d4`?/\xabz\xfbn\x01{\x94v`i\xa4\xb4;8\x96I#\x89Y\xd2\xdb$\x14\u0699\x00\x00\u07d4`B'm\xf2\x98?\xe2\xbcGY\xdc\x19C\xe1\x8f\xdb\xc3Ow\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4`B\xc6D\xba\xe2\xb9o%\xf9M1\xf6x\xc9\r\xc9f\x90\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4`L\xdf\x18b\x8d\xbf\xa82\x91\x94\xd4x\xddR\x01\xee\xccK\xe7\x89\x01?0j$\t\xfc\x00\x00\u07d4`N\x94w\xeb\xf4r|t[\u02bb\xed\xcbl\xcf)\x99@\"\x8966\x9e\xd7t}&\x00\x00\u07d4`gm\x1f\xa2\x1f\xca\x05\"\x97\xe2K\xf9c\x89\u0171*p\u05c9\r\x17|Zzh\xd6\x00\x00\u07d4`gn\x92\u044b\x00\x05\t\xc6\x1d\xe5@\xe6\xc5\u0776v\xd5\t\x89A\rXj \xa4\xc0\x00\x00\u07d4`o\x17q!\xf7\x85\\!\xa5\x06#0\xc8v\"d\xa9{1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4`\x86B6\x93\r\x04\xd8@+]\xcb\xeb\x80\u007f<\xafa\x1e\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4`\xabq\xcd&\xeamnY\xa7\xa0\xf6'\xee\a\x9c\x88^\xbb\xf6\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4`\xaf\x0e\xe1\x18D<\x9b7\xd2\xfe\xadw\xf5\xe5!\u07be\x15s\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4`\xb3X\xcb=\xbe\xfa7\xf4}\xf2\xd76X@\u068e;\u024c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4`\xb8\u05b7;ySO\xb0\x8b\xb8\xcb\xce\xfa\xc7\xf3\x93\xc5{\xfe\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4`\xbeo\x95?*M%\xb6%o\xfd$#\xac\x148%.N\x89\b!\xab\rD\x14\x98\x00\x00\u0794`\xc3qO\xdd\xdbcFY\u48b1\xeaB\xc4r\x8c\u01f8\xba\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4`\xcc=D^\xbd\xf7j}z\xe5q\u0197\x1d\xffh\u0305\x85\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94`\xd5fq@\xd1&\x14\xb2\x1c\x8e^\x8a3\b.2\xdf\xcf#\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4`\xde\"\xa1Pt2\xa4{\x01\xcch\xc5*\v\xf8\xa2\xe0\u0418\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4`\xe0\xbd\u0422Y\xbb\x9c\xb0\x9d?7\xe5\u034b\x9d\xac\uafca\x89JD\x91\xbdm\xcd(\x00\x00\u07d4`\xe3\xccC\xbc\xdb\x02j\xadu\x9cpf\xf5U\xbb\xf2\xacf\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\x04+\x80\xfd`\x95\u0478{\xe2\xf0\x0f\x10\x9f\xab\xaf\xd1W\xa6\x89\x05k\xc7^-c\x10\x00\x00\u07d4a\a\xd7\x1d\xd6\xd0\xee\xfb\x11\xd4\xc9\x16@L\xb9\x8cu>\x11}\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\x0f\xd6\xeeN\xeb\xab\x10\xa8\xc5]\vK\xd2\xe7\xd6\xef\x81qV\x89\x01\x15\x95a\x06]]\x00\x00\u07d4a\x14\xb0\xea\xe5Wi\x03\xf8\v\xfb\x98\x84-$\xed\x92#\u007f\x1e\x89\x05k\xc7^-c\x10\x00\x00\u07d4a!\xaf9\x8a[-\xa6\x9fe\xc68\x1a\xec\x88\u039c\xc6D\x1f\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\u07d4a&g\xf1r\x13[\x95\v,\xd1\xde\x10\xaf\xde\xcehW\xb8s\x8965\u026d\xc5\u07a0\x00\x00\u07d4a,\xed\x8d\xc0\u071e\x89\x9e\xe4oyb33\x15\xf3\xf5^D\x89\x12^5\xf9\xcd=\x9b\x00\x00\u07d4a4\xd9B\xf07\xf2\xcc=BJ#\f`=g\xab\xd3\xed\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a:\xc5;\xe5e\xd4e6\xb8 q[\x9b\x8d:\xe6\x8aK\x95\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\u07d4a?\xabD\xb1k\xbeUMD\xaf\xd1x\xab\x1d\x02\xf3z\ua949lk\x93[\x8b\xbd@\x00\x00\u07d4aN\x8b\xef=\xd2\u015bY\xa4\x14Vt@\x10\x185\x18\x84\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4aQ\x84d\xfd\u0637<\x1b\xb6\xacm\xb6\x00eI8\xdb\xf1z\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4aT}7nSi\xbc\xf9x\xfc\x16,1\xc9\b\"3\xb8%\xd0%\xbe?{\x10V\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4a\x91\xdd\u0276J\x8e\b\x90\xb427\t\u05e0|H\xb9*d\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4a\x96\xc3\xd3\xc0\x90\x8d%Cf\xb7\xbc\xa5WE\"-\x9dM\xb1\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4a\x9f\x17\x14E\xd4+\x02\xe2\xe0p\x04\xad\x8a\xfeiO\xa5=j\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4a\xad\xf5\x92\x9a^)\x81hN\xa2C\xba\xa0\x1f}\x1f^\x14\x8a\x89\x05\xfa\xbfl\x98O#\x00\x00\u07d4a\xb1\xb8\xc0\x12\xcdLx\xf6\x98\xe4p\xf9\x02V\xe6\xa3\x0fH\u0749\n\u05ce\xbcZ\xc6 \x00\x00\u07d4a\xb3\xdf.\x9e\x9f\xd9h\x13\x1f\x1e\x88\xf0\xa0\xeb[\xd7eFM\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4a\xb9\x02\u0166s\x88X&\x82\r\x1f\xe1EI\xe4\x86_\xbd\u0089\x12$\xef\xed*\u1440\x00\u07d4a\xb9\x05\xdef?\xc1s\x86R;:(\xe2\xf7\xd07\xa6U\u0349\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4a\xba\x87\xc7~\x9bYm\xe7\xba\x0e2o\xdd\xfe\xec!c\xeff\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4a\xbf\x84\u056b\x02oX\xc8s\xf8o\xf0\xdf\u0282\xb5W3\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\xc4\xee|\x86LMk^7\xea\x131\xc2\x03s\x9e\x82k/\x89\x01\xa15;8*\x91\x80\x00\u07d4a\xc80\xf1eG\x18\xf0u\u032b\xa3\x16\xfa\xac\xb8[}\x12\v\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4a\xc8\xf1\xfaC\xbf\x84i\x99\xec\xf4{+2M\xfbkc\xfe:\x89+^:\xf1k\x18\x80\x00\x00\u07d4a\xc9\xdc\u8c98\x1c\xb4\x0e\x98\xb0@+\xc3\xeb(4\x8f\x03\xac\x89\n\xac\xac\u0679\xe2+\x00\x00\u07d4a\u03a7\x1f\xa4d\xd6*\a\x06?\x92\v\f\xc9\x17S\x973\u0609Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4a\xd1\x01\xa03\xee\x0e.\xbb1\x00\xed\xe7f\xdf\x1a\xd0$IT\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4a\xedU\x96\u0197 \u007f=U\xb2\xa5\x1a\xa7\xd5\x0f\a\xfa\t\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4a\xff\x8eg\xb3M\x9e\xe6\xf7\x8e\xb3o\xfe\xa1\xb9\xf7\xc1W\x87\xaf\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4b\x05\xc2\xd5dtp\x84\x8a8@\xf3\x88~\x9b\x01]4u\\\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4b(\xad\xe9^\x8b\xb1}\x1a\xe2;\xfb\x05\x18AMI~\x0e\xb8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94b)\xdc\xc2\x03\xb1\xed\xcc\xfd\xf0n\x87\x91\fE*\x1fMzr\x8a\x06\xe1\xd4\x1a\x8f\x9e\xc3P\x00\x00\u0794b+\xe4\xb4T\x95\xfc\xd91C\xef\xc4\x12\u0599\xd6\xcd\xc2=\u0148\xf0\x15\xf2W6B\x00\x00\u07d4b3\x1d\xf2\xa3\xcb\xee5 \xe9\x11\u07a9\xf7>\x90_\x89%\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4bVD\xc9Z\x87>\xf8\xc0l\u06de\x9fm\x8dv\x80\x04=b\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4be\xb2\xe7s\x0f6\xb7v\xb5-\f\x9d\x02\xad\xa5]\x8e<\xb6\x8965\u026d\xc5\u07a0\x00\x00\u07d4bh\n\x15\xf8\u0338\xbd\xc0/s`\xc2Z\xd8\u03f5{\x8c\u034965\u026d\xc5\u07a0\x00\x00\u07d4b\x94\xea\xe6\xe4 \xa3\xd5`\n9\xc4\x14\x1f\x83\x8f\xf8\xe7\xccH\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4b\x97\x1b\xf2cL\xee\v\xe3\u0249\x0fQ\xa5`\x99\u06f9Q\x9b\x89#\x8f\xd4,\\\xf0@\x00\x00\u07d4b\x9b\xe7\xab\x12jS\x98\xed\xd6\u069f\x18D~x\u0192\xa4\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4b\xb4\xa9\"nah\a\x1el\xbea\x11\xfe\xf0\xbcc\x8a\x03\xba\x19\x10\xbf4\x1b\x00\x00\x00\xe0\x94c\n\x91:\x901\xc9I*\xbdLA\u06f1PT\xcf\xecD\x16\x8a\x014X\xdbg\xaf5\xe0\x00\x00\xe0\x94c\fRs\x12mQ|\xe6q\x01\x81\x1c\xab\x16\xb8SL\xf9\xa8\x8a\x01\xfe\xcc\xc6%s\xbb\u04c0\x00\u07d4c\x100\xa5\xb2{\a(\x8aEio\x18\x9e\x11\x14\xf1*\x81\xc0\x89\x1b\x1azB\v\xa0\r\x00\x00\u07d4c\x10\xb0 \xfd\x98\x04IW\x99P\x92\t\x0f\x17\xf0NR\xcd\xfd\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4c+\x91I\xd7\x01x\xa7364'^\x82\u0555?'\x96{\x89%\xf2s\x93=\xb5p\x00\x00\u07d4c,\xec\xb1\f\xfc\xf3\x8e\u0246\xb4;\x87p\xad\xec\xe9 \x02!\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4c1\x02\x8c\xbbZ!H[\xc5\x1bVQB\x99;\xdb%\x82\xa9\x89\x1c\xfd\xd7F\x82\x16\xe8\x00\x00\u07d4c3O\xcf\x17E\x84\x0eK\tJ;\xb4\v\xb7o\x96\x04\xc0L\x89\u05e5\xd7\x03\xa7\x17\xe8\x00\x00\u07d4c4\nWqk\xfac\xebl\xd13r\x12\x02W[\xf7\x96\xf0\x89\va\xe0\xa2\f\x12q\x80\x00\u07d4cN\xfc$7\x11\a\xb4\xcb\xf0?y\xa9=\xfd\x93\xe41\xd5\xfd\x89B5\x82\xe0\x8e\xdc\\\x80\x00\xe0\x94c\\\x00\xfd\xf05\xbc\xa1_\xa3a\r\xf38N\x0f\xb7\x90h\xb1\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4ca.xb\xc2{X|\xfbm\xaf\x99\x12\xcb\x05\x1f\x03\n\x9f\x89\x02[\x19\u053f\xe8\xed\x00\x00\u07d4cfgU\xbdA\xb5\x98i\x97x<\x13\x040\b$+<\xb5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4c{\xe7\x1b:\xa8\x15\xffE=VB\xf70tE\vd\xc8*\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94c}g\xd8\u007fXo\nZG\x9e \xee\x13\xea1\n\x10\xb6G\x8a\n:Y&\xaf\xa1\xe70\x00\x00\u07d4c\u007fXi\xd6\xe4i_\x0e\xb9\xe2s\x11\u0107\x8a\xff33\x80\x89j\xc0Nh\xaa\xec\x86\x00\x00\u07d4c\x97|\xad}\r\xcd\xc5+\x9a\xc9\xf2\xff\xa16\xe8d(\x82\xb8\x89\x04\x10\u0546\xa2\nL\x00\x00\u07d4c\xa6\x1d\xc3\n\x8e;0\xa7c\xc4!<\x80\x1c\xbf\x98s\x81x\x8965\u026d\xc5\u07a0\x00\x00\u07d4c\xacT\\\x99\x12C\xfa\x18\xae\xc4\x1dOoY\x8eUP\x15\u0709 \x86\xac5\x10R`\x00\x00\u07d4c\xb9uMu\xd1-8@9\xeci\x06<\v\xe2\x10\xd5\xe0\u3252\v\x86\f\xc8\xec\xfd\x80\x00\u07d4c\xbbfO\x91\x17\x03v(YM\xa7\xe3\xc5\b\x9f\xd6\x18\xb5\xb5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4c\u00a3\xd25\xe5\xee\xab\xd0\u0526\xaf\u06c9\xd9F'9d\x95\x89CN\xf0[\x9d\x84\x82\x00\x00\u07d4c\xc8\xdf\xde\v\x8e\x01\xda\xdc.t\x8c\x82L\xc06\x9d\U00010cc9\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4c\xd5Z\u065b\x917\xfd\x1b \xcc+O\x03\xd4,\xba\xdd\xf34\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4c\xd8\x00H\x87u\x96\xe0\u0084\x89\xe6P\xcdJ\xc1\x80\tjI\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94c\xe4\x14`>\x80\xd4\xe5\xa0\xf5\xc1\x87t FB%\x82\b\xe4\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94c\xe8\x8e.S\x9f\xfbE\x03\x86\xb4\xe4g\x89\xb2#\xf5GlE\x8a\x01U\x17\nw\x8e%\xd0\x00\x00\u07d4c\xef/\xbc=\xaf^\xda\xf4\xa2\x95b\x9c\xcf1\xbc\xdf@8\xe5\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4c\xf0\xe5\xa7R\xf7\x9fg\x12N\xedc:\xd3\xfd'\x05\xa3\x97\u0509\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94c\xf5\xb5=y\xbf.A\x14\x89Re0\"8E\xfa\xc6\xf6\x01\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4c\xfc\x93\x00\x13\x05\xad\xfb\u0278])\xd9)\x1a\x05\xf8\xf1A\v\x8965\u026d\xc5\u07a0\x00\x00\u0794c\xfek\xccK\x8a\x98P\xab\xbeu\x8070\xc92%\x1f\x14[\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4d\x03\xd0bT\x96\x90\xc8\xe8\xb6>\xaeA\xd6\xc1\tGn%\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4d\x04+\xa6\x8b\x12\xd4\xc1Qe\x1c\xa2\x81;sR\xbdV\xf0\x8e\x89 \x86\xac5\x10R`\x00\x00\u0794d\x05\xdd\x13\xe9:\xbc\xff7~p\x0e<\x1a\x00\x86\xec\xa2})\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94d\n\xbam\xe9\x84\xd9E\x177x\x03p^\xae\xa7\t_J\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4d\v\xf8t\x15\xe0\xcf@s\x01\xe5Y\x9ah6m\xa0\x9b\xba\u0209\x1a\xbc\x9fA`\x98\x15\x80\x00\u07d4d \xf8\xbc\xc8\x16JaR\xa9\x9dk\x99i0\x05\xcc\xf7\xe0S\x8965f3\xeb\xd8\xea\x00\x00\u07d4d$\x1axD)\x0e\n\xb8U\xf1\u052au\xb5SE\x03\"$\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4d&J\xed\xd5-\xca\xe9\x18\xa0\x12\xfb\xcd\f\x03\x0e\xe6\xf7\x18!\x8965\u026d\xc5\u07a0\x00\x00\u07d4d7\x0e\x87 &E\x12Z5\xb2\a\xaf\x121\xfb`r\xf9\xa7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4d=\x9a\xee\u0531\x80\x94~\u04b9 |\xceL=\xdcU\xe1\xf7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4dC\xb8\xaec\x9d\xe9\x1c\xf7\xf0p\xa5G\x03\xb7\x18NH'l\\\x00w\xefK4\x89\x11X\xe4`\x91=\x00\x00\x00\xe0\x94d\xe2\xde! \v\x18\x99\u00e0\xc0e;P@\x13m\r\xc8B\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4d\xec\x8a[t?4y\xe7\a\xda\xe9\xee \u076aO@\xf1\u0649\n\u05ce\xbcZ\xc6 \x00\x00\u07d4e\x03\x86\v\x19\x10\b\xc1U\x83\xbf\u0201X\t\x93\x01v((\x8965\u026d\xc5\u07a0\x00\x00\u07d4e\x051\x911\x9e\x06z%\xe66\x1dG\xf3\u007fc\x18\xf84\x19\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4e\t;#\x9b\xbf\xba#\xc7w\\\xa7\xdaZ\x86H\xa9\xf5L\xf7\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4e\t\xee\xb14~\x84/\xfbA>7\x15^,\xbcs\x82s\xfd\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94e\vBUU\xe4\xe4\xc5\x17\x18\x14h6\xa2\xc1\xeew\xa5\xb4!\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4e\f\xf6}\xb0`\xcc\xe1uh\xd5\xf2\xa4#h|Idv\t\x89\x05k\xc7^-c\x10\x00\x00\u07d4e\x10\xdfB\xa5\x99\xbc\xb0\xa5\x19\u0329a\xb4\x88u\x9aogw\x89lk\x93[\x8b\xbd@\x00\x00\u07d4e6u\xb8B\xd7\u0634a\xf7\"\xb4\x11|\xb8\x1d\xac\x8ec\x9d\x89\x01\xae6\x1f\xc1E\x1c\x00\x00\u07d4eK~\x80\x87\x99\xa8=r\x87\xc6w\x06\xf2\xab\xf4\x9aId\x04\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94eORHG\xb3\xa6\xac\xc0\xd3\xd5\xf1\xf3b\xb6\x03\xed\xf6_\x96\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4eY4\u068etN\xaa=\xe3M\xbb\xc0\x89LN\xda\va\xf2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4e]\\\xd7H\x96)\xe2ANIb?\xabb\xa1~M6\x11\x89\x05\fL\xb2\xa1\f`\x00\x00\u07d4e\xaf\x8d\x8b[\x1d\x1e\xed\xfaw\xbc\xbc\x96\xc1\xb13\xf83\x06\u07c9\x05P\x05\xf0\xc6\x14H\x00\x00\u07d4e\xaf\x90\x87\xe0QgqT\x97\u0265\xa7I\x18\x94\x89\x00M\xef\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u0794e\xb4/\xae\xcc\x1e\u07f1B\x83\u0297\x9a\xf5E\xf6;0\xe6\f\x88\xfc\x93c\x92\x80\x1c\x00\x00\u0794e\xd3>\xb3\x9c\xdadS\xb1\x9ea\xc1\xfeM\xb91p\xef\x9d4\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4e\xd8\xddN%\x1c\xbc\x02\x1f\x05\xb0\x10\xf2\xd5\xdcR\f8r\xe0\x89-CW\x9a6\xa9\x0e\x00\x00\u07d4e\xea&\xea\xbb\xe2\xf6L\xcc\xcf\xe0h)\xc2]F7R\x02%\x89%\xf2s\x93=\xb5p\x00\x00\u07d4e\xeag\xad?\xb5j\xd5\xfb\x948}\u04ce\xb3\x83\x00\x1d|h\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94e\xeb\xae\xd2~\u06dd\xcc\x19W\xae\xe5\xf4R\xac!\x05\xa6\\\x0e\x8a\t7\u07ed\xae%\u26c0\x00\u07d4e\xee \xb0m\x9a\u0549\xa7\xe7\xce\x04\xb9\xf5\xf7\x95\xf4\x02\xae\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4e\xf544m/\xfbx\u007f\xa9\xcf\x18]t[\xa4)\x86\xbdn\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94e\xf5\x87\x0f&\xbc\xe0\x89g}\xfc#\xb5\x00\x1e\xe4\x92H4(\x8a\x01\x12\xb1\xf1U\xaa2\xa3\x00\x00\u07d4e\xfd\x02\xd7\x04\xa1*M\xac\xe9G\x1b\x06E\xf9b\xa8\x96q\u0209\x01\x8d\x1c\xe6\xe4'\u0340\x00\u07d4e\xff\x87O\xaf\xceM\xa3\x18\xd6\xc9=W\xe2\u00ca\rs\xe8 \x8968\x02\x1c\xec\u06b0\x00\x00\xe0\x94f\x05W\xbbC\xf4\xbe:\x1b\x8b\x85\xe7\xdf{<[\xcdT\x80W\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4f\b,u\xa8\xde1\xa59\x13\xbb\xd4M\xe3\xa07O\u007f\xaaA\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4f\x11\xceY\xa9\x8b\a*\xe9Y\xdcI\xadQ\x1d\xaa\xaa\xa1\x9dk\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4f \x1b\xd2'\xaem\u01bd\xfe\xd5\xfb\u0781\x1f\xec\xfe^\x9d\u0649 >\x9e\x84\x92x\x8c\x00\x00\u07d4f#4\x81G$\x93[y1\xdd\xcaa\x00\xe0\rFw'\u0349\"\x88&\x9d\a\x83\xd4\x00\x00\u07d4f'O\xea\x82\xcd0\xb6\u009b#5\x0eOO=1\nX\x99\x89p7\x05P\xab\x82\x98\x00\x00\u07d4f,\xfa\x03\x8f\xab7\xa0\x17E\xa3d\u1e41'\xc5\x03tm\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4f5\xb4oq\x1d-\xa6\xf0\xe1cp\u034e\xe4>\xfb,-R\x89lk\x93[\x8b\xbd@\x00\x00\u07d4f6\x04\xb0P0F\xe6$\xcd&\xa8\xb6\xfbGB\xdc\xe0*o\x89\x03\x8b\x9by~\xf6\x8c\x00\x00\u07d4f6\u05ecczH\xf6\x1d8\xb1L\xfdHe\xd3m\x14(\x05\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4f@\xcc\xf0SU\\\x13\n\xe2\xb6Vd~\xa6\xe3\x167\xb9\xab\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4fBK\xd8x[\x8c\xb4a\x10*\x90\x02\x83\xc3]\xfa\a\xefj\x89\x02.-\xb2ff\xfc\x80\x00\u07d4fL\xd6}\xcc\u026c\x82(\xb4\\U\u06cdvU\ve\x9c\u0709\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4fNC\x11\x98p\xaf\x10zD\x8d\xb1'\x8b\x04H8\xff\u036f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4fQso\xb5\x9b\x91\xfe\xe9\xc9:\xa0\xbdn\xa2\xf7\xb2Pa\x80\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4f[\x00\x0f\vw'P\xcc\x89k\x91\x8a\xacIK\x16\x80\x00\xe0\x94g]\\\xaa`\x9b\xf7\n\x18\xac\xa5\x80F]\x8f\xb71\r\x1b\xbb\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4gc F\u0732ZT\x93i(\xa9oB?3 \xcb\ud489lk\x93[\x8b\xbd@\x00\x00\u07d4ge\xdf%(\x0e\x8eO8\u0531\xcfDo\xc5\xd7\xebe\x9e4\x89\x05k\xc7^-c\x10\x00\x00\u07d4gv\xe13\xd9\xdc5L\x12\xa9Q\b{c\x96P\xf59\xa43\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4g\x85Q<\xf72\xe4~\x87g\ap\xb5A\x9b\xe1\f\xd1\xfct\x89lk\x93[\x8b\xbd@\x00\x00\u07d4g\x947\xea\xcfCxx\xdc)=H\xa3\x9c\x87\xb7B\x1a!l\x89\x03\u007f\x81\x82\x1d\xb2h\x00\x00\u07d4g\x9b\x9a\x10\x990Q~\x89\x99\t\x9c\xcf*\x91LL\x8d\xd94\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4g\xa8\x0e\x01\x90r\x1f\x949\rh\x02r\x9d\xd1,1\xa8\x95\xad\x89lk\x13u\xbc\x91V\x00\x00\u07d4g\xb8\xa6\xe9\x0f\xdf\n\x1c\xacD\x17\x930\x1e\x87P\xa9\xfayW\x890\x84\x9e\xbe\x166\x9c\x00\x00\u07d4g\xbc\x85\xe8}\xc3LN\x80\xaa\xfa\x06k\xa8\u049d\xbb\x8eC\x8e\x89\x15\xd1\xcfAv\xae\xba\x00\x00\u07d4g\xc9&\t>\x9b\x89'\x938\x10\u0642\"\xd6.+\x82\x06\xbb\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4g\xcf\xdanp\xbfvW\u04d0Y\xb5\x97\x90\xe5\x14Z\xfd\xbea\x89#\x05\r\tXfX\x00\x00\u07d4g\u0582\xa2\x82\xefs\xfb\x8dn\x90q\xe2aOG\xab\x1d\x0f^\x8965\u026d\xc5\u07a0\x00\x00\u07d4g\u05a8\xaa\x1b\xf8\xd6\xea\xf78N\x99=\xfd\xf1\x0f\n\xf6\x8aa\x89\n\xbc\xbbW\x18\x97K\x80\x00\u07d4g\u0692.\xff\xa4r\xa6\xb1$\xe8N\xa8\xf8k$\xe0\xf5\x15\xaa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4g\xdf$-$\r\u0538\a\x1dr\xf8\xfc\xf3[\xb3\x80\x9dq\xe8\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4g\xee@n\xa4\xa7\xaej:8\x1e\xb4\xed\xd2\xf0\x9f\x17KI(\x898)c_\th\xb0\x00\x00\u07d4g\xf2\xbbx\xb8\xd3\xe1\x1f|E\x8a\x10\xb5\xc8\xe0\xa1\xd3tF}\x89a\t=|,m8\x00\x00\u07d4g\xfcR}\xce\x17\x85\xf0\xfb\x8b\xc7\xe5\x18\xb1\xc6i\xf7\xec\u07f5\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4h\x02}\x19U\x8e\xd73\x9a\b\xae\xe8\xde5Y\xbe\x06>\xc2\xea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4h\x06@\x83\x8b\xd0zD{\x16\x8dm\x92;\x90\xcflC\xcd\u0289]\u0212\xaa\x111\xc8\x00\x00\u07d4h\a\xdd\u020d\xb4\x89\xb03\xe6\xb2\xf9\xa8\x15SW\x1a\xb3\xc8\x05\x89\x01\x9f\x8euY\x92L\x00\x00\xe0\x94h\rY\x11\xed\x8d\xd9\xee\xc4\\\x06\f\"?\x89\xa7\xf6 \xbb\u054a\x04<3\xc1\x93ud\x80\x00\x00\u07d4h\x11\xb5L\u0456c\xb1\x1b\x94\xda\x1d\xe2D\x82\x85\u035fh\u0649;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4h\x19\f\xa8\x85\xdaB1\x87L\x1c\xfbB\xb1X\n!s\u007f8\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94h(\x97\xbcO\x8e\x89\x02\x91 \xfc\xff\xb7\x87\xc0\x1a\x93\xe6A\x84\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4h)^\x8e\xa5\xaf\xd9\t?\xc0\xa4e\xd1W\x92+]*\xe24\x89\x01\x15NS!}\xdb\x00\x00\u07d4h.\x96'oQ\x8d1\xd7\xe5n0\u07f0\t\xc1!\x82\x01\xbd\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4h5\xc8\xe8\xb7J,\xa2\xae?J\x8d\x0fk\x95J>*\x83\x92\x89\x03B\x9c3]W\xfe\x00\x00\u07d4h63\x01\n\x88hk\xeaZ\x98\xeaS\xe8y\x97\xcb\xf7>i\x89\x05k9Bc\xa4\f\x00\x00\u07d4h=\xba6\xf7\xe9O@\xeaj\xea\ry\xb8\xf5!\xdeU\an\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4hA\x9cm\xd2\xd3\xceo\u02f3\xc7>/\xa0y\xf0`Q\xbd\xe6\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4hG;z}\x96Y\x04\xbe\u06e5V\u07fc\x17\x13l\xd5\xd44\x89\x05k\xc7^-c\x10\x00\x00\u07d4hG\x82[\xde\xe8$\x0e(\x04,\x83\xca\xd6B\U000868fd\u0709QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94hJD\xc0i3\x9d\b\xe1\x9auf\x8b\u06e3\x03\xbe\x85S2\x8a\x0e\u04b5%\x84\x1a\xdf\xc0\x00\x00\u07d4hS\x1fM\u0680\x8fS vz\x03\x114(\xca\f\xe2\xf3\x89\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4hy'\xe3\x04\x8b\xb5\x16*\xe7\xc1\\\xf7k\xd1$\xf9I{\x9e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94h\x80\x9a\xf5\xd52\xa1\x1c\x1aMn2\xaa\xc7\\LR\xb0\x8e\xad\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4h\x86\xad\xa7\xbb\xb0a{\u0684!\x91\u018c\x92.\xa3\xa8\xac\x82\x89>\xe2;\xde\x0e} \x00\x00\xe0\x94h\x88>\x15.V`\xfe\xe5\x96&\xe7\xe3\xb4\xf0Q\x10\xe6\"/\x8a\v\x94c;\xe9u\xa6*\x00\x00\u07d4h\x8aV\x9e\x96U$\xeb\x1d\n\xc3\xd3s>\xab\x90\x9f\xb3\xd6\x1e\x89G\x8e\xae\x0eW\x1b\xa0\x00\x00\xe0\x94h\x8e\xb3\x85;\xbc\xc5\x0e\xcf\xee\x0f\xa8\u007f\n\xb6\x93\u02bd\xef\x02\x8a\x06\xb1\n\x18@\x06G\xc0\x00\x00\u07d4h\xa7B_\xe0\x9e\xb2\x8c\xf8n\xb1y>A\xb2\x11\xe5{\u058d\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4h\xa8l@#\x88\xfd\xdcY\x02\x8f\xecp!\u933f\x83\x0e\xac\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\xe0\x94h\xac\u06a9\xfb\x17\xd3\xc3\t\x91\x1aw\xb0_S\x91\xfa\x03N\xe9\x8a\x01\xe5.3l\xde\"\x18\x00\x00\u07d4h\xad\xdf\x01\x9dk\x9c\xabp\xac\xb1?\v1\x17\x99\x9f\x06.\x12\x89\x02\xb5\x12\x12\xe6\xb7\u0200\x00\u07d4h\xb3\x186\xa3\n\x01j\xda\x15{c\x8a\xc1]\xa7?\x18\xcf\u0789\x01h\u048e?\x00(\x00\x00\xe0\x94h\xb6\x85G\x88\xa7\xc6Il\xdb\xf5\xf8K\x9e\xc5\xef9+x\xbb\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4h\xc0\x84\x90\u021b\xf0\u05b6\xf3 \xb1\xac\xa9\\\x83\x12\xc0\x06\b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4h\xc7\xd1q\x1b\x01\x1a3\xf1o\x1fU\xb5\xc9\x02\xcc\xe9p\xbd\u05c9\b=lz\xabc`\x00\x00\u07d4h\xc8y\x1d\xc3B\xc3sv\x9e\xa6\x1f\xb7\xb5\x10\xf2Q\xd3 \x88\x8965\u026d\xc5\u07a0\x00\x00\u07d4h\u07d4|I[\ubbb8\u8273\xf9S\xd53\x87K\xf1\x06\x89\x1d\x99E\xab+\x03H\x00\x00\u07d4h\xe8\x02'@\xf4\xaf)\xebH\xdb2\xbc\xec\xdd\xfd\x14\x8d=\xe3\x8965\u026d\xc5\u07a0\x00\x00\u07d4h\xecy\u057eqUql@\x94\x1cy\u05cd\x17\u079e\xf8\x03\x89\x1b#8w\xb5 \x8c\x00\x00\u07d4h\xee\xc1\u222c1\xb6\xea\xba~\x1f\xbdO\x04\xadW\x9ak]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4h\xf5%\x92\x1d\xc1\x1c2\x9buO\xbf>R\x9f\xc7#\xc84\u0349WG=\x05\u06ba\xe8\x00\x00\u07d4h\xf7\x19\xae4+\xd7\xfe\xf1\x8a\x05\u02f0/pZ\u04ce\u0572\x898\xeb\xad\\\u0710(\x00\x00\xe0\x94h\xf7W<\xd4W\xe1L\x03\xfe\xa4>0-04|\x10p\\\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4h\xf8\xf4QU\xe9\x8cP)\xa4\xeb\u0175'\xa9.\x9f\xa81 \x89\xf0{D\xb4\a\x93 \x80\x00\u07d4h\xfe\x13W!\x8d\tXI\xcdW\x98B\u012a\x02\xff\x88\x8d\x93\x89lk\x93[\x8b\xbd@\x00\x00\u07d4i\x02(\xe4\xbb\x12\xa8\u0535\u09d7\xb0\xc5\xcf*u\t\x13\x1e\x89e\xea=\xb7UF`\x00\x00\u07d4i\x05\x94\xd3\x06a<\xd3\xe2\xfd$\xbc\xa9\x99J\u064a=s\xf8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94i\a2ir\x9ed\x14\xb2n\xc8\xdc\x0f\xd95\xc7;W\x9f\x1e\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x94i\x19\xdd^]\xfb\x1a\xfa@G\x03\xb9\xfa\xea\x8c\xee5\xd0\rp\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4i4\x92\xa5\xc5\x13\x96\xa4\x82\x88\x16i\xcc\xf6\xd8\xd7y\xf0\tQ\x89\x12\xbfPP:\xe3\x03\x80\x00\u07d4i=\x83\xbe\tE\x9e\xf89\v.0\xd7\xf7\u008d\xe4\xb4(N\x89lk\x93[\x8b\xbd@\x00\x00\u07d4iQp\x83\xe3\x03\xd4\xfb\xb6\xc2\x11E\x14!]i\xbcF\xa2\x99\x89\x05k\xc7^-c\x10\x00\x00\u07d4iUPel\xbf\x90\xb7]\x92\xad\x91\"\xd9\r#\xcah\xcaM\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94iX\xf8;\xb2\xfd\xfb'\xce\x04\t\xcd\x03\xf9\xc5\xed\xbfL\xbe\u074a\x04<3\xc1\x93ud\x80\x00\x00\u0794i[\x0fRBu7\x01\xb2d\xa6pq\xa2\u0708\b6\xb8\u06c8\u3601\x1b\xech\x00\x00\xe0\x94i[L\xce\bXV\xd9\xe1\xf9\xff>y\x94 #5\x9e_\xbc\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94if\x06:\xa5\xde\x1d\xb5\xc6q\xf3\xddi\x9dZ\xbe!>\xe9\x02\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4it\u0224\x14\u03ae\xfd<.M\xfd\xbe\xf40V\x8d\x9a\x96\v\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\xe0\x94iximQP\xa9\xa2cQ?\x8ft\u0196\xf8\xb19|\xab\x8a\x01g\xf4\x82\xd3\u0171\xc0\x00\x00\xe0\x94iy{\xfb\x12\u027e\u0582\xb9\x1f\xbcY5\x91\xd5\xe4\x027(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94i\u007fUSk\xf8Z\xdaQ\x84\x1f\x02\x87b:\x9f\x0e\u041a\x17\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4i\x82\xfe\x8a\x86~\x93\xebJ\v\xd0QX\x93\x99\xf2\xec\x9aR\x92\x89lk\x93[\x8b\xbd@\x00\x00\u07d4i\x8a\x8ao\x01\xf9\xabh/c|yi\xbe\x88_lS\x02\xbf\x89\x01\r:\xa56\xe2\x94\x00\x00\u07d4i\x8a\xb9\xa2\xf33\x81\xe0|\fGC=\r!\xd6\xf36\xb1'\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4i\x94\xfb21\xd7\xe4\x1dI\x1a\x9dh\xd1\xfaL\xae,\xc1Y`\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\x9c\x9e\xe4q\x95Q\x1f5\xf8b\xcaL\"\xfd5\xae\x8f\xfb\xf4\x89\x04V9\x18$O@\x00\x00\u07d4i\x9f\xc6\u058aGuW<\x1d\u036e\xc80\xfe\xfdP9|N\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4i\xaf(\xb0tl\xac\r\xa1p\x84\xb99\x8c^6\xbb:\r\xf2\x896w\x03n\xdf\n\xf6\x00\x00\u07d4i\xb8\x0e\xd9\x0f\x84\x83J\xfa?\xf8.\xb9dp;V\tw\u0589\x01s\x17\x90SM\xf2\x00\x00\xe0\x94i\xb8\x1dY\x81\x14\x1e\u01e7\x14\x10`\xdf\u03cf5\x99\xff\xc6>\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4i\xbc\xfc\x1dC\xb4\xba\x19\xde{'K\xdf\xfb5\x13\x94\x12\xd3\u05c95e\x9e\xf9?\x0f\xc4\x00\x00\u07d4i\xbd%\xad\xe1\xa34lY\xc4\xe90\xdb*\x9dq^\xf0\xa2z\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\xc0\x8dtGT\xdep\x9c\xe9n\x15\xae\r\x1d9[:\"c\x8965\u026d\xc5\u07a0\x00\x00\u07d4i\xc2\xd85\xf1>\xe9\x05\x80@\x8ej2\x83\xc8\u0326\xa44\xa2\x89#\x8f\xd4,\\\xf0@\x00\x00\u07d4i\xc9N\a\u0129\xbe3\x84\xd9]\xfa<\xb9)\x00Q\x87;{\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4i\xcb>!S\x99\x8d\x86\xe5\xee \xc1\xfc\u0466\xba\uec86?\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4i\u04ddQ\b\x89\xe5R\xa3\x96\x13[\xfc\xdb\x06\xe3~8v3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94i\u064f8\xa3\xba=\xbc\x01\xfa\\,\x14'\xd8b\x83//p\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94i\xe2\xe2\xe7\x040|\xcc[\\\xa3\xf1d\xfe\xce.\xa7\xb2\xe5\x12\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4i\xffB\x90t\u02dblc\xbc\x91B\x84\xbc\xe5\xf0\xc8\xfb\xf7\u0409\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4i\xff\x89\x01\xb5Av?\x81|_)\x98\xf0-\xcf\xc1\xdf)\x97\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4j\x02:\xf5}XM\x84^i\x876\xf10\u06dd\xb4\r\xfa\x9a\x89\x05[ \x1c\x89\x00\x98\x00\x00\u07d4j\x04\xf5\xd5?\xc0\xf5\x15\xbe\x94+\x8f\x12\xa9\xcbz\xb0\xf3\x97x\x89\xa9\xaa\xb3E\x9b\xe1\x94\x00\x00\u07d4j\x05\xb2\x1cO\x17\xf9\xd7?_\xb2\xb0\u02c9\xffSV\xa6\xcc~\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\xe0\x94j\x0f\x05`f\xc2\xd5f(\x85\x02s\xd7\xec\xb7\xf8\xe6\xe9\x12\x9e\x8a\x01\x0f\r)<\u01e5\x88\x00\x00\u07d4j\x13\xd5\xe3,\x1f\xd2m~\x91\xffn\x051`\xa8\x9b,\x8a\xad\x89\x02\xe6/ \xa6\x9b\xe4\x00\x00\u07d4j.\x86F\x9a[\xf3|\xee\x82\xe8\x8bL8c\x89](\xfc\xaf\x89\x1c\"\x92f8[\xbc\x00\x00\u07d4j6\x94BL|\u01b8\xbc\u067c\u02baT\f\xc1\xf5\xdf\x18\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94jB\u0297\x1cex\u056d\xe2\x95\xc3\xe7\xf4\xad3\x1d\xd3BN\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4jD\xaf\x96\xb3\xf02\xaed\x1b\xebg\xf4\xb6\xc83B\xd3|]\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4jL\x89\a\xb6\x00$\x80W\xb1\xe4cT\xb1\x9b\u0705\x9c\x99\x1a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4jQNbB\xf6\xb6\x8c\x13~\x97\xfe\xa1\u73b5U\xa7\xe5\xf7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4jS\xd4\x1a\xe4\xa7R\xb2\x1a\xbe\xd57FI\x95:Q=\xe5\xe5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4jaY\aJ\xb5s\xe0\xeeX\x1f\x0f=\xf2\u05a5\x94b\x9bt\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4jc7\x83?\x8fjk\xf1\f\xa7\xec!\xaa\x81\x0e\xd4D\xf4\u02c97\xbd$4\\\xe8\xa4\x00\x00\u07d4jcS\xb9qX\x9f\x18\xf2\x95\\\xba(\xab\xe8\xac\xcejWa\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4jc\xfc\x89\xab\xc7\xf3n(-\x80x{{\x04\xaf\xd6U>q\x89\b\xacr0H\x9e\x80\x00\x00\u07d4jg\x9e7\x8f\xdc\xe6\xbf\xd9\u007f\xe6/\x04)Z$\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4j\x8c\xea-\xe8J\x8d\xf9\x97\xfd?\x84\xe3\b=\x93\xdeW\u0369\x89\x05k\xe0<\xa3\xe4}\x80\x00\xe0\x94j\x97Xt;`>\xea:\xa0RKB\x88\x97#\xc4\x159H\x8a\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4j\xa5s/;\x86\xfb\x8c\x81\xef\xbek[G\xb5cs\v\x06\u020965\u026d\xc5\u07a0\x00\x00\u07d4j\xb3#\xaePV\xed\nE0r\u016b\xe2\xe4/\xcf]q9\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4j\xb5\xb4\xc4\x1c\u0778)i\f/\xda\u007f \xc8^b\x9d\xd5\u0549d\u052fqL2\x90\x00\x00\u07d4j\xc4\x0fS-\xfe\xe5\x11\x81\x17\u04ad5-\xa7}Om\xa2\u0209\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4j\xc4\u053e-\xb0\u065d\xa3\xfa\xaa\xf7RZ\xf2\x82\x05\x1dj\x90\x89\x04X\xcaX\xa9b\xb2\x80\x00\u07d4j\xcd\u0723\xcd+I\x90\xe2\\\xd6\\$\x14\x9d\t\x12\t\x9ey\x89\xa2\xa1\xe0|\x9fl\x90\x80\x00\u07d4j\xd9\v\xe2R\xd9\xcdFM\x99\x81%\xfa\xb6\x93\x06\v\xa8\xe4)\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4j\u0753!\x93\xcd8IJ\xa3\xf0:\xec\xccKz\xb7\xfa\xbc\xa2\x89\x04\xdbs%Gc\x00\x00\x00\xe0\x94j\xe5\u007f'\x91|V*\x13*M\x1b\xf7\xec\n\u01c5\x83)&\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4j\xeb\x9ftt.\xa4\x91\x81=\xbb\xf0\xd6\xfc\xde\x1a\x13\x1dM\xb3\x89\x17\xe5T0\x8a\xa00\x00\x00\u07d4j\xf25\u04bb\xe0P\xe6)\x16\x15\xb7\x1c\xa5\x82\x96X\x81\x01B\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4j\xf6\xc7\xee\x99\xdf'\x1b\xa1[\xf3\x84\xc0\xb7d\xad\xcbM\xa1\x82\x8965f3\xeb\xd8\xea\x00\x00\u07d4j\xf8\xe5Yih,q_H\xadO\xc0\xfb\xb6~\xb5\x97\x95\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4j\xf9@\xf6>\u0278\xd8v'*\u0296\xfe\xf6\\\xda\xce\xcd\ua262\xa1]\tQ\x9b\xe0\x00\x00\u07d4j\xf9\xf0\xdf\uebbb_d\xbf\x91\xabw\x16i\xbf\x05)US\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4j\xff\x14f\xc2b6u\xe3\xcb\x0eu\xe4#\xd3z%\xe4B\xeb\x89]\u0212\xaa\x111\xc8\x00\x00\xe0\x94k\r\xa2Z\xf2g\u05c3l\"k\xca\xe8\xd8r\xd2\xceR\xc9A\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4k\x10\xf8\xf8\xb3\xe3\xb6\r\xe9\n\xa1-\x15_\x9f\xf5\xff\xb2,P\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\x17Y\x8a\x8e\xf5Oyz\xe5\x15\u0336Q}\x18Y\xbf\x80\x11\x89\x05k\xc7^-c\x10\x00\x00\u07d4k \xc0\x80`jy\xc7;\xd8\xe7[\x11qzN\x8d\xb3\xf1\u00c9\x10?sX\x03\xf0\x14\x00\x00\u07d4k\"\x84D\x02!\xce\x16\xa88-\xe5\xff\x02)G\"i\xde\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4k0\xf1\x829\x10\xb8m:\xcbZj\xfc\x9d\xef\xb6\xf3\xa3\v\xf8\x89\u3bb5sr@\xa0\x00\x00\u07d4k8\u0784\x1f\xad\u007fS\xfe\x02\xda\x11[\xd8j\xaff$f\xbd\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4kK\x99\xcb?\xa9\xf7\xb7L\u3903\x17\xb1\xcd\x13\t\n\x1az\x89\x03\x1b2~i]\xe2\x00\x00\u07d4kZ\xe7\xbfx\xecu\xe9\f\xb5\x03\xc7x\xcc\u04f2KO\x1a\xaf\x89+^:\xf1k\x18\x80\x00\x00\u07d4kc\xa2\u07f2\xbc\xd0\xca\xec\x00\"\xb8\x8b\xe3\f\x14Q\xeaV\xaa\x89+\xdbk\xf9\x1f\u007fL\x80\x00\u07d4kew\xf3\x90\x9aMm\xe0\xf4\x11R-Ep8d\x004\\\x89e\xea=\xb7UF`\x00\x00\u07d4kr\xa8\xf0a\xcf\xe6\x99j\xd4G\xd3\xc7,(\xc0\xc0\x8a\xb3\xa7\x89\xe7\x8cj\u01d9\x12b\x00\x00\u07d4kv\rHw\xe6\xa6'\xc1\xc9g\xbe\xe4Q\xa8P}\xdd\u06eb\x891T\xc9r\x9d\x05x\x00\x00\u07d4k\x83\xba\xe7\xb5e$EXU[\xcfK\xa8\xda \x11\x89\x1c\x17\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\x92]\xd5\xd8\xeda2\xabm\b`\xb8,D\xe1\xa5\x1f\x1f\xee\x89P; >\x9f\xba \x00\x00\xe0\x94k\x94a]\xb7Pej\u00cc~\x1c\xf2\x9a\x9d\x13g\u007fN\x15\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4k\x95\x1aC'N\xea\xfc\x8a\t\x03\xb0\xaf.\xc9+\xf1\xef\xc89\x89\x05k\xc7^-c\x10\x00\x00\u07d4k\x99%!\xec\x85#p\x84\x8a\u0597\xcc-\xf6Nc\xcc\x06\xff\x8965\u026d\xc5\u07a0\x00\x00\u07d4k\xa8\xf7\xe2_\xc2\xd8qa\x8e$\xe4\x01\x84\x19\x917\xf9\xf6\xaa\x89\x15\xafd\x86\x9ak\xc2\x00\x00\u07d4k\xa9\xb2\x1b5\x10k\xe1Y\xd1\xc1\xc2ez\xc5l\u049f\xfdD\x89\xf2\xdc}G\xf1V\x00\x00\x00\u07d4k\xafz*\x02\xaex\x80\x1e\x89\x04\xadz\xc0Q\b\xfcV\xcf\xf6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94k\xb2\xac\xa2?\xa1bm\x18\xef\xd6w\u007f\xb9}\xb0-\x8e\n\xe4\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4k\xb4\xa6a\xa3:q\xd4$\u051b\xb5\xdf(b.\xd4\xdf\xfc\xf4\x89\",\x8e\xb3\xfff@\x00\x00\u07d4k\xb5\b\x13\x14j\x9a\xddB\xee\"\x03\x8c\x9f\x1fti\xd4\u007fG\x89\n\xdaUGK\x814\x00\x00\u07d4k\xbc?5\x8af\x8d\u0461\x1f\x03\x80\xf3\xf71\bBj\xbdJ\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4k\xbd\x1eq\x93\x90\xe6\xb9\x10C\xf8\xb6\xb9\u07c9\x8e\xa8\x00\x1b4\x89llO\xa6\xc3\xdaX\x80\x00\u07d4k\xc8Z\xcdY(r.\xf5\tS1\xee\x88\xf4\x84\xb8\u03c3W\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4k\xd3\xe5\x9f#\x9f\xaf\xe4wk\xb9\xbd\xddk\ue0fa]\x9d\x9f\x8965\u026d\xc5\u07a0\x00\x00\u07d4k\xd4W\xad\xe0Qy]\xf3\xf2F\\89\xae\xd3\xc5\xde\xe9x\x8964\xbf9\xab\x98x\x80\x00\u07d4k\xe1c\x13d>\xbc\x91\xff\x9b\xb1\xa2\xe1\x16\xb8T\xea\x93:E\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4k\xe7Y^\xa0\xf0hH\x9a'\x01\xecFI\x15\x8d\xdcC\xe1x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4k\xe9\x03\x0e\xe6\xe2\xfb\u0111\xac\xa3\xde@\"\xd3\x01w+{}\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94k\xec1\x1a\xd0P\b\xb4\xaf5<\x95\x8c@\xbd\x06s\x9a?\xf3\x8a\x03w\xf6*\x0f\nbp\x00\x00\u07d4k\xf7\xb3\xc0e\xf2\xc1\xe7\xc6\xeb\t+\xa0\xd1Pf\xf3\x93\u0478\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4k\xf8o\x1e/+\x802\xa9\\Mw8\xa1\t\xd3\xd0\xed\x81\x04\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4l\x05\xe3N^\xf2\xf4.\u041d\xef\xf1\x02l\xd6k\xcbi`\xbb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4l\b\xa6\xdc\x01s\xc74)U\xd1\xd3\xf2\xc0e\xd6/\x83\xae\u01c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4l\n\xe9\xf0C\xc84\xd4Bq\xf14\x06Y=\xfe\tO8\x9f\x89RD*\xe13\xb6*\x80\x00\u07d4l\f\xc9\x17\xcb\xee}|\t\x97c\xf1Nd\xdf}4\xe2\xbf\t\x89\r\x8drkqw\xa8\x00\x00\xe0\x94l\x0eq/@\\Yr_\xe8)\xe9wK\xf4\xdf\u007fM\xd9e\x8a\f(h\x88\x9c\xa6\x8aD\x00\x00\xe0\x94l\x10\x12\x05\xb3#\xd7uD\xd6\xdcR\xaf7\xac\xa3\xce\xc6\xf7\xf1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4l\x15\xec5 \xbf\x8e\xbb\xc8 \xbd\x0f\xf1\x97x7T\x94\u03dd\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\xe0\x94l\x1d\xdd3\xc8\x19f\u0706!w`q\xa4\x12\x94\x82\xf2\xc6_\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4l%2\u007f\x8d\u02f2\xf4^V\x1e\x86\xe3]\x88P\xe5:\xb0Y\x89;\xcd\xf9\xba\xfe\xf2\xf0\x00\x00\u07d4l.\x9b\xe6\u052bE\x0f\xd1%1\xf3?\x02\x8caFt\xf1\x97\x89\xc2\x12z\xf8X\xdap\x00\x00\u07d4l5\x9eX\xa1=Ex\xa93\x8e3\\g\xe7c\x9f_\xb4\u05c9\v\xd1[\x94\xfc\x8b(\x00\x00\u07d4l=\x18pA&\xaa\x99\xee3B\xce`\xf5\xd4\xc8_\x18g\u0349\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4lGK\xc6jTx\x00f\xaaOQ.\xef\xa7s\xab\xf9\x19\u01c9\x05\x18\x83\x15\xf7v\xb8\x00\x00\u07d4lNBn\x8d\xc0\x05\u07e3Ql\xb8\xa6\x80\xb0.\ua56e\x8e\x89Hz\x9a0E9D\x00\x00\u07d4lR\xcf\b\x95\xbb5\xe6V\x16\x1eM\xc4j\xe0\xe9m\xd3\xe6,\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4lT\"\xfbK\x14\xe6\u064b`\x91\xfd\xecq\xf1\xf0\x86@A\x9d\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4l\\:T\u0367\xc2\xf1\x18\xed\xbaCN\xd8\x1en\xbb\x11\xddz\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4lc\xf8EV\u0490\xbf\u0359\xe44\ue657\xbf\xd7yWz\x89lk\x93[\x8b\xbd@\x00\x00\u07d4lc\xfc\x85\x02\x9a&T\u05db+\xeaM\xe3I\xe4REw\u0149#\xc7W\a+\x8d\xd0\x00\x00\u07d4led\xe5\xc9\xc2N\xaa\xa7D\xc9\xc7\xc9h\xc9\xe2\xc9\xf1\xfb\xae\x89I\x9bB\xa2\x119d\x00\x00\xe0\x94lg\xd6\xdb\x1d\x03Ql\x12\x8b\x8f\xf24\xbf=I\xb2m)A\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4lg\xe0\u05f6.*\bPiE\xa5\xdf\xe3\x82c3\x9f\x1f\"\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4lj\xa0\xd3\vdr\x19\x90\xb9PJ\x86?\xa0\xbf\xb5\xe5}\xa7\x89\x92^\x06\xee\xc9r\xb0\x00\x00\u07d4lqJX\xff\xf6\xe9}\x14\xb8\xa5\xe3\x05\xeb$@eh\x8b\xbd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4l\x80\rKI\xba\a%\x04`\xf9\x93\xb8\xcb\xe0\v&j%S\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4l\x80\x8c\xab\xb8\xff_\xbbc\x12\xd9\xc8\xe8J\xf8\xcf\x12\xef\bu\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94l\x82 )!\x8a\xc8\xe9\x8a&\f\x1e\x06@)4\x889\x87[\x8a\x01\x0f\x97\xb7\x87\xe1\xe3\b\x00\x00\u07d4l\x84\u02e7|m\xb4\xf7\xf9\x0e\xf1=^\xe2\x1e\x8c\xfc\u007f\x83\x14\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94l\x86\x87\xe3Aw\x10\xbb\x8a\x93U\x90!\xa1F\x9ej\x86\xbcw\x8a\x02[-\xa2x\xd9k{\x80\x00\xe0\x94l\x88,'s,\xef\\|\x13\xa6\x86\xf0\xa2\xeawUZ\u0089\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4l\xa5\xde\x00\x81}\xe0\xce\xdc\xe5\xfd\x00\x01(\xde\xde\x12d\x8b<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4l\xa6\xa12\xce\x1c\u0488\xbe\xe3\x0e\xc7\xcf\xef\xfb\x85\xc1\xf5\nT\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94l\xb1\x1e\xcb2\xd3\u0382\x96\x011\x066\xf5\xa1\f\xf7\u03db_\x8a\x04?\u851c8\x01\xf5\x00\x00\u07d4l\xc1\xc8x\xfal\u078a\x9a\v\x83\x11$~t\x1eFB\xfem\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94l\xcb\x03\xac\xf7\xf5<\xe8z\xad\xcc!\xa9\x93-\xe9\x15\xf8\x98\x04\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4l\xd2\x12\xae\xe0N\x01?=*\xba\u04a0#`k\xfb\\j\u01c9lj\xccg\u05f1\xd4\x00\x00\u07d4l\xd2(\xdcq!i0\u007f\xe2|\xebtw\xb4\x8c\xfc\x82r\xe5\x89\x044\xea\x94\u06caP\x00\x00\u07d4l\xe1\xb0\xf6\xad\xc4pQ\xe8\xab8\xb3\x9e\xdbA\x86\xb0;\xab\u0309Ay\x97\x94\xcd$\xcc\x00\x00\u07d4l\xea\xe3s=\x8f\xa4=l\xd8\f\x1a\x96\xe8\xeb\x93\x10\x9c\x83\xb7\x89\x10'\x94\xad \xdah\x00\x00\u07d4m\x05i\xe5U\x8f\xc7\xdf'f\xf2\xba\x15\u070a\xef\xfc[\xebu\x89\xd8\xe6\x00\x1el0+\x00\x00\u07d4m\x12\x0f\f\xaa\xe4O\xd9K\xca\xfeU\xe2\xe2y\uf5ba\\z\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4m\x14V\xff\xf0\x10N\xe8D\xa31G7\x8438\xd2L\xd6l\x89\a\xb0l\xe8\u007f\xddh\x00\x00\u07d4m \xef\x97\x04g\nP\v\xb2i\xb5\x83.\x85\x98\x02\x04\x9f\x01\x89\a\f\x1c\xc7;\x00\xc8\x00\x00\xe0\x94m/\x97g4\xb9\xd0\a\r\x18\x83\xcfz\u02b8\xb3\xe4\x92\x0f\xc1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4m9\xa9\u93c1\xf7i\xd7:\xad,\xea\xd2v\xac\x13\x87\xba\xbe\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4m;x6\xa2\xb9\u0619r\x1aM#{R#\x85\xdc\xe8\xdf\u034966\xc2^f\xec\xe7\x00\x00\u07d4m?+\xa8V\u033b\x027\xfava\x15k\x14\xb0\x13\xf2\x12@\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94m@\b\xb4\xa8\x88\xa8&\xf2H\xeej\v\r\xfd\xe9\xf92\x10\xb9\x8a\x01'\xfc\xb8\xaf\xae \xd0\x00\x00\u07d4m@\xca'\x82m\x97s\x1b>\x86\xef\xfc\u05f9*Aa\xfe\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4mD\x97J1\u0447\xed\xa1m\xddG\xb9\xc7\xecP\x02\xd6\x1f\xbe\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94mK\\\x05\xd0j \x95~\x17H\xabm\xf2\x06\xf3C\xf9/\x01\x8a\x02\x1f6\x06\x99\xbf\x82_\x80\x00\xe0\x94mL\xbf=\x82\x84\x83:\xe9\x93D0>\b\xb4\xd6\x14\xbf\xda;\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4mY\xb2\x1c\xd0\xe2t\x88\x04\u066b\xe0d\xea\u00be\xf0\xc9_'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4mc\u04ce\xe8\xb9\x0e\x0en\xd8\xf1\x92\xed\xa0Q\xb2\u05a5\x8b\xfd\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4mf4\xb5\xb8\xa4\x01\x95\xd9I\x02z\xf4\x82\x88\x02\t,\ued89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94m}\x1c\x94\x95\x11\xf8\x83\x03\x80\x8c`\xc5\xea\x06@\xfc\xc0&\x83\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4m\x84m\xc1&W\xe9\x1a\xf2P\bQ\x9c>\x85\u007fQp}\u0589\xf8\xd3\v\xc9#B\xf8\x00\x00\u07d4m\x91\x93\x99k\x19F\x17!\x11\x06\xd1c^\xb2l\u0136ll\x89\x15\xaa\x1e~\x9d\xd5\x1c\x00\x00\u07d4m\x99\x97P\x98\x82\x02~\xa9G#\x14$\xbe\xde\xde)e\u043a\x89l\x81\u01f3\x11\x95\xe0\x00\x00\u07d4m\xa0\xed\x8f\x1di3\x9f\x05\x9f*\x0e\x02G\x1c\xb4O\xb8\u00fb\x892\xbc8\xbbc\xa8\x16\x00\x00\u07d4m\xb7+\xfdC\xfe\xf4e\xcaV2\xb4Z\xabra@N\x13\xbf\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94m\xbe\x8a\xbf\xa1t(\x06&9\x817\x1b\xf3\xd3U\x90\x80kn\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4m\xc3\xf9+\xaa\x1d!\u06b78+\x892a\xa05o\xa7\xc1\x87\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4m\xc7\x05:q\x86\x16\xcf\u01cb\xeec\x82\xeeQ\xad\xd0\xc7\x030\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94m\xcc~d\xfc\xaf\xcb\xc2\xdcl\x0e^f,\xb3G\xbf\xfc\xd7\x02\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4m\xda_x\x8alh\x8d\u07d2\x1f\xa3\x85.\xb6\xd6\xc6\xc6)f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4m\xdb`\x92w\x9dXB\xea\xd3x\xe2\x1e\x81 \xfdLk\xc12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4m\xdf\xefc\x91U\u06ab\n\\\xb4\x95:\xa8\u016f\xaa\x88\x04S\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4m\xe0/-\xd6~\xfd\xb794\x02\xfa\x9e\xaa\xcb\xcfX\x9d.V\x89@\x13\x8b\x91~\u07f8\x00\x00\u07d4m\u4d418\\\xf7\xfc\x9f\xe8\xc7}\x13\x1f\xe2\xeew$\xc7j\x89})\x97s=\xcc\xe4\x00\x00\u07d4m\xe4\xd1R\x19\x18/\xaf:\xa2\xc5\xd4\xd2Y_\xf20\x91\xa7'\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4m\xed\xf6.t?M,*K\x87\xa7\x87\xf5BJz\xeb9<\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4m\xf2Of\x85\xa6/y\x1b\xa37\xbf?\xf6~\x91\xf3\u053c:\x89ukI\xd4\nH\x18\x00\x00\u07d4m\xf5\xc8O{\x90\x9a\xab>a\xfe\x0e\xcb\x1b;\xf2`\"*\u0489\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4m\xff\x90\xe6\xdc5\x9d%\x90\x88+\x14\x83\xed\xbc\xf8\x87\xc0\xe4#\x8965\u026d\xc5\u07a0\x00\x00\u07d4n\x01\xe4\xadV\x9c\x95\xd0\a\xad\xa3\r^-\xb1(\x88I\"\x94\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\a;f\u0478\xc6gD\u0600\x96\xa8\u0759\xec~\x02(\u0689\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\x0e\xe7\x06\x12\xc9v(}I\x9d\u07e6\xc0\xdc\xc1,\x06\xde\xea\x89\a\v\u0579V!F\x00\x00\xe0\x94n\x12\xb5\x1e\"[JCr\xe5\x9a\u05e2\xa1\xa1>\xa3\u04e17\x8a\x03\x00F\xc8\xccw_\x04\x00\x00\u07d4n\x1a\x04l\xaf[JW\xf4\xfdK\xc1sb!&\xb4\xe2\xfd\x86\x89a\t=|,m8\x00\x00\u07d4n\x1e\xa4\xb1\x83\xe2R\u027bwg\xa0\x06\u05346\x96\u02ca\xe9\x89\x0f\xf3x<\x85\xee\u0400\x00\u07d4n%[p\n\xe7\x13\x8aK\xac\xf2(\x88\xa9\xe2\xc0\n(^\xec\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n'\n\xd5)\xf1\xf0\xb8\xd9\xcbm$'\xec\x1b~-\xc6Jt\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4n.\xab\x85\u0709\xfe)\xdc\n\xa1\x852G\u06b4:R=V\x89\x04V9\x18$O@\x00\x00\u07d4n:Q\xdbt=3M/\xe8\x82$\xb5\xfe|\x00\x8e\x80\xe6$\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4nL*\xb7\xdb\x02i9\xdb\u04fch8J\xf6`\xa6\x18\x16\xb2\x89\t\r\x97/22<\x00\x00\u07d4nM.9\u0203f)\u5d07\xb1\x91\x8af\x9a\xeb\u07556\x8965\u026d\xc5\u07a0\x00\x00\u07d4n\\-\x9b\x1cTj\x86\xee\xfd]\nQ \xc9\xe4\xe70\x19\x0e\x89\n\xd2\x01\xa6yO\xf8\x00\x00\u07d4n`\xae\u19cf\x8e\u068bBLs\xe3S5J\xe6|0B\x89\xbd5\xa4\x8d\x99\x19\xe6\x00\x00\u07d4nd\xe6\x12\x9f\"N7\x8c\x0ensj~z\x06\xc2\x11\xe9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4nm[\xbb\xb9\x05;\x89\xd7D\xa2s\x16\u00a7\xb8\xc0\x9bT}\x891Rq\n\x02>m\x80\x00\u07d4nr\xb2\xa1\x18j\x8e)\x16T;\x1c\xb3jh\x87\x0e\xa5\u0457\x89\n\x15D\xbe\x87\x9e\xa8\x00\x00\u07d4nv\x1e\xaa\x0f4_w{TA\xb7:\x0f\xa5\xb5k\x85\xf2-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4ny\xed\u0504[\anL\u060d\x18\x8bnC-\xd9?5\xaa\x893\xc5I\x901r\f\x00\x00\u07d4n\x82\x12\xb7\"\xaf\xd4\b\xa7\xa7>\xd3\xe29^\xe6EJ\x030\x89\b\x9e\x91y\x94\xf7\x1c\x00\x00\u07d4n\x84\x87m\xbb\x95\xc4\vfV\xe4+\xa9\xae\xa0\x8a\x99;T\u0709;\xbc`\xe3\xb6\u02fe\x00\x00\u07d4n\x84\xc2\xfd\x18\xd8\tW\x14\xa9h\x17\x18\x9c\xa2\x1c\xcab\xba\xb1\x89\x12{lp&!\u0340\x00\u07d4n\x86m\x03-@Z\xbd\xd6\\\xf6QA\x1d\x807\x96\xc2#\x11\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94n\x89\x9eY\xa9\xb4\x1a\xb7\xeaA\xdfu\x17\x86\x0f*\xcbY\xf4\xfd\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4n\x89\xc5\x1e\xa6\xde\x13\xe0l\xdct\x8bg\xc4A\x0f\u9f2b\x03\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\x8a&h\x9fz/\xde\xfd\x00\x9c\xba\xaaS\x10%4P\u06ba\x89o!7\x17\xba\xd8\xd3\x00\x00\u07d4n\x96\xfa\xed\xa3\x05C\x02\xc4_X\xf1a2L\x99\xa3\xee\xbbb\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4n\xb0\xa5\xa9\xae\x96\xd2,\xf0\x1d\x8f\xd6H;\x9f8\xf0\x8c,\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xb3\x81\x96\x17@@X&\x8f\f<\xff5\x96\xbf\xe9\x14\x8c\x1c\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94n\xb5W\x8ak\xb7\xc3!S\x19[\r\x80 \xa6\x91HR\xc0Y\x8a\x8b\u00ab\xf4\x02!\xf4\x80\x00\x00\u07d4n\xbb^iW\xaa\x82\x1e\xf6Y\xb6\x01\x8a9:PL\xaeDP\x89lk\x93[\x8b\xbd@\x00\x00\u07d4n\xbc\xf9\x95\u007f_\xc5\u916d\xd4u\";\x04\xb8\xc1Jz\xed\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4n\xc3e\x95q\xb1\x1f\x88\x9d\xd49\xbc\xd4\xd6u\x10\xa2[\xe5~\x89\x06\xaa\xf7\xc8Qm\f\x00\x00\u07d4n\u021b9\xf9\xf5'jU>\x8d\xa3\x0en\xc1z\xa4~\xef\u01c9\x18BO_\v\x1bN\x00\x00\u07d4n\xc9m\x13\xbd\xb2M\u01e5W)?\x02\x9e\x02\xddt\xb9zU\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xca\xef\xa6\xfc>\xe54bm\xb0,o\x85\xa0\u00d5W\x1ew\x89 \x86\xac5\x10R`\x00\x00\u07d4n\u04a1+\x02\xf8\u0188\u01f5\u04e6\xea\x14\xd66\x87\u06b3\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4n\u0604E\x9f\x80\x9d\xfa\x10\x16\xe7p\xed\xaf>\x9f\xefF\xfa0\x89\xb8R\xd6x \x93\xf1\x00\x00\xe0\x94n\xdf\u007fR\x83r\\\x95>\xe6C\x17\xf6a\x88\xaf\x11\x84\xb03\x8a\x01\xb4d1\x1dE\xa6\x88\x00\x00\u07d4n\xe8\xaa\xd7\xe0\xa0e\u0605-|;\x9an_\xdcK\xf5\f\x00\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4n\xef\u0705\x0e\x87\xb7\x15\xc7'\x91w<\x03\x16\xc3U\x9bX\xa4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4n\xf9\xe8\u0276!}Vv\x9a\xf9}\xbb\x1c\x8e\x1b\x8b\xe7\x99\u0489\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4n\xfb\xa8\xfb*\u0176s\a)\xa9r\xec\"D&\xa2\x87\u00ed\x89\x0fY\x85\xfb\xcb\xe1h\x00\x00\xe0\x94n\xfd\x90\xb55\xe0\v\xbd\x88\x9f\xda~\x9c1\x84\xf8y\xa1Q\u06ca\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4o\x05\x16f\xcbO{\u04b1\x90r!\xb8)\xb5U\u05e3\xdbt\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4o\x0e\xdd#\xbc\xd8_`\x15\xf9(\x9c(\x84\x1f\xe0L\x83\xef\xeb\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4o\x13zq\xa6\xf1\x97\xdf,\xbb\xf0\x10\u073d\x89a\t=|,m8\x00\x00\u07d4p\x10\xbe-\xf5{\u042b\x9a\xe8\x19l\xd5\n\xb0\xc5!\xab\xa9\xf9\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4p#\xc7\tV\xe0J\x92\xd7\x00%\xaa\u0497\xb59\xaf5Xi\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p%\x96]+\x88\xda\x19}DY\xbe=\xc98cD\xcc\x1f1\x89l\xb7\xe7Hg\xd5\xe6\x00\x00\u07d4p(\x02\xf3m\x00%\x0f\xabS\xad\xbc\u0596\xf0\x17oc\x8aI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4pH\x19\xd2\xe4Mn\xd1\xda%\xbf\u0384\u011f\u0322V\x13\xe5\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4pJn\xb4\x1b\xa3O\x13\xad\xdd\xe7\xd2\xdb}\xf0I\x15\u01e2!\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4pJ\xb1\x15\r^\x10\xf5\xe3I\x95\b\xf0\xbfpe\x0f\x02\x8dK\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4pJ\xe2\x1dv-n\x1d\xde(\xc25\xd11\x04Yr6\xdb\x1a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4pM$<)x\xe4l,\x86\xad\xbe\xcd$n;)_\xf63\x89m\x12\x1b\xeb\xf7\x95\xf0\x00\x00\u07d4pM]\xe4\x84m9\xb5<\xd2\x1d\x1cI\xf0\x96\xdb\\\x19\xba)\x89\b=lz\xabc`\x00\x00\u07d4p]\xdd85T\x82\xb8\xc7\u04f5\x15\xbd\xa1P\r\xd7\u05e8\x17\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94pan(\x92\xfa&\x97\x05\xb2\x04k\x8f\xe3\xe7/\xa5X\x16\u04ca\x04<3\xc1\x93ud\x80\x00\x00\u07d4pg\x0f\xbb\x05\xd30\x14DK\x8d\x1e\x8ew\x00%\x8b\x8c\xaam\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\x81\xfak\xaa\xd6\u03f7\xf5\x1b,\xca\x16\xfb\x89p\x99\x1ad\xba\x89\f\xae\xc0\x05\xf6\xc0\xf6\x80\x00\xe0\x94p\x85\xae~~M\x93!\x97\xb5\u01c5\x8c\x00\xa3gF&\xb7\xa5\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4p\x86\xb4\xbd\xe3\xe3]J\xeb$\xb8%\xf1\xa2\x15\xf9\x9d\x85\xf7E\x89lh\xcc\u041b\x02,\x00\x00\u07d4p\x8a*\xf4%\u03b0\x1e\x87\xff\xc1\xbeT\xc0\xf52\xb2\x0e\xac\u0589\aE\u0503\xb1\xf5\xa1\x80\x00\u07d4p\x8e\xa7\a\xba\xe45\u007f\x1e\xbe\xa9Y\u00e2P\xac\u05aa!\xb3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794p\x8f\xa1\x1f\xe3=\x85\xad\x1b\xef\u02ee8\x18\xac\xb7\x1fj}~\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4p\x9101\x16\xd5\xf28\x9b##\x8bMej\x85\x96\u0644\u04c9;N~\x80\xaaX3\x00\x00\u07d4p\x99\xd1/n\xc6V\x89\x9b\x04\x9avW\x06]b\x99h\x92\u0209\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4p\x9f\xe9\xd2\xc1\xf1\xceB |\x95\x85\x04J`\x89\x9f5\x94/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\xa05I\xaaah\xe9~\x88\xa5\b3\nZ\v\xeatq\x1a\x89Hz\x9a0E9D\x00\x00\u07d4p\xa4\x06}D\x8c\xc2]\xc8\xe7\x0ee\x1c\xea|\xf8N\x92\x10\x9e\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4p\xab4\xbc\x17\xb6o\x9c;c\xf1Q'O*r|S\x92c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4p\xc2\x13H\x8a\x02\f<\xfb9\x01N\xf5\xbad\x04rK\u02a3\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4p\xd2^\xd2\u022d\xa5\x9c\b\x8c\xf7\r\xd2+\xf2\u06d3\xac\xc1\x8a\x899GEE\u4b7c\x00\x00\u07d4p\xe5\xe9\xdas_\xf0w$\x9d\u02da\xaf=\xb2\xa4\x8d\x94\x98\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4p\xfe\xe0\x8b\x00\xc6\xc2\xc0Jp\xc0\xce=\x92\u03ca\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u0794q\v\xe8\xfd^)\x18F\x8b\u2abe\xa8\r\x82\x845\u05d6\x12\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4q\x13]\x8f\x05\x96<\x90ZJ\a\x92)\t#Z\x89jR\ua262\xa1]\tQ\x9b\xe0\x00\x00\u07d4q\x1e\xcfw\xd7\x1b=\x0e\xa9\\\xe4u\x8a\xfe\u0379\xc11\a\x9d\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4q!?\xca14\x04 N\u02e8q\x97t\x1a\xa9\xdf\xe9c8\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94q+vQ\x02\x14\xdcb\x0fl:\x1d\u049a\xa2+\xf6\xd2\x14\xfb\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4q/\xf77\n\x13\xed6\ts\xfe\u071f\xf5\xd2\xc9:P^\x9e\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4q3\x84:x\xd99\u019dD\x86\xe1\x0e\xbc{`*4\x9f\xf7\x89\x11\xd5\xca\xcc\xe2\x1f\x84\x00\x00\u07d4qH\xae\xf32a\xd8\x03\x1f\xac?q\x82\xff5\x92\x8d\xafT\u0649\xdeB\xee\x15D\u0750\x00\x00\u07d4qcu\x8c\xbblLR^\x04\x14\xa4\n\x04\x9d\xcc\xcc\xe9\x19\xbb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4qh\xb3\xbb\x8c\x16s!\u067d\xb0#\xa6\xe9\xfd\x11\xaf\u026f\u0649a\t=|,m8\x00\x00\u07d4qirN\xe7\"q\xc54\xca\xd6B\x0f\xb0N\xe6D\u02c6\xfe\x89\x16<+@\u06e5R\x00\x00\u07d4qj\xd3\xc3:\x9b\x9a\n\x18\x96sW\x96\x9b\x94\xee}*\xbc\x10\x89\x1a!\x17\xfeA*H\x00\x00\xe0\x94qk\xa0\x1e\xad*\x91'\x065\xf9_%\xbf\xaf-\xd6\x10\xca#\x8a\ty\xe7\x01 V\xaax\x00\x00\u07d4qmP\u0320\x1e\x93\x85\x00\xe6B\x1c\xc0p\xc3P|g\u04c7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4qv,cg\x8c\x18\xd1\xc67\x8c\xe0h\xe6f8\x13\x15\x14~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4qxL\x10Q\x17\xc1\xf6\x895y\u007f\xe1Y\xab\xc7NC\xd1j\x89l\x81\u01f3\x11\x95\xe0\x00\x00\xe0\x94qyro\\q\xae\x1bm\x16\xa6\x84(\x17Nk4\xb26F\x8a\x01\x8e\xa2P\t|\xba\xf6\x00\x00\xe0\x94q|\xf9\xbe\xab680\x8d\xed~\x19^\f\x86\x13-\x16?\xed\x8a\x032n\xe6\xf8e\xf4\"\x00\x00\u07d4q\x80\xb8>\xe5WC\x17\xf2\x1c\x80r\xb1\x91\u0615\xd4aS\u00c9\x18\xef\xc8J\xd0\u01f0\x00\x00\u07d4q\x94kq\x17\xfc\x91^\xd1\a8_B\u065d\xda\xc62I\u0089lk\x93[\x8b\xbd@\x00\x00\xe0\x94q\x9e\x89\x1f\xbc\xc0\xa3>\x19\xc1-\xc0\xf0 9\xca\x05\xb8\x01\u07ca\x01OU8F:\x1bT\x00\x00\u07d4q\xc7#\n\x1d5\xbd\u0581\x9e\u0539\xa8\x8e\x94\xa0\xeb\a\x86\u0749\uc80b5=$\x14\x00\x00\u07d4q\xd2\xccm\x02W\x8ce\xf7\r\xf1\x1bH\xbe\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4r\x83\xcdFu\xdaX\u0116UaQ\xda\xfd\x80\xc7\xf9\x95\xd3\x18\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4r\x86\xe8\x9c\xd9\u078fz\x8a\x00\xc8o\xfd\xb59\x92\u0752Q\u0449i*\xe8\x89p\x81\xd0\x00\x00\u07d4r\x8f\x9a\xb0\x80\x15}\xb3\a1V\xdb\xca\x1a\x16\x9e\xf3\x17\x94\a\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4r\x94\xc9\x18\xb1\xae\xfbM%\x92~\xf9\u05d9\xe7\x1f\x93\xa2\x8e\x85\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94r\x94\uc763\x10\xbckK\xbd\xf5C\xb0\xefE\xab\xfc>\x1bM\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4r\x9a\xadF'tNS\xf5\xd6c\t\xaatD\x8b:\xcd\xf4o\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xa2\xfc\x86u\xfe\xb9r\xfaA\xb5\r\xff\u06fa\xe7\xfa*\u07f7\x89\x9a\xb4\xfcg\xb5(\xc8\x00\x00\u07d4r\xa8&\b&)G&\xa7[\xf3\x9c\u066a\x9e\a\xa3\xea\x14\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4r\xb0Yb\xfb*\u0549\xd6Z\xd1j\"U\x9e\xba\x14X\xf3\x87\x89\a?u\u0460\x85\xba\x00\x00\u07d4r\xb5c?\xe4w\xfeT.t/\xac\xfdi\f\x13xT\xf2\x16\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4r\xb7\xa0=\xda\x14\u029cf\x1a\x1dF\x9f\xd376\xf6s\xc8\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xb9\x04D\x0e\x90\xe7 \u05ac\x1c*\u05dc2\x1d\xcc\x1c\x1a\x86\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94r\xb9\nM\xc0\x97#\x94\x92\u0179w}\xcd\x1eR\xba+\xe2\u008a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4r\xbb'\u02d9\xf3\xe2\xc2\u03d0\xa9\x8fp}0\xe4\xa2\x01\xa0q\x89X\xe7\x92n\xe8X\xa0\x00\x00\xe0\x94r\xc0\x83\xbe\xad\xbd\xc2'\xc5\xfbC\x88\x15\x97\xe3.\x83\xc2`V\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4r\xcd\x04\x8a\x11\x05tH)\x83I-\xfb\x1b\xd2yB\xa6\x96\xba\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xd0=M\xfa\xb3P\f\xf8\x9b\x86\x86o\x15\xd4R\x8e\x14\xa1\x95\x89\xf3K\x82\xfd\x8e\x91 \x00\x00\u07d4r\u06bb[n\ud799\xbe\x91X\x88\xf6V\x80V8\x16\b\xf8\x89\vL\x96\xc5,\xb4\xfe\x80\x00\u07d4r\xfbI\u009d#\xa1\x89P\u0132\xdc\r\xdfA\x0fS-oS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4r\xfe\xaf\x12EyR9Td[\u007f\xaf\xff\x03x\xd1\xc8$.\x8965\u026d\xc5\u07a0\x00\x00\u07d4s\x01\xdcL\xf2mq\x86\xf2\xa1\x1b\xf8\xb0\x8b\xf2)F?d\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s\x04G\xf9|\xe9\xb2_\"\xba\x1a\xfb6\xdf'\xf9Xk\ub6c9,s\xc97t,P\x00\x00\u07d4s\x06\xde\x0e(\x8bV\xcf\u07d8~\xf0\xd3\xcc)f\a\x93\xf6\u0749\x1b\x8a\xbf\xb6.\xc8\xf6\x00\x00\xe0\x94s\r\x87c\u01a4\xfd\x82J\xb8\xb8Y\x16\x1e\xf7\xe3\xa9j\x12\x00\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4s\x12\x81sH\x95(\x01.v\xb4\x1a^(\u018b\xa4\xe3\xa9\u050965\u026d\xc5\u07a0\x00\x00\u07d4s\x13F\x12\bETUFTE\xa4Y\xb0l7s\xb0\xeb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s/\xea\xd6\x0f{\xfd\u05a9\xde\u0101%\xe3s]\xb1\xb6eO\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4sB#\xd2\u007f\xf2>Y\x06\xca\xed\"YW\x01\xbb4\x83\f\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4sG>r\x11Q\x10\xd0\xc3\xf1\x17\b\xf8nw\xbe+\xb0\x98<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4sRXm\x02\x1a\xd0\xcfw\xe0\xe9(@JY\xf3t\xffE\x82\x89\xb8Pz\x82\a( \x00\x00\u07d4sU\v\xebs+\xa9\u076f\xdaz\xe4\x06\xe1\x8f\u007f\xeb\x0f\x8b\xb2\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4s[\x97\xf2\xfc\x1b\xd2K\x12\an\xfa\xf3\xd1(\x80s\xd2\f\x8c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4s^2\x86f\xedV7\x14+3\x06\xb7|\xccT`\xe7,=\x89j\xb8\xf3xy\u0251\x00\x00\u07d4sc\u0350\xfb\xab[\xb8\u011a\xc2\x0f\xc6,9\x8f\xe6\xfbtL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4skDP=\xd2\xf6\xddTi\xffL[-\xb8\xeaO\xece\u0409\x11\x04\xeeu\x9f!\xe3\x00\x00\xe0\x94sk\xf1@,\x83\x80\x0f\x89>X1\x92X*\x13N\xb52\xe9\x8a\x02\x1e\x19\u0493\xc0\x1f&\x00\x00\xe0\x94s\x8c\xa9M\xb7\u038b\xe1\xc3\x05l\u0598\x8e\xb3v5\x9f3S\x8a\x05f[\x96\xcf5\xac\xf0\x00\x00\u07d4s\x91K\"\xfc/\x13\x15\x84$}\x82\xbeO\ucfd7\x8a\u053a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4s\x93'\t\xa9\u007f\x02\u024eQ\xb0\x911(e\x12#\x85\xae\x8e\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4s\x93\xcb\xe7\xf9\xba!e\xe5\xa7U5\x00\xb6\xe7]\xa3\xc3:\xbf\x89\x05k\xc7^-c\x10\x00\x00\u07d4s\xb4\u0519\xde?8\xbf5\xaa\xf7i\xa6\xe3\x18\xbcm\x126\x92\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94s\xbe\xddo\xda{\xa3'!\x85\b{cQ\xfc\x13=HN7\x8a\x01\x12&\xbf\x9d\xceYx\x00\x00\u07d4s\xbf\xe7q\x0f1\u02b9I\xb7\xa2`O\xbfR9\xce\xe7\x90\x15\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94s\u03c0\xae\x96\x88\xe1X\x0eh\xe7\x82\xcd\b\x11\xf7\xaaIM,\x8a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\xe0\x94s\xd7&\x9f\xf0l\x9f\xfd3uL\xe5\x88\xf7J\x96j\xbb\xbb\xba\x8a\x01e\xc9fG\xb3\x8a \x00\x00\u07d4s\xd8\xfe\xe3\u02c6M\xce\"\xbb&\u029c/\bm^\x95\xe6;\x8965\u026d\xc5\u07a0\x00\x00\u07d4s\xdf<>yU\xf4\xf2\xd8Y\x83\x1b\xe3\x80\x00\xb1\ak8\x84\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4s\u48b6\f\U0010e2ef+w~\x17Z[\x1eM\f-\x8f\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94t\n\xf1\xee\xfd3e\u05cb\xa7\xb1,\xb1\xa6s\xe0j\arF\x8a\x04+\xf0kx\xed;P\x00\x00\xe0\x94t\v\xfdR\xe0\x16g\xa3A\x9b\x02\x9a\x1b\x8eEWj\x86\xa2\u06ca\x03\x8e\xba\xd5\xcd\xc9\x02\x80\x00\x00\u07d4t\x0fd\x16\x14w\x9d\u03e8\x8e\xd1\xd4%\xd6\r\xb4*\x06\f\xa6\x896\"\xc6v\b\x10W\x00\x00\u07d4t\x12\u027c0\xb4\xdfC\x9f\x021\x00\xe69$\x06j\xfdS\xaf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4t\x16\x93\xc3\x03vP\x85\x13\b \xcc+c\xe9\xfa\x92\x13\x1b\x89A\rXj \xa4\xc0\x00\x00\u07d4t!\xce[\xe3\x81s\x8d\u0703\xf0&!\x97O\xf0hly\xb8\x89Xx\x8c\xb9K\x1d\x80\x00\x00\u07d4t1j\xdf%7\x8c\x10\xf5v\u0574\x1aoG\xfa\x98\xfc\xe3=\x89\x128\x13\x1e\\z\xd5\x00\x00\u07d4t6Q\xb5^\xf8B\x9d\xf5\f\xf8\x198\xc2P\x8d\xe5\u0207\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t=\xe5\x00&\xcag\xc9M\xf5O\x06b`\xe1\xd1J\xcc\x11\xac\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tE /\ft)z\x00N\xb3rj\xa6\xa8-\xd7\xc0/\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tK\x03\xbb\xa8X*\xe5I\x8e-\xc2-\x19\x94\x94g\xabS\xfc\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4tL\fw\xba\u007f#i \xd1\xe44\xde]\xa3>H\xeb\xf0,\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4tP\xff\u007f\x99\xea\xa9\x11bu\u07ach\xe4(\xdf[\xbc\u0639\x89lk\x93[\x8b\xbd@\x00\x00\u07d4tV\u0172\xc5Cn>W\x10\b\x93?\x18\x05\xcc\xfe4\xe9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4tZ\u04eb\xc6\xee\xeb$qh\x9bS\x9ex\x9c\xe2\xb8&\x83\x06\x89=A\x94\xbe\xa0\x11\x92\x80\x00\xe0\x94tZ\xec\xba\xf9\xbb9\xb7Jg\xea\x1c\xe6#\xde6\x84\x81\xba\xa6\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4t\\\xcf-\x81\x9e\u06fd\u07a8\x11{\\I\xed<*\x06n\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4tb\u021c\xaa\x9d\x8dx\x91\xb2T]\xef!otd\u057b!\x89\x05\xea\xedT\xa2\x8b1\x00\x00\u07d4td\x8c\xaa\xc7H\xdd\x13\\\xd9\x1e\xa1L(\xe1\xbdM\u007f\xf6\xae\x89\xa8\r$g~\xfe\xf0\x00\x00\xe0\x94tq\xf7.\xeb0\x06$\xeb(.\xabM\x03r\x00\x00\x00\xe0\x94t\x84\xd2k\xec\xc1\xee\xa8\xc61^\xc3\xee\nE\x01\x17\u0706\xa0\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4t\x86:\xce\xc7]\x03\xd5>\x86\x0ed\x00/,\x16^S\x83w\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\x89\u030a\xbeu\u0364\xef\r\x01\xce\xf2`^G\xed\xa6z\xb1\x89\a?u\u0460\x85\xba\x00\x00\u07d4t\x8c(^\xf1#?\xe4\xd3\x1c\x8f\xb17\x833r\x1c\x12\xe2z\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t\x90\x87\xac\x0fZ\x97\xc6\xfa\xd0!S\x8b\xf1\xd6\u0361\x8e\r\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\x95\xaex\xc0\xd9\x02a\xe2\x14\x0e\xf2\x061\x04s\x1a`\xd1\xed\x89\x01\xdbPq\x89%!\x00\x00\u07d4t\x9aJv\x8b_#rH\x93\x8a\x12\xc6#\x84{\xd4\xe6\x88\u0709\x03\xe73b\x87\x14 \x00\x00\u07d4t\x9a\xd6\xf2\xb5pk\xbe/h\x9aD\u0136@\xb5\x8e\x96\xb9\x92\x89\x05k\xc7^-c\x10\x00\x00\u07d4t\xa1\u007f\x06K4N\x84\xdbce\u0695\x91\xff\x16(%vC\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4t\xae\xec\x91]\xe0\x1c\u019b,\xb5\xa65o\xee\xa1FX\xc6\u0149\f\x9a\x95\xee)\x86R\x00\x00\u07d4t\xaf\xe5I\x02\xd6\x15x%v\xf8\xba\xac\x13\xac\x97\f\x05\x0fn\x89\t\xa1\xaa\xa3\xa9\xfb\xa7\x00\x00\u07d4t\xb7\xe0\"\x8b\xae\xd6YW\xae\xbbM\x91m3:\xae\x16O\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4t\xbcJ^ E\xf4\xff\x8d\xb1\x84\xcf:\x9b\f\x06Z\xd8\a\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4t\xbc\xe9\xec86-l\x94\u032c&\xd5\xc0\xe1:\x8b;\x1d@\x8965&A\x04B\xf5\x00\x00\u07d4t\xbfzZ\xb5\x92\x93\x14\x9b\\`\xcf6Bc\xe5\xeb\xf1\xaa\r\x89\x06G\f>w\x1e<\x00\x00\xe0\x94t\xc7<\x90R\x8a\x15s6\xf1\xe7\xea b\n\xe5?\xd2G(\x8a\x01\xe6:.S\x8f\x16\xe3\x00\x00\u07d4t\u0464\xd0\xc7RN\x01\x8dN\x06\xed;d\x80\x92\xb5\xb6\xaf,\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94t\xd3f\xb0{/VG}|pw\xaco\xe4\x97\xe0\xebeY\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4t\xd3zQt{\xf8\xb7q\xbf\xbfC\x9493\xd1\x00\xd2\x14\x83\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\xd6q\u065c\xbe\xa1\xabW\x90cu\xb6?\xf4+PE\x1d\x17\x8965\u026d\xc5\u07a0\x00\x00\u07d4t\xeb\xf4BVF\xe6\u03c1\xb1\t\xce{\xf4\xa2\xa6=\x84\x81_\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4t\xed3\xac\xf4?5\xb9\x8c\x920\xb9\xe6d.\xcbS0\x83\x9e\x89$\xf6\xdf\xfbI\x8d(\x00\x00\u07d4t\xef(i\xcb\xe6\b\x85`E\xd8\xc2\x04\x11\x18W\x9f\"6\xea\x89\x03<\xd6E\x91\x95n\x00\x00\u07d4t\xfcZ\x99\xc0\xc5F\x05\x03\xa1;\x05\tE\x9d\xa1\x9c\xe7\u0350\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4u\v\xbb\x8c\x06\xbb\xbf$\bC\xccux.\xe0/\b\xa9tS\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4u\x14\xad\xbd\xc6?H?0M\x8e\x94\xb6\u007f\xf30\x9f\x18\v\x82\x89!\u0120n-\x13Y\x80\x00\u0794u\x17\xf1l(\xd12\xbb@\xe3\xba6\u01ae\xf11\xc4b\xda\x17\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4u\x1a,\xa3Nq\x87\xc1c\u048e6\x18\xdb(\xb1<\x19m&\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94u\x1a\xbc\xb6\xcc\x030Y\x91\x18\x15\xc9o\u04516\n\xb0D-\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4u&\xe4\x82R\x9f\n\x14\xee\u0248q\xdd\xdd\x0er\x1b\f\u0662\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4u)\xf3y{\xb6\xa2\x0f~\xa6I$\x19\xc8L\x86vA\xd8\x1c\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94u*^\xe22a,\xd3\x00_\xb2n[Y}\xe1\x9fwk\xe6\x8a\x01'\xfc\xb8\xaf\xae \xd0\x00\x00\u07d4u,\x9f\xeb\xf4/f\xc4x{\xfa~\xb1|\xf53;\xbaPp\x89j\x99\xf2\xb5O\xddX\x00\x00\u07d4u930F\u07b1\xef\x8e\u07b9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94u\xc1\xad#\xd2?$\xb3\x84\xd0\xc3\x14\x91w\xe8f\x97a\r!\x8a\x01\\[\xcdl(\x8b\xbd\x00\x00\u07d4u\xc2\xff\xa1\xbe\xf5I\x19\xd2\t\u007fz\x14-.\x14\xf9\xb0JX\x89\x90\xf3XP@2\xa1\x00\x00\u07d4u\xd6|\xe1N\x8d)\xe8\xc2\xff\u3051{\x93\v\x1a\xff\x1a\x87\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4u\xde~\x93R\xe9\v\x13\xa5\x9aXx\xff\xec\u01c3\x1c\xacM\x82\x89\x94\x89#z\u06daP\x00\x00\u07d4u\xf7S\x9d0\x9e\x909\x98\x9e\xfe.\x8b-\xbd\x86Z\r\xf0\x88\x89\x85[[\xa6\\\x84\xf0\x00\x00\u07d4v\b\xf47\xb3\x1f\x18\xbc\vd\u04c1\xae\x86\xfd\x97\x8e\u05f3\x1f\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94v\x0f\xf35N\x0f\u0793\x8d\x0f\xb5\xb8,\xef[\xa1\\=)\x16\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4v\x1an6,\x97\xfb\xbd|Yw\xac\xba-\xa7F\x876_I\x89\t\xf7J\xe1\xf9S\xd0\x00\x00\u07d4v\x1el\xae\xc1\x89\xc20\xa1b\xec\x00e0\x19>g\u03dd\x19\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94v\x1f\x8a:*\U00028f7e\x1d\xa0\t2\x1f\xb2\x97d\xebb\xa1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4v)\x98\xe1\xd7R'\xfc\xedzp\xbe\x10\x9aL\vN\xd8d\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4v-o0\u06b9\x915\xe4\xec\xa5\x1dRC\xd6\xc8b\x11\x02\u0549\x0fI\x89A\xe6d(\x00\x00\u07d4v3\x1e0yl\xe6d\xb2p\x0e\rASp\x0e\u0706\x97w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v8\x86\xe33\xc5o\xef\xf8[\xe3\x95\x1a\xb0\xb8\x89\xce&.\x95\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v:|\xba\xb7\rzd\u0427\xe5)\x80\xf6\x81G%\x93I\f\x89 \x86\xac5\x10R`\x00\x00\u07d4v>\xec\u0c0a\u021e2\xbf\xa4\xbe\xcev\x95\x14\xd8\xcb[\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4v@\xa3\u007f\x80R\x98\x15\x15\xbc\xe0x\u0693\xaf\xa4x\x9bW4\x89lk\x93[\x8b\xbd@\x00\x00\u0794vA\xf7\xd2j\x86\xcd\xdb+\xe10\x81\x81\x0e\x01\xc9\xc8E\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94vO\xc4mB\x8bm\xbc\"\x8a\x0f_U\xc9P\x8cw.\xab\x9f\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4vPn\xb4\xa7\x80\xc9Q\xc7J\x06\xb0=;\x83b\xf0\x99\x9dq\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94v[\xe2\xe1/b\x9ecI\xb9}!\xb6*\x17\xb7\xc80\xed\xab\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94vb\x81P\xe2\x99[['\x9f\xc8>\r\xd5\xf1\x02\xa6q\xdd\x1c\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4vk7Y\xe8yN\x92m\xacG=\x91:\x8f\xb6\x1a\xd0\xc2\u0249\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4vp\xb0/,<\xf8\xfdOG0\xf38\x1aq\xeaC\x1c3\u01c9\x0e~\xeb\xa3A\vt\x00\x00\u07d4vz\x03eZ\xf3`\x84\x1e\x81\r\x83\xf5\xe6\x1f\xb4\x0fL\xd1\x13\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4vz\u0190y\x1c.#E\x10\x89\xfelp\x83\xfeU\u07b6+\x89,s\xc97t,P\x00\x00\u07d4v\u007f\xd7y}Qi\xa0_sd2\x1c\x19\x84:\x8c4\x8e\x1e\x89\x01\x04\xe7\x04d\xb1X\x00\x00\u0794v\x84o\r\xe0;Zv\x97\x1e\xad)\x8c\xdd\b\x84:K\xc6\u0188\xd7\x1b\x0f\u088e\x00\x00\xe0\x94v\x84\x98\x93N7\xe9\x05\xf1\xd0\xe7{D\xb5t\xbc\xf3\xecJ\xe8\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4v\x8c\xe0\u06a0)\xb7\xde\xd0\"\xe5\xfcWM\x11\xcd\xe3\xec\xb5\x17\x89\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\xe0\x94v\x93\xbd\xebo\xc8+[\xcar\x13U\"1u\xd4z\bKM\x8a\x04\xa8\x9fT\xef\x01!\xc0\x00\x00\u07d4v\xaa\xf8\xc1\xac\x01/\x87R\xd4\xc0\x9b\xb4f\a\xb6e\x1d\\\xa8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4v\xab\x87\xddZ\x05\xad\x83\x9aN/\xc8\xc8Z\xa6\xba\x05d\x170\x89lk\x93[\x8b\xbd@\x00\x00\u07d4v\xaf\xc2%\xf4\xfa0}\xe4\x84U+\xbe\x1d\x9d?\x15\aLJ\x89\xa2\x90\xb5\u01ed9h\x00\x00\xe0\x94v\xbe\xca\xe4\xa3\x1d6\xf3\xcbW\u007f*CYO\xb1\xab\xc1\xbb\x96\x8a\x05C\xa9\xce\x0e\x132\xf0\x00\x00\u07d4v\xc2u5\xbc\xb5\x9c\xe1\xfa-\x8c\x91\x9c\xab\xebJk\xba\x01\u0449lk\x93[\x8b\xbd@\x00\x00\u07d4v\xca\"\xbc\xb8y\x9eS'\u012a*}\tI\xa1\xfc\xce_)\x89R\xa0?\"\x8cZ\xe2\x00\x00\u07d4v\xca\u0108\x11\x1aO\u0555\xf5h\xae:\x85\x87p\xfc\x91]_\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94v\u02dc\x8bi\xf48vu\u0102S\xe24\xcb~\rt\xa4&\x8a\x01\x90\xf4H.\xb9\x1d\xae\x00\x00\u07d4v\xf8:\xc3\xda0\xf7\t&(\xc73\x9f \x8b\xfc\x14,\xb1\ue25a\x18\xff\xe7B}d\x00\x00\xe0\x94v\xf9\xad=\x9b\xbd\x04\xae\x05\\\x14w\xc0\xc3^u\x92\xcb* \x8a\b\x83?\x11\xe3E\x8f \x00\x00\u07d4v\xff\xc1W\xadk\xf8\xd5m\x9a\x1a\u007f\u077c\x0f\xea\x01\n\xab\xf4\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\x02\x8e@\x9c\xc4:;\xd3=!\xa9\xfcS\xec`n\x94\x91\x0e\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4w\f/\xb2\u0128\x17S\xac\x01\x82\xeaF\x0e\xc0\x9c\x90\xa5\x16\xf8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4w\r\x98\xd3\x1bCS\xfc\xee\xe8V\fL\u03c0>\x88\xc0\xc4\xe0\x89 \x86\xac5\x10R`\x00\x00\xe0\x94w\x13\xab\x807A\x1c\t\xbah\u007fo\x93d\xf0\xd3#\x9f\xac(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4w\x15\a\xae\xeej%]\xc2\u035d\xf5QT\x06-\b\x97\xb2\x97\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4w\x19\x88\x87\x95\xadtY$\xc7W`\u0771\x82}\xff\xd8\u0368\x89lkLM\xa6\u077e\x00\x00\u07d4w'\xaf\x10\x1f\n\xab\xa4\xd2:\x1c\xaf\xe1|n\xb5\u06b1\xc6\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4w,)\u007f\n\u0454H.\xe8\xc3\xf06\xbd\xeb\x01\xc2\x01\xd5\u0309\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94w0o\xfe.J\x8f<\xa8&\xc1\xa2I\xf7!-\xa4:\xef\xfd\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4w1A\x12}\x8c\xf3\x18\xae\xbf\x886Z\xdd=U'\xd8[j\x8966\u05ef^\u024e\x00\x00\u07d4wF\xb6\xc6i\x9c\x8f4\xca'h\xa8 \xf1\xff\xa4\xc2\a\xfe\x05\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4wQ\xf3c\xa0\xa7\xfd\x053\x19\b\t\u076f\x93@\xd8\xd1\x12\x91\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4wW\xa4\xb9\xcc=\x02G\u032a\xeb\x99\t\xa0\xe5n\x1d\xd6\xdc\u0089\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4w\\\x10\xc9>\r\xb7 [&CE\x823\xc6O\xc3?\xd7[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4wa~\xbcK\xeb\xc5\xf5\xdd\xeb\x1bzp\xcd\xebj\xe2\xff\xa0$\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4wiC\xff\xb2\xef\\\xdd5\xb8<(\xbc\x04k\xd4\xf4gp\x98\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94wp\x1e,I=\xa4|\x1bX\xf4!\xb5I]\xeeE\xbe\xa3\x9b\x8a\x01H\xf6I\xcfaB\xa5\x80\x00\u07d4wy\x8f \x12W\xb9\xc3R\x04\x95pW\xb5Ft\xae\xfaQ\u07c9\b\x13\xcaV\x90m4\x00\x00\u07d4w\x8cC\xd1\x1a\xfe;Xo\xf3t\x19-\x96\xa7\xf2=+\x9b\u007f\x89\x8b\xb4\xfc\xfa;}k\x80\x00\u07d4w\x8cy\xf4\xde\x19S\xeb\u0398\xfe\x80\x06\xd5:\x81\xfbQ@\x12\x8963\x03\"\xd5#\x8c\x00\x00\u07d4w\x92t\xbf\x18\x03\xa36\xe4\u04f0\r\u0753\xf2\xd4\xf5\xf4\xa6.\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\xa1q\"\xfa1\xb9\x8f\x17\x11\xd3*\x99\xf0>\xc3&\xf3=\b\x89\\(=A\x03\x94\x10\x00\x00\u07d4w\xa3I\a\xf3\x05\xa5L\x85\xdb\t\xc3c\xfd\xe3\xc4~j\xe2\x1f\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4w\xa7i\xfa\xfd\xec\xf4\xa68v-[\xa3\x96\x9d\xf61 \xa4\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4w\xbekd\xd7\xc73\xa46\xad\xec^\x14\xbf\x9a\xd7@+\x1bF\x8965\u026d\xc5\u07a0\x00\x00\u07d4w\xbf\xe9<\u0367P\x84~A\xa1\xaf\xfe\xe6\xb2\u0696\xe7!N\x89\x10CV\x1a\x88)0\x00\x00\u07d4w\u0126\x97\xe6\x03\xd4+\x12\x05l\xbb\xa7a\xe7\xf5\x1d\x04C\xf5\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4w\xcc\x02\xf6#\xa9\u03d8S\t\x97\xeag\xd9\\;I\x18Y\xae\x89Is\x03\xc3n\xa0\xc2\x00\x00\u07d4w\xd4?\xa7\xb4\x81\xdb\xf3\xdbS\f\xfb\xf5\xfd\xce\xd0\xe6W\x181\x89lk\x93[\x8b\xbd@\x00\x00\u07d4w\xda^lr\xfb6\xbc\xe1\xd9y\x8f{\xcd\xf1\u044fE\x9c.\x89\x016\x95\xbbl\xf9>\x00\x00\u07d4w\xf4\xe3\xbd\xf0V\x88<\xc8r\x80\xdb\xe6@\xa1\x8a\r\x02\xa2\a\x89\n\x81\x99:+\xfb[\x00\x00\u0794w\xf6\t\u0287 \xa0#&,U\xc4o-&\xfb90\xaci\x88\xf0\x15\xf2W6B\x00\x00\u07d4w\xf8\x1b\x1b&\xfc\x84\xd6\u0797\uf2df\xbdr\xa310\xccJ\x8965\u026d\xc5\u07a0\x00\x00\u07d4x\x19\xb0E\x8e1N+S\xbf\xe0\f8I_\u0539\xfd\xf8\u0589\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4x\x1b\x15\x01dz.\x06\xc0\xedC\xff\x19\u007f\xcc\xec5\xe1p\v\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4x/R\xf0\xa6v\xc7w\x16\xd5t\xc8\x1e\xc4hO\x9a\x02\n\x97\x89.\x14\xe2\x06\xb70\xad\x80\x00\u07d4x5]\xf0\xa20\xf8=\x03,p1TAM\xe3\xee\u06b5W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4x6\xf7\xefk\u01fd\x0f\xf3\xac\xafD\x9c\x84\xddk\x1e,\x93\x9f\x89\xe0\x8d\xe7\xa9,\xd9|\x00\x00\u07d4x7\xfc\xb8v\xda\x00\xd1\xeb;\x88\xfe\xb3\xdf?\xa4\x04/\xac\x82\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4x>\uc2a5\xda\xc7{.f#\xedQ\x98\xa41\xab\xba\xee\a\x89\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4x\\\x8e\xa7t\xd70D\xa74\xfay\n\x1b\x1et>w\xed|\x89\f\xf1Rd\f\\\x83\x00\x00\u07d4x`\xa3\xde8\xdf8*\xe4\xa4\xdc\xe1\x8c\f\a\xb9\x8b\xce=\xfa\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94xcCq\xe1s\x04\xcb\xf39\xb1E*L\xe48\xdcvL\u038a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4xd\u0719\x9f\xe4\xf8\xe0\x03\xc0\xf4=\xec\u00da\xae\x15\"\xdc\x0f\x89\x05\x1e\x10+\xd8\xec\xe0\x00\x00\u07d4xtj\x95\x8d\xce\xd4\xc7d\xf8vP\x8cAJh4,\uce49\x02\xbe7O\xe8\xe2\xc4\x00\x00\xe0\x94x}1?\xd3k\x05>\xee\xae\xdb\xcet\xb9\xfb\x06x32\x89\x8a\x05\xc0X\xb7\x84'\x19`\x00\x00\u07d4x\x85\x9c[T\x8bp\r\x92\x84\xce\xe4\xb6c=GJ\x8a\x04{\x92\xc4\x15B$-\n\b\xc7\x0f\x99\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4x\u03c36\xb3(\xdb=\x87\x81:G+\x9e\x89\xb7^\f\xf3\xbc\x8965\u026d\xc5\u07a0\x00\x00\u07d4x\xd4\xf8\xc7\x1c\x1eh\xa6\x9a\x98\xf5/\xcbE\u068a\xf5n\xa1\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4x\xdf&\x81\xd6\xd6\x02\xe2!B\xd5A\x16\u07a1]EIW\xaa\x89\x10'\x94\xad \xdah\x00\x00\u07d4x\xe0\x8b\xc53A<&\u2473\x14?\xfa|\u026f\xb9{x\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4x\xe8?\x80\xb3g\x8cz\nN>\x8c\x84\xdc\xcd\xe0dBbw\x89a\t=|,m8\x00\x00\u07d4x\xf5\xc7G\x85\xc5f\x8a\x83\x80r\x04\x8b\xf8\xb4SYM\u06ab\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4y\x0f\x91\xbd]\x1c\\\xc4s\x9a\xe9\x13\x00\u06c9\xe1\xc10<\x93\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\x17\u5f42\xa9y\x0f\xd6P\xd0C\xcd\xd90\xf7y\x963\u06c9\xd8\xd4`,&\xbfl\x00\x00\u07d4y\x19\xe7b\u007f\x9b}T\xea;\x14\xbbM\xd4d\x9fO9\xde\xe0\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4y\x1f`@\xb4\xe3\xe5\r\xcf5S\xf1\x82\u0357\xa9\x060\xb7]\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4y0\xc2\xd9\xcb\xfa\x87\xf5\x10\xf8\xf9\x87w\xff\x8a\x84H\xcaV)\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4yE)\u041d\x01rq5\x970\x02pu\xb8z\xd8=\xaen\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4yKQ\u00deS\xd9\xe7b\xb0a;\x82\x9aD\xb4r\xf4\xff\xf3\x89$5\xe0dxA\u0300\x00\xe0\x94yU\x1c\xed\xe3v\xf7G\xe3ql\x8dy@\rvm.\x01\x95\x8a\t\xcb7\xaf\xa4\xffxh\x00\x00\u07d4y^\xbc&&\xfc9\xb0\xc8b\x94\xe0\xe87\xdc\xf5#U0\x90\x8965\u026d\xc5\u07a0\x00\x00\u07d4yn\xbb\xf4\x9b>6\xd6v\x94\xady\xf8\xff6vz\xc6\xfa\xb0\x89\x03K\xc4\xfd\xde'\xc0\x00\x00\u07d4yo\x87\xbaaz)0\xb1g\v\xe9.\xd1(\x1f\xb0\xb3F\xe1\x89\x06\xf5\xe8o\xb5((\x00\x00\u07d4yt'\xe3\xdb\xf0\xfe\xaez%\x06\xf1-\xf1\xdc@2n\x85\x05\x8965\u026d\xc5\u07a0\x00\x00\u07d4yu\x10\xe3\x86\xf5c\x93\xce\xd8\xf4w7\x8aDLHO}\xad\x8965\u026d\xc5\u07a0\x00\x00\u07d4y{\xb7\xf1W\xd9\xfe\xaa\x17\xf7m\xa4\xf7\x04\xb7M\xc1\x03\x83A\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4y\x88\x90\x131\xe3\x87\xf7\x13\xfa\u03b9\x00\\\xb9\xb6Q6\xeb\x14\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4y\x89\u041f8&\xc3\u5bccu*\x81\x15r:\x84\xd8\tp\x89\x16\x86\xf8aL\xf0\xad\x00\x00\xe0\x94y\x95\xbd\x8c\xe2\xe0\xc6{\xf1\u01e51\xd4w\xbc\xa1\xb2\xb9ua\x8a\x01BH\xd6\x17\x82\x9e\xce\x00\x00\u07d4y\xae\xb3Ef\xb9t\xc3ZX\x81\xde\xc0 \x92}\xa7\xdf]%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\xb1 \xeb\x88\x06s#!(\x8fgZ'\xa9\"_\x1c\xd2\ub245\xa0\xbf7\xde\xc9\xe4\x00\x00\u07d4y\xb4\x8d-a7\u00c5Ma\x1c\x01\xeaBBz\x0fY{\xb7\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4y\xb8\xaa\xd8y\xdd0V~\x87x\xd2\xd21\xc8\xf3z\xb8sN\x89lk\x93[\x8b\xbd@\x00\x00\u07d4y\xbf/{n2\x8a\xaf&\xe0\xbb\t?\xa2-\xa2\x9e\xf2\xf4q\x89a\t=|,m8\x00\x00\u07d4y\xc10\xc7b\xb8v[\x19\u04ab\u0260\x83\xab\x8f:\xady@\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4y\xc1\xbe\x19q\x1fs\xbe\xe4\xe61j\xe7T\x94Y\xaa\u03a2\xe0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4y\xc6\x00/\x84R\xca\x15\u007f\x13\x17\xe8\n/\xaf$GUY\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4y\xca\xc6IO\x11\xef'\x98t\x8c\xb52\x85\xbd\x8e\"\xf9|\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4y\u03e9x\n\xe6\xd8{,1\x88?\t'i\x86\u021ag5\x8965\u026d\xc5\u07a0\x00\x00\u07d4y\u06e2VG-\xb4\xe0X\xf2\xe4\xcd\xc3\xeaN\x8aBw83\x89O%\x91\xf8\x96\xa6P\x00\x00\u07d4y\xed\x10\xcf\x1fm\xb4\x82\x06\xb5\t\x19\xb9\xb6\x97\b\x1f\xbd\xaa\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u0794y\xf0\x8e\x01\xce\t\x88\xe6<\u007f\x8f)\b\xfa\xdeC\xc7\xf9\xf5\u0248\xfc\x93c\x92\x80\x1c\x00\x00\u07d4y\xfdmH1Pf\xc2\x04\xf9e\x18i\xc1\tl\x14\xfc\x97\x81\x89lk\x93[\x8b\xbd@\x00\x00\u0794y\xff\xb4\xac\x13\x81*\vx\u0123{\x82u\">\x17k\xfd\xa5\x88\xf0\x15\xf2W6B\x00\x00\u07d4z\x05\x89\xb1C\xa8\xe5\xe1\a\u026cf\xa9\xf9\xf8Yz\xb3\u7ac9Q\xe92\xd7n\x8f{\x00\x00\u07d4z\nx\xa9\xcc9?\x91\xc3\xd9\xe3\x9ak\x8c\x06\x9f\a^k\xf5\x89Hz\x9a0E9D\x00\x00\u07d4z\x13p\xa7B\xec&\x87\xe7a\xa1\x9a\u0167\x942\x9e\xe6t\x04\x89\xa2\xa12ga\xe2\x92\x00\x00\xe0\x94z-\xfcw\x0e$6\x811\xb7\x84w\x95\xf2\x03\xf3\xd5\r[V\x8a\x02i\xfe\xc7\xf06\x1d \x00\x00\u07d4z3\x83N\x85\x83s>-R\xae\xadX\x9b\u046f\xfb\x1d\xd2V\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94z6\xab\xa5\xc3\x1e\xa0\xca~'{\xaa2\xecF\u0393\xcfu\x06\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94z8\x11\"\xba\xday\x1az\xb1\xf6\x03}\xac\x80C'S\xba\xad\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94zH\xd8w\xb6:\x8f\x8f\x93\x83\xe9\xd0\x1eS\xe8\fR\x8e\x95_\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4zO\x9b\x85\x06\x90\xc7\xc9F\x00\xdb\xee\f\xa4\xb0\xa4\x11\xe9\xc2!\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4zc\x86\x9f\xc7g\xa4\u01b1\xcd\x0e\x06I\xf3cL\xb1!\xd2K\x89\x043\x87Oc,\xc6\x00\x00\u07d4zg\xdd\x04:PO\xc2\xf2\xfcq\x94\xe9\xbe\xcfHL\xec\xb1\xfb\x89\r\x8drkqw\xa8\x00\x00\xe0\x94zk&\xf48\u0663RD\x91U\xb8\x87l\xbd\x17\xc9\u065bd\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4zmx\x1cw\u013a\x1f\xca\xdfhsA\xc1\xe3\x17\x99\xe9='\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4zph\xe1\xc37\\\x0eY\x9d\xb1\xfb\xe6\xb2\xea#\xb8\xf4\a\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4zt\xce\xe4\xfa\x0fcp\xa7\x89O\x11l\xd0\f\x11G\xb8>Y\x89+^:\xf1k\x18\x80\x00\x00\u07d4zy\xe3\x0f\xf0W\xf7\n=\x01\x91\xf7\xf5?v\x157\xaf}\xff\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94zzO\x80sW\xa4\xbb\xe6\x8e\x1a\xa8\x0692\x10\xc4\x11\u0333\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4z\x85c\x86y\x01 o?+\xf0\xfa>\x1c\x81\t\u02bc\u0345\x89\amA\xc6$\x94\x84\x00\x00\xe0\x94z\x87\x97i\n\xb7{Tp\xbf|\f\x1b\xbaa%\b\xe1\xac}\x8a\x01\xe0\x92\x96\xc37\x8d\xe4\x00\x00\u07d4z\x8c\x89\xc0\x14P\x9dV\u05f6\x810f\x8f\xf6\xa3\xec\xecsp\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94z\x94\xb1\x99\x92\u03b8\xcec\xbc\x92\xeeKZ\xde\xd1\fM\x97%\x8a\x03\x8d\x1a\x80d\xbbd\xc8\x00\x00\u07d4z\xa7\x9a\xc0C\x16\u030d\b\xf2\x00e\xba\xa6\xd4\x14(\x97\xd5N\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4z\xadM\xbc\u04ec\xf9\x97\u07d3XiV\xf7+d\u062d\x94\xee\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94z\xb2V\xb2\x04\x80\n\xf2\x017\xfa\xbc\xc9\x16\xa22Xu%\x01\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4z\xbaV\xf6:H\xbc\b\x17\u05b9p9\x03\x9az\xd6/\xae.\x89 \x86\xac5\x10R`\x00\x00\xe0\x94z\xbb\x10\xf5\xbd\x9b\xc3;\x8e\xc1\xa8-d\xb5[k\x18wuA\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4z\u010d@\xc6d\u031am\x89\xf1\xc5\xf5\xc8\n\x1cp\xe7D\u6263\x10b\xbe\xee\xd7\x00\x00\x00\u07d4z\u014fo\xfcO\x81\a\xaen07\x8eN\x9f\x99\xc5\u007f\xbb$\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4z\xd3\xf3\aao\x19\u0731C\xe6DM\xab\x9c<3a\x1fR\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4z\xd8,\xae\xa1\xa8\xb4\xed\x051\x9b\x9c\x98p\x17<\x81N\x06\xee\x89!d\xb7\xa0J\u0220\x00\x00\u07d4z\xde]f\xb9D\xbb\x86\f\x0e\xfd\xc8bv\u054fFS\xf7\x11\x89lk\x93[\x8b\xbd@\x00\x00\u07d4z\xdf\xed\xb0m\x91\xf3\xccs\x90E\v\x85U\x02p\x88<{\xb7\x89\x11x\xfa@Q]\xb4\x00\x00\u07d4z\xe1\xc1\x9eS\xc7\x1c\xeeLs\xfa\xe2\xd7\xfcs\xbf\x9a\xb5\u348965\u026d\xc5\u07a0\x00\x00\u07d4z\xe6Y\xeb;\xc4hR\xfa\x86\xfa\xc4\xe2\x1cv\x8dP8\x89E\x89\x0f\x81\f\x1c\xb5\x01\xb8\x00\x00\u07d4z\xea%\xd4+&\x12(n\x99\xc56\x97\u01bcA\x00\xe2\u06ff\x89lk\x93[\x8b\xbd@\x00\x00\u07d4z\xef{U\x1f\v\x9cF\xe7U\xc0\xf3\x8e[:s\xfe\x11\x99\xf5\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4{\v1\xffn$t^\xad\x8e\u067b\x85\xfc\v\xf2\xfe\x1dU\u0509+^:\xf1k\x18\x80\x00\x00\xe0\x94{\x0f\xea\x11v\xd5!Y3:\x14<)IC\xda6\xbb\u0774\x8a\x01\xfc}\xa6N\xa1L\x10\x00\x00\u07d4{\x11g<\xc0\x19bk)\f\xbd\xce&\x04o~m\x14\x1e!\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4{\x12!b\xc9\x13\xe7\x14l\xad\v~\xd3z\xff\xc9*\v\xf2\u007f\x89Q\xaf\tk#\x01\u0440\x00\u07d4{\x1b\xf5:\x9c\xbe\x83\xa7\u07a44W\x9f\xe7*\xac\x8d*\f\u0409\n\xd4\xc81j\v\f\x00\x00\u07d4{\x1d\xaf\x14\x89\x1b\x8a\x1e\x1b\xd4)\u0633k\x9aJ\xa1\u066f\xbf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4{\x1f\xe1\xabM\xfd\x00\x88\xcd\xd7\xf6\x01c\xefY\xec*\xee\x06\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4{%\xbb\x9c\xa8\xe7\x02!~\x933\"RP\xe5<6\x80MH\x89e\xea=\xb7UF`\x00\x00\u07d4{'\xd0\xd1\xf3\xdd<\x14\x02\x94\xd0H\x8bx>\xbf@\x15'}\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94{@\a\xc4^ZW?\u06f6\xf8\xbdtk\xf9J\xd0J<&\x8a\x038!\xf5\x13]%\x9a\x00\x00\u07d4{C\xc7\xee\xa8\xd6#U\xb0\xa8\xa8\x1d\xa0\x81\xc6Dk3\xe9\xe0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4{M*8&\x90i\xc1\x85Ww\rY\x1d$\xc5\x12\x1f^\x83\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94{au\xec\x9b\xef\xc78$\x955\xdd\xde4h\x8c\xd3n\xdf%\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94{f\x12hy\x84M\xfa4\xfee\xc9\xf2\x88\x11\u007f\xef\xb4I\xad\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4{j\x84q\x8d\xd8nc3\x84)\xac\x81\x1d|\x8a\x86\x0f!\xf1\x89a\t=|,m8\x00\x00\xe0\x94{q,z\xf1\x16v\x00jf\xd2\xfc\\\x1a\xb4\xc4y\xce`7\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4{s$-u\u029a\xd5X\xd6P)\r\xf1v\x92\xd5L\u0638\x89lnY\xe6|xT\x00\x00\u07d4{v\x1f\xeb\u007f\u03e7\xde\xd1\xf0\xeb\x05\x8fJ`\v\xf3\xa7\b\u02c9\xf9]\xd2\xec'\xcc\xe0\x00\x00\xe0\x94{\x82|\xae\u007f\xf4t\t\x18\xf2\xe00\xab&\u02d8\xc4\xf4l\xf5\x8a\x01\x94hL\v9\xde\x10\x00\x00\xe0\x94{\x892\x86B~r\xdb!\x9a!\xfcM\xcd_\xbfY(<1\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4{\x92&\xd4o\xe7Q\x94\v\xc4\x16\xa7\x98\xb6\x9c\xcf\r\xfa\xb6g\x89\u3bb5sr@\xa0\x00\x00\u07d4{\x98\xe2<\xb9k\xee\xe8\n\x16\x80i\ube8f \xed\xd5\\\u03c9\v\xa0\xc9\x15\x87\xc1J\x00\x00\u07d4{\xb0\xfd\xf5\xa6c\xb5\xfb\xa2\x8d\x9c\x90*\xf0\xc8\x11\xe2R\xf2\x98\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4{\xb9W\x1f9K\v\x1a\x8e\xbaVd\xe9\u0635\xe8@g{\xea\x89\x01\x11du\x9f\xfb2\x00\x00\xe0\x94{\xb9\x84\xc6\u06f9\xe2y\x96j\xfa\xfd\xa5\x9c\x01\xd0&'\xc8\x04\x8a\x01\xb4d1\x1dE\xa6\x88\x00\x00\u07d4{\xbb\xec^p\xbd\xea\u063b2\xb4(\x05\x98\x8e\x96H\xc0\xaa\x97\x8966\u05ef^\u024e\x00\x00\u07d4{\xca\x1d\xa6\xc8\nf\xba\xa5\xdbZ\u0245A\u013e'kD}\x89$\xcf\x04\x96\x80\xfa<\x00\x00\u07d4{\u0772\xee\x98\xde\x19\xeeL\x91\xf6a\xee\x8eg\xa9\x1d\x05K\x97\x8965\u026d\xc5\u07a0\x00\x00\u0794{\xe2\xf7h\f\x80-\xa6\x15L\x92\xc0\x19J\xe72Qzqi\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4{\xe7\xf2Eiq\x88;\x9a\x8d\xbeL\x91\xde\xc0\x8a\xc3N\x88b\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4{\xe8\u0334\xf1\x1bf\xcan\x1dW\xc0\xb59b!\xa3\x1b\xa5:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94{\xeb\x81\xfb/^\x91Rk*\xc9y^v\u019b\xcf\xf0K\xc0\x8a\x0e\xb2.yO\n\x8d`\x00\x00\u07d4|\b\x83\x05L-\x02\xbcz\x85+\x1f\x86\xc4'w\xd0\xd5\xc8V\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4|\x0f^\a C\xc9\xeet\x02B\x19~x\xccK\x98\xcd\xf9`\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|\x1d\xf2JO\u007f\xb2\u01f4r\xe0\xbb\x00l\xb2}\xcd\x16AV\x8965\u026d\xc5\u07a0\x00\x00\u07d4|)\xd4}W\xa73\xf5k\x9b!pc\xb5\x13\xdc;1Y#\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4|+\x96\x03\x88JO.FN\u03b9}\x17\x93\x8d\x82\x8b\xc0,\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4|8,\x02\x96a.N\x97\xe4@\xe0-8q';U\xf5;\x89\n\xb6@9\x12\x010\x00\x00\u07d4|>\xb7\x13\xc4\xc9\xe08\x1c\xd8\x15L|\x9a}\xb8d\\\xde\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|D\x01\xae\x98\xf1.\xf6\xde9\xae$\u03df\xc5\x1f\x80\xeb\xa1k\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|E\xf0\xf8D*V\xdb\u04dd\xbf\x15\x99\x95A\\R\xedG\x9b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94|S-\xb9\xe0\xc0l&\xfd@\xac\xc5j\xc5\\\x1e\xe9-<:\x8a?\x87\bW\xa3\xe0\xe3\x80\x00\x00\u07d4|`\xa0_zJ_\x8c\xf2xC\x916.uZ\x83A\xefY\x89f\x94\xf0\x18*7\xae\x00\x00\u07d4|`\xe5\x1f\v\xe2(\xe4\xd5o\xdd)\x92\xc8\x14\xdaw@\u01bc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4|i$\xd0|>\xf5\x89\x19f\xfe\nxV\xc8{\xef\x9d 4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94|\x8b\xb6Zo\xbbI\xbdA3\x96\xa9\xd7\xe3\x10S\xbb\xb3z\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94|\x9a\x11\f\xb1\x1f%\x98\xb2\xb2\x0e,\xa4\x002^A\xe9\xdb3\x8a\x05\x81v{\xa6\x18\x9c@\x00\x00\u07d4|\xbc\xa8\x8f\xcaj\x00`\xb9`\x98\\\x9a\xa1\xb0%4\xdc\"\b\x89\x19\x12z\x13\x91\xea*\x00\x00\u07d4|\xbe\xb9\x992\xe9~n\x02\x05\x8c\xfcb\u0432k\xc7\u0325+\x89lk\x93[\x8b\xbd@\x00\x00\u07d4|\xc2Jj\x95\x8c \xc7\xd1$\x96`\xf7Xb&\x95\v\r\x9a\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4|\xd2\x0e\u0335\x18\xb6\f\xab\t[r\x0fW\x15p\u02aaD~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4|\xd5\xd8\x1e\xab7\xe1\x1ebv\xa3\xa1\t\x12Q`~\r~8\x89\x03hM^\xf9\x81\xf4\x00\x00\u07d4|\xdft!9E\x95=\xb3\x9a\xd0\xe8\xa9x\x1a\xddy.M\x1d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4|\xe4hdF\U000547be\xd6r\x15\xeb\rZ\x1d\xd7,\x11\xb8\x89x9\xd3!\xb8\x1a\xb8\x00\x00\u07d4|\xefMC\xaaA\u007f\x9e\xf8\xb7\x87\xf8\xb9\x9dS\xf1\xfe\xa1\ue209g\x8a\x93 b\xe4\x18\x00\x00\u07d4}\x03P\xe4\v3\x8d\xdasfa\x87+\xe3?\x1f\x97R\xd7U\x89\x02\xb4\xf5\xa6\U00051500\x00\xe0\x94}\x04\xd2\xed\xc0X\xa1\xaf\xc7a\xd9\u025a\xe4\xfc\\\x85\xd4\u0226\x8aB\xa9\xc4g\\\x94g\xd0\x00\x00\u07d4}\v%^\xfbW\xe1\x0fp\b\xaa\"\xd4\x0e\x97R\xdf\xcf\x03x\x89\x01\x9f\x8euY\x92L\x00\x00\xe0\x94}\x13\xd6pX\x84\xab!W\u074d\xccpF\xca\xf5\x8e\xe9K\xe4\x8a\x1d\r\xa0|\xbb>\xe9\xc0\x00\x00\u07d4}'>c~\xf1\xea\u0101\x11\x94\x13\xb9\x1c\x98\x9d\xc5\xea\xc1\"\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4}*R\xa7\xcf\f\x846\xa8\xe0\a\x97kl&\xb7\"\x9d\x1e\x15\x89\x17\xbf\x06\xb3*$\x1c\x00\x00\u07d4}4\x805i\xe0\v\u05b5\x9f\xff\b\x1d\xfa\\\n\xb4\x19zb\x89\\\xd8|\xb7\xb9\xfb\x86\x00\x00\u07d4}4\xffY\xae\x84\nt\x13\u01baL[\xb2\xba,u\xea\xb0\x18\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4}9(R\xf3\xab\xd9/\xf4\xbb[\xb2l\xb6\bt\xf2\xbeg\x95\x8966\xc2^f\xec\xe7\x00\x00\u07d4}DRg\u015a\xb8\u04a2\xd9\xe7\t\x99\x0e\th%\x80\u011f\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94}U\x13\x97\xf7\x9a)\x88\xb0d\xaf\xd0\xef\xeb\xee\x80,w!\xbc\x8a\bW\xe0\xd6\xf1\xdav\xa0\x00\x00\u07d4}Z\xa3?\xc1KQ\x84\x1a\x06\x90n\xdb+\xb4\x9c*\x11ri\x89\x10D\x00\xa2G\x0eh\x00\x00\xe0\x94}]/s\x94\x9d\xad\xda\bV\xb2\x06\x98\x9d\xf0\a\x8dQ\xa1\xe5\x8a\x02\xc4:H\x1d\xf0M\x01wb\xed\xcb\\\xaab\x9bZ\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4~\x8f\x96\xcc)\xf5{\tu\x12\f\xb5\x93\xb7\u0743=`kS\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4~\x97*\x8a|*D\xc9;!Cl8\xd2\x1b\x92R\xc3E\xfe\x89a\t=|,m8\x00\x00\u07d4~\x99\u07fe\x98\x9d;\xa5)\u0457Q\xb7\xf41\u007f\x89S\xa3\xe2\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4~\xa0\xf9n\xe0\xa5s\xa30\xb5h\x97v\x1f=L\x010\xa8\xe3\x89Hz\x9a0E9D\x00\x00\u0794~\xa7\x91\xeb\xab\x04E\xa0\x0e\xfd\xfcNJ\x8e\x9a~ue\x13m\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4~\xab\xa05\xe2\xaf7\x93\xfdtgK\x10%@\xcf\x19\n\u0779\x89E\x02l\x83[`D\x00\x00\xe0\x94~\xb4\xb0\x18\\\x92\xb6C\x9a\b\xe72!h\xcb5<\x8awJ\x8a\x02'\x19l\xa0I\x83\xca\x00\x00\xe0\x94~\xbd\x95\xe9\xc4p\xf7(5\x83\xdcn\x9d,M\xce\v\ua3c4\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4~\u0425\xa8G\xbe\xf9\xa9\xda|\xba\x1dd\x11\xf5\xc3\x161&\x19\x89\x02(\xeb7\xe8u\x1d\x00\x00\u07d4~\xda\xfb\xa8\x98K\xafc\x1a\x82\vk\x92\xbb\xc2\xc56U\xf6\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4~\xdb\x02\xc6\x1a\"r\x87a\x1a\xd9Pici\xccNdzh\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4~\xe5\u0280]\xce#\xaf\x89\xc2\xd4D\xe7\xe4\af\xc5Lt\x04\x89\r\v\xd4\x12\xed\xbd\x82\x00\x00\xe0\x94~\xe6\x04\u01e9\xdc)\t\xce2\x1d\u6e72OWgWuU\x8a\x01+\xf9\u01d8\\\xf6-\x80\x00\u07d4~\xf1o\xd8\xd1[7\x8a\x0f\xba0k\x8d\x03\u0758\xfc\x92a\x9f\x89%\xf2s\x93=\xb5p\x00\x00\u07d4~\xf9\x8bR\xbe\xe9S\xbe\xf9\x92\xf3\x05\xfd\xa0'\xf8\x91\x1cXQ\x89\x1b\xe7\" i\x96\xbc\x80\x00\u07d4~\xfc\x90vj\x00\xbcR7,\xac\x97\xfa\xbd\x8a<\x83\x1f\x8e\u0349\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4~\xfe\xc0\xc6%<\xaf9\u007fq(|\x1c\a\xf6\xc9X+[\x86\x89\x1a,\xbc\xb8O0\u0540\x00\u07d4\u007f\x01\xdc|7G\xca`\x8f\x98=\xfc\x8c\x9b9\xe7U\xa3\xb9\x14\x89\v8l\xad_zZ\x00\x00\u07d4\u007f\x06b\xb4\x10)\x8c\x99\xf3\x11\u04e1EJ\x1e\xed\xba/\xeav\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\u007f\x06\u021dY\x80\u007f\xa6\v\xc6\x016\xfc\xf8\x14\u02ef%C\xbd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u007f\v\x90\xa1\xfd\u050f'\xb2h\xfe\xb3\x83\x82\xe5]\xdbP\xef\x0f\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\u007f\x0e\xc3\u06c0F\x92\xd4\xd1\xea2E6Z\xab\x05\x90\a[\u0109\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u007f\x0f\x04\xfc\xf3zS\xa4\xe2N\xden\x93\x10Nx\xbe\x1d<\x9e\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u007f\x13\xd7`I\x8dq\x93\xcahY\xbc\x95\xc9\x018d#\xd7l\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\u007f\x15\n\xfb\x1aw\u00b4Y(\xc2h\xc1\u9f74d\x1dG\u0609lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\x16\x19\x98\x8f7\x15\xe9O\xf1\xd2S&-\xc5X\x1d\xb3\xde\x1c\x890\xca\x02O\x98{\x90\x00\x00\u07d4\u007f\x1c\x81\xee\x16\x97\xfc\x14K|\v\xe5I;V\x15\xae\u007f\xdd\u0289\x1b\x1d\xaba\u04ead\x00\x00\u07d4\u007f#\x82\xff\xd8\xf89VFy7\xf9\xbar7F#\xf1\x1b8\x89 \x86\xac5\x10R`\x00\x00\u07d4\u007f7\t9\x1f?\xbe\xba5\x92\xd1u\xc7@\xe8z\tT\x1d\x02\x89\x1a\x05V\x90\xd9\u06c0\x00\x00\u07d4\u007f8\x9c\x12\xf3\xc6\x16OdFVlwf\x95\x03\xc2y%'\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\xe0\x94\u007f:\x1eE\xf6~\x92\u0200\xe5s\xb43y\xd7\x1e\xe0\x89\xdbT\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\u007f=r\x03\u0224G\xf7\xbf6\u060a\xe9\xb6\x06*^\xeex\xae\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\u007fF\xbb%F\r\xd7\xda\xe4!\x1c\xa7\xf1Z\xd3\x12\xfc}\xc7\\\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\u007fI\xe7\xa4&\x98\x82\xbd\x87\"\u0526\xf5f4v)b@y\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u007fI\xf2\a&G\x1a\xc1\u01e8>\xf1\x06\xe9w\\\xebf%f\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\u007fK^'\x85x\xc0F\xcc\xea\xf6W0\xa0\xe0h2\x9e\u0576\x89e\xea=\xb7UF`\x00\x00\u07d4\u007fOY;a\x8c3\v\xa2\xc3\xd5\xf4\x1e\xce\xeb\x92\xe2~Bl\x89\x96n\xdcuk|\xfc\x00\x00\u07d4\u007fT\x14\x91\u04ac\x00\xd2a/\x94\xaa\u007f\v\xcb\x01FQ\xfb\u0509\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\u007fZ\xe0Z\xe0\xf8\xcb\xe5\xdf\xe7!\xf0D\u05e7\xbe\xf4\xc2y\x97\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\u007f`:\xec\x17Y\xea_\a\xc7\xf8\xd4\x1a\x14(\xfb\xba\xf9\xe7b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u007falo\x00\x8a\u07e0\x82\xf3M\xa7\xd0e\x04`6\x80u\xfb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u007fa\xfal\xf5\xf8\x98\xb4@\xda\u016b\xd8`\rmi\x1f\xde\xf9\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94\u007fe\\g\x89\xed\xdfE\\\xb4\xb8\x80\x99r\x0698\x9e\ubb0a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u007fk(\u0204!\xe4\x85~E\x92\x81\u05c4ai$\x89\xd3\xfb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007fn\xfboC\x18\x87m.\xe6$\xe2u\x95\xf4DF\xf6\x8e\x93\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u007fq\x92\xc0\xdf\x1c}\xb6\xd9\xede\xd7\x11\x84\xd8\xe4\x15Z\x17\xba\x89\x04Sr\x8d3\x94,\x00\x00\u07d4\u007fz:!\xb3\xf5\xa6]\x81\xe0\xfc\xb7\xd5-\xd0\n\x1a\xa3m\xba\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u007f\x8d\xbc\xe1\x80\xed\x9cV65\xaa\xd2\xd9{L\xbcB\x89\x06\u0649\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\u007f\x99=\xdb~\x02\u0082\xb8\x98\xf6\x15_h\x0e\xf5\xb9\xaf\xf9\a\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\u007f\x9f\x9bV\xe4(\x9d\xfbX\xe7\x0f\xd5\xf1*\x97\xb5m5\u01a5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u007f\xa3~\xd6x\x87u\x1aG\x1f\x0e\xb3\x06\xbeD\xe0\xdb\xcd`\x89\x899vt\u007f\xe1\x1a\x10\x00\x00\u07d4\u007f\xaa0\xc3\x15\x19\xb5\x84\xe9rP\xed*<\xf38^\xd5\xfdP\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xcf[\xa6fo\x96lTH\xc1{\xf1\xcb\v\xbc\xd8\x01\x9b\x06\x89\x05k\xc3\u042e\xbeI\x80\x00\xe0\x94\u007f\xd6y\xe5\xfb\r\xa2\xa5\xd1\x16\x19M\xcbP\x83\x18\xed\u0140\xf3\x8a\x01c\x9eI\xbb\xa1b\x80\x00\x00\u07d4\u007f\u06e01\u01cf\x9c\tmb\xd0Z6\x9e\uac3c\xccU\u5257\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\u007f\xdb\u00e8D\xe4\r\x96\xb2\xf3\xa652.`e\xf4\xca\x0e\x84\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xdf\u020dx\xbf\x1b(Z\xc6O\x1a\xdb5\xdc\x11\xfc\xb09Q\x89|\x06\xfd\xa0/\xb06\x00\x00\u07d4\u007f\xea\x19b\xe3]b\x05\x97h\xc7I\xbe\u0756\u02b90\xd3x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u007f\xef\x8c8w\x9f\xb3\a\xeco\x04K\xeb\xe4\u007f<\xfa\xe7\x96\xf1\x89\t#@\xf8l\xf0\x9e\x80\x00\u07d4\u007f\xf0\xc6?p$\x1b\xec\xe1\x9bs~SA\xb1+\x10\x901\u0609\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\u007f\xfa\xbf\xbc9\f\xbeC\u0389\x18\x8f\bh\xb2}\xcb\x0f\f\xad\x8a\x01YQ\x82\"K&H\x00\x00\xe0\x94\u007f\xfd\x02\xed7\fp`\xb2\xaeS\xc0x\xc8\x01!\x90\u07fbu\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u0794\x80\x02*\x12\a\xe9\x10\x91\x1f\xc9(I\xb0i\xab\f\xda\xd0C\u04c8\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\x80\t\xa7\xcb\u0452\xb3\xae\u052d\xb9\x83\xd5(ER\xc1ltQ\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80\x0e}c\x1cnW:\x903/\x17\xf7\x1f_\u045bR\x8c\xb9\x89\b=lz\xabc`\x00\x00\u07d4\x80\x15m\x10\ufa320\u0254\x10c\r7\xe2i\xd4\t<\xea\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\x172\xa4\x81\u00c0\xe5~\xd6-l)\u0799\x8a\xf3\xfa;\x13\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x1de\xc5\x18\xb1\x1d\x0e?OG\x02!Ap\x13\xc8\xe5>\u0149\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80&CZ\xacr\x8dI{\x19\xb3\xe7\xe5|(\xc5c\x95O+\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x80-\xc3\xc4\xff-}\x92^\u215fJ\x06\u05fa`\xf10\x8c\x89\x05P\x94\f\x8f\xd3L\x00\x00\u07d4\x800\xb1\x11\u0198?\x04\x85\u076c\xa7b$\xc6\x18\x064x\x9f\x89\x04V9\x18$O@\x00\x00\u07d4\x805\xbc\xff\xae\xfd\xee\xea5\x83\fI}\x14(\x9d6 #\u0789\x10CV\x1a\x88)0\x00\x00\u07d4\x805\xfeNkj\xf2z\u44a5xQ^\x9d9\xfao\xa6[\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80C\xed\"\xf9\x97\u58a4\xc1n6D\x86\xaed\x97V\x92\u0109=I\x04\xff\xc9\x11.\x80\x00\u07d4\x80C\xfd\u043cL\x97=\x16c\xd5_\xc15P\x8e\xc5\xd4\xf4\xfa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x80L\xa9IrcOc:Q\xf3V\v\x1d\x06\xc0\xb2\x93\xb3\xb1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x80R-\u07d4N\xc5.'\xd7$\xedL\x93\xe1\xf7\xbe`\x83\u0589\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x80Y\x1aB\x17\x9f4\xe6M\x9d\xf7]\xcdF;(hoUt\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x80\\\xe5\x12\x97\xa0y;\x81 g\xf0\x17\xb3\xe7\xb2\u07db\xb1\xf9\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x80]\x84o\xb0\xbc\x02\xa73r&\u0585\xbe\x9e\xe7s\xb9\x19\x8a\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x80c7\x9a{\xf2\u02d2:\x84\xc5\t>h\xda\xc7\xf7T\x81\u0149\x11v\x10.n2\xdf\x00\x00\u07d4\x80hTX\x8e\xcc\xe5AI_\x81\u008a)\x03s\xdf\x02t\xb2\x89\x1f\x8c\xdf\\n\x8dX\x00\x00\u07d4\x80oD\xbd\xebh\x807\x01^\x84\xff!\x80I\xe3\x823*3\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\x80tF\x18\xde9jT1\x97\xeeH\x94\xab\xd0c\x98\xdd|'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80w\xc3\xe4\xc4EXn\tL\xe1\x02\x93\u007f\xa0[s{V\x8c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x90\u007fY1H\xb5|F\xc1w\xe2=%\xab\u012a\xe1\x83a\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\x97s\x16\x94NYB\xe7\x9b\x0e:\xba\u04cd\xa7F\be\x19\x89\x02\x1auJm\xc5(\x00\x00\xe0\x94\x80\xa0\xf6\xcc\x18l\xf6 \x14\x00sn\x06Z9\x1fR\xa9\xdfJ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x80\xab\xecZ\xa3n\\\x9d\t\x8f\x1b\x94(\x81\xbdZ\xca\u0196=\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xb2=8\v\x82\\F\xe098\x99\xa8UVF-\xa0\u1309lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xb4-\xe1p\xdb\xd7#\xf4T\xe8\x8fw\x16E-\x92\x98P\x92\x89\x10F#\xc0v-\xd1\x00\x00\u07d4\x80\xb7\x9f3\x83\x90\u047a\x1b77\xa2\x9a\x02W\xe5\xd9\x1e\a1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x80\xbf\x99^\u063a\x92p\x1d\x10\xfe\u011f\x9e}\x01M\xbe\xe0&\x89\x1f\x047\xca\x1a~\x12\x80\x00\u07d4\x80\xc0N\xfd1\x0fD\x04\x83\xc7?tK[\x9edY\x9c\xe3\xec\x89A\rXj \xa4\xc0\x00\x00\u07d4\x80\u00e9\xf6\x95\xb1m\xb1Yr\x86\u0473\xa8\xb7il9\xfa'\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x80\xc5>\xe7\xe35\u007f\x94\xce\rxh\x00\x9c \x8bJ\x13\x01%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x80\xcc!\xbd\x99\xf3\x90\x05\u014f\xe4\xa4H\x90\x92 !\x8ff\u02c966\xc9yd6t\x00\x00\u07d4\x80\xd5\xc4\fY\xc7\xf5N\xa3\xa5_\xcf\xd1uG\x1e\xa3P\x99\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x80\xda/\u0762\x9a\x9e'\xf9\xe1\x15\x97^i\xae\x9c\xfb\xf3\xf2~\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x80\xe7\xb3 R0\xa5f\xa1\xf0a\xd9\"\x81\x9b\xb4\xd4\u04a0\xe1\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x80\xea\x1a\xcc\x13n\xcaKh\xc8B\xa9Z\xdfk\u007f\xee~\xb8\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x80\xf0z\xc0\x9e{,<\n=\x1e\x94\x13\xa5D\xc7:A\xbe\u02c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x81\r\xb2Vu\xf4^\xa4\xc7\xf3\x17\u007f7\xce)\xe2-g\x99\x9c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81\x13\x9b\xfd\u0326V\xc40 ?r\x95\x8cT;e\x80\xd4\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x81\x14a\xa2\xb0\u0290\xba\xda\xc0j\x9e\xa1nx{3\xb1\x96\u0309\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\x81\x16M\xeb\x10\x81J\xe0\x83\x91\xf3,\bf{bH\xc2}z\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\x81\x18i1\x18A7\xd1\x19*\u020c\xd3\xe1\xe5\xd0\xfd\xb8jt\x89\x9d5\x95\xab$8\xd0\x00\x00\u0794\x81*U\xc4<\xae\xdcYr\x187\x90\x00\xceQ\rT\x886\xfd\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x81.\xa7\xa3\xb2\xc8n\xed2\xffO,sQL\xc6;\xac\xfb\u038965\u026d\xc5\u07a0\x00\x00\u07d4\x814\xdd\x1c\x9d\xf0\xd6\u0225\x81$&\xbbU\xc7a\u0283\x1f\b\x89\x06\xa2\x16\v\xb5|\xcc\x00\x00\u07d4\x81A5\u068f\x98\x11\aW\x83\xbf\x1a\xb6pb\xaf\x8d>\x9f@\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x81I\x8c\xa0{\x0f/\x17\xe8\xbb\xc7\xe6\x1a\u007fJ\xe7\xbef\xb7\x8b\x89\x05\x81\xfb\xb5\xb3;\xb0\x00\x00\u07d4\x81Um\xb2sI\xab\x8b'\x00ID\xedP\xa4n\x94\x1a\x0f_\x89\u063beI\xb0+\xb8\x00\x00\u07d4\x81U\xfalQ\xeb1\xd8\bA-t\x8a\xa0\x86\x10P\x18\x12/\x89e\xea=\xb7UF`\x00\x00\xe0\x94\x81V6\v\xbd7\ta\xce\xcakf\x91\xd7P\x06\xad L\xf2\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x81a\xd9@\xc3v\x01\x00\xb9\b\x05)\xf8\xa6\x03%\x03\x0fn\u0709\x10CV\x1a\x88)0\x00\x00\xe0\x94\x81d\xe7\x83\x14\xae\x16\xb2\x89&\xccU=,\xcb\x16\xf3V'\r\x8a\x01\xca\x13N\x95\xfb2\xc8\x00\x00\u07d4\x81e\u02b0\xea\xfbZ2\x8f\xc4\x1a\xc6M\xaeq[.\xef,e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81h\xed\xce\u007f)a\xcf)[\x9f\xcdZE\xc0l\xde\xdan\xf5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81m\x97r\xcf\x119\x91\x16\xcc\x1er\xc2lgt\xc9\xed\xd79\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x81s\xc85djg.\x01R\xbe\x10\xff\xe8Ab\xdd%nL\x89\x1a\xab\xdf!E\xb40\x00\x00\u07d4\x81t\x93\u035b\xc6#p*$\xa5o\x9f\x82\xe3\xfdH\xf3\xcd1\x89\x9eK#\xf1-L\xa0\x00\x00\u07d4\x81y\xc8\tp\x18,\u0177\xd8*M\xf0n\xa9M\xb6:%\xf3\x89'o%\x9d\xe6k\xf4\x00\x00\u07d4\x81z\xc3;\xd8\xf8GVsr\x95\x1fJ\x10\u05e9\x1c\xe3\xf40\x89\n\xd7\xc4\x06\xc6m\xc1\x80\x00\xe0\x94\x81\x8f\xfe'\x1f\u00d75e\xc3\x03\xf2\x13\xf6\xd2\u0689\x89~\xbd\x8a\x016\xe0SB\xfe\u1e40\x00\u07d4\x81\x97\x94\x81!s.c\xd9\xc1H\x19N\xca\xd4n0\xb7I\u0209\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x81\x9a\xf9\xa1\xc2s2\xb1\xc3i\xbb\xda\x1b=\xe1\xc6\xe93\xd6@\x89\x11\t\xe6T\xb9\x8fz\x00\x00\xe0\x94\x81\x9c\u06a506x\xef|\xecY\u050c\x82\x16:\xcc`\xb9R\x8a\x03\x13QT_y\x81l\x00\x00\u07d4\x81\x9e\xb4\x99\vZ\xbaUG\t=\xa1+k<\x10\x93\xdfmF\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81\xa8\x81\x96\xfa\xc5\xf2<>\x12\xa6\x9d\xecK\x88\x0e\xb7\xd9s\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x81\xbc\xcb\xff\x8fD4~\xb7\xfc\xa9['\xce|\x95$\x92\xaa\xad\x89\b@\xc1!e\xddx\x00\x00\u07d4\x81\xbdu\xab\xd8e\xe0\xc3\xf0J\vO\xdb\xcbt\xd3@\x82\xfb\xb7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x81\xc1\x8c*#\x8d\xdcL\xba#\n\a-\xd7\xdc\x10\x1eb\x02s\x89Hz\x9a0E9D\x00\x00\u07d4\x81\xc9\xe1\xae\xe2\xd36]S\xbc\xfd\u0356\xc7\xc58\xb0\xfd~\xec\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\x81\u03edv\t\x13\xd3\xc3\"\xfc\xc7{I\u00ae9\a\xe7On\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x81\xd6\x19\xffW&\xf2@_\x12\x90Lr\xeb\x1e$\xa0\xaa\xeeO\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x81\xef\u25aev\xc8`\xd1\xc5\xfb\xd3=G\xe8\u0399\x96\xd1W\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x81\xf8\xde,(=_\u052f\xbd\xa8]\xed\xf9v\x0e\xab\xbb\xb5r\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x82\f\x19)\x11\x96P[e\x05\x9d\x99\x14\xb7\t\v\xe1\u06c7\u0789\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x82\x1c\xb5\xcd\x05\xc7\uf41f\xe1\xbe`s=\x89c\xd7`\xdcA\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x82\x1dy\x8a\xf1\x99\x89\u00ee[\x84\xa7\xa7(<\xd7\xfd\xa1\xfa\xbe\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x82\x1e\xb9\t\x94\xa2\xfb\xf9K\xdc23\x91\x02\x96\xf7o\x9b\xf6\xe7\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x82$\x9f\xe7\x0fa\u01b1o\x19\xa3$\x84\x0f\xdc\x02\x021\xbb\x02\x8a\x02\x036\xb0\x8a\x93c[\x00\x00\u07d4\x82(\xeb\xc0\x87H\x0f\xd6EG\xca(\x1f^\xac\xe3\x04\x14S\xb9\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\x82)\u03b9\xf0\xd7\b9I\x8dD\xe6\xab\xed\x93\xc5\xca\x05\x9f]\x8a\x1a\x1c\x1b<\x98\x9a \x10\x00\x00\u07d4\x82.\xdf\xf66V:a\x06\xe5.\x9a%\x98\xf7\xe6\xd0\xef'\x82\x89\x01\xf4\xf9i=B\u04c0\x00\u07d4\x822\x19\xa2Yv\xbb*\xa4\xaf\x8b\xadA\xac5&\xb4\x936\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x822\xd1\xf9t.\u07cd\xd9'\xda5;*\xe7\xb4\xcb\xceu\x92\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\x824\xf4c\u0444\x85P\x1f\x8f\x85\xac\xe4\x97,\x9bc-\xbc\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\x827htg7\xcem\xa3\x12\xd5>TSN\x10o\x96|\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82;\xa7dr8\xd1\x13\xbc\xe9\x96JC\u0420\x98\x11\x8b\xfeM\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x82@t1(\x06\xdaGHCBf\xee\x00!@\u305a\u0089Q\xb1\u04c3\x92a\xac\x00\x00\u07d4\x82C\x8f\u04b3*\x9b\xddgKI\xd8\xcc_\xa2\xef\xf9x\x18G\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82HW(\xd0\xe2\x81V7X\xc7Z\xb2~\xd9\u80a0\x00-\x89\a\xf8\b\xe9)\x1el\x00\x00\u07d4\x82K<\x19)]~\xf6\xfa\xa7\xf3t\xa4y\x84\x86\xa8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82Q5\x8c\xa4\xe0`\u0775Y\xcaX\xbc\v\u077e\xb4\a\x02\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82Q5\xb1\xa7\xfc\x16\x05aL\x8a\xa4\u042cm\xba\u040fH\x0e\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\x82S\t\xa7\xd4]\x18\x12\xf5\x1en\x8d\xf5\xa7\xb9ol\x90\x88\x87\x89\x804\xf7\u0671f\xd4\x00\x00\u07d4\x82Z\u007fN\x10\x94\x9c\xb6\xf8\x96Bh\xf1\xfa_W\xe7\x12\xb4\u0109\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x82a\xfa#\f\x90\x1dC\xffW\x9fG\x80\u04d9\xf3\x1e`v\xbc\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82b\x16\x9baXp\x13N\xb4\xacl_G\x1ck\xf2\xf7\x89\xfc\x89\x19\x12z\x13\x91\xea*\x00\x00\u07d4\x82c\xec\xe5\xd7\t\xe0\u05eeq\u0328h\xed7\xcd/\xef\x80{\x895\xab\x02\x8a\xc1T\xb8\x00\x00\xe0\x94\x82l\xe5y\x052\xe0T\x8ca\x02\xa3\r>\xac\x83k\xd68\x8f\x8a\x03\xcf\xc8.7\xe9\xa7@\x00\x00\u07d4\x82n\xb7\xcds\x19\xb8-\xd0z\x1f;@\x90q\xd9n9g\u007f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x82u1\xa6\u0141z\xe3_\x82\xb0\v\x97T\xfc\xf7LU\xe22\x89\xc3(\t>a\xee@\x00\x00\u0794\x82u\xcdhL6y\u0548}\x03fN3\x83E\xdc<\xdd\xe1\x88\xdbD\xe0I\xbb,\x00\x00\u07d4\x82\x84\x92;b\u62ff|+\x9f4\x14\xd1>\xf6\xc8\x12\xa9\x04\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\x82\x8b\xa6Q\u02d3\x0e\xd9xqV)\x9a=\xe4L\u040br\x12\x89Hz\x9a0E9D\x00\x00\u07d4\x82\xa1\\\xef\x1dl\x82`\xea\xf1Y\xea?\x01\x80\xd8g}\xce\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82\xa8\xb9kl\x9e\x13\xeb\xec\x1e\x9f\x18\xac\x02\xa6\x0e\xa8\x8aH\xff\x89lk\x8c@\x8es\xb3\x00\x00\u07d4\x82\xa8\u02ff\xdf\xf0+.8\xaeK\xbf\xca\x15\xf1\xf0\xe8;\x1a\xea\x89\x04\x9b\x99\x1c'\xefm\x80\x00\u07d4\x82\xe4F\x1e\xb9\xd8I\xf0\x04\x1c\x14\x04!\x9eBr\u0110\n\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x82\xe5w\xb5\x15\xcb+\b`\xaa\xfe\x1c\xe0\x9aY\xe0\x9f\xe7\xd0@\x89 \x86\xac5\x10R`\x00\x00\u07d4\x82\xea\x01\xe3\xbf.\x83\x83nqpN\"\xa2q\x93w\xef\xd9\u00c9\xa4\xccy\x95c\u00c0\x00\x00\u07d4\x82\xf2\xe9\x91\xfd2L_]\x17v\x8e\x9fa3]\xb61\x9dl\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x82\xf3\x9b'X\xaeB'{\x86\u059fu\xe6(\xd9X\xeb\u02b0\x8a\bxg\x83&\xea\xc9\x00\x00\x00\xe0\x94\x82\xf8T\xc9\xc2\xf0\x87\xdf\xfa\x98Z\xc8 \x1ebl\xa5Fv\x86\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\x82\xffqo\xdf\x03>\xc7\xe9B\xc9\t\u0643\x18g\xb8\xb6\xe2\xef\x89a\t=|,m8\x00\x00\u07d4\x83\b\xed\n\xf7\xf8\xa3\xc1u\x1f\xaf\xc8w\xb5\xa4*\xf7\xd3X\x82\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x83\x1cD\xb3\b@G\x18K*\xd2\x18h\x06@\x907P\xc4]\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x83!\x05\x83\xc1jN\x1e\x1d\xac\x84\xeb\xd3~=\x0f|W\ub909lk\x93[\x8b\xbd@\x00\x00\u07d4\x83,T\x17k\xdfC\xd2\u027c\u05f8\b\xb8\x95V\xb8\x9c\xbf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x833\x16\x98]Gt+\xfe\xd4\x10`J\x91\x95<\x05\xfb\x12\xb0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x834vK{9zNW\x8fP6M`\xceD\x89\x9b\xff\x94\x89\x05\x03\xb2\x03\xe9\xfb\xa2\x00\x00\xe0\x94\x83;j\x8e\xc8\xda@\x81\x86\xac\x8a}*m\xd6\x15#\xe7\u0384\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x83=?\xaeT*\xd5\xf8\xb5\f\xe1\x9b\xde+\xecW\x91\x80\u020c\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\x83=\xb4,\x14\x16<{\xe4\u02b8j\u0153\xe0bf\u0599\u054a$\xe4\r+iC\xef\x90\x00\x00\xe0\x94\x83V;\xc3d\ud060\xc6\xda;V\xffI\xbb\xf2g\x82z\x9c\x8a\x03\xab\x91\xd1{ \xdeP\x00\x00\u07d4\x83zd]\xc9\\IT\x9f\x89\x9cN\x8b\u03c7S$\xb2\xf5|\x89 \x8c9J\xf1\u0208\x00\x00\u07d4\x83\x8b\xd5e\xf9\x9f\xdeH\x05?y\x17\xfe3<\xf8J\xd5H\xab\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x83\x90\x8a\xa7G\x8am\x1c\x9b\x9b\x02\x81\x14\x8f\x8f\x9f$+\x9f\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\x83\x92\xe57vq5x\x01[\xffI@\xcfC\x84\x9d}\u02e1\x89\bM\xf05]V\x17\x00\x00\xe0\x94\x83\x97\xa1\xbcG\xac\xd6GA\x81Y\xb9\x9c\xeaW\xe1\xe6S-n\x8a\x01\xf1\x0f\xa8'\xb5P\xb4\x00\x00\u07d4\x83\x98\xe0~\xbc\xb4\xf7_\xf2\x11m\xe7|\x1c*\x99\xf3\x03\xa4\u03c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xa3\x14\x883\xd9dI\x84\xf7\xc4u\xa7\x85\a\x16\ufd00\xff\x89\xb8Pz\x82\a( \x00\x00\u07d4\x83\xa4\x02C\x8e\x05\x19w=TH2k\xfba\xf8\xb2\f\xf5-\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\x83\xa9;[\xa4\x1b\xf8\x87 \xe4\x15y\f\xdc\vg\xb4\xaf4\u0109\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x83\xc2=\x8aP!$\xee\x15\x0f\b\xd7\x1d\xc6rt\x10\xa0\xf9\x01\x8a\a3\x1f;\xfef\x1b\x18\x00\x00\u07d4\x83\u0217\xa8Ki^\xeb\xe4fy\xf7\xda\x19\xd7vb\x1c&\x94\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xd52\u04cdm\xee?`\xad\u018b\x93a3\u01e2\xa1\xb0\u0749\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x83\xdb\xf8\xa1(S\xb4\n\xc6\x19\x96\xf8\xbf\x1d\xc8\xfd\xba\xdd\xd3)\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x83\xdb\xfd\x8e\xda\x01\xd0\u078e\x15\x8b\x16\u0413_\xc28\n]\u01c9 \x86\xac5\x10R`\x00\x00\u07d4\x83\xe4\x80U2|(\xb5\x93o\xd9\xf4D~s\xbd\xb2\xdd3v\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\x83\xfeZ\x1b2\x8b\xaeD\a\x11\xbe\xafj\xad`&\xed\xa6\xd2 \x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x84\x00\x8ar\xf8\x03o?\xeb\xa5B\xe3Px\xc0W\xf3*\x88%\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x84\x0e\xc8>\xa96!\xf04\xe7\xbb7b\xbb\x8e)\xde\xd4\xc4y\x89\x87\x86x2n\xac\x90\x00\x00\xe0\x94\x84\x11E\xb4H@\xc9F\xe2\x1d\xbc\x19\x02d\xb8\xe0\xd5\x02\x93i\x8a?\x87\bW\xa3\xe0\xe3\x80\x00\x00\u07d4\x84#!\a\x93+\x12\xe01\x86X5%\xce\x02:p>\xf8\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4\x84$O\xc9ZiW\xed|\x15\x04\xe4\x9f0\xb8\xc3^\xcaKy\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x841'}{\xdd\x10E}\xc0\x17@\x8c\x8d\xbb\xbdAJ\x8d\xf3\x89\x02\"\xc8\xeb?\xf6d\x00\x00\u07d4\x847Z\xfb\xf5\x9b:\x1da\xa1\xbe2\xd0u\xe0\xe1ZO\xbc\xa5\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x84;\xd3P/E\xf8\xbcM\xa3p\xb3#\xbd\xac?\xcf_\x19\xa6\x89P\x03\x9dc\xd1\x1c\x90\x00\x00\u07d4\x84P34c\rw\xf7AG\xf6\x8b.\bf\x13\xc8\xf1\xad\xe9\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x84R\x03u\x0fqH\xa9\xaa&)!\xe8mC\xbfd\x19t\xfd\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x84a\xec\u0126\xa4^\xb1\xa5\xb9G\xfb\x86\xb8\x80i\xb9\x1f\xcdo\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84g^\x91wrmE\xea\xa4k9\x92\xa3@\xba\u007fq\f\x95\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x84hl{\xadv,T\xb6g\u055f\x90\x94<\xd1M\x11z&\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\x89\xf6\xad\x1d\x9a\x94\xa2\x97x\x91V\x89\x9d\xb6AT\xf1\u06f5\x89\x13t\a\xc0<\x8c&\x80\x00\u07d4\x84\x8c\x99Jy\x00?\xe7\xb7\xc2l\xc62\x12\xe1\xfc/\x9c\x19\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\x8f\xbd)\xd6|\xf4\xa0\x13\xcb\x02\xa4\xb1v\xef$N\x9e\u6349\x01\x17*ck\xbd\xc2\x00\x00\u07d4\x84\x94\x9d\xbaU\x9ac\xbf\xc8E\xde\xd0n\x9f-\x9b\u007f\x11\xef$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\x9a\xb8\a\x90\xb2\x8f\xf1\xff\u05ba9N\xfctc\x10\\6\xf7\x89\x01\xe0+\xe4\xael\x84\x00\x00\u07d4\x84\x9b\x11oYc\x01\xc5\u063bb\xe0\xe9z\x82H\x12n9\xf3\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x84\xa7L\xee\xcf\xf6\\\xb9;/\x94\x9dw>\xf1\xad\u007f\xb4\xa2E\x89\x05\n\x9bDF\x85\xc7\x00\x00\u07d4\x84\xaa\xc7\xfa\x19\u007f\xf8\\0\xe0;zS\x82\xb9W\xf4\x1f:\xfb\x89\b\x8b#\xac\xff\u0650\x00\x00\u07d4\x84\xaf\x1b\x15sB\xd5Ch&\r\x17\x87b0\xa54\xb5K\x0e\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x84\xb0\xeek\xb87\u04e4\xc4\xc5\x01\x1c:\"\x8c\x0e\u06b4cJ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\xb4\xb7Nf#\xba\x9d\x15\x83\xe0\u03feId?\x168AI\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x84\xb6\xb6\xad\xbe/[>-h,f\xaf\x1b\u0110S@\xc3\xed\x89!\x92\xf8\xd2\"\x15\x00\x80\x00\xe0\x94\x84\xb9\x1e.)\x02\xd0^+Y\x1bA\b;\u05fe\xb2\xd5,t\x8a\x02\x15\xe5\x12\x8bE\x04d\x80\x00\u07d4\x84\xbc\xbf\"\xc0\x96\a\xac\x844\x1d.\xdb\xc0;\xfb\x179\xd7D\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x84\xbf\xce\xf0I\x1a\n\xe0iK7\u03ac\x02E\x84\xf2\xaa\x04g\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x84\xcb}\xa0P-\xf4\\\xf5a\x81{\xbd#b\xf4Q\xbe\x02\u0689Hz\x9a0E9D\x00\x00\u07d4\x84\xccxx\xda`_\xdb\x01\x9f\xab\x9bL\xcf\xc1Wp\x9c\u0765\x89Hy\x85\x13\xaf\x04\xc9\x00\x00\u07d4\x84\xdb\x14Y\xbb\x00\x81.\xa6~\xcb=\xc1\x89\xb7!\x87\xd9\xc5\x01\x89\b\x11\xb8\xfb\u0685\xab\x80\x00\u07d4\x84\u9516\x80\xbe\xcehA\xb9\xa7\xe5%\r\b\xac\xd8}\x16\u0349\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x84\xe9\u03c1f\xc3j\xbf\xa4\x90S\xb7\xa1\xad@6 &\x81\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x84\xec\x06\xf2G\x00\xfeBAL\xb9\x89|\x15L\x88\xde/a2\x89Hz\x9a0E9D\x00\x00\xe0\x94\x84\xf5\"\xf0R\x0e\xbaR\xdd\x18\xad!\xfaK\x82\x9f+\x89\u02d7\x8a\x01\fQ\x06\xd5\x13O\x13\x00\x00\u07d4\x85\v\x9d\xb1\x8f\xf8K\xf0\xc7\xdaI\xea7\x81\xd9 \x90\xad~d\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\x85\x10\xee\x93O\f\xbc\x90\x0e\x10\a\xeb8\xa2\x1e*Q\x01\xb8\xb2\x89\x05\xbf\v\xa6cOh\x00\x00\u07d4\x85\x16\xfc\xafw\u0213\x97\x0f\xcd\x1a\x95\x8b\xa9\xa0\x0eI\x04@\x19\x89\n\xa3\xeb\x16\x91\xbc\xe5\x80\x00\u07d4\x85\x1a\xa9\x1c\x82\xf4/\xad]\xd8\xe8\xbb^\xa6\x9c\x8f:Yw\u0449\b\x0eV\x1f%xy\x80\x00\u07d4\x85\x1c\rb\xbeF5\xd4w~\x805\xe3~K\xa8Q|a2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x85\x1d\u00ca\xdbE\x93r\x9av\xf3:\x86\x16\u06b6\xf5\xf5\x9aw\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x852I\b\x97\xbb\xb4\u038b\u007fk\x83~L\xba\x84\x8f\xbe\x99v\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x85>j\xba\xf4Di\xc7/\x15\x1dN\"8\x19\xac\xedN7(\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x85F\x91\xceqO2\\\xedU\xceY(\u039b\xa1/\xac\u0478\x89\xedp\xb5\xe9\xc3\xf2\xf0\x00\x00\u07d4\x85L\fF\x9c$k\x83\xb5\u0473\xec\xa4C\xb3\x9a\xf5\xee\x12\x8a\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x85]\x9a\xef,9\xc6#\r\t\u025e\xf6II\x89\xab\u61c5\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\x85c\u0113a\xb6%\xe7hw\x1c\x96\x15\x1d\xbf\xbd\x1c\x90iv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x85fa\t\x01\xaa\xce8\xb82D\xf3\xa9\xc810jg\xb9\u0709\xb0\x82\x13\xbc\xf8\xff\xe0\x00\x00\xe0\x94\x85j\xa2<\x82\xd7![\xec\x8dW\xf6\n\xd7^\xf1O\xa3_D\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x85nZ\xb3\xf6L\x9a\xb5k\x00\x93\x93\xb0\x16d\xfc\x03$\x05\x0e\x89a\t=|,m8\x00\x00\u07d4\x85n\xb2\x04$\x1a\x87\x83\x0f\xb2)\x03\x13C\xdc0\x85OX\x1a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x85s,\x06\\\xbdd\x11\x99A\xae\xd40\xacYg\vlQ\u0109'\xa5sb\xab\n\x0e\x80\x00\xe0\x94\x85x\xe1\x02\x12\xca\x14\xff\a2\xa8$\x1e7F}\xb8V2\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x85y\xda\xdf\x1a9Z4q\xe2\vov=\x9a\x0f\xf1\x9a?o\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x85\u007f\x10\v\x1aY0\"^\xfc~\x90 \u05c3'\xb4\x1c\x02\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x85\x94mV\xa4\xd3q\xa93hS\x96\x90\xb6\x0e\xc8%\x10tT\x89]\u0212\xaa\x111\xc8\x00\x00\xe0\x94\x85\x99\xcb\u0566\xa9\xdc\u0539f\xbe8}iw]\xa5\xe3C'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86$_Yf\x91\t>\xce?=<\xa2&>\xac\xe8\x19A\u0649\n1\x06+\xee\xedp\x00\x00\u07d4\x86%i!\x1e\x8cc'\xb5A^:g\xe5s\x8b\x15\xba\xafn\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x86)}s\x0f\xe0\xf7\xa9\xee$\xe0\x8f\xb1\b{1\xad\xb3\x06\xa7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86D\xcc(\x1b\xe32\xcc\xce\xd3m\xa4\x83\xfb*\aF\u067a.\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x86I\x9a\x12(\xff-~\xe3\au\x93dPo\x8e\x8c\x83\a\xa5\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x86K\xecPi\xf8U\xa4\xfdX\x92\xa6\xc4I\x1d\xb0|\x88\xff|\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86W\n\xb2Y\u0271\xc3,\x97) /w\xf5\x90\xc0}\xd6\x12\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86c\xa2A\xa0\xa8\x9ep\xe1\x82\xc8E\xe2\x10\\\x8a\xd7&K\u03ca\x03#\xb1=\x83\x98\xf3#\x80\x00\u07d4\x86g\xfa\x11U\xfe\xd72\u03f8\u0725\xa0\xd7e\xce\r\a\x05\xed\x89\x04n\xc9e\u00d3\xb1\x00\x00\u07d4\x86h\xaf\x86\x8a\x1e\x98\x88_\x93\u007f&\x15\xde\xd6u\x18\x04\xeb-\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x86t\nFd\x8e\x84Z]\x96F\x1b\x18\t\x1f\xf5{\xe8\xa1o\x8a\x14\xc0\x974\x85\xbf9@\x00\x00\xe0\x94\x86~\xbaVt\x8aY\x045\r,\xa2\xa5\u039c\xa0\vg\n\x9b\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x86\x80dt\xc3X\x04}\x94\x06\xe6\xa0\u007f@\x94[\xc82\x8eg\x8a\x01u.\xb0\xf7\x01=\x10\x00\x00\u07d4\x86\x88=T\xcd9\x15\xe5I\tU0\xf9\xab\x18\x05\xe8\xc5C-\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x86\x8c#\xbe\x874f\xd4\xc7L\"\n\x19\xb2E\xd1x~\x80\u007f\x89J\x13\xbb\xbd\x92\u020e\x80\x00\xe0\x94\x86\x92O\xb2\x11\xaa\xd2<\xf5\xce`\x0e\n\xae\x80c\x96D@\x87\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x86\x93\u9e3e\x94B^\xefyi\xbci\xf9\xd4/|\xadg\x1e\x8967\tlK\xcci\x00\x00\xe0\x94\x86\x9f\x1a\xa3\x0eDU\xbe\xb1\x82 \x91\xde\\\xad\xecy\xa8\xf9F\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x86\xa1\xea\xde\xeb0F\x13E\xd9\xefk\xd0R\x16\xfa$|\r\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86\xa5\xf8%\x9e\u0570\x9e\x18\x8c\xe3F\xee\x92\xd3J\xa5\u0753\xfa\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xb7\xbdV<\uad86\xf9bD\xf9\xdd\xc0*\u05f0\xb1K\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x86\u008bVx\xaf7\xd7'\xec\x05\xe4Dw\x90\xf1_q\xf2\xea\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xc4\xce\x06\u066c\x18[\xb1H\xd9o{z\xbes\xf4A\x00m\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x86\xc8\xd0\u0642\xb59\xf4\x8f\x980\xf9\x89\x1f\x9d`z\x94&Y\x8a\x02\xce\xd3wa\x82O\xb0\x00\x00\u07d4\x86\xc94\xe3\x8eS\xbe;3\xf2t\xd0S\x9c\xfc\xa1Y\xa4\xd0\u04494\x95tD\xb8@\xe8\x00\x00\xe0\x94\x86\xca\x01E\x95~k\r\xfe6\x87_\xbez\r\xecU\xe1z(\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x86\u02af\xac\xf3*\xa01|\x03*\xc3k\xab\xed\x97G\x91\xdc\x03\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x86\u0377\xe5\x1a\xc4Gr\xbe6\x90\xf6\x1d\x0eYvn\x8b\xfc\x18\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x86\xdfs\xbd7\u007f,\t\xdec\xc4]g\xf2\x83\xea\xef\xa0\xf4\xab\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x86\xe3\xfe\x86\xe9=\xa4\x86\xb1Bf\xea\xdf\x05l\xbf\xa4\xd9\x14C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x86\xe8g\x0e'Y\x8e\xa0\x9c8\x99\xabw\x11\u04f9\xfe\x90\x1c\x17\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x86\xefd&!\x19I\xcc7\xf4\xc7^xP6\x9d\f\xf5\xf4y\x8a\x02\xd6_2\xea\x04Z\xf6\x00\x00\u07d4\x86\xf0]\x19\x06>\x93i\xc6\x00N\xb3\xf1#\x94:|\xffN\xab\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x86\xf2>\x9c\n\xaf\u01cb\x9c@M\xcd`3\x9a\x92[\xff\xa2f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x86\xf4\xf4\n\u0644\xfb\xb8\t3\xaebn\x0eB\xf93?\xddA\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x86\xf9\\[\x11\xa2\x93\x94\x0e5\xc0\xb8\x98\u0637_\b\xaa\xb0m\x8a\x06D\xe3\xe8u\xfc\xcft\x00\x00\u07d4\x86\xff\xf2 \xe5\x93\x05\xc0\x9fH8`\xd6\xf9N\x96\xfb\xe3/W\x89\x02S[j\xb4\xc0B\x00\x00\u07d4\x87\a\x96\xab\xc0\u06c4\xaf\x82\xdaR\xa0\xedhsM\xe7\xe66\xf5\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x87\x0f\x15\xe5\u07cb\x0e\xab\xd0%iSz\x8e\xf9;Vx\\B\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\x87\x181`\xd1r\xd2\xe0\x84\xd3'\xb8k\xcb|\x1d\x8eg\x84\xef\x89\xd8\xd8X?\xa2\xd5/\x00\x00\xe0\x94\x87\x1b\x8a\x8bQ\u07a1\x98\x9aY!\xf1>\xc1\xa9U\xa5\x15\xadG\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x87%\xe8\xc7S\xb3\xac\xbf\u0725_I\x13\\3\x91\x99\x10`)\n\xa7\xf6\u0338\xf8Zx\u06c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x87Pa\xee\x12\xe8 \x04\x1a\x01\x94,\xb0\xe6[\xb4'\xb0\x00`\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\x87XJ?a;\xd4\xfa\xc7L\x1ex\v\x86\xd6\xca\xeb\x89\f\xb2\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x87d\xd0'\"\x00\t\x96\xec\xd4u\xb43)\x8e\x9fT\v\x05\xbf\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x87l?!\x8bGv\xdf<\xa9\xdb\xfb'\r\xe1R\xd9N\xd2R\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x87u\xa6\x10\xc5\x02\xb9\xf1\xe6\xadL\xda\u06cc\xe2\x9b\xffu\xf6\xe4\x89 \x86\xac5\x10R`\x00\x00\u07d4\x87vN6w\xee\xf6\x04\xcb\u015a\xed$\xab\xdcVk\t\xfc%\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\x87\x87\xd1&w\xa5\xec)\x1eW\xe3\x1f\xfb\xfa\xd1\x05\xc32K\x87\x8a\x02\xa2N\xb52\b\xf3\x12\x80\x00\u07d4\x87\x94\xbfG\xd5E@\xec\xe5\xc7\"7\xa1\xff\xb5\x11\u0777Gb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x87\xa5>\xa3\x9fY\xa3[\xad\xa85%!dU\x94\xa1\xa7\x14\u02c9g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x87\xa7\xc5\b\xefqX-\u0665Cr\xf8\x9c\xb0\x1f%/\xb1\x80\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x87\xaf%\xd3\xf6\xf8\xee\xa1S\x13\xd5\xfeEW\xe8\x10\xc5$\xc0\x83\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x87\xb1\x0f\x9c(\x00\x98\x17\x9a+v\xe9\u0390\xbea\xfc\x84M\r\x89Hz\x9a0E9D\x00\x00\u07d4\x87\xbf|\xd5\u0629)\xe1\u01c5\xf9\xe5D\x91\x06\xac#$c\u0249\x047\xb1\x1f\xccEd\x00\x00\u07d4\x87\u0118\x17\t4\xb8#=\x1a\xd1\xe7i1}\\G_/@\x897\b\xba\xed=h\x90\x00\x00\u07d4\x87\xcf6\xad\x03\xc9\xea\xe9\x05:\xbbRB\u0791\x17\xbb\x0f*\v\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x87\u05ec\x06S\xcc\xc6z\xa9\xc3F\x9e\xefCR\x19?}\xbb\x86\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\xe0\x94\x87\xe3\x06+#!\xe9\u07f0\x87\\\u311c\x9b.5\"\xd5\n\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x87\xe6\x03N\xcf#\xf8\xb5c\x9d_\x0e\xa7\n\"S\x8a\x92\x04#\x89\x11\xc7\xea\x16.x \x00\x00\u07d4\x87\xefm\x8bj|\xbf\x9b\\\x8c\x97\xf6~\xe2\xad\u00a7;?w\x89\n\xdd\x1b\xd2<\x00L\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x88F\x92\x8dh2\x89\xa2\xd1\x1d\xf8\xdbz\x94t\x98\x8e\xf0\x13H\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x88I\x80\xebEe\xc1\x04\x83\x17\xa8\xf4\u007f\u06f4a\x96[\u4049\xd8\xd6\x11\x9a\x81F\x05\x00\x00\xe0\x94\x88Jz9\u0411n\x05\xf1\xc2B\xdfU`\u007f7\u07cc_\u068a\x04\xf4\x84<\x15|\x8c\xa0\x00\x00\u07d4\x88T\x93\xbd\xa3j\x042\x97eF\xc1\xdd\xceq\xc3\xf4W\x00!\x89\v\xbfQ\r\xdf\xcb&\x00\x00\xe0\x94\x88`\x9e\nF[n\x99\xfc\xe9\a\x16mW\xe9\xda\b\x14\xf5\u020a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x88m\n\x9e\x17\xc9\xc0\x95\xaf.\xa25\x8b\x89\xecpR\x12\ue509\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\x88y~Xg^\xd5\xccL\x19\x98\a\x83\xdb\xd0\xc9V\bQS\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x88|\xacA\xcdpo3E\xf2\xd3J\xc3N\x01u*nY\t\x89 F\\\ue7617\x00\x00\u07d4\x88\x88\x8aW\xbd\x96\x87\xcb\xf9P\xae\xea\u03d7@\xdc\xc4\xd1\xefY\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u0794\x88\x89D\x83\x16\xcc\xf1N\xd8m\xf8\xe2\xf4x\xdcc\xc43\x83@\x88\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x88\x8c\x16\x14I3\x19|\xac&PM\xd7n\x06\xfdf\x00\u01c9\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x88\x8e\x94\x91p\x83\xd1R +S\x1699\x86\x9d'\x11u\xb4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x88\x90\x87\xf6o\xf2\x84\xf8\xb5\xef\xbd)I;pg3\xab\x14G\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\x88\x95\xebrb&\xed\xc3\xf7\x8c\u01a5\x15\a{2\x96\xfd\xb9^\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x88\x97Z_\x1e\xf2R\x8c0\v\x83\xc0\xc6\a\xb8\xe8}\u0593\x15\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\u07d4\x88\x9d\xa4\x0f\xb1\xb6\x0f\x9e\xa9\xbdzE>XL\xf7\xb1\xb4\xd9\xf7\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x88\x9d\xa6b\xebJ\n*\x06\x9d+\xc2K\x05\xb4\xee.\x92\xc4\x1b\x89Z,\x8cTV\xc9\xf2\x80\x00\u07d4\x88\xa1\"\xa28,R91\xfbQ\xa0\u032d;\xeb[rY\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x88\xa2\x15D0\xc0\xe4\x11G\xd3\xc1\xfe\u3cf0\x06\xf8Q\xed\xbd\x8965f3\xeb\xd8\xea\x00\x00\u07d4\x88\xb2\x17\u0337\x86\xa2T\xcfM\xc5\u007f]\x9a\xc3\xc4U\xa3\x04\x83\x892$\xf4'#\xd4T\x00\x00\xe0\x94\x88\xbcC\x01.\xdb\x0e\xa9\xf0b\xacCxC%\n9\xb7\x8f\xbb\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x88\xc2Qj|\xdb\t\xa6'mr\x97\xd3\x0fZM\xb1\xe8K\x86\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x88\xc3ad\rki7;\b\x1c\xe0\xc43\xbdY\x02\x87\xd5\xec\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x88\xd5A\xc8@\xceC\xce\xfb\xafm\x19\xafk\x98Y\xb5s\xc1E\x89\t79SM(h\x00\x00\u07d4\x88\xde\x13\xb0\x991\x87|\x91\rY1e\xc3d\u0221d\x1b\u04c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x88\xde\u017d?N\xba-\x18\xb8\xaa\xce\xfa{r\x15H\xc3\x19\xba\x89JD\x91\xbdm\xcd(\x00\x00\u07d4\x88\xe6\xf9\xb2G\xf9\x88\xf6\xc0\xfc\x14\xc5o\x1d\xe5>\u019dC\u0309\x05k\xc7^-c\x10\x00\x00\u07d4\x88\xee\u007f\x0e\xfc\x8fw\x8ckh~\xc3+\xe9\xe7\xd6\xf0 \xb6t\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x88\xf1\x04_\x19\xf2\xd3\x19\x18\x16\xb1\xdf\x18\xbbn\x145\xad\x1b8\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x89\x00\x9e\a\xe3\xfahc\xa7x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x89Wr~r\xcfb\x90 \xf4\xe0^\xdfy\x9a\xa7E\x80b\u0409wC\"\x17\xe6\x83`\x00\x00\u07d4\x89]iN\x88\v\x13\xcc\u0404\x8a\x86\xc5\xceA\x1f\x88Gk\xbf\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x89^\xc5TVD\u0dc30\xff\xfa\xb8\xdd\xea\xc9\xe83\x15l\x89 \x86\xac5\x10R`\x00\x00\u07d4\x89`\tRj,{\f\t\xa6\xf6:\x80\xbd\U0009d707\u079c\x89\xbb\xb8k\x82#\xed\xeb\x00\x00\u07d4\x89g\u05f9\xbd\xb7\xb4\xae\xd2.e\xa1]\xc8\x03\xcbz!?\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x89n3\\\xa4z\xf5yb\xfa\x0fM\xbf>E\xe6\x88\u02e5\x84\x89J/\xc0\xab`R\x12\x00\x00\u07d4\x89s\xae\xfd^\xfa\xee\x96\t]\x9e(\x8fj\x04l\x977KC\x89\a\xa4\u0120\xf32\x14\x00\x00\u07d4\x89\x8cr\xddseX\xef\x9eK\xe9\xfd\xc3O\xefT\xd7\xfc~\b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x89\x9b<$\x9f\fK\x81\xdfu\xd2\x12\x00M=m\x95/\xd2#\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x89\xab\x13\xee&mw\x9c5\xe8\xbb\x04\u034a\x90\xcc!\x03\xa9[\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\x89\xc43\xd6\x01\xfa\xd7\x14\xdaci0\x8f\xd2l\x1d\u0254+\xbf\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x89\xd7[\x8e\b1\xe4o\x80\xbc\x17A\x88\x18N\x00o\xde\x0e\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x89\u3d5a\x15\x86G7\u0513\xc1\xd2<\xc5=\xbf\x8d\xcb\x13b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x89\xfc\x8eM8k\r\v\xb4\xa7\a\xed\xf3\xbdV\r\xf1\xad\x8fN\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\x89\xfe\xe3\r\x17(\xd9l\xec\xc1\u06b3\xda.w\x1a\xfb\u03eaA\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x8a\x1c\u016c\x11\x1cI\xbf\xcf\xd8H\xf3}\xd7h\xaae\u0208\x02\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x8a \xe5\xb5\xce\xe7\xcd\x1fU\x15\xba\xce;\xf4\xf7\u007f\xfd\xe5\xcc\a\x89\x04V9\x18$O@\x00\x00\xe0\x94\x8a!}\xb3\x8b\xc3_!_\xd9)\x06\xbeBCo\xe7\xe6\xed\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x8a$:\n\x9f\xeaI\xb89TwE\xff-\x11\xaf?K\x05\"\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x8a$}\x18e\x10\x80\x9fq\xcf\xfcEYG\x1c9\x10\x85\x81!\x89a\t=|,m8\x00\x00\u07d4\x8a4p(-^**\xef\u05e7P\x94\xc8\"\xc4\xf5\xae\uf289\r(\xbc`dx\xa5\x80\x00\u07d4\x8a6\x86\x9a\xd4x\x99|\xbfm\x89$\xd2\n<\x80\x18\xe9\x85[\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8aC\x14\xfba\u0353\x8f\xc3>\x15\xe8\x16\xb1\x13\U000ac267\xfb\x89\x17vNz\xede\x10\x00\x00\u07d4\x8aOJ\u007fR\xa3U\xba\x10_\xca r\xd3\x06_\xc8\xf7\x94K\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8aX1(,\xe1Jezs\r\xc1\x88&\xf7\xf9\xb9\x9d\xb9h\x89\uaf8a[A\xc16\x00\x00\u07d4\x8a_\xb7W\x93\xd0C\xf1\xbc\xd48\x85\xe07\xbd0\xa5(\xc9'\x89\x13Snm.\x9a\xc2\x00\x00\u07d4\x8af\xab\xbc-0\xce!\xa83\xb0\u06ceV\x1dQ\x05\xe0\xa7,\x89%\xf1\xde\\v\xac\xdf\x00\x00\u07d4\x8atl]g\x06G\x11\xbf\xcah[\x95\xa4\xfe)\x1a'\x02\x8e\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8ax\n\xb8z\x91E\xfe\x10\xed`\xfaGjt\n\xf4\u02b1\u0489\x12\x1b.^ddx\x00\x00\u07d4\x8az\x06\xbe\x19\x9a:X\x01\x9d\x84j\xc9\xcb\xd4\xd9]\xd7W\u0789\xa2\xa4#\x94BV\xf4\x00\x00\u07d4\x8a\x81\x01\x14\xb2\x02]\xb9\xfb\xb5\x00\x99\xa6\xe0\u02de.\xfak\u0709g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x8a\x86\xe4\xa5\x1c\x01;\x1f\xb4\xc7k\xcf0f|x\xd5.\xed\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\x9e\u029cZ\xba\x8e\x13\x9f\x80\x03\xed\xf1\x16:\xfbp\xaa:\xa9\x89#\xc7W\a+\x8d\xd0\x00\x00\u07d4\x8a\xb89\xae\xaf*\xd3|\xb7\x8b\xac\xbb\xb63\xbc\xc5\xc0\x99\xdcF\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\u021b\u06780\x1ek\x06w\xfa%\xfc\xf0\xf5\x8f\f\u01f6\x11\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x8a\xdcS\xef\x8c\x18\xed0Qx]\x88\xe9\x96\xf3\xe4\xb2\x0e\xcdQ\x8a\b\xe4\xd3\x16\x82v\x86@\x00\x00\u07d4\x8a\xe6\xf8\vp\xe1\xf2<\x91\xfb\u0569f\xb0\xe4\x99\xd9]\xf82\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x8a\xe9\uf30a\x8a\u07e6\xaby\x8a\xb2\xcd\xc4\x05\b*\x1b\xbbp\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8a\xf6&\xa5\xf3'\xd7Pe\x89\xee\xb7\x01\x0f\xf9\xc9D` \u0489K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\x8b\x01\xda4\xd4p\xc1\xd1\x15\xac\xf4\xd8\x11\xe1\x01\xdb\x1e\x14\xec\xc7\xd3\"\xc7+\x8c\x04s\x89\x18\xb2j1>\x8a\xe9\x00\x00\xe0\x94\x8bH\xe1\x9d9\xdd5\xb6nn\x1b\xb6\xb9\xc6W\xcb,\xf5\x9d\x04\x8a\x03\xc7U\xac\x9c\x02J\x01\x80\x00\xe0\x94\x8bP^(q\xf7\u07b7\xa68\x95 \x8e\x82'\u072a\x1b\xff\x05\x8a\f\xf6\x8e\xfc0\x8dy\xbc\x00\x00\u07d4\x8bW\xb2\xbc\x83\u030dM\xe31 N\x89?/;\x1d\xb1\a\x9a\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8b\\\x91K\x12\x8b\xf1i\\\b\x89#\xfaF~y\x11\xf3Q\xfa\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\xe0\x94\x8b_)\xcc/\xaa&,\xde\xf3\x0e\xf5T\xf5\x0e\xb4\x88\x14n\xac\x8a\x01;hp\\\x97 \x81\x00\x00\u07d4\x8bpV\xf6\xab\xf3\xb1\x18\xd0&\xe9D\xd5\xc0sC<\xa4Q\u05c965\xc6 G9\u0640\x00\u07d4\x8bqE\"\xfa(9b\x04p\xed\xcf\fD\x01\xb7\x13f=\xf1\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x8bt\xa7\xcb\x1b\xb8\u014f\xce&tf\xa3\x03X\xad\xafR\u007fa\x8a\x02\xe2WxN%\xb4P\x00\x00\u07d4\x8b~\x9fo\x05\xf7\xe3dv\xa1n>q\x00\xc9\x03\x1c\xf4\x04\xaf\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8b\x81\x15ni\x869\x94<\x01\xa7Rr\xad=5\x85\x1a\xb2\x82\x89\x12\xb3\x16_e\xd3\xe5\x00\x00\u07d4\x8b\x95w\x92\x00S\xb1\xa0\x01\x890M\x88\x80\x10\xd9\xef,\xb4\xbf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8b\x98A\x86.w\xfb\xbe\x91\x94p\x93U\x83\xa9<\xf0'\xe4P\x89llS4B\u007f\x1f\x00\x00\u07d4\x8b\x99}\xbc\a\x8a\xd0)a5]\xa0\xa1Y\xf2\x92~\xd4=d\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x8b\x9f\xda}\x98\x1f\xe9\xd6B\x87\xf8\\\x94\xd8?\x90t\x84\x9f\u030a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x8b\xb0!/2\x95\xe0)\u02b1\xd9a\xb0A3\xa1\x80\x9e{\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8b\xbe\xac\xfc)\xcf\xe94\x02\xdb\xd6j\x1a\xcbvv\x85c7\xb9;\xf0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8b\xf3s\xd0v\x81L\xbcW\xe1\xc6\xd1j\x82\u017e\x13\xc7=7\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x8c\x10#\xfd\xe1WM\xb8\xbbT\xf1s\x96p\x15|\xa4}\xa6R\x8a\x01y\u03da\u00e1\xb1w\x00\x00\u07d4\x8c\x1f\xbe_\n\xea5\x9cZ\xa1\xfa\b\u0209T\x12\u028e\x05\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\"B`U\xb7o\x11\xf0\xa2\xde\x1a\u007f\x81\x9aa\x96\x85\xfe`\x89kV\x05\x15\x82\xa9p\x00\x00\u07d4\x8c+}\x8b`\x8d(\xb7\u007f\\\xaa\x9c\xd6E$*\x82>L\u0649b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\x8c/\xbe\ue3ac\xc5\xc5\xd7|\x16\xab\xd4b\ue701E\xf3K\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x8c:\x9e\xe7\x1fr\x9f#l\xba8g\xb4\u05dd\x8c\xee\xe2]\xbc\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8cP\xaa*\x92\x12\xbc\xdeVA\x8a\xe2a\xf0\xb3^z\x9d\xbb\x82\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8cT\xc7\xf8\xb9\x89nu\xd7\xd5\xf5\xc7`%\x86\x99\x95qB\xad\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x8c]\x16\xede\xe3\xed~\x8b\x96\u0297+\xc8as\xe3P\v\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8cj\xa8\x82\xee2,\xa8HW\x8c\x06\xcb\x0f\xa9\x11\xd3`\x83\x05\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\x8cj\xe7\xa0Z\x1d\xe5u\x82\xae'h Bv\xc0\xffG\xed\x03\x8a,\v\xb3\xdd0\xc4\xe2\x00\x00\x00\u07d4\x8co\x9fN[z\xe2v\xbfXI{\u05ff*}%$_d\x89\x93\xfe\\W\xd7\x10h\x00\x00\u07d4\x8cu\x95n\x8f\xedP\xf5\xa7\xdd|\xfd'\xda \x0fgF\xae\xa6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c|\xb4\xe4\x8b%\x03\x1a\xa1\xc4\xf9)%\xd61\xa8\xc3\xed\xc7a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\u007f\xa5\xca\xe8/\xed\xb6\x9a\xb1\x89\xd3\xff'\xae \x92\x93\xfb\x93\x89\x15\xaf\x88\r\x8c\u06c3\x00\x00\xe0\x94\x8c\x81A\x0e\xa85L\xc5\xc6\\A\xbe\x8b\xd5\xdes<\v\x11\x1d\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\x8c\x83\xd4$\xa3\xcf$\xd5\x1f\x01\x92=\xd5J\x18\u05b6\xfe\xde{\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8c\x90\n\x826\xb0\x8c+e@]9\xd7_ \x06*ua\xfd\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\x8c\x93\xc3\xc6\u06dd7q}\xe1e\u00e1\xb4\xfeQ\x95,\b\u0789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8c\x99\x95\x91\xfdr\xefq\x11\xef\xcaz\x9e\x97\xa25k;\x00\n\x89\xddd\xe2\xaa\ngP\x00\x00\u07d4\x8c\xa6\x98\x97F\xb0n2\xe2Hta\xb1\u0399j':\xcf\u05c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8c\xb3\xaa?\xcd!(T\xd7W\x8f\xcc0\xfd\xed\xe6t*1*\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x8c\xc0\xd7\xc0\x16\xfaz\xa9P\x11J\xa1\xdb\tH\x82\xed\xa2t\xea\x89\b\xa9\xab\xa5W\xe3l\x00\x00\u07d4\x8c\xc6R\xdd\x13\xe7\xfe\x14\u06bb\xb3m]2\r\xb9\xff\xee\x8aT\x89a\t=|,m8\x00\x00\u07d4\x8c\u02bf%\a\u007f:\xa4\x15E4MS\xbe\x1b+\x9c3\x90\x00\x89[\xe8f\xc5b\xc5D\x00\x00\u07d4\x8c\xcf:\xa2\x1a\xb7BWj\xd8\xc4\"\xf7\x1b\xb1\x88Y\x1d\ua28965\u026d\xc5\u07a0\x00\x00\u07d4\x8c\xd0\xcd\"\xe6 \xed\xa7\x9c\x04a\xe8\x96\xc9\xd1b)\x12K_z\xfb\xec\x89\a?u\u0460\x85\xba\x00\x00\u07d4\x8c\xe2/\x9f\xa3rD\x9aB\x06\x10\xb4z\xe0\xc8\xd5eH\x122\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x8c\u451d\x8a\x16T-B<\x17\x98Ng9\xfar\u03b1w\x8a\x05K@Y&\xf4\xa6=\x80\x00\u07d4\x8c\xe5\xe3\xb5\xf5\x91\xd5\uc8ca\xbf\"\x8f.<5\x13K\xda\xc0\x89}\xc3[\x84\x89|8\x00\x00\xe0\x94\x8c\xee8\xd6YW\x88\xa5n?\xb9F4\xb3\xff\xe1\xfb\xdb&\u058a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x8c\xee\xa1^\xec;\xda\xd8\x02?\x98\xec\xf2[+\x8f\xef'\xdb)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8c\xf3To\xd1\u0363=X\x84_\xc8\xfc\xfe\u02bc\xa7\xc5d*\x89\x1f\x1e9\x93,\xb3'\x80\x00\u07d4\x8c\xf6\xda\x02\x04\xdb\u0106\vF\xad\x97?\xc1\x11\x00\x8d\x9e\fF\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x8c\xfe\xde\xf1\x98\xdb\n\x91C\xf0\x91)\xb3\xfdd\u073b\x9bIV\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\x04\xa5\xeb\xfb]\xb4\t\xdb\x06\x17\xc9\xfaV1\xc1\x92\x86\x1fJ\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x8d\x06\xe4d$\\\xadaI9\xe0\xaf\bE\xe6\xd70\xe2\x03t\x89\n\u070a(\xf3\xd8}\x80\x00\u07d4\x8d\a\xd4-\x83\x1c-|\x83\x8a\xa1\x87+:\xd5\xd2w\x17h#\x89\x12\xee\x1f\x9d\xdb\xeeh\x00\x00\u07d4\x8d\v\x9e\xa5?\xd2cA^\xac\x119\x1f|\xe9\x12V\xb9\xfb\x06`\xf6\xf0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8dy\\_JV\x89\xadb\u0696\x16q\xf0(\x06R\x86\xd5T\x89o\x05\xb5\x9d; \x00\x00\x00\u07d4\x8d\u007f>a)\x9c-\xb9\xb9\xc0H|\xf6'Q\x9e\xd0\n\x91#\x89^t\xa8P^\x80\xa0\x00\x00\xe0\x94\x8d\x89\x17\v\x92\xb2\xbe,\b\xd5|H\xa7\xb1\x90\xa2\xf1Fr\x0f\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\x8d\x93\xda\u01c5\xf8\x8f\x1a\x84\xbf\x92}Se+E\xa1T\xcc\u0749\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x8d\x99R\u043bN\xbf\xa0\xef\xd0\x1a:\xa9\xe8\xe8\u007f\x05%t.\x89\xbb\x91%T\"c\x90\x00\x00\u07d4\x8d\x9a\fp\xd2& B\xdf\x10\x17\xd6\xc3\x03\x13 $w'\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\x9e\xd7\xf4U0X\xc2ox6\xa3\x80-0d\xeb\x1b6=\x89\x04\xe1\x00;(\xd9(\x00\x00\u07d4\x8d\xa1\x17\x8fU\xd9wr\xbb\x1d$\x11\x1a@JO\x87\x15\xb9]\x89/\x9a\xc3\xf6\xde\x00\x80\x80\x00\u07d4\x8d\xa1\xd3Y\xbal\xb4\xbc\xc5}zCw \xd5]\xb2\xf0\x1cr\x89\x04V9\x18$O@\x00\x00\u07d4\x8d\xab\x94\x8a\xe8\x1d\xa3\x01\xd9r\xe3\xf6\x17\xa9\x12\xe5\xa7Sq.\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x8d\xad\xdfR\xef\xbdt\u0695\xb9i\xa5GoO\xbb\xb5c\xbf\u0489-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4\x8d\xb1\x85\xfe\x1bp\xa9Jj\b\x0e~#\xa8\xbe\xdcJ\xcb\xf3K\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x8d\xb5\x8e@n -\xf9\xbcpl\xb43\xe1\x94\xf4\x0f\x82\xb4\x0f\xaa\xdb\x1f\x8b\x85a\x16\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x8d\xc1\xd5\x11\x1d\t\xaf%\xfd\xfc\xacE\\|\xec(>mgu\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8d\u0504\xff\x8a0sd\xebf\xc5%\xa5q\xaa\xc7\x01\xc5\xc3\x18\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\u05a9\xba\xe5\u007fQ\x85I\xad\xa6wFo\ua2b0O\u0674\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\xde<\xb8\x11\x85h\xefE\x03\xfe\x99\x8c\xcd\xf56\xbf\x19\xa0\x98\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8d\xde`\xeb\b\xa0\x99\xd7\u06a3V\u06aa\xb2G\r{\x02Zk\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\x8d\xf39!Kj\u0472Fc\xceq`4t\x9dn\xf88\u064a\x02TO\xaaw\x80\x90\xe0\x00\x00\xe0\x94\x8d\xf5=\x96\x19\x14q\xe0Y\xdeQ\xc7\x18\xb9\x83\xe4\xa5\x1d*\xfd\x8a\x06\u01b95\xb8\xbb\xd4\x00\x00\x00\u07d4\x8d\xfb\xaf\xbc\x0e[\\\x86\xcd\x1a\u0597\xfe\xea\x04\xf41\x88\u0796\x89\x15%+\u007f_\xa0\xde\x00\x00\u07d4\x8e\a;\xad%\xe4\"\x18a_J\x0ek.\xa8\xf8\xde\"0\xc0\x89\x82=b\x9d\x02k\xfa\x00\x00\u07d4\x8e\x0f\xee8hZ\x94\xaa\xbc\xd7\u0385{k\x14\t\x82Ou\xb8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e#\xfa\xcd\x12\xc7e\xc3j\xb8\x1am\xd3M\x8a\xa9\xe6\x89\x18\xae\x89\t\x11\u418d\xba\x9b\x00\x00\xe0\x94\x8e/\x904\xc9%G\x19\u00ceP\u026ad0^\u0596\xdf\x1e\x8a\x01\x00N.E\xfb~\xe0\x00\x00\u07d4\x8e2@\xb0\x81\x0e\x1c\xf4\a\xa5\x00\x80G@\u03cdad2\xa4\x89\x02/fU\xef\v8\x80\x00\u07d4\x8eHj\x04B\xd1q\xc8`[\xe3H\xfe\xe5~\xb5\b^\xff\r\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8eaV3k\xe2\u037e2\x14\r\xf0\x8a+\xa5_\u0425\x84c\x89\x04\t\x9e\x1dcW\x18\x00\x00\u07d4\x8eg\b\x15\xfbg\xae\xae\xa5{\x86SN\xdc\x00\xcd\xf5d\xfe\u5272\xe4\xb3#\xd9\xc5\x10\x00\x00\u07d4\x8emt\x85\xcb\u942c\xc1\xad\x0e\xe9\xe8\xcc\xf3\x9c\f\x93D\x0e\x893\xc5I\x901r\f\x00\x00\xe0\x94\x8et\xe0\u0477~\xbc\x82:\xca\x03\xf1\x19\x85L\xb1 '\xf6\u05ca\x16\xb3R\xda^\x0e\xd3\x00\x00\x00\u07d4\x8ex\xf3QE}\x01oJ\xd2u^\xc7BN\\!\xbamQ\x89\a\xea(2uw\b\x00\x00\u07d4\x8ey6\u0552\x00\x8f\xdcz\xa0N\xde\xebuZ\xb5\x13\u06f8\x9d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8e\u007f\xd28H\xf4\xdb\a\x90j}\x10\xc0K!\x80;\xb0\x82'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8e\x92\xab\xa3\x8er\xa0\x98\x17\v\x92\x95\x92FSz.UV\xc0\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x8e\x98ve$\xb0\xcf'G\xc5\r\xd4;\x95gYM\x971\u0789lD\xb7\xc2a\x82(\x00\x00\u07d4\x8e\x9b5\xadJ\n\x86\xf7XDo\xff\xde4&\x9d\x94\f\xea\u0349\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8e\x9c\b\xf78f\x1f\x96v#n\xff\x82\xbaba\xdd?H\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8e\x9cB\x92f\xdf\x05~\xfax\xdd\x1d_w\xfc@t*\xd4f\x89\x10D.\u0475l|\x80\x00\u07d4\x8e\xa6V\xe7\x1e\xc6Q\xbf\xa1|ZWY\xd8`1\xcc5\x99w\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x8e\xae)CU\x98\xba\x8f\x1c\x93B\x8c\xdb>+M1\a\x8e\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\xb1\xfb\xe4\xe5\xd3\x01\x9c\xd7\xd3\r\xae\x9c\r[Lv\xfbc1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\xb5\x17t\xaf k\x96k\x89\t\xc4Z\xa6r'H\x80,\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e\xb8\xc7\x19\x82\xa0\x0f\xb8Bu)2S\xf8\x04ED\xb6kI\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\x8e\xcb\u03ec\xbf\xaf\xe9\xf0\f9\"\xa2N,\xf0\x02gV\xca \x8a\x011\xbe\xb9%\xff\xd3 \x00\x00\u07d4\x8e\u03b2\xe1$Sl[_\xfcd\x0e\xd1O\xf1^\u0668\xcbq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8e\u042f\x11\xff(p\xda\x06\x81\x00J\xfe\x18\xb0\x13\xf7\xbd8\x82\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\x8e\xd1Cp\x1f/r(\x0f\xd0J{Ad(\x19y\xea\x87\u0248\xc2I\xfd\xd3'x\x00\x00\u07d4\x8e\xd1R\x8bD~\xd4)y\x02\xf69\xc5\x14\u0414J\x88\xf8\u0209\n\xc6\xe7z\xb6c\xa8\x00\x00\u07d4\x8e\xd4(L\x0fGD\x9c\x15\xb8\u0673$]\u8fb6\u0380\xbf\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\x8e\xde~=\xc5\aI\xc6\xc5\x0e.(\x16\x84x\xc3M\xb8\x19F\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\x8e\xe5\x843}\xdb\xc8\x0f\x9e4\x98\xdfU\xf0\xa2\x1e\xac\xb5\u007f\xb1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x8e\xeb\xec\x1ab\xc0\x8b\x05\xa7\xd1\u0551\x80\xaf\x9f\xf0\u044e?6\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x8e\xf4\u0622\xc2o\xf7\x14\xb6u\x89\x19\x80\x1c\x83\xb6\xc7\xc0\x00\x00\u07d4\x8fM\x1dAi>F,\xf9\x82\xfd\x81\u042ap\x1d:St\u0249\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8fM\x1e~Ea(J4\xfe\xf9g<\r4\xe1*\xf4\xaa\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8fO\xb1\xae\xa7\xcd\x0fW\x0e\xa5\xe6\x1b@\xa4\xf4Q\vbd\xe4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x8fV\x1bA\xb2\t\xf2H\u0229\x9f\x85\x87\x887bP`\x9c\xf3\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94\x8fX\xd84\x8f\xc1\xdcN\r\xd84;eC\xc8W\x04^\xe9@\x8a\x02\xe3\x03\x8d\xf4s\x03(\x00\x00\u07d4\x8f`\x89_\xbe\xbb\xb5\x01\u007f\xcb\xff<\u0763\x97)+\xf2[\xa6\x89\x17D\x06\xff\x9fo\u0480\x00\u07d4\x8fd\xb9\xc1$m\x85x1d1\a\xd3U\xb5\xc7_\xef]O\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\x8ff\x0f\x8b.L|\u00b4\xac\x9cG\xed(P\x8d_\x8f\x86P\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x8fi\xea\xfd\x023\xca\xdb@Y\xabw\x9cF\xed\xf2\xa0PnH\x89`\xf0f \xa8IE\x00\x00\xe0\x94\x8fq~\xc1U/LD\x00\x84\xfb\xa1\x15J\x81\xdc\x00>\xbd\xc0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\x8f\x8a\xcb\x10v\a8\x84y\xf6K\xaa\xab\xea\x8f\xf0\a\xad\xa9}\x8a\x05\xc6\xf3\b\n\xd4#\xf4\x00\x00\u07d4\x8f\x8c\xd2n\x82\xe7\xc6\xde\xfd\x02\u07ed\a\x97\x90!\xcb\xf7\x15\f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x8f\x8f7\u042d\x8f3]*q\x01\xb4\x11V\xb6\x88\xa8\x1a\x9c\xbe\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\x8f\x92\x84O(*\x92\x99\x9e\u5d28\xd7s\xd0kiM\xbd\x9f\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x8f\xact\x8fxJ\x0f\xedh\u06e43\x19\xb4*u\xb4d\x9cn\x891T\xc9r\x9d\x05x\x00\x00\u07d4\x8f\u0665\xc3:}\x9e\xdc\xe0\x99{\xdfw\xab0d$\xa1\x1e\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x8f\xef\xfa\xdb8z\x15G\xfb(M\xa9\xb8\x14\u007f>|m\xc6\u0689-b{\xe4S\x05\b\x00\x00\u07d4\x8f\xf4`Ehw#\xdc3\xe4\u0419\xa0i\x04\xf1\ubd44\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\x8f\xfa\x06!\"\xac0t\x18\x82\x1a\u06d3\x11\aZ7\x03\xbf\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x8f\xfe2)\x97\xb8\xe4\x04B-\x19\xc5J\xad\xb1\x8f[\xc8\u9dc9\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x90\x01\x94\u0131\aC\x05\u045d\xe4\x05\xb0\xacx(\x0e\xca\xf9g\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x90\x03\xd2p\x89\x1b\xa2\xdfd=\xa84\x15\x83\x195E\xe3\xe0\x00\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x90\x05z\xf9\xaaf0~\xc9\xf03\xb2\x97$\u04f2\xf4\x1e\xb6\xf9\x8a\x19\xd1\u05aa\xdb,R\xe8\x00\x00\u07d4\x90\x0f\v\x8e5\xb6h\xf8\x1e\xf2R\xb18U\xaaP\a\xd0\x12\xe7\x89\x17\n\x0fP@\xe5\x04\x00\x00\u07d4\x90\x18\xcc\x1fH\xd20\x8e%*\xb6\b\x9f\xb9\x9a|\x1dV\x94\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x90\x1d\x99\xb6\x99\xe5\u0191\x15\x19\xcb v\xb4\xc7c0\xc5M\"\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x90-t\xa1W\xf7\u04b9\xa37\x8b\x1fVp70\xe0:\x17\x19\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x904\x13\x87\x8a\xea;\xc1\bc\t\xa3\xfev\x8beU\x9e\x8c\xab\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x90If\xcc\"\x13\xb5\xb8\xcb[\xd6\b\x9e\xf9\xcd\xdb\xef~\xdf\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\x90L\xaaB\x9ca\x9d\x94\x0f\x8egA\x82j\r\xb6\x92\xb1\x97(\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x90R\xf2\xe4\xa3\xe3\xc1-\xd1\xc7\x1b\xf7\x8aN\xc3\x04=\u020b~\x89\x0e~\xeb\xa3A\vt\x00\x00\u0794\x90U&V\x8a\xc1#\xaf\xc0\xe8J\xa7\x15\x12O\xeb\xe8=\xc8|\x88\xf8i\x93)g~\x00\x00\u07d4\x90\x92\x91\x87\a\xc6!\xfd\xbd\x1d\x90\xfb\x80\xebx\u007f\xd2osP\x89\x85[[\xa6\\\x84\xf0\x00\x00\u07d4\x90\x9b^v:9\xdc\u01d5\"=s\xa1\u06f7\xd9L\xa7Z\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\x90\xac\xce\xd7\xe4\x8c\b\u01b94dm\xfa\n\xdf)\u0714\aO\x89\x03\vK\x15{\xbdI\x00\x00\u07d4\x90\xb1\xf3p\xf9\xc1\xeb\v\xe0\xfb\x8e+\x8a\xd9jAcq\u074a\x890\xca\x02O\x98{\x90\x00\x00\u07d4\x90\xb6/\x13\x1a_)\xb4UqQ>\xe7\xa7J\x8f\v#\"\x02\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x90\xbdb\xa0P\x84Ra\xfaJ\x9f|\xf2A\xeac\v\x05\ufe09\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x90\xc4\x1e\xba\x00\x8e \xcb\xe9'\xf3F`?\u0206\x98\x12Yi\x89\x02F\xdd\xf9yvh\x00\x00\u07d4\x90\u0480\x9a\xe1\xd1\xff\xd8\xf6>\xda\x01\xdeI\xddU-\xf3\u047c\x89\u063beI\xb0+\xb8\x00\x00\u07d4\x90\xdc\t\xf7\x17\xfc*[i\xfd`\xba\b\xeb\xf4\v\xf4\xe8$l\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x90\xe3\x00\xacqE\x1e@\x1f\x88\u007fnw(\x85\x16G\xa8\x0e\a\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x90\xe3Z\xab\xb2\xde\xef@\x8b\xb9\xb5\xac\xefqDW\xdf\xdebr\x89\x05l\xd5_\xc6M\xfe\x00\x00\u07d4\x90\xe7\a\x0fM\x03?\xe6\x91\f\x9e\xfeZ'\x8e\x1f\xc6#M\xef\x89\x05q8\b\x19\xb3\x04\x00\x00\u07d4\x90\xe9>M\xc1q!HyR36\x14\x00+\xe4#VI\x8e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x90\u9a68.\u06a8\x14\u0084\xd22\xb6\u9e90p\x1dIR\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\x90\xf7t\xc9\x14}\u0790\x85=\xdcC\xf0\x8f\x16\xd4U\x17\x8b\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x90\xfcS{!\x06Xf\n\x83\xba\xa9\xacJ\x84\x02\xf6WF\xa8\x89e\xea=\xb7UF`\x00\x00\u07d4\x91\x05\n\\\xff\xad\xed\xb4\xbbn\xaa\xfb\xc9\xe5\x014(\xe9l\x80\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x91\x05\x17d\xafk\x80\x8eB\x12\xc7~0\xa5W.\xaa1pp\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x91\v}Wz~9\xaa#\xac\xf6*\xd7\xf1\xef4)4\xb9h\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x91\x0e\x99eC4Lh\x15\xfb\x97\u0367\xafK\x86\x98vZ[\x89\x05\x9a\xf6\x98)\xcfd\x00\x00\u07d4\x91\x1f\xee\xa6\x1f\xe0\xedP\u0179\xe5\xa0\xd6`q9\x9d(\xbd\u0189\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x91\x1f\xf23\xe1\xa2\x11\xc0\x17,\x92\xb4l\xf9\x97\x03\x05\x82\xc8:\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x91 \xe7\x11s\xe1\xba\x19\xba\x8f\x9fO\xdb\u072a4\xe1\u05bbx\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91!\x17\x12q\x9f+\bM;8u\xa8Pi\xf4f61A\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x91#\x04\x11\x8b\x80G=\x9e\x9f\xe3\xeeE\x8f\xbea\x0f\xfd\xa2\xbb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x91Tky\xec\xf6\x9f\x93kZV\x15\b\xb0\xd7\xe5\f\u0159/\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x91V\u0440)5\x0eG\x04\b\xf1_\x1a\xa3\xbe\x9f\x04\ng\u018965\u026d\xc5\u07a0\x00\x00\u07d4\x91b\x0f>\xb3\x04\xe8\x13\u048b\x02\x97Ume\xdcN]\u5a89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\x91k\xf7\xe3\xc5E\x92\x1d2\x06\xd9\x00\xc2O\x14\x12|\xbd^p\x8a\x03\xd0\u077c}\xf2\xbb\x10\x00\x00\u0794\x91l\xf1}qA(\x05\xf4\xaf\xc3DJ\v\x8d\xd1\xd93\x9d\x16\x88\xc6s\xce<@\x16\x00\x00\u07d4\x91{\x8f\x9f:\x8d\t\xe9 ,R\u009erA\x96\xb8\x97\xd3^\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\x91\x89g\x91\x8c\u0617\xdd\x00\x05\xe3m\xc6\u0203\xefC\x8f\xc8\u01c9\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\x91\x89\x8e\xab\x8c\x05\xc0\"(\x83\xcdM\xb2;w\x95\xe1\xa2J\u05c9lk\x93[\x8b\xbd@\x00\x00\u0794\x91\x91\xf9F\x98!\x05\x16\xcfc!\xa1B\a\x0e Yvt\xed\x88\xee\x9d[\xe6\xfc\x11\x00\x00\u07d4\x91\xa4\x14\x9a,{\x1b:g\xea(\xaf\xf3G%\u0fcdu$\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x91\xa7\x87\xbcQ\x96\xf3HW\xfe\f7/M\xf3v\xaa\xa7f\x13\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91\xa8\xba\xae\xd0\x12\xea.c\x80;Y=\r\f*\xabL[\n\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x91\xac\\\xfeg\xc5J\xa7\xeb\xfb\xa4HflF\x1a;\x1f\xe2\xe1\x89\x15\xc94\x92\xbf\x9d\xfc\x00\x00\u07d4\x91\xbb?y\x02+\xf3\xc4S\xf4\xff%n&\x9b\x15\xcf,\x9c\xbd\x89RX\\\x13\xfe:\\\x00\x00\u07d4\x91\xc7^<\xb4\xaa\x89\xf3F\x19\xa1d\xe2\xa4x\x98\xf5gM\x9c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x91\xc8\f\xaa\b\x1b85\x1d*\x0e\x0e\x00\xf8\n4\xe5dt\xc1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x91\xccF\xaa7\x9f\x85jf@\xdc\xcdZd\x8ay\x02\xf8I\u0649\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x91\u04a9\xee\x1am\xb2\x0fS\x17\u0327\xfb\xe218\x95\u06ce\xf8\x8a\x01\xcc\u00e5/0n(\x00\x00\u07d4\x91\xd6n\xa6(\x8f\xaaK=`l*\xa4\\{k\x8a%'9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x91\u06f6\xaa\xad\x14\x95\x85\xbeG7\\]m\xe5\xff\t\x19\x15\x18\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x91\xe8\x81\x06R\xe8\xe6\x16\x15%\xd6;\xb7u\x1d\xc2\x0fg`v\x89'Mej\xc9\x0e4\x00\x00\u07d4\x91\xf5\x16\x14l\xda (\x17\x19\x97\x80`\u01beAI\x06|\x88\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x91\xf6$\xb2J\x1f\xa5\xa0V\xfeW\x12)\xe77\x9d\xb1K\x9a\x1e\x8a\x02\x8a\x85\x17\xc6i\xb3W\x00\x00\xe0\x94\x91\xfe\x8aLad\u07cf\xa6\x06\x99]k\xa7\xad\xca\xf1\u0213\u038a\x03\x99\x92d\x8a#\u0220\x00\x00\u07d4\x92\x1fRa\xf4\xf6\x12v\a\x06\x89&%\xc7^{\u0396\xb7\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92!\xc9\xce\x01#&et\x10\x96\xac\a#Y\x03\xad\x1f\xe2\xfc\x89\x06\xdbc3U\"b\x80\x00\u07d4\x92%\x988`\xa1\xcbF#\xc7$\x80\xac\x16'+\f\x95\xe5\xf5\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x92%\xd4jZ\x80\x949$\xa3\x9e[\x84\xb9m\xa0\xacE\x05\x81\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x92* \u01da\x1d:&\xdd8)g{\xf1\xd4\\\x8fg+\xb6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x92C\x8eR\x03\xb64o\xf8\x86\xd7\xc3b\x88\xaa\xcc\xccx\xce\u028965\u026d\xc5\u07a0\x00\x00\u07d4\x92C\xd7v-w({\x12c\x86\x88\xb9\x85N\x88\xa7i\xb2q\x8965\u026d\xc5\u07a0\x00\x00\u0794\x92K\xcez\x85<\x97\v\xb5\xec{\xb7Y\xba\xeb\x9ct\x10\x85{\x88\xbe -j\x0e\xda\x00\x00\u07d4\x92N\xfam\xb5\x95\xb7\x93\x13'~\x881\x96%\akX\n\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92U\x82&\xb3\x84bl\xadH\xe0\x9d\x96k\xf19^\xe7\xea]\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4\x92`\x82\xcb~\xedK\x19\x93\xad$ZGrg\xe1\xc3<\xd5h\x89\x14Jt\xba\u07e4\xb6\x00\x00\u07d4\x92b\t\xb7\xfd\xa5N\x8d\u06dd\x9eM=\x19\xeb\u070e\x88\u009f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92h\xd6&FV6\x11\xdc;\x83*0\xaa#\x94\xc6F\x13\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92i\x8e4Sx\xc6-\x8e\xda\x18M\x946j\x14K\f\x10[\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\x92y:\u0173rhwJq0\xde+\xbd3\x04\x05f\x17s\x89\x02,\xa3X|\xf4\xeb\x00\x00\xe0\x94\x92y\xb2\"\x8c\xec\x8f{M\xda?2\x0e\x9a\x04f\xc2\xf5\x85\u028a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x92|\xb7\xdc\x18p6\xb5B{\xc7\xe2\x00\xc5\xecE\f\x1d'\u0509\v\xb5\x9a'\x95<`\x00\x00\u07d4\x92|\u00bf\xda\x0e\b\x8d\x02\xef\xf7\v8\xb0\x8a\xa5<\xc3\tA\x89do`\xa1\xf9\x866\x00\x00\xe0\x94\x92\x84\xf9m\xdbG\xb5\x18n\xe5X\xaa12M\xf56\x1c\x0fs\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\x92\x9d6\x8e\xb4j-\x1f\xbd\xc8\xff\xa0`~\xdeK\xa8\x8fY\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xa7\u0166Cb\xe9\xf8B\xa2=\xec\xa2\x105\x85\u007f\x88\x98\x00\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\x92\xa8\x98\xd4o\x19q\x9c8\x12j\x8a<'\x86z\xe2\xce\u5589lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xa9q\xa79y\x9f\x8c\xb4\x8e\xa8G]r\xb2\xd2GAr\xe6\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x92\xaa\xe5\x97h\xed\xdf\xf8<\xfe`\xbbQ.s\n\x05\xa1a\u05c9\\\x97xA\fv\u0440\x00\u07d4\x92\xad\x1b=u\xfb\xa6}Tf=\xa9\xfc\x84\x8a\x8a\xde\x10\xfag\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xae[|~\xb4\x92\xff\x1f\xfa\x16\xddB\xad\x9c\xad@\xb7\xf8\u0709.\xe4IU\b\x98\xe4\x00\x00\u07d4\x92\xc0\xf5s\xec\xcfb\xc5H\x10\xeek\xa8\xd1\xf1\x13T+0\x1b\x89\xb7ro\x16\u0331\xe0\x00\x00\u07d4\x92\xc1?\xe0\xd6\u0387\xfdP\xe0=\uf7e6@\x05\t\xbdps\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x92\xc9L( \xdf\xcfqV\xe6\xf10\x88\xec\u754b6v\xfd\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4\x92\xcf\xd6\x01\x88\xef\u07f2\xf8\xc2\xe7\xb1i\x8a\xbb\x95&\xc1Q\x1f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\u062d\x9aMah;\x80\u0526g.\x84\xc2\rbB\x1e\x80\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x92\u0725\xe1\x02\xb3\xb8\x1b`\xf1\xa5\x04cIG\xc3t\xa8\x8c\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x92\xe454\x0e\x9d%<\x00%c\x89\xf5+\x06}U\x97Nv\x89\x0e\x87?D\x13<\xb0\x00\x00\xe0\x94\x92\xe49(\x16\xe5\xf2\xef_\xb6X7\xce\xc2\xc22\\\xc6I\"\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x92\xe6X\x1e\x1d\xa1\xf9\xb8F\xe0\x93G3=\xc8\x18\xe2\u04acf\x89\xc5S%\xcat\x15\xe0\x00\x00\u07d4\x93\x1d\xf3M\x12%\xbc\xd4\"Nch\r\\L\t\xbc\xe75\xa6\x89\x03\xaf\xb0\x87\xb8v\x90\x00\x00\u07d4\x93\x1f\xe7\x12\xf6B\a\xa2\xfdP\"r\x88CT\x8b\xfb\x8c\xbb\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x93#_4\r(c\xe1\x8d/LR\x99e\x16\x13\x8d\"\x02g\x89\x04\x00.D\xfd\xa7\xd4\x00\x00\u07d4\x93%\x82U\xb3|\u007fX\xf4\xb1\x06s\xa92\xdd:\xfd\x90\xf4\xf2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x93(\xd5\\\xcb?\xceS\x1f\x19\x93\x823\x9f\x0eWn\xe8@\xa3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x93)\xff\xdc&\x8b\xab\u0788t\xb3f@l\x81D[\x9b-5\x89\x16\xe6/\x8cs\f\xa1\x80\x00\u07d4\x93+\x9c\x04\xd4\r*\xc80\x83\xd9B\x98\x16\x9d\xae\x81\xab.\u0409lk\x93[\x8b\xbd@\x00\x00\u07d4\x9346\xc8G&U\xf6L:\xfa\xaf|Lb\x1c\x83\xa6+8\x8965\u026d\xc5\u07a0\x00\x00\u0794\x93;\xf3?\x82\x99p+:\x90&B\xc3>\v\xfa\xea\\\x1c\xa3\x88\xd2\xf1?w\x89\xf0\x00\x00\u07d4\x93@4\\\xa6\xa3\uaf77sc\xf2X`C\xf2\x948\xce\v\x89\x1c\xc8\x05\xda\r\xff\xf1\x00\x00\xe0\x94\x93@\xb5\xf6x\xe4^\xe0^\xb7\b\xbbz\xbbn\xc8\xf0\x8f\x1bk\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x93J\xf2\x1b~\xbf\xa4g\xe2\xce\xd6Z\xa3N\xdd:\x0e\xc7\x132\x8a\a\x80\x1f>\x80\xcc\x0f\xf0\x00\x00\xe0\x94\x93PiDJj\x98M\xe2\bNFi*\xb9\x9fg\x1f\xc7'\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\xe0\x94\x93P~\x9e\x81\x19\xcb\xce\u068a\xb0\x87\xe7\xec\xb0q8=i\x81\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\x93g\x8a\x00\x00\xe0\x94\x93m\xcf\x00\x01\x94\xe3\xbf\xf5\n\u0174$:;\xa0\x14\xd6a\u060a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x93o8\x13\xf5\xf6\xa1;\x8eO\xfe\xc8?\xe7\xf8&\x18jq\u0349\x1c0s\x1c\xec\x03 \x00\x00\u07d4\x93t\x86\x9dJ\x99\x11\xee\x1e\xafU\x8b\xc4\u00b6>\xc6:\xcf\u074965\u026d\xc5\u07a0\x00\x00\u07d4\x93uc\u0628\x0f\u05657\xb0\xe6m \xa0%%\xd5\u0606`\x89\x87\x86x2n\xac\x90\x00\x00\u07d4\x93v\xdc\xe2\xaf.\xc8\xdc\xdat\x1b~sEfF\x81\xd96h\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x93\x86\x8d\xdb*yM\x02\xeb\xda/\xa4\x80|v\xe3`\x98X\u0709m\xee\x15\xfc|$\xa7\x80\x00\xe0\x94\x93\x9cC\x13\xd2(\x0e\xdf^\a\x1b\xce\xd8F\x06?\n\x97]T\x8a\x19i6\x89t\xc0[\x00\x00\x00\xe0\x94\x93\xa6\xb3\xabB0\x10\xf9\x81\xa7H\x9dJ\xad%\xe2b\\WA\x8a\x04F\x80\xfej\x1e\xdeN\x80\x00\u07d4\x93\xaa\x8f\x92\xeb\xff\xf9\x91\xfc\x05^\x90ne\x1a\xc7h\xd3+\u02092\xf5\x1e\u06ea\xa30\x00\x00\u07d4\x93\xb4\xbf?\xdf\xf6\xde?NV\xbamw\x99\xdcK\x93\xa6T\x8f\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4\x93\xbc}\x9aJ\xbdD\u023b\xb8\xfe\x8b\xa8\x04\xc6\x1a\xd8\xd6Wl\x89\xd8\xd6\x11\x9a\x81F\x05\x00\x00\u07d4\x93\xc2\xe6N]\xe5X\x9e\xd2P\x06\xe8C\x19n\xe9\xb1\xcf\v>\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\x93\u020e-\x88b\x1e0\xf5\x8a\x95\x86\xbe\xd4\t\x89\x99\xebg\u074a\x06\x9bZ\xfa\xc7P\xbb\x80\x00\x00\u07d4\x93\xe0\xf3~\xcd\xfb\x00\x86\xe3\xe8b\xa9p4D{\x1eM\xec\x1a\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\xe0\x94\x93\xe3\x03A\x1a\xfa\xf6\xc1\a\xa4A\x01\u026c[6\xe9\xd6S\x8b\x8a\r\xf9\xdd\xfe\xcd\x03e@\x00\x00\u07d4\x93\xf1\x8c\xd2R`@v\x14\x88\xc5\x13\x17M\x1eycv\x8b,\x89\x82\xff\xac\x9a\u0553r\x00\x00\u07d4\x94\x0fqQ@P\x9f\xfa\xbf\x97EF\xfa\xb3\x90\"\xa4\x19R\u0489K\xe4\xe7&{j\xe0\x00\x00\u07d4\x94,k\x8c\x95[\xc0\u0608\x12g\x8a#g%\xb3'9\xd9G\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\x94=7\x86JJS}5\xc8\u0657#\xcdd\x06\xce%b\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94C\x9c\xa9\xcc\x16\x9ay\u0520\x9c\xae^gvJo\x87\x1a!\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x94D\x9c\x01\xb3*\u007f\xa5Z\xf8\x10OB\xcd\xd8D\xaa\x8c\xbc@\x8a\x03\x81\x11\xa1\xf4\xf0<\x10\x00\x00\xe0\x94\x94E\xba\\0\xe9\x89a\xb8`$a\xd08]@\xfb\xd8\x03\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x94O\a\xb9o\x90\xc5\xf0\xd7\xc0\u0140S1I\xf3\xf5\x85\xa0x\x89\x04\x02\xf4\xcf\xeeb\xe8\x00\x00\u07d4\x94T\xb3\xa8\xbf\xf9p\x9f\xd0\u1407~l\xb6\u0219t\xdb\u0589\x90\xf54`\x8ar\x88\x00\x00\u07d4\x94]\x96\xeaW>\x8d\xf7&+\xbf\xa5r\"\x9bK\x16\x01k\x0f\x89\vX\x9e\xf9\x14\xc1B\x00\x00\u07d4\x94^\x18v\x9d~\xe7'\xc7\x01?\x92\xde$\xd1\x17\x96\u007f\xf3\x17\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94a'\x81\x03;W\xb1F\xeet\xe7S\xc6r\x01\u007fS\x85\xe4\x89\xc3(\t>a\xee@\x00\x00\xe0\x94\x94dJ\xd1\x16\xa4\x1c\xe2\xca\u007f\xbe\xc6\t\xbd\xefs\x8a*\xc7\u01ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x94p\xcc6YE\x86\x82\x18!\xc5\u0256\xb6\xed\xc8;mZ2\x89\x01M\x11 \u05f1`\x00\x00\xe0\x94\x94u\xc5\x10\xec\x9a&\x97\x92GtL=\x8c;\x0e\v_D\u04ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x94~\x11\xe5\xea)\ro\u00f3\x80H\x97\x9e\f\xd4N\xc7\xc1\u007f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94\x83\u064f\x14\xa3?\xdc\x11\x8d@9U\u00995\xed\xfc_p\x89\x18\xea;4\xefQ\x88\x00\x00\u07d4\x94\x911\xf2\x89C\x92\\\xfc\x97\xd4\x1e\f\xea\v&)s\xa70\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\x94\x9f\x84\xf0\xb1\xd7\u0127\xcfI\xee\u007f\x8b,J\x13M\xe3(x\x89%\"H\u07b6\xe6\x94\x00\x00\u07d4\x94\x9f\x8c\x10{\xc7\xf0\xac\xea\xa0\xf1pR\xaa\xdb\xd2\xf9s+.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x94\xa7\u0368\xf4\x81\xf9\u061dB\xc3\x03\xae\x162\xb3\xb7\t\xdb\x1d\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x94\xa9\xa7\x16\x911| d'\x1bQ\xc95?\xbd\xed5\x01\xa8\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x94\xadK\xad\x82K\xd0\ub7a4\x9cX\u03bc\xc0\xff^\b4k\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\x94\xbb\xc6}\x13\xf8\x9e\xbc\xa5\x94\xbe\x94\xbcQp\x92\f0\xd9\xf3\x89\x04X\xff\xa3\x15\nT\x00\x00\u07d4\x94\xbe:\xe5Ob\xd6c\xb0\xd4\u031e\x1e\xa8\xfe\x95V\ua7bf\x89\x01C\x13,\xa8C\x18\x00\x00\xe0\x94\x94\xc0U\xe8X5z\xaa0\xcf A\xfa\x90Y\xce\x16J\x1f\x91\x8a\x04<%\xe0\xdc\xc1\xbd\x1c\x00\x00\xe0\x94\x94\xc7B\xfdz\x8by\x06\xb3\xbf\xe4\xf8\x90O\xc0\xbe\\v\x803\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x94\xcaV\xdew\u007f\xd4S\x17\u007f^\x06\x94\xc4x\xe6j\xff\x8a\x84\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x94\xd8\x10t\xdbZ\xe1\x97\u04bb\x13s\xab\x80\xa8}\x12\x1cK\u04ca\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x94\u06c0xs\x86\n\xac=Z\xea\x1e\x88^R\xbf\xf2\x86\x99T\x89\xae\x8ez\v\xb5u\xd0\x00\x00\u07d4\x94\xe1\xf5\u02db\x8a\xba\xce\x03\xa1\xa6B\x82VU;i\f#U\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x94\xef\x8b\xe4Pw\xc7\xd4\xc5e'@\u0794jbbOq?\x89\x05l\xf5Y:\x18\xf8\x80\x00\u07d4\x94\xf1?\x9f\b6\xa3\xee$7\xa8I\"\u0498M\xc0\xf7\xd5;\x89\xa2\xa02\x9b\u00ca\xbe\x00\x00\u07d4\x94\xf8\xf0W\xdb~`\xe6u\xad\x94\x0f\x15X\x85\u0464w4\x8e\x89\x15\xbeat\xe1\x91.\x00\x00\xe0\x94\x94\xfc\u03ad\xfe\\\x10\x9c^\xae\xafF-C\x871B\u020e\"\x8a\x01\x045a\xa8\x82\x93\x00\x00\x00\u07d4\x95\x03N\x16!\x86Q7\xcdG9\xb3F\xdc\x17\xda:'\xc3N\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\x95\fh\xa4\t\x88\x15M#\x93\xff\xf8\xda|\u0369\x96\x14\xf7,\x89\xf9AF\xfd\x8d\xcd\xe5\x80\x00\xe0\x94\x95\x0f\xe9\xc6\xca\xd5\f\x18\xf1\x1a\x9e\xd9\xc4W@\xa6\x18\x06\x12\u040a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x95!\x83\xcf\u04ce5.W\x9d6\xde\xce\u0171\x84P\xf7\xfb\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95'\x8b\b\xde\xe7\xc0\xf2\xc8\xc0\xf7\"\xf9\xfc\xbb\xb9\xa5$\x1f\u0689\x82\x93\t\xf6O\r\xb0\x00\x00\u07d4\x95,W\xd2\xfb\x19Q\a\xd4\xcd\\\xa3\x00wA\x19\u07ed/x\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x955r\xf0\xeam\xf9\xb1\x97\xca\xe4\x0eK\x8e\xcc\x05lCq\u014965\u026d\xc5\u07a0\x00\x00\u07d4\x95>\xf6R\xe7\xb7i\xf5=nxjX\x95/\xa9>\xe6\xab\u725b\ny\x1f\x12\x110\x00\x00\u07d4\x95DpF1;/:^\x19\xb9H\xfd;\x8b\xed\xc8,q|\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x95]\xb3\xb7C`\xb9\xa2hg~s\u03a8!f\x8a\xf6\xfa\u038a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\x95`\xe8\xacg\x18\xa6\xa1\xcd\xcf\xf1\x89\xd6\x03\xc9\x06>A=\xa6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x95g\xa0\u0781\x1d\xe6\xff\t[~\xe6N\u007f\x1b\x83\xc2a[\x80\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\x95h\x1c\xda\xe6\x9b I\xce\x10\x1e2\\u\x98\x92\xca\xc3\xf8\x11\x89\x9a\xe9*\x9b\xc9L@\x00\x00\xe0\x94\x95h\xb7\xdeuV(\xaf5\x9a\x84T=\xe25\x04\xe1^A\xe6\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x95i\xc6:\x92\x84\xa8\x05bm\xb3\xa3.\x9d#c\x93GaQ\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x95\x80\x9e\x8d\xa3\xfb\xe4\xb7\xf2\x81\xf0\xb8\xb1q_B\x0f}}c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95\x9fW\xfd\xedj\xe3y\x13\xd9\x00\xb8\x1e_H\xa7\x93\"\xc6'\x89\r\xdb&\x10GI\x11\x80\x00\u07d4\x95\x9f\xf1\u007f\x1dQ\xb4s\xb4@\x10\x05'U\xa7\xfa\x8cu\xbdT\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x95\xa5w\xdc.\xb3\xael\xb9\xdf\xc7z\xf6\x97\xd7\xef\xdf\xe8\x9a\x01\x89\a_a\x0fp\xed \x00\x00\u07d4\x95\xcbm\x8acy\xf9J\xba\x8b\x88ViV,MD\x8eV\xa7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x95\xd5PB{ZQLu\x1ds\xa0\xf6\u049f\xb6]\"\xed\x10\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x95\u064d\f\x10i\x90\x8f\x06zR\xac\xac+\x8bSM\xa3z\xfd\x89oY\xb60\xa9)p\x80\x00\xe0\x94\x95\xdfN4E\xd7f&$\u010e\xbat\u03de\nS\xe9\xf72\x8a\v\xdb\xc4\x1e\x03H\xb3\x00\x00\x00\u07d4\x95\xe6\xa5K-_g\xa2JHu\xafu\x10|\xa7\xea\x9f\xd2\xfa\x89Hz\x9a0E9D\x00\x00\xe0\x94\x95\xe6\xf9=\xac\"\x8b\xc7XZ%sZ\xc2\xd0v\xcc:@\x17\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x95\xe7ad$\xcd\ta\xa7\x17'$t7\xf0\x06\x92r(\x0e\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x95\xe8\n\x82\xc2\f\xbe= `$,\xb9-sX\x10\xd04\xa2\x89\x01\xc3.F?\u0539\x80\x00\u07d4\x95\xf6-\x02C\xed\xe6\x1d\xad\x9a1e\xf59\x05'\rT\xe2B\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\x95\xfbZ\xfb\x14\xc1\uf6b7\xd1y\xc5\xc3\x00P?\xd6j^\xe2\x89\x01\xda\xf7\xa0+\r\xbe\x80\x00\u07d4\x96\x10Y\"\x02\u0082\xab\x9b\u0628\x84Q\x8b>\v\xd4u\x817\x89\x0e\x87?D\x13<\xb0\x00\x00\xe0\x94\x96\x1cY\xad\xc7E\x05\u0446M\x1e\xcf\u02ca\xfa\x04\x12Y<\x93\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\x96,\r\xec\x8a=FK\xf3\x9b\x12\x15\xea\xfd&H\n\xe4\x90\u0349l\x82\xe3\xea\xa5\x13\xe8\x00\x00\u07d4\x96,\xd2*\x8e\xdf\x1eONU\xb4\xb1]\xdb\xfb]\x9dT\x19q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x963K\xfe\x04\xff\xfaY\x02\x13\xea\xb3e\x14\xf38\xb8d\xb76\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x967\xdc\x12r=\x9cxX\x85B\uac02fO?\x03\x8d\x9d\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x96N\xabK'kL\u0618>\x15\xcar\xb1\x06\x90\x0f\xe4\x1f\u0389\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x96b\xee\x02\x19&h+1\xc5\xf2\x00\xceEz\xbe\xa7ll\xe9\x89$Y\x0e\x85\x89\xebj\x00\x00\xe0\x94\x96l\x04x\x1c\xb5\xe6}\xde25\xd7\xf8b\x0e\x1a\xb6c\xa9\xa5\x8a\x10\r P\xdacQ`\x00\x00\u07d4\x96pv\xa8w\xb1\x8e\xc1ZA[\xb1\x16\xf0n\xf3&E\u06e3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96{\xfa\xf7bC\u0379@\t\xae<\x8d5\x05\xe9\xc0\x80EK\xe0\xe8\x19\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x96\x92A\x91\xb7\xdfe[3\x19\xdcma7\xf4\x81\xa7:\x0f\xf3\x89\xd9\xec\xb4\xfd \x8eP\x00\x00\u07d4\x96\x96\x05!83\x8cr/\x11@\x81\\\xf7t\x9d\r;:t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x96\xa5_\x00\xdf\xf4\x05\xdcM\xe5\xe5\x8cW\xf6\xf6\xf0\xca\xc5]/\x89jf\x167\x9c\x87\xb5\x80\x00\u07d4\x96\xaaW?\xed/#4\x10\u06eeQ\x80\x14[#\xc3\x1a\x02\xf0\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x96\xadW\x9b\xbf\xa8\u06ce\xbe\xc9\u0486\xa7.Fa\xee\xd8\xe3V\x89:\v\xa4+\xeca\x83\x00\x00\u07d4\x96\xb44\xfe\x06W\xe4*\u0302\x12\xb6\x86Q9\xde\xde\x15\x97\x9c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x96\xb9\x06\xear\x9fFU\xaf\xe3\xe5}5'|\x96}\xfa\x15w\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x96\xd6-\xfdF\b\u007fb@\x9d\x93\xdd`a\x88\xe7\x0e8\x12W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96\xd9\u0328\xf5^\xea\x00@\xecn\xb3H\xa1wK\x95\xd9>\xf4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x96\xe7\xc0\xc9\u057f\x10\x82\x1b\xf1@\xc5X\xa1E\xb7\xca\xc2\x13\x97\x899>\xf1\xa5\x12|\x80\x00\x00\u07d4\x96\xeaj\u021a+\xac\x954{Q\u06e6=\x8b\xd5\xeb\xde\xdc\xe1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x96\xea\xfb\xf2\xfboM\xb9\xa46\xa7LE\xb5eDR\xe28\x19\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x96\xebR>\x83/P\n\x01}\xe1>\xc2\u007f]6lV\x0e\xff\x89\x10\xac\u03baC\xee(\x00\x00\u07d4\x96\xf0F*\xe6\xf8\xb9`\x88\xf7\xe9\u018ct\xb9\u062d4\xb3G\x89a\t=|,m8\x00\x00\u07d4\x96\xf8 P\vp\xf4\xa3\xe3#\x9da\x9c\xff\x8f\" u\xb15\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x96\xfeY\xc3\u06f3\xaa|\xc8\xcbbH\fe\xe5nb\x04\xa7\xe2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x96\xffoP\x99h\xf3l\xb4,\xbaH\xdb2\xf2\x1fVv\xab\xf8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x97\t8R*\xfb^\x8f\x99Hs\xc9\xfb\xdc&\xe3\xb3~1L\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x97\n\xbdS\xa5O\xcaJd) |\x18-MW\xbb9\u0520\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97\r\x8b\x8a\x00\x16\xd1C\x05O\x14\x9f\xb3\xb8\xe5P\xdc\a\x97\u01c965\u026d\xc5\u07a0\x00\x00\u07d4\x97,/\x96\xaa\x00\u03ca/ Z\xbc\xf8\x93|\fu\xf5\xd8\u0649\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97?N6\x1f\xe5\xde\u0358\x9dL\x8f}|\xc9y\x908]\xaf\x89\x15\x0f\x85C\xa3\x87B\x00\x00\u07d4\x97M\x05A\xabJG\xec\u007fu6\x9c\x00i\xb6J\x1b\x81w\x10\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u0794\x97M/\x17\x89_)\x02\x04\x9d\xea\xae\xcf\t\xc3\x04e\a@-\x88\xcc\x19\u00947\xab\x80\x00\u07d4\x97R\xd1O^\x10\x93\xf0qq\x1c\x1a\xdb\xc4\xe3\xeb\x1e\\W\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97V\xe1v\xc9\xefi>\xe1\xee\u01b9\xf8\xb1Q\xd3\x13\xbe\xb0\x99\x89A\rXj \xa4\xc0\x00\x00\u07d4\x97_7d\xe9{\xbc\xcfv|\xbd;y[\xa8m\x8b\xa9\x84\x0e\x89\x12\xc1\xb6\xee\xd0=(\x00\x00\xe0\x94\x97j\x18Sj\xf4\x18tBc\b\x87\x1b\xcd\x15\x12\xa7u\xc9\xf8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x97n<\xea\xf3\xf1\xafQ\xf8\u009a\xff]\u007f\xa2\x1f\x03\x86\xd8\xee\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x97w\xcca\xcfuk\xe3\xb3\xc2\f\xd4I\x1ci\xd2u\xe7\xa1 \x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x97\x81\v\xaf\xc3~\x840c2\xaa\xcb5\xe9*\xd9\x11\xd2=$\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x97\x8cC\f\xe45\x9b\x06\xbc,\xdf\\)\x85\xfc\x95\x0eP\xd5\u0209\x1a\x05V\x90\xd9\u06c0\x00\x00\u07d4\x97\x95\xf6C\x19\xfc\x17\xdd\x0f\x82a\xf9\xd2\x06\xfbf\xb6L\xd0\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97\x99\xca!\xdb\xcfi\xbf\xa1\xb3\xf7+\xacQ\xb9\xe3\xcaX|\xf9\x89\\(=A\x03\x94\x10\x00\x00\u07d4\x97\x9c\xbf!\xdf\xec\x8a\xce?\x1c\x19m\x82\u07d6%4\xdf9O\x89\x99\x91\xd4x\xddM\x16\x00\x00\u07d4\x97\x9dh\x1ca}\xa1o!\xbc\xac\xa1\x01\xed\x16\xed\x01Z\xb6\x96\x89e\xea=\xb7UF`\x00\x00\u07d4\x97\x9f0\x15\x8bWK\x99\x9a\xab4\x81\a\xb9\xee\xd8[\x1f\xf8\xc1\x894\x95tD\xb8@\xe8\x00\x00\u07d4\x97\xa8o\x01\xce?|\xfdDA3\x0e\x1c\x9b\x19\xe1\xb1\x06\x06\xef\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x97\xb9\x1e\xfesP\xc2\xd5~~@k\xab\x18\xf3a{\xcd\xe1J\x8a\x02\x1e\x19\x99\xbb\xd5\u04be\x00\x00\u07d4\x97\xd0\xd9r^;p\xe6u\x841s\x93\x8e\xd3q\xb6,\u007f\xac\x89\t79SM(h\x00\x00\u07d4\x97\xd9\xe4jv\x04\u05f5\xa4\xeaN\xe6\x1aB\xb3\xd25\x0f\xc3\xed\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x97\xdc&\xecg\n1\xe0\"\x1d*u\xbc]\xc9\xf9\f\x1fo\u0509\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\x97\xde!\xe4!\xc3\u007f\xe4\xb8\x02_\x9aQ\xb7\xb3\x90\xb5\xdfx\x04\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\x97\xe2\x89s\xb8`\xc5g@(\x00\xfb\xb6<\xe3\x9a\x04\x8a=y\x89\x05B%:\x12l\xe4\x00\x00\u07d4\x97\xe5\xcca'\xc4\xf8\x85\xbe\x02\xf4KB\xd1\u0230\xac\x91\u44c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x97\xf1\xfeL\x80\x83\xe5\x96!*\x18w(\xdd\\\xf8\n1\xbe\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\x97\xf7v\x06W\xc1\xe2\x02u\x90\x86\x96>\xb4!\x1c_\x819\xb9\x8a\n\x8a\t\u007f\xcb=\x17h\x00\x00\xe0\x94\x97\xf9\x9bk\xa3\x13F\u0358\xa9\xfeL0\x8f\x87\u0165\x8cQQ\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x98\n\x84\xb6\x86\xfc1\xbd\xc8<\"\x10XTjq\xb1\x1f\x83\x8a\x89*AUH\xaf\x86\x81\x80\x00\u07d4\x98\x10\xe3J\x94\xdbn\xd1V\xd08\x9a\x0e+\x80\xf4\xfdk\n\x8a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\x1d\xdf\x04\x04\xe4\xd2-\xdaUj\a&\xf0\v-\x98\xab\x95i\x8965f3\xeb\xd8\xea\x00\x00\xe0\x94\x98\x1fq'u\xc0\xda\xd9u\x18\xff\xed\xcbG\xb9\xad\x1dl'b\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\x984h!\x80\xb9\x82\xd1f\xba\u06dd\x9d\x1d\x9b\xbf\x01m\x87\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x986\xb4\xd3\x04sd\x1a\xb5j\xee\xe1\x92Bv\x1drrQx\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x989sB\xec_=L\xb8w\xe5N\xf5\xd6\xf1\xd3fs\x1b\u050a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\x98Fd\x886\xa3\a\xa0W\x18O\xd5\x1fb\x8a_\x8c\x12B|\x8a\x04\vi\xbfC\xdc\xe8\xf0\x00\x00\xe0\x94\x98Jy\x85\xe3\xcc~\xb5\xc96\x91\xf6\xf8\xcc{\x8f$]\x01\xb2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x98]p\xd2\a\x89+\xed9\x85\x90\x02N$!\xb1\xcc\x11\x93Y\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x98m\xf4~v\xe4\u05e7\x89\xcd\xee\x91<\u0243\x16P\x93l\x9d\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\x98t\x80?\xe1\xf3\xa06^y\"\xb1Bp\xea\xeb\x03,\xc1\xb5\x89<\xf5\x92\x88$\xc6\xc2\x00\x00\u07d4\x98ub4\x95\xa4l\xdb\xf2YS\x0f\xf88\xa1y\x9e\u00c9\x91\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98v\x18\xc8VV |{\xac\x15\a\xc0\xff\xef\xa2\xfbd\xb0\x92\x89\x03}\xfeC1\x89\xe3\x80\x00\u07d4\x98|\x9b\xcdn?9\x90\xa5+\xe3\xed\xa4q\f'Q\x8fOr\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x98\x82\x96|\xeeh\u04a89\xfa\u062bJ|=\xdd\xf6\xc0\xad\u0209Hx\xbe\x1f\xfa\xf9]\x00\x00\u07d4\x98\x85\\}\xfb\xee3SD\x90J\x12\xc4\fs\x17\x95\xb1:T\x899\xfb\xae\x8d\x04-\xd0\x00\x00\u07d4\x98\x9c\f\xcf\xf6T\xda\x03\xae\xb1\x1a\xf7\x01\x05Ea\xd6)~\x1d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x98\xa0\xe5Lm\x9d\u023e\x96'l\xeb\xf4\xfe\xc4`\xf6#]\x85\x89j\u0202\x10\tR\u01c0\x00\u07d4\x98\xb7i\xcc0\\\xec\xfbb\x9a\x00\xc9\a\x06\x9d~\xf9\xbc:\x12\x89\x01h\u048e?\x00(\x00\x00\xe0\x94\x98\xbaN\x9c\xa7/\xdd\xc2\fi\xb49ov\xf8\x18?z*N\x8a\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\x00\u07d4\x98\xbeimQ\xe3\x90\xff\x1cP\x1b\x8a\x0fc1\xb6(\xdd\u016d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\xbe\u04e7.\xcc\xfb\xaf\xb9#H\x92\x93\xe4)\xe7\x03\xc7\xe2[\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x98\xbfJ\xf3\x81\v\x84#\x87\xdbp\xc1MF\t\x96&\x00=\x10\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x98\xc1\x0e\xbf,O\x97\u02e5\xa1\xab?*\xaf\xe1\xca\xc4#\xf8\u02c9\x10CV\x1a\x88)0\x00\x00\u07d4\x98\xc1\x9d\xba\x81\v\xa6\x11\xe6\x8f/\x83\xee\x16\xf6\xe7tO\f\x1f\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x98\xc5IJ\x03\xac\x91\xa7h\xdf\xfc\x0e\xa1\xdd\u0b3f\x88\x90\x19\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\x98\xd2\x04\xf9\b_\x8c\x8e}\xe2>X\x9bd\xc6\xef\xf6\x92\xccc\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x98\xd3s\x19\x92\xd1\xd4\x0e\x12\x11\xc7\xf75\xf2\x18\x9a\xfa\a\x02\xe0\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x98\xe2\xb6\xd6\x06\xfd-i\x91\xc9\xd6\xd4\a\u007f\xdf?\xddE\x85\u06890\xdf\x1ao\x8a\xd6(\x00\x00\u07d4\x98\xe3\xe9\v(\xfc\xca\ue087y\xb8\xd4\nUh\xc4\x11n!\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\x98\xe6\xf5G\u06c8\xe7_\x1f\x9c\x8a\xc2\xc5\xcf\x16'\xbaX\v>\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x98\xf4\xaf:\xf0\xae\xde_\xaf\xdcB\xa0\x81\xec\xc1\xf8\x9e<\xcf \x8a\x01\xfd\x934\x94\xaa_\xe0\x00\x00\u07d4\x98\xf6\xb8\xe6!=\xbc\x9aU\x81\xf4\xcc\xe6e_\x95%+\xdb\a\x89\x11Xr\xb0\xbc\xa40\x00\x00\u07d4\x99\te\r\u05719{\x8b\x8b\x0e\xb6\x94\x99\xb2\x91\xb0\xad\x12\x13\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x99\x11s`\x19G\xc2\bJb\xd69R~\x96\x15\x12W\x9a\xf9\x89 \x86\xac5\x10R`\x00\x00\u07d4\x99\x12\x9d[<\f\xdeG\xea\r\xefM\xfc\a\r\x1fJY\x95'\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x99\x17\u058dJ\xf3A\xd6Q\xe7\xf0\a\\m\xe6\xd7\x14Nt\t\x8a\x012\xd4Gl\b\xe6\xf0\x00\x00\u07d4\x99\x1a\xc7\xcap\x97\x11_& ^\xee\x0e\xf7\xd4\x1e\xb4\xe3\x11\xae\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\x99#e\xd7d\xc5\xce5@9\xdd\xfc\x91.\x02:u\xb8\xe1h\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x99&F\xac\x1a\u02ab\xf5\u076b\xa8\xf9B\x9a\xa6\xa9Nt\x96\xa7\x8967Pz0\xab\xeb\x00\x00\u07d4\x99&\x83'\xc3s3.\x06\xc3\xf6\x16B\x87\xd4U\xb9\xd5\xfaK\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99(\xffqZ\xfc:+`\xf8\xebL\u013aN\xe8\u06b6\u5749\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4\x992\xef\x1c\x85\xb7Z\x9b*\x80\x05}P\x874\xc5\x10\x85\xbe\u0309\x02\xb8?\xa50\x1dY\x00\x00\xe0\x94\x99?\x14ax`^f\xd5\x17\xbex.\xf0\xb3\xc6\x1aN\x19%\x8a\x01|\x1f\x055\u05e5\x83\x00\x00\xe0\x94\x99A7\x04\xb1\xa3.p\xf3\xbc\ri\u0748\x1c8VkT\u02ca\x05\xcckiF1\xf7\x12\x00\x00\u07d4\x99AR\xfc\x95\xd5\xc1\u028b\x88\x11:\xbb\xadMq\x0e@\xde\xf6\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x99D\xfe\xe9\xd3JJ\x88\x00#\u01c92\xc0\vY\xd5\xc8*\x82\x89(\xa8\xa5k6\x90\a\x00\x00\u07d4\x99L\u00b5\"~\xc3\xcf\x04\x85\x12F|A\xb7\xb7\xb7H\x90\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99q\xdf`\xf0\xaef\xdc\xe9\xe8\xc8N\x17\x14\x9f\t\xf9\xc5/d\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x99v\x94~\xff_j\xe5\xda\b\xddT\x11\x92\xf3x\xb4(\xff\x94\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x99}e\x92\xa3\x15\x89\xac\xc3\x1b\x99\x01\xfb\xeb<\xc3\xd6[2\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\x82\xa5\x89\x0f\xfbT\x06\u04ec\xa8\u04bf\xc1\xddp\xaa\xa8\n\xe0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\x87\x8f\x9dn\n~\u066e\u01c2\x97\xb78y\xa8\x01\x95\xaf\xe0\x89\xd7\xc1\x98q\x0ef\xb0\x00\x00\u07d4\x99\x8c\x1f\x93\xbc\xdbo\xf2<\x10\xd0\u0712G(\xb7;\xe2\xff\x9f\x896[\xf3\xa43\xea\xf3\x00\x00\u07d4\x99\x91aL[\xaaG\xddl\x96\x87FE\xf9z\xdd,=\x83\x80\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x99\x92J\x98\x16\xbb}\xdf?\xec\x18D\x82\x8e\x9a\xd7\xd0k\xf4\xe6\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\x99\x99vh\xf7\xc1\xa4\xff\x9e1\xf9\x97z\xe3\"K\u02c8z\x85\x89\x0f\xc969(\x01\xc0\x00\x00\u07d4\x99\x9cI\xc1t\xca\x13\xbc\x83l\x1e\n\x92\xbf\xf4\x8b'\x15C\u0289\xb1\xcf$\xdd\u0431@\x00\x00\u07d4\x99\xa4\xde\x19\xde\u05d0\b\xcf\xdc\xd4]\x01M.XK\x89\x14\xa8\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\x99\xa9k\xf2$.\xa1\xb3\x9e\xceo\xcc\r\x18\xae\xd0\f\x01y\xf3\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x99\xb0\x18\x93+\xca\xd3U\xb6y+%]\xb6p-\xec\x8c\xe5\u0749\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\x99\xb7C\xd1\xd9\xef\xf9\r\x9a\x194\xb4\xdb!\xd5\x19\u061bJ8\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x99\xb8\xc8$\x86\x9d\xe9\xed$\xf3\xbf\xf6\x85L\xb6\xddE\xcc?\x9f\x89e\xea=\xb7UF`\x00\x00\u07d4\x99\xc0\x17L\xf8N\a\x83\xc2 \xb4\xebj\xe1\x8f\xe7\x03\x85J\u04c9py\xa2W=\fx\x00\x00\u07d4\x99\xc1\xd9\xf4\fj\xb7\xf8\xa9/\xce/\xdc\xe4zT\xa5\x86\xc5?\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x99\xc26\x14\x1d\xae\xc87\xec\xe0O\xda\xee\x1d\x90\u03cb\xbd\xc1\x04\x89ve\x16\xac\xac\r \x00\x00\u07d4\x99\xc3\x1f\xe7HX7\x87\xcd\xd3\xe5%\xb2\x81\xb2\x18\x96\x179\xe3\x897\b\xba\xed=h\x90\x00\x00\u07d4\x99\xc4u\xbf\x02\xe8\xb9!J\xda_\xad\x02\xfd\xfd\x15\xba6\\\f\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\x99\u0203%\x85F\xcc~N\x97\x1fR.8\x99\x18\xda^\xa6:\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x99\xc9\xf9>E\xfe<\x14\x18\xc3S\xe4\u016c8\x94\xee\xf8\x12\x1e\x89\x05\x85\xba\xf1E\x05\v\x00\x00\xe0\x94\x99\xd1W\x9c\xd4&\x82\xb7dN\x1dOq(D\x1e\xef\xfe3\x9d\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\x99\u0475\x85\x96_@jB\xa4\x9a\x1c\xa7\x0fv\x9evZ?\x98\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\x99\xdf\xd0PL\x06\xc7C\xe4e4\xfd{U\xf1\xf9\xc7\xec3)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\xf4\x14|\xcck\u02c0\u0304.i\xf6\xd0\x0e0\xfaA3\u0649\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\x99\xf7\u007f\x99\x8b \xe0\xbc\xdc\xd9\xfc\x83\x86ARl\xf2Y\x18\xef\x89a\t=|,m8\x00\x00\u07d4\x99\xfa\xd5\x008\xd0\xd9\xd4\xc3\xfb\xb4\xbc\xe0V\x06\xec\xad\xcdQ!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x99\xfe\r \x12(\xa7S\x14VU\xd4(\xeb\x9f\xd9I\x85\xd3m\x89i \xbf\xf3QZ:\x00\x00\u07d4\x9a\a\x9c\x92\xa6)\xca\x15\xc8\xca\xfa.\xb2\x8d[\xc1z\xf8(\x11\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9a\r<\xee=\x98\x92\xea;7\x00\xa2\u007f\xf8A@\xd9\x02T\x93\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x9a$\u038dH\\\xc4\xc8nI\u07b3\x90\"\xf9,t0\xe6~\x89Fy\x1f\xc8N\a\xd0\x00\x00\u07d4\x9a,\xe4;]\x89\u0593k\x8e\x8c5G\x91\xb8\xaf\xff\x96$%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9a9\x01bS^9\x88w\xe4\x16x}b9\xe0uN\x93|\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9a=\xa6P#\xa10 \xd2!E\xcf\xc1\x8b\xab\x10\xbd\x19\xceN\x89\x18\xbfn\xa3FJ:\x00\x00\xe0\x94\x9a>+\x1b\xf3F\xdd\a\v\x02sW\xfe\xacD\xa4\xb2\xc9}\xb8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9aL\xa8\xb8!\x17\x89NC\xdbr\xb9\xfax\xf0\xb9\xb9:\xce\t\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x9aR.R\xc1\x95\xbf\xb7\xcf_\xfa\xae\u06d1\xa3\xbath\x16\x1d\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9aZ\xf3\x1c~\x063\x9a\u0234b\x8d|M\xb0\xce\x0fE\u0224\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\x9ac?\xcd\x11,\xce\xebv_\xe0A\x81ps*\x97\x05\u7708\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x9ac\u0445\xa7\x91)\xfd\xab\x19\xb5\x8b\xb61\xea6\xa4 TN\x89\x02F\xdd\xf9yvh\x00\x00\u07d4\x9ag\b\u0778\x90<(\x9f\x83\xfe\x88\x9c\x1e\xdc\xd6\x1f\x85D#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9ao\xf5\xf6\xa7\xaf{z\xe0\xed\x9c \xec\xecP#\u0481\xb7\x86\x89\x8a\x12\xb9\xbdjg\xec\x00\x00\xe0\x94\x9a\x82\x82m<)H\x1d\xcc+\u0495\x00G\xe8\xb6\x04\x86\xc38\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9a\x8e\xcaA\x89\xffJ\xa8\xff~\u0536\xb7\x03\x9f\t\x02!\x9b\x15\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9a\x95;[\xccp\x93y\xfc\xb5Y\u05f9\x16\xaf\u06a5\f\xad\u0309\x05k\xc7^-c\x10\x00\x00\u07d4\x9a\x99\v\x8a\xebX\x8d~\xe7\xec.\xd8\xc2\xe6Os\x82\xa9\xfe\xe2\x89\x01\xd1'\xdbi\xfd\x8b\x00\x00\u07d4\x9a\x9d\x1d\xc0\xba\xa7}n \xc3\xd8I\u01c8b\xdd\x1c\x05L\x87\x89/\xb4t\t\x8fg\xc0\x00\x00\xe0\x94\x9a\xa4\x8cf\xe4\xfbJ\u0419\x93N2\x02.\x82t'\xf2w\xba\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9a\xa80\x8fB\x91\x0eZ\xde\t\xc1\xa5\xe2\x82\xd6\xd9\x17\x10\xbd\xbf\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9a\xaa\xfa\x00gd~\u0659\x06kzL\xa5\xb4\xb3\xf3\xfe\xaao\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9a\xb9\x88\xb5\x05\xcf\xee\x1d\xbe\x9c\u044e\x9bTs\xb9\xa2\xd4\xf56\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\x9a\xb9\x8dm\xbb\x1e\xaa\xe1mE\xa0EhT\x1a\xd3\xd8\xfe\x06\u0309\x0e\xc5\x04d\xfe#\xf3\x80\x00\xe0\x94\x9a\xba+^'\xffx\xba\xaa\xb5\xcd\u0248\xb7\xbe\x85\\\xeb\xbd\u038a\x02\x1e\f\x00\x13\a\n\xdc\x00\x00\u07d4\x9a\xc4\xdaQ\xd2x\"\xd1\xe2\b\xc9n\xa6J\x1e[U)\x97#\x89\x05lUy\xf7\"\x14\x00\x00\u0794\x9a\xc8S\x97y*i\u05cf(k\x86C*\a\xae\u03b6\x0ed\x88\xc6s\xce<@\x16\x00\x00\xe0\x94\x9a\xc9\a\xee\x85\xe6\xf3\xe2#E\x99\x92\xe2V\xa4?\xa0\x8f\xa8\xb2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9a\xd4\u007f\xdc\xf9\u0354-(\xef\xfd[\x84\x11[1\xa6X\xa1>\x89\xb2Y\xec\x00\xd5;(\x00\x00\u07d4\x9a\xdb\u04fc{\n\xfc\x05\xd1\xd2\xed\xa4\x9f\xf8c\x93\x9cH\xdbF\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x9a\xdfE\x8b\xff5\x99\xee\xe1\xa2c\x98\x85\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\x9a\xf9\xdb\xe4t\"\xd1w\xf9E\xbd\xea\xd7\xe6\xd8)05b0\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x9a\xfaSkLf\xbc8\xd8u\u0133\x00\x99\xd9&\x1f\xdb8\xeb\x89\v*\x8f\x84*w\xbc\x80\x00\u07d4\x9b\x06\xad\x84\x1d\xff\xbeL\xcfF\xf1\x03\x9f\u00c6\xf3\xc3!Dn\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\x11h\u078a\xb6KGU/3\x89\x80\n\x9c\xc0\x8bFf\u03c9]\u0212\xaa\x111\xc8\x00\x00\u07d4\x9b\x18\x11\xc3\x05\x1fF\xe6d\xaeK\xc9\xc8$\u0445\x92\xc4WJ\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\x9b\x18G\x86U\xa4\x85\x1c\xc9\x06\xe6`\xfe\xaca\xf7\xf4\u023f\xfc\x89\xe2G\x8d8\x90}\x84\x00\x00\u07d4\x9b\"\xa8\r\\{3t\xa0[D`\x81\xf9}\n4\a\x9e\u007f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x9b+\xe7\xf5gT\xf5\x05\xe3D\x1a\x10\xf7\xf0\xe2\x0f\xd3\xdd\xf8I\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\x9b2\xcfOQ\x15\xf4\xb3J\x00\xa6La}\xe0c\x875C#\x89\x05\xb8\x1e\u0608 |\x80\x00\u07d4\x9bC\u0739_\xde1\x80u\xa5g\xf1\xe6\xb5v\x17\x05^\xf9\xe8\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\x9bDO\xd37\xe5\xd7R\x93\xad\xcf\xffp\xe1\xea\x01\xdb\x022\"\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9bH$\xff\x9f\xb2\xab\xdaUM\xeeO\xb8\xcfT\x91eW\x061\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9bL'\x15x\f\xa4\xe9\x9e`\xeb\xf2\x19\xf1Y\f\x8c\xadP\n\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\x9bY\xeb!;\x1eue\xe4PG\xe0N\xa07O\x10v-\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\\9\xf7\xe0\xac\x16\x8c\x8e\xd0\xed4\x04w\x11}\x1bh.\xe9\x89\x05P\x05\xf0\xc6\x14H\x00\x00\u07d4\x9b^\xc1\x8e\x83\x13\x88}\xf4a\u0490.\x81\xe6z\x8f\x11;\xb1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9bd\xd3\u034d+s\xf6hA\xb5\xc4k\xb6\x95\xb8\x8a\x9a\xb7]\x89\x01 :Ov\f\x16\x80\x00\u07d4\x9be\x8f\xb3a\xe0F\xd4\xfc\xaa\x8a\xefm\x02\xa9\x91\x11\"6%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9bfA\xb1>\x17/\xc0r\xcaK\x83'\xa3\xbc(\xa1[f\xa9\x89\x06\x81U\xa46v\xe0\x00\x00\xe0\x94\x9bh\xf6t\x16\xa6;\xf4E\x1a1\x16L\x92\xf6r\xa6\x87Y\xe9\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\x9bw6i\xe8}v\x01\x8c\t\x0f\x82U\xe5D\t\xb9\u0728\xb2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9bw\xeb\xce\xd7\xe2\x15\xf0\x92\x0e\x8c+\x87\x00$\xf6\xec\xb2\xff1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9b|\x88\x10\xcc|\u021e\x80Nm>8\x12\x18PG(w\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9b\xa5=\xc8\xc9^\x9aG/\xeb\xa2\xc4\xe3,\x1d\xc4\xdd{\xabF\x89Hz\x9a0E9D\x00\x00\xe0\x94\x9b\xac\xd3\xd4\x0f;\x82\xac\x91\xa2d\xd9\u060d\x90\x8e\xac\x86d\xb9\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9b\xb7`\xd5\u0089\xa3\xe1\xdb\x18\xdb\tSE\xcaA;\x9aC\u0089\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\x9b\xb7b\x04\x18j\xf2\xf6;\xe7\x91h`\x16\x87\xfc\x9b\xadf\x1f\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9b\xb9\xb0*&\xbf\xe1\xcc\xc3\xf0\xc6!\x9e&\x1c9\u007f\xc5\xcax\x89Hz\x9a0E9D\x00\x00\u07d4\x9b\xc5s\xbc\xda#\xb8\xb2o\x90s\xd9\f#\x0e\x8eq\xe0'\v\x896/u\xa40]\f\x00\x00\u07d4\x9b\xd7\u00caB\x100JMe>\xde\xff\x1b<\xe4_\xcexC\x89\x0fI\x89A\xe6d(\x00\x00\xe0\x94\x9b\u0600h\xe10u\xf3\xa8\xca\xc4d\xa5\xf9I\xd6\xd8\x18\xc0\xf6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9b\xd9\x05\xf1q\x9f\u01ec\xd0\x15\x9dM\xc1\xf8\xdb/!G#8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9b\xdb\u071b\x9741\xd1<\x89\xa3\xf9u~\x9b;bu\xbf\u01c9\x1b\x1a}\u03caD\u04c0\x00\u07d4\x9b\xe3\xc3)\xb6*(\xb8\xb0\x88l\xbd\x8b\x99\xf8\xbc\x93\f\xe3\xe6\x89\x04\t\xe5+H6\x9a\x00\x00\xe0\x94\x9b\xf5\x8e\xfb\xea\a\x84\xeb\x06\x8a\xde\u03e0\xbb!P\x84\xc7:5\x8a\x01:k+VHq\xa0\x00\x00\u07d4\x9b\xf6r\xd9y\xb3fR\xfcR\x82Tzjk\xc2\x12\xaeCh\x89#\x8f\xd4,\\\xf0@\x00\x00\xe0\x94\x9b\xf7\x03\xb4\x1c6$\xe1_@T\x96#\x90\xbc\xba0R\xf0\xfd\x8a\x01H>\x01S<.<\x00\x00\u07d4\x9b\xf7\x1f\u007f\xb57\xacT\xf4\xe5\x14\x94\u007f\xa7\xffg(\xf1m/\x89\x01\u03c4\xa3\n\n\f\x00\x00\u07d4\x9b\xf9\xb3\xb2\xf2<\xf4a\xebY\x1f(4\v\xc7\x19\x93\x1c\x83d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x9b\xfce\x9c\x9c`\x1e\xa4*k!\xb8\xf1p\x84\xec\x87\xd7\x02\x12\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9b\xff\xf5\r\xb3jxUU\xf0vR\xa1S\xb0\xc4+\x1b\x8bv\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\x05\xe9\xd0\xf0u\x8eyS\x03q~1\xda!<\xa1W\u618965\u026d\xc5\u07a0\x00\x00\u07d4\x9c\x1bw\x1f\t\xaf\x88*\xf0d0\x83\xde*\xa7\x9d\xc0\x97\xc4\x0e\x89\x86p\xe9\xece\x98\xc0\x00\x00\u07d4\x9c(\xa2\xc4\b`\x91\xcb]\xa2&\xa6W\xce2H\xe8\xea{o\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\x9c/\xd5@\x89\xaff]\xf5\x97\x1ds\xb8\x04a`9dsu\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c4@\x98\xbaaZ9\x8f\x11\xd0\t\x90[\x17|D\xa7\xb6\x02\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c=\x06\x92\xce\xee\xf8\n\xa4\x96\\\xee\xd2b\xff\xc7\xf0i\xf2\u0709\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9c@\\\xf6\x97\x95a8\x06^\x11\xc5\xf7U\x9eg$[\u0465\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9cE *%\xf6\xad\x00\x11\xf1\x15\xa5\xa7\"\x04\xf2\xf2\x19\x88f\x8a\x01\x0f\xcf:b\xb0\x80\x98\x00\x00\xe0\x94\x9cI\xde\xffG\b_\xc0\x97\x04\u02a2\u0728\u0087\xa9\xa17\u068a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\x9cK\xbc\xd5\xf1dJo\aX$\xdd\xfe\x85\xc5q\u05ab\xf6\x9c\x89a\x94\x04\x9f0\xf7 \x00\x00\u07d4\x9cRj\x14\x06\x83\xed\xf1C\x1c\xfa\xa1(\xa95\xe2\xb6\x14\u060b\x89\x06\x04o7\xe5\x94\\\x00\x00\xe0\x94\x9cT\xe4\xedG\x9a\x85h)\u01bbB\u069f\vi*u\xf7(\x8a\x01\x97\xa8\xf6\xddU\x19\x80\x00\x00\xe0\x94\x9cX\x1a`\xb6\x10(\xd94\x16y)\xb2-p\xb3\x13\xc3O\u040a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\x9c\\\xc1\x11\t,\x12!\x16\xf1\xa8_N\xe3\x14\bt\x1a}/\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\x9ck\u0264k\x03\xaeT\x04\xf0C\xdf\xcf!\x88>A\x10\xcc3\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9cx\x96?\xbc&<\t\xbdr\xe4\xf8\xde\xf7J\x94u\xf7\x05\\\x8a\x02\ub3b1\xa1r\u0738\x00\x00\u07d4\x9cx\xfb\xb4\xdfv\x9c\xe2\xc1V\x92\f\xfe\xdf\xda\x03:\x0e%J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x9c{m\xc5\x19\x0f\xe2\x91)c\xfc\xd5yh>\xc79Q\x16\xb0\x89*\x11)\u0413g \x00\x00\u07d4\x9c\x80\xbc\x18\xe9\xf8\u0516\x8b\x18]\xa8\u01df\xa6\xe1\x1f\xfc>#\x89\r\x02\xabHl\xed\xc0\x00\x00\xe0\x94\x9c\x98\xfd\xf1\xfd\u034b\xa8\xf4\u0170L:\xe8X~\xfd\xf0\xf6\xe6\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\x9c\x99\xa1\u0691\u0552\v\xc1N\f\xb9\x14\xfd\xf6+\x94\u02c3X\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\x9c\x99\xb6&\x06(\x1b\\\xef\xab\xf3aV\xc8\xfeb\x83\x9e\xf5\xf3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x9c\x9a\a\xa8\xe5|1r\xa9\x19\xefdx\x94tI\x0f\r\x9fQ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9c\x9d\xe4G$\xa4\x05M\xa0\xea\xa6\x05\xab\u0300&hw\x8b\xea\x89\n\xd7\xd5\xca?\xa5\xa2\x00\x00\u07d4\x9c\x9f;\x8a\x81\x1b!\xf3\xff?\xe2\x0f\xe9p\x05\x1c\xe6j\x82O\x89>\xc2\u07bc\a\u053e\x00\x00\xe0\x94\x9c\x9f\x89\xa3\x91\x0fj*\xe8\xa9\x10G\xa1z\xb7\x88\xbd\xde\xc1p\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9c\xa0B\x9f\x87O\x8d\xce\xe2\xe9\xc0b\xa9\x02\n\x84*Xz\xb9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\xa4.\u7838\x98\xf6\xa5\xcc`\xb5\xa5\u05f1\xbf\xa3\xc321\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9c\xb2\x8a\xc1\xa2\n\x10o\u007f76\x92\xc5\xceLs\xf172\xa1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9c\xcd\u0732\xcf\u00b2[\br\x9a\n\x98\xd9\xe6\xf0 .\xa2\xc1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9c\xe2\u007f$^\x02\xd1\xc3\x12\xc1\xd5\x00x\x8c\x9d\xefv\x90E;\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9c\xe56;\x13\xe8#\x8a\xa4\xdd\x15\xac\u0432\xe8\xaf\xe0\x872G\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\x9c\xf2\x92\x8b\xee\xf0\x9a@\xf9\xbf\xc9S\xbe\x06\xa2Q\x11a\x82\xfb\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9d\x06\x91\x97\xd1\xdeP\x04Z\x18o^\xc7D\xac@\u8bd1\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\x9d\x0e}\x92\xfb0XS\u05d8&;\xf1^\x97\xc7+\xf9\xd7\xe0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9d\x0f4~\x82k}\u03aa\xd2y\x06\n5\xc0\x06\x1e\xcf3K\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d u\x17B,\xc0\xd6\r\xe7\xc27\tzMO\xce \x94\f\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\x9d%\n\xe4\xf1\x10\xd7\x1c\xaf\u01f0\xad\xb5.\x8d\x9a\xcbfy\xb8\x8a\x02\x15mn\x99r\x13\xc0\x00\x00\xe0\x94\x9d+\xfc6\x10o\x03\x82P\xc0\x18\x01hW\x85\xb1l\x86\xc6\r\x8aPw\xd7]\xf1\xb6u\x80\x00\x00\xe0\x94\x9d0\xcb#{\xc0\x96\xf1p6\xfc\x80\xdd!\xcah\x99,\xa2\u064a\x06n\xe71\x8f\u070f0\x00\x00\u07d4\x9d2\x96.\xa9\x97\x00\xd92(\xe9\xdb\xda\xd2\xcc7\xbb\x99\xf0~\x89\xb4c+\xed\xd4\xde\xd4\x00\x00\u07d4\x9d4\xda\xc2[\xd1X(\xfa\xef\xaa\xf2\x8fq\aS\xb3\x9e\x89\u0709;\x1cV\xfe\xd0-\xf0\x00\x00\u07d4\x9d6\x91e\xfbp\xb8\x1a:v_\x18\x8f\xd6\f\xbe^{\th\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9d@\xe0\x12\xf6\x04%\xa3@\xd8-\x03\xa1\xc7W\xbf\xab\xc7\x06\xfb\x89\t4o:\xdd\u020d\x80\x00\u07d4\x9dAt\xaaj\xf2\x84v\xe2)\xda\xdbF\x18\b\b\xc6u\x05\xc1\x89B\x1a\xfd\xa4.\u0597\x00\x00\u07d4\x9dB\x133\x9a\x01U\x18avL\x87\xa9<\xe8\xf8_\x87\x95\x9a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9dF\f\x1b7\x9d\xdb\x19\xa8\xc8[LgG\x05\r\xdf\x17\xa8u\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\x9dG\xba[L\x85\x05\xad\x8d\xa4)4(\va\xa0\xe1\xe8\xb9q\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9dM2\x11w%n\xbd\x9a\xfb\xda0A5\xd5\x17\xc3\xdcV\x93\x89!d\xb7\xa0J\u0220\x00\x00\u07d4\x9dO\xf9\x89\xb7\xbe\u066b\x10\x9d\x10\xc8\xc7\xe5_\x02\xd7g4\xad\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\x9dQ\x15C\xb3\xd9\xdc`\xd4\u007f\t\u051d\x01\xb6\u0118\xd8 x\x8a\x02a\x97\xb9Qo\u00d4\x00\x00\u07d4\x9dn\u03e0:\xf2\xc6\xe1D\xb7\xc4i*\x86\x95\x1e\x90.\x9e\x1f\x89\xa2\xa5\xaa`\xad$?\x00\x00\u07d4\x9dvU\xe9\xf3\xe5\xba]n\x87\xe4\x12\xae\xbe\x9e\xe0\u0512G\ue24e\t1\x1c\x1d\x80\xfa\x00\x00\u07d4\x9dx1\xe84\xc2\v\x1b\xaaiz\xf1\xd8\xe0\xc6!\u016f\xff\x9a\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4\x9dx\xa9u\xb7\xdb^M\x8e(\x84\\\xfb\xe7\xe3\x14\x01\xbe\r\u0649H\xa40k\xa2\u5e5c\x8ahX\u02f5,\f\xf75\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\x9d\u007f\xdapp\xbf>\xe9\xbb\u0664\x1fU\xca\u0505J\xe6\xc2,\x8a\x02U\u02e3\xc4o\xcf\x12\x00\x00\u07d4\x9d\x81\xae\xa6\x9a\xedj\xd0p\x89\xd6\x14E4\x8c\x17\xf3K\xfc[\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9d\x91\x1f6\x82\xf3/\xe0y.\x9f\xb6\xff<\xfcG\xf5\x89\xfc\xa5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\x91;]3\x9c\x95\xd8wEV%c\xfe\xa9\x8b#\xc6\f\u0109\tA0,\u007fM#\x00\x00\u07d4\x9d\x93\xfa\xb6\xe2(E\xf8\xf4Z\aIo\x11\xdeqS\r\xeb\u01c9lO\xd1\xee$nx\x00\x00\u07d4\x9d\x99\xb1\x89\xbb\u0664\x8f\xc2\xe1n\x8f\u0363;\xb9\x9a1{\xbb\x89=\x16\xe1\vm\x8b\xb2\x00\x00\u07d4\x9d\x9cN\xfe\x9fC9\x89\xe2;\xe9@I!S)\xfaU\xb4\u02c9\r\u3c89\x03\u01b5\x80\x00\u07d4\x9d\x9eW\xfd\xe3\x0ePh\xc0>I\x84\x8e\xdc\xe3C\xb7\x02\x83X\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\x9d\xa30\"@\xaf\x05\x11\xc6\xfd\x18W\xe6\u07779Ow\xabk\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\x9d\xa4\xec@pw\xf4\xb9p{-\x9d.\xde^\xa5(+\xf1\u07c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\xa6\t\xfa:~l\xf2\xcc\x0ep\u036b\xe7\x8d\xc4\xe3\x82\xe1\x1e\x89A\rXj \xa4\xc0\x00\x00\xe0\x94\x9d\xa6\x1c\xcdb\xbf\x86\x06V\xe02]qW\xe2\xf1`\xd9;\xb5\x8a\x01\x0f\f\xa9V\xf8y\x9e\x00\x00\xe0\x94\x9d\xa6\xe0u\x98\x9ct\x19\tL\xc9\xf6\xd2\u44d3\xbb\x19\x96\x88\x8a\x02Y\xbbq\u056d\xf3\xf0\x00\x00\u07d4\x9d\xa8\xe2,\xa1\x0eg\xfe\xa4NR^GQ\xee\xac6\xa3\x11\x94\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\x9d\xb2\xe1\\\xa6\x81\xf4\xc6`H\xf6\xf9\xb7\x94\x1e\u040b\x1f\xf5\x06\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9d\xc1\x0f\xa3\x8f\x9f\xb0h\x10\xe1\x1f`\x17>\xc3\xd2\xfdju\x1e\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\x9d\xd2\x19f$\xa1\xdd\xf1J\x9d7^_\a\x15+\xaf\"\xaf\xa2\x89A\xb0^$c\xa5C\x80\x00\u07d4\x9d\xd4k\x1cm?\x05\u279co\x03~\xed\x9aYZ\xf4\xa9\xaa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9d\xdd5^cN\xe9\x92~K\u007fl\x97\xe7\xbf:/\x1ehz\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\x9d\xe2\n\xe7j\xa0\x82c\xb2\x05\xd5\x14$a\x96\x1e$\b\xd2f\x89\r\xa93\xd8\xd8\xc6p\x00\x00\u07d4\x9d\xe2\v\xc3~\u007fH\xa8\x0f\xfdz\xd8O\xfb\xf1\xa1\xab\xe1s\x8c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9d\xe78m\xde@\x1c\xe4\xc6{q\xb6U?\x8a\xa3N\xa5\xa1}\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\x9d\xeb9\x02z\xf8w\x99+\x89\xf2\xecJ\x1f\x82.\xcd\xf1&\x93\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9d\xef\xe5j\x0f\xf1\xa1\x94}\xba\t#\xf7\xdd%\x8d\x8f\x12\xfaE\x8a\x05\xb1*\ufbe8\x04\x00\x00\x00\u07d4\x9d\xf0W\xcd\x03\xa4\xe2~\x8e\x03/\x85y\x85\xfd\u007f\x01\xad\xc8\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9d\xf3*P\x1c\vx\x1c\x02\x81\x02/B\xa1)?\xfd{\x89*\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4\x9e\x01vZ\xff\b\xbc\"\x05P\xac\xa5\xea.\x1c\xe8\u5c19#\x8965\u026d\xc5\u07a0\x00\x00\u07d4\x9e \xe5\xfd6\x1e\xab\xcfc\x89\x1f[\x87\xb0\x92h\xb8\xeb7\x93\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9e#,\b\xc1M\xc1\xa6\xed\v\x8a;(h\x97{\xa5\xc1}\x10\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e#\xc5\u4dc2\xb0\n_\xad\U0006eb47\xda\xcf[\x03g\xa1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e59\x90q\xa4\xa1\x01\xe9\x19M\xaa?\t\xf0J\v_\x98p\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9e>\xb5\t'\x8f\xe0\xdc\xd8\xe0\xbb\xe7\x8a\x19N\x06\xb6\x809C\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\x9eBrrQk>g\xd4\xfc\xbf\x82\xf5\x93\x90\xd0L\x8e(\xe5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\x9eL\xec5:\xc3\u3043^<\t\x91\xf8\xfa\xa5\xb7\u0428\xe6\x8a\x02\x1e\x18\xb9\xe9\xabE\xe4\x80\x00\u07d4\x9eX\x11\xb4\v\xe1\xe2\xa1\xe1\u048c;\at\xac\xde\n\t`=\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\x9eZ1\x1d\x9fi\x89\x8a|j\x9dc`h\x048\xe6z{/\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\x9e| P\xa2'\xbb\xfd`\x93~&\x8c\xea>h\xfe\xa8\xd1\xfe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\x9e\u007fe\xa9\x0e\x85\b\x86{\xcc\xc9\x14%j\x1e\xa5t\xcf\a\xe3\x89C8t\xf62\xcc`\x00\x00\xe0\x94\x9e\x81D\xe0\x8e\x89dx\x11\xfekr\xd4E\u05a5\xf8\n\xd2D\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9e\x8fd\xdd\xcd\u9e34Q\xba\xfa\xa25\xa9\xbfQ\x1a%\xac\x91\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\x9e\x95\x1fm\xc5\xe3R\xaf\xb8\xd0B\x99\xd2G\x8aE\x12Y\xbfV\x89\x03\xe7A\x98\x81\xa7:\x00\x00\u07d4\x9e\x96\r\xcd\x03\u057a\x99\xcb\x11]\x17\xffL\t$\x8a\xd4\u043e\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9e\xafj2\x8a@v\x02N\xfakg\xb4\x8b!\xee\xdc\xc0\xf0\xb8\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\x9e\xb1\xffqy\x8f(\xd6\xe9\x89\xfa\x1e\xa0X\x8e'\xba\x86\xcb}\x89\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\x9e\xb2\x81\xc3'\x19\xc4\x0f\xdb>!m\xb0\xf3\u007f\xbcs\xa0&\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\x9e\xb3\xa7\xcb^g&Bz:6\x1c\xfa\x8dad\xdb\u043a\x16\x89+\x95\xbd\xcc9\xb6\x10\x00\x00\u07d4\x9e\xb7\x83N\x17\x1dA\xe0i\xa7yG\xfc\xa8v\"\xf0\xbaNH\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\x9e\xc0>\x02\u51f7v\x9d\xefS\x84\x13\xe9\u007f~U\xbeq\u060a\x04+\xf0kx\xed;P\x00\x00\u07d4\x9e\u02eb\xb0\xb2'\x82\xb3uD)\xe1uz\xab\xa0K\x81\x18\x9f\x89,\xa7\xbb\x06\x1f^\x99\x80\x00\u07d4\x9e\xce\x14\x00\x80\t6\xc7\xc6H_\xcd\xd3b`\x17\u041a\xfb\xf6\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4\x9e\xd4\xe6?ReB\xd4O\xdd\xd3MY\xcd%8\x8f\xfdk\u0689\u049b4\xa4cH\x94\x00\x00\u07d4\x9e\xd8\x0e\xda\u007fU\x05M\xb9\xfbR\x82E\x16\x88\xf2k\xb3t\xc1\x89\x10CV\x1a\x88)0\x00\x00\u07d4\x9e\u0710\xf4\xbe!\be!J\xb5\xb3^Z\x8d\xd7t\x15'\x9d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9e\u07acL\x02k\x93\x05M\u0171\xd6a\fo9`\xf2\xads\x89A\rXj \xa4\xc0\x00\x00\u07d4\x9e\xe9?3\x9eg&\xece\xee\xa4O\x8aK\xfe\x10\xda=2\x82\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\x9e\xe9v\f\xc2s\xd4pj\xa0\x83u\xc3\xe4o\xa20\xaf\xf3\u054a\x01\xe5.3l\xde\"\x18\x00\x00\u07d4\x9e\xeb\a\xbd+x\x90\x19^}F\xbd\xf2\a\x1bf\x17QM\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\x9e\xefD-)\x1aD}t\xc5\xd2S\u011e\xf3$\xea\xc1\xd8\xf0\x89\xb9f\b\xc8\x10;\xf0\x00\x00\u07d4\x9e\xf1\x89k\x00|2\xa1Q\x14\xfb\x89\xd7=\xbdG\xf9\x12+i\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9f\x01w\x06\xb80\xfb\x9c0\ufc20\x9fPk\x91WEu4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x10\xf2\xa0F;e\xae0\xb0p\xb3\xdf\x18\xcfF\xf5\x1e\x89\xbd\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\x9f\x19\xfa\u0223$7\xd8\n\u0183z\v\xb7\x84\x17)\xf4\x97.\x89#=\xf3)\x9far\x00\x00\u07d4\x9f\x1a\xa8\xfc\xfc\x89\xa1\xa52\x8c\xbdcD\xb7\x1f'\x8a,\xa4\xa0\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\x9f!0,\xa5\tk\xeat\x02\xb9\x1b\x0f\xd5\x06%O\x99\x9a=\x89C\x97E\x1a\x00=\xd8\x00\x00\u07d4\x9f'\x1d(U\x00\xd78F\xb1\x8fs>%\u074bO]J\x8b\x89'#\xc3F\xae\x18\b\x00\x00\u07d4\x9f4\x97\xf5\xef_\xe60\x95\x83l\x00N\xb9\xce\x02\xe9\x01;K\x89\"V\x86\x1b\xf9\xcf\b\x00\x00\xe0\x94\x9f:t\xfd^~\xdc\xc1\x16)\x93\x17\x13\x81\u02f62\xb7\xcf\xf0\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\x9fF\xe7\xc1\xe9\a\x8c\xae\x860Z\xc7\x06\v\x01F}f\x85\xee\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\x9fIl\xb2\x06\x95c\x14M\b\x11g{\xa0\xe4q:\nAC\x89<\xd2\xe0\xbfc\xa4H\x00\x00\u07d4\x9fJq\x95\xac|\x15\x1c\xa2X\xca\xfd\xa0\u02b0\x83\xe0I\xc6\x02\x89SS\x8c2\x18\\\xee\x00\x00\u07d4\x9fJ\xc9\xc9\xe7\xe2L\xb2DJ\x04T\xfa[\x9a\xd9\xd9-8S\x89-C\xf3\xeb\xfa\xfb,\x00\x00\u07d4\x9f_D\x02kWjJ\xdbA\xe9YaV\x1dA\x03\x9c\xa3\x91\x89\r\x8drkqw\xa8\x00\x00\u07d4\x9f`{?\x12F\x9fDa!\u03bf4u5kq\xb42\x8c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\x9fa\xbe\xb4o^\x85=\n\x85!\xc7Dnh\xe3L}\ts\x89\x1e[\x8f\xa8\xfe*\xc0\x00\x00\u07d4\x9fd\xa8\xe8\xda\xcfJ\xde0\xd1\x0fMY\xb0\xa3\u056b\xfd\xbft\x8966\x9e\xd7t}&\x00\x00\u07d4\x9ff.\x95'A!\xf1wVncm#\x96L\xf1\xfdho\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9fj2*mF\x99\x81Bj\xe8D\x86]~\xe0\xbb\x15\u01f3\x89\x02\xb5\xeeW\x92\x9f\u06c0\x00\u07d4\x9fy\x86\x92J\xeb\x02h|\xd6A\x89\x18\x9f\xb1g\xde\xd2\xdd\\\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\x9fz\x03\x92\xf8Ws.0\x04\xa3u\xe6\xb1\x06\x8dI\xd801\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x82E\u00eb}\x171d\x86\x1c\u04d9\x1b\x94\xf1\xba@\xa9:\x89\x9b\ny\x1f\x12\x110\x00\x00\u07d4\x9f\x83\xa2\x93\xc3$\xd4\x10l\x18\xfa\xa8\x88\x8fd\u0499\x05L\xa0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\x9f\x86\xa0f\xed\xb6\x1f\xcbXV\u0793\xb7\\\x8cy\x18d\xb9{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\x9f\x98\xeb4\xd4iy\xb0\xa6\u078b\x05\xaaS:\x89\xb8%\xdc\xf1\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\xe0\x94\x9f\x9f\xe0\xc9_\x10\xfe\xe8z\xf1\xaf r6\xc8\xf3aN\xf0/\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\x9f\xae\xa1\xc5\xe8\x1ez\xcb?\x17\xf1\xc3Q\xee.\u0649\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa0\b\x01\x98c\xc1\xa7|\x14\x99\xeb9\xbb\u05ff-\u05e3\x1c\xb9\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xa0\t\xbf\ao\x1b\xa3\xfaW\u04a7!r\x18\xbe\xd5VZzz\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa0\x1e\x94v\u07c4C\x18%\xc86\xe8\x80:\x97\xe2/\xa5\xa0\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\xa0\x1f\x12\xd7\x0fD\xaa{\x11;(\\\"\xdc\xdbE\x874T\xa7\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xa0\x1f\u0450j\x90\x85\x06\xde\xda\xe1\xe2\b\x12\x88r\xb5n\u7489\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa0\"\x82@\xf9\x9e\x1d\xe9\xcb2\xd8,\x0f/\xa9\xa3\xd4K\v\xf3\x89V\xbcu\xe2\xd61\x00\x00\x00\xe0\x94\xa0+\xdedahn\x19\xace\f\x97\r\x06r\xe7m\xcbO\u008a\x01\xe0\x92\x96\xc37\x8d\xe4\x00\x00\u07d4\xa0,\x1e4\x06O\x04u\xf7\xfa\x83\x1c\xcb%\x01L:\xa3\x1c\xa2\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xa0-\u01aa2\x8b\x88\r\u97acTh#\xfc\xcfw@G\xfb\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa0.?\x8fYY\xa7\xaa\xb7A\x86\x12\x12\x9bp\x1c\xa1\xb8\x00\x10\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa04\u007f\n\x98wc\x90\x16\\\x16m2\x96;\xf7M\xcd\n/\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa05\xa3e$x\xf8-\xbdm\x11_\xaa\x8c\xa9F\xec\x9eh\x1d\x89\x05\xf4\xe4-\u052f\xec\x00\x00\u07d4\xa0:=\xc7\xc53\xd1tB\x95\xbe\x95]a\xaf?R\xb5\x1a\xf5\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa0E\x9e\xf3i:\xac\xd1d|\xd5\u0612\x989 L\xefS\xbe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa0O*\xe0*\xdd\x14\xc1/\xafe\xcb%\x90\"\u0403\n\x8e&\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xa0l\xd1\xf3\x969l\ndFFQ\xd7\xc2\x05\xef\xaf8|\xa3\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xa0ri\x1c\x8d\xd7\xcdB7\xffr\xa7\\\x1a\x95\x06\xd0\xce[\x9e\x89\x14\x0e\xc8\x0f\xa7\xee\x88\x00\x00\u07d4\xa0r\u03beb\xa9\xe9\xf6\x1c\xc3\xfb\xf8\x8a\x9e\xfb\xfe>\x9a\x8dp\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa0v\x82\x00\v\x1b\xcf0\x02\xf8\\\x80\xc0\xfa)I\xbd\x1e\x82\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa0z\xa1mt\xae\u8a63(\x8dR\xdb\x15Q\u0553\x882\x97\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa0\x8d![[j\xacHa\xa2\x81\xac~@\vx\xfe\xf0L\xbf\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa0\x95\x19p\xdf\u0403/\xb8;\xda\x12\xc25E\xe7\x90Aul\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa0\x9fM^\xaae\xa2\xf4\xcbu\nI\x924\x01\xda\u5410\xaf\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\xe0\x94\xa0\xa0\xe6R\x04T\x1f\u029b/\xb2\x82\u0355\x13\x8f\xae\x16\xf8\t\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa0\xaa_\x02\x01\xf0M;\xbe\xb8\x98\x13/|\x11g\x94f\xd9\x01\x89\x01\xfb\xedR\x15\xbbL\x00\x00\u07d4\xa0\xaa\xdb\xd9P\x97\"p_m#X\xa5\u01df7\x97\x0f\x00\xf6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa0\xb7q\x95\x1c\xe1\xde\xee6:\xe2\xb7q\xb7>\a\u0135\xe8\x00\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa0\xde\\`\x1eif5\u0198\xb7\xae\x9c\xa4S\x9f\u01f9A\xec\x89\x12\xc3\xcb\xd7\x04\xc9w\x00\x00\u07d4\xa0\xe8\xbaf\x1bH\x15L\xf8C\xd4\u00a5\xc0\xf7\x92\xd5(\xee)\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa0\xfc~S\xc5\xeb\xd2z*\xbd\xacE&\x1f\x84\xab;Q\xae\xfb\x89\xa3\x13\xda\xec\x9b\xc0\xd9\x00\x00\xe0\x94\xa0\xff[L\xf0\x16\x02~\x83#I}D(\xd3\xe5\xa8;\x87\x95\x8a\x01e\x98\xd3\xc8>\xc0B\x00\x00\u07d4\xa1\x06F[\xbd\x19\u1dbc\xe5\r\x1b\x11W\xdcY\tZ60\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa1\x06\xe6\x92>\xddS\u028e\xd6P\x96\x8a\x91\b\xd6\xcc\xfd\x96p\x8a\x02\x02\xfe\x15\x05\xaf\uc240\x00\u07d4\xa1\t\u12f0\xa3\x9c\x9e\xf8/\xa1\x95\x97\xfc^\xd8\xe9\xebmX\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\xa1\x1a\x03\u013b&\xd2\x1e\xffg}]U\\\x80\xb2TS\xeez\x89\x03\xcb'Y\xbcA\x0f\x80\x00\u07d4\xa1\x1e\xff\xabl\xf0\xf5\x97,\xff\xe4\xd5e\x96\xe9\x89h\x14J\x8f\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xa1 M\xad_V\a(\xa3\\\r\x8f\u01d4\x81\x05{\xf7s\x86\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa1&#\xe6)\u07d3\tg\x04\xb1`\x84\xbe,\u061dV-\xa4\x8a\x01\xcc\xc92E\x11\xe4P\x00\x00\xe0\x94\xa1*l-\x98]\xaf\x0eO_ z\xe8Q\xaa\xf7)\xb32\u034a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa13m\xfb\x96\xb6\xbc\xbeK>\xdf2\x05\xbeW#\xc9\x0f\xadR\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa1;\x9d\x82\xa9\x9b<\x9b\xbaZ\xe7.\xf2\x19\x9e\xdc};\xb3l\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xa1<\xfe\x82mm\x18A\u072eD;\xe8\u00c7Q\x816\xb5\xe8\x8a\x1d\xa5jK\b5\xbf\x80\x00\x00\xe0\x94\xa1C.\xd2\u01b7wz\x88\xe8\xd4m8\x8epG\u007f \x8c\xa5\x8a\x01\xb1\xa7\xe4\x13\xa1\x96\xc5\x00\x00\u07d4\xa1D\xf6\xb6\x0fr\xd6J!\xe30\xda\xdbb\u0619\n\xde+\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa1P%\xf5\x95\xac\xdb\xf3\x11\x0fw\u017f$G~eH\xf9\xe8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1X\x14\x8a.\x0f>\x92\xdc,\xe3\x8f\xeb\xc2\x01\a\xe3%<\x96\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1a`\x85\x1d+\x9c4\x9b\x92\xe4o\x82\x9a\xbf\xb2\x10\x945\x95\x89a\t=|,m8\x00\x00\u07d4\xa1f\xf9\x11\xc6D\xac2\x13\u049e\x0e\x1a\xe0\x10\xf7\x94\u056d&\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1m\x9e=c\x98aY\xa8\x00\xb4h7\xf4^\x8b\xb9\x80\xee\v\x89n\x11u\xdaz\xd1 \x00\x00\u07d4\xa1pp\xc2\xe9\u0169@\xa4\xec\x0eIT\xc4\xd7\xd6C\xbe\x8fI\x89lk\x17\x03;6\x1c\x80\x00\u07d4\xa1|\x9eC#\x06\x95\x18\x18\x9dR\a\xa0r\x8d\u02d20j?\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa1\x83`\xe9\x85\xf2\x06.\x8f\x8e\xfe\x02\xad,\xbc\x91\xad\x9aZ\xad\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa1\x91\x14\x05\xcfn\x99\x9e\xd0\x11\xf0\xdd\xcd*O\xf7\u008f%&\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa1\x92i\x80\a\xcc\x11\xaa`=\"\x1d_\xee\xa0v\xbc\xf7\xc3\r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\x92\xf0j\xb0R\xd5\xfd\u007f\x94\xee\xa81\x8e\x82x\x15\xfegz\x89\a\x1f\x8a\x93\xd0\x1eT\x00\x00\u07d4\xa1\x99\x81D\x96\x8a\\p\xa6AUT\xce\xfe\u0082F\x90\u0125\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa1\xa1\xf0\xfam \xb5\nyO\x02\xefR\b\\\x9d\x03j\xa6\u028965\u026d\xc5\u07a0\x00\x00\u07d4\xa1\xae\x8dE@\xd4\xdbo\xdd\xe7\x14oA[C\x1e\xb5\\y\x83\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xa1\xb4|M\x0e\xd6\x01\x88B\xe6\xcf\xc8c\n\u00e3\x14.^k\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xa1\xc4\xf4Z\x82\xe1\xc4x\xd8E\b.\xb1\x88u\xc4\xeae9\xab\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xa1\xdc\xd0\xe5\xb0Z\x97|\x96#\xe5\xae/Y\xb9\xad\xa2\xf3>1\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa1\xe48\n;\x1ft\x96s\xe2p\"\x99\x93\xeeU\xf3Vc\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\xf1\x93\xa0Y/\x1f\xeb\x9f\xdf\xc9\n\xa8\x13xN\xb8\x04q\u0249K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa1\xf2\x85@P\xf8re\x8e\xd8.R\xb0\xad{\xbc\x1c\xb9!\xf6\x89m\x03\x17\xe2\xb3&\xf7\x00\x00\u07d4\xa1\xf5\xb8@\x14\rZ\x9a\xce\xf4\x02\xac<\u00c8jh\xca\xd2H\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa1\xf7e\xc4O\xe4_y\x06w\x94HD\xbeO-B\x16_\xbd\x89\xc7\xe9\xcf\xdev\x8e\xc7\x00\x00\u07d4\xa1\xf7\xdd\xe1\xd78\xd8\xcdg\x9e\xa1\xee\x96[\xee\"K\xe7\xd0M\x89=\x18DP\xe5\xe9<\x00\x00\u07d4\xa1\xf8\u063c\xf9\x0ew\u007f\x19\xb3\xa6Iu\x9a\xd9P'\xab\xdf\u00c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\x02TrB\x80onp\xe7@X\xd6\xe5)-\xef\xc8\xc8\u0509l\x87T\xc8\xf3\f\b\x00\x00\u07d4\xa2\r\a\x1b\x1b\x000cI}y\x90\xe1$\x9d\xab\xf3l5\xf7\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa2\r\x8f\xf6\f\xaa\xe3\x1d\x02\xe0\xb6e\xfaC]v\xf7|\x94B\x89\x1a\x8a\x90\x9d\xfc\xef@\x00\x00\u07d4\xa2\x11\xda\x03\xcc\x0e1\xec\xceS\t\x99\x87\x18QU(\xa0\x90\u07c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\x14B\xab\x054\n\xdeh\xc9\x15\xf3\xc39\x9b\x99U\xf3\xf7\xeb\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xa2\"\"Y\u075c>=\xed\x12p\x84\xf8\b\xe9*\x18\x870,\x89\b\xc83\x9d\xaf\xedH\x00\x00\u07d4\xa2*\xde\r\xdb\\n\xf8\xd0\u034d\xe9M\x82\xb1\x10\x82\xcb.\x91\x897KW\xf3\xce\xf2p\x00\x00\u07d4\xa2L:\xb6!\x81\xe9\xa1[x\xc4b\x1eL|X\x81'\xbe&\x89\b\xcd\xe4:\x83\xd31\x00\x00\u07d4\xa2W\xadYK\u0603(\xa7\xd9\x0f\xc0\xa9\a\u07d5\xee\xca\xe3\x16\x89\x1c7\x86\xff8F\x93\x00\x00\u07d4\xa2[\bd7\xfd!\x92\u0420\xf6On\xd0D\xf3\x8e\xf3\xda2\x89\x12)\x0f\x15\x18\v\xdc\x00\x00\u07d4\xa2v\xb0X\u02d8\u060b\xee\xdbg\xe5CPl\x9a\r\x94p\u0609\x90\xaa\xfcv\xe0/\xbe\x00\x00\u07d4\xa2\x82\xe9i\xca\xc9\xf7\xa0\xe1\xc0\u0350\xf5\xd0\xc48\xacW\r\xa3\x89\"\a\xeb\x89\xfc'8\x00\x00\xe0\x94\xa2\x91\xe9\u01d9\rU-\u046e\x16\u03bc?\xca4,\xba\xf1\u044a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa2\x93\x19\xe8\x10i\xe5\xd6\r\xf0\x0f=\xe5\xad\xee5\x05\xec\xd5\xfb\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa2\x96\x8f\xc1\xc6K\xac\vz\xe0\u058b\xa9I\x87Mm\xb2S\xf4\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xa2\x9d[\xdat\xe0\x03GHr\xbdX\x94\xb8\x853\xffd\u00b5\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa2\x9df\x1acv\xf6m\vt\xe2\xfe\x9d\x8f&\xc0$~\xc8L\x89\xdf3\x04\a\x9c\x13\xd2\x00\x00\u07d4\xa2\xa45\xdeD\xa0\x1b\xd0\ucc9eD\xe4vD\xe4j\f\xdf\xfb\x89\x1b\x1dDZz\xff\xe7\x80\x00\u07d4\xa2\xac\xe4\u0253\xbb\x1eS\x83\xf8\xact\xe1y\x06n\x81O\x05\x91\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa2\xb7\x01\xf9\xf5\xcd\u041eK\xa6+\xae\xba\u3a02W\x10X\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa2\u0145O\xf1Y\x9f\x98\x89,W%\xd2b\xbe\x1d\xa9\x8a\xad\xac\x89\x11\t\xff30\x10\xe7\x80\x00\u07d4\xa2\xc7\xea\xff\xdc,\x9d\x93sE l\x90\x9aR\u07f1LG\x8f\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\u07d4\xa2\u04aabk\t\xd6\xd4\xe4\xb1?\u007f\xfcZ\x88\xbdz\xd3gB\x89\xfb\x80xPuS\x83\x00\x00\u07d4\xa2\u04cd\xe1\xc79\x06\xf6\xa7\xcan\xfe\xb9|\xf6\xf6\x9c\xc4!\xbe\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa2\xdce\xee%kY\xa5\xbdy)wO\x90K5\x8d\U000ed84a\x04\x83\xbc\xe2\x8b\xeb\t\xf8\x00\x00\u07d4\xa2\xe0h:\x80]\xe6\xa0^\xdb/\xfb\xb5\xe9o\x05p\xb67\u00c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa2\u1e2a\x90\x0e\x9c\x13\x9b?\xa1\"5OaV\xd9*\x18\xb1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa2\u2d54\x1e\f\x01\x94K\xfe\x1d_\xb4\xe8\xa3K\x92,\u03f1\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\xe4`\xa9\x89\xcb\x15V_\x9e\u0327\xd1!\xa1\x8eN\xb4\x05\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa2\xec\xce,I\xf7*\t\x95\xa0\xbd\xa5z\xac\xf1\xe9\xf0\x01\xe2*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xa2\xf4r\xfeO\"\xb7}\xb4\x89!\x9e\xa4\x02=\x11X*\x93)\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xa2\xf7\x98\xe0w\xb0}\x86\x12N\x14\a\xdf2\x89\r\xbbKcy\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa2\xf8k\xc0a\x88N\x9e\xef\x05d\x0e\xddQ\xa2\xf7\xc0Yli\x89llD\xfeG\xec\x05\x00\x00\u07d4\xa2\xfa\x17\xc0\xfbPl\xe4\x94\x00\x8b\x95W\x84\x1c?d\x1b\x8c\xae\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa3\x04X\x8f\r\x85\f\xd8\u04cfv\xe9\xe8<\x1b\xf6>3>\u0789\x02(V\x01!l\x8c\x00\x00\u07d4\xa3\x05\x8cQszN\x96\xc5_.\xf6\xbd{\xb3X\x16~\u00a7\x89 \xdb:\xe4H\x1a\u0500\x00\u07d4\xa3\t\xdfT\u02bc\xe7\f\x95\xec03\x14\x9c\xd6g\x8ao\xd4\u03c9\f\x1f\x12\xc7Q\x01X\x00\x00\u07d4\xa3\nER\x0eR\x06\xd9\x00@p\xe6\xaf>{\xb2\xe8\xddS\x13\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa3\x0e\n\xcbSL\x9b0\x84\xe8P\x1d\xa0\x90\xb4\xeb\x16\xa2\xc0\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3 0\x95\xed\xb7\x02\x8ehq\xce\n\x84\xf5HE\x9f\x830\n\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa3!\t\x1d0\x18\x06By\xdb9\x9d+*\x88\xa6\xf4@\xae$\x89\xadx\xeb\u016cb\x00\x00\x00\u07d4\xa3#-\x06\x8dP\x06I\x03\xc9\xeb\xc5c\xb5\x15\xac\u0237\xb0\x97\x89l\x87T\xc8\xf3\f\b\x00\x00\xe0\x94\xa3$\x1d\x89\n\x92\xba\xf5)\b\xdcJ\xa0Irk\xe4&\xeb\u04ca\x04<-\xa6a\xca/T\x00\x00\u07d4\xa3)F&\xec)\x84\xc4;C\xdaM]\x8eFi\xb1\x1dKY\x896\xa4\xcfcc\x19\xc0\x00\x00\u07d4\xa3,\xf7\xdd\xe2\f=\xd5g\x9f\xf5\xe3%\x84\\p\u0156&b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa39\xa3\xd8\xca(\x0e'\xd2A[&\xd1\xfcy2(\xb6`C\x896\U00086577\x8f\xf0\x00\x00\u07d4\xa3<\xb4P\xf9[\xb4n%\xaf\xb5\x0f\xe0_\xee\xe6\xfb\x8c\xc8\xea\x89*\x11)\u0413g \x00\x00\u07d4\xa3?p\xdaru\xef\x05q\x04\u07e7\xdbd\xf4r\xe9\xf5\xd5S\x89\x04YF\xb0\xf9\xe9\xd6\x00\x00\u07d4\xa3@v\xf8K\xd9\x17\xf2\x0f\x83B\u024b\xa7\x9eo\xb0\x8e\xcd1\x89\u3bb5sr@\xa0\x00\x00\u07d4\xa3C\x0e\x1fd\u007f2\x1e\xd3G9V##\xc7\xd6#A\vV\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xa3O\x9dV\x8b\xf7\xaf\xd9L*[\x8a_\xf5\\f\xc4\by\x99\x89\x84}P;\"\x0e\xb0\x00\x00\u07d4\xa3V\x06\xd5\x12 \xee\u007f!F\xd4\x11X.\xe4\xeeJEYn\x89\u062a\xbe\b\v\xc9@\x00\x00\u07d4\xa3VU\x1b\xb7}OE\xa6\xd7\xe0\x9f\n\b\x9ey\u0322I\u02c9\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xa3\\\x19\x13,\xac\x195Wj\xbf\xedl\x04\x95\xfb\a\x88\x1b\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3e\x91\x8b\xfe?&'\xb9\xf3\xa8gu\xd8un\x0f\u0629K\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa3n\r\x94\xb9Sd\xa8&q\xb6\b\xcb-72Ea)\t\x89\b!\xd2!\xb5)\x1f\x80\x00\u07d4\xa3u\xb4\xbc$\xa2N\x1fyu\x93\xcc0+/3\x10c\xfa\\\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa3v\"\xac\x9b\xbd\xc4\xd8+u\x01]t[\x9f\x8d\xe6Z(\uc25d\xc0\\\xce(\u00b8\x00\x00\xe0\x94\xa3y\xa5\a\fP=/\xac\x89\xb8\xb3\xaf\xa0\x80\xfdE\xedK\xec\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xa3\x80-\x8ae\x9e\x89\xa2\xc4~\x90T0\xb2\xa8'\x97\x89P\xa7\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa3\x83\x06\xcbp\xba\xa8\u4446\xbdh\xaap\xa8=$/)\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\x84vi\x1d4\x94.\xeak/v\x88\x92#\x04}\xb4az\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\x87\xceN\x96\x1axG\xf5`\a\\d\xe1YkVA\xd2\x1c\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xa3\x87\xec\xde\x0e\xe4\xc8\a\x94\x99\xfd\x8e\x03G;\u060a\xd7R*\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa3\x88:$\xf7\xf1f _\x1aj\x99I\al&\xa7nqx\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xa3\x8b[\xd8\x1a\x9d\xb9\u04b2\x1d^\xc7\xc6\x05R\xcd\x02\xedV\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa3\x90\xca\x12+\x85\x01\xee>^\a\xa8\xcaKA\x9f~M\xae\x15\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xa3\x93*1\xd6\xffu\xfb;\x12q\xac\xe7\u02a7\xd5\xe1\xff\x10Q\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xa3\x94\xadO\xd9\xe6S\x0eo\\S\xfa\xec\xbe\u0781\xcb\x17-\xa1\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xa3\x97\x9a\x92v\n\x13Z\xdfi\xd7/u\xe1gu_\x1c\xb8\u00c9\x05k\xc7^-c\x10\x00\x00\xe0\x94\xa3\x9b\xfe\xe4\xae\u027du\xbd\"\u01b6r\x89\x8c\xa9\xa1\xe9]2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa3\xa2b\xaf\u0493h\x19#\b\x92\xfd\xe8O-ZYJ\xb2\x83\x89e\xea=\xb7UF`\x00\x00\u07d4\xa3\xa2\xe3\x19\xe7\u04e1D\x8bZ\xa2F\x89S\x16\f-\xbc\xbaq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\xa5{\a\x16\x13(\x04\xd6\n\xac(\x11\x97\xff+=#{\x01\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xa3\xa9>\xf9\xdb\xea&6&=\x06\xd8I/jA\u0790|\"\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\xa3\xae\x18y\x00}\x80\x1c\xb5\xf3RqjM\u063a'!\xde=\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xa3\xba\r:6\x17\xb1\xe3\x1bNB,\xe2i\xe8s\x82\x8d]i\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\xa3\xbc\x97\x9bp\x80\t/\xa1\xf9/n\x0f\xb3G\u2359PE\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xa3\xbf\xf1\u07e9\x97\x16h6\f\r\x82\x82\x842\xe2{\xf5Ng\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa3\xc1J\xce(\xb1\x92\u02f0b\x14_\u02fdXi\xc6rq\xf6\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa3\xc3:\xfc\x8c\xb4pN#\x15=\xe2\x04\x9d5\xaeq3$r\x89+X\xad\u06c9\xa2X\x00\x00\u07d4\xa3\u0430<\xff\xbb&\x9fyj\u009d\x80\xbf\xb0}\xc7\u01ad\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa3\u0543\xa7\xb6[#\xf6\vy\x05\xf3\xe4\xaab\xaa\xc8\u007fB'\x898\xbe\xfa\x12mZ\x9f\x80\x00\u07d4\xa3\xdb6J3-\x88K\xa9;&\x17\xaeM\x85\xa1H\x9b\xeaG\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xa3\xe0Q\xfbtJ\xa3A\f;\x88\xf8\x99\xf5\xd5\u007f\x16\x8d\xf1-\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\xa3\xe3\xa6\xeaP\x95s\xe2\x1b\xd0#\x9e\xce\x05#\xa7\xb7\u061b/\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xa3\xf4\xad\x14\xe0\xbbD\xe2\xce,\x145\x9cu\xb8\xe72\xd3pT\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa3\xfa\xccP\x19\\\vI3\xc8X\x97\xfe\xcc[\xbd\x99\\4\xb8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\x03Z\xb1\xe5\x18\b!\xf0\xf3\x80\xf1\x13\x1bs\x87\xc8\u0641\u0349\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\n\xa2\xbb\xce\fr\xb4\xd0\xdf\xff\xccBq[+T\xb0\x1b\xfa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4\x19\xa9\x84\x14#c&uuV`\x894\x0e\xea\x0e\xa2\b\x19\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xa4!\u06f8\x9b:\aA\x90\x84\xad\x10\xc3\xc1]\xfe\x9b2\xd0\u008a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa4\"\xe4\xbf\v\xf7AG\u0309[\xed\x8f\x16\xd3\xce\xf3BaT\x89\x12\xef?b\xee\x116\x80\x00\u07d4\xa4%\x9f\x83E\xf7\u3a37+\x0f\xec,\xf7^2\x1f\xdaM\u0089g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xa4)\b\xe7\xfeS\x98\n\x9a\xbf@D\xe9W\xa5Kp\u973e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4)\xfa\x88s\x1f\xdd5\x0e\x8e\xcdn\xa5B\x96\xb6HO\u6549j\xc5\xc6-\x94\x86\a\x00\x00\xe0\x94\xa40\x99]\xdb\x18[\x98e\xdb\xe6%9\xad\x90\xd2.Ks\u008a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa46\xc7TS\xcc\xcaJ\x1f\x1bb\xe5\u0123\r\x86\xdd\xe4\xbeh\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa47\xfen\xc1\x03\u028d\x15\x8fc\xb34\"N\u032c[>\xa3\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4;m\xa6\xcbz\xacW\x1d\xff'\xf0\x9d9\xf8F\xf57i\xb1\x89\x14\x99\x8f2\xacxp\x00\x00\u07d4\xa4;\x81\xf9\x93V\xc0\xaf\x14\x1a\x03\x01\rw\xbd\x04,q\xc1\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4>\x19G\xa9$+5Ua\xc3\n\x82\x9d\xfe\uc881Z\xf8\x89\xd2=\x99\x96\x9f\u0591\x80\x00\u07d4\xa4H\x9aP\xea\xd5\xd5DZ{\xeeM-U6\u00a7lA\xf8\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa4O\xe8\x00\xd9o\xca\xd7;qp\xd0\xf6\x10\u02cc\x06\x82\xd6\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa4T2\xa6\xf2\xac\x9dVW{\x93\x8a7\xfa\xba\xc8\xcc|F\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4f\xd7p\u0618\xd8\xc9\xd4\x05\xe4\xa0\xe5Q\xef\xaf\xcd\xe5<\xf9\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\xe0\x94\xa4g\a1\x17X\x93\xbb\xcf\xf4\xfa\x85\u0397\xd9O\xc5\x1cK\xa8\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4kC\x87\xfbM\xcc\xe0\x11\xe7nMsT}D\x81\xe0\x9b\xe5\x89Hz\x9a0E9D\x00\x00\u07d4\xa4l\xd27\xb6>\xeaC\x8c\x8e;e\x85\xf6y\xe4\x86\b2\xac\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4wy\u063c\x1c{\xce\x0f\x01\x1c\xcb9\xefh\xb8T\xf8\u078f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa4\x82kl8\x82\xfa\xd0\xed\\\x8f\xbb%\xcc@\xccO3u\x9f\x89p\x1bC\xe3D3\xd0\x00\x00\u07d4\xa4\x87Y(E\x8e\xc2\x00]\xbbW\x8c\\\xd35\x80\xf0\xcf\x14R\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa4\x9fR:\xa5\x13d\xcb\xc7\u0655\x16=4\xebY\r\xed/\b\x89\x90'B\x1b*\x9f\xbc\x00\x00\u07d4\xa4\xa4\x9f\v\xc8h\x8c\xc9\xe6\xdc\x04\xe1\xe0\x8dR\x10&\xe6Ut\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa4\xa7\xd3\x06\xf5\x10\xcdX5\x94(\xc0\xd2\xf7\xc3`\x9dVt\u05c9\xb5\x8c\xb6\x1c<\xcf4\x00\x00\u07d4\xa4\xa8:\a8y\x9b\x97\x1b\xf2\xdep\x8c.\xbf\x91\x1c\xa7\x9e\xb2\x89 \x86\xac5\x10R`\x00\x00\u07d4\xa4\xb0\x9d\xe6\xe7\x13\xdciTnv\xef\n\xcf@\xb9O\x02A\xe6\x89\x11}\xc0b~\xc8p\x00\x00\xe0\x94\xa4\u04b4)\xf1\xadSI\xe3\x17\x04\x96\x9e\xdc_%\xee\x8a\xca\x10\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xa4\xd6\xc8.\u076eYG\xfb\xe9\xcd\xfb\xd5H\xae3\xd9\x1aq\x91\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa4\xda4E\r\"\xec\x0f\xfc\xed\xe0\x00K\x02\xf7\x87.\xe0\xb7:\x89\x05\x0fafs\xf0\x83\x00\x00\xe0\x94\xa4\xddY\xab^Q}9\x8eI\xfaS\u007f\x89\x9f\xedL\x15\xe9]\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa4\xe6#E\x1e~\x94\xe7\u86e5\xed\x95\u0228:b\xff\xc4\xea\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa4\xed\x11\xb0r\u061f\xb16u\x9f\u019bB\x8cH\xaa]L\xed\x89\x0e?\x15'\xa0<\xa8\x00\x00\u07d4\xa4\xfb\x14@\x9ag\xb4V\x88\xa8Y>\\\xc2\xcfYl\xedo\x11\x89a\t=|,m8\x00\x00\xe0\x94\xa5\x14\xd0\x0e\xddq\b\xa6\xbe\x83\x9ac\x8d\xb2AT\x18\x17A\x96\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\xe0\x94\xa5\"\xde~\xb6\xae\x12PR*Q13\xa9;\xd4(IG\\\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa5$\xa8\xcc\xccIQ\x8d\x17\n2\x82p\xa2\xf8\x813\xfb\xaf]\x89\x0f\xf7\x02-\xac\x10\x8a\x00\x00\u07d4\xa59\xb4\xa4\x01\xb5\x84\xdf\xe0\xf3D\xb1\xb4\"\xc6UC\x16~.\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa5>\xadT\xf7\x85\n\xf2\x148\xcb\xe0z\xf6\x86'\x9a1[\x86\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa5C\xa0f\xfb2\xa8f\x8a\xa0sj\f\x9c\xd4\rx\t\x87'\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa5gw\vj\xe3 \xbd\xdeP\xf9\x04\xd6c\xe7F\xa6\x1d\xac\xe6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5h\xdbMW\xe4\xd6tb\xd73\u019a\x9e\x0f\xe2n!\x83'\x89;k\xff\x92f\xc0\xae\x00\x00\u07d4\xa5i\x8059\x1eg\xa4\x90\x13\xc0\x00 yY1\x14\xfe\xb3S\x89\r\x02\xabHl\xed\xc0\x00\x00\u07d4\xa5p\":\xe3\u02a8QA\x8a\x98C\xa1\xacU\xdbH$\xf4\xfd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa5s`\xf0\x02\xe0\xd6M-tE}\x8c\xa4\x85~\xe0\v\xcd\u07c9\x123\xe22\xf6\x18\xaa\x00\x00\u07d4\xa5u\xf2\x89\x1d\xcf\u0368<\\\xf0\x14t\xaf\x11\xee\x01\xb7-\u0089\x05l\xd5_\xc6M\xfe\x00\x00\u07d4\xa5x;\xf342\xff\x82\xacI\x89\x85\xd7\xd4`\xaeg\xec6s\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xa5\x87MuF5\xa7b\xb3\x81\xa5\xc4\u01d2H:\xf8\xf2=\x1d\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\xa5\xa4\"\u007fl\xf9\x88%\xc0\u057a\xffS\x15u,\xcc\x1a\x13\x91\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa5\xabK\xd3X\x8fF\xcb'.V\xe9=\xee\u04c6\xba\x8bu=\x89HB\xf0A\x05\x87,\x80\x00\xe0\x94\xa5\xba\xd8e\t\xfb\xe0\xe0\xe3\xc0\xe9?m8\x1f\x1a\xf6\xe9\u0501\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa5\xc36\b;\x04\xf9G\x1b\x8cn\xd76y\xb7Mf\xc3c\uc263e\nL\x9d \xe2\x00\x00\u07d4\xa5\xcd\x129\x92\x19K4\xc4x\x13\x140;\x03\xc5IH\xf4\xb9\x89l\xfc\xc3\xd9\x1d\xa5c\x00\x00\u07d4\xa5\u0578\xb6-\x00-\xef\x92A7\x10\xd1;o\xf8\xd4\xfc}\u04c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xa5\xd9ni}F5\x8d\x11\x9a\xf7\x81\x9d\xc7\b\u007fj\xe4\u007f\xef\x8a\x03\x17\xbe\xe8\xaf3\x15\xa7\x80\x00\u07d4\xa5\xde^CO\xdc\xddh\x8f\x1c1\xb6\xfbQ,\xb1\x96rG\x01\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xa5\xe0\xfc<:\xff\xed=\xb6q\tG\xd1\xd6\xfb\x01\u007f>'m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5\xe9;I\xea|P\x9d\xe7\xc4Ml\xfe\xdd\xefY\x10\u07aa\xf2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa5\xe9\xcdKt%]\"\xb7\u0672z\xe8\xddC\xedn\xd0%+\x89)\x8d\xb2\xf5D\x11\u0640\x00\xe0\x94\xa5\xf0\a{5\x1flP\\\xd5\x15\u07e6\xd2\xfa\u007f\\L\u0487\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xa5\xf0u\xfd@\x135W{f\x83\u0081\xe6\xd1\x01C-\xc6\xe0\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xa5\xfe,\xe9\u007f\x0e\x8c8V\xbe\r\xe5\xf4\u0732\xce]8\x9a\x16\x89\x01=\xb0\xb8\xb6\x86>\x00\x00\u07d4\xa5\xffb\"-\x80\xc0\x13\xce\xc1\xa0\xe8\x85\x0e\xd4\xd3T\xda\xc1m\x89\vA\a\\\x16\x8b\x18\x00\x00\u07d4\xa6\t\xc2m\xd3P\xc25\xe4K+\x9c\x1d\xdd\xcc\u0429\xd9\xf87\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa6\f\x12\tuO]\x87\xb1\x81\xdaO\b\x17\xa8\x18Y\xef\x9f\u0609\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\xe0\x94\xa6\x10\x1c\x96\x1e\x8e\x1c\x15y\x8f\xfc\xd0\xe3 \x1dw\x86\xec7:\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xa6\x13Ei\x96@\x8a\xf1\xc2\xe9>\x17w\x88\xabU\x89^+2\x8a\x01Y\x19\xffG|\x88\xb8\x00\x00\u07d4\xa6\x18\x87\x81\x8f\x91J \xe3\x10w)\v\x83qZk-n\xf9\x89e\xea=\xb7UF`\x00\x00\u07d4\xa6\x1aT\xdfxJD\xd7\x1bw\x1b\x871u\t!\x13\x81\xf2\x00\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa6\x1c\u06ed\xf0K\x1eT\u0203\xde`\x05\xfc\xdf\x16\xbe\xb8\xeb/\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa69\xac\xd9k1\xbaS\xb0\u0407c\"\x9e\x1f\x06\xfd\x10^\x9d\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa6BP\x10\x04\xc9\x0e\xa9\xc9\xed\x19\x98\xba\x14\nL\xd6,o_\x89\r\x94\xfb\x8b\x10\xf8\xb1\x80\x00\u07d4\xa6D\xed\x92,\xc27\xa3\xe5\u0117\x9a\x99Tw\xf3nP\xbcb\x89\x1f\xa7=\x84]~\x96\x00\x00\u07d4\xa6F\xa9\\moY\xf1\x04\xc6T\x1dw`uz\xb3\x92\xb0\x8c\x89\u3bb5sr@\xa0\x00\x00\xe0\x94\xa6HL\u0184\xc4\xc9\x1d\xb5>\xb6\x8aM\xa4Zjk\xda0g\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa6N_\xfbpL,\x919\xd7~\xf6\x1d\x8c\u07e3\x1dz\x88\xe9\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\xe0\x94\xa6T&\xcf\xf3x\xed#%5\x13\xb1\x9fIm\xe4_\xa7\u13ca\x01\x86P\x12|\xc3\u0700\x00\x00\u07d4\xa6jIc\xb2\u007f\x1e\xe1\x93+\x17+\xe5\x96N\r:\xe5KQ\x89\t`\xdbwh\x1e\x94\x00\x00\u07d4\xa6\u007f8\x81\x95eB:\xa8_>:\xb6\x1b\xc7c\u02eb\x89\u0749sw\xb0\"\u01be\b\x00\x00\u07d4\xa6\x8c14E\xc2-\x91\x9e\xe4l\xc2\xd0\xcd\xff\x04:uX%\x89\x04\x13t\xfd!\xb0\u0600\x00\u07d4\xa6\x8e\f0\u02e3\xbcZ\x88>T\x03 \xf9\x99\xc7\xcdU\x8e\\\x89a\x9237b\xa5\x8c\x80\x00\u07d4\xa6\x90\xf1\xa4\xb2\n\xb7\xba4b\x86 \u079c\xa0@\xc4<\x19c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa6\x9d|\xd1}HB\xfe\x03\xf6*\x90\xb2\xfb\xf8\xf6\xaf{\xb3\x80\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa6\xa0\x82R\xc8YQw\xcc.`\xfc'Y>#y\xc8\x1f\xb1\x89\x01\x16Q\xac>zu\x80\x00\u07d4\xa6\xa0\xdeB\x1a\xe5Om\x17(\x13\b\xf5dm/9\xf7w]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa6\xb2\xd5s)s`\x10,\a\xa1\x8f\xc2\x1d\xf2\xe7I\x9f\xf4\xeb\x89\xd9o\u0390\u03eb\xcc\x00\x00\xe0\x94\xa6\xc9\x10\xceMIJ\x91\x9c\u036a\xa1\xfc;\x82\xaat\xba\x06\u03ca\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xa6\u3ea3\x8e\x10J\x1e'\xa4\xd8(i\xaf\xb1\xc0\xaen\xff\x8d\x89\x01\x11@\ueb4bq\x00\x00\u07d4\xa6\xee\xbb\xe4d\u04d1\x87\xbf\x80\u029c\x13\xd7 '\xec[\xa8\xbe\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa6\xf6+\x8a=\u007f\x11\"\a\x01\xab\x9f\xff\xfc\xb3'\x95\x9a'\x85\x89\x1bn)\x1f\x18\u06e8\x00\x00\u07d4\xa6\xf93\a\xf8\xbc\xe01\x95\xfe\u0387 C\xe8\xa0?{\xd1\x1a\x89\x9csK\xadQ\x11X\x00\x00\u07d4\xa7\x01\xdfy\xf5\x94\x90\x1a\xfe\x14DH^k \u00fd\xa2\xb9\xb3\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa7\x02L\xfdt,\x1e\xc1<\x01\xfe\xa1\x8d0B\xe6_\x1d]\xee\x8a\x02c\x11\x9a(\xab\u0430\x80\x00\u07d4\xa7\x18\xaa\xadY\xbf9\\\xba+#\xe0\x9b\x02\xfe\f\x89\x81bG\x8960<\x97\xe4hx\x00\x00\u07d4\xa7$|S\xd0Y\xeb|\x93\x10\xf6(\xd7\xfclj\nw?\b\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xa7%7c\xcfJu\u07d2\xca\x1evm\xc4\xee\x8a'E\x14{\x8a\x02F7p\xe9\n\x8fP\x00\x00\u07d4\xa7.\xe6f\u0133^\x82\xa5\x06\x80\x8bD<\xeb\xd5\xc62\xc7\u0749+^:\xf1k\x18\x80\x00\x00\u07d4\xa7DD\xf9\x0f\xbbT\xe5o:\u0276\xcf\u032aH\x19\xe4aJ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa7GC\x9a\xd0\u04d3\xb5\xa08a\xd7r\x962m\u8edd\xb9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xa7`{BW;\xb6\xf6\xb4\xd4\xf2<~*&\xb3\xa0\xf6\xb6\xf0\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\xa7i)\x89\n{G\xfb\x85\x91\x96\x01lo\u0742\x89\u03b7U\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u0794\xa7kt?\x98\x1bi0r\xa11\xb2+\xa5\x10\x96\\/\xef\u05c8\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xa7m?\x15bQ\xb7,\f\xcfKG\xa39<\xbdoI\xa9\u0149Hz\x9a0E9D\x00\x00\u07d4\xa7t(\xbc\xb2\xa0\xdbv\xfc\x8e\xf1\xe2\x0eF\x1a\n2\u016c\x15\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xa7u\x8c\xec\xb6\x0e\x8faL\u0396\x13~\xf7+O\xbd\awJ\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa7w^J\xf6\xa2:\xfa \x1f\xb7\x8b\x91^Q\xa5\x15\xb7\xa7(\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\xa7\u007f>\u1793\x88\xbb\xbb\"\x15\xc6#\x97\xb9e`\x13#`\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa7\x85\x9f\xc0\u007fun\xa7\xdc\xeb\xbc\xcdB\xf0X\x17X-\x97?\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa7\x96lH\x9fLt\x8az\u902a'\xa5t%\x17g\xca\xf9\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xa7\xa3\xbba9\xb0\xad\xa0\f\x1f\u007f\x1f\x9fV\u0654\xbaM\x1f\xa8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa7\xa3\xf1S\xcd\u00c8!\xc2\f]\x8c\x82A\xb2\x94\xa3\xf8+$\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa7\xa5\x17\u05ed5\x82\v\t\u0517\xfa~U@\xcd\xe9IXS\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xa7\xc9\u04c8\xeb\xd8s\xe6k\x17\x13D\x83\x97\xd0\xf3\u007f\x8b\u04e8\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa7\u073b\xa9\xb9\xbfgb\xc1EAlPjq\u3d17 \x9c\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xa7\xe7O\v\xdb'\x8f\xf0\xa8\x05\xa6Ha\x8e\xc5+\x16o\xf1\xbe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa7\xe87r\xbc \x0f\x90\x06\xaa*&\r\xba\xa8H=\xc5+0\x89\vB\xd56f7\xe5\x00\x00\xe0\x94\xa7\xef5\u0387\xed\xa6\u008d\xf2HxX\x15\x05>\xc9zPE\x8a\x01\x0f\f\xe9I\xe0\x0f\x93\x00\x00\u07d4\xa7\xf9\"\f\x80G\x82k\xd5\xd5\x18?Ngjmw\xbf\xed6\x89\bPh\x97k\xe8\x1c\x00\x00\u07d4\xa8\a\x10O'\x03\xd6y\xf8\u07af\xc4B\xbe\xfe\x84\x9eB\x95\v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\f\xb1s\x8b\xac\b\xd4\xf9\xc0\x8bM\xef\xf5\x15T_\xa8XO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa8\x19\xd2\xec\xe1\"\xe0(\xc8\xe8\xa0J\x06M\x02\xb9\x02\x9b\b\xb9\x8965\u026d\xc5\u07a0\x00\x00\u0794\xa8%\xfdZ\xbby&\xa6|\xf3k\xa2F\xa2K\xd2{\xe6\xf6\xed\x88\xf4?\xc2\xc0N\xe0\x00\x00\u07d4\xa8(U9\x86\x9d\x88\xf8\xa9aS7Uq}~\xb6Uv\xae\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xa83\x82\xb6\xe1Rg\x97J\x85P\xb9\x8fqv\xc1\xa3S\xf9\xbe\x89\xbf\xfd\xaf/\xc1\xb1\xa4\x00\x00\xe0\x94\xa8DlG\x81\xa77\xacC(\xb1\xe1[\x8a\v?\xbb\x0f\xd6h\x8a\x04\x87\x94\xd1\xf2F\x19*\x00\x00\u07d4\xa8E[A\x17e\u0590\x1e1\x1erd\x03\t\x1eB\xc5f\x83\x89\xb7:\xec;\xfe\x14P\x00\x00\xe0\x94\xa8f\x13\xe6\u0124\xc9\xc5_\\\x10\xbc\xda2\x17]\u02f4\xaf`\x8a\x02C\xd6\xc2\xe3k\xe6\xae\x00\x00\u07d4\xa8m\xb0}\x9f\x81/G\x96b-@\xe0=\x13Xt\xa8\x8at\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa8\u007fz\xbdo\xa3\x11\x94(\x96x\xef\xb6<\xf5\x84\xee^*a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xa8\x80\u2a3f\x88\xa1\xa8&H\xb4\x01W\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa8\x9d\xf3HY\xed\xd7\xc8 \u06c8w@\xd8\xff\x9e\x15\x15|{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\xa4<\x00\x91\x00al\xb4\xaeN\x03?\x1f\xc5\xd7\xe0\xb6\xf1R\x89\u0548\xd0x\xb4?M\x80\x00\u07d4\xa8\xa7\b\xe8O\x82\u06c6\xa3U\x02\x19;Ln\xe9\xa7n\xbe\x8f\x897\b\xba\xed=h\x90\x00\x00\xe0\x94\xa8\xa7\xb6\x8a\u06b4\xe3\xea\xdf\xf1\x9f\xfaX\xe3J?\xce\xc0\xd9j\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xa8\xa8\xdb\xdd\x1a\x85\u047e\xee%i\xe9\x1c\xccM\t\xae\u007fn\xa1\x8a\x01:k+VHq\xa0\x00\x00\u07d4\xa8\xac\xa7H\xf9\xd3\x12\xect\u007f\x8bex\x14&\x94\xc7\xe9\xf3\x99\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa8\xb6[\xa3\x17\x1a?w\xa65\v\x9d\xaf\x1f\x8dU\xb4\xd2\x01\xeb\x89(b\xf3\xb0\xd2\"\x04\x00\x00\u07d4\xa8\xbe\xb9\x1c+\x99\u0216J\xa9[kJ\x18K\x12i\xfc4\x83\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u0794\xa8\xc0\xb0/\xaf\x02\xcbU\x19\u0768\x84\xde{\xbc\x8c\x88\xa2\u0681\x88\xe7\xc2Q\x85\x05\x06\x00\x00\u07d4\xa8\xc1\u05aaA\xfe=e\xf6{\xd0\x1d\xe2\xa8f\xed\x1e\u066eR\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\xa8\xca\xfa\xc3\"\x80\xd0!\x02\v\xf6\xf2\xa9x(\x83\u05ea\xbe\x12\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xa8\xdb\v\x9b \x14S3A<;\fb\xf5\xf5.\u0544\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa8\xf3\u007f\n\xb3\xa1\xd4H\xa9\xe3\xce@\x96_\x97\xa6F\b:4\x89\x11\xe0\xe4\xf8\xa5\v\xd4\x00\x00\u07d4\xa8\xf8\x9d\xd5\xccnd\u05f1\xee\xac\xe0\a\x02\x02,\xd7\xd2\xf0=\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\xa9\x04v\xe2\xef\xdf\xeeO8{\x0f2\xa5\x06x\xb0\xef\xb5s\xb5\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa9\x14PF\xfa6(\xcf_\xd4\xc6\x13\x92{\xe51\xe6\xdb\x1f\u0749\x06\x12O\xee\x99;\xc0\x00\x00\u07d4\xa9\x14\u0375q\xbf\xd9=d\xdaf\xa4\xe1\b\xea\x13NP\xd0\x00\x89M\x878\x99G\x13y\x80\x00\xe0\x94\xa9\x1aZ{4\x1f\x99\xc55\x14N \xbe\x9ck;\xb4\u008eM\x8a\x01&u:\xa2$\xa7\v\x00\x00\u07d4\xa9%%Q\xa6$\xaeQ7\x19\u06beR\a\xfb\xef\xb2\xfdwI\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xa9'\u050b\xb6\u02c1K\xc6\t\xcb\u02a9\x15\x1f]E\x9a'\xe1\x89\x0e\xb95\t\x00d\x18\x00\x00\u07d4\xa9)\u023dq\xdb\f0\x8d\xac\x06\b\n\x17G\xf2\x1b\x14e\xaa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xa9K\xbb\x82\x14\u03cd\xa0\xc2\xf6h\xa2\xacs\xe8bHR\x8dK\x894\n\xad!\xb3\xb7\x00\x00\x00\u07d4\xa9Q\xb2D\xffP\u03eeY\x1d^\x1a\x14\x8d\xf6\xa98\xef*\x1a\x89^\x00\x15\x84\xdf\xcfX\x00\x00\u07d4\xa9`\xb1\xca\xdd;\\\x1a\x8el\xb3\xab\xca\xf5.\xe7\xc3\xd9\xfa\x88\x89R\x8b\xc3T^Rh\x00\x00\u07d4\xa9a\x17\x1fSB\xb1s\xddp\xe7\xbf\xe5\xb5\xca#\x8b\x13\xbc\u0749\xb8'\x94\xa9$O\f\x80\x00\u07d4\xa9u\xb0w\xfc\xb4\u030e\xfc\xbf\x83\x84Y\xb6\xfa$:AY\u0589\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xa9{\xeb:H\xc4_\x15((L\xb6\xa9_}\xe4S5\x8e\u018a\x06\x90\x83l\n\xf5\xf5`\x00\x00\u07d4\xa9~\a!DI\x9f\xe5\xeb\xbd5J\xcc~~\xfbX\x98]\b\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xa9\x86v/zO)O.\v\x172y\xad,\x81\xa2\"4X\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xa9\x8f\x10\x985\xf5\xea\xcd\x05Cd|4\xa6\xb2i\xe3\x80/\xac\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xa9\x97\xdf\u01d8j'\x05\bH\xfa\x1cd\u05e7\xd6\xe0z\u0322\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\u07d4\xa9\x99\x91\u03bd\x98\xd9\xc88\xc2_zt\x16\xd9\xe2D\xca%\r\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xa9\xa1\xcd\xc3;\xfd7o\x1c\rv\xfbl\x84\xb6\xb4\xac'Mh\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xa9\xa8\xec\xa1\x1a#\xd6F\x89\xa2\xaa>A}\xbb=3k\xb5\x9a\x89\x0e4S\xcd;g\xba\x80\x00\u07d4\xa9\xac\xf6\x00\b\x1b\xb5[\xb6\xbf\xba\xb1\x81_\xfcN\x17\xe8Z\x95\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xa9\xad\x19&\xbcf\xbd\xb31X\x8e\xa8\x197\x88SM\x98,\x98\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xa9\xaf!\xac\xbeH/\x811\x89j\"\x806\xbaQ\xb1\x94S\u00c9\x02\xb5\xe0!\x98\f\xc1\x80\x00\xe0\x94\xa9\xb2\xd2\xe0IN\xab\x18\xe0}7\xbb\xb8V\xd8\x0e\x80\xf8L\u04ca\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xa9\xbaoA;\x82\xfc\xdd\xf3\xaf\xfb\xbd\u0412\x87\xdc\xf5\x04\x15\u0289\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xa9\xbe\x88\xad\x1eQ\x8b\v\xbb\x02J\xb1\xd8\xf0\xe7?y\x0e\fv\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xa9\xbf\xc4\x10\xdd\xdb q\x1eE\xc0s\x87\xea\xb3\n\x05N\x19\xac\x89>\x99`\x1e\xdfNS\x00\x00\u07d4\xa9\u0522\xbc\xbe[\x9e\bi\xd7\x0f\x0f\xe2\xe1\u05aa\xcdE\xed\u0149\n\xc6\xe7z\xb6c\xa8\x00\x00\xe0\x94\xa9\xd6KO;\xb7\x85\a\"\xb5\x8bG\x8b\xa6\x917^\"NB\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xa9\xd6\xf8q\xcax\x1au\x9a \xac:\u06d7,\xf1()\xa2\b\x892$\xf4'#\xd4T\x00\x00\xe0\x94\xa9\xdc\x04$\u0196\x9dy\x83X\xb3\x93\xb1\x93:\x1fQ\xbe\xe0\n\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xa9\xe1\x94f\x1a\xacpN\xe9\u07a0C\x97N\x96\x92\xde\xd8J]\x89\x1a&\xa5\x14\"\xa0p\x00\x00\u07d4\xa9\xe2\x837\xe65q\x93\xd9\xe2\xcb#k\x01\xbeD\xb8\x14'\u07c9wC\"\x17\xe6\x83`\x00\x00\u07d4\xa9\xe6\xe2^ekv%Xa\x9f\x14z!\x98[\x88t\xed\xfe\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xa9\xe9\xdb\xcez,\xb06\x94y\x98\x97\xbe\xd7\xc5M\x15_\u06a8\x89\n\xb5\xae\x8f\u025de\x80\x00\u07d4\xa9\xed7{}n\xc2Yq\xc1\xa5\x97\xa3\xb0\xf3\xbe\xadW\u024f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xaa\x02\x00\xf1\xd1~\x9cT\xda\x06G\xbb\x969]W\xa7\x858\u06099>\xf1\xa5\x12|\x80\x00\x00\u07d4\xaa\f\xa3ss7\x17\x8a\f\xaa\xc3\t\x9cXK\x05lV0\x1c\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xaa\x13kG\x96+\xb8\xb4\xfbT\r\xb4\xcc\xf5\xfd\xd0B\xff\xb8\u03c9\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\xaa\x14B-o\n\xe5\xa7X\x19N\xd1W\x80\xc88\xd6\u007f\x1e\xe1\x8a\x06\t2\x05lD\x9d\xe8\x00\x00\u07d4\xaa\x16&\x9a\xac\x9c\r\x800h\xd8/\u01d1Q\xda\xdd3Kf\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xaa\x16p&\u04da\xb7\xa8V5\x94N\xd9\xed\xb2\xbf\xeb\xa1\x18P\x8a\x01\xc1\xd5\xe2\x1bO\xcfh\x00\x00\u07d4\xaa\x1b7h\xc1m\x82\x1fX\x0ev\xc8\xe4\xc8\xe8m}\u01c8S\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xaa\x1d\xf9.Q\xdf\xf7\v\x19s\xe0\xe9$\xc6b\x87\xb4\x94\xa1x\x89\x1c\xf8J0\xa0\xa0\xc0\x00\x00\u07d4\xaa,g\x00\x96\xd3\xf990S%B~\xb9U\xa8\xa6\r\xb3\u0149l\x95Y\x06\x99#-\x00\x00\u07d4\xaa15\xcbT\xf1\x02\xcb\xef\xe0\x9e\x96\x10:\x1ayg\x18\xffT\x89\x03\"\"\xd9\xc31\x94\x00\x00\u07d4\xaa2\x1f\xdb\xd4I\x18\r\xb8\xdd\xd3O\x0f\xe9\x06\xec\x18\xee\t\x14\x89%\"H\u07b6\xe6\x94\x00\x00\xe0\x94\xaa9%\xdc\"\v\xb4\xae!w\xb2\x880x\xb6\xdc4l\xa1\xb2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xaa?)`\x1a\x131t^\x05\xc4(0\xa1^q\x93\x8ab7\x89\\(=A\x03\x94\x10\x00\x00\xe0\x94\xaaG\xa4\xff\xc9y622\u025b\x99\xfa\xda\x0f'4\xb0\xae\xee\x8a\x01\xb8H\x9d\xf4\xdb\xff\x94\x00\x00\u07d4\xaaI=?O\xb8fI\x1c\xf8\xf8\x00\xef\xb7\xe22N\xd7\xcf\xe5\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xaaV\xa6]\u012b\xb7/\x11\xba\xe3+o\xbb\aDG\x91\xd5\u0249(\x94\xe9u\xbfIl\x00\x00\xe0\x94\xaaZ\xfc\xfd\x83\t\xc2\u07dd\x15\xbe^jPN}pf$\u014a\x01<\xf4\"\xe3\x05\xa17\x80\x00\u07d4\xaa\x8e\xb0\x82;\a\xb0\xe6\xd2\n\xad\xda\x0e\x95\xcf85\xbe\x19.\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xaa\x91#~t\r%\xa9/\u007f\xa1F\xfa\xa1\x8c\xe5m\xc6\xe1\xf3\x892$\xf4'#\xd4T\x00\x00\u07d4\xaa\x96\x0e\x10\xc5#\x91\xc5N\x158|\xc6z\xf8'\xb51m\u0309lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\x9b\xd4X\x955\xdb'\xfa+\xc9\x03\xca\x17\xd6y\xddeH\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\xa8\xde\xfe\x11\xe3a?\x11\x06\u007f\xb9\x83bZ\b\x99Z\x8d\xfc\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaa\xaa\xe6\x8b2\x14\x02\xc8\xeb\xc14h\xf3A\xc6<\f\xf0?\u0389Rf<\u02b1\xe1\xc0\x00\x00\u07d4\xaa\xad\x1b\xaa\xdeZ\xf0N+\x17C\x9e\x93Y\x87\xbf\x8c+\xb4\xb9\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xaa\xb0\n\xbfX(\xd7\xeb\xf2kG\u03ac\u0378\xba\x032Qf\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xaa\xbd\xb3\\\x15\x14\x98J\x03\x92\x13y?3E\xa1h\xe8\x1f\xf1\x89\x10\xca\u0216\xd29\x00\x00\x00\u07d4\xaa\xca`\xd9\xd7\x00\u7156\xbb\xbb\xb1\xf1\xe2\xf7\x0fF'\xf9\u060965\xbbw\xcbK\x86\x00\x00\u07d4\xaa\xce\u0629V;\x1b\xc3\x11\xdb\xdf\xfc\x1a\xe7\xf5u\x19\xc4D\f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\u04b7\xf8\x10f\x95\a\x8el\x13\x8e\xc8\x1at\x86\xaa\xca\x1e\xb2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaa\xe6\x1eC\xcb\r\f\x96\xb3\x06\x99\xf7~\x00\xd7\x11\u0423\x97\x9b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaa\xe72\xed\xa6Y\x88\u00e0\f\u007fG/5\x1cF;\x1c\x96\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaa\xf0#\xfe\U0009091b\xb7\x8b\xb7\xab\xc9]f\x9cP\xd5(\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xaa\xf5\xb2\a\xb8\x8b\r\xe4\xac@\xd7G\xce\xe0n\x17-\xf6\xe7E\x8a\x06\xa7\xb7\x1d\u007fQ\u0410\x00\x00\u07d4\xaa\xf9\xeeK\x88lm\x1e\x95Io\xd2t#[\xf4\xec\xfc\xb0}\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xaa\xfb{\x01:\xa1\xf8T\x1c~2{\xf6P\xad\xbd\x19L \x8f\x89I\x9e\t-\x01\xf4x\x00\x00\xe0\x94\xab\t\x863\xee\xee\f\xce\xfd\xf62\xf9WTV\xf6\u0740\xfc\x86\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xab\f\xedv.\x16a\xfa\xe1\xa9*\xfb\x14\b\x88\x94\x13yH%\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xab\x14\xd2!\xe3=TF)\x19\x8c\u0416\xedc\u07e2\x8d\x9fG\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xab \x9f\u0729y\u0426G\x01\n\xf9\xa8\xb5/\xc7\xd2\r\x8c\u044a\x01\xee\xe2S,|-\x04\x00\x00\u07d4\xab'\xbax\xc8\xe5\xe3\xda\xef1\xad\x05\xae\xf0\xff\x03%r\x1e\b\x89\x19^\xce\x00n\x02\xd0\x00\x00\u07d4\xab(q\xe5\a\u01fe9eI\x8e\x8f\xb4b\x02Z\x1a\x1cBd\x89*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xab8a\"o\xfe\xc1(\x91\x87\xfb\x84\xa0\x8e\xc3\xed\x042d\xe8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab=\x86\xbc\x82\x92~\f\xd4!\xd1F\xe0\u007f\x91\x93'\xcd\xf6\xf9\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xab>b\xe7z\x8b\"^A\x15\x92\xb1\xaf0\aR\xfeA$c\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xab>x)K\xa8\x86\xa0\xcf\xd5\xd3H\u007f\xb3\xa3\a\x8d3\x8dn\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xab@\x04\xc0@?~\xab\xb0\xeaXo!!V\xc4 =g\xf1\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xabAo\xe3\rX\xaf\xe5\xd9EL\u007f\xce\u007f\x83\v\xccu\x03V\x89\x0657\x01\xc6\x05\u06c0\x00\u07d4\xabEr\xfb\xb1\xd7+W]i\xecj\xd1s3\x87>\x85R\xfc\x89lj\xc5L\xdahG\x00\x00\u07d4\xabZy\x01av2\ts\xe8\xcd8\xf67U0\x02%1\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab]\xfc\x1e\xa2\x1a\xdcB\u03cc?n6\x1e$?\xd0\xdaa\xe5\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xabke\xea\xb8\xdf\xc9\x17\xec\x02Q\xb9\xdb\x0e\u03e0\xfa\x03(I\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xabp\x91\x93.K\u00dd\xbbU#\x80\u0293O\xd7\x16m\x1en\x89\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\xabt\x16\xff2%IQ\u02fcbN\xc7\xfbE\xfc~\u02a8r\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xab|B\xc5\xe5-d\x1a\a\xadu\t\x9cb\x92\x8b\u007f\x86b/\x89\x126\x1a\xa2\x1d\x14\xba\x00\x00\u07d4\xab}T\xc7\xc6W\x0e\xfc\xa5\xb4\xb8\xcep\xf5*Ws\xe5\xd5;\x89\x0f(:\xbe\x9d\x9f8\x00\x00\u07d4\xab~\v\x83\xed\x9aBLm\x1ejo\x87\xa4\xdb\xf0d\t\xc7\u0589\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xab\x84\xa0\xf1G\xad&T\x00\x00+\x85\x02\x9aA\xfc\x9c\xe5\u007f\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xab\x93\xb2n\xce\n\n\xa2\x13e\xaf\xed\x1f\xa9\xae\xa3\x1c\xd5Dh\x89W+{\x98sl \x00\x00\u07d4\xab\x94\x8aJ\xe3y\\\xbc\xa11&\xe1\x92S\xbd\xc2\x1d:\x85\x14\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xab\x9a\xd3n\\t\xce.\x969\x9fW\x83\x941\xd0\u77d6\xab\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\xab\xb2\xe6\xa7*@\xban\xd9\b\u037c\xec\x91\xac\xfdwx0\xd1dcG\x8a\xe0\xfcw \x89\a?u\u0460\x85\xba\x00\x00\xe0\x94\xab\u071f\x1b\xcfM\x19\xee\x96Y\x100\xe7r\xc340/}\x83\x8a\b~^\x11\xa8\x1c\xb5\xf8\x00\x00\u07d4\xab\xde\x14{*\xf7\x89\ua946T~f\xc4\xfa&d\xd3(\xa4\x89\rk`\x81\xf3L\x12\x80\x00\xe0\x94\xab\xe0|\xedj\xc5\xdd\xf9\x91\xef\xf6\xc3\xda\"jt\x1b\xd2C\xfe\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xab\xf1/\xa1\x9e\x82\xf7lq\x8f\x01\xbd\xca\x00\x03gE#\xef0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xab\xf7(\u03d3\x12\xf2!(\x02NpF\xc2Q\xf5\xdcY\x01\xed\x8a\x06A\xe8\xa15c\xd8\xf8\x00\x00\u07d4\xab\xf8\xff\xe0p\x8a\x99\xb5(\xcc\x1e\xd4\xe9\xceK\r\x060\xbe\x8c\x89z\xb5\u00ae\xee\xe68\x00\x00\u07d4\xab\xfc\xf5\xf2P\x91\xceW\x87_\xc6t\xdc\xf1\x04\xe2\xa7=\xd2\xf2\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xab\xfe\x93d%\xdc\u01f7K\x95P\x82\xbb\xaa\xf2\xa1\x1dx\xbc\x05\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xac\x02OYO\x95X\xf0ICa\x8e\xb0\xe6\xb2\xeeP\x1d\xc2r\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\x12*\x03\xcd\x05\x8c\x12._\xe1{\x87/Hw\xf9\u07d5r\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xac\x14.\xda\x11W\xb9\xa9\xa6C\x90\xdf~j\xe6\x94\xfa\u0249\x05\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\x1d\xfc\x98Kq\xa1\x99)\xa8\x1d\x81\xf0J|\xbb\x14\a7\x03\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xac!\xc1\xe5\xa3\xd7\xe0\xb5\x06\x81g\x9d\xd6\u01d2\xdb\u0287\xde\u02ca\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xac(\x89\xb5\x96o\f\u007f\x9e\xdbB\x89\\\xb6\x9d\x1c\x04\xf9#\xa2\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xac(\xb5\xed\xea\x05\xb7o\x8c_\x97\bEA'|\x96ijL\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xac,\x8e\t\xd0d\x93\xa68XC{\xd2\v\xe0\x19bE\x03e\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xac.vm\xac?d\x8fcz\xc6q?\u0770h\xe4\xa4\xf0M\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\xac9\x00)\x8d\xd1M|\xc9mJ\xbbB\x8d\xa1\xba\xe2\x13\xff\xed\x8a\x05<\xa1)t\x85\x1c\x01\x00\x00\u07d4\xac=\xa5&\xcf\u0388)s\x02\xf3LI\xcaR\r\xc2q\xf9\xb2\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xacD`\xa7nm\xb2\xb9\xfc\xd1R\xd9\xc7q\x8d\x9a\xc6\xed\x8co\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xacJ\xcf\xc3n\xd6\tJ'\xe1\x18\xec\xc9\x11\xcdG>\x8f\xb9\x1f\x89a\x91>\x14@<\f\x00\x00\u07d4\xacL\xc2V\xaet\xd6$\xac\xe8\r\xb0x\xb2 \u007fW\x19\x8fk\x89lyt\x12?d\xa4\x00\x00\u07d4\xacN\xe9\xd5\x02\xe7\xd2\xd2\xe9\x9eY\xd8\xca}_\x00\xc9KM\u058965\u026d\xc5\u07a0\x00\x00\u07d4\xacR\xb7~\x15fH\x14\xf3\x9eO'\x1b\xe6A0\x8d\x91\xd6\u0309\v\xed\x1d\x02c\xd9\xf0\x00\x00\u07d4\xacY\x99\xa8\x9d-\u0486\u0568\fm\xee~\x86\xaa\xd4\x0f\x9e\x12\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xac_br1H\r\r\x950.m\x89\xfc2\xcb\x1dO\xe7\xe3\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xac`\x8e+\xac\x9d\xd2\a(\u0494~\xff\xbb\xbf\x90\n\x9c\xe9K\x8a\x01EK\r\xb3uh\xfc\x00\x00\u07d4\xacm\x02\xe9\xa4k7\x9f\xacJ\u0271\u05f5\xd4{\xc8P\xce\x16\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xacoh\xe87\xcf\x19a\xcb\x14\xabGDm\xa1h\xa1m\u0789\x89Hz\x9a0E9D\x00\x00\u07d4\xacw\xbd\xf0\x0f\u0558[]\xb1+\xbe\xf8\x008\n\xbc*\x06w\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xac~\x03p'#\xcb\x16\xee'\xe2-\u0438\x15\xdc-\\\xae\x9f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xac\x8bP\x9a\xef\xea\x1d\xbf\xaf+\xb35\x00\xd6W\vo\xd9mQ\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xac\x8e\x87\xdd\xda^x\xfc\xbc\xb9\xfa\u007f\xc3\xce\x03\x8f\x9f}.4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xac\x9f\xffh\xc6\x1b\x01\x1e\xfb\xec\xf08\xedr\u06d7\xbb\x9er\x81\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xac\xa1\xe6\xbcd\xcc1\x80\xf6 \xe9M\u0171\xbc\xfd\x81X\xe4]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\xa2\xa883\v\x170-\xa71\xd3\r\xb4\x8a\x04\xf0\xf2\a\xc1\x89Hz\x9a0E9D\x00\x00\u07d4\xac\xaa\xdd\xcb\xf2\x86\xcb\x0e!]\xdaUY\x8f\u007f\xf0\xf4\xad\xa5\u018965\u026d\xc5\u07a0\x00\x00\u07d4\xac\xb9C8UK\u0108\u0308\xae-\x9d\x94\b\rk\u07c4\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xac\xbc-\x19\xe0l;\xab\xbb[o\x05+k\xf7\xfc7\xe0r)\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\xbd\x18U\x89\xf7\xa6\x8ag\xaaK\x1b\xd6Pw\xf8\xc6NN!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xac\xc0bp,Ya]4D\xefb\x14\xb8\x86+\x00\x9a\x02\xed\x89QO\xcb$\xff\x9cP\x00\x00\u07d4\xac\xc0\x90\x9f\xda.\xa6\xb7\xb7\xa8\x8d\xb7\xa0\xaa\xc8h\t\x1d\xdb\xf6\x89\x013v_\x1e&\u01c0\x00\u07d4\xac\xc1\u01c7\x86\xabM+;'q5\xb5\xba\x12>\x04\x00Hk\x89\x04E\x91\xd6\u007f\xec\xc8\x00\x00\u07d4\xac\xc4j*U\\t\xde\u0522\xbd\tN\x82\x1b\x97\x84;@\xc0\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xac\u015f;0\xce\xff\xc5da\xcc[\x8d\xf4\x89\x02$\x0e\x0e{\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\xce\x01\xe0\xa7\x06\x10\xdcp\xbb\x91\xe9\x92o\xa9\x95\u007f7/\xba\x89\x1d\x1c_>\xda \xc4\x00\x00\u07d4\xac\xd8\u0751\xf7\x14vLEg|c\xd8R\xe5n\xb9\xee\xce.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xac\u2af6;\x06\x04@\x9f\xbd\xe3\xe7\x16\u0487mD\xe8\xe5\u0749\b=lz\xabc`\x00\x00\xe0\x94\xac\xec\x91\xefiA\xcfc\v\xa9\xa3\u71e0\x12\xf4\xa2\xd9\x1d\u050a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xad\nJ\xe4x\xe9cn\x88\xc6\x04\xf2B\xcfT9\xc6\xd4V9\x89\xbe\xd1\xd0&=\x9f\x00\x00\x00\u07d4\xad\x17\x99\xaa\xd7`+E@\u0343/\x9d\xb5\xf1\x11P\xf1hz\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xad\x1dh\xa08\xfd%\x86\x06~\xf6\xd15\xd9b\x8ey\xc2\xc9$\x89\xfe\t\xa5'\x9e*\xbc\x00\x00\u07d4\xad*\\\x00\xf9#\xaa\xf2\x1a\xb9\xf3\xfb\x06n\xfa\n\x03\xde/\xb2\x8965\xbbw\xcbK\x86\x00\x00\u07d4\xad5e\xd5+h\x8a\xdd\xed\b\x16\x8b-8r\xd1}\n&\xae\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xad7|\xd2^\xb5>\x83\xae\t\x1a\n\x1d+E\x16\xf4\x84\xaf\u0789i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xadAM)\xcb~\xe9s\xfe\xc5N\"\xa3\x88I\x17\x86\xcfT\x02\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xadD5~\x01~$OGi1\u01f8\x18\x9e\xfe\xe8\n]\n\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xadW\xaa\x9d\x00\xd1\fC\x9b5\xef\xcc\v\xec\xac.9U\xc3\x13\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xadY\xa7\x8e\xb9\xa7J\u007f\xbd\xae\xfa\xfa\x82\xea\u0684u\xf0\u007f\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xadZ\x8dV\x01L\xfc\xb3`\xf4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xadr\x81!\x87?\x04V\xd0Q\x8b\x80\xabe\x80\xa2\x03pe\x95\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xads,\x97e\x93\xee\xc4x;N.\xcdy9yx\v\xfe\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xad}\xd0S\x85\x9e\xdf\xf1\xcbo\x9d*\xcb\xedm\xd5\xe32Bo\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xad\x80\xd8e\xb8\\4\xd2\xe6IK.z\xef\xeak\x9a\xf1\x84\u06c9\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xad\x8b\xfe\xf8\u018aH\x16\xb3\x91o5\xcb{\xfc\xd7\xd3\x04\tv\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xad\x8eH\xa3wi]\xe0\x146:R:(\xb1\xa4\fx\xf2\b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xad\x91\n#\u0585\x06\x13eJ\xf7\x863z\u04a7\bh\xacm\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xad\x92~\x03\xd1Y\x9ax\xca+\xf0\xca\u04a1\x83\xdc\xebq\xea\xc0\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xad\x92\xca\x06n\xdb|q\x1d\xfc[\x16a\x92\xd1\xed\xf8\xe7q\x85\x8a\a\x9f\x90\\o\xd3N\x80\x00\x00\u07d4\xad\x94#_\u00f3\xf4z$\x13\xaf1\u8111I\b\xef\fE\x89\x1b\x1b\x01B\xd8\x15\x84\x00\x00\u07d4\xad\x9e\x97\xa0H/5:\x05\xc0\xf7\x92\xb9w\xb6\xc7\xe8\x11\xfa_\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xad\x9fL\x89\n;Q\x1c\xeeQ\xdf\xe6\xcf\xd7\xf1\t;vA,\x89\x1bv|\xbf\xeb\f\xe4\x00\x00\u07d4\xad\xaa\x0eT\x8c\x03Z\xff\xedd\xcag\x8a\x96?\xab\xe9\xa2k\xfd\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\xad\xb9H\xb1\xb6\xfe\xfe }\xe6^\x9b\xbc-\xe9\x8e`]\vW\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xad\xc1\x9e\xc85\xaf\xe3\u5347\u0713\xa8\xa9!<\x90E\x13&\x89j\xdb\xe54\"\x82\x00\x00\x00\u07d4\xad\xc8\"\x8e\xf9(\xe1\x8b*\x80}\x00\xfb1\xfcgX\x15\xaa\x00\x00\u07d4\xad\xff\r\x1d\v\x97G\x1ev\u05c9\xd2\u470at\xf9\xbdT\xff\x89e\xea=\xb7UF`\x00\x00\u07d4\xae\x06,D\x86\x18d0u\xdez\x0004-\xce\xd6=\xba\u05c9,\xc6\u034c\u0082\xb3\x00\x00\xe0\x94\xae\x10\xe2z\x01O\r0k\xaf&mH\x97\u021a\xee\xe2\xe9t\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xae\x12k8,\xf2W\xfa\xd7\xf0\xbc}\x16)~T\xccrg\u0689\x10CV\x1a\x88)0\x00\x00\u07d4\xae\x13\xa0\x85\x11\x11\x0f2\xe5;\xe4\x12xE\xc8C\xa1\xa5|{\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xae\x17\x9aF\r\xb6c&t=$\xe6u#\xa5{$m\xaf\u007f\x8a\x01\x00\a\xae|\xe5\xbb\xe4\x00\x00\u07d4\xae\"(ey\x90y\xaa\xf4\xf0gJ\f\u06ab\x02\xa6\xd5p\xff\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xae#\x9a\xcf\xfdN\xbe.\x1b\xa5\xb4\x17\x05r\xdcy\xcce3\xec\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xae/\x9c\x19\xacv\x13e\x94C#\x93\xb0G\x1d\b\x90!d\u04c9%\xdf\x05\u01a8\x97\xe4\x00\x00\u07d4\xae4\x86\x1d4\"S\x19O\xfcfR\xdf\xdeQ\xabD\xca\xd3\xfe\x89\x19F\bhc\x16\xbd\x80\x00\u07d4\xae6\xf7E!!\x91>\x80\x0e\x0f\xcd\x1ae\xa5G\x1c#\x84o\x89\b\xe3\xf5\v\x17<\x10\x00\x00\u07d4\xae?\x98\xa4C\xef\xe0\x0f>q\x1dR]\x98\x94\u071aa\x15{\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\xe0\x94\xaeG\xe2`\x9c\xfa\xfe6\x9df\xd4\x15\xd99\xde\x05\b\x1a\x98r\x8a\x05\xba\xec\xf0%\xf9\xb6P\x00\x00\u07d4\xaeO\x12.5\xc0\xb1\xd1\xe4\x06\x92\x91E|\x83\xc0\u007f\x96_\xa3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaePU\x81L\xb8\xbe\f\x11{\xb8\xb1\xc8\u04b6;F\x98\xb7(\x89\x01\xbc\x93.\xc5s\xa3\x80\x00\u07d4\xaeS\x8cs\u0173\x8d\x8dXM~\xbd\xad\xef\xb1\\\xab\xe4\x83W\x896'\xe8\xf7\x127<\x00\x00\u07d4\xaeW\xcc\x12\x9a\x96\xa8\x99\x81\xda\xc6\r/\xfb\x87}]\xc5\xe42\x89<:#\x94\xb3\x96U\x00\x00\u07d4\xaeZ\xa1\xe6\u00b6\x0fo\xd3\xef\xe7!\xbbJq\x9c\xbe=o]\x89+$\u01b5Z^b\x00\x00\u07d4\xae\\\x9b\xda\xd3\xc5\u0221\"\x04D\xae\xa5\xc2)\xc1\x83\x9f\x1dd\x89\x19\xe2\xa4\xc8\x18\xb9\x06\x00\x00\u07d4\xae\\\xe35Z{\xa9\xb32v\f\tP\u00bcE\xa8_\xa9\xa0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xae]\"\x1a\xfc\xd3\u0493U\xf5\b\xea\xdf\xca@\x8c\xe3<\xa9\x03\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xaec[\xf781\x11\x9d-)\xc0\xd0O\xf8\xf8\xd8\u0425zF\x89Hz\x9a0E9D\x00\x00\xe0\x94\xaed\x81U\xa6X7\x0f\x92\x9b\xe3\x84\xf7\xe0\x01\x04~I\xddF\x8a\x02\xdf$\xae2\xbe D\x00\x00\xe0\x94\xaeo\fs\xfd\xd7|H\x97'Q!t\u0675\x02\x96a\x1cL\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xaep\xe6\x9d,J\n\xf8\x18\x80{\x1a'\x05\xf7\x9f\u0435\xdb\u01095e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xaew9\x12N\xd1S\x05%\x03\xfc\x10\x14\x10\xd1\xff\xd8\xcd\x13\xb7\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xaex\xbb\x84\x919\xa6\xba8\xae\x92\xa0\x9ai`\x1c\xc4\xcbb\u0449\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xae\x84\"\x10\xf4M\x14\u0124\u06d1\xfc\x9d;;P\x01O{\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xae\x84.\x81\x85\x8e\xcf\xed\xf6Plhm\xc2\x04\xac\x15\xbf\x8b$\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xae\x89T\xf8\xd6\x16m\xe5\a\xcfa)}\x0f\xc7\xcak\x9eq(\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xae\x9e\xcdk\u0755.\xf4\x97\xc0\x05\n\u0aca\x82\xa9\x18\x98\u0389\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\xae\x9f\\?\xbb\xe0\u027c\xbf\x1a\xf8\xfft\xea(\v:]\x8b\b\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xae\xad\x88\u0589Ak\x1c\x91\xf26D!7[}\x82\xd0RR\n\xfb\\Wm\x9f~\xb9>\u048a\r\xd0A \xba\t\xcf\xe6\x00\x00\u07d4\xae\xc2\u007f\xf5\xd7\xf9\xdd\u0691\x18?F\xf9\xd5%C\xb6\xcd+/\x89\x18e\x01'\xcc=\xc8\x00\x00\u07d4\xae\xe4\x9dh\xad\xed\xb0\x81\xfdCpZ_x\xc7x\xfb\x90\xdeH\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xae\xf5\xb1\"X\xa1\x8d\xec\a\xd5\xec.1et\x91\x9dy\xd6\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\xae\xfc\xfe\x88\xc8&\xcc\xf11\xd5N\xb4\ua7b8\x0ea\xe1\xee%\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xaf\x06\xf5\xfam\x12\x14\xecC\x96}\x1b\xd4\xdd\xe7J\xb8\x14\xa98\x89\x04\xc5>\xcd\xc1\x8a`\x00\x00\u07d4\xaf\x11H\xefl\x8e\x10=u0\xef\xc9\x16y\u026c'\x00\t\x93\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xaf >\"\x9d~mA\x9d\xf47\x8e\xa9\x87\x15Q_c\x14\x85\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xaf X\xc7(,\xf6|\x8c<\xf90\x13<\x89a|\xe7])\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4\xaf&\xf7\u01bfE> x\xf0\x89S\u4c80\x04\xa2\xc1\xe2\t\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xaf0\x87\xe6.\x04\xbf\x90\rZT\xdc>\x94bt\u0692B;\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf6\x14\u0736\x8a6\xe4ZN\x91\x1ebybG\"-Y[\x89z\x81\x06_\x11\x03\xbc\x00\x00\u07d4\xaf6\x15\u01c9\u0431\x15*\xd4\xdb%\xfe]\xcf\"(\x04\xcfb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xaf<\xb5\x96Y3\xe7\xda\u0603i;\x9c>\x15\xbe\xb6\x8aHs\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xafD\x93\xe8R\x1c\xa8\x9d\x95\xf5&|\x1a\xb6?\x9fEA\x1e\x1b\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xafL\xf4\x17\x85\x16\x1fW\x1d\f\xa6\x9c\x94\xf8\x02\x1fA)N\u028a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xafR\x9b\xdbE\x9c\xc1\x85\xbe\xe5\xa1\u014b\xf7\xe8\xcc\xe2\\\x15\r\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xafg\xfd>\x12\u007f\xd9\xdc6\xeb?\xcdj\x80\u01feOu2\xb2\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xafw\x1094Z40\x01\xbc\x0f\x8aY#\xb1&\xb6\rP\x9c\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94\xaf\u007fy\xcbAZ\x1f\xb8\u06fd\tF\a\xee\x8dA\xfb|Z;\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xaf\x87\xd27\x1e\xf3x\x95\u007f\xbd\x05\xba/\x1df\x93\x1b\x01\u2e09%\xf2s\x93=\xb5p\x00\x00\u07d4\xaf\x88\x0f\xc7V}U\x95\xca\xcc\xe1\\?\xc1L\x87B\xc2l\x9e\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xaf\x8e\x1d\xcb1L\x95\r6\x87CM0\x98X\xe1\xa8s\x9c\u0509\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xaf\x99-\xd6i\xc0\x88>U\x15\xd3\xf3\x11*\x13\xf6\x17\xa4\xc3g\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaf\xa1\u056d8\xfe\xd4GY\xc0[\x89\x93\xc1\xaa\r\xac\xe1\x9f@\x89\x04V9\x18$O@\x00\x00\xe0\x94\xaf\xa59XnG\x19\x17J;F\xb9\xb3\xe6c\xa7\u0475\xb9\x87\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xaf\xa6\x94n\xff\xd5\xffS\x15O\x82\x01\x02S\xdfG\xae(\f\u0309j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xaf\xc8\xeb\u860b\xd4\x10Z\xccL\x01\x8eTj\x1e\x8f\x9cx\x88\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xaf\xcc}\xbb\x83V\xd8B\xd4:\xe7\xe2<\x84\"\xb0\"\xa3\b\x03\x8a\x06o\xfc\xbf\xd5\xe5\xa3\x00\x00\x00\u07d4\xaf\xd0\x19\xff6\xa0\x91U4ki\x97H\x15\xa1\xc9\x12\xc9\n\xa4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xaf\xda\xc5\xc1\xcbV\xe2E\xbfp3\x00f\xa8\x17\uabecL\u0449\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf\xdd\x1bxab\xb81~ \xf0\xe9y\xf4\xb2\xceHmv]\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xaf\xf1\x04Z\xdf'\xa1\xaa2\x94a\xb2M\xe1\xba\u950ai\x8b\x89\x01\u03c4\xa3\n\n\f\x00\x00\u07d4\xaf\xf1\a\x96\v~\xc3N\u0590\xb6e\x02M`\x83\x8c\x19\x0fp\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xaf\xf1\x1c\xcfi\x93\x04\xd5\xf5\x86*\xf8`\x83E\x1c&\xe7\x9a\xe5\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xaf\xf1at\nm\x90\x9f\xe9\x9cY\xa9\xb7yE\xc9\x1c\xc9\x14H\x89\x03@\xaa\xd2\x1b;p\x00\x00\xe0\x94\xaf\xfc\x99\xd5\ubd28O\xe7x\x8d\x97\xdc\xe2t\xb08$\x048\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xaf\xfe\xa0G7\"\xcb\u007f\x0e\x0e\x86\xb9\xe1\x18\x83\xbfB\x8d\x8dT\x89i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xb0\t\x96\xb0Vn\xcb>rC\xb8\"y\x88\u0733R\xc2\x18\x99\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xb0\x1e8\x9b(\xa3\x1d\x8eI\x95\xbd\xd7\xd7\xc8\x1b\xee\xab\x1eA\x19\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb0-\x06(s3EE\u03a2\x92\x18\xe4\x05w`Y\x0ft#\x89\xac\xb6\xa1\xc7\xd9:\x88\x00\x00\u07d4\xb0/\xa2\x93\x87\xec\x12\xe3\u007fi\"\xacL\xe9\x8c[\t\xe0\xb0\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb06\x91k\xda\u03d4\xb6\x9eZ\x8ae`)u\xeb\x02a\x04\u0749\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb0A1\x0f\xe9\xee\u0586L\xed\u053e\xe5\x8d\xf8\x8e\xb4\xed<\xac\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb0U\xafL\xad\xfc\xfd\xb4%\xcfe\xbad1\a\x8f\a\xec\u056b\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xb0W\x11S\xdb\x1cN\u05ec\xae\xfe\x13\xec\xdf\xdbr\xe7\xe4\xf0j\x8a\x11\f\xffyj\xc1\x95 \x00\x00\u07d4\xb0n\xab\t\xa6\x10\u01a5=V\xa9F\xb2\xc44\x87\xac\x1d[-\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb0rI\xe0U\x04J\x91U5\x9a@)7\xbb\xd9T\xfeH\xb6\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xb0v\x182\x8a\x90\x13\a\xa1\xb7\xa0\xd0X\xfc\xd5xn\x9er\xfe\x8a\x06gI]JC0\xce\x00\x00\u07d4\xb0y\xbbM\x98f\x14:m\xa7*\xe7\xac\x00\"\x06)\x811\\\x89)3\x1eeX\xf0\xe0\x00\x00\u07d4\xb0{\xcc\bZ\xb3\xf7)\xf2D\x00Ah7\xb6\x996\xba\x88s\x89lm\x84\xbc\xcd\xd9\xce\x00\x00\u07d4\xb0{\xcf\x1c\xc5\xd4F.Q$\xc9e\xec\xf0\xd7\r\xc2z\xcau\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\xb0|\xb9\xc1$\x05\xb7\x11\x80uC\u0113De\xf8\u007f\x98\xbd-\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb0\u007f\u07af\xf9\x1dD`\xfel\xd0\u8870\xbd\x8d\"\xa6.\x87\x8a\x01\x1d%)\xf3SZ\xb0\x00\x00\xe0\x94\xb0\x9f\xe6\xd44\x9b\x99\xbc7\x93\x80T\x02-T\xfc\xa3f\xf7\xaf\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\xe0\x94\xb0\xaa\x00\x95\f\x0e\x81\xfa2\x10\x17>r\x9a\xaf\x16:'\xcdq\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xb0\xacN\xfff\x80\xee\x14\x16\x9c\xda\xdb\xff\xdb0\x80Om%\xf5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb0\xb3j\xf9\xae\xee\u07d7\xb6\xb0\"\x80\xf1\x14\xf19\x84\xea2`\x895e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xb0\xb7y\xb9K\xfa<.\x1fX{\u031c~!x\x92\"7\x8f\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xb0\xba\xeb0\xe3\x13wlLm$t\x02\xbaAg\xaf\u0361\u0309j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb0\xbb)\xa8a\xea\x1dBME\xac\u053f\u0112\xfb\x8e\xd8\t\xb7\x89\x04V9\x18$O@\x00\x00\xe0\x94\xb0\xc1\xb1w\xa2 \xe4\x1f|t\xd0|\u0785i\xc2\x1cu\xc2\xf9\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xb0\xc7\xceL\r\xc3\u00bb\xb9\x9c\xc1\x85{\x8aE_a\x17\x11\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb0\xce\xf8\xe8\xfb\x89\x84\xa6\x01\x9f\x01\xc6y\xf2r\xbb\xe6\x8f\\w\x89\b=lz\xabc`\x00\x00\xe0\x94\xb0\xd3+\xd7\xe4\u6577\xb0\x1a\xa3\xd0Ao\x80U}\xba\x99\x03\x8a\x03s\x9f\xf0\xf6\xe6\x130\x00\x00\xe0\x94\xb0\xd3\u0247+\x85\x05n\xa0\xc0\xe6\xd1\xec\xf7\xa7~<\u6ac5\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\xb0\xe4i\u0206Y8\x15\xb3IV8Y]\xae\xf0f_\xaeb\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xb0\xe7`\xbb\a\xc0\x81wsE\xe0W\x8e\x8b\u0218\"mN;\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1\x040\x04\xec\x19A\xa8\xcfO+\x00\xb1W\x00\u076co\xf1~\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb1\x05\xdd=\x98|\xff\xd8\x13\xe9\xc8P\n\x80\xa1\xad%}V\u0189lj\xccg\u05f1\xd4\x00\x00\u07d4\xb1\x0f\u04a6G\x10/\x88\x1ft\xc9\xfb\xc3}\xa62\x94\x9f#u\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xb1\x15\xee:\xb7d\x1e\x1a\xa6\xd0\x00\xe4\x1b\xfc\x1e\xc7!\f/2\x8a\x02\xc0\xbb=\xd3\fN \x00\x00\u07d4\xb1\x17\x8a\xd4s\x83\xc3\x1c\x814\xa1\x94\x1c\xbc\xd4t\xd0bD\xe2\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xb1\x17\x95\x89\u1779\xd4\x15W\xbb\xec\x1c\xb2L\xcc-\xec\x1c\u007f\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xb1\x19\u76a9\xb9\x16Re\x81\xcb\xf5!\xefGJ\xe8M\xcf\xf4\x89O\xba\x10\x01\xe5\xbe\xfe\x00\x00\u07d4\xb1\x1f\xa7\xfb'\n\xbc\xdfZ.\xab\x95\xaa0\u013566\uffc9+^:\xf1k\x18\x80\x00\x00\u07d4\xb1$\xbc\xb6\xff\xa40\xfc\xae.\x86\xb4_'\xe3\xf2\x1e\x81\xee\b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1)\xa5\xcbq\x05\xfe\x81\v\u0615\xdcr\x06\xa9\x91\xa4TT\x88\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\xe0\x94\xb1.\xd0{\x8a8\xadU\x066?\xc0z\vmy\x996\xbd\xaf\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb14\xc0\x049\x1a\xb4\x99(x3zQ\xec$/B(WB\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1?\x93\xaf0\xe8\xd7fs\x81\xb2\xb9[\xc1\xa6\x99\xd5\xe3\xe1)\x89\x16\u012b\xbe\xbe\xa0\x10\x00\x00\u07d4\xb1E\x92\x85\x86>\xa2\xdb7Y\xe5F\u03b3\xfb7a\xf5\x90\x9c\x89<\xd7*\x89@\x87\xe0\x80\x00\u07d4\xb1F\xa0\xb9%U<\xf0o\xca\xf5J\x1bM\xfe\xa6!)\aW\x89lnY\xe6|xT\x00\x00\xe0\x94\xb1Jz\xaa\x8fI\xf2\xfb\x9a\x81\x02\u05bb\xe4\u010a\xe7\xc0o\xb2\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb1K\xbe\xffpr\tu\xdca\x91\xb2\xa4O\xf4\x9f&r\x87<\x89\a\xc0\x86\x0eZ\x80\xdc\x00\x00\xe0\x94\xb1L\xc8\xde3\xd63\x826S\x9aH\x90 \xceFU\xa3+\u018a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb1M\xdb\x03\x86\xfb`c\x98\xb8\xccGVZ\xfa\xe0\x0f\xf1\xd6j\x89\xa1*\xff\b>f\xf0\x00\x00\u07d4\xb1S\xf8(\xdd\amJ|\x1c%t\xbb-\xee\x1aD\xa3\x18\xa8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb1T\x0e\x94\xcf\xf3F\\\xc3\u0447\xe7\xc8\u3f6f\x98FY\u2262\x15\xe4C\x90\xe33\x00\x00\u07d4\xb1X\xdbC\xfab\xd3\x0ee\xf3\u041b\xf7\x81\u01f6sr\uba89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xb1ar_\xdc\xed\xd1yR\xd5{#\xef([~K\x11i\xe8\x89\x02\xb6\xdf\xed6d\x95\x80\x00\u07d4\xb1dy\xba\x8e}\xf8\xf6>\x1b\x95\xd1I\u0345)\xd75\xc2\u0689-\xe3:j\xac2T\x80\x00\u07d4\xb1f\xe3}.P\x1a\xe7<\x84\x14+_\xfbZ\xa6U\xddZ\x99\x89l]\xb2\xa4\xd8\x15\xdc\x00\x00\u07d4\xb1\x83\xeb\xeeO\xcbB\xc2 \xe4wt\xf5\x9dlT\xd5\xe3*\xb1\x89V\xf7\xa9\xc3<\x04\xd1\x00\x00\u07d4\xb1\x88\a\x84D\x02~8g\x98\xa8\xaehi\x89\x19\xd5\xcc#\r\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xb1\x89j7\xe5\u0602Z-\x01vZ\xe5\xdeb\x99w\u0783R\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb1\x8eg\xa5\x05\n\x1d\xc9\xfb\x19\t\x19\xa3=\xa88\xefDP\x14\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb1\xa2\xb4:t3\xdd\x15\v\xb8\"'\xedQ\x9c\u05b1B\u04c2\x89\x94mb\rtK\x88\x00\x00\u07d4\xb1\xc0\u040b6\xe1\x84\xf9\x95*@7\xe3\xe5:f}\a\nN\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb1\xc3(\xfb\x98\xf2\xf1\x9a\xb6do\n|\x8cVo\xdaZ\x85@\x89\x87\x86x2n\xac\x90\x00\x00\xe0\x94\xb1\xc7Qxi9\xbb\xa0\xd6q\xa6w\xa1X\u01ab\xe7&^F\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xb1\xcdK\xdf\xd1\x04H\x9a\x02n\u025dYs\a\xa0By\xf1s\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb1\u03d4\xf8\t\x15\x05\x05_\x01\n\xb4\xba\u0196\xe0\xca\x0fg\xa1\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xb1\u05b0\x1b\x94\xd8T\xfe\x8b7J\xa6^\x89\\\xf2*\xa2V\x0e\x892\xf5\x1e\u06ea\xa30\x00\x00\xe0\x94\xb1\u06e5%\v\xa9bWU$n\x06yg\xf2\xad/\a\x91\u078a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xb1\xe2\u0755\xe3\x9a\xe9w\\U\xae\xb1?\x12\xc2\xfa#0S\xba\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb1\xe6\xe8\x10\xc2J\xb0H\x8d\xe9\xe0\x1eWH7\x82\x9f|w\u0409\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb1\xe9\xc5\xf1\xd2\x1eauzk.\xe7Y\x13\xfcZ\x1aA\x01\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\x03\u049elV\xb9&\x99\u0139-\x1fo\x84d\x8d\xc4\u03fc\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\x16\xdcY\xe2|=ry\xf5\xcd[\xb2\xbe\u03f2`n\x14\u0649\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\x1byy\xbf|\\\xa0\x1f\xa8-\xd6@\xb4\x1c9\xe6\u01bcu\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xb2#\xbf\x1f\xbf\x80H\\\xa2\xb5V}\x98\xdb{\xc3SM\xd6i\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb2-PU\xd9b15\x96\x1ej\xbd'<\x90\xde\xea\x16\xa3\xe7\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xb2-\xad\xd7\xe1\xe0R2\xa927\xba\xed\x98\xe0\u07d2\xb1\x86\x9e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb24\x03_uDF<\xe1\xe2+\xc5S\x06F\x84\xc5\x13\xcdQ\x89\r\x89\xfa=\u010d\xcf\x00\x00\u07d4\xb2G\u03dcr\xecH*\xf3\xea\xa7Ye\x8fy=g\nW\f\x891p\x8a\xe0\x04T@\x00\x00\u07d4\xb2ghA\xee\x9f-1\xc1r\xe8#\x03\xb0\xfe\x9b\xbf\x9f\x1e\t\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb2y\xc7\xd3U\u0088\x03\x92\xaa\u046a!\xee\x86|;5\a\u07c9D[\xe3\xf2\uf1d4\x00\x00\u07d4\xb2|\x1a$ L\x1e\x11\x8du\x14\x9d\xd1\t1\x1e\a\xc0s\xab\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb2\x81\x81\xa4X\xa4@\xf1\u01bb\x1d\xe8@\x02\x81\xa3\x14\x8fL5\x89\x14b\fW\xdd\xda\xe0\x00\x00\xe0\x94\xb2\x82E\x03|\xb1\x92\xf7W\x85\u02c6\xcb\xfe|\x93\r\xa2X\xb0\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xb2\x87\xf7\xf8\xd8\u00c7,\x1bXk\xcd}\n\xed\xbf~s'2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb2\x8b\xb3\x9f4fQ|\xd4o\x97\x9c\xf5\x96S\xee}\x8f\x15.\x89\x18e\x01'\xcc=\xc8\x00\x00\u07d4\xb2\x8d\xbf\xc6I\x98\x94\xf7:q\xfa\xa0\n\xbe\x0fK\xc9\u045f*\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xb2\x96\x8f}5\xf2\b\x87\x161\xc6h{?=\xae\xab\xc6al\x89\bu\xc4\u007f(\x9fv\x00\x00\u07d4\xb2\x9f[|\x190\xd9\xf9z\x11^\x06pf\xf0\xb5M\xb4K;\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb2\xa1D\xb1\xeag\xb9Q\x0f\"g\xf9\xda9\xd3\xf9=\xe2fB\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xa2\xc2\x11\x16\x12\xfb\x8b\xbb\x8e}\xd97\x8dg\xf1\xa3\x84\xf0P\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb2\xa4\x98\xf0;\xd7\x17\x8b\u0627\x89\xa0\x0fR7\xafy\xa3\xe3\xf8\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\u07d4\xb2\xaa/\x1f\x8e\x93\xe7\x97\x13\xd9,\xea\x9f\xfc\xe9\xa4\n\xf9\xc8-\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xb5\x16\xfd\u045e\u007f8d\xb6\xd2\xcf\x1b%*AV\xf1\xb0;\x89\x02\xe9\x83\xc7a\x15\xfc\x00\x00\u07d4\xb2\xb7\u0374\xffKa\u0577\xce\v\"p\xbb\xb5&\x97C\xec\x04\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb2\xbd\xbe\u07d5\x90\x84v\xd7\x14\x8a7\f\u0193t6(\x05\u007f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb2\xbf\xaaX\xb5\x19l\\\xb7\xf8\x9d\xe1_G\x9d\x188\xdeq=\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4\xb2\xc5>\xfa3\xfeJ:\x1a\x80 \\s\xec;\x1d\xbc\xad\x06\x02\x89h\x01\u06b3Y\x18\x93\x80\x00\xe0\x94\xb2\xd06\x05\x15\xf1}\xab\xa9\x0f\u02ec\x82\x05\xd5i\xb9\x15\u05ac\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xb2\xd1\xe9\x9a\xf9\x121\x85\x8epe\xdd\x19\x183\r\xc4\xc7G\u054a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\xb2\u066b\x96d\xbc\xf6\xdf <4o\u0192\xfd\x9c\xba\xb9 ^\x89\x17\xbex\x97`e\x18\x00\x00\u07d4\xb2\u0777\x86\xd3yN'\x01\x87\xd0E\x1a\xd6\u0237\x9e\x0e\x87E\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb2\xe0\x85\xfd\xdd\x14h\xba\aA['NsN\x11#\u007f\xb2\xa9\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xb2\xe9\xd7k\xf5\x0f\xc3k\xf7\u04d4Kc\xe9\u0288\x9bi\x99h\x89\x902\xeab\xb7K\x10\x00\x00\xe0\x94\xb2\xf9\xc9r\xc1\xe9swU\xb3\xff\x1b0\x88s\x83\x969[&\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xb2\xfc\x84\xa3\xe5\nP\xaf\x02\xf9M\xa08>\u055fq\xff\x01\u05ca\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xb3\x05\v\xef\xf9\xde3\xc8\x0e\x1f\xa1R%\xe2\x8f,A:\xe3\x13\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\xb3\x11\x96qJH\xdf\xf7&\xea\x943\xcd)\x12\xf1\xa4\x14\xb3\xb3\x89\x91Hx\xa8\xc0^\xe0\x00\x00\xe0\x94\xb3\x14[tPm\x1a\x8d\x04|\xdc\xdcU9*{SPy\x9a\x8a\x1bb)t\x1c\r=]\x80\x00\u07d4\xb3 \x83H6\xd1\xdb\xfd\xa9\xe7\xa3\x18M\x1a\xd1\xfdC \xcc\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3#\u073f.\xdd\xc58.\u4efb \x1c\xa3\x93\x1b\xe8\xb48\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb3$\x00\xfd\x13\xc5P\t\x17\xcb\x03{)\xfe\"\xe7\xd5\"\x8f-\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xb3%gL\x01\xe3\xf7)\rR&3\x9f\xbe\xacg\xd2!'\x9f\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xb3(%\xd5\xf3\xdb$\x9e\xf4\xe8\\\xc4\xf31S\x95\x89v\u8f09\x1b-\xf9\xd2\x19\xf5y\x80\x00\u07d4\xb3*\xf3\xd3\xe8\xd0u4I&To.2\x88{\xf9;\x16\xbd\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb3/\x1c&\x89\xa5\xcey\xf1\xbc\x97\v1XO\x1b\xcf\"\x83\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb3<\x03#\xfb\xf9\xc2l\x1d\x8a\xc4N\xf7C\x91\u0400F\x96\u0689\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb3O\x04\xb8\xdbe\xbb\xa9\xc2n\xfcL\xe6\xef\xc5\x04\x81\xf3\xd6]\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb3U}9\xb5A\x1b\x84D__T\xf3\x8fb\xd2qM\x00\x87\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xb3X\xe9|p\xb6\x05\xb1\xd7\xd7)\u07f6@\xb4<^\xaf\xd1\xe7\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xb3^\x8a\x1c\r\xac~\x0ef\u06ecsjY*\xbdD\x01%a\x88\xcf\xceU\xaa\x12\xb3\x00\x00\xe0\x94\xb3fx\x94\xb7\x86<\x06\x8a\xd3D\x87?\xcf\xf4\xb5g\x1e\x06\x89\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb3qw1\xda\xd6Q2\xday-\x87`0\xe4j\xc2'\xbb\x8a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3s\x1b\x04l\x8a\u0195\xa1'\xfdy\u0425\xd5\xfaj\xe6\xd1.\x89lO\xd1\xee$nx\x00\x00\u07d4\xb3|+\x9fPc{\xec\xe0\u0295\x92\b\xae\xfe\xe6F;\xa7 \x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb3\x88\xb5\xdf\xec\xd2\xc5\u4d56W|d%V\xdb\xfe'xU\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb3\x8cNS{]\xf90\xd6Zt\xd0C\x83\x1dkH[\xbd\xe4\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xb3\x919Wa\x94\xa0\x86a\x95\x15\x1f3\xf2\x14\n\xd1\u0306\u03ca\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xb3\x9fL\x00\xb2c\f\xab}\xb7)^\xf4=G\xd5\x01\xe1\u007f\u05c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb3\xa6K\x11vrOT\t\xe1AJ5#f\x1b\xae\xe7KJ\x89\x01ch\xffO\xf9\xc1\x00\x00\u07d4\xb3\xa6\xbdA\xf9\xd9\xc3 \x1e\x05\v\x87\x19\x8f\xbd\xa3\x994\"\x10\x89\xc4a\xe1\xdd\x10)\xb5\x80\x00\u07d4\xb3\xa8\xc2\xcb}5\x8eW9\x94\x1d\x94[\xa9\x04Z\x02:\x8b\xbb\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3\xaeT\xfb\xa0\x9d>\xe1\u05bd\xd1\xe9W\x929\x19\x02L5\xfa\x89\x03\x8d,\xeee\xb2*\x80\x00\u07d4\xb3\xb7\xf4\x93\xb4J,\x8d\x80\xecx\xb1\xcd\xc7Ze+s\xb0l\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb3\xc2(s\x1d\x18m-\xed[_\xbe\x00Lfl\x8eF\x9b\x86\x89\x01\x92t\xb2Y\xf6T\x00\x00\u07d4\xb3\xc2``\x9b\x9d\xf4\t^l]\xff9\x8e\xeb^-\xf4\x99\x85\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xb3\xc6[\x84Z\xbal\xd8\x16\xfb\xaa\xe9\x83\xe0\xe4l\x82\xaa\x86\"\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb3\xc9H\x11\xe7\x17[\x14\x8b(\x1c\x1a\x84[\xfc\x9b\xb6\xfb\xc1\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb3\xe2\x0e\xb4\xde\x18\xbd\x06\x02!h\x98\x94\xbe\u5bb2SQ\xee\x89\x03\xfc\x80\xcc\xe5\x16Y\x80\x00\u07d4\xb3\xe3\xc49\x06\x98\x80\x15f\x00\u0089.D\x8dA6\xc9-\x9b\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\xe0\x94\xb3\xf8*\x87\xe5\x9a9\xd0\u0480\x8f\aQ\xebr\xc22\x9c\xdc\u014a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb3\xfc\x1dh\x81\xab\xfc\xb8\xbe\xcc\v\xb0!\xb8\xb7;r3\u0751\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xb4\x05\x94\xc4\xf3fN\xf8I\u0326\"{\x8a%\xaai\t%\xee\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb4\x1e\xaf]Q\xa5\xba\x1b\xa3\x9b\xb4\x18\u06f5O\xabu\x0e\xfb\x1f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4$\u058d\x9d\r\x00\xce\xc1\x93\x8c\x85N\x15\xff\xb8\x80\xba\x01p\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb4%bs\x96+\xf61\xd0\x14U\\\xc1\xda\r\xcc1akI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb40g\xfep\u0675Ys\xbaX\xdcd\xdd\u007f1\x1eUBY\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb46W\xa5\x0e\xec\xbc0w\xe0\x05\xd8\xf8\xd9O7xv\xba\u0509\x01\xec\x1b:\x1f\xf7Z\x00\x00\u07d4\xb4<'\xf7\xa0\xa1\"\bK\x98\xf4\x83\x92%A\u0203l\xee,\x89&\u009eG\u0104L\x00\x00\xe0\x94\xb4A5v\x86\x9c\b\xf9Q*\xd3\x11\xfe\x92Y\x88\xa5-4\x14\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xb4F\x05U$q\xa6\xee\xe4\u06abq\xff;\xb4\x13&\xd4s\xe0\x89-~=Q\xbaS\xd0\x00\x00\u07d4\xb4GW\x1d\xac\xbb>\u02f6\xd1\xcf\v\f\x8f88\xe5#$\xe2\x89\x01\xa3\x18f\u007f\xb4\x05\x80\x00\u07d4\xb4G\x83\xc8\xe5{H\a\x93\xcb\u059aE\xd9\f{O\fH\xac\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb4H\x15\xa0\xf2\x8eV\x9d\x0e\x92\x1aJ\u078f\xb2d%&Iz\x89\x03\x027\x9b\xf2\xca.\x00\x00\u07d4\xb4Im\xdb'y\x9a\"$W\xd79y\x11g(\u8844[\x89\x8d\x81\x9e\xa6_\xa6/\x80\x00\xe0\x94\xb4RL\x95\xa7\x86\x0e!\x84\x02\x96\xa6\x16$@\x19B\x1cJ\xba\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb4\\\xca\r6\x82fbh<\xf7\u0432\xfd\xach\u007f\x02\xd0\u010965\u026d\xc5\u07a0\x00\x00\u0794\xb4d@\u01d7\xa5V\xe0L}\x91\x04f\x04\x91\xf9k\xb0v\xbf\x88\xce\xc7o\x0eqR\x00\x00\u07d4\xb4j\u0386^,P\xeaF\x98\xd2\x16\xabE]\xffZ\x11\xcdr\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4m\x11\x82\xe5\xaa\xca\xff\r&\xb2\xfc\xf7/<\x9f\xfb\xcd\xd9}\x89\xaa*`<\xdd\u007f,\x00\x00\u07d4\xb4\x89!\xc9h}U\x10tE\x84\x93n\x88\x86\xbd\xbf-\xf6\x9b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\x98\xbb\x0fR\x00\x05\xb6!jD%\xb7Z\xa9\xad\xc5-b+\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xb4\xb1\x1d\x10\x9f`\x8f\xa8\xed\xd3\xfe\xa9\xf8\xc3\x15d\x9a\xeb=\x11\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb4\xb1K\xf4TU\u042b\b\x035\x8bu$\xa7+\xe1\xa2\x04[\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb4\xb1\x85\xd9C\xee+Xc\x1e3\xdf\xf5\xafhT\xc1y\x93\xac\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\xbf$\u02c3hk\xc4i\x86\x9f\xef\xb0D\xb9\tqi\x93\xe2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb4\xc2\x00@\xcc\u0661\xa3(=\xa4\u0522\xf3e\x82\bC\xd7\xe2\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb4\xc8\x17\x0f{*\xb56\xd1\u0662[\xdd :\xe1(\x8d\xc3\u0549\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb4\xd8/.i\x94?}\xe0\xf5\xf7t8y@o\xac.\x9c\xec\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xb4\xddF\f\xd0\x16rZd\xb2.\xa4\xf8\xe0n\x06gN\x03>\x8a\x01#\x1b\xb8t\x85G\xa8\x00\x00\u07d4\xb4\xddT\x99\xda\xeb%\a\xfb-\xe1\"\x97s\x1dLr\xb1k\xb0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb5\x04l\xb3\xdc\x1d\xed\xbd6E\x14\xa2\x84\x8eD\xc1\xdeN\xd1G\x8a\x03{}\x9b\xb8 @^\x00\x00\xe0\x94\xb5\b\xf9\x87\xb2\xde4\xaeL\xf1\x93\u0785\xbf\xf6\x13\x89b\x1f\x88\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xb5\tU\xaan4\x15q\x98f\b\xbd\u0211\xc2\x13\x9fT\f\u07c9j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb5\f\x14\x9a\x19\x06\xfa\xd2xo\xfb\x13Z\xabP\x177\xe9\xe5o\x89\x15\b\x94\xe8I\xb3\x90\x00\x00\u07d4\xb5\f\x9fW\x89\xaeD\xe2\xdc\xe0\x17\xc7\x14\xca\xf0\f\x83\x00\x84\u0089\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xb5\x14\x88,\x97\x9b\xb6B\xa8\r\u04c7T\u0578\xc8)m\x9a\a\x893\xc5I\x901r\f\x00\x00\u07d4\xb5\x1d\u0734\xddN\x8a\xe6\xbe3m\xd9eIq\xd9\xfe\xc8kA\x89\x16\xd4d\xf8=\u2500\x00\u07d4\xb5\x1eU\x8e\xb5Q/\xbc\xfa\x81\xf8\u043d\x93\x8cy\xeb\xb5$+\x89&\u009eG\u0104L\x00\x00\u07d4\xb5#\xff\xf9t\x98q\xb3S\x88C\x887\xf7\xe6\xe0\u07a9\xcbk\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xb5-\xfbE\xde]t\xe3\xdf \x832\xbcW\x1c\x80\x9b\x8d\xcf2\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xb55\xf8\u06c7\x9f\xc6\u007f\xecX\x82J\\\xbenT\x98\xab\xa6\x92\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xb57\xd3jp\xee\xb8\xd3\xe5\xc8\r\xe8\x15\"\\\x11X\u02d2\u0109QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xb5;\xcb\x17L%\x184\x8b\x81\x8a\xec\xe0 6E\x96Fk\xa3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5I>\xf1srDE\xcf4\\\x03]'\x9b\xa7Y\xf2\x8dQ\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb5S\xd2]kT!\xe8\x1c*\xd0^\v\x8b\xa7Q\xf8\xf0\x10\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5Tt\xbaX\xf0\xf2\xf4\x0el\xba\xbe\xd4\xea\x17n\x01\x1f\xca\u0589j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb5U\xd0\x0f\x91\x90\xcc6w\xae\xf3\x14\xac\xd7?\xdc99\x92Y\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5W\xab\x949\xefP\xd27\xb5S\xf0%\b6JFj\\\x03\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5jx\x00(\x03\x9c\x81\xca\xf3{gu\xc6 \u7195Gd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5j\u04ae\xc6\xc8\xc3\xf1\x9e\x15\x15\xbb\xb7\u0751(RV\xb69\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5t\x13\x06\n\xf3\xf1N\xb4y\x06_\x1e\x9d\x19\xb3uz\xe8\u0309\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xb5uI\xbf\xbc\x9b\xdd\x18\xf76\xb2&P\xe4\x8as`\x1f\xa6\\\x89\x18-~L\xfd\xa08\x00\x00\xe0\x94\xb5w\xb6\xbe\xfa\x05N\x9c\x04\x04a\x85P\x94\xb0\x02\xd7\xf5{\u05ca\x18#\xf3\xcfb\x1d#@\x00\x00\u07d4\xb5{\x04\xfa#\xd1 ?\xae\x06\x1e\xacEB\xcb`\xf3\xa5v7\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\xb5\x87\f\xe3B\xd43C36s\x03\x8bGd\xa4n\x92_>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5\x87\xb4J,\xa7\x9eK\xc1\u074b\xfd\xd4: qP\xf2\xe7\xe0\x89\",\x8e\xb3\xfff@\x00\x00\u07d4\xb5\x89gm\x15\xa0DH4B0\xd4\xff'\xc9^\xdf\x12,I\x8965\u026d\xc5\u07a0\x00\x00\u0794\xb5\x8bR\x86^\xa5]\x806\xf2\xfa\xb2`\x98\xb3R\u0283~\x18\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xb5\x90k\n\u9881X\xe8\xacU\x0e9\xda\bn\xe3\x15v#\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5\xa4g\x96\x85\xfa\x14\x19l.\x920\xc8\xc4\xe3;\xff\xbc\x10\xe2\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xb5\xa5\x89\u075f@q\u06f6\xfb\xa8\x9b?]]\xae}\x96\xc1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5\xa6\x06\xf4\xdd\u02f9G\x1e\xc6\u007fe\x8c\xaf+\x00\xees\x02^\x89\xeaun\xa9*\xfct\x00\x00\u07d4\xb5\xadQW\u0769!\xe6\xba\xfa\u0350\x86\xaes\xae\x1fa\x1d?\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb5\xad\xd1\u701f}\x03\x06\x9b\xfe\x88;\n\x93\"\x10\xbe\x87\x12\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb5\xba)\x91|x\xa1\xd9\xe5\xc5\xc7\x13fl\x1eA\x1d\u007fi:\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb5\xc8\x16\xa8(<\xa4\xdfh\xa1\xa7=c\xbd\x80&\x04\x88\xdf\b\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb5\xca\xc5\xed\x03G}9\v\xb2g\xd4\xeb\xd4a\x01\xfb\xc2\xc3\u0689\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xb5\u037cA\x15@oR\u5a85\xd0\xfe\xa1p\u0497\x9c\u01fa\x89Hz\x9a0E9D\x00\x00\u0794\xb5\u0653M{)+\xcf`;(\x80t\x1e\xb7`(\x83\x83\xa0\x88\xe7\xc2Q\x85\x05\x06\x00\x00\u07d4\xb5\xddP\xa1]\xa3Ih\x89\nS\xb4\xf1?\xe1\xaf\b\x1b\xaa\xaa\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb5\xfa\x81\x84\xe4>\xd3\u0e2b\x91!da\xb3R\x8d\x84\xfd\t\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xb5\xfb~\xa2\xdd\xc1Y\x8bfz\x9dW\xdd9\xe8Z8\xf3]V\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb6\x00B\x97R\xf3\x99\xc8\r\a4tK\xae\n\x02.\xcag\u0189\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb6\x00\xfe\xabJ\xa9lSu\x04\xd9`W\"1Ai,\x19:\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xb6\x04|\u07d3-\xb3\xe4\x04_Iv\x12#AS~\u0556\x1e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb6\x15\xe9@\x14>\xb5\u007f\x87X\x93\xbc\x98\xa6\x1b=a\x8c\x1e\x8c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xb6\x1c4\xfc\xac\xdap\x1aZ\xa8p$Y\u07b0\u4b83\x8d\xf8\x8a\aiZ\x92\xc2\ro\xe0\x00\x00\xe0\x94\xb60d\xbd3U\xe6\xe0~-7p$\x12Z3wlJ\xfa\x8a\b7Z*\xbc\xca$@\x00\x00\u07d4\xb65\xa4\xbcq\xfb(\xfd\xd5\xd2\xc3\"\x98:V\u0084Bni\x89\t79SM(h\x00\x00\u07d4\xb6F\u07d8\xb4\x94BtkaR\\\x81\xa3\xb0K\xa3\x10bP\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xb6YA\xd4LP\xd2Ffg\r6Gf\xe9\x91\xc0.\x11\u0089 \x86\xac5\x10R`\x00\x00\xe0\x94\xb6[\u05c0\xc7CA\x15\x16 'VR#\xf4NT\x98\xff\x8c\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\xb6d\x11\xe3\xa0-\xed\xb7&\xfay\x10}\xc9\v\xc1\xca\xe6MH\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb6fu\x14.1\x11\xa1\xc2\xea\x1e\xb2A\x9c\xfaB\xaa\xf7\xa24\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xb6o\x92\x12K^c\x03XY\xe3\x90b\x88i\xdb\u07a9H^\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xb6rsJ\xfc\xc2$\xe2\xe6\t\xfcQ\xd4\xf0Ys'D\xc9H\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\xe0\x94\xb6w\x1b\v\xf3B\u007f\x9a\xe7\xa9>|.a\xeec\x94\x1f\xdb\b\x8a\x03\xfb&i)T\xbf\xc0\x00\x00\u07d4\xb6z\x80\xf1p\x19}\x96\xcd\xccJ\xb6\u02e6'\xb4\xaf\xa6\xe1,\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xb6\x88\x99\xe7a\rL\x93\xa255\xbc\xc4H\x94[\xa1fo\x1c\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb6\xa8)3\xc9\xea\u06bd\x98\x1e]m`\xa6\x81\x8f\xf8\x06\xe3k\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xb6\xaa\u02cc\xb3\v\xab*\xe4\xa2BF&\xe6\xe1+\x02\xd0F\x05\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xb6\xb3J&?\x10\xc3\xd2\xec\xeb\n\xccU\x9a{*\xb8\\\xe5e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb6\xbf\xe1\xc3\xef\x94\xe1\x84o\xb9\xe3\xac\xfe\x9bP\xc3\xe9\x06\x923\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xb6\xcdt2\xd5\x16\x1b\xe7\x97h\xadE\xde>Dz\a\x98 c\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb6\xceM\xc5`\xfcs\xdci\xfbzb\xe3\x88\xdb~r\xeavO\x894]\xf1i\xe9\xa3X\x00\x00\u07d4\xb6\xde\u03c2\x96\x98\x19\xba\x02\xde)\xb9\xb5\x93\xf2\x1bd\xee\xda\x0f\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\xe0\x94\xb6\xe6\xc3\"+ko\x9b\xe2\x87]*\x89\xf1'\xfbd\x10\x0f\xe2\x8a\x01\xb2\x1dS#\xcc0 \x00\x00\u07d4\xb6\xe8\xaf\xd9=\xfa\x9a\xf2\u007f9\xb4\xdf\x06\ag\x10\xbe\xe3\u07eb\x89\x01Z\xf1\u05cbX\xc4\x00\x00\xe0\x94\xb6\xf7\x8d\xa4\xf4\xd0A\xb3\xbc\x14\xbc[\xa5\x19\xa5\xba\f2\xf1(\x8a$}\xd3,?\xe1\x95\x04\x80\x00\xe0\x94\xb6\xfb9xbP\b\x14&\xa3B\xc7\rG\xeeR\x1e[\xc5c\x8a\x03-&\xd1.\x98\v`\x00\x00\u07d4\xb7\r\xba\x93\x91h+J6Nw\xfe\x99%c\x01\xa6\xc0\xbf\x1f\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\x16#\xf3Q\a\xcft1\xa8?\xb3\xd2\x04\xb2\x9e\u0c67\xf4\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xb7\x1a\x13\xba\x8e\x95\x16{\x803\x1bR\u059e7\x05O\xe7\xa8&\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\x1bb\xf4\xb4H\xc0+\x12\x01\xcb^9J\xe6'\xb0\xa5`\xee\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xb7\" \xad\xe3d\xd06\x9f--\xa7\x83\xcaGM{\x9b4\u0389\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\xe0\x94\xb7#\r\x1d\x1f\xf2\xac\xa3f\x969\x14\xa7\x9d\xf9\xf7\xc5\xea,\x98\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\xb7$\n\U000af433<\b\xae\x97d\x10>5\xdc\xe3c\x84(\x8a\x01\xca\xdd/\xe9hnc\x80\x00\u07d4\xb7'\xa9\xfc\x82\xe1\xcf\xfc\\\x17_\xa1HZ\x9b\xef\xa2\u037d\u04496'\xe8\xf7\x127<\x00\x00\u07d4\xb7,*\x01\x1c\r\xf5\x0f\xbbn(\xb2\n\xe1\xaa\xd2\x17\x88g\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb78-7\xdb\x03\x98\xacrA\f\xf9\x81=\xe9\xf8\xe1\uc36d\x8966\xc2^f\xec\xe7\x00\x00\u07d4\xb7;O\xf9\x9e\xb8\x8f\u061b\vmW\xa9\xbc3\x8e\x88o\xa0j\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xb7=jwU\x9c\x86\xcfet$)\x039K\xac\xf9n5p\x89\x04\xf1\xa7|\xcd;\xa0\x00\x00\u07d4\xb7Cr\xdb\xfa\x18\x1d\xc9$/9\xbf\x1d71\xdf\xfe+\xda\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7G\x9d\xabP\"\xc4\xd5\u06ea\xf8\xde\x17\x1bN\x95\x1d\u0464W\x89\x04V9\x18$O@\x00\x00\u07d4\xb7I\xb5N\x04\u0571\x9b\xdc\xed\xfb\x84\xdaw\x01\xabG\x8c'\xae\x89\x91Hx\xa8\xc0^\xe0\x00\x00\u07d4\xb7N\xd2f`\x01\xc1c3\xcfz\xf5\x9eJ=H`6;\x9c\x89\n~\xbd^Cc\xa0\x00\x00\u07d4\xb7QI\xe1\x85\xf6\xe3\x92pWs\x90s\xa1\x82*\xe1\xcf\r\xf2\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xb7S\xa7_\x9e\xd1\v!d:\n=\xc0Qz\xc9k\x1a@h\x89\x15\xc8\x18[,\x1f\xf4\x00\x00\xe0\x94\xb7V\xadR\xf3\xbft\xa7\xd2LgG\x1e\b\x87Ci6PL\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xb7Wn\x9d1M\xf4\x1e\xc5Pd\x94):\xfb\x1b\xd5\xd3\xf6]\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb7X\x89o\x1b\xaa\x86O\x17\xeb\xed\x16\xd9S\x88o\xeeh\xaa\xe6\x8965\u026d\xc5\u07a0\x00\x00\u0794\xb7h\xb5#N\xba:\x99h\xb3Mm\xdbH\x1c\x84\x19\xb3e]\x88\xcf\xceU\xaa\x12\xb3\x00\x00\u07d4\xb7\x82\xbf\xd1\xe2\xdep\xf4gdo\x9b\xc0\x9e\xa5\xb1\xfc\xf4P\xaf\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xb7\xa2\xc1\x03r\x8bs\x05\xb5\xaen\x96\x1c\x94\xee\x99\xc9\xfe\x8e+\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xb7\xa3\x1a|8\xf3\xdb\t2.\xae\x11\xd2'!A\xea\"\x99\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7\xa6y\x1c\x16\xebN!b\xf1Ke7\xa0+=c\xbf\xc6\x02\x89*Rc\x91\xac\x93v\x00\x00\u07d4\xb7\xa7\xf7|4\x8f\x92\xa9\xf1\x10\fk\xd8)\xa8\xacm\u007f\u03d1\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xb7\xc0w\x94ft\xba\x93A\xfbLtz]P\xf5\xd2\xdad\x15\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xb7\xc0\xd0\xcc\vM4-@b\xba\xc6$\xcc\xc3\xc7\f\xc6\xda?\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xb7\xc9\xf1+\x03\x8esCm\x17\xe1\xc1/\xfe\x1a\xec\u0373\xf5\x8c\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\xb7\xcck\x1a\xcc2\u0632\x95\xdfh\xed\x9d^`\xb8\xf6L\xb6{\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xb7\xcehK\t\xab\xdaS8\x9a\x87Si\xf7\x19X\xae\xac;\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\xb7\xd1.\x84\xa2\xe4\u01264Z\xf1\xdd\x1d\xa9\xf2PJ*\x99n\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xb7\xd2R\xee\x94\x02\xb0\xee\xf1D)_\x0ei\xf0\xdbXl\bq\x89#\xc7W\a+\x8d\xd0\x00\x00\xe0\x94\xb7\u0541\xfe\n\xf1\xec8?;\xce\x00\xaf\x91\x99\xf3\xcf_\xe0\xcc\xe2\x8c\xd1J\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\xb8R\x18\xf3B\xf8\x01.\u069f'Nc\xce!R\xb2\xdc\xfd\xab\x89\xa8\r$g~\xfe\xf0\x00\x00\u07d4\xb8UP\x10wn<\\\xb3\x11\xa5\xad\xee\xfe\x9e\x92\xbb\x9ad\xb9\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xb8_&\xdd\x0er\xd9\u009e\xba\xf6\x97\xa8\xafwG,+X\xb5\x8a\x02\x85\x19\xac\xc7\x19\fp\x00\x00\u07d4\xb8_\xf0>{_\xc4\"\x98\x1f\xae^\x99A\xda\xcb\u06bau\x84\x89Hz\x9a0E9D\x00\x00\xe0\x94\xb8f\a\x02\x1bb\xd3@\xcf&R\xf3\xf9_\xd2\xdcgi\x8b\u07ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xb8}\xe1\xbc\u0492i\xd5!\xb8v\x1c\u00dc\xfbC\x19\xd2\xea\u054965\u026d\xc5\u07a0\x00\x00\u07d4\xb8\u007fSv\xc2\xde\vl\xc3\xc1y\xc0`\x87\xaaG=kFt\x89Hz\x9a0E9D\x00\x00\u07d4\xb8\x84\xad\u060d\x83\xdcVJ\xb8\xe0\xe0,\xbd\xb69\x19\xae\xa8D\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\x8a7\xc2\u007fx\xa6\x17\xd5\xc0\x91\xb7\u0577:7a\xe6_*\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\x94x\"\u056c\u79ad\x83&\xe9T\x96\"\x1e\v\xe6\xb7=\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb8\x9c\x03n\xd7\u0112\x87\x99!\xbeA\xe1\f\xa1i\x81\x98\xa7L\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xb8\x9fF2\xdfY\t\xe5\x8b*\x99d\xf7O\xeb\x9a;\x01\xe0\u014a\x04\x88u\xbc\xc6\xe7\xcb\xeb\x80\x00\u07d4\xb8\xa7\x9c\x84\x94^G\xa9\xc3C\x86\x83\u05b5\x84,\xffv\x84\xb1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xb8\xa9y5'Y\xba\t\xe3Z\xa5\x93]\xf1u\xbf\xf6x\xa1\b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xb8\xab9\x80[\xd8!\x18Ol\xbd=$s4{\x12\xbf\x17\\\x89\x06hZ\xc1\xbf\xe3,\x00\x00\xe0\x94\xb8\xac\x11}\x9f\r\xba\x80\x90\x14E\x82:\x92\x11\x03\xa51o\x85Zew\x9d\x1b\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4\xb9\xe9\f\x11\x92\xb3\xd5\xd3\xe3\xab\a\x00\xf1\xbfe_]\xd44z\x89\x1b\x19\xe5\vD\x97|\x00\x00\u07d4\xb9\xfd83\xe8\x8e|\xf1\xfa\x98y\xbd\xf5Z\xf4\xb9\x9c\xd5\xce?\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xba\x02I\xe0\x1d\x94[\xef\x93\xee^\xc6\x19%\xe0<\\\xa5\t\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xba\x0f9\x02;\xdb)\xeb\x18b\xa9\xf9\x05\x9c\xab]0nf/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\x10\xf2vB\x90\xf8uCCr\xf7\x9d\xbfq8\x01\u02ac\x01\x893\xc5I\x901r\f\x00\x00\u07d4\xba\x151\xfb\x9ey\x18\x96\xbc\xf3\xa8\x05X\xa3Y\xf6\xe7\xc1D\xbd\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xba\x17m\xbe2I\xe3E\xcdO\xa9g\xc0\xed\x13\xb2LG\u5189\x15\xae\xf9\xf1\xc3\x1c\u007f\x00\x00\xe0\x94\xba\x1f\x0e\x03\u02da\xa0!\xf4\xdc\xeb\xfa\x94\xe5\u0209\xc9\u01fc\x9e\x8a\x06\u0450\xc4u\x16\x9a \x00\x00\u07d4\xba\x1f\xca\xf2#\x93~\xf8\x9e\x85gU\x03\xbd\xb7\xcaj\x92\x8bx\x89\"\xb1\xc8\xc1\"z\x00\x00\x00\xe0\x94\xba$\xfcCgS\xa79\xdb,\x8d@\xe6\xd4\xd0LR\x8e\x86\xfa\x8a\x02\xc0\xbb=\xd3\fN \x00\x00\u07d4\xbaB\xf9\xaa\xceL\x18E\x04\xab\xf5BWb\xac\xa2oq\xfb\u0709\x02\a\a}\u0627\x9c\x00\x00\u07d4\xbaF\x9a\xa5\u00c6\xb1\x92\x95\u0521\xb5G;T\x03S9\f\x85\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbad@\xae\xb3s{\x8e\xf0\xf1\xaf\x9b\f\x15\xf4\xc2\x14\xff\xc7\u03c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbam1\xb9\xa2a\xd6@\xb5\u07a5\x1e\xf2\x16,1\t\xf1\uba0a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xbap\xe8\xb4u\x9c\f<\x82\xcc\x00\xacN\x9a\x94\xdd[\xaf\xb2\xb8\x890C\xfa3\xc4\x12\xd7\x00\x00\u07d4\xba\x8ac\xf3\xf4\r\u4a03\x88\xbcP!/\xea\x8e\x06O\xbb\x86\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\x8eF\u059d.#C\xd8l`\xd8,\xf4, A\xa0\xc1\u0089\x05k\xc7^-c\x10\x00\x00\u07d4\xba\xa4\xb6L+\x15\xb7\x9f_ BF\xfdp\xbc\xbd\x86\xe4\xa9*\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xba\u0212,J\xcc},\xb6\xfdY\xa1N\xb4\\\xf3\xe7\x02!K\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xba\xd25\xd5\b]\u01f0h\xa6|A&w\xb0>\x186\x88L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xba\xd4B^\x17\x1c>r\x97^\xb4j\xc0\xa0\x15\xdb1Z]\x8f\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xba\xdc*\xef\x9fYQ\xa8\u05cak5\xc3\u0433\xa4\xe6\xe2\xe79\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xba\xdeCY\x9e\x02\xf8OL0\x14W\x1c\x97k\x13\xa3le\xab\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xba\xe9\xb8/r\x99c\x14\be\x9d\xd7N\x89\x1c\xb8\xf3\x86\x0f\xe5\x89j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xbb\x03f\xa7\u03fd4E\xa7\r\xb7\xfeZ\xe3H\x85uO\xd4h\x8a\x01M\xef,B\xeb\xd6@\x00\x00\u07d4\xbb\aj\xac\x92 \x80i\xea1\x8a1\xff\x8e\xeb\x14\xb7\xe9\x96\xe3\x89\b\x13\xcaV\x90m4\x00\x00\u07d4\xbb\bW\xf1\xc9\x11\xb2K\x86\u0227\x06\x81G?\u6aa1\xcc\xe2\x89\x05k\xc7^-c\x10\x00\x00\u0794\xbb\x19\xbf\x91\u02edt\xcc\xeb_\x81\x1d\xb2~A\x1b\xc2\xea\x06V\x88\xf4?\xc2\xc0N\xe0\x00\x00\xe0\x94\xbb'\u01a7\xf9\x10uGZ\xb2)a\x90@\xf8\x04\xc8\xeczj\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xbb7\x1cr\xc9\xf01l\xea+\xd9\xc6\xfb\xb4\a\x9ewT)\xef\x89_h\xe8\x13\x1e\u03c0\x00\x00\xe0\x94\xbb;\x01\v\x18\xe6\xe2\xbe\x115\x87\x10&\xb7\xba\x15\xea\x0f\xde$\x8a\x02 |\x800\x9bwp\x00\x00\xe0\x94\xbb;\x90\x05\xf4o\xd2\xca;0\x16%\x99\x92\x8cw\xd9\xf6\xb6\x01\x8a\x01\xb1\xae\u007f+\x1b\xf7\xdb\x00\x00\u07d4\xbb?\xc0\xa2\x9c\x03Mq\b\x12\xdc\xc7u\xc8\u02b9\u048diu\x899\xd4\xe8D\xd1\xcf_\x00\x00\u07d4\xbbH\xea\xf5\x16\xce-\xec>A\xfe\xb4\xc6y\xe4\x95vA\x16O\x89\xcf\x15&@\xc5\xc80\x00\x00\u07d4\xbbKJKT\x80p\xffAC,\x9e\b\xa0\xcao\xa7\xbc\x9fv\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\u07d4\xbbV\xa4\x04r<\xff \xd0hT\x88\xb0Z\x02\xcd\xc3Z\xac\xaa\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbba\x8e%\"\x1a\u0667@\xb2\x99\xed\x14\x06\xbc94\xb0\xb1m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbba\xa0K\xff\xd5|\x10G\rE\u00d1\x03\xf6FP4v\x16\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbbh#\xa1\xbd\x81\x9f\x13QU8&J-\xe0R\xb4D\"\b\x89\x01ch\xffO\xf9\xc1\x00\x00\u07d4\xbbl(J\xac\x8ai\xb7\\\u0770\x0f(\xe1EX;V\xbe\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xbbu\xcbPQ\xa0\xb0\x94KFs\xcau*\x97\x03\u007f|\x8c\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbb\x99;\x96\xee\x92Z\xda}\x99\u05c6W=?\x89\x18\f\u3a89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbb\xa3\u0180\x04$\x8eH\x95s\xab\xb2t6w\x06k$\u0227\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbb\xa4\xfa\xc3\xc4 9\xd8(\xe7B\xcd\xe0\xef\xff\xe7t\x94\x1b9\x89lj\u04c2\xd4\xfba\x00\x00\u07d4\xbb\xa8\xab\"\xd2\xfe\xdb\xcf\xc6?hL\b\xaf\xdf\x1c\x17P\x90\xb5\x89\x05_)\xf3~N;\x80\x00\u07d4\xbb\xa9v\xf1\xa1!_u\x12\x87\x18\x92\xd4_pH\xac\xd3V\u0209lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbb\xab\x00\v\x04\b\xed\x01Z7\xc0GG\xbcF\x1a\xb1N\x15\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xbb\xab\xf6d;\xebK\xd0\x1c\x12\v\xd0Y\x8a\t\x87\xd8)g\u0449\xb52\x81x\xad\x0f*\x00\x00\u07d4\xbb\xb4\xee\x1d\x82\xf2\xe1VD,\xc938\xa2\xfc(o\xa2\x88d\x89JD\x91\xbdm\xcd(\x00\x00\u07d4\xbb\xb5\xa0\xf4\x80,\x86H\x00\x9e\x8ai\x98\xaf5,\u0787TO\x89\x05-T(\x04\xf1\xce\x00\x00\u07d4\xbb\xb6C\xd2\x18{6J\xfc\x10\xa6\xfd6\x8d}U\xf5\r\x1a<\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbb\xb8\xff\xe4?\x98\u078e\xae\x18F#\xaeRd\xe4$\u0438\u05c9\x05\xd5?\xfd\xe9(\b\x00\x00\u07d4\xbb\xbdn\u02f5u(\x91\xb4\u03b3\xcc\xe7:\x8fGpY7o\x89\x01\xf3\x99\xb1C\x8a\x10\x00\x00\u07d4\xbb\xbf9\xb1\xb6y\x95\xa4\"APO\x97\x03\u04a1JQV\x96\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xbb\xc8\xea\xffc~\x94\xfc\u014d\x91\xdb\\\x89\x12\x1d\x06\xe1/\xff\x98\x80\x00\u07d4\xbc\u065e\xdc!`\xf2\x10\xa0^:\x1f\xa0\xb0CL\xed\x00C\x9b\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbc\u07ec\xb9\xd9\x02<4\x17\x18.\x91\x00\xe8\xea\x1d73\x93\xa3\x89\x034-`\xdf\xf1\x96\x00\x00\u07d4\xbc\xe1>\"2*\u03f3U\xcd!\xfd\r\xf6\f\xf9:\xdd&\u0189\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xbc\xe4\x04u\xd3E\xb0q-\xeep=\x87\xcdvW\xfc\u007f;b\x8a\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\xbc\xed\xc4&|\u02c9\xb3\x1b\xb7d\xd7!\x11q\x00\x8d\x94\xd4M\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbc\xfc\x98\xe5\xc8+j\xdb\x18\n?\xcb\x12\v\x9av\x90\xc8j?\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbd\x04;g\xc6>`\xf8A\xcc\xca\x15\xb1)\xcd\xfee\x90\xc8\xe3\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbd\x04\u007f\xf1\xe6\x9c\u01b2\x9a\xd2d\x97\xa9\xa6\xf2z\x90?\xc4\u0749.\xe4IU\b\x98\xe4\x00\x00\u07d4\xbd\b\xe0\xcd\xde\xc0\x97\xdby\x01\ua05a=\x1f\xd9\u0789Q\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbd\t\x12l\x89\x1cJ\x83\x06\x80Y\xfe\x0e\x15ylFa\xa9\xf4\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xbd\f\\\u05d9\xeb\u0106B\xef\x97\xd7N\x8eB\x90d\xfe\u4489\x11\xac(\xa8\xc7)X\x00\x00\u07d4\xbd\x17\xee\xd8+\x9a%\x92\x01\x9a\x1b\x1b<\x0f\xba\xd4\\@\x8d\"\x89\r\x8drkqw\xa8\x00\x00\u07d4\xbd\x18\x037\v\u0771)\xd29\xfd\x16\xea\x85&\xa6\x18\x8a\u5389\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xbd+p\xfe\xcc7d\x0fiQO\xc7\xf3@IF\xaa\xd8k\x11\x89A\rXj \xa4\xc0\x00\x00\u07d4\xbd0\x97\xa7\x9b<\r.\xbf\xf0\xe6\xe8j\xb0\xed\xad\xbe\xd4p\x96\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xbd2]@)\xe0\xd8r\x9fm9\x9cG\x82$\xae\x9ez\xe4\x1e\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xbdC*9\x16$\x9bG$):\xf9\x14nI\xb8(\n\u007f*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xbdG\xf5\xf7n;\x93\x0f\xd9HR\t\xef\xa0\xd4v=\xa0uh\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbdK`\xfa\xect\n!\xe3\a\x13\x91\xf9j\xa54\xf7\xc1\xf4N\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xbdK\u0571\"\xd8\xef{|\x8f\x06gE\x03 \xdb!\x16\x14.\x89 \x86\xac5\x10R`\x00\x00\u07d4\xbdQ\xee.\xa1C\u05f1\u05b7~~D\xbd\xd7\xda\x12\U00105b09G~\x06\u0332\xb9(\x00\x00\u07d4\xbdY\tN\aO\x8dy\x14*\xb1H\x9f\x14\x8e2\x15\x1f \x89\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbdZ\x8c\x94\xbd\x8b\xe6G\x06D\xf7\f\x8f\x8a3\xa8\xa5\\cA\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbd^G:\xbc\xe8\xf9zi2\xf7|/\xac\xaf\x9c\xc0\xa0\x05\x14\x89<\x92X\xa1\x06\xa6\xb7\x00\x00\u07d4\xbd_F\u02ab,=K(\x93\x96\xbb\xb0\u007f *\x06\x11>\xd4\xc3\xfb\xa1\xa8\x91;\x19@~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\x9eV\xe9\x02\xf4\xbe\x1f\xc8v\x8d\x808\xba\xc6>*\u02ff\x8e\x8965f3\xeb\xd8\xea\x00\x00\u07d4\xbd\xa4\xbe1~~K\xed\x84\xc0I^\xee2\xd6\a\xec8\xcaR\x89}2'yx\xefN\x80\x00\u07d4\xbd\xb6\v\x82:\x11s\xd4Z\a\x92$_\xb4\x96\xf1\xfd3\x01\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xba\xf6CM@\xd65[\x1e\x80\xe4\f\u012b\x9ch\xd9a\x16\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xbd\xc0,\xd43\f\x93\xd6\xfb\xdaOm\xb2\xa8]\xf2/C\xc23\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xc4aF+c\"\xb4b\xbd\xb3?\"y\x9e\x81\b\xe2A}\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xbd\xc79\xa6\x99p\v.\x8e,JL{\x05\x8a\x0eQ=\u07be\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbd\xc7Hs\xaf\x92+\x9d\xf4t\x85;\x0f\xa7\xff\v\xf8\xc8&\x95\x89\xd8\xc9F\x00c\xd3\x1c\x00\x00\u07d4\xbd\xca*\x0f\xf3E\x88\xafb_\xa8\xe2\x8f\xc3\x01Z\xb5\xa3\xaa\x00\x89~\xd7?w5R\xfc\x00\x00\u07d4\xbd\xd3%N\x1b:m\xc6\xcc,i}Eq\x1a\xca!\xd5\x16\xb2\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbd\u07e3M\x0e\xbf\x1b\x04\xafS\xb9\x9b\x82IJ\x9e=\x8a\xa1\x00\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xbd\xe4\xc7?\x96\x9b\x89\xe9\u03aef\xa2\xb5\x18DH\x0e\x03\x8e\x9a\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbd\xe9xj\x84\xe7[H\xf1\x8erm\u05cdp\xe4\xaf>\xd8\x02\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4\xbd\xed\x11a/\xb5\xc6\u0699\xd1\xe3\x0e2\v\xc0\x99Tf\x14\x1e\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xbd\xed~\a\xd0q\x1ehM\xe6Z\u0232\xabW\xc5\\\x1a\x86E\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\xbd\xf6\x93\xf83\xc3\xfeG\x17S\x18G\x88\xebK\xfeJ\xdc?\x96\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbd\xf6\xe6\x8c\f\xd7X@\x80\xe8G\xd7,\xbb#\xaa\xd4j\xeb\x1d\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbe\n/8_\t\xdb\xfc\xe9g2\xe1+\xb4\n\xc3I\x87\x1b\xa8\x89WL\x11^\x02\xb8\xbe\x00\x00\u07d4\xbe\f*\x80\xb9\xde\bK\x17(\x94\xa7l\xf4szOR\x9e\x1a\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xbe\x1c\xd7\xf4\xc4r\a\th\xf3\xbd\xe2h6k!\xee\xea\x83!\x89\xe9\x1a|\u045f\xa3\xb0\x00\x00\u07d4\xbe#F\xa2\u007f\xf9\xb7\x02\x04OP\r\xef\xf2\xe7\xff\xe6\x82EA\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xbe$q\xa6\u007f`G\x91\x87r\xd0\xe3h9%^\xd9\u0591\xae\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xbe+\"\x80R7h\xea\x8a\xc3\\\xd9\xe8\x88\xd6\nq\x93\x00\u0509lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbe+2nx\xed\x10\xe5P\xfe\xe8\xef\xa8\xf8\a\x03\x96R/Z\x8a\bW\xe0\xd6\xf1\xdav\xa0\x00\x00\xe0\x94\xbe0Zyn3\xbb\xf7\xf9\xae\xaee\x12\x95\x90f\xef\xda\x10\x10\x8a\x02M\xceT\xd3J\x1a\x00\x00\x00\u07d4\xbeG\x8e\x8e=\xdek\xd4\x03\xbb-\x1ce|C\x10\xee\x19'#\x89\x1a\xb2\xcf|\x9f\x87\xe2\x00\x00\u07d4\xbeN}\x98?.*ck\x11\x02\xecp9\xef\xeb\xc8B\u9349\x03\x93\xef\x1aQ'\xc8\x00\x00\u07d4\xbeO\xd0sap\"\xb6\u007f\\\x13I\x9b\x82\u007fv69\xe4\xe3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbeRZ3\xea\x91aw\xf1r\x83\xfc\xa2\x9e\x8b5\v\u007fS\v\x89\x8f\x01\x9a\xafF\xe8x\x00\x00\u07d4\xbeS2/C\xfb\xb5\x84\x94\xd7\xcc\xe1\x9d\xda'+$P\xe8'\x89\n\xd7\u03afB\\\x15\x00\x00\u07d4\xbeS\x82F\xddNo\f \xbfZ\xd17<;F:\x13\x1e\x86\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbeZ`h\x99\x98c\x9a\xd7[\xc1\x05\xa3qt>\xef\x0fy@\x89\x1b2|s\xe1%z\x00\x00\u07d4\xbe\\\xba\x8d7By\x86\xe8\xca&\x00\xe8X\xbb\x03\xc3YR\x0f\x89\xa00\xdc\xeb\xbd/L\x00\x00\u07d4\xbe`\x03~\x90qJK\x91~a\xf1\x93\xd84\x90g\x03\xb1:\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xbec:77\xf6\x849\xba\xc7\xc9\nR\x14 X\ue38ao\x894\n\xad!\xb3\xb7\x00\x00\x00\xe0\x94\xbee\x9d\x85\xe7\xc3O\x883\xea\u007fH\x8d\xe1\xfb\xb5\xd4\x14\x9b\xef\x8a\x01\xeb\xd2:\xd9\u057br\x00\x00\u07d4\xbes'M\x8cZ\xa4J<\xbe\xfc\x82c\xc3{\xa1!\xb2\n\u04c9\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xbe\x86\u0430C\x84\x19\u03b1\xa081\x927\xbaR\x06\xd7.F\x8964\xfb\x9f\x14\x89\xa7\x00\x00\u07d4\xbe\x8d\u007f\x18\xad\xfe]l\xc7u9I\x89\xe1\x93\f\x97\x9d\x00}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbe\x91\x86\xc3JRQJ\xbb\x91\a\x86\x0fgO\x97\xb8!\xbd[\x89\x1b\xa0\x1e\xe4\x06\x03\x10\x00\x00\u07d4\xbe\x93W\x93\xf4[p\xd8\x04]&T\xd8\xdd:\xd2K[a7\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xbe\x98\xa7\u007f\xd4\x10\x97\xb3OY\xd7X\x9b\xaa\xd0!e\x9f\xf7\x12\x890\xca\x02O\x98{\x90\x00\x00\u07d4\xbe\x9b\x8c4\xb7\x8e\xe9G\xff\x81G.\xdaz\xf9\xd2\x04\xbc\x84f\x89\b!\xab\rD\x14\x98\x00\x00\u07d4\xbe\xa0\r\xf1pg\xa4:\x82\xbc\x1d\xae\xca\xfbl\x140\x0e\x89\xe6\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xbe\xa0\xaf\xc9:\xae!\b\xa3\xfa\xc0Yb;\xf8o\xa5\x82\xa7^\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xbe\xb35\x8cP\u03dfu\xff\xc7mD<,\u007fU\aZ\x05\x89\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xbe\xb4\xfd1UYC`E\u0739\x9dI\xdc\xec\x03\xf4\fB\u0709lk\x93[\x8b\xbd@\x00\x00\u07d4\xbe\xc2\xe6\xde9\xc0|+\xaeUj\u03fe\xe2\xc4r\x8b\x99\x82\xe3\x89\x1f\x0f\xf8\xf0\x1d\xaa\xd4\x00\x00\u07d4\xbe\xc6d\x0fI\t\xb5\x8c\xbf\x1e\x80cB\x96\x1d`u\x95\tl\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xbe\xc8\xca\xf7\xeeIF\x8f\xeeU.\xff:\xc5#N\xb9\xb1}B\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbe\xce\xf6\x1c\x1cD+\xef|\xe0Ks\xad\xb2I\xa8\xba\x04~\x00\x896;V\u00e7T\xc8\x00\x00\u0794\xbe\xd4d\x9d\xf6F\u2052)\x03-\x88hUo\xe1\xe0S\u04c8\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\xbe\xd4\xc8\xf0\x06\xa2|\x1e_|\xe2\x05\xdeu\xf5\x16\xbf\xb9\xf7d\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xbe\xe8\u0430\bB\x19T\xf9-\x00\r9\x0f\xb8\xf8\xe6X\xea\xee\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbe\xec\u05af\x90\f\x8b\x06J\xfc\xc6\a?-\x85\u055a\xf1\x19V\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xbe\xef\x94!8y\xe0&\"\x14+\xeaa)\tx\x93\x9a`\u05ca\x016\x85{2\xad\x86\x04\x80\x00\xe0\x94\xbe\xf0}\x97\xc3H\x1f\x9dj\xee\x1c\x98\xf9\xd9\x1a\x18\n2D+\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xbe\xfbD\x8c\f_h?\xb6~\xe5p\xba\xf0\xdbV\x86Y\x97Q\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xbf\x05\a\f,4!\x93\x11\xc4T\x8b&\x14\xa48\x81\r\xedm\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbf\x05\xff^\xcf\r\xf2\u07c8wY\xfb\x82t\xd928\xac&}\x89+^:\xf1k\x18\x80\x00\x00\xe0\x94\xbf\t\xd7pH\xe2p\xb6b3\x0e\x94\x86\xb3\x8bC\xcdx\x14\x95\x8a\\S\x9b{\xf4\xff(\x80\x00\x00\u07d4\xbf\x17\xf3\x97\xf8\xf4o\x1b\xaeE\u0447\x14\x8c\x06\xee\xb9Y\xfaM\x896I\u0156$\xbb0\x00\x00\u07d4\xbf\x186A\xed\xb8\x86\xce`\xb8\x19\x02a\xe1OB\xd9<\xce\x01\x89\x01[5W\xf1\x93\u007f\x80\x00\u07d4\xbf*\xeaZ\x1d\xcfn\u04f5\xe829D\xe9\x83\xfe\xdf\u046c\xfb\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xbf@\x96\xbcT}\xbf\xc4\xe7H\t\xa3\x1c\x03\x9e{8\x9d^\x17\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xbfI\xc1H\x981eg\u0637\t\xc2\xe5\x05\x94\xb3f\xc6\u04cc\x89'\xbf8\xc6TM\xf5\x00\x00\u07d4\xbfLs\xa7\xed\xe7\xb1d\xfe\a!\x14\x846T\xe4\xd8x\x1d\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xbfP\xce.&K\x9f\xe2\xb0h0az\xed\xf5\x02\xb25\x1bE\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbfY\xae\xe2\x81\xfaC\xfe\x97\x19CQ\xa9\x85~\x01\xa3\xb8\x97\xb2\x89 \x86\xac5\x10R`\x00\x00\u07d4\xbfh\u048a\xaf\x1e\xee\xfe\xf6F\xb6^\x8c\xc8\u0450\xf6\xc6\u069c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xbfi%\xc0\aQ\x00\x84@\xa6s\x9a\x02\xbf+l\u06ab^:\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xbfw\x01\xfcb%\u0561x\x15C\x8a\x89A\xd2\x1e\xbc]\x05\x9d\x89e\xea=\xb7UF`\x00\x00\u07d4\xbf\x8b\x80\x05\xd66\xa4\x96d\xf7Bu\xefBC\x8a\xcde\xac\x91\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbf\x92A\x8a\fl1$M\"\x02`\xcb>\x86}\u05f4\xefI\x89\x05i\x00\xd3<\xa7\xfc\x00\x00\u07d4\xbf\x9a\xcdDE\xd9\xc9UF\x89\u02bb\xba\xb1\x88\x00\xff\x17A\u008965\u026d\xc5\u07a0\x00\x00\u07d4\xbf\x9f'\x1fz~\x12\xe3m\xd2\xfe\x9f\xac\xeb\xf3\x85\xfeaB\xbd\x89\x03f\xf8O{\xb7\x84\x00\x00\u07d4\xbf\xa8\xc8X\xdf\x10,\xb1$!\x00\x8b\n1\xc4\xc7\x19\n\xd5`\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xbf\xae\xb9\x10ga}\u03cbD\x17+\x02\xafaVt\x83]\xba\x89\b\xb5\x9e\x88H\x13\b\x80\x00\xe0\x94\xbf\xb0\xea\x02\xfe\xb6\x1d\xec\x9e\"\xa5\a\tY3\x02\x99\xc40r\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xbf\xbc\xa4\x18\xd3R\x9c\xb3\x93\b\x10b\x03*n\x11\x83\u01b2\u070a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xbf\xbe\x05\u831c\xbb\xcc\x0e\x92\xa4\x05\xfa\xc1\xd8]\xe2H\xee$\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xbf\xbf\xbc\xb6V\u0099+\xe8\xfc\u0782\x19\xfb\xc5J\xad\u055f)\x8a\x02\x1e\x18\xd2\xc8!\xc7R\x00\x00\u07d4\xbf\xc5z\xa6f\xfa\u239f\x10zI\xcbP\x89\xa4\xe2!Q\u074965\u026d\xc5\u07a0\x00\x00\u07d4\xbf\u02d70$c\x04p\r\xa9\vAS\xe7\x11Ab.\x1cA\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xbf\xd9<\x90\u009c\a\xbc_\xb5\xfcI\xae\xeaU\xa4\x0e\x13O5\x8a\x05\xed\xe2\x0f\x01\xa4Y\x80\x00\x00\xe0\x94\xbf\xe3\xa1\xfcn$\xc8\xf7\xb3%\x05`\x99\x1f\x93\u02e2\u03c0G\x8a\x10\xf0\xcf\x06M\u0552\x00\x00\x00\u07d4\xbf\u6f30\xf0\xc0xRd3$\xaa]\xf5\xfdb%\xab\xc3\u0289\x04\t\xe5+H6\x9a\x00\x00\u07d4\xbf\xf5\xdfv\x994\xb8\x94<\xa9\x13}\x0e\xfe\xf2\xfen\xbb\xb3N\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xbf\xfbi)$\x1fx\x86\x93'>p\"\xe6\x0e>\xab\x1f\xe8O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\x06O\x1d\x94t\xab\x91]V\x90l\x9f\xb3 \xa2\xc7\t\x8c\x9b\x89\x13h?\u007f<\x15\xd8\x00\x00\u07d4\xc0\a\xf0\xbd\xb6\xe7\x00\x92\x02\xb7\xaf>\xa9\t\x02i|r\x14\x13\x89\xa2\xa0\xe4>\u007f\xb9\x83\x00\x00\u07d4\xc0\n\xb0\x80\xb6C\xe1\u00ba\xe3c\xe0\u0455\xde.\xff\xfc\x1cD\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u0794\xc0 wD\x9a\x13Jz\xd1\xef~M\x92z\xff\xec\ueb75\xae\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xc0$q\xe3\xfc.\xa0S&\x15\xa7W\x1dI2\x89\xc1<6\xef\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0-n\xad\xea\xcf\x1bx\xb3\u0285\x03\\c{\xb1\xce\x01\xf4\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc03\xb12Z\n\xf4Tr\xc2U'\x85;\x1f\x1c!\xfa5\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xc03\xbe\x10\xcbHa;\xd5\xeb\xcb3\xedI\x02\xf3\x8bX0\x03\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc04[3\xf4\x9c\xe2\u007f\xe8,\xf7\xc8M\x14\x1ch\xf5\x90\xcev\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc0=\xe4*\x10\x9bezd\xe9\"$\xc0\x8d\xc1'^\x80\u0672\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0@i\u07f1\x8b\tlxg\xf8\xbe\xe7zm\xc7Gz\xd0b\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xc0A?Z|-\x9aK\x81\b(\x9e\xf6\xec\xd2qx\x15$\xf4\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xc0C\xf2E-\u02d6\x02\xefb\xbd6\x0e\x03=\xd29q\xfe\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc0OK\xd4\x04\x9f\x04F\x85\xb8\x83\xb6)Y\xaec\x1df~5\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\xc0V\u053dk\xf3\u02ec\xace\xf8\xf5\xa0\xe3\x98\v\x85'@\xae\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xc0[t\x06 \xf1s\xf1nRG\x1d\u00cb\x9cQJ\v\x15&\x89\a\x96\xe3\xea?\x8a\xb0\x00\x00\u07d4\xc0i\xef\x0e\xb3B\x99\xab\xd2\xe3-\xab\xc4yD\xb2r3H$\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\xc0l\xeb\xbb\xf7\xf5\x14\x9af\xf7\xeb\x97k>G\xd5e\x16\xda/\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc0r^\u00bd\xc3:\x1d\x82`q\u07a2\x9db\xd48Z\x8c%\x8a\b\xa0\x85\x13F:\xa6\x10\x00\x00\u07d4\xc0~8g\xad\xa0\x96\x80z\x05\x1al\x9c4\xcc;?J\xd3J\x89`\xf0f \xa8IE\x00\x00\u07d4\xc0\x89^\xfd\x05m\x9a:\x81\xc3\xdaW\x8a\xda1\x1b\xfb\x93V\u03c9\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc0\x90\xfe#\xdc\xd8k5\x8c2\xe4\x8d*\xf9\x10$%\x9fef\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc0\x9af\x17*\xea7\r\x9ac\xda\x04\xffq\xff\xbb\xfc\xff\u007f\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\x9e<\xfc\x19\xf6\x05\xff>\xc9\xc9\xc7\x0e%@\xd7\xee\x97Cf\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc0\xa0*\xb9N\xbeV\xd0E\xb4\x1bb\x9b\x98F.:\x02J\x93\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xc0\xa3\x93\b\xa8\x0e\x9e\x84\xaa\xaf\x16\xac\x01\xe3\xb0\x1dt\xbdk-\x89\afM\xddL\x1c\v\x80\x00\u07d4\xc0\xa6\u02edwi*=\x88\xd1A\xefv\x9a\x99\xbb\x9e<\x99Q\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xc0\xa7\xe8C]\xff\x14\xc2Uws\x9d\xb5\\$\u057fW\xa3\u064a\nm\xd9\f\xaeQ\x14H\x00\x00\u07d4\xc0\xae\x14\xd7$\x83./\xce'x\xde\u007f{\x8d\xaf{\x12\xa9>\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc0\xaf\xb7\u0637\x93p\xcf\xd6c\u018c\u01b9p*7\u035e\xff\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc0\xb0\xb7\xa8\xa6\xe1\xac\xdd\x05\xe4\u007f\x94\xc0\x96\x88\xaa\x16\u01ed\x8d\x89\x03{m\x02\xacvq\x00\x00\xe0\x94\xc0\xb3\xf2D\xbc\xa7\xb7\xde[H\xa5>\u06dc\xbe\xab\vm\x88\xc0\x8a\x01;\x80\xb9\x9cQ\x85p\x00\x00\u07d4\xc0\xc0M\x01\x06\x81\x0e>\xc0\xe5J\x19\U000ab157\xe6\x9aW=\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xc0\xca2w\x94.tE\x87K\xe3\x1c\xeb\x90)rqO\x18#\x89\r\x8drkqw\xa8\x00\x00\u07d4\xc0\u02ed<\xcd\xf6T\xda\"\xcb\xcf\\xe\x97\xca\x19U\xc1\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc0\xcb\xf6\x03/\xa3\x9e|F\xffw\x8a\x94\xf7\xd4E\xfe\"\xcf0\x89\x10\xce\x1d=\x8c\xb3\x18\x00\x00\u07d4\xc0\xe0\xb9\x03\b\x8e\fc\xf5=\xd0iWTR\xaf\xf5$\x10\u00c9\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc0\xe4W\xbdV\xec6\xa1$k\xfa20\xff\xf3\x8eY&\xef\"\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xc0\xed\rJ\xd1\r\xe045\xb1S\xa0\xfc%\xde;\x93\xf4R\x04\x89\xabM\xcf9\x9a:`\x00\x00\u07d4\xc0\xf2\x9e\xd0\af\x11\xb5\xe5^\x13\x05G\xe6\x8aH\xe2m\xf5\u4262\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xc1\x13(x#\\]\u06e5\xd9\xf3\"\x8bR6\xe4p \xdco\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc1\x17\r\xba\xad\xb3\xde\xe6\x19\x8e\xa5D\xba\xec\x93%\x18`\xfd\xa5\x89A\rXj \xa4\xc0\x00\x00\xe0\x94\xc1&W=\x87\xb0\x17ZR\x95\xf1\xdd\a\xc5u\u03cc\xfa\x15\xf2\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc1'\xaa\xb5\x90e\xa2\x86D\xa5k\xa3\xf1^.\xac\x13\xda)\x95\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xc1+\u007f@\u07da/{\xf9\x83f\x14\"\xab\x84\xc9\xc1\xf5\bX\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xc1,\xfb{=\xf7\x0f\xce\xca\x0e\xde&5\x00\xe2xs\xf8\xed\x16\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc1/\x88\x1f\xa1\x12\xb8\x19\x9e\xcb\xc7>\xc4\x18W\x90\xe6\x14\xa2\x0f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc18Lnq~\xbeK#\x01NQ\xf3\x1c\x9d\xf7\xe4\xe2[1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc1C\x8c\x99\xddQ\xef\x1c\xa88j\xf0\xa3\x17\xe9\xb0AEx\x88\x89\f\x1d\xaf\x81\u0623\xce\x00\x00\u07d4\xc1c\x12(\xef\xbf*.:@\x92\xee\x89\x00\xc69\xed4\xfb\u02093\xc5I\x901r\f\x00\x00\u07d4\xc1u\xbe1\x94\xe6iB-\x15\xfe\xe8\x1e\xb9\xf2\xc5lg\xd9\u0249\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc1\x82v\x86\xc0\x16\x94\x85\xec\x15\xb3\xa7\xc8\xc0\x15\x17\xa2\x87M\xe1\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xc1\x8a\xb4g\xfe\xb5\xa0\xaa\xdf\xff\x91#\x0f\xf0VFMx\xd8\x00\x89lk\x93[\x8b\xbd@\x00\x00\u0794\xc1\x95\x05CUM\x8aq0\x03\xf6b\xbba,\x10\xadL\xdf!\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xc1\xa4\x1aZ'\x19\x92&\xe4\xc7\xeb\x19\x8b\x03\x1bY\x19o\x98B\x89\nZ\xa8P\t\xe3\x9c\x00\x00\u07d4\xc1\xb2\xa0\xfb\x9c\xadE\xcdi\x91\x92\xcd'T\v\x88\xd38By\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc1\xb2\xaa\x8c\xb2\xbfb\xcd\xc1:G\xec\xc4e\u007f\xac\xaa\x99_\x98\x8967\x93\xfa\x96\u6980\x00\u07d4\xc1\xb5\x00\x01\x1c\xfb\xa9]|\xd66\xe9^l\xbfagFK%\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc1\xb9\xa5pM5\x1c\xfe\x98?y\xab\xee\xc3\u06fb\xae;\xb6)\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc1\xcb\xd2\xe23*RL\xf2\x19\xb1\r\x87\x1c\xcc \xaf\x1f\xb0\xfa\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xc1\xcd\xc6\x01\xf8\x9c\x04(\xb3\x13\x02\u0447\xe0\xdc\b\xad}\x1cW\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xc1\u052f8\xe9\xbay\x90@\x89HI\xb8\xa8!\x93u\xf1\xacx\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc1\xe1@\x9c\xa5,%CQ4\xd0\x06\u00a6\xa8T-\xfbrs\x89\x01\xdd\x1eK\xd8\xd1\xee\x00\x00\u07d4\xc1\xeb\xa5hJ\xa1\xb2L\xbac\x15\x02c\xb7\xa9\x13\x1a\xee\u008d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc1\xec\x81\xdd\x12=K|-\u0674\xd48\xa7\a,\x11\u0707L\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc1\xf3\x9b\xd3]\xd9\xce\xc37\xb9oG\xc6w\x81\x81`\xdf7\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\xc1\xff\xad\a\u06d6\x13\x8cK*S\x0e\xc1\xc7\xde)\xb8\xa0Y,\x88\xf4?\xc2\xc0N\xe0\x00\x00\xe0\x94\xc2\x1f\xa6d:\x1f\x14\xc0)\x96\xadqD\xb7Y&\xe8~\xcbK\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc24\nL\xa9L\x96x\xb7IL<\x85%(\xed\xe5\xeeR\x9f\x89\x02\xa3k\x05\xa3\xfd|\x80\x00\u07d4\xc29\xab\u07ee>\x9a\xf5E\u007fR\xed+\x91\xfd\n\xb4\xd9\xc7\x00\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2;/\x92\x1c\xe4\xa3z%\x9e\u4b4b!X\xd1]fOY\x89\x01`\x89\x95\xe8\xbd?\x80\x00\u07d4\xc2C\x99\xb4\xbf\x86\xf73\x8f\xbfd^;\"\xb0\u0dd79\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2L\u03bc#D\xcc\xe5d\x17\xfbhL\xf8\x16\x13\xf0\xf4\xb9\xbd\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xc2Rf\xc7gf2\xf1>\xf2\x9b\xe4U\ud50a\xddVw\x92\x89Hz\x9a0E9D\x00\x00\u07d4\xc2\\\xf8&U\f\x8e\xaf\x10\xaf\"4\xfe\xf9\x04\u0779R\x13\xbe\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc2f?\x81E\xdb\xfe\xc6\xc6F\xfc\\I\x96\x13E\xde\x1c\x9f\x11\x89%g\xacp9+\x88\x00\x00\u07d4\xc2pEh\x854+d\vL\xfc\x1bR\x0e\x1aTN\xe0\xd5q\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xc2sv\xf4]!\xe1^\xde;&\xf2e_\xce\xe0,\xcc\x0f*\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xc2w\x97q\xf0Smy\xa8p\x8fi1\xab\xc4K05\u964a\x047\u04ca\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xc2\xc1>r\xd2h\xe7\x15\r\u01d9\xe7\xc6\xcf\x03\u0209T\xce\u05c9%\xf2s\x93=\xb5p\x00\x00\u07d4\xc2\xcb\x1a\xda]\xa9\xa0B8s\x81G\x93\xf1aD\xef6\xb2\xf3\x89HU~;p\x17\xdf\x00\x00\u07d4\xc2\xd1w\x8e\xf6\xee_\xe4\x88\xc1E\xf3Xkn\xbb\xe3\xfb\xb4E\x89>\x1f\xf1\xe0;U\xa8\x00\x00\xe0\x94\xc2\xd9\xee\xdb\xc9\x01\x92c\xd9\xd1l\u016e\a-\x1d=\xd9\xdb\x03\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc2\xe0XJq4\x8c\xc3\x14\xb7; )\xb6#\v\x92\u06f1\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc2\xe2\u0518\xf7\r\xcd\bY\xe5\v\x02:q\nmK!3\xbd\x8989\x11\xf0\f\xbc\xe1\x00\x00\u07d4\xc2\xed_\xfd\u046d\xd8U\xa2i/\xe0b\xb5\xd6\x18t#`\u0509A\rXj \xa4\xc0\x00\x00\u07d4\xc2\xee\x91\xd3\xefX\xc9\u0465\x89\x84N\xa1\xae1%\xd6\u017ai\x894\x95tD\xb8@\xe8\x00\x00\u07d4\xc2\xfa\xfd\xd3\n\xcbmg\x06\xe9)<\xb0&A\xf9\xed\xbe\a\xb5\x89Q\x00\x86\vC\x0fH\x00\x00\u07d4\xc2\xfd\v\xf7\xc7%\xef>\x04~Z\xe1\u009f\xe1\x8f\x12\xa7)\x9c\x89Hz\x9a0E9D\x00\x00\u07d4\xc2\xfe}us\x1fcm\xcd\t\xdb\xda\x06q9;\xa0\xc8*}\x89wC\"\x17\xe6\x83`\x00\x00\u07d4\xc3\x10z\x9a\xf32-R8\xdf\x012A\x911b\x959W}\x89\x1a\xb4\xe4d\xd4\x141\x00\x00\xe0\x94\xc3\x11\v\xe0\x1d\xc9sL\xfcn\x1c\xe0\u007f\x87\xd7}\x13E\xb7\xe1\x8a\x01\x0f\f\xe9I\xe0\x0f\x93\x00\x00\u07d4\xc3 8\xcaR\xae\xe1\x97E\xbe\\1\xfc\xdcT\x14\x8b\xb2\xc4\u0409\x02\xb5\xaa\xd7,e \x00\x00\u07d4\xc3%\xc3R\x80\x1b\xa8\x83\xb3\"l_\xeb\r\xf9\xea\xe2\xd6\xe6S\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xc3.\xc7\xe4*\xd1l\xe3\xe2UZ\xd4\xc5C\x06\xed\xa0\xb2gX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc32\xdfP\xb1<\x014\x90\xa5\xd7\xc7]\xbf\xa3f\u0687\xb6\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc3:\u0373\xba\x1a\xab'P{\x86\xb1]g\xfa\xf9\x1e\xcfb\x93\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3>\u0393Z\x8fN\xf98\xea~\x1b\xac\x87\u02d2]\x84\x90\u028a\a\x03\x8c\x16x\x1fxH\x00\x00\u07d4\xc3@\xf9\xb9\x1c&r\x8c1\xd1!\xd5\xd6\xfc;\xb5m=\x86$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3F\xcb\x1f\xbc\xe2\xab(]\x8eT\x01\xf4-\xd7#M7\xe8m\x89\x04\x86\u02d7\x99\x19\x1e\x00\x00\xe0\x94\xc3H=n\x88\xac\x1fJ\xe7<\xc4@\x8dl\x03\xab\xe0\xe4\x9d\u028a\x03\x99\x92d\x8a#\u0220\x00\x00\xe0\x94\xc3H\xfcZF\x13#\xb5{\xe3\x03\u02c96\x1b\x99\x19\x13\xdf(\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xc3N;\xa12.\xd0W\x11\x83\xa2O\x94 N\xe4\x9c\x18fA\x89\x03'\xaf\uf927\xbc\x00\x00\xe0\x94\xc3[\x95\xa2\xa3s|\xb8\xf0\xf5\x96\xb3E$\x87+\xd3\r\xa24\x8a\x01\x98\xbe\x85#^-P\x00\x00\xe0\x94\xc3c\x1cv\x98\xb6\xc5\x11\x19\x89\xbfE''\xb3\xf99Zm\xea\x8a\x02C'X\x96d\x1d\xbe\x00\x00\u07d4\xc3l\vc\xbf\xd7\\/\x8e\xfb\x06\b\x83\xd8h\xcc\xcdl\xbd\xb4\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xc3uk\xcd\xcc~\xect\xed\x89j\xdf\xc35'Y0&n\b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u00c4\xacn\xe2|9\xe2\xf2x\xc2 \xbd\xfa[\xae\xd6&\xd9\u04c9 \x86\xac5\x10R`\x00\x00\u07d4\u00e0F\xe3\u04b2\xbfh\x14\x88\x82n2\xd9\xc0aQ\x8c\xfe\x8c\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\u07d4\u00e9\"j\xe2u\xdf,\xab1+\x91\x10@cJ\x9c\x9c\x9e\xf6\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u00f9(\xa7o\xadex\xf0O\x05U\xe69R\xcd!\xd1R\n\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3\xc2)s)\xa6\xfd\x99\x11~T\xfcj\xf3y\xb4\xd5VT~\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc3\xc3\xc2Q\rg\x80 HZcs]\x13\a\xecL\xa60+\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc3\xcbk6\xafD?,n%\x8bJ9U:\x81\x87G\x81\x1f\x89WG=\x05\u06ba\xe8\x00\x00\xe0\x94\xc3\xdbVW\xbbr\xf1\rX\xf21\xfd\xdf\x11\x98\n\xffg\x86\x93\x8a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\xc3\u06df\xb6\xf4lH\n\xf3De\u05d7S\xb4\xe2\xb7Jg\u038a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc3\xddX\x908\x860;\x92\x86%%z\xe1\xa0\x13\xd7\x1a\xe2\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\xe0G\x1cd\xff5\xfaR2\xcc1!\xd1\u04cd\x1a\x0f\xb7\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\xe2\f\x96\u07cdN8\xf5\v&Z\x98\xa9\x06\xd6\x1b\xc5\x1aq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc3\u31f0<\xe9\\\xcf\xd7\xfaQ\u0744\x01\x83\xbcCS(\t\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc3\xf8\xf6r\x95\xa5\xcd\x04\x93d\xd0]#P&#\xa3\xe5.\x84\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc4\x01\xc4'\xcc\xcf\xf1\r\xec\xb8d /6\xf5\x80\x83\"\xa0\xa8\x89\xb4{Q\xa6\x9c\xd4\x02\x00\x00\u07d4\xc4\b\x8c\x02_>\x85\x01?T9\xfb4@\xa1s\x01\xe5D\xfe\x89~\t\xdbM\x9f?4\x00\x00\u07d4\xc4\x14a\xa3\u03fd2\u0246UU\xa4\x8117\xc0v1#`\x8965\xc6 G9\u0640\x00\u07d4\xc4 8\x8f\xbe\xe8J\xd6V\xddh\xcd\xc1\xfb\xaa\x93\x92x\v4\x89\n-\xcac\xaa\xf4\u0140\x00\u07d4\xc4\"P\xb0\xfeB\xe6\xb7\xdc\xd5\u0210\xa6\xf0\u020f__\xb5t\x89\b\x1e\xe4\x82SY\x84\x00\x00\u07d4\xc4-j\xebq\x0e:P\xbf\xb4Ml1\t)i\xa1\x1a\xa7\xf3\x89\b\"c\xca\xfd\x8c\xea\x00\x00\xe0\x94\xc4@\xc7\xca/\x96Kir\xeffJ\"a\xdd\xe8\x92a\x9d\x9c\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xc4K\xde\xc8\xc3l\\h\xba\xa2\xdd\xf1\xd41i2)rlC\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xc4OJ\xb5\xbc`9|s~\xb0h3\x91\xb63\xf8\xa2G\x1b\x12\x1c\xa4\x89 .h\xf2\u00ae\xe4\x00\x00\u07d4\xc4h\x1es\xbb\x0e2\xf6\xb7& H1\xffi\xba\xa4\x87~2\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xc4k\xbd\xefv\xd4\xca`\xd3\x16\xc0\u007f]\x1ax\x0e;\x16_~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc4}a\v9\x92P\xf7\x0e\xcf\x13\x89\xba\xb6),\x91&O#\x89\x0f\xa7\xe7\xb5\xdf<\xd0\x00\x00\u07d4\u0100;\xb4\a\xc7b\xf9\vu\x96\xe6\xfd\u1513\x1ev\x95\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0106Q\xc1\xd9\xc1k\xffL\x95T\x88l??&C\x1foh\x89#\xab\x95\x99\xc4?\b\x00\x00\u07d4\u0109\xc8?\xfb\xb0%*\xc0\xdb\xe3R\x12\x17c\x0e\x0fI\x1f\x14\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u010bi<\xac\xef\xdb\xd6\xcb]x\x95\xa4.1\x962~&\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0113H\x9eV\u00fd\xd8)\x00}\xc2\xf9VA)\x06\xf7k\xfa\x89\x02\xa7\x91H\x8eqT\x00\x00\u07d4\u0116\u02f0E\x9aj\x01`\x0f\u0149\xa5Z2\xb4T!\u007f\x9d\x89\x0e\u0683\x8cI)\b\x00\x00\u07d4\u011c\xfa\xa9g\xf3\xaf\xbfU\x03\x10a\xfcL\xef\x88\xf8]\xa5\x84\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u0136\xe5\xf0\x9c\xc1\xb9\r\xf0x\x03\xce=M\x13vj\x9cF\xf4\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\u013e\xc9c\b\xa2\x0f\x90\u02b1\x83\x99\u0113\xfd=\x06Z\xbfE\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xc4\xc0\x1a\xfc>\x0f\x04R!\xda\x12\x84\u05c7\x85tD/\xb9\xac\x8a\x01\x92\xb5\u0249\x02J\x19\xc1\xbdo\x12\x80\x00\xe0\x94\xc5\x00\xb7 sN\xd2)8\u05cc^H\xb2\xba\x93g\xa5u\xba\x8a\a\x12\x9e\x1c\xdf7>\xe0\x00\x00\u07d4\xc5\x0f\xe4\x15\xa6A\xb0\x85lNu\xbf\x96\x05\x15D\x1a\xfa5\x8d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5\x13L\xfb\xb1\xdfz \xb0\xedpWb.\xee\u0480\x94}\xad\x89\xcd\xff\x97\xfa\xbc\xb4`\x00\x00\xe0\x94\xc5\x17\xd01\\\x87\x88\x13\xc7\x17\u132f\xa1\xea\xb2eN\x01\u068a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc5\x18y\x9aY%Wb\x13\xe2\x18\x96\xe0S\x9a\xbb\x85\xb0Z\xe3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc5\"\xe2\x0f\xbf\x04\xed\u007fk\x05\xa3{G\x18\xd6\xfc\xe0\x14.\x1a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc5$\bmF\xc8\x11+\x12\x8b/\xafo|}\x81`\xa88l\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc5-\x1a\fs\u00a1\xbe\x84\x91Q\x85\xf8\xb3O\xaa\n\xdf\x1d\xe3\x89K\xe4\xea\xb3\xfa\x0f\xa6\x80\x00\xe0\x94\xc55\x94\xc7\u03f2\xa0\x8f(L\xc9\u05e6;\xbd\xfc\v1\x972\x8a\nk#(\xff:b\xc0\x00\x00\u07d4\xc57I(\xcd\xf1\x93pTC\xb1L\xc2\r\xa4#G<\xd9\u03c9\a}\x10P\x9b\xb3\xaf\x80\x00\u07d4\xc58\xa0\xff(*\xaa_Ku\u03f6,p\x03~\xe6}O\xb5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5;P\xfd;+r\xbclC\v\xaf\x19JQU\x85\u04d8m\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xc5=y\xf7\u02dbp\x95/\xd3\x0f\xceX\xd5K\x9f\vY\xf6G\x8a\x01\x13\xe2\xd6tCE\xf8\x00\x00\u07d4\xc5I\u07c3\xc6\xf6^\xec\x0f\x1d\u0260\x93J\\_:P\xfd\x88\x89\x9d\xc0\\\xce(\u00b8\x00\x00\u07d4\xc5P\x05\xa6\xc3~\x8c\xa7\xe5C\xce%\x99s\xa3\xca\u0396\x1aJ\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5U\xb91V\xf0\x91\x01#\x80\x00\xe0\x94\u0166)\xa3\x96%R\u02ce\xde\u0609cj\xaf\xbd\f\x18\xcee\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u016e\x86\xb0\xc6\xc7\xe3\x90\x0f\x13h\x10\\VS\u007f\xaf\x8dt>\x89\n1\x06+\xee\xedp\x00\x00\u07d4\u0170\t\xba\xea\xf7\x88\xa2v\xbd5\x81:\xd6[@\v\x84\x9f;\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0175l\xd24&|(\xe8\x9cok\"f\xb0\x86\xa1/\x97\f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xc5\u01a4\x99\x8a3\xfe\xb7dCz\x8b\xe9)\xa7;\xa3J\ad\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\xe0\x94\xc5\xc7=a\xcc\xe7\xc8\xfeL\x8f\xce)\xf3\x90\x92\xcd\x19>\x0f\xff\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xc5\xc7Y\vV!\xec\xf85\x85\x88\u079bh\x90\xf2baC\U000498a1]\tQ\x9b\xe0\x00\x00\u07d4\xc5\xcd\xce\xe0\xe8]\x11}\xab\xbfSj?@i\xbfD?T\xe7\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xc5\u050c\xa2\xdb/\x85\xd8\xc5U\xcb\x0e\x9c\xfe\x82i6x?\x9e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc5\xde\x12\x03\xd3\xcc,\xea1\xc8.\xe2\xdeY\x16\x88\a\x99\xea\xfd\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xc5\xe4\x88\xcf+Vw\x939q\xf6L\xb8 -\xd0WR\xa2\xc0\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc5\xe8\x12\xf7o\x15\xf2\xe1\xf2\xf9\xbcH#H<\x88\x04cog\x89\x03\xf5\x14\x19:\xbb\x84\x00\x00\u07d4\xc5\u94d34\xf1%.\u04ba&\x81D\x87\xdf\u0498+1(\x89\x03\xcbq\xf5\x1f\xc5X\x00\x00\u07d4\xc5\xebB)^\x9c\xad\xea\xf2\xaf\x12\xde\u078a\x8dS\xc5y\xc4i\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\xc5\xed\xbb\xd2\xca\x03WeJ\xd0\xeaG\x93\xf8\xc5\xce\xcd0\xe2T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc5\xf6K\xab\xb7\x031B\xf2\x0eF\u05eab\x01\xed\x86\xf6q\x03\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc5\xf6\x87qrF\u068a \r \xe5\u9f2c`\xb6\u007f8a\x89\x01\x8d\x99?4\xae\xf1\x00\x00\u07d4\xc6\x04[<5\vL\xe9\xca\fkuO\xb4\x1ai\xb9~\x99\x00\x892$\xf4'#\xd4T\x00\x00\u07d4\xc6\v\x04eN\x00;F\x83\x04\x1f\x1c\xbdk\u00cf\xda|\xdb\u0589lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\x14F\xb7T\xc2N;\x16B\xd9\xe5\x17e\xb4\xd3\xe4k4\xb6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\x18R\x13!\xab\xaf[&Q:J\x95(\bo\"\n\xdco\x89\x01v\xb3D\xf2\xa7\x8c\x00\x00\u07d4\xc6#FW\xa8\a8A&\xf8\x96\x8c\xa1p\x8b\xb0{\xaaI<\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc6%\xf8\u024d'\xa0\x9a\x1b\u02bdQ(\xb1\u00a9HV\xaf0\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc65^\xc4v\x8cp\xa4\x9a\xf6\x95\x13\u0343\xa5\xbc\xa7\xe3\xb9\u034a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc6:\xc4\x17\x99.\x9f\x9b`8n\xd9S\xe6\xd7\xdf\xf2\xb0\x90\xe8\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xc6<\u05c8!\x18\xb8\xa9\x1e\aML\x8fK\xa9\x18Q0;\x9a\x89\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\xc6R\x87\x1d\x19$\"\u01bc#_\xa0c\xb4J~\x1dC\u3149\bg\x0e\x9e\xc6Y\x8c\x00\x00\xe0\x94\xc6gD\x1e\u007f)y\x9a\xbaadQ\xd5;?H\x9f\x9e\x0fH\x8a\x02\xf2\x9a\xceh\xad\u0740\x00\x00\u07d4\xc6j\xe4\xce\xe8\u007f\xb352\x19\xf7\u007f\x1dd\x86\u0140(\x032\x89\x01\x9a\x16\xb0o\xf8\xcb\x00\x00\u07d4\xc6t\xf2\x8c\x8a\xfd\a?\x8by\x96\x91\xb2\xf0XM\xf9B\xe8D\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u0197\xb7\x04w\u02b4.+\x8b&f\x81\xf4\xaesu\xbb%A\x8a\x01.W2\xba\xba\\\x98\x00\x00\u07d4\u019b\x85U9\xce\x1b\x04qG(\xee\xc2Z7\xf3g\x95\x1d\xe7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u019b\xe4@\x13Mb\x80\x98\x01D\xa9\xf6M\x84t\x8a7\xf3I\x89&\u009eG\u0104L\x00\x00\u07d4\u019df<\x8d`\x90\x83\x91\xc8\xd26\x19\x153\xfd\xf7wV\x13\x89\x1aJ\xba\"\\ t\x00\x00\u0794\u01a2\x86\xe0e\xc8_:\xf7H\x12\xed\x8b\u04e8\xce]%\xe2\x1d\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\u01a3\x0e\xf5\xbb3 \xf4\r\xc5\xe9\x81#\rR\xae:\xc1\x93\"\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\u01ae(}\xdb\xe1\x14\x9b\xa1m\xdc\xcaO\xe0j\xa2\uaa48\xa9\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc6\xc7\xc1\x917\x98\x97\u075c\x9d\x9a3\x83\x9cJ_b\xc0\x89\r\x89\xd8\xd8T\xb2$0h\x80\x00\xe0\x94\xc6\xcdh\xec56,Z\xd8L\x82\xadN\xdc#!%\x91-\x99\x8a\x05\xe0T\x9c\x962\xe1\xd8\x00\x00\u07d4\xc6\u0615N\x8f?\xc53\xd2\xd20\xff\x02\\\xb4\xdc\xe1O4&\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xc6\xdb\u06de\xfd^\xc1\xb3xn\x06q\xeb\"y\xb2S\xf2\x15\xed\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc6\xdf u\xeb\xd2@\xd4Hi\u00bek\u07c2\xe6=N\xf1\xf5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc6\xe2\xf5\xaf\x97\x9a\x03\xfdr:\x1bn\xfar\x83\x18\u03dc\x18\x00\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xc6\xe3$\xbe\xeb[6v^\xcdFB`\xf7\xf2`\x06\xc5\xc6.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc6\xe4\xcc\fr\x83\xfc\x1c\x85\xbcH\x13\xef\xfa\xafr\xb4\x98#\xc0\x89\x0f\x03\x1e\xc9\xc8}\xd3\x00\x00\xe0\x94\xc6\xee5\x93B)i5)\xdcA\u067bq\xa2IfX\xb8\x8e\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xc6\xfb\x1e\xe3t\x17\u0400\xa0\xd0H\x92;\u06ba\xb0\x95\xd0w\u0189\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc7\x05'\xd4D\u0110\xe9\xfc?\\\xc4Nf\xebO0k8\x0f\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7\r\x85mb\x1e\xc1E0<\nd\x00\xcd\x17\xbb\xd6\xf5\xea\xf7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xc7\x0f\xa4Uv\xbf\x9c\x86_\x988\x93\x00,AI&\xf6\x10)\x89\x15\xb4\xaa\x8e\x97\x02h\x00\x00\u07d4\xc7\x11E\xe5)\u01e7\x14\xe6y\x03\xeeb\x06\xe4\xc3\x04+g'\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xc7\x1b*=q5\u04a8_\xb5\xa5q\u073ei^\x13\xfcC\u034965\u026d\xc5\u07a0\x00\x00\u07d4\xc7\x1f\x1du\x87?3\u0732\xddK9\x87\xa1-\a\x91\xa5\xce'\x897\b\xba\xed=h\x90\x00\x00\u07d4\xc7\x1f\x92\xa3\xa5J{\x8c/^\xa4C\x05\xfc\u02c4\xee\xe21H\x89\x02\xb5\x9c\xa11\xd2\x06\x00\x00\u07d4\xc7!\xb2\xa7\xaaD\xc2\x12\x98\xe8P9\xd0\x0e.F\x0eg\v\x9c\x89\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\xc7,\xb3\x01%\x8e\x91\xbc\b\x99\x8a\x80]\u0452\xf2\\/\x9a5\x89 \t\xc5\u023fo\xdc\x00\x00\xe0\x94\xc76\x8b\x97\t\xa5\xc1\xb5\x1c\n\xdf\x18ze\xdf\x14\xe1+}\xba\x8a\x02\x02o\xc7\u007f\x03\u5b80\x00\u07d4\xc79%\x9e\u007f\x85\xf2e\x9b\xef_`\x9e\xd8k=Yl \x1e\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc7>!\x12(\"\x15\xdc\ab\xf3+~\x80}\xcd\x1az\xae>\x8a\x01v\f\xbcb;\xb3P\x00\x00\xe0\x94\xc7If\x80B\xe7\x11#\xa6H\x97^\b\xedc\x82\xf8>\x05\xe2\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xc7J9\x95\xf8\a\xde\x1d\xb0\x1a.\xb9\xc6.\x97\xd0T\x8fio\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc7Pl\x10\x19\x12\x1f\xf0\x8a,\x8c\x15\x91\xa6^\xb4\xbd\xfbJ?\x89 \x86\xac5\x10R`\x00\x00\u07d4\xc7\\7\xce-\xa0k\xbc@\b\x11Y\u01ba\x0f\x97n9\x93\xb1\x89:y#\x15\x1e\xcfX\x00\x00\u07d4\xc7]\"Y0j\xec}\xf0\"v\x8ci\x89\x9ae!\x85\xdb\u0109\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7`\x97\x1b\xbc\x18\x1cj|\xf7tA\xf2BG\u045c\xe9\xb4\u03c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xc7a0\xc7<\xb9!\x028\x02\\\x9d\xf9]\v\xe5J\xc6\u007f\xbe\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xc7e\xe0\x04v\x81\tG\x81j\xf1B\xd4m.\u7f28\xccO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc7g^VG\xb9\xd8\xda\xf4\xd3\xdf\xf1\xe5R\xf6\xb0qT\xac8\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xc7{\x01\xa6\xe9\x11\xfa\x98\x8d\x01\xa3\xab3dk\xee\xf9\xc18\xf3\x89'\x1bo\xa5\xdb\xe6\xcc\x00\x00\u07d4\u01c3z\u0420\xbf\x14\x18i7\xac\xe0lUF\xa3j\xa5OF\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u01d8\x06\x03+\xc7\xd8(\xf1\x9a\u01a6@\u018e=\x82\x0f\xa4B\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u01d9\xe3N\x88\xff\x88\xbe}\xe2\x8e\x15\xe4\xf2\xa6=\v3\xc4\u02c9\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\u01ddPb\u01d6\xddwa\xf1\xf1>U\x8ds\xa5\x9f\x82\xf3\x8b\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\u01e0\x18\xf0\x96\x8aQ\xd1\xf6`<\\I\xdcT[\xcb\x0f\xf2\x93\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u01ef\xf9\x19)yt\x89UZ/\xf1\xd1M\\iZ\x10\x83U\x8965\u026d\xc5\u07a0\x00\x00\u0794\u01f1\xc8>c ?\x95G&>\xf6(.}\xa3;n\xd6Y\x88\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\u01f3\x9b\x06\x04Q\x00\f\xa1\x04\x9b\xa1T\xbc\xfa\x00\xff\x8a\xf2b\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\u01ff\x17\xc4\xc1\x1f\x98\x94\x1fP~w\bO\xff\xbd-\xbd=\xb5\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\u01ff.\xd1\xed1)@\xeej\xde\xd1Qn&\x8eJ`HV\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xc7\xd4O\xe3,\u007f\x8c\xd5\xf1\xa9t'\xb6\xcd:\xfc\x9eE\x02>\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xc7\xd5\xc7\x05@\x81\xe9\x18\xech{Z\xb3n\x97=\x18\x13)5\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xc7\xde^\x8e\xaf\xb5\xf6+\x1a\n\xf2\x19\\\xf7\x93\u01c9L\x92h\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xc7\xe30\xcd\f\x89\n\u025f\xe7q\xfc\xc7\xe7\xb0\t\xb7A=\x8a\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc7\xea\xc3\x1a\xbc\xe6\xd5\xf1\u07a4\"\x02\xb6\xa6t\x15=\xb4z)\x89 \t\xc5\u023fo\xdc\x00\x00\xe0\x94\xc7\xecb\xb8\x04\xb1\xf6\x9b\x1e0p\xb5\xd3b\xc6/\xb3\t\xb0p\x8a\x02\xc4k\xf5A`f\x11\x00\x00\u07d4\xc7\xf7+\xb7X\x01k7G\x14\u0509\x9b\xce\"\xb4\xae\xc7\n1\x89:&\xc9G\x8f^-\x00\x00\u0794\xc8\v6\u047e\xaf\xba_\xccdM`\xacnF\xed)'\xe7\u0708\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xc8\x11\xc2\xe9\xaa\x1a\xc3F.\xba^\x88\xfc\xb5\x12\x0e\x9fn,\xa2\x89K\xe6\u0607\xbd\x87n\x00\x00\u07d4\xc8\x17\xdf\x1b\x91\xfa\xf3\x0f\xe3%\x15qr|\x97\x11\xb4]\x8f\x06\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xc8\x1f\xb7\xd2\x0f\u0480\x01\x92\xf0\xaa\xc1\x98\xd6\u05a3}?\xcb}\x89\x0e\x11I3\x1c-\xde\x00\x00\u07d4\xc8 \xc7\x11\xf0w\x05'8\a\xaa\xaam\xe4M\x0eKH\xbe.\x89\bg\x0e\x9e\xc6Y\x8c\x00\x00\u07d4\xc8#\x1b\xa5\xa4\x11\xa1>\"+)\xbf\xc1\b?v1X\xf2&\x8967\tlK\xcci\x00\x00\u07d4\xc86\xe2Jo\xcf)\x94;6\b\xe6b)\n!_e)\xea\x89\x0f\xd4Pd\xea\xee\x10\x00\x00\xe0\x94\xc8;\xa6\u0755I\xbe\x1d2\x87\xa5\xa6T\xd1\x06\xc3Lk]\xa2\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xc8>\x9djX%;\uefb7\x93\xe6\xf2\x8b\x05JXI\x1bt\x89\x0fF\u00b6\xf5\xa9\x14\x00\x00\u07d4\xc8A\x88O\xa4x_\xb7s\xb2\x8e\x97\x15\xfa\xe9\x9aQ40]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc8M\x9b\xea\n{\x9f\x14\x02 \xfd\x8b\x90\x97\u03ff\xd5\xed\xf5d\x89\x06\xab\x9e\u0091\xad}\x80\x00\u07d4\xc8RB\x8d+Xd\x97\xac\xd3\fV\xaa\x13\xfbU\x82\xf8D\x02\x893B\xd6\r\xff\x19`\x00\x00\u07d4\xc8S![\x9b\x9f-,\xd0t\x1eX^\x98{_\xb8\f!.\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xc8S%\uaca5\x9b>\xd8c\xc8j_)\x06\xa0B)\xff\xa9\x89\x19=\u007f}%=\xe0\x00\x00\u07d4\xc8^\xf2}\x82\x04\x03\x80_\xc9\xed%\x9f\xffd\xac\xb8\xd64j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc8akN\xc0\x91(\xcd\xff9\xd6\u4e6c\x86\xee\xc4q\xd5\xf2\x89\x01\r:\xa56\xe2\x94\x00\x00\xe0\x94\xc8a\x90\x90K\x8d\a\x9e\xc0\x10\xe4b\xcb\xff\xc9\b4\xff\xaa\\\x8a\x02#\x85\xa8'\xe8\x15P\x00\x00\u07d4\xc8q\r~\x8bZ;\u059aB\xfe\x0f\xa8\xb8|5\u007f\xdd\xcd\u0209\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc8sR\u06e5\x82\xee f\xb9\xc0\x02\xa9b\xe0\x03\x13Ox\xb1\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xc8|w\xe3\xc2J\xde\xcd\xcd\x108\xa3\x8bV\xe1\x8d\xea\u04f7\x02\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\xc8}:\xe3\u0607\x04\u066b\x00\t\xdc\xc1\xa0\x06q1\xf8\xba<\x89j\xc5\xc6-\x94\x86\a\x00\x00\xe0\x94\u0201N4R>8\xe1\xf9'\xa7\xdc\xe8FjDz\t6\x03\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u0202U\xed\xdc\xf5!\xc6\xf8\x1d\x97\xf5\xa4!\x81\xc9\a=N\xf1\x89\x0f\u00d0D\xd0\n*\x80\x00\u07d4\u0205\xa1\x8a\xab\xf4T\x1b{{~\xcd0\xf6\xfa\u619d\x95i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u020c\xa1\xe6\xe5\xf4\xd5X\xd17\x80\xf4\x88\xf1\rJ\xd3\x13\r4\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u020e\xecT\xd3\x05\xc9(\xcc(H\xc2\xfe\xe251\xac\xb9mI\x89lj\u04c2\xd4\xfba\x00\x00\xe0\x94\u021c\xf5\x04\xb9\xf3\xf85\x18\x1f\xd8BO\\\xcb\xc8\xe1\xbd\xdf}\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u0222\xc4\xe5\x9e\x1c\u007f\xc5H\x05X\x048\xae\xd3\xe4J\xfd\xf0\x0e\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4\u022aI\u301f\b\x99\xf2\x8a\xb5~gCp\x9dXA\x903\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\u022b\x1a<\xf4l\xb8\xb0d\xdf.\"-9`s\x94 2w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0231\x85\x05%\xd9F\xf2\xae\x84\xf3\x17\xb1Q\x88\xc56\xa5\u0706\x89\x91\x8d\xdc:B\xa3\xd4\x00\x00\u07d4\xc8\xd4\xe1Y\x9d\x03\xb7\x98\t\xe0\x13\n\x8d\u00c4\b\xf0^\x8c\u04c9\x9f\xad\x06$\x12y\x16\x00\x00\u07d4\xc8\xdd'\xf1k\xf2$P\xf5w\x1b\x9f\xe4\xedO\xfc\xb3\t6\xf4\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xc8\xdezVL\u007f@\x12\xa6\xf6\xd1\x0f\u040fG\x89\x0f\xbf\a\u0509\x10CV\x1a\x88)0\x00\x00\u07d4\xc8\xe2\xad\xebT^I\x9d\x98,\f\x11sc\u03b4\x89\u0171\x1f\x895e\x9e\xf9?\x0f\xc4\x00\x00\xe0\x94\xc8\xe5X\xa3\xc5i~o\xb2:%\x94\u0200\xb7\xa1\xb6\x8f\x98`\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xc8\xf2\xb3 \xe6\xdf\xd7\t\x06\u0157\xba\xd2\xf9P\x13\x12\u01c2Y\x89Q\x93K\x8b:W\xd0\x00\x00\u07d4\xc9\x03\x00\xcb\x1d@w\xe6\xa6\xd7\xe1i\xa4`F\x8c\xf4\xa4\x92\u05c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9\f7e\x15k\u028eH\x97\xab\x80$\x19\x15<\xbeR%\xa9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xc9\x10\xa9pUl\x97\x16\xeaS\xaff\xdd\xef\x93\x141$\x91=\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\xe0\x94\xc9\x12{\u007ff)\xee\x13\xfc?`\xbc/Dg\xa2\aE\xa7b\x8a\x03|\x9a\xa4\xe7\xceB\x1d\x80\x00\u07d4\xc9\x1b\xb5b\xe4+\xd4a0\xe2\u04eeFR\xb6\xa4\ub1bc\x0f\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\xe0\x94\xc90\x88y\x05m\xfe\x13\x8e\xf8 \x8fy\xa9\x15\u01bc~p\xa8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xc94\xbe\xca\xf7\x1f\"_\x8bJK\xf7\xb1\x97\xf4\xac\x9604\\\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc9?\xbd\xe8\xd4m+\xcc\x0f\xa9\xb3;\u063a\u007f\x80B\x12Ue\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xc9@\x89U:\xe4\xc2,\xa0\x9f\xbc\x98\xf5pu\xcf.\u0155\x04\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xc9A\x10\xe7\x1a\xfeW\x8a\xa2\x18\xe4\xfc(d\x03\xb03\n\u038d\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xc9F\u056c\xc14n\xba\nry\xa0\xac\x1dF\\\x99m\x82~\x8a\x03x=T_\xdf\n\xa4\x00\x00\u07d4\xc9J(\xfb20\xa9\xdd\xfa\x96Nw\x0f,\xe3\xc2S\xa7\xbeO\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xc9JXR\x03\xda{\xba\xfd\x93\xe1X\x84\xe6`\u0531\xea\xd8T\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xc9O|5\xc0'\xd4}\xf8\xefO\x9d\xf8Z\x92H\xa1}\xd2;\x89\x01\x9f\x8euY\x92L\x00\x00\u07d4\xc9Q\x90\f4\x1a\xbb\xb3\xba\xfb\xf7\xee )7pq\xdb\xc3j\x89\x11\xc2]\x00M\x01\xf8\x00\x00\u07d4\xc9S\xf94\xc0\xeb-\x0f\x14K\u06b0\x04\x83\xfd\x81\x94\x86\\\xe7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9f&r\x8a\xaaLO\xb3\xd3\x1c&\xdf:\xf3\x10\b\x17\x10\u0449\xb5\x0f\u03ef\xeb\xec\xb0\x00\x00\u07d4\xc9gQel\n\x8e\xf45{sD2!4\xb9\x83PJ\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\u0240Hh\u007f+\xfc\u027d\x90\xed\x18slW\xed\xd3R\xb6]\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0241\xd3\x12\u0487\xd5X\x87\x1e\u0757:\xbbv\xb9y\xe5\xc3^\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0242Xmc\xb0\xd7L \x1b\x1a\xf8A\x83r\xe3\fv\x16\xbe\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u0249CO\x82Z\xaf\x9cU/h^\xba|\x11\xdbJ_\xc7:\x89\x1b(\u014d\x96\x96\xb4\x00\x00\u07d4\u0249\xee\xc3\a\u80db\x9dr7\xcf\xda\b\x82)b\xab\u41c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u0252\xbeY\xc6r\x1c\xafN\x02\x8f\x9e\x8f\x05\xc2\\UQ[\u0509\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0255{\xa9L\x1b)\xe5'~\xc3f\"pI\x04\xc6=\xc0#\x89h>\xfcg\x82d,\x00\x00\xe0\x94\u025a\x9c\xd6\xc9\xc1\xbe54\xee\u0352\xec\xc2/\\8\xe9Q[\x8a\x01\x05Y;:\x16\x9dw\x00\x00\xe0\x94\u026c\x01\xc3\xfb\t)\x03?\f\xcc~\x1a\xcf\uaae7\x94]G\x8a\x02\xa3j\x9e\x9c\xa4\xd2\x03\x80\x00\u07d4\u0276\x98\xe8\x98\xd2\rMO@\x8eNM\x06\x19\"\xaa\x85c\a\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u0276\xb6\x86\x11\x16\x91\xeej\xa1\x97\xc7#\x1a\x88\xdc`\xbd)]\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc9\u01ec\v\u0753B\xb5\xea\xd46\t#\xf6\x8cr\xa6\xbac:\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xc9\xc8\r\xc1.{\xab\x86\xe9I\xd0\x1eL>\xd3_+\x9b\xba_\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xc9\xd7dF\u056a\xdf\xf8\vh\xb9\x1b\b\u035b\xc8\xf5U\x1a\xc1\x89&\xb4\xbd\x91\x10\xdc\xe8\x00\x00\xe0\x94\xc9\u073b\x05oM\xb7\xd9\xda9\x93b\x02\u017d\x820\xb3\xb4w\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xc9\xe0&\b\x06h(\x84\x8a\xeb(\xc76r\xa1)%\x18\x1fM\x89\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xca\x042\xcb\x15{Qy\xf0.\xbb\xa5\xc9\u0475O\xecM\x88\u028965\u026d\xc5\u07a0\x00\x00\u07d4\xca\x12,\xf0\U00094216\xb7HC\xf4\x9a\xfe\u043a\x16\x18\xee\u05c9\x1e[\x8f\xa8\xfe*\xc0\x00\x00\xe0\x94\xca\"\u0363`m\xa5\xca\xd0\x13\xb8\aG\x06\xd7\xe9\xe7!\xa5\f\x8a\x01q\x81\xc6\xfa9\x81\x94\x00\x00\u07d4\xca#\xf6-\xff\rd`\x03lb\xe8@\xae\xc5W~\v\xef\u0489\a\xa1\xfe\x16\x02w\x00\x00\x00\u07d4\xca%\xff4\x93L\x19B\xe2*N{\xd5o\x14\x02\x1a\x1a\xf0\x88\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xca7?\xe3\xc9\x06\xb8\xc6U\x9e\xe4\x9c\xcd\a\xf3|\xd4\xfbRf\x89a\t=|,m8\x00\x00\u07d4\xcaA\u032c0\x17 R\xd5\"\xcd//\x95}$\x81S@\x9f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xcaB\x88\x01N\xdd\xc5c/_\xac\xb5\xe3\x85\x17\xa8\xf8\xbc]\x98\x89\x12nr\xa6\x9aP\xd0\x00\x00\u07d4\xcaB\x88c\xa5\xca06\x98\x92\xd6\x12\x18>\xf9\xfb\x1a\x04\xbc\xea\x89Rf<\u02b1\xe1\xc0\x00\x00\u07d4\xcaI\xa5\xf5\x8a\xdb\xef\xae#\xeeY\xee\xa2A\xcf\x04\x82b.\xaa\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xcaL\xa9\xe4w\x9dS\x0e\u02ec\xd4~j\x80X\xcf\xdee\u064f\x89+^:\xf1k\x18\x80\x00\x00\u07d4\xcae~\xc0o\xe5\xbc\t\xcf#\xe5*\xf7\xf8\f\xc3h\x9en\u07890\xca\x02O\x98{\x90\x00\x00\u07d4\xcaf\xb2(\x0f\xa2\x82\u0176v1\xceU+b\xeeU\xad\x84t\x89j\xc4\"\xf54\x92\x88\x00\x00\xe0\x94\xcal\x81\x8b\xef\xd2Q6\x1e\x02t@h\xbe\x99\u062a`\xb8J\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcap\xf4\u077f\x06\x9d!C\xbdk\xbc\u007fikRx\x9b2\u7262\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xcatuvDjL\x8f0\xb0\x83@\xfe\xe1\x98\xdec\xec\x92\u03ca\x01|\x8e\x12\x06r*0\x00\x00\u07d4\xca{\xa3\xffSl~_\x0e\x158\x00\xbd8=\xb81)\x98\xe0\x89\t1\xac=k\xb2@\x00\x00\xe0\x94\u0282v\xc4w\xb4\xa0{\x80\x10{\x845\x94\x18\x96\a\xb5;\xec\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\u0284\t\b>\x01\xb3\x97\xcf\x12\x92\x8a\x05\xb6\x84U\xceb\x01\u07c9V\xbcu\xe2\xd61\x00\x00\x00\u07d4\u0298\u01d8\x8e\xfa\b\xe9%\uf719ER\x03&\xe9\xf4;\x99\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u029a\x04*j\x80o\xfc\x92\x17\x95\x00\xd2D)\xe8\xabR\x81\x17\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\u029d\xec\x02\x84\x1a\xdf\\\xc9 WjQ\x87\xed\u04bdCJ\x18\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u029f\xaa\x17T/\xaf\xbb8\x8e\xab!\xbcL\x94\u89f3G\x88\x89lk\x8f\xce\r\x18y\x80\x00\xe0\x94\u02aah\xeel\xdf\r4EJv\x9b\r\xa1H\xa1\xfa\xaa\x18e\x8a\x01\x87.\x1d\xe7\xfeR\xc0\x00\x00\u07d4\u02ad\x9d\xc2\rX\x9c\xe4(\xd8\xfd\xa3\xa9\xd5:`{y\x88\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u02b0\xd3,\xf3v\u007f\xa6\xb3S|\x842\x8b\xaa\x9fPE\x816\x8a\x01\xe5\xb8\xfa\x8f\xe2\xac\x00\x00\x00\u07d4\u02b9\xa3\x01\xe6\xbdF\xe9@5P(\xec\xcd@\xceMZ\x1a\u00c9\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u02b9\xa9z\xda\x06\\\x87\x81nh`\xa8\xf1Bo\xe6\xb3\xd7u\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\u02ba\xb6'N\xd1P\x89s~({\xe8x\xb7W\x93Hd\xe2\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\u02bd\xaf5OG \xa4f\xa7d\xa5(\xd6\x0e:H*9<\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xca\xcbg^\t\x96#T\x04\ufbfb.\u02c1R'\x1bU\xe0\x89%\xf2s\x93=\xb5p\x00\x00\u07d4\xca\xd1O\x9e\xbb\xa7f\x80\xeb\x83k\a\x9c\u007f{\xaa\xf4\x81\xedm\x89\f\xef={\xd7\xd04\x00\x00\xe0\x94\xca\xe3\xa2S\xbc\xb2\xcfN\x13\xba\x80\u0098\xab\x04\x02\xda|*\xa0\x8a\x01$\xbc\r\u0752\xe5`\x00\x00\u07d4\xca\xef\x02{\x1a\xb5\x04\xc7?A\xf2\xa1\ty\xb4t\xf9~0\x9f\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xca\xf4H\x1d\x9d\xb7\x8d\xc4\xf2_{J\u023d;\x1c\xa0\x10k1\x8a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\xca\xfd\xe8U\x86L%\x98\xda<\xaf\xc0Z\u064d\U00089380H\x8a\x03\x00\xa8\xed\x96\xffJ\x94\x00\x00\xe0\x94\xcb\r\xd7\xcfN]\x86a\xf6\x02\x89C\xa4\xb9\xb7\\\x91D6\xa7\x8a\x19i6\x89t\xc0[\x00\x00\x00\u07d4\xcb\x1b\xb6\xf1\xda^\xb1\rH\x99\xf7\xe6\x1d\x06\xc1\xb0\x0f\u07f5-\x898E$\xccp\xb7x\x00\x00\u07d4\xcb=vl\x98?\x19+\xce\xca\xc7\x0fN\xe0=\xd9\xffqMQ\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xcbB\xb4N\xb5\xfd`\xb5\x83~O\x9e\xb4rgR=\x1a\"\x9c\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xcbG\xbd0\u03e8\xecTh\xaa\xa6\xa9FB\xce\xd9\xc8\x19\xc8\u0509\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xcbH\xfe\x82e\u066fU\xebp\x06\xbc3VE\xb0\xa3\xa1\x83\xbe\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xcbJ\x91M+\xb0)\xf3._\xef\\#LO\xec--\xd5w\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xcbJ\xbf\u0082\xae\xd7n]W\xaf\xfd\xa5B\xc1\xf3\x82\xfc\xac\xf4\x8a\x01\xb9\x0f\x11\xc3\x18?\xaa\x00\x00\u07d4\xcbJ\xd0\xc7#\xdaF\xabV\xd5&\xda\f\x1d%\xc7=\xaf\xf1\n\x89\x1b\xa5\xab\xf9\xe7y8\x00\x00\u07d4\xcbK\xb1\xc6#\xba(\xdcB\xbd\xaa\xa6\xe7N\x1d*\xa1%l*\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xcbPXt\x12\x82#\x04\xeb\u02e0}\xab:\x0f\t\xff\xfe\u4189JD\x91\xbdm\xcd(\x00\x00\u07d4\xcbX\x99\v\u0350\u03ffm\x8f\t\x86\xf6\xfa`\x02v\xb9N-\x8964\xbf9\xab\x98x\x80\x00\u07d4\xcbh\xaeZ\xbe\x02\xdc\xf8\xcb\u016aq\x9c%\x81FQ\xaf\x8b\x85\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xcbty\x10\x9bC\xb2fW\xf4F_M\x18\xc6\xf9t\xbe_B\x89b\xa9\x92\xe5:\n\xf0\x00\x00\xe0\x94\xcb}+\x80\x89\xe91,\u026e\xaa's\xf3S\b\xecl*{\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u02c6\xed\xbc\x8b\xbb\x1f\x911\x02+\xe6IV^\xbd\xb0\x9e2\xa1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u02d3\x19\x9b\x9c\x90\xbcI\x15\xbd\x85\x9e=B\x86m\xc8\xc1\x87I\x89\f\x90\xdf\a\xde\xf7\x8c\x00\x00\u07d4\u02d4\xe7o\xeb\xe2\b\x11g3\xe7n\x80]H\xd1\x12\xec\x9f\u028965\u026d\xc5\u07a0\x00\x00\u07d4\u02dbQ\x03\xe4\u0389\xafOd\x91aP\xbf\xf9\xee\u02df\xaa\\\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u02e2\\zP<\xc8\xe0\xd0Iq\xca\x05\xc7b\xf9\xb7b\xb4\x8b\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u02e2\x88\xcd<\x1e\xb4\u055d\xdb\x06\xa6B\x1c\x14\xc3E\xa4{$\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u02f3\x18\x9eK\xd7\xf4_\x17\x8b\x1c0\xc7n&1MJK\n\x89\x0f\xfe\vg|e\xa9\x80\x00\xe0\x94\u02f7\xbe\x17\x95?,\u0313\u1f19\x80[\xf4U\x11CNL\x8a\n\xae[\x9d\xf5m/ \x00\x00\xe0\x94\xcb\xc0KM\x8b\x82\xca\xf6p\x99o\x16\f6)@\xd6o\xcf\x1a\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcb\u07974\xb8\xe6\xaaS\x8c)\x1dm\u007f\xac\xed\xb0\xf38\xf8W\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xcb\xe1\xb9H\x86M\x84t\xe7e\x14XX\xfc\xa4U\x0fxK\x92\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xe5/\xc53\xd7\xdd`\x8c\x92\xa2`\xb3|?E\u07b4\xeb3\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcb\xe8\x10\xfe\x0f\xec\xc9dGJ\x1d\xb9w(\xbc\x87\xe9s\xfc\xbd\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xf1j\x0f\xe2tRX\xcdR\xdb+\xf2\x19T\xc9u\xfcj\x15\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\xcb\xf3\u007f\xf8T\xa2\xf1\xceS\x93D\x94wx\x92\xd3\xeceW\x82\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcb\xfaj\xf6\u0083\xb0F\xe2w,`c\xb0\xb2\x15S\xc4\x01\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcb\xfav\xdb\x04\xce8\xfb ]7\xb8\xd3w\xcf\x13\x80\xda\x03\x17\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xcc\x03I\x85\xd3\xf2\x8c-9\xb1\xa3K\xce\xd4\u04f2\xb6\xca#N\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xcc\x04\x8d\u01f9]\xca%\xdf&\xee\xfac\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcc+_D\x8f5(\xd3\xfeA\xcc}\x1f\xa9\xc0\xdcv\xf1\xb7v\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xcc-\x04\xf0\xa4\x01q\x89\xb3@\xcaw\x19\x86A\xdc\xf6Ek\x91\x89\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94\xccA\x9f\u0651+\x85\x13VY\xe7z\x93\xbc=\xf1\x82\xd4Q\x15\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xccE\xfb:U[\xad\x80{8\x8a\x03W\xc8U _|u\xe8\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xccHAM*\xc4\xd4*Yb\xf2\x9e\xeeD\x97\t/C\x13R\x89\b\xbaR\xe6\xfcE\xe4\x00\x00\u07d4\xccJ/,\xf8l\xf3\xe43u\xf3`\xa4sF\x91\x19_\x14\x90\x89I\x15\x05;\xd1)\t\x80\x00\u07d4\xccO\x0f\xf2\xae\xb6}T\xce;\xc8\xc6Q\v\x9a\xe8>\x9d2\x8b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xccO\xaa\xc0\v\xe6b\x8f\x92\xefk\x8c\xb1\xb1\xe7j\xac\x81\xfa\x18\x89\v\"\xa2\xea\xb0\xf0\xfd\x00\x00\xe0\x94\xccO\xebr\u07d8\xff5\xa18\xe0\x17a\xd1 ?\x9b~\xdf\n\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xcc`oQ\x13\x97\xa3\x8f\u01c7+\u04f0\xbd\x03\xc7\x1b\xbdv\x8b\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xcc`\xf86\xac\xde\xf3T\x8a\x1f\xef\u0321>\u01a97\xdbD\xa0\x89\x04\xb0m\xbb\xb4\x0fJ\x00\x00\u07d4\xccl\x03\xbd`>\t\xdeT\xe9\xc4\u056cmA\xcb\xceqW$\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\xccl-\xf0\x0e\x86\xec\xa4\x0f!\xff\xda\x1ag\xa1i\x0fG|e\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94\xccm{\x12\x06\x1b\xc9m\x10M`me\xff\xa3+\x006\xeb\a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xccs\xdd5kIy\xb5y\xb4\x01\xd4\xccz1\xa2h\xdd\xceZ\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xccu\x8d\a\x1d%\xa62\n\xf6\x8c]\xc9\xc4\xf6\x95[\xa9E \x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcc{\x04\x81\xcc2\xe6\xfa\xef#\x86\xa0p\"\xbc\xb6\xd2\u00f4\xfc\x89\xabM\xcf9\x9a:`\x00\x00\xe0\x94\u0314;\xe1\",\xd1@\n#\x99\xdd\x1bE\x94E\xcfmT\xa9\x8a\x02\xa7@\xaee6\xfc\x88\x00\x00\u07d4\u0315\x19\xd1\xf3\x98_k%^\xad\xed\x12\xd5bJ\x97'!\xe1\x8965\u026d\xc5\u07a0\x00\x00\u0794\u031a\xc7\x15\xcdo&\x10\xc5+XgdV\x88B\x97\x01\x8b)\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\u0320{\xb7\x94W\x1dJ\xcf\x04\x1d\xad\x87\xf0\xd1\xef1\x85\xb3\x19\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u032b\xc6\x04\x8aSFD$\xfc\xf7n\xeb\x9en\x18\x01\xfa#\u0509\x02\xab{&\x0f\xf3\xfd\x00\x00\u07d4\u032e\r=\x85*}\xa3\x86\x0f\x066\x15L\nl\xa3\x16(\u0509\x05\xc6\xd1+k\xc1\xa0\x00\x00\u07d4\xcc\xca$\xd8\xc5mn,\a\xdb\bn\xc0~X[\xe2g\xac\x8d\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xcc\xd5!\x13-\x98l\xb9hi\x84&\"\xa7\u0762l>\xd0W\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcc\xf49u\xb7k\xfes_\xec<\xb7\xd4\xdd$\xf8\x05\xba\tb\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xcc\xf6*f?\x13S\xba.\xf8\xe6R\x1d\xc1\xec\xb6s\xec\x8e\xf7\x89\b=lz\xabc`\x00\x00\u07d4\xcc\xf7\x11\r\x1b\u0667K\xfd\x1d}}-\x9dU`~{\x83}\x890\xca\x02O\x98{\x90\x00\x00\u07d4\xcc\xfdrW`\xa6\x88#\xff\x1e\x06/L\xc9~\x13`\xe8\u0657\x89\x15\xacV\xed\xc4\xd1,\x00\x00\u07d4\xcd\x02\x0f\x8e\xdf\xcfRG\x98\xa9\xb7:d\x034\xbb\xf7/\x80\xa5\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xcd\x06\xf8\xc1\xb5\u037d(\xe2\xd9kcF\xc3\xe8Z\x04\x83\xba$\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcd\a.n\x183\x13y\x95\x19m{\xb1r_\xef\x87a\xf6U\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xcd\n\x16\x1b\xc3g\xae\t'\xa9*\xac\x9c\xf6\xe5\bg\x14\xef\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\xcd\n\xf3GN\"\xf0i\xec4\a\x87\r\xd7pD=[\x12\xb0\x89\x8e^\xb4\xeew\xb2\xef\x00\x00\u07d4\xcd\v\x02W\u70e3\xd2\xc2\u3e9dny\xb7^\xf9\x80$\u0509\x9f\xad\x06$\x12y\x16\x00\x00\u07d4\xcd\x10,\xd6\xdb=\xf1J\u05af\x0f\x87\xc7$y\x86\x1b\xfc=$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcd\x1ef\xedS\x9d\xd9/\xc4\v\xba\xa1\xfa\x16\u078c\x02\xc1ME\x89\fw\xe4%hc\xd8\x00\x00\u07d4\xcd\x1e\xd2c\xfb\xf6\xf6\xf7\xb4\x8a\xef\x8fs=2\x9dC\x82\xc7\u01c9\x01\x00\xbd3\xfb\x98\xba\x00\x00\u07d4\xcd*6\xd7S\xe9\xe0\xed\x01*XMqh\aX{A\xd5j\x89\x0e+\xa7[\v\x1f\x1c\x00\x00\u07d4\xcd2\xa4\xa8\xa2\u007f\x1c\xc69T\xaacOxW\x05s4\u01e3\x89:\xd1fWlr\xd4\x00\x00\u07d4\xcd5\xff\x01\x0e\xc5\x01\xa7!\xa1\xb2\xf0z\x9c\xa5\x87}\xfc\xf9Z\x89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4\xcdC\x06\xd7\xf6\x94z\xc1tMN\x13\xb8\xef2\xcbe~\x1c\x00\x89\x1b\x1a\xb3\x19\xf5\xecu\x00\x00\u07d4\xcdC%\x8bs\x92\xa90\x83\x9aQ\xb2\xef\x8a\xd24\x12\xf7Z\x9f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcdI\xbf\x18^p\xd0E\a\x99\x9f\x92\xa4\xdeDU1('\u040965\u026d\xc5\u07a0\x00\x00\u07d4\xcdU\x10\xa2B\u07f0\x18=\xe9%\xfb\xa8f\xe3\x12\xfa\xbc\x16W\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xcdVj\u05f8\x83\xf0\x1f\u04d9\x8a\x9aX\xa9\xde\xe4rM\u0725\x89\x030\xae\x185\xbe0\x00\x00\xe0\x94\xcdY\xf3\xdd\xe7~\t\x94\v\xef\xb6\xeeX\x03\x19e\xca\xe7\xa36\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xcdr]p\xbe\x97\xe6w\xe3\xc8\xe8\\\v&\xef1\xe9\x95PE\x89Hz\x9a0E9D\x00\x00\xe0\x94\xcd~G\x90\x94d\xd8q\xb9\xa6\xdcv\xa8\xe9\x19]\xb3H^z\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xcd~\xce\bkKa\x9b;6\x93R\xee8\xb7\x1d\xdb\x06C\x9a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xcd\u007f\t\xd7\xedf\xd0\u00cb\u016dN2\xb7\xf2\xb0\x8d\xc1\xb3\r\x89>;\xb3M\xa2\xa4p\x00\x00\u07d4\u0355)I+\\)\xe4u\xac\xb9A@+=;\xa5\x06\x86\xb0\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0355\xfaB=o\xc1 'J\xac\xde\x19\xf4\xee\xb7f\xf1\x04 \x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u035bL\xefs9\f\x83\xa8\xfdq\u05f5@\xa7\xf9\u03cb\x8c\x92\x89\x04\xe1\x00;(\xd9(\x00\x00\u07d4\u0361t\x11\t\xc0&[?\xb2\xbf\x8d^\xc9\u00b8\xa34kc\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0361\xb8\x86\u39d5\u027aw\x91N\n/\xe5go\x0f\\\u03c9\x05\xbf`\xeaB\xc2\x04\x00\x00\u07d4\u0364S\x0fK\x9b\xc5\t\x05\xb7\x9d\x17\u008f\xc4o\x954\x9b\u07c93\x10\xe0I\x11\xf1\xf8\x00\x00\u07d4\u036bF\xa5\x90 \x80do\xbf\x95B\x04 J\xe8\x84\x04\x82+\x89\x1d\x8a\x96\xe5\xc6\x06\xeb\x00\x00\u07d4\u0375\x97)\x900\x18?n-#\x853\xf4d*\xa5\x87T\xb6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xcd\xd5\u0601\xa76,\x90p\a;\u07fcu\xe7$S\xacQ\x0e\x89-\xa5\x18\xea\xe4\x8e\xe8\x00\x00\u07d4\xcd\xd6\rs\xef\xaa\xd8s\u027b\xfb\x17\x8c\xa1\xb7\x10Z\x81\xa6\x81\x89\x01\xbc\x16\xd6t\xec\x80\x00\x00\u07d4\xcd\xd9\xef\xacMm`\xbdq\xd9U\x85\xdc\xe5\u0557\x05\xc15d\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xcd\xe3m\x81\xd1(\u015d\xa1Ee!\x93\xee\u00bf\xd9e\x86\xef\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xcd\xea8o\x9d\x0f\xd8\x04\xd0(\x18\xf27\xb7\xd9\xfavF\xd3^\x89\xa3I\xd3m\x80\xecW\x80\x00\u07d4\xcd\xec\xf5gT3\u0370\xc2\xe5Zh\xdb]\x8b\xbexA\x9d\u0489\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xcd\xfd\x82\x173\x97%\xd7\xeb\xac\x11\xa66U\xf2e\xef\xf1\xcc=\x8a\x01\x0f\fid\x10\xe3\xa9\x00\x00\u07d4\xce\a\x9fQ\x88wt\xd8\x02\x1c\xb3\xb5u\xf5\x8f\x18\xe9\xac\xf9\x84\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xce\x18\x84\u077b\xb8\xe1\x0eM\xbanD\xfe\xee\u00a7\xe5\xf9/\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xce\x1b\f\xb4j\xae\xcf\u05db\x88\f\xad\x0f-\u068a\x8d\xed\u0431\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xce&\xf9\xa50_\x83\x81\tCT\xdb\xfc\x92fN\x84\xf9\x02\xb5\x89\fz\xaa\xb0Y\x1e\xec\x00\x00\u07d4\xce-\xea\xb5\x1c\n\x9a\xe0\x9c\xd2\x12\xc4\xfaL\xc5+S\xcc\r\xec\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xce.\r\xa8\x93F\x99\xbb\x1aU>U\xa0\xb8\\\x16\x945\xbe\xa3\x8a\x01\x0f\fid\x10\xe3\xa9\x00\x00\u07d4\xce:a\xf0F\x1b\x00\x93^\x85\xfa\x1e\xad\x82\xc4^Zd\u0508\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xceK\x06]\xbc\xb20G 2b\xfbH\xc1\x18\x83d\x97tp\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xceS\xc8\xcd\xd7B\x96\xac\xa9\x87\xb2\xbc\x19\u00b8u\xa4\x87I\u0409\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xce^\x04\xf0\x18Ci\xbc\xfa\x06\xac\xa6o\xfa\x91\xbfY\xfa\x0f\xb9\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xce^\xb6:{\xf4\xfb\xc2\xf6\u4ea0\u018a\xb1\xcbL\xf9\x8f\xb4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xceb\x12Z\xde\xc37\n\xc5!\x10\x95:Nv\v\xe9E\x1e;\x89\b=lz\xabc`\x00\x00\xe0\x94\xceq\bmL`%T\xb8-\xcb\xfc\xe8\x8d cMS\xccM\x8a\t(\x96R\x9b\xad\u0708\x00\x00\u07d4\u038akmP3\xb1I\x8b\x1f\xfe\xb4\x1aAU\x04\x05\xfa\x03\xa2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0397\x86\xd3q/\xa2\x00\xe9\xf6\x857\xee\xaa\x1a\x06\xa6\xf4ZK\x89a\t=|,m8\x00\x00\u07d4\u039d!\u0192\xcd<\x01\xf2\x01\x1fP_\x87\x006\xfa\x8fl\u0489\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\u03a2\x89f#\xf4\x91\x02\x87\xa2\xbd\u017e\x83\xae\xa3\xf2\xe6\xde\b\x8a\x01\xfbZ7Q\xe4\x90\xdc\x00\x00\u07d4\u03a3JM\xd9=\u066e\xfd9\x90\x02\xa9}\x99z\x1bK\x89\u0349QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\u03a4?pu\x81k`\xbb\xfc\u62d9:\xf0\x88\x12p\xf6\u0109lk\x93[\x8b\xbd@\x00\x00\u07d4\u03a8t3AS<\xb2\xf0\xb9\xc6\xef\xb8\xfd\xa8\rw\x16(%\x89\x05k\xc7^-c\x10\x00\x00\u07d4\u03b0\x89\xec\x8ax3~\x8e\xf8\x8d\xe1\x1bI\xe3\u0751\x0ft\x8f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u03b3=x\xe7Tz\x9d\xa2\xe8}Q\xae\xc5\xf3D\x1c\x87\x92:\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u03b3\x898\x1dH\xa8\xaeO\xfcH:\u043b^ L\xfd\xb1\xec\x89('\xe6\xe4\xddb\xba\x80\x00\u07d4\xce\xc6\xfce\x85?\x9c\xce_\x8e\x84Fv6.\x15y\x01_\x02\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xce\xd3\u01fe\x8d\xe7XQ@\x95*\xebP\x1d\xc1\xf8v\ucbf0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xce\xd8\x1e\xc3S?\xf1\xbf\xeb\xf3\xe3\x84>\xe7@\xad\x11u\x8d>\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xce\u0733\xa1\u0584?\xb6\xbe\xf6Ca}\xea\U000cf398\xdd_\x89\x19\xe2\xa4\xc8\x18\xb9\x06\x00\x00\u07d4\xce\xe6\x99\xc0pzx6%+)/\x04|\xe8\xad(\x9b/U\x89\x11\x9a\x1e!\xaaiV\x00\x00\u07d4\xce\xedG\xca[\x89\x9f\xd1b?!\xe9\xbdM\xb6Z\x10\u5c1d\x89\a8w@L\x1e\xee\x00\x00\u07d4\xce\xf7tQ\u07e2\xc6C\xe0\v\x15mlo\xf8N#s\xebf\x89\n1\x06+\xee\xedp\x00\x00\u07d4\xcf\x11i\x04\x1c\x17E\xe4[\x17$5\xa2\xfc\x99\xb4\x9a\xce+\x00\x89\x01\xbb\x88\xba\xab-|\x00\x00\xe0\x94\xcf\x15v\x12vN\x0f\u0596\xc8\xcb_\xba\x85\xdfL\r\xdc<\xb0\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u0794\xcf\x1b\xdby\x9b.\xa6<\xe14f\x8b\xdc\x19\x8bT\x84\x0f\x18\v\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xcf\"\x88\xefN\xbf\x88\xe8m\xb1=\x8a\x0e\v\xf5*\x05e\x82\u00c9\x89Po\xbf\x97@t\x00\x00\u07d4\xcf&Ni%\x13\t\x06\xc4\xd7\xc1\x85\x91\xaaA\xb2\xa6\u007foX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf&\xb4{\xd04\xbcP\x8elK\xcf\xd6\xc7\xd3\x004\x92Wa\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xcf.*\xd65\xe9\x86\x1a\xe9\\\xb9\xba\xfc\xca\x03kR\x81\xf5\u038a\at2!~h6\x00\x00\x00\u07d4\xcf.s@B\xa3U\xd0_\xfb.9\x15\xb1h\x11\xf4Zi^\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf4\x8f/\xe4{~A<\az{\xaf:u\xfb\xf8B\x86\x92\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xcf?\x91(\xb0r\x03\xa3\xe1\r}WU\xc0\u012b\xc6\xe2\xca\u008a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xcf?\xbf\xa1\xfd2\u05e6\xe0\xe6\xf8\xefN\xabW\xbe4\x02\\L\x899\xa1\xc0\xf7YMH\x00\x00\u07d4\xcfAftn\x1d;\xc1\xf8\xd0qK\x01\xf1~\x8ab\xdf\x14d\x896w\x03n\xdf\n\xf6\x00\x00\u07d4\xcfO\x118\xf1\xbdk\xf5\xb6\u0505\xcc\xe4\xc1\x01\u007f\u02c5\xf0}\x89/\u043cw\xc3+\xff\x00\x00\u07d4\xcfZo\x9d\xf7Uy\xc6D\xf7\x94q\x12\x15\xb3\rw\xa0\xce@\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcf^\x0e\xac\u0473\x9d\x06U\xf2\xf7u5\xeff\b\xeb\x95\v\xa0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xcfhM\xfb\x83\x04r\x93U\xb5\x83\x15\xe8\x01\x9b\x1a\xa2\xad\x1b\xac\x89\x17r$\xaa\x84Lr\x00\x00\u07d4\xcfi@\x81\xc7m\x18\xc6L\xa7\x13\x82\xbe\\\xd6;<\xb4v\xf8\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xcfnR\xe6\xb7t\x80\xb1\x86~\xfe\xc6Dm\x9f\xc3\xcc5w\xe8\x89\f\t\x01\xf6\xbd\x98y\x00\x00\u07d4\u03c8: 2\x96g\xea\"j\x1e\x9a\x92*\x12\xf2\x1f\xaa\x03\x81V\x91\x8cO\u02dc\x89\x04E\x91\xd6\u007f\xec\xc8\x00\x00\u07d4\xcf\xf7\xf8\x9aMB\x19\xa3\x82\x95%\x131V\x82\x10\xff\xc1\xc14\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xcf\xf8\xd0k\x00\xe3\xf5\f\x19\x10\x99\xadV\xbaj\xe2eq\u0348\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xcf\xfcI\xc1x~\ubcb5l\xab\xe9$\x04\xb66\x14}EX\x8a\x013\xe00\x8f@\xa3\u0680\x00\u07d4\xd0\bQ;'`J\x89\xba\x17c\xb6\xf8L\u6233F\x94[\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\x0f\x06r\x86\xc0\xfb\u0402\xf9\xf4\xa6\x10\x83\xecv\u07b3\xce\xe6\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\x15\xf6\xfc\xb8M\xf7\xbbA\x0e\x8c\x8f\x04\x89J\x88\x1d\xca\xc27\x898E$\xccp\xb7x\x00\x00\u07d4\xd0\x1a\xf9\x13O\xafRW\x17N\x8by\x18oB\xee5Nd-\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0!\b\u04ae<\xab\x10\xcb\xcf\x16W\xaf\">\x02|\x82\x10\xf6\x89lm\x84\xbc\xcd\xd9\xce\x00\x00\u07d4\xd0*\xfe\u03ce.\u00b6*\u022d Aa\xfd\x1f\xaew\x1d\x0e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd01\x919\xfb\xab.\x8e*\xcc\xc1\xd9$\u0531\x1d\xf6ilZ\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd07\xd2\x15\xd1\x1d\x1d\xf3\xd5O\xbd2\x1c\u0495\xc5F^';\x89K\xe4\xe7&{j\xe0\x00\x00\u07d4\xd0:-\xa4\x1e\x86\x8e\xd3\xfe\xf5t[\x96\xf5\xec\xa4b\xffo\u0689\xa2\xa1]\tQ\x9b\xe0\x00\x00\xe0\x94\xd0?\xc1eWj\xae\xd5%\xe5P,\x8e\x14\x0f\x8b.\x86\x969\x8a\x01sV\u0633%\x01\xc8\x00\x00\u07d4\xd0C\xa0\x11\xecBp\xee~\u0239hsu\x15\xe5\x03\xf80(\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd0K\x86\x1b=\x9a\xccV:\x90\x16\x89\x94\x1a\xb1\xe1\x86\x11a\xa2\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd0ZD|\x91\x1d\xbb'[\xfb.Z7\xe5\xa7\x03\xa5o\x99\x97\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd0_\xfb+t\xf8g O\xe51e;\x02H\xe2\x1c\x13TN\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0bX\x81q\u03d9\xbb\xebX\xf1&\xb8p\xf9\xa3r\x8da\xec\x89\xf3\xf2\v\x8d\xfai\xd0\x00\x00\u07d4\xd0c\x8e\xa5q\x89\xa6\xa6\x99\x02J\u05ccq\xd99\xc1\xc2\xff\x8c\x89\x8e\xaeVg\x10\xfc \x00\x00\xe0\x94\xd0d\x8aX\x1b5\b\xe15\xa2\x93]\x12\xc9epE\xd8q\u028a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4\xd0q\x19)f\xebi\xc3R\x0f\xca:\xa4\xdd\x04)~\xa0KN\x89\x05\xf6\x8e\x811\xec\xf8\x00\x00\u07d4\xd0q\x85 \xea\xe0\xa4\xd6-p\xde\x1b\xe0\xcaC\x1c^\xea$\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd0w]\xba*\xf4\xc3\n:x6Y9\xcdq\xc2\xf9\u0795\u0489i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xd0{\xe0\xf9\t\x97\xca\xf9\x03\u022c\x1dS\xcd\xe9\x04\xfb\x19\aA\x8968\x908\xb6\x99\xb4\x00\x00\u07d4\xd0~Q\x18d\xb1\u03d9i\xe3V\x06\x02\x82\x9e2\xfcNq\xf5\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\u0400\x94\x98\xc5H\x04z\x1e**\xa6\xa2\x9c\xd6\x1a\x0e\xe2h\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0402'_tZ,\xac\x02v\xfb\xdb\x02\u0532\xa3\xab\x17\x11\xfe\x89\x01\xa0Ui\r\x9d\xb8\x00\x00\u07d4\u040f\xc0\x9a\x000\xfd\t(\xcd2\x11\x98X\x01\x82\xa7j\xae\x9f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0413\xe8)\x81\x9f\xd2\xe2[\x978\x00\xbb=XA\xdd\x15-\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0414J\xa1\x85\xa13pa\xae \u071d\xd9l\x83\xb2\xbaF\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u0416V[|t\a\xd0e6X\x03U\xfd\xd6\xd29\x14J\xa1\x89\r\x8drkqw\xa8\x00\x00\u07d4\u041c\xb2\xe6\b-i:\x13\xe8\xd2\xf6\x8d\xd1\u0744a\xf5X@\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u0426\xc6\xf9\xe9\u0133\x83\xd7\x16\xb3\x1d\xe7\x8dVAM\xe8\xfa\x91\x89\x10CV\x1a\x88)0\x00\x00\u07d4\u0427 \x9b\x80\xcf`\xdbb\xf5}\n]}R\x1ai`fU\x89\b\xacr0H\x9e\x80\x00\x00\xe0\x94\u0428\xab\xd8\n\x19\x9bT\xb0\x8be\xf0\x1d \x9c'\xfe\xf0\x11[\x8a\x01a\xc6&\xdca\xa2\xef\x80\x00\xe0\x94\u042b\xccp\xc0B\x0e\x0e\x17/\x97\xd4;\x87\xd5\xe8\f3n\xa9\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u042es]\x91^\x94hf\xe1\xfe\xa7~^\xa4f\xb5\xca\xdd\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0431\x1do+\u0394^\fjP \u00f5'S\xf8\x03\xf9\u0449\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xd0\xc1\x01\xfd\x1f\x01\xc6?k\x1d\x19\xbc\x92\r\x9f\x93#\x14\xb16\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd0\xc5Z\xbf\x97o\xdc=\xb2\xaf\u9f99\u0519HMWl\x02\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd0\u0422\xadE\xf5\x9a\x9d\xcc\u0195\xd8_%\xcaF\xed1\xa5\xa3\x89-\x89W}}@ \x00\x00\u07d4\xd0\xd6,G\xea`\xfb\x90\xa3c\x92\t\xbb\xfd\xd4\xd93\x99\x1c\u0189\n\x84Jt$\xd9\xc8\x00\x00\u07d4\xd0\xdbEax o\\D0\xfe\x00Pc\x90<=zI\xa7\x89&I\x1eE\xa7S\xc0\x80\x00\u07d4\xd0\xe1\x94\xf3K\x1d\xb6\t(\x85\t\xcc\xd2\xe7;a1\xa2S\x8b\x8965f3\xeb\xd8\xea\x00\x00\u07d4\xd0\xe3^\x04vF\xe7Y\xf4Qp\x93\xd6@\x86BQ\u007f\bM\x89\u054f\xa4h\x18\xec\u02c0\x00\u07d4\xd0\xeeM\x02\xcf$8,0\x90\xd3\xe9\x95`\xde6xs\\\u07c9\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\xd0\xf0OR\x10\x9a\xeb\xec\x9a{\x1e\x932v\x1e\x9f\xe2\xb9{\xb5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd0\xf9Yx\x11\xb0\xb9\x92\xbb}7W\xaa%\xb4\xc2V\x1d2\xe2\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\x03\x02\xfa\xa1\x92\x9a2i\x04\xd3v\xbf\v\x8d\xc9:\xd0LL\x89a\t=|,m8\x00\x00\xe0\x94\xd1\x10\r\xd0\x0f\xe2\xdd\xf1\x81c\xad\x96M\vi\xf1\xf2\xe9e\x8a\x8a\x01C\x12\tU\xb2Pk\x00\x00\u07d4\xd1\x16\xf3\xdc\xd5\xdbtK\xd0\b\x88v\x87\xaa\x0e\xc9\xfdr\x92\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd1\x19A|Fs,\xf3M\x1a\x1a\xfby\xc3\xe7\xe2\u034e\xec\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1-w\xae\x01\xa9-5\x11{\xacpZ\xac\u0642\xd0.t\xc1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd15yK\x14\x9a\x18\xe1G\xd1nb\x1ai1\xf0\xa4\n\x96\x9a\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xd1C%8\xe3[vd\x95j\u4563*\xbd\xf0A\xa7\xa2\x1c\x8a\x04+\xf0kx\xed;P\x00\x00\u07d4\xd1C\x82g#\x17\x04\xfcr\x80\xd5c\xad\xf4v8D\xa8\a\"\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd1S\x8e\x9a\x87\u5729\xec\x8eX&\xa5\xb7\x93\xf9\x9f\x96\xc4\u00c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd1d\x85\x03\xb1\xcc\u0178\xbe\x03\xfa\x1e\xc4\xf3\xee&~j\xdf{\x8a\x01;\xef\xbfQ\xee\xc0\x90\x00\x00\xe0\x94\xd1h,!Y\x01\x8d\xc3\xd0\u007f\b$\n\x8c`m\xafe\xf8\xe1\x8a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xd1q\xc3\xf2%\x8a\xef5\xe5\x99\xc7\xda\x1a\xa0s\x00#M\xa9\xa6\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1w\x8c\x13\xfb\xd9h\xbc\b<\xb7\xd1\x02O\xfe\x1fI\xd0,\xaa\x89\xd9\xec\xb4\xfd \x8eP\x00\x00\u07d4\xd1\u007f\xbe\"\xd9\x04b\xed7(\x06p\xa2\xea\v0\x86\xa0\xd6\u0589\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\u0441\x1cU\x97i\x80\xf0\x83\x90\x1d\x8a\r\xb2i\"-\xfb\\\xfe\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\u044e\xb9\xe1\u0485\u06be\x93\xe5\u053a\xe7k\xee\xfeC\xb5!\xe8\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\u0453\xe5\x83\xd6\a\x05c\xe7\xb8b\xb9aJG\u9509\xf3\xe5\x8965f3\xeb\xd8\xea\x00\x00\u07d4\u0457\x8f.4@\u007f\xab\x1d\xc2\x18=\x95\xcf\xdab`\xb3Y\x82\x89*\xb7\xb2`\xff?\xd0\x00\x00\u07d4\u045c\xaf9\xbb7\u007f\xdf,\xf1\x9b\xd4\xfbRY\x1c&1\xa6<\x8965\u026d\xc5\u07a0\x00\x00\u0794\u0463\x96\xdc\u06b2\xc7IA0\xb3\xfd0x 4\r\xfd\x8c\x1f\x88\xf9\"P\xe2\xdf\xd0\x00\x00\xe0\x94\u0467\x1b-\bX\xe82p\b]\x95\xa3\xb1T\x96P\x03^#\x8a\x03'\xbb\t\xd0j\xa8P\x00\x00\u07d4\u046c\xb5\xad\xc1\x189s%\x8dk\x85$\xff\xa2\x8f\xfe\xb2=\xe3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u0473\u007f\x03\xcb\x10t$\xe9\xc4\xddW\\\xcdOL\xeeW\xe6\u0349lk\x93[\x8b\xbd@\x00\x00\u07d4\u0475\xa4T\xac4\x05\xbbAy \x8cl\x84\xde\x00k\u02db\xe9\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\xc4YT\xa6+\x91\x1a\xd7\x01\xff.\x90\x13\x1e\x8c\xeb\x89\xc9\\\x89K\x91\xa2\xdeE~\x88\x00\x00\u07d4\xd1\xc9np\xf0Z\xe0\xe6\xcd`!\xb2\b7P\xa7q|\xdeV\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\u0571\u007f\xfe-{\xbby\xcc}y0\xbc\xb2\xe5\x18\xfb\x1b\xbf\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd1\xda\f\x8f\xb7\xc2\x10\xe0\xf2\xeca\x8f\x85\xbd\xae}>sK\x1c\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd1\xddy\xfb\x15\x81`\xe5\xb4\xe8\xe2?1.j\x90\u007f\xbcMN\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd1\xdeZ\xad:_\xd8\x03\U00071bb6\x10<\xb8\xe1O\xe7#\xb7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd1\xe1\xf2\xb9\xc1l0\x98t\xde\xe7\xfa\xc3&u\xaf\xf1)\u00d8\x89\x03\xf2M\x8eJ\x00p\x00\x00\xe0\x94\xd1\xe5\xe24\xa9\xf4Bf\xa4\xa6$\x1a\x84\u05e1\xa5Z\u0567\xfe\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd1\xeaMr\xa6{[>\x0f1UY\xf5+\xd0aMq0i\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd1\xee\x90YW\xfe|\xc7\x0e\xc8\xf2\x86\x8bC\xfeG\xb1?\xeb\xff\x89\x02b\x9ff\xe0\xc50\x00\x00\u07d4\xd1\xf1iM\"g\x1bZ\xadj\x94\x99\\6\x9f\xbea3go\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd1\xf4\xdc\x1d\u06ca\xbb\x88H\xa8\xb1N%\xf3\xb5Z\x85\x91\xc2f\x89\r\x8drkqw\xa8\x00\x00\u07d4\xd1\xfe\u042e\xe6\xf5\xdf\xd7\xe2Wi%L<\xfa\xd1Z\xde\u032a\x89'\x92\xc8\xfcKS(\x00\x00\u07d4\xd2\x05\x1c\xb3\xcbg\x04\xf0T\x8c\u0210\xab\n\x19\xdb4\x15\xb4*\x89\x12\x1b.^ddx\x00\x00\u07d4\xd2\x06\xaa\u07736\xd4^yr\xe9<\xb0uG\x1d\x15\x89{]\x89 \x86\xac5\x10R`\x00\x00\u07d4\xd2\tH+\xb5I\xab\xc4w{\xeam\u007fe\x00b\xc9\xc5z\x1c\x89\x11e\x1a\xc3\xe7\xa7X\x00\x00\u07d4\xd2\r\xcb\vxh+\x94\xbc0\x00(\x14H\xd5W\xa2\v\xfc\x83\x890\x84\x9e\xbe\x166\x9c\x00\x00\u07d4\xd2\x10{57&\u00e2\xb4ef\xea\xa7\xd9\xf8\v]!\xdb\xe3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd2\x11\xb2\x1f\x1b\x12\xb5\ta\x81Y\r\xe0~\xf8\x1a\x89S~\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd2\x18\xef\xb4\u06d8\x1c\xddjy\u007fK\u050c|&)<\xeb@\x89\xa1Fk1\xc6C\x1c\x00\x00\xe0\x94\xd2\x1asA\xeb\x84\xfd\x15\x10T\xe5\u31fb%\xd3nI\x9c\t\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xd2$\xf8\x80\xf9G\x9a\x89\xd3/\t\xe5+\u9432\x88\x13\\\xef\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xd2/\f\xa4\xcdG\x9ef\x17u\x05;\xccI\xe3\x90\xf6p\u074a\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd21\x92\x975\x13!\x02G\x1b\xa5\x90\a\xb6dL\xc0\xc1\xde>\x8967\tlK\xcci\x00\x00\u07d4\xd25\xd1\\\xb5\xec\xee\xbba)\x9e\x0e\x82\u007f\xa8'H\x91\x1d\x89\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd2:$\xd7\xf9F\x83C\xc1C\xa4\x1ds\xb8\x8f|\xbec\xbe^\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd2=z\xff\xac\xdc>\x9f=\xaez\xfc\xb4\x00oX\xf8\xa4F\x00\x89\xc3(\t>a\xee@\x00\x00\u07d4\xd2C\x18L\x80\x1e]y\xd2\x06?5x\u06ee\x81\u7ce9\u02c9k\u0722h\x1e\x1a\xba\x00\x00\u07d4\xd2KfD\xf49\xc8\x05\x1d\xfcd\u04c1\xb8\xc8lu\xc1u8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd2K\xf1--\xdfE}\xec\xb1xt\xef\xde R\xb6\\\xbbI\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\xe0\x94\xd2Q\xf9\x03\xae\x18rrY\xee\xe8A\xa1\x89\xa1\xf5i\xa5\xfdv\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd2R\x96\v\v\xf6\xb2\x84\x8f\u07ad\x80\x13m\xb5\xf5\a\xf8\xbe\x02\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd2X\x1aU\xce#\xab\x10\u062d\x8cD7\x8fY\a\x9b\xd6\xf6X\x8a\x01\xdd\f\x88_\x9a\r\x80\x00\x00\u07d4\xd2Z\xec\xd7\xeb\x8b\xd64[\x06;]\xbd'\x1cw\xd3QD\x94\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xd2|#O\xf7\xac\xca\xce=\x99g\b\xf8\xf9\xb0Ip\xf9}6\x89Hz\x9a0E9D\x00\x00\u07d4\u0482\x98RM\xf5\xecK$\xb0\xff\xb9\u07c5\x17\n\x14Z\x9e\xb5\x89\x0f\x98\xa3\xb9\xb37\xe2\x00\x00\xe0\x94\u0483\xb8\xed\xb1\n%R\x8aD\x04\xde\x1ce\xe7A\r\xbc\xaag\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\u0484\xa5\x03\x82\xf8:am9\xb8\xa9\xc0\xf3\x96\xe0\ubfe9]\x8966\xc2^f\xec\xe7\x00\x00\u07d4\u0488\xe7\xcb{\xa9\xf6 \xab\x0ftR\xe5\bc=\x1cZ\xa2v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u049d\xc0\x8e\xfb\xb3\xd7.&?x\xabv\x10\xd0\"m\xe7k\x00\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\u04a00\xac\x89R2_\x9e\x1d\xb3x\xa7\x14\x85\xa2N\x1b\a\xb2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u04a4y@CG\xc5T:\xab)*\xe1\xbbJo\x15\x83W\xfa\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\u04a5\xa0$#\nW\xcc\xc6fv\v\x89\xb0\xe2l\xaf\u0449\u01ca\n\x96YZ\\n\x8a?\x80\x00\u07d4\u04a8\x03'\xcb\xe5\\L{\xd5\x1f\xf9\xdd\xe4\xcad\x8f\x9e\xb3\xf8\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\u04a8Oug\\b\xd8\f\x88ulB\x8e\xee+\xcb\x18T!\x89A\rXj \xa4\xc0\x00\x00\u07d4\u04ab\xd8J\x18\x10\x93\xe5\xe2)\x13oB\xd85\xe8#]\xe1\t\x89\x05k\xe0<\xa3\xe4}\x80\x00\u07d4\u04ac\r:X`^\x1d\x0f\x0e\xb3\xde%\xb2\xca\xd1)\xed`X\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u04bfg\xa7\xf3\xc6\xceV\xb7\xbeAg]\xbb\xad\xfe~\xa9:3\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd2\xdb\xeb\xe8\x9b\x03W\xae\xa9\x8b\xbe\x8eIc8\u07bb(\xe8\x05\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd2\xe2\x1e\xd5hh\xfa\xb2\x8e\tG\x92z\xda\xf2\x9f#\xeb\xadl\x89l\x18O\x13U\xd0\xe8\x00\x00\u07d4\xd2\xe8\x17s\x8a\xbf\x1f\xb4\x86X?\x80\xc3P1\x8b\xed\x86\f\x80\x89\r\x02\xce\xcf_]\x81\x00\x00\u07d4\xd2\xed\xd1\xdd\xd6\xd8m\xc0\x05\xba\xebT\x1d\"\xb6@\xd5\xc7\xca\xe5\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd2\xf1\x99\x8e\x1c\xb1X\f\xecOl\x04}\xcd=\xce\xc5L\xf7<\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd2\xf2A%]\xd7\xc3\xf7<\a\x040q\xec\b\xdd\xd9\xc5\xcd\xe5\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd2\xffg \x16\xf6;/\x859\x8fJo\xed\xbb`\xa5\r<\u0389\x12\x91$o[sJ\x00\x00\u07d4\xd3\rLC\xad\xcfU\xb2\xcbS\u0583#&A4I\x8d\x89\u038965\u026d\xc5\u07a0\x00\x00\u07d4\xd3\x0e\xe9\xa1+Mh\xab\xac\xe6\xba\u029a\u05ff\\\xd1\xfa\xf9\x1c\x89QO\xcb$\xff\x9cP\x00\x00\u07d4\xd3\x11\x8e\xa3\xc85\x05\xa9\u0613\xbbg\xe2\xde\x14-Sz>\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd3\x11\xbc\u05eaN\x9bO8?\xf3\xd0\u05b6\xe0~!\xe3p]\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd3\x15\xde\xea\x1d\x8c\x12q\xf9\xd11\x12c\xabG\xc0\a\xaf\xb6\xf5\x89\x03\xc8\x1dNeK@\x00\x00\u07d4\xd3+,y\xc3dx\xc5C\x19\x01\xf6\xd7\x00\xb0M\xbe\x9b\x88\x10\x89\x15w\x9a\x9d\xe6\xee\xb0\x00\x00\u07d4\xd3+EVF\x14Ql\x91\xb0\u007f\xa9\xf7-\xcfx|\xceN\x1c\x89\x0f\xc6o\xae7F\xac\x00\x00\u07d4\xd30r\x811\xfe\x8e:\x15Hz4W<\x93E~*\xfe\x95\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd31\xc8#\x82Z\x9eRc\xd0R\u0611]M\xcd\xe0z\\7\x89\x1e\x93\x12\x83\xcc\xc8P\x00\x00\u07d4\xd33btE\xf2\u05c7\x90\x1e\xf3;\xb2\xa8\xa3g^'\xff\xec\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd3<\xf8+\xf1LY&@\xa0\x86\b\x91L#py\u057e4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd3Mp\x8ds\x98\x02E3\xa5\xa2\xb20\x9b\x19\xd3\xc5Qq\xbb\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd3N\x03\xd3j+\xd4\u045a_\xa1b\x18\xd1\xd6\x1e?\xfa\v\x15\x89\x11X\xe4`\x91=\x00\x00\x00\u07d4\xd3Pu\xcaa\xfeY\xd1#\x96\x9c6\xa8-\x1a\xb2\xd9\x18\xaa8\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xd3g\x00\x9a\xb6X&;b\xc23:\x1c\x9eA@I\x8e\x13\x89\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd3g\x9aG\xdf-\x99\xa4\x9b\x01\u024d\x1c>\f\x98|\xe1\xe1X\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\xe0\x94\u04cf\xa2\xc4\xcc\x14z\xd0j\u0562\xf7Uy(\x1f\"\xa7\xcc\x1f\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\u04da]\xa4`9+\x94\v\u01ee8\xf1e\u007f\x8a\x01f\xc5H\b\x89\xdbw\x00\x00\xe0\x94\xd3\xd6\xe9\xfb\x82T/\u049e\xd9\xea6\t\x89\x1e\x15\x13\x96\xb6\xf7\x8a\voX\x8a\xa7\xbc\xf5\xc0\x00\x00\xe0\x94\xd3\xda\u0476\u040dE\x81\u032ee\xa8s-\xb6\xaci\xf0\u019e\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xd3\xdf;S\xcb;GU\xdeT\xe1\x80E\x1c\xc4L\x9e\x8a\u0a89#\u0114\t\xb9w\x82\x80\x00\u07d4\xd3\xf8s\xbd\x99V\x13W\x89\xab\x00\xeb\xc1\x95\xb9\"\xe9K%\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4\x02\xb4\xf6\xa0\x99\xeb\xe7\x16\xcb\x14\xdfOy\xc0\xcd\x01\xc6\a\x1b\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd4\r\x00U\xfd\x9a8H\x8a\xff\x92?\xd0=5\xecF\xd7\x11\xb3\x8a\x01\x0f\b\xed\xa8\xe5U\t\x80\x00\u07d4\xd4\x0e\xd6j\xb3\xce\xff$\xca\x05\xec\xd4q\ufd12\xc1__\xfa\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd4\x18\x87\v\xc2\xe4\xfa{\x8aa!\xae\br\xd5RG\xb6%\x01\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xd4\x1d\u007f\xb4\x9f\xe7\x01\xba\xac%qpBl\u0273\x8c\xa3\xa9\xb2\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4\xd4 U\x92\x84@U\xb3\u01e1\xf8\f\xef\xe3\xb8\xebP\x9b\xcd\xe7\x89\t\xb3\xbf\xd3B\xa9\xfc\x80\x00\u07d4\xd4+ \xbd\x03\x11`\x8bf\xf8\xa6\xd1[*\x95\xe6\xde'\u017f\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd44O}\\\xade\xd1~\\-\x0es#\x94=ob\xfe\x92\x89\x0e~\xeb\xa3A\vt\x00\x00\u07d4\xd4>\xe48\xd8=\xe9\xa3ub\xbbN(l\xb1\xbd\x19\xf4\x96M\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd4C4\xb4\xe2:\x16\x9a\f\x16\xbd!\xe8f\xbb\xa5-\x97\x05\x87\x89\x8c\xf2?\x90\x9c\x0f\xa0\x00\x00\xe0\x94\xd4M\x81\xe1\x8fF\xe2\u03f5\xc1\xfc\xf5\x04\x1b\xc8V\x97g\xd1\x00\x8a\a\xb4B\xe6\x84\xf6Z\xa4\x00\x00\u07d4\xd4OJ\xc5\xfa\xd7k\xdc\x157\xa3\xb3\xafdr1\x9bA\r\x9d\x89V\xbcu\xe2\xd61\x00\x00\x00\u07d4\xd4O^\xdf+\xcf$3\xf2\x11\xda\xdd\f\xc4P\xdb\x1b\x00\x8e\x14\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xd4Oj\u00d2;_\xd71\xa4\xc4YD\xecO~\xc5*j\xe4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd4[3A\xe8\xf1\\\x802\x93 \u00d7~;\x90\xe7\x82j~\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xd4]]\xaa\x13\x8d\xd1\xd3t\xc7\x1b\x90\x19\x91h\x11\xf4\xb2\nN\x89\x1f9\x9b\x148\xa1\x00\x00\x00\u07d4\xd4`\xa4\xb9\b\xdd+\x05gY\xb4\x88\x85\vf\xa88\xfcw\xa8\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd4g\xcf\x06L\bq\x98\x9b\x90\u0632\xeb\x14\xcc\xc6;6\b#\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd4k\xaea\xb0'\xe5\xbbB.\x83\xa3\xf9\xc9?<\x8f\xc7}'\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4o\x82#E)\x82\xa1\xee\xa0\x19\xa8\x81n\xfc-o\xc0\ah\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xd4uG\u007f\xa5c\x90\xd30\x17Q\x8dg\x11\x02\u007f\x05\U0008dfc9k\x11\x133\xd4\xfdL\x00\x00\u07d4\xd4|$.\xdf\xfe\xa0\x91\xbcT\xd5}\xf5\xd1\xfd\xb91\x01Gl\x89\x9d\xf7\u07e8\xf7`H\x00\x00\u07d4\xd4}\x86\x85\xfa\xee\x14|R\x0f\u0646p\x91u\xbf/\x88k\xef\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd4\u007fP\u07c9\xa1\xcf\xf9e\x13\xbe\xf1\xb2\xae:)q\xac\xcf,\x89-\x89W}}@ \x00\x00\u07d4\u0502\xe7\xf6\x8eA\xf28\xfeQx)\xde\x15G\u007f\xe0\xf6\xdd\x1d\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u0507\x9f\xd1+\x1f:'\xf7\xe1\tv\x1b#\xca4\xfa#\x06K\x1c\xaf\x00Qn(pJ\x82\xa4\xf8\x89Hz\x9a0E9D\x00\x00\u07d4\xd5\x00\xe4\xd1\u0242K\xa9\xf5\xb65\u03e3\xa8\xc2\u00cb\xbdL\xed\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd5\b\u04dcp\x91oj\xbcL\xc7\xf9\x99\xf0\x11\xf0w\x10X\x02\x89\x05rM$\xaf\xe7\u007f\x00\x00\u07d4\xd5\x0f\u007f\xa0>8\x98v\u04d0\x8b`\xa57\xa6pc\x04\xfbV\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xd5\x13\xa4P\x80\xff/\xeb\xe6,\u0545J\xbe)\xeeDg\xf9\x96\x89\bN\x13\xbcO\xc5\xd8\x00\x00\u07d4\xd5'o\f\xd5\xff\xd5\xff\xb6?\x98\xb5p=U\x94\xed\xe0\x83\x8b\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xd5)KfbB0;m\xf0\xb1\u020d7B\x9b\xc8\xc9e\xaa\x89\x10M\r\x00\u04b7\xf6\x00\x00\u07d4\xd5*\xec\xc6I98\xa2\x8c\xa1\xc3g\xb7\x01\xc2\x15\x98\xb6\xa0.\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\xd5\x99x\xee \xa3\x8c?I\x8dc\xd5\u007f1\xa3\x9fj\x06\x8a\x022\xb3o\xfcg*\xb0\x00\x00\u07d4\u05568\xd3\xc5\xfa\xa7q\x1b\xf0\x85t_\x9d[\xdc#\u0518\u0609lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u055d\x92\xd2\xc8p\x19\x80\xcc\a<7]r\n\xf0dt<\f\x8a\x04\x05\xfd\xf7\u5bc5\xe0\x00\x00\u07d4\u0567\xbe\xc32\xad\xde\x18\xb3\x10KW\x92Tj\xa5\x9b\x87\x9bR\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0571\x17\xec\x11n\xb8FA\x89a\xeb~\xdbb\x9c\xd0\xddi\u007f\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\u0572\x84\x04\x010\xab\xf7\xc1\xd1cq#q\xcc~(\xadf\u0689j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u0579\xd2w\u062a\xd2\x06\x97\xa5\x1fv\xe2\tx\x99k\xff\xe0U\x89\a\xc3\xfe<\aj\xb5\x00\x00\u07d4\u057d^\x84U\xc10\x16\x93W\xc4q\xe3\u6077\x99jrv\x89-\x9e(\x8f\x8a\xbb6\x00\x00\u07d4\xd5\u02e5\xb2k\xea]s\xfa\xbb\x1a\xba\xfa\xcd\xef\x85\xde\xf3h\u0309\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd5\xceU\u0476/YC\xc0?\x89\b\xe3\x1f\xe1h\x9d\x8a\x00\x00\u07d4\xd6\x06Q\xe3\x93x4#\xe5\xcc\x1b\xc5\xf8\x89\xe4N\xf7\xea$>\x89\x15\x9ev7\x11)\xc8\x00\x00\u07d4\xd6\t\xbfO\x14n\xeak\r\xc8\xe0m\xdc\xf4D\x8a\x1f\xcc\xc9\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\t\xec\v\xe7\r\n\xd2ong\xc9\xd4v+R\xeeQ\x12,\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd6\nRX\a(R\r\xf7Tk\xc1\xe2\x83)\x17\x88\u06ee\f\x8964\x89\xef?\xf0\xd7\x00\x00\u07d4\xd6\v$s!\xa3*Z\xff\xb9k\x1e'\x99'\xccXM\xe9C\x89z\xd0 \xd6\xdd\xd7v\x00\x00\u07d4\xd6\x11\x02v\xcf\xe3\x1eB\x82ZW\u007fkC]\xbc\xc1\f\xf7d\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd6\x12Y{\xc3\x17C\u01c63\xf63\xf29\xb1\xe9Bk\xd9%\x8a\x10\x17\xf7\u07d6\xbe\x17\x80\x00\x00\u07d4\xd6#J\xafE\xc6\xf2.f\xa2%\xff\xb9:\xddb\x9bN\xf8\x0f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd6.\u06d6\xfc\u259a\xaflT^\x96|\xf1\xc0\xbc\x80R\x05\x89\x04\xa5eSjZ\u0680\x00\u07d4\xd60\v2\x15\xb1\x1d\xe7b\xec\xdeKp\xb7\x92}\x01)\x15\x82\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd69]\xb5\xa4\xbbf\xe6\x0fL\xfb\xcd\xf0\x05{\xb4\xd9xb\xe2\x891T\xc9r\x9d\x05x\x00\x00\xe0\x94\xd6J-P\xf8\x85\x857\x18\x8a$\xe0\xf5\r\xf1h\x1a\xb0~\u05ca\b7Z*\xbc\xca$@\x00\x00\u07d4\xd6X\n\xb5\xedL}\xfaPo\xa6\xfed\xad\\\xe1)pw2\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd6Y\x8b\x13\x86\xe9<\\\u02d6\x02\xffK\xbb\xec\xdb\xd3p\x1d\u0109\f%\xf4\xec\xb0A\xf0\x00\x00\u07d4\xd6dM@\xe9\v\xc9\u007f\xe7\xdf\xe7\u02bd2i\xfdW\x9b\xa4\xb3\x89\b\x9e\x91y\x94\xf7\x1c\x00\x00\xe0\x94\xd6g\f\x03m\xf7T\xbeC\xda\u074fP\xfe\xea(\x9d\x06\x1f\u058a\x01D\xa2\x904H\xce\xf7\x80\x00\u07d4\xd6hR:\x90\xf0)=e\xc58\xd2\xddlWg7\x10\x19n\x89\x02$,0\xb8S\xee\x00\x00\u07d4\xd6j\xb7\x92\x94\aL\x8bb}\x84-\xabA\xe1}\xd7\f]\xe5\x8965\u026d\xc5\u07a0\x00\x00\u0794\xd6j\xcc\r\x11\xb6\x89\u03a6\xd9\xea_\xf4\x01L\"J]\xc7\u0108\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xd6m\xdf\x11Y\xcf\"\xfd\x8czK\xc8\u0540wV\xd43\xc4>\x89wC\"\x17\xe6\x83`\x00\x00\u07d4\u0587\xce\xc0\x05\x90\x87\xfd\xc7\x13\xd4\xd2\xd6^w\xda\xef\xed\xc1_\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\u0588\xe7\x85\u024f\x00\xf8K:\xa1S3U\u01e2X\xe8yH\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u05a2.Y\x8d\xab\u04ce\xa6\xe9X\xbdy\u050d\u0756\x04\xf4\u07c965\u026d\xc5\u07a0\x00\x00\u07d4\u05a7\xacM\xe7\xb5\x10\xf0\xe8\xdeQ\x9d\x97?\xa4\xc0\x1b\xa84\x00\x89e\xea=\xb7UF`\x00\x00\u07d4\u05ac\xc2 \xba.Q\xdf\xcf!\xd4C6\x1e\xeav\\\xbd5\u0609\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u05ac\xff\u043f\u065c8.{\xd5o\xf0\xe6\x14J\x9eR\xb0\x8e\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xd6\xc0\u043c\x93\xa6.%qtp\x0e\x10\xf0$\u0232?\x1f\x87\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd6\xcf\\\x1b\u03dd\xa6b\xbc\xea\"U\x90P\x99\xf9\xd6\xe8M\u030a\x01\u011eB\x01W\xd9\xc2\x00\x00\u07d4\xd6\xd05r\xa4RE\xdb\xd46\x8cO\x82\xc9W\x14\xbd!g\xe2\x89?\x00\xc3\xd6f\x86\xfc\x00\x00\u07d4\xd6\xd6wiX\xee#\x14:\x81\xad\xad\xeb\b8 \t\xe9\x96\u0089\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd6\xd9\xe3\x0f\bB\x01*qv\xa9\x17\xd9\xd2\x04\x8c\xa0s\x87Y\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd6\xe0\x9e\x98\xfe\x13\x003!\x04\xc1\xca4\xfb\xfa\xc5T6N\u0649lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\xe8\xe9z\u90db\x9e\xe5\a\xee\xdb(\xed\xfbtw\x03\x149\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd6\uea18\u052e+q\x80'\xa1\x9c\xe9\xa5\xebs\x00\xab\xe3\u0289\x01}J\xce\xeec\u06c0\x00\xe0\x94\xd6\xf1\xe5[\x16\x94\b\x9e\xbc\xb4\xfe}x\x82\xaaf\u0217av\x8a\x04<#\xbd\xbe\x92\x9d\xb3\x00\x00\u07d4\xd6\xf4\xa7\xd0N\x8f\xaf \xe8\xc6\ub15c\xf7\xf7\x8d\xd2=z\x15\x89\a$\xde\xd1\xc7H\x14\x00\x00\u07d4\xd6\xfc\x04F\u01a8\xd4\n\xe3U\x1d\xb7\xe7\x01\xd1\xfa\x87nJI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\x03\u01a4\xf1\x1d`\x19Ey\u054c'f\xa7\xef\x16\xc3\n)\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\x05%\x19uj\xf4%\x90\xf1S\x91\xb7#\xa0?\xa5d\xa9Q\x89\xfa61H\r\x01\xfd\x80\x00\u07d4\xd7\na+\xd6\u0769\xea\xb0\xdd\xdc\xffJ\xafA\"\u04cf\xea\xe4\x89\x1dF\x01b\xf5\x16\xf0\x00\x00\u07d4\xd7\n\xd2\xc4\xe9\uefe67\xefV\xbdHj\u04a1\xe5\xbc\xe0\x93\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\x14\f\x8eZC\a\xfa\xb0\xcc'\xba\u0752\x95\x01\x8b\xf8yp\x89\x05\xf1\x01kPv\xd0\x00\x00\u07d4\xd7\x16J\xa2a\xc0\x9a\u0672\xb5\x06\x8dE>\xd8\xebj\xa10\x83\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd7\x1eC\xa4Qw\xadQ\xcb\xe0\xf7!\x84\xa5\xcbP9\x17(Z\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\x1f\xb10\xf0\x15\fVRi\xe0\x0e\xfbC\x90+R\xa4U\xa6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\"W8\xdc\xf3W\x848\xf8\xe7\u0233\x83~B\xe0J&/\x89\x18+\x8c\ubec3\xaa\x00\x00\u07d4\xd7'MP\x80M\x9cw\u0693\xfaH\x01V\xef\xe5{\xa5\x01\u0789i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xd71\xbbk_<79^\t\u03ac\xcd\x14\xa9\x18\xa6\x06\a\x89\x89\u0556{\xe4\xfc?\x10\x00\x00\xe0\x94\xd7>\xd2\u0645\xb5\xf2\x1bU\xb2td;\xc6\xda\x03\x1d\x8e\u074d\x8a\nm\xd9\f\xaeQ\x14H\x00\x00\u07d4\xd7D\xac~S\x10\xbeijc\xb0\x03\xc4\v\xd097\x05a\u0189Z\x87\xe7\xd7\xf5\xf6X\x00\x00\xe0\x94\xd7Jn\x8dj\xab4\u0385\x97h\x14\xc12{\xd6\xea\a\x84\u048a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xd7ZP*[gr\x87G\x0fe\u016aQ\xb8|\x10\x15\x05r\x8910\xb4dc\x85t\x00\x00\u07d4\xd7m\xba\xeb\xc3\rN\xf6{\x03\xe6\xe6\xec\xc6\xd8N\x00MP-\x89mv\xb9\x18\x8e\x13\x85\x00\x00\u07d4\xd7q\xd9\xe0\u028a\b\xa1\x13wW1CN\xb3'\x05\x99\xc4\r\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xd7x\x8e\xf2\x86X\xaa\x06\xccS\xe1\xf3\xf0\xdeX\xe5\xc3q\xbex\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xd7x\x92\xe2';#]v\x89\xe40\xe7\xae\ud73c\xe8\xa1\xf3\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u05c1\xf7\xfc\t\x18F\x11V\x85p\xb4\x98n,r\x87+~\u0409\x01\x15\x95a\x06]]\x00\x00\u07d4\u05c5\xa8\xf1\x8c8\xb9\xbcO\xfb\x9b\x8f\xa8\xc7r{\xd6B\xee\x1c\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u05ce\xcd%\xad\xc8k\xc2\x05\x1d\x96\xf6Sd\x86kB\xa4&\xb7\x89\xd20X\xbf/&\x12\x00\x00\xe0\x94\u05cf\x84\xe3\x89D\xa0\xe0%_\xae\xceH\xbaIP\u053d9\u048a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\u05d4\x83\xf6\xa8DO%I\xd6\x11\xaf\xe0,C-\x15\xe1\x10Q\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u05d85\xe4\x04\xfb\x86\xbf\x84_\xba\t\rk\xa2^\f\x88f\xa6\x89\x82\x1a\xb0\xd4AI\x80\x00\x00\u07d4\u05da\xff\x13\xba-\xa7]F$\f\xac\n$g\xc6V\x94\x98#\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\u05dd\xb5\xabCb\x1az=\xa7\x95\xe5\x89)\xf3\xdd%\xafg\u0649lj\xccg\u05f1\xd4\x00\x00\u07d4\u05e1C\x1e\xe4S\xd1\xe4\x9a\x05P\xd1%hy\xb4\xf5\xd1\x02\x01\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\u05ed\t\xc6\xd3&WhSU\xb5\xc6\uc39fW\xb4\ube42\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u05f7@\xdf\xf8\xc4Wf\x8f\xdft\xf6\xa2f\xbf\xc1\u0737#\xf9\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xd7\u0080>\u05f0\xe0\x83sQA\x1a\x8ef7\xd1h\xbc[\x05\x8a\x06A\xda\xf5\xc9\x1b\xd95\x80\x00\u07d4\xd7\xc6&]\xea\x11\x87l\x90;q\x8eL\u062b$\xfe&[\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xca\u007f\xdc\xfe\xbeE\x88\xef\xf5B\x1d\x15\"\xb6\x13(\xdf{\xf3\x89\xd8\xe6\x00\x1el0+\x00\x00\u07d4\xd7\u037dA\xff\xf2\r\xf7'\xc7\vbU\xc1\xbav\x06\x05Th\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\xd1W\xe4\xc0\xa9d7\xa6\u0485t\x1d\xd2>\xc46\x1f\xa3k\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xd2\xc6\xfc\xa8\xad\x1fu9R\x10\xb5}\xe5\xdf\xd6s\x939\t\x89\x12nr\xa6\x9aP\xd0\x00\x00\xe0\x94\xd7\xd3\xc7Y Y\x048\xb8,>\x95\x15\xbe.\xb6\xedz\x8b\x1a\x8a\f\xb4\x9bD\xba`-\x80\x00\x00\u07d4\xd7\xd7\xf2\u02a4b\xa4\x1b;0\xa3J\xeb;\xa6\x10\x10\xe2bo\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd7\xe7J\xfd\xba\xd5^\x96\u03bcZ7O,\x8bv\x86\x80\xf2\xb0\x89\x05]\xe6\xa7y\xbb\xac\x00\x00\xe0\x94\xd7\xeb\x901b'\x1c\x1a\xfa5\xfei\xe3s\"\u0224\u049b\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xd7\xeb\u0779\xf99\x87w\x9bh\x01U7T8\xdbe\xaf\xcbj\x89\x05t\x1a\xfe\xff\x94L\x00\x00\u07d4\xd7\xef4\x0ef\xb0\u05ef\xcc\xe2\n\x19\xcb{\xfc\x81\xda3\xd9N\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xd7\xf3p\u053e\xd9\xd5|oI\u0259\xder\x9e\xe5i\xd3\xf4\xe4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd7\xfa_\xfb`H\xf9o\xb1\xab\xa0\x9e\xf8{\x1c\x11\xddp\x05\xe4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd8\x06\x9f\x84\xb5!I?G\x15\x03\u007f2&\xb2_3\xb6\x05\x86\x89g\x8a\x93 b\xe4\x18\x00\x00\u0794\xd8\x15\xe1\xd9\xf4\xe2\xb5\xe5~4\x82k|\xfd\x88\x81\xb8Th\x90\x88\xf0\x15\xf2W6B\x00\x00\u07d4\xd8\x1b\xd5K\xa2\xc4Jok\xeb\x15a\u058b\x80\xb5DNm\u0189?\x17\r~\xe4\"\xf8\x9c\x80-1({\x96q\xe8\x1c\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xd8K\x92/xA\xfcWt\xf0\x0e\x14`J\xe0\xdfB\xc8U\x1e\x89\xd9o\u0390\u03eb\xcc\x00\x00\u07d4\xd8U\xb0<\xcb\x02\x9awG\xb1\xf0s\x03\xe0\xa6dy59\u0209lk\x93[\x8b\xbd@\x00\x00\u07d4\xd8_\u07af*a\xf9]\xb9\x02\xf9\xb5\xa5<\x9b\x8f\x92f\u00ec\x89l\xf6Z~\x90G(\x00\x00\u07d4\xd8q^\xf9\x17o\x85\v.0\xeb\x8e8'\a\xf7w\xa6\xfb\xe9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd8t\xb9\u07eeEj\x92\x9b\xa3\xb1\xa2~W,\x9b,\xec\u07f3\x89\t79SM(h\x00\x00\u07d4\u0613\n9\xc7sW\xc3\n\u04e0`\xf0\v\x06\x04c1\xfdb\x89,s\xc97t,P\x00\x00\u07d4\u061b\xc2q\xb2{\xa3\xabib\xc9JU\x90\x06\xae8\xd5\xf5j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0637}\xb9\xb8\x1b\xbe\x90B{b\xf7\x02\xb2\x01\xff\u009f\xf6\x18\x892m\x1eC\x96\xd4\\\x00\x00\u07d4\xd8\xcdd\xe0(N\xecS\xaaF9\xaf\xc4u\b\x10\xb9\u007f\xabV\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd8\xd6C\x84$\x9bwg\x94\x06;V\x98x\xd5\xe3\xb50\xa4\xb2\x89\t\xa0C\u0432\xf9V\x80\x00\u07d4\xd8\xd6T \xc1\x8c#'\xccZ\xf9t%\xf8W\xe4\xa9\xfdQ\xb3\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xd8\xe5\xc9g^\xf4\xde\xed&k\x86\x95o\xc4Y\x0e\xa7\u0522}\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xd8\xe8GB\x92\xe7\xa0Q`L\xa1d\xc0pw\x83\xbb(\x85\xe8\x8a\x02\xd4\xca\x05\xe2\xb4<\xa8\x00\x00\u07d4\xd8\xebxP>\xc3\x1aT\xa9\x016x\x1a\xe1\t\x00Lt2W\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd8\xee\xf4\xcfK\xeb\x01\xee \xd1\x11t\x8ba\xcbM?d\x1a\x01\x89\x94\x89#z\u06daP\x00\x00\u07d4\xd8\xf4\xba\xe6\xf8M\x91\rm}Z\xc9\x14\xb1\xe6\x83r\xf9A5\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xd8\xf6 6\xf0;v5\xb8X\xf1\x10?\x8a\x1d\x90\x19\xa8\x92\xb6\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xd8\xf6e\xfd\x8c\xd5\u00bc\xc6\xdd\xc0\xa8\xaeR\x1eM\u01aa``\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xd8\xf9$\fU\xcf\xf05RB\x80\xc0\x9e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xd8\xfe\b\x8f\xff\u0394\x8fQ7\xee#\xb0\x1d\x95\x9e\x84\xacB#\x89\f[T\xa9O\xc0\x17\x00\x00\u07d4\xd9\x0f0\t\xdbC~N\x11\u01c0\xbe\u0209os\x8de\xef\r\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd9\x10;\xb6\xb6zU\xa7\xfe\xce-\x1a\xf6-E|!x\x94m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd9\x13\xf0w\x19Iu\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xd9D\u0226\x9f\xf2\xca\x12Ii\f\x12)\xc7\x19/6%\x10b\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xd9JW\x88*Rs\x9b\xbe*\x06G\xc8\f$\xf5\x8a+O\x1c\x89H\xb5N*\xdb\xe1+\x00\x00\xe0\x94\xd9SB\x95<\x8a!\xe8\xb65\xee\xfa\u01c1\x9b\xea0\xf1pG\x8a\x13\xf0l\u007f\xfe\xf0]@\x00\x00\u07d4\xd9\\\x90\xff\xbeT\x84\x86G\x80\xb8gIJ\x83\u0212V\xd6\xe4\x89X\xe7\x92n\xe8X\xa0\x00\x00\u07d4\xd9g\x11T\x0e.\x99\x83C\xd4\xf5\x90\xb6\xfc\x8f\xac;\xb8\xb3\x1d\x89_Z@h\xb7\x1c\xb0\x00\x00\u07d4\xd9j\xc2Pt\t\u01e3\x83\xab.\xee\x18\"\xa5\xd78\xb3kV\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xd9m\xb3;{Z\x95\f>\xfa-\xc3\x1b\x10\xba\x10\xa52\uf1c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xd9wYe\xb7\x16Gfu\xa8\xd5\x13\xeb\x14\xbb\xf7\xb0|\xd1J\x8a\x01\x13.m-#\xc5\xe4\x00\x00\u07d4\xd9{\xc8J\xbdG\xc0[\xbfE{.\xf6Y\xd6\x1c\xa5\xe5\u43c9\x06\x9d\x17\x11\x9d\u0168\x00\x00\u07d4\xd9\u007fE&\u07a9\xb1c\xf8\xe8\xe3:k\u03d2\xfb\x90}\xe6\xec\x89\x0feJ\xafM\xb2\xf0\x00\x00\u07d4\xd9\u007f\xe6\xf5?*X\xf6\xd7mu*\xdft\xa8\xa2\xc1\x8e\x90t\x89\x10\xcd\xf9\xb6\x9aCW\x00\x00\u07d4\u0659\x99\xa2I\r\x94\x94\xa50\xca\xe4\xda\xf3\x85T\xf4\xddc>\x89\x06\x81U\xa46v\xe0\x00\x00\u07d4\u065d\xf7B\x1b\x93\x82\xe4,\x89\xb0\x06\xc7\xf0\x87p*\aW\xc0\x89\x1a\x05V\x90\xd9\u06c0\x00\x00\xe0\x94\u0677\x83\xd3\x1d2\xad\xc5\x0f\xa3\xea\u02a1]\x92\xb5h\xea\xebG\x8a\a3\xaf\x907L\x1b(\x00\x00\u07d4\xd9\xd3p\xfe\xc65v\xab\x15\xb3\x18\xbf\x9eX6M\u00a3U*\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xd9\xd4/\xd1>\xbdK\xf6\x9c\xac^\x9c~\x82H:\xb4m\xd7\xe9\x8a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xd9\xe2~\xb0}\xfcq\xa7\x06\x06\f\u007f\a\x928\u0293\xe8\x859\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xd9\xe3\x85~\xfd\x1e *D\x17p\xa7w\xa4\x9d\xccE\xe2\xe0\u04c9\f\x1d\xaf\x81\u0623\xce\x00\x00\u07d4\xd9\xec.\xfe\x99\xff\\\xf0\r\x03\xa81{\x92\xa2J\xefD\x1f~\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xd9\xec\x8f\xe6\x9bw\x16\xc0\x86Z\xf8\x88\xa1\x1b+\x12\xf7 \xed3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xd9\xf1\xb2d\b\xf0\xecg\xad\x1d\ro\xe2.\x85\x15\xe1t\x06$\x89\x01M\x11 \u05f1`\x00\x00\u07d4\xd9\xf5G\xf2\xc1\xde\x0e\u064aS\xd1a\xdfWc]\xd2\x1a\x00\xbd\x89\x05V\xf6L\x1f\xe7\xfa\x00\x00\u07d4\xd9\xff\x11]\x01&l\x9fs\xb0c\xc1\xc28\xef5e\xe6;6\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\xda\x06\x04N)#&\xffil\x0091h\xceF\xff\xac9\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda*\x14\xf9r@\x15\u05d0\x14\xed\x8eY\th\x1dYaH\xf1\x89\x02\xa1\x0f\x0f\x8a\x91\xab\x80\x00\u07d4\xda*\u054ew\xde\xdd\xed\xe2\x18vF\xc4e\x94Z\x8d\xc3\xf6A\x89#\xc7W\a+\x8d\xd0\x00\x00\u07d4\xda0\x17\xc1P\xdd\r\xce\u007f\u03c8\x1b\nH\xd0\xd1\xc7V\xc4\u01c9\x05k\xf9\x1b\x1ae\xeb\x00\x00\u07d4\xda4\xb2\xea\xe3\v\xaf\xe8\xda\xec\xcd\xe8\x19\xa7\x94\u0349\xe0\x95I\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdaJ_U\u007f;\xab9\n\x92\xf4\x9b\x9b\x90\n\xf3\fF\xae\x80\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdaPU7S\u007f\xfb3\xc4\x15\xfe\xc6Ni\xba\xe0\x90\xc5\xf6\x0f\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xdai\x8dd\xc6\\\u007f+,rS\x05\x9c\xd3\u0441\u0619\xb6\xb7\x89\x10\x04\xe2\xe4_\xb7\xee\x00\x00\u07d4\xdaw2\xf0/.'.\xaf(\u07d7.\xcc\r\xde\xed\x9c\xf4\x98\x89\v \xbf\xbfig\x89\x00\x00\u07d4\xdaz\xd0%\xeb\xde%\xd2\"C\u02c3\x0e\xa1\xd3\xf6JVc#\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\u0685]SG\u007fP^\xc4\xc8\xd5\u8ed1\x80\u04c6\x81\x11\x9c\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\u0687^N/<\xab\xe4\xf3~\x0e\xae\xd7\xd1\xf6\xdc\xc6\xff\xefC\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u068b\xbe\xe1\x82\xe4U\xd2\t\x8a\xcb3\x8amE\xb4\xb1~\u0636\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0698.\x96C\xff\xec\xe7#\aZ@\xfewnZ\xce\x04\xb2\x9b\x89\b\xb8\xb6\u0259\x9b\xf2\x00\x00\u07d4\u069fUF\tF\u05ff\xb5p\xdd\xecu|\xa5w;XB\x9a\x89\x1b\x84]v\x9e\xb4H\x00\x00\u07d4\u06a1\xbdz\x91H\xfb\x86\\\xd6\x12\xdd5\xf1b\x86\x1d\x0f;\u0709\xa68\xabr\xd9,\x13\x80\x00\xe0\x94\u06a6<\xbd\xa4]\u0507\xa3\xf1\xcdJtj\x01\xbb^\x06\v\x90\x8a\x01\x04\x16\u0670*\x89$\x00\x00\u07d4\u06a7v\xa6uDi\u05f9&z\x89\xb8g%\xe7@\xda\x0f\xa0\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\u06ac\x91\xc1\xe8Y\xd5\xe5~\xd3\bKP \x0f\x97f\xe2\xc5+\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u06ac\xda\xf4\"&\xd1\\\xb1\u03d8\xfa\x15\x04\x8c\u007fL\xee\xfei\x89\x10CV\x1a\x88)0\x00\x00\xe0\x94\u06b6\xbc\u06c3\xcf$\xa0\xae\x1c\xb2\x1b;[\x83\xc2\xf3\x82I'\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\u06bb\b\x89\xfc\x04)&\xb0^\xf5{% \x91\n\xbcKAI\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06bc\"PB\xa6Y,\xfa\x13\xeb\xe5N\xfaA\x04\bx\xa5\xa2\x89\x0e\x11\xfa\xd5\xd8\\\xa3\x00\x00\u07d4\xda\xc0\xc1w\xf1\x1c\\>>x\xf2\xef\xd6c\xd12!H\x85t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda\xd16\xb8\x81x\xb4\x83zlx\x0f\xeb\xa2&\xb9\x85i\xa9L\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xda\xdb\xfa\xfd\x8bb\xb9*$\xef\xd7RV\u0743\xab\xdb\u05fb\u06c9\x01\x11du\x9f\xfb2\x00\x00\u07d4\xda\xdc\x00\xaby'`\xaa4\x15i\xfa\x9f\xf5\x98&\x84\x85JJ2\x8an\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xda\xe7 \x1e\xab\x8c\x063\x02\x93\ri9)\xd0\u007f\x95\xe7\x19b\x89\x91\xae\xc0(\xb4\x19\x81\x00\x00\u07d4\xda\xed\u052d\x10{'\x1e\x89Hl\xbf\x80\xeb\xd6!\u0757Ex\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x04\xfa\xd9\u011f\x9e\x88\v\xeb\x8f\xcf\x1d:8\x90\u4cc4o\x89CZ\xe6\xcc\fX\xe5\x00\x00\u07d4\xdb\f\u01cft\u0642{\u070ads'n\xb8O\u0717b\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x12\x93\xa5\x06\xe9\f\xad*Y\xe1\xb8V\x1f^f\x96\x1ag\x88\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb\x19\xa3\x98\"06\x8f\x01w!\x9c\xb1\f\xb2Y\u0372%|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb#\xa6\xfe\xf1\xaf{X\x1ew,\xf9\x18\x82\u07b2Qo\xc0\xa7\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb$O\x97\xd9\xc4K\x15\x8a@\xed\x96\x06\xd9\xf7\xbd8\x9131\x89\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xdb(\x8f\x80\xff\xe22\u00baG\u0314\xc7c\xcfo\u0278+\r\x89\x04\x9b\x9c\xa9\xa6\x944\x00\x00\u07d4\xdb*\f\x9a\xb6M\xf5\x8d\u07f1\u06ec\xf8\xba\r\x89\xc8[1\xb4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdb4t^\u0785v\xb4\x99\xdb\x01\xbe\xb7\xc1\xec\u0685\xcfJ\xbe\x89\x04V9\x18$O@\x00\x00\u07d4\xdb?%\x8a\xb2\xa3\xc2\xcf3\x9cD\x99\xf7ZK\xd1\xd3G.\x9e\x89QP\xae\x84\xa8\xcd\xf0\x00\x00\u07d4\xdbK\xc8;\x0ek\xaa\xdb\x11V\xc5\xcf\x06\xe0\xf7!\x80\x8cR\u01c9/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xdbc\x12-\xe7\x03}\xa4\x97\x151\xfa\u9bc5\x86x\x86\u0192\x89\x0f\x04%\xb0d\x1f4\x00\x00\u07d4\xdbl*s\xda\xc7BJ\xb0\xd01\xb6ga\x12%f\xc0\x10C\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xdbnV\f\x9b\xc6 \u053e\xa3\xa9MG\xf7\x88\v\xf4\u007f-_\x89\x04\xda\x0f\xdf\xcf\x05v\x00\x00\u07d4\xdbo\xf7\x1b=\xb0\x92\x8f\x83\x9e\x05\xa72;\xfbW\u049c\x87\xaa\x891T\xc9r\x9d\x05x\x00\x00\u07d4\xdbsF\vY\xd8\xe8PE\xd5\xe7R\xe6%Y\x87^BP.\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\xdbw\xb8\x8d\xcbq/\xd1~\xe9\x1a[\x94t\x8dr\f\x90\xa9\x94\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdb}@7\b\x1fle\xf9Gk\x06\x87\xd9\u007f\x1e\x04M\n\x1d\x89#\xc7W\a+\x8d\xd0\x00\x00\xe0\x94\u06c8.\xac\xed\xd0\xef\xf2cQ\x1b1*\u06fcY\u01b8\xb2[\x8a\x01\xedO\xdez\"6\xb0\x00\x00\u07d4\u06d3q\xb3\fL\x84NY\xe0>\x92K\xe6\x06\xa98\xd1\xd3\x10\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06e4ym\f\xebM:\x83k\x84\xc9o\x91\n\xfc\x10?[\xa0\x89\t\b\xf4\x93\xf77A\x00\x00\u07d4\u06ed\xc6\x1e\xd5\xf0F\n\u007f\x18\xe5\x1b/\xb2aM\x92d\xa0\xe0\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u06f6\xacH@'\x04\x16B\xbb\xfd\x8d\x80\xf9\xd0\xc1\xcf3\xc1\xeb\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u06fc\xbby\xbfG\x9aB\xadq\xdb\u02b7{Z\u07ea\x87,X\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xdb\xc1\xce\x0eI\xb1\xa7\x05\xd2. 7\xae\xc8x\xee\ru\xc7\x03\x89\r\x8drkqw\xa8\x00\x00\u07d4\xdb\xc1\xd0\xee+\xabS\x11@\xde\x13w\"\xcd6\xbd\xb4\xe4q\x94\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb\u015e\u0609s\u07ad1\b\x84\":\xf4\x97c\xc0P0\xf1\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u0794\xdb\xc6ie\xe4&\xff\x1a\xc8z\xd6\xebx\xc1\xd9Rq\x15\x8f\x9f\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xdb\xcb\xcdzW\ua7724\x9b\x87\x8a\xf3K\x1a\xd6B\xa7\xf1\u0449\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdb\xd5\x1c\xdf,;\xfa\xcd\xff\x10b!\xde.\x19\xadmB\x04\x14\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xdb\xd7\x1e\xfaK\x93\u0209\xe7e\x93\xde`\x9c;\x04\u02ef\xbe\b\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xdb\xf5\xf0a\xa0\xf4\x8e^ia\x879\xa7}.\xc1\x97h\xd2\x01\x89\b=lz\xabc`\x00\x00\u07d4\xdb\xf8\xb19g\xf5Q%'-\xe0V%6\xc4P\xbaVU\xa0\x89n\xf5x\xf0n\f\xcb\x00\x00\u07d4\xdb\xfb\x1b\xb4d\xb8\xa5\x8eP\r.\xd8\u0797,E\xf5\xf1\xc0\xfb\x89V\xbcu\xe2\xd61\x00\x00\x00\xe0\x94\xdc\x06~\xd3\xe1-q\x1e\xd4u\xf5\x15n\xf7\xe7\x1a\x80\xd94\xb9\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xdc\b\u007f\x93\x90\xfb\x9e\x97j\xc2:\xb6\x89TJ\tB\xec !\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xdc\x1e\xb9\xb6\xe6CQ\xf5d$P\x96E\xf8>y\xee\xe7l\xf4\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdc\x1f\x19ya_\b!@\xb8\xbbx\xc6{'\xa1\x94'\x13\xb1\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xdc#\xb2`\xfc\xc2n}\x10\xf4\xbd\x04J\xf7\x94W\x94`\xd9\u0689\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xdc)\x11\x97E\xd23s \xdaQ\xe1\x91\x00\xc9H\u0640\xb9\x15\x89\b\xacr0H\x9e\x80\x00\x00\u07d4\xdc-\x15\xa6\x9fk\xb3;$j\xef@E\aQ\xc2\xf6uj\u0489l4\x10\x80\xbd\x1f\xb0\x00\x00\u07d4\xdc=\xaeY\xed\x0f\xe1\x8bXQ\x1eo\xe2\xfbi\xb2\x19h\x94#\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xdc?\x0evr\xf7\x1f\xe7R[\xa3\v\x97U\x18: \xb9\x16j\x8a\x02\b\x9c\xf5{[>\x96\x80\x00\xe0\x94\xdcCE\u0581.\x87\n\xe9\fV\x8cg\xd2\xc5g\u03f4\xf0<\x8a\x01k5-\xa5\xe0\xed0\x00\x00\u07d4\xdcD'[\x17\x15\xba\xea\x1b\x03EsZ)\xacB\xc9\xf5\x1bO\x89?\x19\xbe\xb8\xdd\x1a\xb0\x00\x00\u07d4\xdcF\xc13%\u034e\xdf\x020\xd0h\x89d\x86\xf0\a\xbfN\xf1\x89Hz\x9a0E9D\x00\x00\u07d4\xdcQ\xb2\u071d$z\x1d\x0e[\xc3l\xa3\x15oz\xf2\x1f\xf9\xf6\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xdcS\x05\xb4\x02\n\x06\xb4\x9de||\xa3L5\xc9\x1c_,V\x8a\x01}\xf6\xc1\r\xbe\xba\x97\x00\x00\u07d4\xdcW4[8\xe0\xf0g\u0263\x1d\x9d\xea\xc5'Z\x10\x94\x93!\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdcWG}\xaf\xa4/p\\\u007f\xe4\x0e\xae\x9c\x81un\x02%\xf1\x89\x1b\x1b\x81(\xa7An\x00\x00\u07d4\xdc_Z\xd6c\xa6\xf2c2}d\xca\xc9\xcb\x13=,\x96\x05\x97\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdcp:_7\x94\xc8Ml\xb3TI\x18\xca\xe1J5\u00fdO\x89dI\xe8NG\xa8\xa8\x00\x00\xe0\x94\xdcs\x8f\xb2\x17\u03ad/iYL\b\x17\r\xe1\xaf\x10\xc4\x19\xe3\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xdcv\xe8[\xa5\v\x9b1\xec\x1e& \xbc\xe6\xe7\xc8\x05\x8c\x0e\xaf\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\u0703\xb6\xfd\rQ!1 G\a\xea\xf7.\xa0\xc8\u027e\xf9v\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u070c)\x12\xf0\x84\xa6\u0444\xaasc\x85\x13\u033c2n\x01\x02\x89F3\xbc6\xcb\xc2\xdc\x00\x00\u07d4\u0711\x1c\xf7\xdc]\u04016Vg\x05(\xe93\x8eg\x03G\x86\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0730;\xfal\x111#NV\xb7\xea|Or\x14\x87Tkz\x89Hz\x9a0E9D\x00\x00\xe0\x94\u0736M\xf47X\xc7\u03d7O\xa6`HO\xbbq\x8f\x8cg\xc1\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xdc\xc5-\x8f\x8d\x9f\xc7B\xa8\xb8'g\xf0US\x87\xc5c\xef\xff\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xdc\xcb7\x0e\u058a\xa9\"(0C\xef|\xad\x1b\x9d@?\xc3J\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdc\u0324 E\xec>\x16P\x8b`?\xd96\xe7\xfd}\xe5\xf3j\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xdc\xd1\fU\xbb\x85OuD4\xf1!\x9c,\x9a\x98\xac\xe7\x9f\x03\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xdc\u057c\xa2\x00S\x95\xb6u\xfd\xe5\x03VY\xb2k\xfe\xfcI\xee\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xdc\u06fdN&\x04\xe4\x0e\x17\x10\xccg0(\x9d\xcc\xfa\u04c9-\x89\xf9]\xd2\xec'\xcc\xe0\x00\x00\u07d4\xdc\xe3\f1\xf3\xcafr\x1e\xcb!<\x80\x9a\xabV\x1d\x9bR\xe4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdc\xf39eS\x13\x80\x161h\xfc\x11\xf6~\x89\xc6\xf1\xbc\x17\x8a\x89\x12'v\x854\x06\xb0\x80\x00\u07d4\xdc\xf6\xb6W&n\x91\xa4\xda\xe6\x03=\xda\xc1S2\u074d+4\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xdc\xf9q\x9b\xe8|oFum\xb4\x89\x1d\xb9\xb6\x11\xd2F\x9cP\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xdc\xff\xf3\xe8\xd2<*4\xb5k\u0473\xbdE\u01d3tC\"9\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xdd\x04\xee\xe7N\v\xf3\f?\x8dl,\u007fR\xe0Q\x92\x10\u07d3\x89\x04V9\x18$O@\x00\x00\xe0\x94\xdd&\xb4)\xfdC\xd8N\xc1y\x82S$\xba\u057f\xb9\x16\xb3`\x8a\x01\x16\xbf\x95\xbc\x842\x98\x00\x00\u07d4\xdd*#:\xde\xdef\xfe\x11&\xd6\xc1h#\xb6*\x02\x1f\xed\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xdd+\u07e9\x17\xc1\xf3\x10\xe6\xfa5\xaa\x8a\xf1i9\xc23\xcd}\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xdd5\xcf\xdb\u02d93\x95Sz\xec\xc9\xf5\x90\x85\xa8\xd5\u0776\xf5\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xddG\x18\x9a>d9qg\xf0b\x0eHEe\xb7b\xbf\xbb\xf4\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4\xddM\xd6\xd3`3\xb0co\u030d\t8`\x9fM\xd6OJ\x86\x89\x03@\xaa\xd2\x1b;p\x00\x00\u07d4\xddO_\xa2\x11\x1d\xb6\x8fk\xde5\x89\xb60)9[i\xa9-\x89\b\x96=\xd8\xc2\xc5\xe0\x00\x00\xe0\x94\xddc\x04/%\xed2\x88J\xd2n:\xd9Y\xeb\x94\xea6\xbfg\x8a\x04\x84\xd7\xfd\xe7\u0553\xf0\x00\x00\u07d4\xdde\xf6\xe1qc\xb5\xd2\x03d\x1fQ\xcc{$\xb0\x0f\x02\xc8\xfb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xddl\x06!\x93\xea\xc2=/\xdb\xf9\x97\xd5\x06:4k\xb3\xb4p\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdd{\u0366Y$\xaa\xa4\x9b\x80\x98J\xe1su\x02X\xb9(G\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdd\u007f\xf4A\xbao\xfe6q\xf3\xc0\u06bb\xff\x18#\xa5\x043p\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u0742T\x12\x1an\x94/\xc9\b(\xf2C\x1fQ\x1d\xad\u007f2\u6263\x9b)\xe1\xf3`\xe8\x00\x00\xe0\x94\u074a\xf9\xe7vR#\xf4DoD\xd3\xd5\t\x81\x9a==\xb4\x11\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\u0755\xdb\xe3\x0f\x1f\x18w\xc5\xddv\x84\xae\xef0*\xb6\x88Q\x92\x8a\x01\xc5\xd8\xd6\xeb>2P\x00\x00\xe0\x94\u0756|L_\x8a\xe4~&o\xb4\x16\xaa\u0456N\xe3\xe7\xe8\u00ca\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\u075bHZ;\x1c\xd3:j\x9cb\xf1\xe5\xbe\xe9'\x01\x85m%\x89\f3\x83\xed\x03\x1b~\x80\x00\xe0\x94\u0763q\xe6\x00\xd3\x06\x88\xd4q\x0e\b\x8e\x02\xfd\xf2\xb9RM_\x8a\x01w\"J\xa8D\xc7 \x00\x00\u07d4\u0764\xed*X\xa8\xdd \xa72u4{X\rq\xb9[\xf9\x9a\x89\x15\xa1<\xc2\x01\xe4\xdc\x00\x00\xe0\x94\u0764\xff}\xe4\x91\u0187\xdfEt\xdd\x1b\x17\xff\x8f$k\xa3\u044a\x04&\x84\xa4\x1a\xbf\xd8@\x00\x00\u07d4\u076bkQ\xa9\x03\v@\xfb\x95\xcf\vt\x8a\x05\x9c$\x17\xbe\u01c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\u076bu\xfb/\xf9\xfe\u02c8\xf8\x94vh\x8e+\x00\xe3g\xeb\xf9\x8a\x04\x1b\xad\x15^e\x12 \x00\x00\xe0\x94\u076b\xf1<<\x8e\xa4\xe3\xd7=x\xecqz\xfa\xfaC\x0eTy\x8a\b\xcf#\xf9\t\xc0\xfa\x00\x00\x00\u07d4\u076c1*\x96UBj\x9c\f\x9e\xfa?\xd8%Y\xefE\x05\xbf\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\u076ck\xf4\xbb\xdd}Y}\x9chm\x06\x95Y;\xed\xcc\xc7\xfa\x89.\xe4IU\b\x98\xe4\x00\x00\xe0\x94\u077d+\x93,v;\xa5\xb1\xb7\xae;6.\xac>\x8d@\x12\x1a\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\u077d\xdd\x1b\xbd8\xff\xad\xe00]0\xf0 (\xd9.\x9f:\xa8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\u077e\xe6\xf0\x94\xea\xe64 \xb0\x03\xfbGW\x14*\xeal\xd0\xfd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdd\u059c[\x9b\xf5\xebZ9\xce\xe7\xc34\x1a\x12\r\x97?\xdb4\x89k\xc1K\x8f\x8e\x1b5\x00\x00\xe0\x94\xdd\xdd{\x9en\xab@\x9b\x92&:\xc2r\u0680\x1bfO\x8aW\x8ai\xe1\r\xe7fv\u0400\x00\x00\u07d4\xdd\xe6p\xd0\x169fuv\xa2-\xd0]2F\xd6\x1f\x06\xe0\x83\x89\x01s\x17\x90SM\xf2\x00\x00\xe0\x94\xdd\xe7zG@\xba\b\xe7\xf7?\xbe:\x16t\x91)1t.\xeb\x8a\x044\xfeMC\x82\xf1\u0500\x00\u07d4\xdd\xe8\xf0\xc3\x1bt\x15Q\x1d\xce\xd1\xcd}F2>K\xd1\"2\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\xdd\xe9i\xae\xf3N\xa8z\u0099\xb7Y~)+J\x01U\u030a\x89\x102\xf2YJ\x01s\x80\x00\u07d4\xdd\xf0\xcc\xe1\xfe\x99m\x91v5\xf0\a\x12\xf4\x05 \x91\xdf\xf9\xea\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdd\xf3\xadv58\x10\xbej\x89\xd71\xb7\x87\xf6\xf1q\x88a+\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xdd\xf5\x81\n\x0e\xb2\xfb.22;\xb2\u0255\t\xab2\x0f$\xac\x8a\x03\xca\\f\u067cD0\x00\x00\xe0\x94\xdd\xf9\\\x1e\x99\xce/\x9fV\x98\x05|\x19\xd5\xc9@'\xeeJn\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u0794\xdd\xfa\xfd\xbc|\x90\xf12\x0eT\xb9\x8f7F\x17\xfb\xd0\x1d\x10\x9f\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xdd\xfc\xca\x13\xf94\xf0\u03fe#\x1d\xa109\xd7\x04u\xe6\xa1\u040968\"\x16`\xa5\xaa\x80\x00\u07d4\xde\x02~\xfb\xb3\x85\x03\"n\xd8q\t\x9c\xb3\v\xdb\x02\xaf\x135\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xde\x06\xd5\xeawzN\xb1G^`]\xbc\xbfCDN\x807\xea\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xde\a\xfb[zFN;\xa7\xfb\xe0\x9e\x9a\xcb'\x1a\xf53\x8cX\x89\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xde\x11!\x82\x9c\x9a\b(@\x87\xa4?\xbd/\xc1\x14*23\xb4\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xde\x17kR\x84\xbc\xee:\x83\x8b\xa2Og\xfc|\xbfg\u05ce\xf6\x89\x02\t\xce\b\xc9b\xb0\x00\x00\u07d4\xde!\"\x93\xf8\xf1\xd21\xfa\x10\xe6\tG\rQ,\xb8\xff\xc5\x12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde0\xe4\x9eZ\xb3\x13!M/\x01\u072b\u0389@\xb8\x1b\x1cv\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xde3\xd7\b\xa3\xb8\x9e\x90\x9e\xafe;0\xfd\u00e5\xd5\u0334\xb3\x89\t\x9c\x88\"\x9f\xd4\xc2\x00\x00\u07d4\xde7B\x99\xc1\xd0}ySs\x85\x19\x0fD.\xf9\xca$\x06\x1f\x89\a?u\u0460\x85\xba\x00\x00\u07d4\xdeB\xfc\xd2L\xe4#\x93\x830CgY_\x06\x8f\fa\a@\x89\x02r*p\xf1\xa9\xa0\x00\x00\u07d4\xdeP\x86\x8e\xb7\xe3\xc7\x197\xecs\xfa\x89\u074b\x9e\xe1\rE\xaa\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xdeU\xde\x04X\xf8P\xb3~Mx\xa6A\xdd.\xb2\u074f8\u0389\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xde[\x00_\xe8\u06ae\x8d\x1f\x05\xde>\xda\x04 f\xc6\xc4i\x1c\x89;\xa1\x91\v\xf3A\xb0\x00\x00\u07d4\xdea-\a$\xe8N\xa4\xa7\xfe\xaa=!B\xbd^\xe8-2\x01\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdem61\x06\xccb8\xd2\xf0\x92\xf0\xf07!6\xd1\xcdP\u018a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xde}\xee\"\x0f\x04W\xa7\x18}V\xc1\xc4\x1f.\xb0\n\xc5`!\x89\"%\xf3\x9c\x85\x05*\x00\x00\u07d4\u0782\u030dJ\x1b\xb1\xd9CC\x92\x96[>\x80\xba\xd3\xc0=O\x89P\x18nu\u0797\xa6\x00\x00\u07d4\u0797\xf43\a\x00\xb4\x8cImC|\x91\xca\x1d\xe9\u0130\x1b\xa4\x89\x9d\xcc\x05\x15\xb5n\f\x00\x00\u07d4\u079e\xffLy\x88\x11\xd9h\xdc\xcbF\r\x9b\x06\x9c\xf3\x02x\xe0\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\u07b1\xbc4\xd8mJM\xde%\x80\u063e\xaf\aN\xb0\xe1\xa2D\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\u07b2I]j\xca{*j-\x13\x8bn\x1aB\xe2\xdc1\x1f\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\u07b9rTGL\r/Zyp\xdc\xdb/R\xfb\x10\x98\xb8\x96\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u07b9\xa4\x9aC\x870 \xf0u\x91\x85\xe2\v\xbbL\U000c1ecf\x89\vx\xed\xb0\xbf.^\x00\x00\u07d4\u07bb\u0743\x1e\x0f \xaen7\x82R\xde\xcd\xf9/|\xf0\xc6X\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde\xc3\xee\xc2d\nu,Fn+~~\u616f\xe9\xacA\xf4\x89G\u0257SYk(\x80\x00\u07d4\xde\xc8#s\xad\xe8\xeb\xcf*\xcbo\x8b\xc2AM\u05eb\xb7\rw\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xde\u0221\xa8\x98\xf1\xb8\x95\xd80\x1f\xe6J\xb3\xad]\xe9A\xf6\x89\x89*\xb4\xf6~\x8as\x0f\x80\x00\u07d4\xde\u025e\x97/\xcaqwP\x8c\x8e\x1aG\xac\"\xd7h\xac\xab|\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xde\xd8w7\x84\a\xb9Nx\x1cN\xf4\xaf|\xfc[\xc2 \xb5\x16\x89\x141y\xd8i\x11\x02\x00\x00\u07d4\xde\xe9B\xd5\xca\xf5\xfa\xc1\x14!\xd8k\x01\vE\x8e\\9)\x90\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xde\xee&\x89\xfa\x90\x06\xb5\x9c\xf2\x85#}\xe5;:\u007f\xd0\x148\x89\x18ey\xf2\x9e %\x00\x00\u07d4\xde\xfd\xdf\u055b\x8d,\x15N\xec\xf5\xc7\xc1g\xbf\v\xa2\x90]>\x89\x05\x12\xcb^&GB\x00\x00\u07d4\xde\xfe\x91A\xf4pE\x99\x15\x9d{\"=\xe4+\xff\xd8\x04\x96\xb3\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xdf\t\x8f^N=\xff\xa5\x1a\xf27\xbd\xa8e,Os\ud726\x89\x1b6\xa6DJ>\x18\x00\x00\xe0\x94\xdf\r\ba{\xd2R\xa9\x11\u07cb\xd4\x1a9\xb8=\u07c0\x96s\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xdf\x0f\xf1\xf3\xd2z\x8e\xc9\xfb\x8fk\f\xb2T\xa6;\xba\x82$\xa5\x89\xec\xc5 )E\xd0\x02\x00\x00\u07d4\xdf\x1f\xa2\xe2\x0e1\x98^\xbe,\x0f\f\x93\xb5L\x0f\xb6z&K\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xdf!\x1c\xd2\x12\x88\xd6\xc5o\xaef\xc3\xffTb]\u0531T'\x89\x87\x86\xcdvN\x1f,\x00\x00\u07d4\xdf#k\xf6\xab\xf4\xf3)7\x95\xbf\f(q\x8f\x93\u3c73k\x89Hz\x9a0E9D\x00\x00\u07d4\xdf1\x02_VI\xd2\xc6\xee\xa4\x1e\u04fd\xd3G\x1ay\x0fu\x9a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xdf7\xc2.`:\xed\xb6\nbrS\xc4}\x8b\xa8f\xf6\xd9r\x8a\x05\x15\n\xe8J\x8c\xdf\x00\x00\x00\u07d4\xdf;r\u017dq\u0501N\x88\xa6#!\xa9=@\x11\xe3W\x8b\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdf?W\xb8\xeed4\xd0G\"=\xeft\xb2\x0fc\xf9\xe4\xf9U\x89\r\x94b\xc6\xcbKZ\x00\x00\u07d4\xdfD\xc4\u007f\xc3\x03\xacv\xe7O\x97\x19L\xcag\xb5\xbb<\x02?\x89 \t\xc5\u023fo\xdc\x00\x00\u07d4\xdfG\xa6\x1brSQ\x93\xc5a\xcc\xccu\xc3\xf3\xce\b\x04\xa2\x0e\x89\x15\x93\\\vN=x\x00\x00\u07d4\xdfG\xa8\xef\x95\xf2\xf4\x9f\x8eoX\x18AT\x14]\x11\xf7'\x97\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xdfS\x003F\xd6\\^zdk\xc04\xf2\xb7\xd3/\xcb\xe5j\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xdfW5:\xaf\xf2\xaa\xdb\n\x04\xf9\x01N\x8d\xa7\x88N\x86X\x9c\x89\bH\x86\xa6nO\xb0\x00\x00\u07d4\xdf`\xf1\x8c\x81*\x11\xedN'v\xe7\xa8\x0e\xcf^S\x05\xb3\u05890\xca\x02O\x98{\x90\x00\x00\u07d4\xdfd\x85\xc4)z\xc1R\xb2\x89\xb1\x9d\xde2\xc7~\xc4\x17\xf4}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xdff\n\x91\u06b9\xf70\xf6\x19\rP\xc89\x05aP\aV\u0289lk\x93[\x8b\xbd@\x00\x00\u07d4\xdfn\xd6\x00jj\xbe\x88n\xd3=\x95\xa4\xde(\xfc\x12\x189'\x891T\xc9r\x9d\x05x\x00\x00\u07d4\u07c5\x10y>\xee\x81\x1c-\xab\x1c\x93\xc6\xf4G?0\xfb\xef[\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u07cdH\xb1\xeb\a\xb3\xc2\x17y\x0el-\xf0M\xc3\x19\xe7\xe8H\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\u07e6\xb8\xb8\xad1\x84\xe3W\xda()Q\u05d1a\u03f0\x89\xbc\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\u07ef1\xe6\"\xc0=\x9e\x18\xa0\u0778\xbe`\xfb\xe3\xe6a\xbe\n\x8a\x02\x1e\x17\x1a>\xc9\xf7,\x00\x00\u07d4\u07f1bn\xf4\x8a\x1d}uR\xa5\xe0)\x8f\x1f\xc2:;H-\x89\\\xe8\x95\u0754\x9e\xfa\x00\x00\xe0\x94\u07f4\u052d\xe5/\u0301\x8a\xccz,k\xb2\xb0\x02$e\x8fx\x8a\x01\xa4 \xdb\x02\xbd}X\x00\x00\u07d4\u07fdB2\xc1|@z\x98\r\xb8\u007f\xfb\u036060\xe5\xc4Y\x89\x1d\xfc\u007f\x92I#S\x00\x00\u07d4\xdf\xcb\xdf\tEN\x1a^J@\xd3\xee\xf7\xc5\xcf\x1c\xd3\u0794\x86\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xdf\xdb\xce\xc1\x01K\x96\xda!X\xcaQ>\x9c\x8d;\x9a\xf1\xc3\u0409lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xdf\xde\xd2WK'\xd1a:}\x98\xb7\x15\x15\x9b\r\x00\xba\xab(\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xdf\xdfC9P\x8b\x0fnZ\xb1\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe0\x06\x04b\xc4\u007f\xf9g\x9b\xae\xf0qY\xca\xe0\x8c)\xf2t\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\r\x15;\x106\x91C\xf9\u007fT\xb8\xd4\xca\"\x9e\xb3\xe8\xf3$\x89\b=lz\xabc`\x00\x00\u07d4\xe0\x12\xdbE8'\xa5\x8e\x16\xc16V\b\xd3n\xd6Xr\x05\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x15G\xbaB\xfc\xaf\xaf\x93\x93\x8b\xec\xf7i\x9ft)\n\xf7O\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x16\xdc\x13\x8e%\x81[\x90\xbe?\xe9\xee\xe8\xff\xb2\xe1\x05bO\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0\x18Y\xf2B\xf1\xa0\xec`/\xa8\xa3\xb0\xb5v@\xec\x89\a^\x89\x1e\x16,\x17{\xe5\xcc\x00\x00\xe0\x94\xe0 \xe8cb\xb4\x87u(6\xa6\xde\v\xc0,\xd8\u061a\x8bj\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xe0#\xf0\x9b(\x87a,|\x9c\xf1\x98\x8e::`+3\x94\u0249lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0'\"\x13\xe8\xd2\xfd>\x96\xbdb\x17\xb2KK\xa0\x1bapy\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe0+t\xa4v(\xbe1[\x1fv\xb3\x15\x05J\xd4J\xe9qo\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xe02 \u0197\xbc\u048f&\xef\vt@J\x8b\xeb\x06\xb2\xba{\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xe05/\u07c1\x9b\xa2e\xf1L\x06\xa61\\J\xc1\xfe\x13\x1b.\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe08\x8a\xed\xdd?\xe2\xadV\xf8WH\xe8\x0eq\n4\xb7\xc9.\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0<\x00\xd0\x03\x88\xec\xbfO&=\n\xc7x\xbbA\xa5z@\u064966\xc9yd6t\x00\x00\u07d4\xe0I \xdcn\xcc\x1dn\xcc\bO\x88\xaa\n\xf5\u06d7\xbf\x89:\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xe0Ir\xa8<\xa4\x11+\xc8q\xc7-J\xe1al/\a(\u06c9\x0e\x81\xc7\u007f)\xa3/\x00\x00\u07d4\xe0O\xf5\xe5\xa7\u2bd9]\x88W\xce\x02\x90\xb5:+\x0e\xda]\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xe0P)\xac\xeb\axg[\xef\x17A\xab,\u0493\x1e\xf7\xc8K\x8a\x01\x0f\r\xba\xe6\x10\tR\x80\x00\u07d4\xe0V\xbf?\xf4\x1c&%o\xefQqf\x12\xb9\u04da\u0799\x9c\x89\x05k\xe7W\xa1.\n\x80\x00\u07d4\xe0a\xa4\xf2\xfcw\xb2\x96\u045a\xda#\x8eI\xa5\u02ce\xcb\xfap\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe0f>\x8c\xd6g\x92\xa6A\xf5nP\x03f\x01G\x88\x0f\x01\x8e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0f\x8f\xa8,\x14\xd6\xe8\xd9:S\x11>\xf2\x86/\xa8\x15\x81\xbc\x89//9\xfclT\x00\x00\x00\u07d4\xe0i\xc0\x173R\xb1\v\xf6\x83G\x19\xdb[\xed\x01\xad\xf9{\xbc\x89\x01\x064\xf8\xe52;\x00\x00\u07d4\xe0l)\xa8\x15\x17\xe0\u0507\xb6\u007f\xb0\xb6\xaa\xbcOW6\x83\x88\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xe0l\xb6)G\x04\xee\xa7C|/\xc3\xd3\as\xb7\xbf8\x88\x9a\x89\x01\x16\xdc:\x89\x94\xb3\x00\x00\u07d4\xe0q7\xae\r\x11m\x0353\xc4\uad16\xf8\xa9\xfb\tV\x9c\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\xe0v\xdb0\xabHoy\x19N\xbb\xc4]\x8f\xab\x9a\x92B\xf6T\x8a\x01\x06`~4\x94\xba\xa0\x00\x00\u07d4\xe0~\xbb\xc7\xf4\xdaAnB\xc8\xd4\xf8B\xab\xa1b3\xc1%\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x81\xca\x1fH\x82\xdb`C\u0569\x19\a\x03\xfd\xe0\xab;\xf5m\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\x83\xd3Hc\xe0\xe1\u007f\x92ky(\xed\xff1~\x99\x8e\x9cK\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\x8b\x9a\xbak\xd9\u048b\xc2\x05gy\xd2\xfb\xf0\xf2\x85Z=\x9d\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\x8b\u009c+H\xb1i\xff+\xdc\x16qLXnl\xb8\\\u03c9\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe0\x8c`11\x06\xe3\xf93O\xe6\xf7\xe7bM!\x110\xc0w\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xe0\x9ch\xe6\x19\x98\xd9\xc8\x1b\x14\xe4\xee\x80+\xa7\xad\xf6\xd7L\u06c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe0\x9f\xeauZ\xee\x1aD\xc0\xa8\x9f\x03\xb5\u07b7b\xba3\x00o\x89;\xa2\x89\xbc\x94O\xf7\x00\x00\xe0\x94\xe0\xa2T\xac\t\xb9r[\xeb\xc8\xe4`C\x1d\xd0s.\xbc\xab\xbf\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xe0\xaai6UU\xb7?(#3\xd1\xe3\f\x1b\xbd\a(T\xe8\x8a\x01{x\x83\xc0i\x16`\x00\x00\u07d4\xe0\xba\u064e\ue598\xdb\xf6\xd7`\x85\xb7\x92=\xe5uN\x90m\x89\t\r\x97/22<\x00\x00\u07d4\xe0\u012b\x90r\xb4\xe6\xe3eJI\xf8\xa8\xdb\x02jK3\x86\xa9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\u0380\xa4a\xb6H\xa5\x01\xfd\v\x82F\x90\u0206\x8b\x0eM\xe8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe0\xcfi\x8a\x053'\xeb\xd1k}w\x00\t/\xe2\xe8T$F\x89\x05*4\u02f6\x1fW\x80\x00\xe0\x94\xe0\xd21\xe1D\xec\x91\a8l|\x9b\x02\xf1p,\xea\xa4\xf7\x00\x8a\x01\x0f\r\xba\xe6\x10\tR\x80\x00\u07d4\xe0\xd7kqf\xb1\xf3\xa1+@\x91\xee+)\u078c\xaa}\a\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xe0\xe0\xb2\xe2\x9d\xdes\xafu\x98~\xe4Dl\x82\x9a\x18\x9c\x95\xbc\x89\b\x13\xcaV\x90m4\x00\x00\xe0\x94\xe0\xe9xu=\x98/\u007f\x9d\x1d#\x8a\x18\xbdH\x89\xae\xfeE\x1b\x8a\x02\r\u058a\xaf2\x89\x10\x00\x00\u07d4\xe0\xf3r4|\x96\xb5_}C\x06\x03K\xeb\x83&o\xd9\tf\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe0\xf9\x03\xc1\xe4\x8a\xc4!\xabHR\x8f=J&H\b\x0f\xe0C\x897\b\xba\xed=h\x90\x00\x00\u07d4\xe0\xff\v\xd9\x15D9\u0125\xb7#>)\x1d}\x86\x8a\xf5?3\x89\x15y!jQ\xbb\xfb\x00\x00\xe0\x94\xe1\n\xc1\x9cTo\xc2T|a\xc19\xf5\xd1\xf4Zff\u0570\x8a\x01\x02\xdao\xd0\xf7:<\x00\x00\xe0\x94\xe1\fT\x00\x88\x11?\xa6\xec\x00\xb4\xb2\u0202O\x87\x96\xe9n\u010a2\x0fE\t\xab\x1e\xc7\xc0\x00\x00\xe0\x94\xe1\x17:$})\xd8#\x8d\xf0\x92/M\xf2Z\x05\xf2\xafw\u00ca\bx\xc9]V\x0f0G\x80\x00\xe0\x94\xe1 >\xb3\xa7#\xe9\x9c\" \x11|\xa6\xaf\xebf\xfaBOa\x8a\x02\x00\uf49e2V\xfe\x00\x00\xe0\x94\xe11\xf8~\xfc^\xf0~C\xf0\xf2\xf4\xa7G\xb5Q\xd7P\xd9\xe6\x8a\x04<%\xe0\xdc\xc1\xbd\x1c\x00\x00\u07d4\xe13N\x99\x83y\xdf\xe9\x83\x17pby\x1b\x90\xf8\x0e\xe2-\x8d\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe15@\xec\xee\x11\xb2\x12\xe8\xb7u\u070eq\xf3t\xaa\xe9\xb3\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe1;=+\xbf\u073c\x87r\xa23\x15rL\x14%\x16|V\x88\x897\xf3y\x14\x1e\xd0K\x80\x00\u07d4\xe1D=\xbd\x95\xccA#\u007fa:HEi\x88\xa0Oh2\x82\x89\xd8\xd8X?\xa2\xd5/\x00\x00\u07d4\xe1F\x17\xf6\x02%\x01\xe9~{>-\x886\xaaa\xf0\xff-\xba\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe1I\xb5rl\xafm^\xb5\xbf*\xccA\xd4\xe2\xdc2\x8d\u1089i*\xe8\x89p\x81\xd0\x00\x00\xe0\x94\xe1T\xda\xea\xdbTX8\xcb\u01aa\fUu\x19\x02\xf5(h*\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\u07d4\xe1l\xe3Ya\xcdt\xbdY\r\x04\u012dJ\x19\x89\xe0V\x91\u0189\a\xea(2uw\b\x00\x00\u07d4\xe1r\xdf\xc8\xf8\f\xd1\xf8\u03459\xdc&\b \x14\xf5\xa8\xe3\u8262\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xe1w\xe0\xc2\x01\xd35\xba9V\x92\x9cW\x15\x88\xb5\x1cR#\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe1x\x12\xf6l^e\x94\x1e\x18lF\x92+n{/\x0e\xebF\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xe1\x80\u079e\x86\xf5{\xaf\xac\u05d0O\x98&\xb6\xb4\xb2c7\xa3\x89-\x04\x1dpZ,`\x00\x00\xe0\x94\xe1\x92H\x9b\x85\xa9\x82\xc1\x882F\xd9\x15\xb2)\xcb\x13 \u007f8\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xe1\x95\xbb\xc6,{tD\x04\x0e\xb9\x96#\x96Ovg\xb3v\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe2\x06\xfbs$\xe9\u07b7\x9e\x19\x904\x96\u0596\x1b\x9b\xe5f\x03\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xe2\aW\x8e\x1fM\u06cf\xf6\u0546{9X-q\xb9\x81*\u0149\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xe2\b\x81*h@\x98\xf3\xdaN\xfej\xba%bV\xad\xfe?\xe6\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe2\tT\xd0\xf4\x10\x8c\x82\xd4\u0732\x14\x8d&\xbb\xd9$\xf6\xdd$\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xe2\v\xb9\xf3\x96d\x19\xe1K\xbb\xaa\xaag\x89\xe9$\x96\u03e4y\x89\xbb\xd8%\x03\aRv\x00\x00\u07d4\xe2\r\x1b\xcbq(m\xc7\x12\x8a\x9f\xc7\xc6\xed\u007fs8\x92\xee\xf5\x896d\xf8\xe7\xc2J\xf4\x00\x00\u0794\xe2\x19\x12\x15\x98?3\xfd3\xe2,\u0522I\x00T\xdaS\xfd\u0708\xdbD\xe0I\xbb,\x00\x00\u07d4\xe2\x19\x8c\x8c\xa1\xb3\x99\xf7R\x15a\xfdS\x84\xa7\x13/\xbaHk\x897\b\xba\xed=h\x90\x00\x00\xe0\x94\xe2\x1cw\x8e\xf2\xa0\xd7\xf7Q\xea\x8c\aM\x1f\x81\"C\x86>N\x8a\x01\x1f\xc7\x0e,\x8c\x8a\xe1\x80\x00\xe0\x94\xe2)\xe7F\xa8?,\xe2S\xb0\xb0>\xb1G$\x11\xb5~W\x00\x8a\x016\x9f\xb9a(\xacH\x00\x00\u07d4\xe2+ \xc7x\x94F;\xafwL\xc2V\u057d\u06ff}\xdd\t\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe20\xfe\x1b\xff\x03\x18m\x02\x19\xf1]LH\x1b}Y\xbe(j\x89\x01\xfdt\x1e\x80\x88\x97\x00\x00\u07d4\xe27\xba\xa4\xdb\u0252n2\xa3\xd8]\x12d@-T\xdb\x01/\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe2A\t\xbe/Q=\x87I\x8e\x92j(d\x99uO\x9e\u051e\x890\x0e\xa8\xad\x1f'\xca\x00\x00\u07d4\xe2Fh<\u025d\xb7\u0125+\u02ec\xaa\xb0\xb3/k\xfc\x93\u05c9lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe2Z\x16{\x03\x1e\x84am\x0f\x01?1\xbd\xa9]\xcccP\xb9\x8a\x02\x8c*\xaa\u0243\xd0]\u0187st\xa8\xf4F\xee\xe9\x89\n\xb6@9\x12\x010\x00\x00\u07d4\xe2\x8b\x06\"Y\xe9n\xeb<\x8dA\x04\x94?\x9e\xb3%\x89<\xf5\x89Hz\x9a0E9D\x00\x00\xe0\x94\u237c\x8e\xfd^Ajv.\xc0\xe0\x18\x86K\xb9\xaa\x83({\x8a\x051\xf2\x00\xab>\x03\n\x80\x00\u07d4\xe2\x90K\x1a\xef\xa0V9\x8bb4\xcb5\x81\x12\x88\xd76\xdbg\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\u274a\xe4R\xdc\xf3\xb6\xacd^c\x04\t8UQ\xfa\xae\n\x89\x04Z\r\xa4\xad\xf5B\x00\x00\u07d4\xe2\xbb\xf8FA\xe3T\x1fl3\xe6\xedh:cZp\xbd\xe2\xec\x89\x1bA<\xfc\xbfY\xb7\x80\x00\u07d4\xe2\xcf6\n\xa22\x9e\xb7\x9d+\xf7\xca\x04\xa2z\x17\xc52\xe4\u0609\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xe2\xdf#\xf6\xea\x04\xbe\xcfJ\xb7\x01t\x8d\xc0\x961\x84U\\\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xe2\xe1\\`\xdd8\x1e:K\xe2Pq\xab$\x9aL\\Rd\u0689\u007fk\u011b\x81\xb57\x00\x00\u07d4\xe2\xe2nN\x1d\xcf0\xd0H\xccn\u03ddQ\xec\x12\x05\xa4\xe9&\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xe2\xeei\x1f#~\xe6R\x9beW\xf2\xfc\xdd=\xcf\fY\xecc\x8a\x01'r\x9c\x14h| \x00\x00\u07d4\xe2\xef\xa5\xfc\xa7\x958\xce`h\xbf1\xd2\xc5\x16\xd4\xd5<\b\xe5\x89\a\x1c\xc4\b\xdfc@\x00\x00\xe0\x94\xe2\xef\u0429\xbc@~\xce\x03\xd6~\x8e\xc8\xe9\u0483\xf4\x8d*I\x8a\x02\x99\xb3;\xf9\u0144\xe0\x00\x00\u07d4\xe2\xf4\r5\x8f^?\xe7F>\xc7\x04\x80\xbd.\u04d8\xa7\x06;\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe2\xf98=X\x10\xea{C\x18+\x87\x04\xb6+'\xf5\x92]9\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe2\xff\x9e\xe4\xb6\xec\xc1AA\xcct\xcaR\xa9\xe7\xa2\xee\x14\xd9\b\x89K\xe4\xe7&{j\xe0\x00\x00\xe0\x94\xe3\x02\x12\xb2\x01\x1b\xb5k\xdb\xf1\xbc5i\x0f:N\x0f\xd9\x05\xea\x8a\x01\xb2\u07dd!\x9fW\x98\x00\x00\u07d4\xe3\x03\x16\u007f=I`\xfe\x88\x1b2\x80\n+J\xef\xf1\xb0\x88\u0509lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3\x04\xa3/\x05\xa87btJ\x95B\x97o\xf9\xb7#\xfa1\xea\x89Ur\xf2@\xa3F \x00\x00\u07d4\xe3\bCR\x04y7d\xf5\xfc\xbee\xebQ\x0fZtJeZ\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3\t\x97L\xe3\x9d`\xaa\xdf.ig2Q\xbf\x0e\x04v\n\x10\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xe3\x1bN\xef\x18L$\xab\t\x8e6\xc8\x02qK\xd4t=\xd0\u0509\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3!\xbbJ\x94j\xda\xfd\xad\xe4W\x1f\xb1\\\x00C\u04de\xe3_\x89Udu8+L\x9e\x00\x00\u07d4\xe3&<\xe8\xafm\xb3\xe4gXE\x02\xedq\t\x12^\xae\"\xa5\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3+\x1cG%\xa1\x87TI\u93d7\x0e\xb3\xe5@b\xd1X\x00\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe3/\x95vmW\xb5\xcdK\x172\x89\u0587o\x9edU\x81\x94\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xe38@\u063c\xa7\u0698\xa6\xf3\u0416\xd8=\xe7\x8bp\xb7\x1e\xf8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe38\xe8Y\xfe.\x8c\x15UHH\xb7\\\xae\u0368w\xa0\xe82\x89a\xac\xff\x81\xa7\x8a\xd4\x00\x00\u07d4\xe3=\x98\x02 \xfa\xb2Y\xafj\x1fK8\xcf\x0e\xf3\xc6\xe2\xea\x1a\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe3=\xf4\u0380\u0336*v\xb1+\xcd\xfc\xec\xc4b\x89\x97:\xa9\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xe3?\xf9\x87T\x1d\xde\\\xde\u0a29m\xcc?3\xc3\xf2L\u008a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xe3A\v\xb7U|\xf9\x1dy\xfai\xd0\xdf\xea\n\xa0u@&Q\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe3Ad-@\u04af\xce.\x91\a\xc6py\xacz&`\bl\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe3TS\xee\xf2\xcc2\x89\x10CR\x8d\t\x84i\x80\x00\xe0\x94\xe5\x10\xd6y\u007f\xba=f\x93\x83Z\x84N\xa2\xadT\x06\x91\x97\x1b\x8a\x03\xae9\xd4s\x83\xe8t\x00\x00\u07d4\xe5\x14!\xf8\xee\"\x10\xc7\x1e\xd8p\xfea\x82v\u0215J\xfb\xe9\x89Hz\x9a0E9D\x00\x00\u07d4\xe5\x1e\xb8~\u007f\xb71\x1fR(\xc4y\xb4\x8e\u0247\x881\xacL\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5!V1\xb1BH\xd4Z%R\x96\xbe\xd1\xfb\xfa\x030\xff5\x89G\x03\xe6\xebR\x91\xb8\x00\x00\xe0\x94\xe5(\xa0\xe5\xa2g\xd6g\xe99:e\x84\xe1\x9b4\u071b\xe9s\x8a\x01/\x93\x9c\x99\xed\xab\x80\x00\x00\u07d4\xe54%\xd8\xdf\x1f\x11\xc3A\xffX\xae_\x148\xab\xf1\xcaS\u03c9\x11t\xa5\xcd\xf8\x8b\xc8\x00\x00\u07d4\xe5No\x9c\xffV\xe1\x9cF\x1e\xb4T\xf9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5A\x02SM\xe8\xf2>\xff\xb0\x93\xb3\x12B\xad;#?\xac\xfd\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xe5E\xee\x84\xeaH\xe5d\x16\x1e\x94\x82\u055b\xcf@j`,\xa2\x89dI\xe8NG\xa8\xa8\x00\x00\u07d4\xe5H\x1a\u007f\xedB\xb9\x01\xbb\xed x\x9b\u052d\xe5\r_\x83\xb9\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5Y\xb5\xfd3{\x9cUr\xa9\xbf\x9e\x0f%!\xf7\xd4F\xdb\xe4\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe5\\\x80R\n\x1b\x0fu[\x9a,\xd3\xce!Ov%e>\x8a\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe5mC\x13$\xc9)\x11\xa1t\x9d\xf2\x92p\x9c\x14\xb7ze\u034a\x01\xbc\x85\xdc*\x89\xbb \x00\x00\u07d4\xe5})\x95\xb0\xeb\xdf?<\xa6\xc0\x15\xeb\x04&\r\xbb\x98\xb7\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\u51f1j\xbc\x8at\b\x1e6\x13\xe1CB\xc03u\xbf\bG\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\x89\xfav\x98M\xb5\xec@\x04\xb4n\u8954\x92\xc3\aD\u0389\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xe5\x8d\xd228\xeen\xa7\xc2\x13\x8d8]\xf5\x00\xc3%\xf3v\xbe\x89b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xe5\x95?\xeaIq\x04\xef\x9a\xd2\xd4\xe5\x84\x1c'\x1f\a5\x19\u0089&)\xf6n\fS\x00\x00\x00\xe0\x94\u5587\x97F\x8e\xf7g\x10\x1bv\x1dC\x1f\xce\x14\xab\xff\u06f4\x8a\x01\xb3\xd9i\xfaA\x1c\xa0\x00\x00\u07d4\xe5\x97\xf0\x83\xa4i\xc4Y\x1c=+\x1d,w'\x87\xbe\xfe'\xb2\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xe5\x9b;\xd3\x00\x89?\x97#>\xf9G\xc4or\x17\xe3\x92\xf7\xe9\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5\xa3e4<\xc4\xeb\x1ew\x03h\xe1\xf1\x14Jw\xb82\xd7\xe0\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe5\xa3\xd7\xeb\x13\xb1\\\x10\x01w#m\x1b\xeb0\xd1~\xe1T \x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xaa\v\x83;\xb9\x16\xdc\x19\xa8\xddh?\x0e\xde$\x1d\x98\x8e\xba\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\u5def\x14i\x86\xc0\xff\x8f\x85\xd2.l\xc34\a}\x84\xe8$\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xb8&\x19l\x0e\x1b\xc1\x11\x9b\x02\x1c\xf6\xd2Y\xa6\x10\u0256p\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe5\xb9o\u026c\x03\xd4H\xc1a:\xc9\x1d\x15\x97\x81E\xdb\xdf\u0449\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\u5e40\u048e\xec\xe2\xc0o\xcal\x94s\x06\x8b7\u0526\xd6\xe9\x89%\xaf\u058c\xac+\x90\x00\x00\u07d4\u5eb4\xf0\xaf\u0629\u0463\x81\xb4Wa\xaa\x18\xf3\xd3\xcc\xe1\x05\x89Q\xbf\xd7\xc18x\xd1\x00\x00\u07d4\xe5\xbc\u020c;%on\xd5\xfeU\x0eJ\x18\x19\x8b\x943V\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xbd\xf3OL\xccH>L\xa50\xcc|\xf2\xbb\x18\xfe\xbe\x92\xb3\x89\x06\xd85\xa1\v\xbc\xd2\x00\x00\u07d4\xe5\u0713I\xcbR\xe1a\x19a\"\u03c7\xa3\x896\xe2\xc5\u007f4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xe38\x00\xa1\xb2\xe9k\xde\x101c\n\x95\x9a\xa0\a\xf2nQ\x89Hz\x9a0E9D\x00\x00\u07d4\xe5\xe3~\x19@\x8f,\xfb\xec\x834\x9d\u0501S\xa4\xa7\x95\xa0\x8f\x89\u3bb5sr@\xa0\x00\x00\u07d4\xe5\xed\xc7>bo]4A\xa4U9\xb5\xf7\xa3\x98\u0153\xed\xf6\x89.\xe4IU\b\x98\xe4\x00\x00\u07d4\xe5\xed\xf8\x12?$\x03\xce\x1a\x02\x99\xbe\xcfz\xactM\a_#\x89\n\xdaUGK\x814\x00\x00\u07d4\xe5\xf8\xefm\x97\x066\xb0\u072aO \x0f\xfd\xc9\xe7Z\xf1t\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe5\xfb1\xa5\xca\xeej\x96\xde9;\xdb\xf8\x9f\xbee\xfe\x12[\xb3\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe5\xfb\xe3I\x84\xb67\x19o3\x1cg\x9d\f\fG\xd84\x10\xe1\x89llD\xfeG\xec\x05\x00\x00\u07d4\xe6\tU\xdc\v\xc1V\xf6\xc4\x18I\xf6\xbdwk\xa4K\x0e\xf0\xa1\x89\x10C\x16'\xa0\x93;\x00\x00\u07d4\xe6\nU\xf2\u07d9m\u00ee\xdbil\b\xdd\xe09\xb2d\x1d\xe8\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe6\x11[\x13\xf9y_~\x95e\x02\xd5\aEg\u06b9E\xcek\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xe6\x1f(\t\x15\xc7t\xa3\x1d\"<\xf8\f\x06\x92f\xe5\xad\xf1\x9b\x89/\xb4t\t\x8fg\xc0\x00\x00\u07d4\xe6/\x98e\a\x12\xeb\x15\x87S\xd8)r\xb8\u9723\xf6\x18w\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe6/\x9d|d\xe8\xe2cZ\xeb\x88=\xd7;\xa6\x84\xee|\x10y\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xe6>xt\x14\xb9\x04\x84x\xa5\a35\x9e\xcd\xd7\xe3dz\xa6\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xe6FfXr\xe4\v\rz\xa2\xff\x82r\x9c\xaa\xba[\xc3\u8789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe6N\xf0\x12e\x8dT\xf8\xe8`\x9cN\x90#\xc0\x9f\xe8e\xc8;\x89\x01\x84\x93\xfb\xa6N\xf0\x00\x00\u07d4\xe6On\x1dd\x01\xb5l\akd\xa1\xb0\x86}\v/1\rN\x89\x02\u02edq\xc5:\xe5\x00\x00\u07d4\xe6g\xf6R\xf9W\u008c\x0ef\u04364\x17\xc8\f\x8c\x9d\xb8x\x89 \x9d\x92/RY\xc5\x00\x00\xe0\x94\xe6w\xc3\x1f\xd9\xcbr\x00u\u0724\x9f\x1a\xbc\xcdY\xec3\xf74\x8a\x01\xa6\u05be\xb1\xd4.\xe0\x00\x00\u07d4\xe6|,\x16e\u02038h\x81\x87b\x9fI\xe9\x9b`\xb2\u04fa\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xe6\x9al\xdb:\x8a}\xb8\xe1\xf3\f\x8b\x84\xcds\xba\xe0+\xc0\xf8\x8a\x03\x94\xfd\xc2\xe4R\xf6q\x80\x00\u07d4\xe6\x9d\x1c7\x8bw\x1e\x0f\xef\xf0Q\xdbi\xd9f\xacgy\xf4\xed\x89\x1d\xfaj\xaa\x14\x97\x04\x00\x00\u07d4\xe6\x9f\xcc&\xed\"_{.7\x984\xc5$\xd7\f\x175\u5f09lk\x93[\x8b\xbd@\x00\x00\u07d4\xe6\xa3\x01\x0f\x02\x01\xbc\x94\xffg\xa2\xf6\x99\xdf\xc2\x06\xf9\xe7gB\x89/\xa7\xcb\xf6dd\x98\x00\x00\u07d4\xe6\xa6\xf6\xddop\xa4V\xf4\xec\x15\xefz\xd5\xe5\u06f6\x8b\xd7\u0709\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe6\xb2\x0f\x98\n\xd8S\xad\x04\xcb\xfc\x88|\xe6`\x1ck\xe0\xb2L\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\u6cec?]M\xa5\xa8\x85}\v?0\xfcK+i+w\u05c9O%\x91\xf8\x96\xa6P\x00\x00\u07d4\xe6\xb9T_~\u0406\xe5R\x92F9\xf9\xa9\xed\xbb\xd5T\v>\x89\xcb\xd4{n\xaa\x8c\xc0\x00\x00\xe0\x94\xe6\xbc\xd3\n\x8f\xa18\xc5\xd9\xe5\xf6\xc7\xd2\u0680i\x92\x81-\u034a7\x0e\xa0\xd4|\xf6\x1a\x80\x00\x00\u07d4\xe6\xc8\x1f\xfc\xec\xb4~\xcd\xc5\\\vq\xe4\x85_>^\x97\xfc\x1e\x89\x12\x1e\xa6\x8c\x11NQ\x00\x00\u07d4\xe6\xcb&\vqmL\n\xb7&\xee\xeb\a\xc8pr\x04\xe2v\xae\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe6\xcb?1$\xc9\xc9\xcc84\xb1'K\xc33dV\xa3\x8b\xac\x89\x17+\x1d\xe0\xa2\x13\xff\x00\x00\xe0\x94\xe6\xd2\"\t\xff\u0438u\t\xad\xe3\xa8\xe2\xefB\x98y\u02c9\xb5\x8a\x03\xa7\xaa\x9e\x18\x99\xca0\x00\x00\u07d4\xe6\u051f\x86\xc2(\xf4sg\xa3^\x88l\xaa\xcb'\x1eS\x94)\x89\x16^\xc0\x9d\xa7\xa1\x98\x00\x00\u07d4\xe6\xe6!\xea\xab\x01\xf2\x0e\xf0\x83k|\xadGFL\xb5\xfd<\x96\x89\x11!\x93B\xaf\xa2K\x00\x00\u07d4\xe6\xe8\x861{jf\xa5\xb4\xf8\x1b\xf1d\xc58\xc2d5\x17e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe6\u98ddu\x0f\xe9\x949N\xb6\x82\x86\xe5\xeab\xa6\x99x\x82\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xe6\xec\\\xf0\u011b\x9c1~\x1epc\x15\uf7b7\xc0\xbf\x11\xa7\x8a\x03\xa4i\xf3F~\x8e\xc0\x00\x00\u07d4\xe6\xf5\xebd\x9a\xfb\x99Y\x9cAK'\xa9\xc9\xc8U5\u007f\xa8x\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xe6\xfe\n\xfb\x9d\xce\xdd7\xb2\xe2,E\x1b\xa6\xfe\xabg4\x803\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xe7\x10\xdc\u041b\x81\x01\xf9C{\xd9}\xb9\ns\xef\x99=\v\xf4\x89\x14\xee6\xc0Z\xc2R\x00\x00\u07d4\xe7'\xe6~\xf9\x11\xb8\x1fl\xf9\xc7?\xcb\xfe\xbc+\x02\xb5\xbf\u0189lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7.\x1d3\\\u009a\x96\xb9\xb1\xc0/\x00:\x16\xd9q\xe9\v\x9d\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xe71\x1c\x953\xf0\t,rH\xc9s\x9b[,\x86J4\xb1\u0389\x97\xf9}l\xc2m\xfe\x00\x00\u07d4\xe7;\xfe\xad\xa6\xf0\xfd\x01o\xbc\x84>\xbc\xf6\xe3p\xa6[\xe7\f\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xe7<\xcfCg%\xc1Q\xe2U\xcc\xf5!\f\xfc\xe5\xa4?\x13\xe3\x89\x01\x15NS!}\xdb\x00\x00\u07d4\xe7B\xb1\xe6\x06\x9a\x8f\xfc'\f\xc6\x1f\xa1d\xac\x15SE\\\x10]\x04\x88~\x14\x89\x06\x96\xd8Y\x00 \xbb\x00\x00\u07d4\xe7\\\x1f\xb1w\b\x9f>X\xb1\x06y5\xa6Yn\xf1s\u007f\xb5\x89\x05j\x87\x9f\xa7uG\x00\x00\u07d4\xe7\\;8\xa5\x8a?3\xd5V\x90\xa5\xa5\x97f\xbe\x18^\x02\x84\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe7a\xd2\u007f\xa3P,\xc7k\xb1\xa6\bt\x0e\x14\x03\u03dd\xfci\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xe7f\xf3O\xf1o<\xfc\xc9s!r\x1fC\xdd\xf5\xa3\x8b\f\xf4\x89T\x06\x923\xbf\u007fx\x00\x00\u07d4\xe7m\x94Z\xa8\x9d\xf1\xe4W\xaa4+1\x02\x8a^\x910\xb2\u03897\b\xba\xed=h\x90\x00\x00\u07d4\xe7s^\xc7e\x18\xfcj\xa9-\xa8qZ\x9e\xe3\xf6%x\x8f\x13\x89lM\x16\v\xaf\xa1\xb7\x80\x00\xe0\x94\xe7z\x89\xbdE\xdc\x04\xee\xb4\xe4\x1d{Ykp~nQ\xe7L\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xe7}}\uac96\u0234\xfa\a\xca;\xe1\x84\x16=Zm`l\x89\x05\x049\x04\xb6q\x19\x00\x00\u07d4\xe7\u007f\xeb\xab\xdf\b\x0f\x0f]\xca\x1d?Wf\xf2\xa7\x9c\x0f\xfa|\x89K\"\x9d(\xa8Ch\x00\x00\xe0\x94\u7025c\x06\xba\x1ek\xb31\x95,\"S\x9b\x85\x8a\xf9\xf7}\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\xe7\x81\xecs-@\x12\x02\xbb\x9b\xd18`\x91\r\xd6\u009a\xc0\xb6\x89C8t\xf62\xcc`\x00\x00\u07d4\xe7\x84\xdc\xc8s\xaa\x8c\x15\x13\xec&\xff6\xbc\x92\xea\xc6\xd4\xc9h\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe7\x91-L\xf4V,W=\xdc[q\xe3s\x10\xe3x\xef\x86\u0249\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xe7\x91\u0545\xb8\x996\xb2])\x8f\x9d5\xf9\xf9\xed\xc2Z)2\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7\x924\x9c\xe9\xf6\xf1O\x81\xd0g@\x96\xbe\xfa\x1f\x92!\xcd\xea\x89[]#J\r\xb48\x80\x00\u07d4\xe7\x96\xfdN\x83\x9bL\x95\xd7Q\x0f\xb7\xc5\xc7+\x83\xc6\xc3\xe3\u01c9\x1b\xc43\xf2?\x83\x14\x00\x00\xe0\x94\xe7\xa4/Y\xfe\xe0t\xe4\xfb\x13\xea\x9eW\xec\xf1\xccH(\"I\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xe7\xa4V\f\x84\xb2\x0e\x0f\xb5LIg\f)\x03\xb0\xa9lB\xa4\x89 j\xea\u01e9\x03\x98\x00\x00\u07d4\xe7\xa8\xe4q\xea\xfby\x8fET\xccnRg0\xfdV\xe6,}\x8965\u026d\xc5\u07a0\x00\x00\u07d4\u7f82\xc6Y<\x1e\xed\xdd*\xe0\xb1P\x01\xff \x1a\xb5{/\x89\x01\t\x10\xd4\xcd\xc9\xf6\x00\x00\u07d4\xe7\u01b5\xfc\x05\xfct\x8e[C\x81rdI\xa1\xc0\xad\x0f\xb0\xf1\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe7\xd1u$\xd0\v\xad\x82I|\x0f'\x15jd\u007f\xf5\x1d'\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xe7\xd2\x13\x94\u007f\u02d0J\xd78H\v\x1e\xed/\\2\x9f'\xe8\x89\x01\x03\u00f1\xd3\xe9\xc3\x00\x00\u07d4\xe7\xd6$\x06 \xf4,^\u06f2\xed\xe6\xae\xc4=\xa4\xed\x9bWW\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe7\xda`\x9d@\xcd\xe8\x0f\x00\xce[O\xfbj\xa9\u04304\x94\xfc\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe7\xf0oi\x9b\xe3\x1cD\vC\xb4\xdb\x05\x01\xec\x0e%&\x16D\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe7\xf4\xd7\xfeoV\x1f\u007f\xa1\xda0\x05\xfd6TQ\xad\x89\u07c9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe7\xfd\x8f\xd9Y\xae\xd2v~\xa7\xfa\x96\f\xe1\xdbS\xaf\x80%s\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe8\x0e\u007f\xef\x18\xa5\xdb\x15\xb0\x14s\xf3\xadkx\xb2\xa2\xf8\xac\u0649\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xe8\x13\u007f\xc1\xb2\xec|\xc7\x10:\xf9!\x89\x9bJ9\xe1\xd9Y\xa1\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\xe8\x1c-4l\n\xdfL\xc5g\b\xf69K\xa6\xc8\u0226J\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8,X\xc5yC\x1bg5F\xb5:\x86E\x9a\xca\xf1\u079b\x93\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xe84\xc6C\x18 \\\xa7\xddJ!\xab\xcb\b&l\xb2\x1f\xf0,\x8965\xc6 G9\u0640\x00\u07d4\xe86\x04\xe4\xffk\xe7\xf9o`\x18\xd3\xec0r\xecR]\xffk\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\xe0\x94\xe8E\xe3\x87\xc4\xcb\u07d8\"\x80\xf6\xaa\x01\xc4\x0eK\xe9X\u0772\x8a\x05K@\xb1\xf8R\xbd\xa0\x00\x00\u07d4\xe8H\xca~\xbf\xf5\xc2O\x9b\x9c1g\x97\xa4;\xf7\xc3V)-\x89\x06.\x11\\\x00\x8a\x88\x00\x00\u07d4\xe8KU\xb5%\xf1\x03\x9etK\x91\x8c\xb33$\x92\xe4^\xcaz\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe8O\x80v\xa0\xf2\x96\x9e\xcd3>\xef\x8d\xe4\x10B\x98b\x91\xf2\x89\x17k4O*x\xc0\x00\x00\u07d4\xe8d\xfe\xc0~\xd1!Je1\x1e\x11\xe3)\xde\x04\r\x04\xf0\xfd\x89Y\u0283\xf5\xc4\x04\x96\x80\x00\u07d4\xe8}\xba\xc66\xa3w!\xdfT\xb0\x8a2\xefIY\xb5\xe4\xff\x82\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xe8~\x9b\xbf\xbb\xb7\x1c\x1at\ft\xc7#Bm\xf5]\x06=\u064a\x01\xb1\x92\x8c\x00\u01e68\x00\x00\u07d4\xe8~\xacm`+A\t\xc9g\x1b\xf5{\x95\f,\xfd\xb9\x9dU\x89\x02\xb4\xf2\x19r\xec\xce\x00\x00\xe0\x94\u807b\xbeir-\x81\xef\xec\xaaH\u0455*\x10\xa2\xbf\xac\x8f\x8a\x03c\\\x9a\xdc]\xea\x00\x00\x00\u07d4\xe8\x92Is\x8b~\xce\xd7\xcbfjf\xe4s\xbcv\x82/U\t\x8d\x89\xb9\x1c\u0149lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8\xc3\u04f0\xe1\u007f\x97\xd1\xe7V\xe6\x84\xf9N\x14p\xf9\x9c\x95\xa1\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xe8\xc3\xf0E\xbb}8\xc9\xd2\U000d5c3a\x84\x92\xb2S#\t\x01\x8a\x01\xe7\xe4\x17\x1b\xf4\u04e0\x00\x00\u07d4\xe8\xccC\xbcO\x8a\xcf9\xbf\xf0N\xbf\xbfB\xaa\xc0j2\x84p\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xe8\xd9B\xd8/\x17^\xcb\x1c\x16\xa4\x05\xb1\x01C\xb3\xf4k\x96:\x89\x1e\xd2\xe8\xffm\x97\x1c\x00\x00\u07d4\xe8\u077e\xd72\xeb\xfeu@\x96\xfd\xe9\bk\x8e\xa4\xa4\xcd\xc6\x16\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe8\xder^\xca]\xef\x80_\xf7\x94\x1d1\xac\x1c.4-\xfe\x95\x89\x85~\ro\x1d\xa7j\x00\x00\u07d4\xe8\xe9\x85\x05\x86\xe9OR\x99\xabIK\xb8!\xa5\xf4\f\x00\xbd\x04\x89\xcf\x15&@\xc5\xc80\x00\x00\xe0\x94\xe8\xea\u047b\x90\xcc\u00ee\xa2\xb0\xdc\u0175\x80VUFU\xd1\u054a\x01\xa4\xab\xa2%\xc2\a@\x00\x00\u07d4\xe8\xea\xf1)D\t-\xc3Y\x9b9S\xfa|\xb1\xc9v\x1c\xc2F\x89a\x94\x04\x9f0\xf7 \x00\x00\xe0\x94\xe8\xedQ\xbb\xb3\xac\xe6\x9e\x06\x02K3\xf8hD\xc4sH\u06de\x8a\"\xf9\xea\x89\xf4\xa7\xd6\xc4\x00\x00\u07d4\xe8\xef\x10\r|\xe0\x89X2\xf2g\x8d\xf7-J\u03cc(\xb8\xe3\x89\x1b\x1bk\u05efd\xc7\x00\x00\u07d4\xe8\xf2\x99i\xe7\\e\xe0\x1c\xe3\xd8aT }\n\x9e|v\xf2\x89\xa2/\xa9\xa7:'\x19\x80\x00\u07d4\xe8\xfc6\xb0\x13\x1e\xc1 \xac\x9e\x85\xaf\xc1\f\xe7\vV\u0636\xba\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe9\n5L\xec\x04\u059e]\x96\xdd\xc0\xc5\x13\x8d=3\x15\n\xa0\x89\x1b\x1a}\u03caD\u04c0\x00\xe0\x94\xe9\x13>}1\x84]_+f\xa2a\x87\x92\xe8i1\x1a\xcff\x8a\x05\x17\xc0\xcb\xf9\xa3\x90\x88\x00\x00\u07d4\xe9\x1d\xac\x01\x95\xb1\x9e7\xb5\x9bS\xf7\xc0\x17\xc0\xb29[\xa4L\x89e\xea=\xb7UF`\x00\x00\u07d4\xe9\x1f\xa0\xba\xda\u0779\xa9~\x88\xd3\xf4\xdb|U\u05bbt0\xfe\x89\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\xe9#\xc0aw\xb3B~\xa4H\xc0\xa6\xff\x01\x9bT\xccT\x8d\x95\x89\x01\xf7\x80\x01Fg\xf2\x80\x00\xe0\x94\xe9=G\xa8\u0288]T\fNRo%\xd5\xc6\xf2\xc1\b\u0138\x8a\x17\xda:\x04\u01f3\xe0\x00\x00\x00\u07d4\xe9E\x8fh\xbb',\xb5g:\x04\xf7\x81\xb4\x03Uo\u04e3\x87\x89\x03N\x8b\x88\xce\xe2\xd4\x00\x00\u07d4\xe9IA\xb6\x03`\x19\xb4\x01j0\xc1\x03}Zi\x03\xba\xba\xad\x89*H\xac\xabb\x04\xb0\x00\x00\u07d4\xe9I[\xa5\x84'(\xc0\ud5fe7\xd0\xe4\"\xb9\x8di ,\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xe9M\xed\x99\u0735r\xb9\xbb\x1d\u02e3/m\xee\x91\xe0W\x98N\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\xe0\x94\xe9QyR}\uc951l\xa9\xa3\x8f!\\\x1e\x9c\xe77\xb4\u024a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xe9U\x91\x85\xf1f\xfc\x95\x13\xccq\x11aD\xce-\xeb\x0f\x1dK\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xe9^\x92\xbb\xc6\xde\a\xbf:f\x0e\xbf_\xeb\x1c\x8a5'\xe1\u0148\xfc\x93c\x92\x80\x1c\x00\x00\xe0\x94\xe9e\u06a3@9\xf7\xf0\xdfb7Z7\u5acar\xb3\x01\xe7\x8a\x01\x03\xfd\xde\u0373\xf5p\x00\x00\u07d4\xe9i\xea\x15\x95\xed\xc5\u0127\a\xcf\xde8\t)c2Q\xa2\xb0\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xe9k\x18N\x1f\x0fT\x92J\xc8t\xf6\v\xbfDptF\xb7+\x89\x9d\xcc\x05\x15\xb5n\f\x00\x00\xe0\x94\xe9m}L\xdd\x15U:NM1mmd\x80\xca<\xea\x1e8\x8a\x02\x95]\x02\xe1\xa15\xa0\x00\x00\u07d4\xe9n-8\x13\xef\xd1\x16_\x12\xf6\x02\xf9\u007fJb\x90\x9d\x1b;\xc0\xe9\xaa\"\u007f\x90\x89'\xcaK\xd7\x19\xf0\xb8\x00\x00\u07d4\xea,\x19}&\xe9\x8b\r\xa8>\x1br\u01c7a\x8c\x97\x9d=\xb0\x89\x01\x11du\x9f\xfb2\x00\x00\xe0\x94\xea7y\xd1J\x13\xf6\u01c5f\xbc\xde@5\x91A:b9\u06ca)\xb7d2\xb9DQ \x00\x00\u07d4\xeaN\x80\x9e&j\xe5\xf1<\xdb\u33dd\x04V\xe68m\x12t\x89\xf3\xf2\v\x8d\xfai\xd0\x00\x00\xe0\x94\xeaS\xc9T\xf4\xed\x97\xfdH\x10\x11\x1b\u06b6\x9e\xf9\x81\xef%\xb9\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xeaS\xd2ed\x85\x9d\x9e\x90\xbb\x0eS\xb7\xab\xf5`\xe0\x16,8\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xea`Ci\x12\xdek\xf1\x87\u04e4r\xff\x8fS3\xa0\xf7\xed\x06\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xea`T\x9e\xc7U?Q\x1d!I\xf2\xd4fl\xbd\x92C\xd9<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xeaf\xe7\xb8M\u037f6\xee\xa3\xe7[\x858*u\xf1\xa1]\x96\x89]\xbc\x91\x91&o\x11\x80\x00\u07d4\xeahlPW\t<\x17\x1cf\u06d9\xe0\x1b\x0e\xce\xcb0\x86\x83\x89\x14\u0768],\xe1G\x80\x00\u07d4\xeaj\xfe,\xc9(\xac\x83\x91\xeb\x1e\x16_\xc4\x00@\xe3t!\u7262\u007f\xa0c\xb2\xe2\xe6\x80\x00\u07d4\xeay\x05}\xab\xef^d\xe7\xb4O\u007f\x18d\x8e~S7\x18\u0489\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xea|Mm\xc7)\xcdk\x15|\x03\xad#|\xa1\x9a \x93F\u00c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\x81h\xfb\xf2%\xe7\x86E\x9c\xa6\xbb\x18\xd9c\xd2kPS\t\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xea\x81\u02868T\f\xd9\xd4\xd7=\x06\x0f,\xeb\xf2$\x1f\xfc>\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xea\x83\x17\x19yYB@A\xd9\xd7\xc6z>\xce\x1d\xbbx\xbbU\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xea\x85'\xfe\xbf\xa1\xad\xe2\x9e&A\x93)\u04d3\xb9@\xbb\xb7\u0709lj\xccg\u05f1\xd4\x00\x00\u07d4\xea\x8f0\xb6\xe4\xc5\xe6R\x90\xfb\x98d%\x9b\u0159\x0f\xa8\ue289\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xea\x94\xf3(\b\xa2\uf29b\xf0\x86\x1d\x1d$\x04\xf7\xb7\xbe%\x8a\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xea\xa4\\\xea\x02\xd8},\xc8\xfd\xa9CN-\x98[\xd4\x03\x15\x84\x89h\x1f\xc2\xccn+\x8b\x00\x00\xe0\x94\uac3d\x14\x83\t\x18l\xf8\xcb\xd1;r2\xd8\tZ\u02c3:\x8a\x02C\x9a\x88\x1cjq|\x00\x00\u07d4\uaed0\xd3y\x89\xaa\xb3\x1f\xea\xe5G\xe0\xe6\xf3\x99\x9c\xe6\xa3]\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xc0\x82~\xff\fn?\xf2\x8a}JT\xf6\\\xb7h\x9d{\x99\x89\x9a\xd9\u67ddGR\x00\x00\u07d4\xea\xc1H(&\xac\xb6\x11\x1e\x19\xd3@\xa4_\xb8QWk\xed`\x89\x01\xbe\x8b\xab\x04\u067e\x80\x00\xe0\x94\xea\xc1{\x81\xedQ\x91\xfb\b\x02\xaaT3s\x13\x83A\a\xaa\xa4\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xea\u00efW\x84\x92\u007f\u9958\xfcN\xec8\xb8\x10/7\xbcX\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xea\u01b9\x88BT.\xa1\v\xb7O&\xd7\xc7H\x8fi\x8bdR\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xea\xc7h\xbf\x14\xb8\xf9C.i\xea\xa8*\x99\xfb\xeb\x94\xcd\f\x9c\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4\xea\xd2\x1c\x1d\xec\u03ff\x1c\\\xd9f\x88\xa2Gki\xba\a\xceJ\x89\x03\xf2M\x8eJ\x00p\x00\x00\u07d4\xea\xd4\xd2\xee\xfbv\xab\xaeU3\x96\x1e\xdd\x11@\x04\x06\xb2\x98\xfc\x89\xd2U\xd1\x12\xe1\x03\xa0\x00\x00\u07d4\xea\xd6Rb\xed]\x12-\xf2\xb2u\x14\x10\xf9\x8c2\xd1#\x8fQ\x89\x05\x83\x17\xedF\xb9\xb8\x00\x00\u07d4\xea\xd7P\x16\u3801Pr\xb6\xb1\b\xbc\xc1\xb7\x99\xac\xf08>\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xea#\xaa\x05r\x00\xe7\xc9\xc1^\x8f\xf1\x90\xd0\xe6l\f\x0e\x83\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xea\xed\x16\xea\xf5\u06ab[\xf0)^^\a\u007fY\xfb\x82U\x90\v\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xea\xed\xcck\x8bib\xd5\xd9(\x8c\x15lW\x9dG\xc0\xa9\xfc\xff\x89\x04\x9b\x9c\xa9\xa6\x944\x00\x00\u07d4\xea\xf5#\x88Tn\xc3Z\xcaolc\x93\xd8\xd6\t\xde:K\xf3\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xeb\x10E\x8d\xac\xa7\x9eJk$\xb2\x9a\x8a\x8a\xdaq\x1b\u007f.\xb6\x89\u063beI\xb0+\xb8\x00\x00\u07d4\xeb\x1c\xea{E\u047dM\x0e*\x00{\u04ff\xb3Tu\x9e,\x16\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\xeb%H\x1f\u035c\"\x1f\x1a\xc7\xe5\xfd\x1e\u0353\a\xa1b\x15\xb8\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\xe0\x94\xeb.\xf3\u04cf\xe6R@<\xd4\xc9\xd8^\xd7\xf0h,\xd7\xc2\u078a\t\x0fSF\b\xa7(\x80\x00\x00\xe0\x94\xeb;\xddY\xdc\u0765\xa9\xbb*\xc1d\x1f\xd0!\x80\xf5\xf3e`\x8a\x01e\xc9fG\xb3\x8a \x00\x00\u07d4\xeb<\xe7\xfc8\x1cQ\xdb}_\xbdi/\x8f\x9e\x05\x8aLp=\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xebE?Z:\xdd\u074a\xb5gP\xfa\xdb\x0f\xe7\xf9M\x9c\x89\xe7\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xebO\x00\xe2\x836\xea\t\x94%\x88\ueb12\x18\x11\xc5\"\x14<\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xebR\xab\x10U4\x922\x9c\x1cT\x83:\xe6\x10\xf3\x98\xa6[\x9d\x89\b=lz\xabc`\x00\x00\u07d4\xebW\r\xba\x97R'\xb1\xc4-n\x8d\xea,V\u026d\x96\x06p\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xebc\x94\xa7\xbf\xa4\u0489\x11\u0565\xb2>\x93\xf3^4\f\"\x94\x89\x04:w\xaa\xbd\x00x\x00\x00\u07d4\xebh\x10i\x1d\x1a\xe0\u045eG\xbd\"\u03be\u0cfa'\xf8\x8a\x89\x87\x85c\x15\xd8x\x15\x00\x00\u07d4\xebvBL\x0f\u0557\xd3\xe3A\xa9d*\xd1\xee\x11\x8b+W\x9d\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xeb| +F+|\u0145]t\x84u_n&\xefC\xa1\x15\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xeb\x83\\\x1a\x91\x18\x17\x87\x8a3\xd1gV\x9e\xa3\xcd\u04c7\xf3(\x8965\u026d\xc5\u07a0\x00\x00\u07d4\ub268\x82g\t\t\xcf7~\x9ex(n\xe9{\xa7\x8dF\u0089+|\xc2\xe9\xc3\"\\\x00\x00\xe0\x94\xeb\x90\u01d3\xb3S\x97a\xe1\xc8\x14\xa2\x96q\x14\x86\x92\x19>\xb4\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xeb\x9c\xc9\xfe\bi\xd2\u06b5,\u01ea\xe8\xfdW\xad\xb3_\x9f\xeb\x89j\x93\xbb\x17\xaf\x81\xf8\x00\x00\xe0\x94\ub8c8\xb0\xda'\xc8{\x1c\xc0\xea\xc6\xc5{,Z\vE\x9c\x1a\x8a\x01p\xa0\xf5\x04\x0eP@\x00\x00\u07d4\xeb\xaa!m\xe9\xccZC\x03\x17\a\xd3o\xe6\u057e\xdc\x05\xbd\xf0\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xeb\xac+D\b\xefT1\xa1;\x85\b\xe8bP\x98!\x14\xe1E\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xeb\xb6,\xf8\xe2,\x88K\x1b(\xc6\xfa\x88\xfb\xbc\x17\x93\x8a\xa7\x87\x89+By\x84\x03\u0278\x00\x00\u07d4\xeb\xb7\xd2\xe1\x1b\u01b5\x8f\n\x8dE\xc2\xf6\xde0\x10W\n\u0211\x89\x01s\x17\x90SM\xf2\x00\x00\u07d4\xeb\xbbO,=\xa8\xbe>\xb6-\x1f\xfb\x1f\x95\x02a\u03d8\xec\u0689lk\x93[\x8b\xbd@\x00\x00\u07d4\xeb\xbdM\xb9\x01\x99R\u058b\x1b\x0fm\x8c\xf0h<\x008{\xb5\x89\x12\x04\x01V=}\x91\x00\x00\u07d4\xeb\xbe\xeb%\x91\x84\xa6\xe0\x1c\xcc\xfc\"\a\xbb\u0603xZ\xc9\n\x89!\x9b\xc1\xb0G\x83\xd3\x00\x00\u07d4\xeb\xd3V\x15j81#4=H\x84;\xff\xeda\x03\xe8f\xb3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xeb\xd3{%ec\xe3\fo\x92\x89\xa8\xe2p/\bR\x88\b3\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xeb\xe4l\xc3\xc3L2\xf5\xad\xd6\xc3\x19[\xb4\x86\xc4q>\xb9\x18\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xeb\xff\x84\xbb\xefB0q\xe6\x04\xc3a\xbb\xa6w\xf5Y=\xefN\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xec\t'\xba\xc7\xdc6f\x9c(5J\xb1\xbe\x83\xd7\xee\xc3\t4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\x0e\x18\xa0\x1d\xc4\xdc]\xaa\xe5g\xc3\xfaL\u007f\x8f\x9bY\x02\x05\x89\x11\x1f\xfe@JA\xe6\x00\x00\xe0\x94\xec\x116,\xec\x81\t\x85\xd0\xeb\xbd{sE\x14D\x98[6\x9f\x8a\x06ZNIWpW1\x80\x00\u07d4\xec,\xb8\xb97\x8d\xff1\xae\xc3\xc2.\x0em\xad\xff1J\xb5\u0749lk\x93[\x8b\xbd@\x00\x00\u07d4\xec0\xad\u0749[\x82\xee1\x9eT\xfb\x04\xcb+\xb09q\xf3k\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec;\x8bX\xa1'\x03\xe5\x81\xce_\xfd~!\xc5}\x1e\\f?\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xecHg\xd2\x17Z\xb5\xb9F\x93aYUFUF\x84\u0364`\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xecM\b\xaa.GIm\u0287\"]\xe3?+@\xa8\xa5\xb3o\x89\b\x90\xb0\xc2\xe1O\xb8\x00\x00\u07d4\xecX\xbc\r\f \xd8\xf4\x94efAS\xc5\xc1\x96\xfeY\u6f89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xec[\x19\x8a\x00\u03f5Z\x97\xb5\xd56D\xcf\xfa\x8a\x04\u04abE\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec]\xf2'\xbf\xa8]z\xd7kBn\x1c\xee\x96;\xc7\xf5\x19\u074965\u026d\xc5\u07a0\x00\x00\xe0\x94\xec_\xea\xfe!\f\x12\xbf\u0265\xd0Y%\xa1#\xf1\xe7?\xbe\xf8\x8a`\x8f\xcf=\x88t\x8d\x00\x00\x00\u07d4\xeci\x04\xba\xe1\xf6\x97\x90Y\x17\t\xb0`\x97\x83s?%s\xe3\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\xe0\x94\xecs\x11L^@o\u06fe\t\xb4\xfab\x1b\xd7\x0e\xd5N\xa1\xef\x8a\x050%\xcd!o\xceP\x00\x00\u07d4\xecs\x83=\xe4\xb8\x10\xbb\x02x\x10\xfc\x8fi\xf5D\xe8<\x12\u044965\u026d\xc5\u07a0\x00\x00\u07d4\xecu\xb4\xa4u\x13\x12\v\xa5\xf8`9\x81O\x19\x98\xe3\x81z\u00c9\t\xb0\xbc\xe2\xe8\xfd\xba\x00\x00\u07d4\xecv\xf1.W\xa6U\x04\x03?,\v\xceo\xc0;\xd7\xfa\n\u0109\xc2\x12z\xf8X\xdap\x00\x00\u0794\xec\x80\x14\xef\xc7\xcb\xe5\xb0\xceP\xf3V,\xf4\xe6\u007f\x85\x93\xcd2\x88\xf0\x15\xf2W6B\x00\x00\u07d4\xec\x82\xf5\r\x06G_hM\xf1\xb3\x92\xe0\r\xa3A\xaa\x14TD\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xec\x83\xe7\x98\u00d6\xb7\xa5^*\"$\xab\u0343K'\xeaE\x9c\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xec\x89\xf2\xb6x\xa1\xa1[\x914\xec^\xb7\fjb\a\x1f\xba\xf9\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xec\x8c\x1d{j\xac\xcdB\x9d\xb3\xa9\x1e\xe4\xc9\xeb\x1c\xa4\xf6\xf7<\x89\xe6d\x99\"\x88\xf2(\x00\x00\xe0\x94\xec\x98Q\xbd\x91rpa\x02g\xd6\x05\x18\xb5M<\xa2\xb3[\x17\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xec\x99\xe9]\xec\xe4o\xff\xfb\x17^\xb6@\x0f\xbe\xbb\b\ue6d5\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xec\xa5\xf5\x87\x92\xb8\xc6-*\xf5Vq~\xe3\xee0(\xbeM\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\xabZ\xba[\x82\x8d\xe1pS\x81\xf3\x8b\xc7D\xb3+\xa1\xb47\x892\xf5\x1e\u06ea\xa30\x00\x00\u07d4\xec\xaf3P\xb7\xce\x14M\x06\x8b\x18`\x10\x85,\x84\xdd\f\xe0\xf0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xec\xb9LV\x8b\xfeY\xad\xe6Pd_O&0lsl\xac\xe4\x89\x0e~\xeb\xa3A\vt\x00\x00\xe0\x94\xec\xbeB^g\r9\tN \xfbVC\xa9\xd8\x18\xee\xd26\u078a\x01\x0f\f\xf0d\xddY \x00\x00\xe0\x94\xec\xbe^\x1c\x9a\u04b1\xdc\xcf\n0_\xc9R/Fi\xdd:\xe7\x8a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xec\xcfz\x04W\xb5f\xb3F\xcag:\x18\x0fDA0!j\u00c9\x05k\xc7^-c\x10\x00\x00\u07d4\xec\u0466(\x025\x1aAV\x8d#\x030\x04\xac\xc6\xc0\x05\xa5\u04c9\x02\xb5\xe3\xaf\x16\xb1\x88\x00\x00\u07d4\xec\xd2v\xafd\u01dd\x1b\u0669+\x86\xb5\u835a\x95\xeb\x88\xf8\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xec\u0506\xfc\x19g\x91\xb9,\xf6\x12\xd3HaO\x91VH\x8b~\x8a\x02\x8a\x85t%Fo\x80\x00\x00\u07d4\xec\xda\xf92)\xb4^\xe6r\xf6]\xb5\x06\xfb^\xca\x00\xf7\xfc\xe6\x89W\x01\xf9m\xcc@\xee\x80\x00\u07d4\xec\xe1\x11g\vV<\u037e\xbc\xa5#\x84)\x0e\xcdh\xfe\\\x92\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xec\xe1\x15&\x82\xb7Y\x8f\xe2\xd1\xe2\x1e\xc1U3\x88T5\xac\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xec\xe1)\bw\xb5\x83\xe3a\xa2\xd4\x1b\x00\x93F\xe6'N%8\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xec\xf0]\a\xea\x02n~\xbfIA\x00#5\xba\xf2\xfe\xd0\xf0\x02\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xec\xf2L\xdd|\"\x92\x8cD\x1eiM\xe4\xaa1\xb0\xfa\xb5\x97x\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xec\xfd\x00M\x02\xf3l\xd4\u0634\xa8\xc1\xa9S;j\xf8\\\xd7\x16\x8a\x01\x0fA\xac\xb4\xbb;\x9c\x00\x00\xe0\x94\xed\x02\x06\xcb#1Q(\xf8\xca\xff&\xf6\xa3\v\x98Tg\xd0\"\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xed\x10e\xdb\u03dds\xc0O\xfcy\b\x87\r\x88\x14h\xc1\xe12\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xed\x12vQ;o\u0186(\xa7A\x85\xc2\xe2\f\xbb\xcax\x17\xbf\x89\nZ\xa8P\t\xe3\x9c\x00\x00\xe0\x94\xed\x12\xa1\xba\x1f\xb8\xad\xfc\xb2\r\xfa\x19X.RZ\xa3\xb7E$\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xed\x16\xce9\xfe\xef;\xd7\xf5\xd1b\x04^\x0fg\xc0\xf0\x00F\xbb\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xed\x1a\\C\xc5t\xd4\xe94)\x9b$\xf1G,\u071f\xd6\xf0\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xed\x1b$\xb6\x91-Q\xb34\xac\r\xe6\xe7q\xc7\xc0EF\x95\xea\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xed\x1f\x1e\x11Z\r`\xce\x02\xfb%\xdf\x01M(\x9e:\f\xbe}\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xed10\\1\x9f\x92s\u04d3m\x8f[/q\u9c72)c\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xed2z\x14\xd5\u03ed\u0641\x03\xfc\t\x99q\x8d~\xd7\x05(\xea\x89N\x10\x03\xb2\x8d\x92\x80\x00\x00\u07d4\xed<\xbc7\x82\u03bdg\x98\x9b0\\A3\xb2\xcd\xe3\"\x11\xeb\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xed@\x14S\x8c\xeefJ/\xbc\xb6\xdcf\x9fz\xb1m\v\xa5|\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xedA\u188f\\\xaa\x848\x80\xefN\x8b\b\xbdl3\x14\x1e\u07c9*\xd5\xdd\xfaz\x8d\x83\x00\x00\xe0\x94\xedK\xe0J\x05-z\u0333\xdc\u03901\x9d\xba@ \xab,h\x8a\a\xf3zp\xea\xf3b\x17\x80\x00\xe0\x94\xedR\xa2\xcc\bi\u071e\x9f\x84+\u0415|G\xa8\xe9\xb0\xc9\xff\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xed[LA\xe7b\xd9B@Cs\xca\xf2\x1e\xd4a]%\xe6\xc1\x89m-O=\x95%\xb4\x00\x00\u07d4\xed`\u012bnT\x02\x061~5\x94zc\xa9\xcak\x03\xe2\u02c9\x03\x1a\u066d\vF\u007f\x80\x00\u07d4\xedd\x1e\x066\x8f\xb0\xef\xaa\x17\x03\xe0\x1f\xe4\x8fJhS\t\xeb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xedfC\xc0\xe8\x88K-2\x11\x857\x85\xa0\x8b\xf8\xf3>\u049f\x89Hz\x9a0E9D\x00\x00\xe0\x94\xedp\xa3|\xdd\x1c\xbd\xa9tm\x93\x96X\xae*a\x81(\x85x\x8a\x02\bj\xc3Q\x05&\x00\x00\x00\u07d4\xedsFvn\x1agm\r\x06\xec\x82\x18g\xa2v\xa0\x83\xbf1\x89\u064a\t1\xcc-I\x00\x00\u07d4\xed\x86&\x16\xfc\xbf\xb3\xbe\xcbt\x06\xf7<\\\xbf\xf0\f\x94\aU\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xed\x9e\x03\f\xa7\\\xb1\u049e\xa0\x1d\rL\xdf\xdc\xcd8D\xb6\xe4\x89\x01\xac\xc1\x16\u03ef\xb1\x80\x00\xe0\x94\ud7bc\u02e4/\x98\x15\xe7\x823&m\xd6\xe85\xb6\xaf\xc3\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\ud7f1\xf5\xaf/\xbf\u007f\xfcP)\xce\xe4+p\xff\\'[\xf5\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xed\xa4\xb2\xfaY\u0584\xb2z\x81\r\xf8\x97\x8as\xdf0\x8ac\u0089\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xed\xb4s59y\xa2\x06\x87\x9d\xe1D\xc1\n:\xcf\x12\xa7'OV9a\xf57R\x9d\x89\xc7\u0789lk\x93[\x8b\xbd@\x00\x00\u07d4\xeer\x88\xd9\x10\x86\xd9\xe2\xeb\x91\x00\x14\u066b\x90\xa0-x\u00a0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xee|=\xed|(\xf4Y\xc9/\xe1;M\x95\xba\xfb\xab\x026}\x89%\xf2s\x93=\xb5p\x00\x00\xe0\x94\xee\x86} \x91k\xd2\xe9\xc9\xec\xe0\x8a\xa0C\x85\xdbf|\x91.\x8a\n\x96\x81c\xf0\xa5{@\x00\x00\u07d4\ue25b\x02\xcb\xcb99\xcda\xde\x13B\xd5\x04\x82\xab\xb6\x852\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xee\x90m}_\x17H%\x81t\xbeL\xbc8\x93\x03\x02\xab{B\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\ue5ea\x8a\u019e\xdfz\x98}mp\x97\x9f\x8e\xc1\xfb\xcaz\x94\x89\x14b\fW\xdd\xda\xe0\x00\x00\u07d4\xee\xa1\xe9y\x88\xdeu\xd8!\xcd(\xadh\"\xb2,\u0398\x8b1\x89\x1c0s\x1c\xec\x03 \x00\x00\xe0\x94\xee\u048c?\x06\x8e\tJ0K\x85<\x95\nh\t\xeb\xcb\x03\xe0\x8a\x03\xa9\u057a\xa4\xab\xf1\xd0\x00\x00\u07d4\xee\u04c4\xef-A\xd9\xd2\x03\x97NW\xc1#(\xeav\x0e\b\xea\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xee\xdflB\x80\xe6\xeb\x05\xb94\xac\xe4(\xe1\x1dB1\xb5\x90[\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xee\xe7a\x84~3\xfda\u0653\x87\xee\x14b\x86\x94\u047f\xd5%\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xee\xe9\xd0Rn\xda\x01\xe41\x16\xa3\x952-\u0689pW\x8f9\x8a\x02\x1e\x19\x99\xbb\xd5\u04be\x00\x00\u07d4\xee\xf1\xbb\xb1\xe5\xa8?\u0782H\xf8\x8e\xe3\x01\x8a\xfa-\x132\xeb\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xee\xfb\xa1-\xfc\x99gB\xdby\x04d\xca}';\xe6\xe8\x1b>\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xee\xfd\x05\xb0\xe3\xc4\x17\xd5[3C\x06\x04\x86\xcd\xd5\xe9*\xa7\xa6\x89M\x85<\x8f\x89\b\x98\x00\x00\u07d4\xef\r\xc7\xddzS\xd6\x12r\x8b\xcb\u04b2|\x19\xddM}fo\x89&A\x1c[5\xf0Z\x00\x00\u07d4\xef\x11RR\xb1\xb8E\u0345\u007f\x00-c\x0f\x1bo\xa3zNP\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xef\x1c\x04w\xf1\x18M`\xac\u02b3t\xd3tUz\n>\x10\xf3\x89\b=lz\xabc`\x00\x00\u07d4\xef,4\xbbH}7b\xc3\u0327\x82\xcc\xddz\x8f\xbb\n\x991\x89\t\xc2\x00vQ\xb2P\x00\x00\u07d4\xef5\xf6\u0531\a^j\xa19\x15\x1c\x97K/FX\xf7\x058\x89<;\xc3?\x94\xe5\r\x80\x00\u07d4\xef9\u0291s\xdf\x15S\x1ds\xe6\xb7*hKQ\xba\x0f+\xb4\x89V\xa0\xb4un\xe28\x00\x00\u07d4\xefF<&y\xfb'\x91d\xe2\f=&\x915\x87s\xa0\xad\x95\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xefG\xcf\a>6\xf2q\xd5\"\xd7\xfaNq \xadP\a\xa0\xbc\x89\x87\x86x2n\xac\x90\x00\x00\u07d4\xefa\x15[\xa0\t\xdc\u07be\xf1\v(\xd9\xda=\x1b\xc6\xc9\xce\u0509\x034-`\xdf\xf1\x96\x00\x00\u0794\xefix\x1f2\xff\xce34o,\x9a\xe3\xf0\x84\x93\xf3\xe8/\x89\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xefv\xa4\u034f\xeb\xcb\u0278\x18\xf1x(\xf8\xd94s\xf3\xf3\u02c9\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\uf4c1\x8fhM\xb0\xc3g^\xc8\x132\xb3\x18>\xcc(\xa4\x95\x89T\x06\x923\xbf\u007fx\x00\x00\xe0\x94\xef\x9fY\xae\xdaA\x8c\x14\x94h-\x94\x1a\xabI$\xb5\xf4\x92\x9a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\uf9b1\xf0\xdb`57\x82h\x91\xb8\xb4\xbc\x169\x84\xbb@\u03495e\x9e\xf9?\x0f\xc4\x00\x00\u07d4\xef\xbdR\xf9}\xa5\xfd:g:F\xcb\xf30D{~\x8a\xad\\\x89\x05l<\x9b\x80\xa0\xa6\x80\x00\xe0\x94\xef\xc8\xcf\x19c\u0269Rg\xb2(\xc0\x86#\x98\x89\xf4\xdf\xd4g\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xef\u02ae\x9f\xf6M,\xd9[RI\xdc\xff\xe7\xfa\xa0\xa0\xc0\xe4M\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xef\xcc\xe0k\xd6\b\x9d\x0eE\x8e\xf5a\xf5\xa6\x89H\n\xfep\x00\x89 \x86\xac5\x10R`\x00\x00\u07d4\xef\xe0g]\xa9\x8a]\xdap\u0356\x19k\x87\xf4\xe7&\xb43H\x89?\x19\xbe\xb8\xdd\x1a\xb0\x00\x00\u07d4\xef\xe8\xff\x87\xfc&\x0e\agc\x8d\xd5\xd0/\xc4g.\x0e\xc0m\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xef\xeb\x19\x97\xaa\xd2w\xcc3C\x0ea\x11\xed\tCY@H\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xef\xee\xa0\x10uo\x81\xdaK\xa2[r\x17\x87\xf0X\x17\v\uff49\x01\u009c\x9c\xf7p\xef\x00\x00\u07d4\xef\xf5\x1dr\xad\xfa\xe1C\xed\xf3\xa4+\x1a\xecU\xa2\xcc\xdd\v\x90\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xef\xf8kQ#\xbc\xdc\x17\xedL\xe8\xe0[~\x12\xe5\x13\x93\xa1\xf7\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xef\xfc\x15\u41f1\xbe\xda\n\x8d\x13%\xbd\xb4\x17\"@\xdcT\n\x89\x03\x8599\xee\xe1\xde\x00\x00\xe0\x94\xf0\x11\x95\xd6W\xef<\x94.l\xb89I\xe5\xa2\v\\\xfa\x8b\x1e\x8a\x05ts\xd0]\xab\xae\x80\x00\x00\u07d4\xf0'\x96)Q\x01gB\x88\xc1\xd94g\x05=\x04\"\x19\xb7\x94\x89(\x1d\x90\x1fO\xdd\x10\x00\x00\u07d4\xf09h={=\"[\xc7\xd8\u07ed\xefc\x164A\xbeA\xe2\x89\x01\xdd\x1eK\xd8\xd1\xee\x00\x00\u07d4\xf0Jj7\x97\b\xb9B\x8dr*\xa2\xb0kw\xe8\x895\u03c9\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xf0M,\x91\xef\xb6\xe9\xc4_\xfb\xe7KCL\x8c_+\x02\x8f\x1f\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf0W\xaaf\xcav~\xde\x12J\x1c[\x9c\xc5\xfc\x94\xef\v\x017\x89p\xa2K\u02b6\xf4]\x00\x00\u07d4\xf0[\xa8\u05f6\x859\xd930\v\xc9(\x9c=\x94t\xd0A\x9e\x89\x06\xda'\x02M\xd9`\x00\x00\u07d4\xf0\\\xee\xabeA\x05dp\x99Qw<\x84E\xad\x9fN\u01d7\x89\x10C\x16'\xa0\x93;\x00\x00\xe0\x94\xf0_\xcdL\rs\xaa\x16~US\xc8\xc0\xd6\xd4\xf2\xfa\xa3\x97W\x8a\x02\xd2\xd6l1p\xb2\x98\x00\x00\u07d4\xf0g\xe1\xf1\u0583UjL\xc4\xfd\f\x03\x13#\x9f2\xc4\xcf\u060965\u026d\xc5\u07a0\x00\x00\u07d4\xf0g\xfb\x10\u07f2\x93\u962b\xe5d\xc0U\xe34\x8f\x9f\xbf\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0h\xdf\xe9]\x15\xcd:\u007f\x98\xff\xa6\x88\xb44hB\xbe&\x90\x89D\n\xd8\x19\xe0\x97L\x00\x00\xe0\x94\xf0j\x85J<]\xc3m\x1cI\xf4\xc8}m\xb33\xb5~J\u074a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf0y\xe1\xb1&_P\xe8\u0229\x8e\xc0\u01c1^\xb3\xae\xac\x9e\xb4\x89\x01\x16\xdc:\x89\x94\xb3\x00\x00\xe0\x94\xf0{\xd0\xe5\xc2\xcei\xc7\u0127$\xbd&\xbb\xfa\x9d*\x17\xca\x03\x8a\x01@a\xb9\xd7z^\x98\x00\x00\xe0\x94\xf0\x83*k\xb2U\x03\xee\xcaC[\xe3\x1b\v\xf9\x05\xca\x1f\xcfW\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xf0\x9b>\x87\xf9\x13\xdd\xfdW\xae\x80I\xc71\u06e9\xb66\xdf\u00c9 \xf5\xb1\uab4d\x80\x00\x00\u07d4\xf0\xb14\v\x99oo\v\xf0\xd9V\x1c\x84\x9c\xaf\u007fD0\xbe\xfa\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf0\xb1\xf9\xe2x2\xc6\xdei\x14\xd7\n\xfc#\x8ct\x99\x95\xac\xe4\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xf0\xb4i\xea\xe8\x9d@\f\xe7\xd5\xd6j\x96\x95\x03p6\xb8\x89\x03\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xf0\xb9\u0583\u03a1+\xa6\x00\xba\xac\xe2\x19\xb0\xb3\xc9~\x8c\x00\xe4\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf0\xbe\x0f\xafMy#\xfcDF\"\u0458\f\xf2\u0650\xaa\xb3\a\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0\xc0\x81\xdaR\xa9\xae6d*\xdf^\b _\x05\xc5Ah\xa6\x89\x06\x04o7\xe5\x94\\\x00\x00\u07d4\xf0\xc7\r\rm\xabvc\xaa\x9e\xd9\xce\xeaV~\xe2\u01b0'e\x89qC\x8a\u0167\x91\xa0\x80\x00\u07d4\xf0\xcb\xef\x84\xe1ic\x00\x98\xd4\xe3\x01\xb2\x02\b\xef\x05\x84j\u0249\x0e\v\x83EPkN\x00\x00\u07d4\xf0\xd2\x16c\u0630\x17n\x05\xfd\xe1\xb9\x0e\xf3\x1f\x850\xfd\xa9_\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xf0\xd5\xc3\x1c\xcbl\xbe0\xc7\xc9\xea\x19\xf2h\xd1Y\x85\x1f\x8c\x9c\x8a\x03\x89O\x0eo\x9b\x9fp\x00\x00\u07d4\xf0\xd6L\xf9\xdf\tt\x113\xd1pH_\xd2K\x00P\x11\xd5 \x89\x1b\b\x93A\xe1O\xcc\x00\x00\u07d4\xf0\xd8X\x10^\x1bd\x81\x01\xac?\x85\xa0\xf8\"+\xf4\xf8\x1dj\x89 \x86\xac5\x10R`\x00\x00\u07d4\xf0\xdcC\xf2\x05a\x91'P{+\x1c\x1c\xfd\xf3-(1\t \x89\x10^\xb7\x9b\x94\x17\b\x80\x00\u07d4\xf0\xe1\u07e4*\u07ac/\x17\xf6\xfd\xf5\x84\xc9Hb\xfdV3\x93\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf0\xe2d\x9c~j?,]\xfe3\xbb\xfb\xd9'\xca<5\nX\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf0\xe7\xfb\x9eB\nS@\xd56\xf4\x04\b4O\xea\xef\xc0j\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf1\x04b\xe5\x8f\xcc\a\U000d5121\x87c\x94Q\x16~\x85\x92\x01\x89\t4\xdd]3\xbc\x97\x00\x00\xe0\x94\xf1\x06a\xff\x94\x14\x0f >zH%rCy8\xbe\xc9\xc3\xf7\x8a\x04<3\xc1\x93ud\x80\x00\x00\u0794\xf1\x14\xff\r\x0f$\xef\xf8\x96\xed\xdeTq\u07a4\x84\x82J\x99\xb3\x88\xbe -j\x0e\xda\x00\x00\u07d4\xf1\x16\xb0\xb4h\x0fS\xabr\xc9h\xba\x80.\x10\xaa\x1b\xe1\x1d\u0209\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xf1\x1c\xf5\xd3cto\xeehd\xd3\xca3m\xd8\x06y\xbb\x87\xae\x8a\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xf1\x1e\x01\u01e9\xd1$\x99\x00_M\xaew\x16\tZ4\x17bw\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf1;\b0\x93\xbaVN-\xc61V\x8c\xf7T\r\x9a\x0e\xc7\x19\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xf1O\x0e\xb8m\xb0\xebhu?\x16\x91\x8e]K\x80t7\xbd>\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf1Qx\xff\xc4:\xa8\a\x0e\xce2~\x93\x0f\x80\x9a\xb1\xa5O\x9d\x89\n\xb6@9\x12\x010\x00\x00\u07d4\xf1V\xdc\v*\x98\x1e[U\xd3\xf2\xf0;\x814\xe31\u06ed\xb7\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf1]\x9dZ!\xb1\x92\x9ey\x03q\xa1\u007f\x16\xd9_\fie\\\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1^\x18,O\xbb\xady\xbd\x934\"B\xd4\xdc\xcf+\xe5\x89%\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf1bM\x98\ve3o\xea\u0166\xd5A%\x00\\\xfc\xf2\xaa\u02c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1g\xf5\x86\x8d\xcfB3\xa7\x83\x06\th,\xaf-\xf4\xb1\xb8\a\x89\x81\xe5B\xe1\xa78?\x00\x00\u07d4\xf1m\xe1\x89\x1d\x81\x96F\x13\x95\xf9\xb16&[;\x95F\xf6\xef\x89\x01\xb2\x8e\x1f\x98\xbb\u0380\x00\u07d4\xf1z\x92\xe06\x1d\xba\xce\xcd\xc5\xde\r\x18\x94\x95Z\xf6\xa9\xb6\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1z\xdbt\x0fE\u02fd\xe3\tN~\x13qo\x81\x03\xf5c\xbd\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1\x8b\x14\xcb\xf6iC6\xd0\xfe\x12\xac\x1f%\xdf-\xa0\xc0]\xbb\x89\xd8\xd4`,&\xbfl\x00\x00\u07d4\xf1\x9b98\x9dG\xb1\x1b\x8a,?\x1d\xa9\x12M\xec\xff\xbe\xfa\xf7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf1\x9f\x195\b9>M*\x12{ \xb2\x03\x1f9\xc8%\x81\u0189\xbd\xbdz\x83\xbd/l\x00\x00\u07d4\xf1\xa1\xf3 @yd\xfd<\x8f.,\u0224X\r\xa9O\x01\xea\x89ll!wU|D\x00\x00\u07d4\xf1\xb4\xec\xc65%\xf7C,=\x83O\xfe+\x97\x0f\xbe\xb8r\x12\x89\xa2\xa2@h\xfa\u0340\x00\x00\u07d4\U000753ef\xfa\x87\x94\xf5\n\xf8\xe8\x83\t\u01e6&TU\xd5\x1a\x8963\x03\"\xd5#\x8c\x00\x00\u07d4\xf1\xc8\u0129A\xb4b\x8c\rl0\xfd\xa5dR\u065c~\x1bd\x89N\x8c\xea\x1e\xdeu\x04\x00\x00\u07d4\xf1\xda@so\x99\xd5\xdf;\x06\x8a]t_\xaf\xc6F?\u0271\x89\x06\x96\xca#\x05\x8d\xa1\x00\x00\u07d4\xf1\u070a\xc8\x10B\xc6z\x9c\\c2!\xa8\xf76>e\x87\f\x9f(t$\u04a9`\x89J\xcfX\xe0rW\x10\x00\x00\u07d4\xf2B\u0684]B\u053fw\x9a\x00\xf2\x95\xb4\aP\xfeI\xea\x13\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf2RY\xa5\xc99\xcd%\x96l\x9bc\x03\xd3s\x1cS\u077cL\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf2^Lp\xbcFV2\u021eV%\xa82\xa7r/k\xff\xab\x89\xf3K\x82\xfd\x8e\x91 \x00\x00\u07d4\xf2k\xce\xdc\xe3\xfe\xad\u03a3\xbc>\x96\xeb\x10@\xdf\xd8\xff\u1809*\x03I\x19\u07ff\xbc\x00\x00\u07d4\xf2py%v\xf0]QD\x93\xff\xd1\xf5\xe8K\xecK-\xf8\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf2s,\xf2\xc1;\x8b\xb8\xe7I*\x98\x8f_\x89\xe3\x82s\xdd\u0209 \x86\xac5\x10R`\x00\x00\xe0\x94\xf2t.hY\xc5i\xd5\xf2\x10\x83Q\xe0\xbfM\xca5*H\xa8\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf2\x81:d\xc5&]\x02\x025\u02dc1\x9bl\x96\xf9\x06\xc4\x1e\x89\x12\xf99\u025e\u06b8\x00\x00\u07d4\xf2\x87\xffR\xf4a\x11z\xdb>\x1d\xaaq\x93-\x14\x93\xc6_.\x89\xc5S%\xcat\x15\xe0\x00\x00\u07d4\xf2\xab\x11au\x02D\xd0\xec\xd0H\xee\r>Q\xab\xb1C\xa2\xfd\x89B\xfe+\x90ss\xbc\x00\x00\u07d4\xf2\xb4\xab,\x94'\xa9\x01^\xf6\xee\xff\xf5\xed\xb6\x019\xb7\x19\u0449&\u06d9*;\x18\x00\x00\x00\u07d4\xf2\xc0>*8\x99\x8c!d\x87`\xf1\xe5\xae~\xa3\a}\x85\"\x89\x8f?q\x93\xab\a\x9c\x00\x00\u0794\xf2\u0090N\x9f\xa6d\xa1\x1e\xe2VV\xd8\xfd,\xc0\u0665\"\xa0\x88\xb9\x8b\xc8)\xa6\xf9\x00\x00\u07d4\xf2\xc3b\xb0\xef\x99\x1b\xc8/\xb3nf\xffu\x93*\xe8\u0742%\x89\x04\x02\xf4\xcf\xeeb\xe8\x00\x00\u07d4\xf2\xd0\xe9\x86\xd8\x14\xea\x13\xc8\xf4f\xa0S\x8cS\u0712&Q\xf0\x89J\xcfX\xe0rW\x10\x00\x00\xe0\x94\xf2\u04775w$\xecL\x03\x18[\x87\x9bc\xf5~&X\x91S\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xf2\xd5v<\xe0s\x12~,\xed\xdeo\xab\xa7\x86\xc7<\xa9AA\x8a\x01\xacB\x86\x10\x01\x91\xf0\x00\x00\xe0\x94\xf2\u055c\x89#u\x90s\xd6\xf4\x15\xaa\xf8\xeb\x06_\xf2\U000f614a\x01\xab,\xf7\xc9\xf8~ \x00\x00\u07d4\xf2\xe9\x9f\\\xbb\x83kz\xd3bGW\x1a0,\xbeKH\x1ci\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf2\xed>w%J\u02c3#\x1d\xc0\x86\x0e\x1a\x11$+\xa6'\u06c9kV\x05\x15\x82\xa9p\x00\x00\xe0\x94\xf2\xed\xde7\xf9\xa8\u00dd\u07a2My\xf4\x01WW\xd0k\xf7\x86\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xf2\xef\xe9e`\xc9\xd9{r\xbd6DxC\x88\\\x1d\x90\xc21\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf2\xfb\xb6\u0607\xf8\xb8\xcc:\x86\x9a\xba\x84\u007f=\x1fd\xfcc\x97\xaae\xfbS\xa8\xf0z\x0f\x89:\xae0\xe8\xbc\xee\x89|\xf28\x1fa\x9f\x15\x00\x00\u07d4\xf3@\x83\xec\xea8P\x17\xaa@\xbd\xd3^\xf7\xef\xfbL\xe7v-\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf3F\xd7\u0792t\x1c\b\xfcX\xa6M\xb5[\x06-\xde\x01-\x14\x89\x0f\xffk\x1fv\x1em\x00\x00\xe0\x94\xf3U\xd3\xec\f\xfb\x90}\x8d\xbb\x1b\xf3FNE\x81(\x19\v\xac\x8a\x01\v\x04n\u007f\r\x80\x10\x00\x00\u07d4\xf3m\xf0/\xbd\x89`sG\xaf\xce)i\xb9\xc4#jX\xa5\x06\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3s\xe9\u06ac\f\x86u\xf5;yz\x16\x0fo\xc04\xaek#\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf3{BeG\xa1d-\x8032H\x14\xf0\xed\xe3\x11O\xc2\x12\x89\x15\xbeat\xe1\x91.\x00\x00\u07d4\xf3{\xf7\x8cXu\x15G\x11\xcbd\r7\xeam(\xcf\xcb\x12Y\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xf3\x82\xdfX1U\xd8T\x8f?\x93D\f\xd5\xf6\x8c\xb7\x9d`&\x8a8u}\x02\u007f\xc1\xfd\\\x00\x00\xe0\x94\xf3\x82\xe4\xc2\x04\x10\xb9Q\b\x9e\x19\xba\x96\xa2\xfe\xe3\xd9\x1c\xce~\x8a\x01\x11\xfaV\xee\u00a88\x00\x00\xe0\x94\xf3\x8al\xa8\x01hS~\x97M\x14\xe1\xc3\xd19\x90\xa4L,\x1b\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\u07d4\xf3\x9a\x9dz\xa3X\x1d\xf0~\xe4'\x9a\xe6\xc3\x12\xef!\x036X\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf3\xb6h\xb3\xf1M\x92\x0e\xbc7\x90\x92\u06d8\x03\x1bg\xb2\x19\xb3\x89\n\xd6\xee\xdd\x17\xcf;\x80\x00\u07d4\U000fe679\x10<\xe7U\n\xa7O\xf1\xdb\x18\xe0\x9d\xfe2\xe0\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xc1\xab\u049d\xc5{A\xdc\x19-\x0e8M\x02\x1d\xf0\xb4\xf6\u0509\x97\xae\f\u07cf\x86\xf8\x00\x00\u07d4\xf3\xc4qm\x1e\xe5'\x9a\x86\xd0\x16:\x14a\x81\x81\xe1a6\u01c965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf3\u030b\xcbU\x94e\xf8\x1b\xfeX;\u05eb\n#\x06E;\x9e\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xf3\u0588\xf0k\xbd\xbfP\xf9\x93,AE\xcb\xe4\x8e\xcd\xf6\x89\x04\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf3\xdb\xcf\x13Z\u02dd\xee\x1aH\x9cY<\x02O\x03\u00bb\xae\u0389lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xde_&\xefj\xde\xd6\xf0m;\x91\x13F\xeep@\x1d\xa4\xa0\x89\x13:\xb3}\x9f\x9d\x03\x00\x00\u07d4\xf3\xdfc\xa9q\x99\x93308;>\xd7W\v\x96\u0101#4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf3\xe7OG\f}:?\x003x\x0fv\xa8\x9f>\xf6\x91\xe6\u02c9\xa3\xcf\xe61\xd1Cd\x00\x00\u07d4\xf3\xeb\x19H\xb9Q\xe2-\xf1ax)\xbf;\x8d\x86\x80\xeckh\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf3\xf1\xfa9\x18\xca4\xe2\xcf~\x84g\v\x1fM\x8e\xca\x16\r\xb3\x89$\xdc\xe5M4\xa1\xa0\x00\x00\u07d4\xf3\xf2O\u009e @?\xc0\xe8\xf5\xeb\xbbU4&\xf7\x82p\xa2\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf3\xfar5R\xa5\xd0Q.+b\xf4\x8d\xca{+\x81\x050[\x89\amA\xc6$\x94\x84\x00\x00\u07d4\xf3\xfeQ\xfd\xe3D\x13\xc73\x18\xb9\xc8T7\xfe~\x82\x0fV\x1a\x896b2\\\u044f\xe0\x00\x00\u07d4\xf4\x00\xf9=_\\~?\xc3\x03\x12\x9a\xc8\xfb\f/xd\a\xfa\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf4\v\x13O\xea\"\u01b2\x9c\x84W\xf4\x9f\x00\x0f\x9c\xdax\x9a\u06c9 \x86\xac5\x10R`\x00\x00\u07d4\xf4\x15W\xdf\u07f1\xa1\xbd\xce\xfe\xfe.\xba\x1e!\xfe\nJ\x99B\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf4\x17z\r\x85\u050b\x0e&B\x11\xce*\xa2\xef\xd3\xf1\xb4\u007f\b\x89\xc2\xcc\xca&\xb7\xe8\x0e\x80\x00\u07d4\xf4/\x90R1\xc7p\xf0\xa4\x06\xf2\xb7h\x87\u007f\xb4\x9e\xee\x0f!\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xf42\xb9\u06ef\x11\xbd\xbds\xb6Q\x9f\xc0\xa9\x04\x19\x87q\xaa\u0189\b=lz\xabc`\x00\x00\u07d4\xf4=\xa3\xa4\xe3\xf5\xfa\xb1\x04\u029b\xc1\xa0\xf7\xf3\xbbJV\xf3Q\x89lj\xccg\u05f1\xd4\x00\x00\xe0\x94\xf4G\x10\x8b\x98\xdfd\xb5~\x87\x103\x88\\\x1a\xd7\x1d\xb1\xa3\xf9\x8a\x01v\xf4\x9e\xad4\x83P\x80\x00\u07d4\xf4O\x85Q\xac\xe93r\a\x12\xc5\u0111\u0376\xf2\xf9Qsl\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u0794\xf4V\x05Z\x11\xab\x91\xfff\x8e.\xc9\"\x96\x1f*#\xe3\xdb%\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xf4V\xa7[\xb9\x96U\xa7A,\xe9}\xa0\x81\x81m\xfd\xb2\xb1\xf2\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf4[\x1d\xcb.A\xdc'\xff\xa0$\u06ad\xf6\x19\xc1\x11u\xc0\x87\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xf4c\xa9\f\xb3\xf1>\x1f\x06CB66\xbe\xab\x84\xc1#\xb0m\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf4h\x90n~\xdffJ\xb0\u063e=\x83\xebz\xb3\xf7\xff\xdcx\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xf4i\x80\u3929\u049ajn\x90`E7\xa3\x11K\xcb(\x97\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf4kk\x9c|\xb5R\x82\x9c\x1d=\xfd\x8f\xfb\x11\xaa\xba\xe7\x82\xf6\x89\x01#n\xfc\xbc\xbb4\x00\x00\u07d4\xf4v\xe1&\u007f\x86$|\xc9\b\x81o.z\xd58\x8c\x95-\xb0\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf4v\xf2\xcbr\b\xa3.\x05\x1f\xd9N\xa8f)\x92c\x82\x87\xa2\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xf4{\xb14\xda0\xa8\x12\xd0\x03\xaf\x8d\u0338\x88\xf4K\xbfW$\x8a\x01\x19Y\xb7\xfe3\x95X\x00\x00\u07d4\xf4\x83\xf6\a\xa2\x1f\xcc(\x10\n\x01\x8cV\x8f\xfb\xe1@8\x04\x10\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf4\x8e\x1f\x13\xf6\xafM\x84\xb3q\xd7\xdeK'=\x03\xa2c'\x8e\x89 \x86\xac5\x10R`\x00\x00\xe0\x94\xf4\x9cG\xb3\xef\xd8knj[\xc9A\x8d\x1f\x9f\xec\x81Ki\xef\x8a\x04<3\xc1\x93ud\x80\x00\x00\xe0\x94\xf4\x9fo\x9b\xaa\xbc\x01\x8c\x8f\x8e\x11\x9e\x01\x15\xf4\x91\xfc\x92\xa8\xa4\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf4\xa3g\xb1f\u0499\x1a+\xfd\xa9\xf5dc\xa0\x9f%,\x1b\x1d\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf4\xa5\x1f\xceJ\x1d[\x94\xb0q\x83\x89\xbaNx\x14\x13\x9c\xa78\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xf4\xa9\xd0\f\xef\xa9{zX\xef\x94\x17\xfcbg\xa5\x06\x909\xee\x89\x01.\x89(\u007f\xa7\x84\x00\x00\u07d4\xf4\xaa\xa3\xa6\x16>7\x06W{I\xc0v~\x94\x8ah\x1e\x16\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf4\xb1bn$\xf3\v\xca\xd9'!\xb2\x93r\x89U\xa6\xe7\x9c\xcd\x1d0\x00\x00\u07d4\xf5U\xa2{\xb1\xe2\xfdN,\u01c4\xca\ue493\x9f\xc0n/\u0249lk\x93[\x8b\xbd@\x00\x00\u07d4\xf5X\xa2\xb2\xdd&\u0755\x93\xaa\xe0E1\xfd<<\u00c5Kg\x89\n\xbb\xcdN\xf3wX\x00\x00\u07d4\xf5`H\xdd!\x81\u0523od\xfc\xec\xc6!T\x81\xe4*\xbc\x15\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf5dB\xf6\x0e!i\x13\x95\u043f\xfa\xa9\x19M\xca\xff\x12\u2dc9\x0e\x189\x8ev\x01\x90\x00\x00\u07d4\xf5yqJE\xeb\x8fR\xc3\xd5{\xbd\xef\xd2\xc1[./\x11\u07c9T\x91YV\xc4\t`\x00\x00\u07d4\xf5\x93\xc6R\x85\xeek\xbdf7\U000fe3c9\xad@\u0509\xf6U\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf5\x98\xdb.\t\xa8\xa5\xee}r\r+\\C\xbb\x12m\x11\xec\u0089\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xf5\x9d\xab\x1b\xf8\xdf\x112~a\xf9\xb7\xa1KV:\x96\xec5T\x8a\x01EB\xba\x12\xa37\xc0\x00\x00\xe0\x94\xf5\x9f\x9f\x02\xbb\u024e\xfe\t~\xab\xb7\x82\x10\x97\x90!\x89\x8b\xfd\x8a\x02\x1e\x17\x1a>\xc9\xf7,\x00\x00\u07d4\xf5\xa5E\x9f\xcd\xd5\xe5\xb2s\x83\r\xf8\x8e\xeaL\xb7}\xda\u07f9\x89\x04\t\xe5+H6\x9a\x00\x00\u07d4\xf5\xa7gj\xd1H\xae\x9c\x1e\xf8\xb6\xf5\xe5\xa0\xc2\xc4s\xbe\x85\v\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf5\xb0h\x98\x9d\xf2\x9c%5w\xd0@Z\xden\x0eu(\xf8\x9e\x89WG=\x05\u06ba\xe8\x00\x00\u07d4\xf5\xb6\xe9\x06\x1aN\xb0\x96\x16\aw\xe2gb\xcfH\xbd\u0635]\x89\r\xc5_\xdb\x17d{\x00\x00\u07d4\xf5\xcf\xfb\xbabN~\xb3!\xbc\x83\xc6\f\xa6\x81\x99\xb4\xe3fq\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf5\xd1ER\xb1\xdc\xe0\xd6\xdc\x1f2\r\xa6\xff\u02231\xcdo\f\x89Hz\x9a0E9D\x00\x00\xe0\x94\xf5\xd6\x1a\xc4\u0295G^[{\xff\xd5\xf2\xf6\x90\xb3\x16u\x96\x15\x8a\x06\x92\xae\x88\x97\b\x1d\x00\x00\x00\u07d4\xf5\xd9\xcf\x00\xd6X\xddEQzH\xa9\xd3\xf5\xf63T\x1aS=\x89\x06O_\xdfIOx\x00\x00\u07d4\xf5\xea\xdc\xd2\u0478ez\x12\x1f3\xc4X\xa8\xb1>v\xb6U&\x89\r\x8b\x0fZZ\xc2J\x00\x00\u07d4\xf6\a\xc2\x15\r>\x1b\x99\xf2O\xa1\xc7\xd5@\xad\xd3\\N\xbe\x1e\x89\xa7\xf1\xaa\a\xfc\x8f\xaa\x00\x00\u07d4\xf6\v\xd75T>k\xfd.\xa6\xf1\x1b\xffbs@\xbc\x03Z#\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf6\f\x1bE\xf1d\xb9X\x0e 'Z\\9\xe1\xd7\x1e5\xf8\x91\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xf6\x0fb\xd797\x95?\xef5\x16\x9e\x11\xd8r\xd2\xea1~\xec\x8a\x01!\xeah\xc1\x14\xe5\x10\x00\x00\u07d4\xf6\x12\x83\xb4\xbd\x85\x04\x05\x8c\xa3`\u94d9\x9bb\xcb\xc8\xcdg\x89\r\xd2\xd5\xfc\xf3\xbc\x9c\x00\x00\u07d4\xf6\x17\xb9g\xb9\xbdH_v\x95\xd2\xefQ\xfbw\x92\u0618\xf5\x00\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf6\x18\u0671\x04A\x14\x80\xa8c\xe6#\xfcU#-\x1aOH\xaa\x89\x0eh\x9emD\xb1f\x80\x00\u07d4\xf6\"\u5126b>\xaa\xf9\x9f+\xe4\x9eS\x80\xc5\xcb\xcf\\\u0609\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf62\xad\xffI\r\xa4\xb7-\x126\xd0KQ\x0ft\xd2\xfa\xa3\u0349K\xe4\xe7&{j\xe0\x00\x00\u07d4\xf69\xac1\u069fg'\x1b\xd1\x04\x02\xb7eN\\\xe7c\xbdG\x89\x15\xaf\x0fB\xba\xf9&\x00\x00\u07d4\xf6:W\x9b\xc3\xea\u00a9I\x04\x10\x12\x8d\xbc\xeb\xe6\xd9\u0782C\x89P\xc5\xe7a\xa4D\b\x00\x00\u07d4\xf6E\xdd|\x89\x00\x93\xe8\xe4\u022a\x92\xa6\xbb55\"\xd3\u0718\x89\aC\x9f\xa2\t\x9eX\x00\x00\xe0\x94\xf6H\xea\x89\xc2u%q\x01r\x94Ny\xed\xff\x84x\x03\xb7u\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xf6JJ\xc8\xd5@\xa9(\x9ch\xd9`\xd5\xfb|\xc4Zw\x83\x1c\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf6N\xcf!\x17\x93\x1cmSZ1\x1eO\xfe\xae\xf9\u0514\x05\xb8\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xf6O\xe0\x93\x9a\x8d\x1e\xea*\x0e\u035a\x970\xfdyX\xe31\t\x89\x01\x1d\xe1\xe6\xdbE\f\x00\x00\u07d4\xf6V\x16\xbe\x9c\x8by~t\x15\"|\x918\xfa\xa0\x89\x17B\u05c9*\xd3s\xcef\x8e\x98\x00\x00\u07d4\xf6W\xfc\xbeh.\xb4\xe8\xdb\x15.\u03c9$V\x00\vQ=\x15\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf6X\x19\xacL\xc1L\x13\u007f\x05\xddyw\xc7\xda\xe0\x8d\x1aJ\xb5\x89\x05\x87\x88\u02d4\xb1\xd8\x00\x00\u07d4\xf6{\xb8\xe2\x11\x8b\xbc\u0550'fn\xed\xf6\x94>\xc9\xf8\x80\xa5\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf6\x84d\xbfd\xf2A\x13V\xe4\xd3%\x0e\xfe\xfe\\P\xa5\xf6[\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf6\x86x[\x89r\va\x14_\ua017\x8dj\u030e\v\xc1\x96\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf6\x8c^3\xfa\x97\x13\x9d\xf5\xb2\xe68\x86\xce4\xeb\xf3\u45dc\x89\xb3\xfaAi\xe2\xd8\xe0\x00\x00\u07d4\xf6\xa8cWW\xc5\xe8\xc14\xd2\r\x02\x8c\xf7x\u03c6\t\xe4j\x89O\x1dw/\xae\xc1|\x00\x00\u07d4\xf6\xb7\x82\xf4\xdc\xd7E\xa6\xc0\xe2\xe00`\x0e\x04\xa2K%\xe5B\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\xe0\x94\xf6\xbc7\xb1\u04a3x\x8dX\x9bm\xe2\x12\xdc\x17\x13\xb2\xf6\u738a\x01\x0f\f\xf0d\xddY \x00\x00\u07d4\xf6\xc3\u010a\x1a\xc0\xa3G\x99\xf0M\xb8n\u01e9u\xfewh\xf3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf6\xd2]?=\x84m#\x9fR_\xa8\xca\xc9{\xc45x\u06ec\x890\x92\u007ft\xc9\xde\x00\x00\x00\u07d4\xf6\xea\xacp2\u0512\xef\x17\xfd`\x95\xaf\xc1\x1dcOV\xb3\x82\x89\x1b\x1bk\u05efd\xc7\x00\x00\xe0\x94\xf6\xea\xd6}\xbf[~\xb13X\xe1\x0f6\x18\x9dS\xe6C\xcf\u03ca\bxg\x83&\xea\xc9\x00\x00\x00\u07d4\xf6\xf1\xa4C\t\x05\x1ck%\xe4}\xff\x90\x9b\x17\x9b\xb9\xabY\x1c\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xf7\x03(\xef\x97b_\xe7E\xfa\xa4\x9e\xe0\xf9\u052a;\r\xfbi\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf7\n\x99\x8aq{3\x8d\x1d\u0658T@\x9b\x1a3\x8d\ue930\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7\rcz\x84\\\x06\xdbl\u0711\xe67\x1c\xe7\xc48\x8ab\x8e\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\x15R\x13D\x98\x92tK\xc6\x0f.\x04@\a\x88\xbd\x04\x1f\u0749\x03\x9f\xba\xe8\xd0B\xdd\x00\x00\xe0\x94\xf7\x1bE4\xf2\x86\xe40\x93\xb1\xe1^\xfe\xa7I\xe7Y{\x8bW\x8a\x16\x1c\x13\xd34\x1c\x87(\x00\x00\u07d4\xf74\xec\x03rM\xde\xe5\xbbRy\xaa\x1a\xfc\xf6\x1b\f\xb4H\xa1\x89\xe5\xbf,\u0270\x97\x80\x00\x00\u07d4\xf76\u0716v\x00\x128\x8f\xe8\x8bf\xc0n\xfeW\xe0\xd7\xcf\n\x89q\xd7Z\xb9\xb9 P\x00\x00\u07d4\xf7:\xc4l ;\xe1S\x81\x11\xb1Q\xec\x82 \u01c6\xd8AD\x89\x0f\xf77x\x17\xb8+\x80\x00\u07d4\xf7=\xd9\xc1B\xb7\x1b\xce\x11\xd0n0\xe7\xe7\xd02\xf2\uc71e\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf7A\x8a\xa0\xe7\x13\xd2H\"\x87v\xb2\xe7CB\"\xaeu\u3949lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7Nn\x14S\x82\xb4\u06c2\x1f\xe0\xf2\u0643\x88\xf4V\t\u019f\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xf7P\f\x16o\x8b\xea/\x824v\x06\xe5\x02K\xe9\xe4\xf4\u0399\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7W\xfc\x87 \xd3\xc4\xfaRw\a^`\xbd\\A\x1a\xeb\xd9w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7[\xb3\x9cy\x97y\xeb\xc0J3m&\r\xa61F\xed\x98\u0409\x01Z\xf1\u05cbX\xc4\x00\x00\xe0\x94\xf7h\xf3!\xfdd3\xd9kO5M<\xc1e,\x172\xf5\u007f\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf7oi\xce\xe4\xfa\xa0\xa6;0\xae\x1ex\x81\xf4\xf7\x15ep\x10\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf7w6\x1a=\u062bb\xe5\xf1\xb9\xb0GV\x8c\xc0\xb5UpL\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf7|{\x84QI\xef\xba\x19\xe2a\xbc|u\x15y\b\xaf\xa9\x90\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf7\u007f\x95\x87\xffz-r\x95\xf1\xf5q\u0206\xbd3\x92jR|\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xf7\x82X\xc1$\x81\xbc\xdd\u06f7*\x8c\xa0\xc0C\tra\xc6\u0149\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\x98\xd1m\xa4\xe4`\xc4`\xcdH_\xae\x0f\xa0Y\x97\b\ub08965\u026d\xc5\u07a0\x00\x00\u07d4\xf7\xa1\xad\xe2\xd0\xf5)\x12=\x10U\xf1\x9b\x17\x91\x9fV!Ng\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf7\xac\xff\x93K\x84\xda\ti\xdc7\xa8\xfc\xf6C\xb7\xd7\xfb\xedA\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xf7\xb1Q\xcc^W\x1c\x17\xc7e9\xdb\xe9\x96L\xbbo\xe5\xdey\x89tq|\xfbh\x83\x10\x00\x00\u07d4\xf7\xb2\x9b\x82\x19\\\x88-\xabx\x97\u00ae\x95\xe7w\x10\xf5xu\x89w5Aa2\xdb\xfc\x00\x00\u07d4\xf7\xbcLD\x91\rZ\xed\xd6n\xd25U8\xa6\xb1\x93\xc3a\xec\x89\x05A\xde,-\x8db\x00\x00\u07d4\xf7\xc0\f\xdb\x1f\x02\x03\x10\u056c\xab{Ij\xaaD\xb7y\b^\x89Z\x87\xe7\xd7\xf5\xf6X\x00\x00\u07d4\xf7\xc1\xb4C\x96\x8b\x11{]\u0677UW/\xcd9\xca^\xc0K\x89\x18\xb9h\u0092\xf1\xb5\x00\x00\xe0\x94\xf7\xc5\x0f\x92*\xd1ka\xc6\u047a\xa0E\xed\x81h\x15\xba\u010f\x8a\x02\xa99j\x97\x84\xad}\x00\x00\u07d4\xf7\xc7\b\x01Pq\xd4\xfb\n:*\t\xa4]\x15c\x96\xe34\x9e\x89\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf7\xcb\u06e6\xbel\xfeh\xdb\xc2<+\x0f\xf50\xee\x05\"o\x84\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf7\xd0\xd3\x10\xac\xea\x18@a8\xba\xaa\xbb\xfe\x05q\xe8\r\xe8_\x89Hz\x9a0E9D\x00\x00\u07d4\xf7\u05ef LV\xf3\x1f\xd9C\x98\xe4\r\xf1\x96K\u063f\x12<\x89\b!\xd2!\xb5)\x1f\x80\x00\u07d4\xf7\xdc%\x11\x96\xfb\u02f7|\x94}|\x19F\xb0\xffe\x02\x1c\xea\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf7\xe4Z\x12\xaaq\x1cp\x9a\xce\xfe\x95\xf3;xa-*\xd2*\x8a\x0e\x06U\xe2\xf2k\xc9\x18\x00\x00\u07d4\xf7\xf4\x89\x8cLRm\x95_!\xf0U\xcbnG\xb9\x15\xe5\x19d\x89|\b`\xe5\xa8\r\xc0\x00\x00\u07d4\xf7\xf9\x1ez\xcb[\x81)\xa3\x06\x87|\xe3\x16\x8eoC\x8bf\xa1\x89\t\x8a}\x9b\x83\x14\xc0\x00\x00\u07d4\xf7\xfcE\xab\xf7oP\x88\xe2\u5d68\xd12\xf2\x8aMN\xc1\xc0\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\x06:\xf4\xcc\x1d\xd9a\x9a\xb5\u063f\xf3\xfc\xd1\xfa\xa8H\x82!\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\bnBf\x1e\xa9)\xd2\u0761\xablt\x8c\xe3\x05]\x11\x1e\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xf8\bw\x86\xb4-\xa0N\xd6\xd1\xe0\xfe&\xf6\xc0\xee\xfe\x1e\x9fZ\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xf8\r6\x19p/\xa5\x83\x8cH9\x18Y\xa89\xfb\x9c\xe7\x16\x0f\x89l\a\xa7\u0471np\x00\x00\u07d4\xf8\x14y\x9fm\xdfM\xcb)\xc7\xee\x87\x0eu\xf9\xcc-52m\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xf8\x15\xc1\n\x03-\x13\xc3K\x89v\xfan;\xd2\xc9\x13\x1a\x8b\xa9\x89Hz\x9a0E9D\x00\x00\u07d4\xf8\x16\"\xe5WW\xda\xeafu\x97]\xd958\xda}\x16\x99\x1e\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8$\xee3\x1eJ\xc3\xccXv\x939[W\xec\xf6%\xa6\xc0\u0089V\xc9]\xe8\xe8\xca\x1d\x00\x00\u07d4\xf8'\xd5n\xd2\xd3' \u052b\xf1\x03\xd6\xd0\xefM;\xcdU\x9b\x89\x01l\x80\x06W\x91\xa2\x80\x00\u07d4\xf8)\x85\x91R>P\xb1\x03\xf0\xb7\x01\xd6#\xcb\xf0\xf7EV\xf6\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf8H\xfc\xe9\xaba\x1c}\x99 n#\xfa\u019a\u0508\xb9O\xe1\x89\x02\xa1\x12\x9d\t6r\x00\x00\u07d4\xf8O\t\n\xdf?\x8d\xb7\u1533P\xfb\xb7u\x00i\x9ff\xfd\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf8Q\xb0\x10\xf63\xc4\n\xf1\xa8\xf0js\ubeabe\az\xb5\x89\xee\x86D/\xcd\x06\xc0\x00\x00\u07d4\xf8X\x17\x1a\x04\xd3W\xa1;IA\xc1n~U\xdd\u0514\x13)\x89\x02F\xa5!\x8f*\x00\x00\x00\u07d4\xf8[\xab\x1c\xb3q\x0f\xc0_\xa1\x9f\xfa\xc2.gR\x1a\v\xa2\x1d\x89l\x955\u007f\xa6\xb3l\x00\x00\u07d4\xf8j>\xa8\a\x1fp\x95\xc7\u06ca\x05\xaePz\x89)\u06f8v\x89\x126\xef\xcb\u02f3@\x00\x00\u07d4\xf8pL\x16\xd2\xfd[\xa3\xa2\xc0\x1d\x0e\xb2\x04\x84\xe6\xec\xfa1\t\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf8p\x99_\xe1\xe5\"2\x1duC7\xa4\\\f\x9d{8\x95\x1c\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf8s\xe5ze\xc9;n\x18\xcbu\xf0\xdc\a}[\x893\xdc\\\x89\n\xad\xec\x98?\xcf\xf4\x00\x00\u07d4\xf8ua\x9d\x8a#\xe4]\x89\x98\u0444\u0500\xc0t\x89p\x82*\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf8{\xb0{(\x9d\xf70\x1eT\xc0\xef\xdaj,\xf2\x91\xe8\x92\x00\x89K\xe4\xe7&{j\xe0\x00\x00\u0794\xf8\x89\x00\xdbsyU\xb1Q\x9b\x1a}\x17\n\x18\x86L\xe5\x90\xeb\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xf8\x8bX\xdb7B\vFL\v\xe8\x8bE\xee+\x95)\x0f\x8c\xfa\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf8\x96+u\xdb]$\xc7\xe8\xb7\xce\xf1\x06\x8c>g\u03bb0\xa5\x89\x0f-\xc7\xd4\u007f\x15`\x00\x00\u07d4\xf8\xa0e\xf2\x87\xd9\x1dw\xcdbj\xf3\x8f\xfa\"\r\x9bU*+\x89g\x8a\x93 b\xe4\x18\x00\x00\u07d4\xf8\xa4\x9c\xa29\f\x1fm\\\x0ebQ;\a\x95qt?|\u0189\xa2\xa1]\tQ\x9b\xe0\x00\x00\u07d4\xf8\xa5\f\xee.h\x8c\xee\u3b24\u0522\x97%\xd4\a,\u0103\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xacJ9\xb5<\x110x \x97;D\x13e\xcf\xfeYof\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xae\x85{g\xa4\xa2\x89:?\xbe|z\x87\xff\x1c\x01\u01a6\xe7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xf8\xbf\x9c\x04\x87NZw\xf3\x8fL8R~\x80\xc6v\xf7\xb8\x87\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xc7\xf3J8\xb3\x18\x01\xdaC\x064w\xb1+'\xd0\xf2\x03\xff\x89\x1a\u04ba\xbao\xefH\x00\x00\u07d4\xf8\xca3l\x8e\x91\xbd \xe3\x14\xc2\v-\xd4`\x8b\x9c\x8b\x94Y\x89-\u071b\u0173,x\x00\x00\u07d4\xf8\xd1t$\xc7g\xbe\xa3\x12\x05s\x9a+W\xa7'r\x14\uef89\x02F\xdd\xf9yvh\x00\x00\u07d4\xf8\xd5-\xcc_\x96\xcc(\x00{>\u02f4\t\xf7\xe2*dl\xaa\x89\b\x16\x90\xe1\x81(H\x00\x00\u07d4\xf8\xdc\xe8g\xf0\xa3\x9c[\xef\x9e\xeb\xa6\t\"\x9e\xfa\x02g\x8bl\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf8\xf2&\x14*B\x844\xab\x17\xa1\x86J%\x97\xf6J\xab/\x06\x89\tY\x8b/\xb2\xe9\xf2\x80\x00\u07d4\xf8\xf6d^\r\xeedK=\xad\x81\xd5q\uf6ef\x84\x00!\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9\x01\xc0\x0f\xc1\u06c8\xb6\x9cK\xc3%+\\\xa7\x0e\xa6\xee\\\xf6\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf9=[\xcb\x06D\xb0\xcc\xe5\xfc\u0763C\xf5\x16\x8f\xfa\xb2\x87}\x89\vb\a\xb6}&\xf9\x00\x00\u07d4\xf9W\x0e\x92L\x95\u07bbpa6\x97\x92\xcf.\xfe\u00a8-^\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xf9d \x86\xb1\xfb\xaea\xa6\x80M\xbe_\xb1^\xc2\u04b57\xf4\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9d\x88i\x85\x90\xdc;,UVB\xb8q4\x8d\xfa\x06z\u0549\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf9d\u064d(\x170\xba5\xb2\xe3\xa3\x14yn{B\xfe\xdfg\x89S\xb0\x87`\x98\xd8\f\x00\x00\u07d4\xf9e\ri\x89\xf1\x99\xab\x1c\xc4ycm\xed0\xf2A\x02\x1fe\x89.\x14\x1e\xa0\x81\xca\b\x00\x00\xe0\x94\xf9h\x83X$Y\x90\x8c\x82v'\xe8o(\xe6F\xf9\xc7\xfcz\x8a\x01\u0127\x877\xcd\u03f8\x00\x00\u07d4\xf9kL\x00voSsj\x85t\xf8\"\xe6GL/!\xda-\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xf9r\x9dH(,\x9e\x87\x16m^\xef-\x01\xed\xa9\xdb\xf7\x88!\x89\x05k\x83\xdd\xc7(T\x80\x00\u07d4\xf9v~N\xcbJY\x80Ru\b\u05fe\xc3\xd4^Ld\x9c\x13\x89g\x8a\x93 b\xe4\x18\x00\x00\xe0\x94\xf9x\xb0%\xb6B3U\\\xc3\xc1\x9a\xda\u007fA\x99\xc94\x8b\xf7\x8aT\xb4\v\x1f\x85+\xda\x00\x00\x00\u07d4\xf9{V\xeb\u0577z\xbc\x9f\xba\u02eb\u0514\xb9\xd2\xc2!\xcd\x03\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xf9\x81\x1f\xa1\x9d\xad\xbf\x02\x9f\x8b\xfeV\x9a\xdb\x18\"\x8c\x80H\x1a\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xf9\x82Ps\fLa\xc5\u007f\x12\x985\xf2h\b\x94yEB\xf3\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xf9\x894gr\x99^\xc1\x90o\xaf\xfe\xba*\u007f\xe7\u079ck\xab\x8a\x01je\x02\xf1Z\x1eT\x00\x00\u07d4\xf9\x98\xca4\x11s\nl\xd1\x0etU\xb0A\x0f\xb0\xf6\xd3\xff\x80\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xf9\x9a\xeeDKW\x83\xc0\x93\xcf\xff\xd1\xc4c,\xf9\x90\x9f\xbb\x91\x1d/\x81\x92\xf8B\t\x89\x90\xf54`\x8ar\x88\x00\x00\u07d4\xf9\xbf\xb5\x9dS\x8a\xfcHt\xd4\xf5\x94\x1b\b\xc9s\x0e8\xe2K\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\u07d4\xf9\xdd#\x90\b\x18/\xb5\x19\xfb0\xee\xdd \x93\xfe\xd1c\x9b\xe8\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xf9\u07ba\xec\xb5\xf39\xbe\xeaH\x94\xe5 K\xfa4\r\x06\u007f%\x89ZB\x84Fs\xb1d\x00\x00\xe0\x94\xf9\xe3tG@lA!\x97\xb2\u2bbc\x00\x1dn0\u024c`\x8a\x01\xc4y\xbbCI\xc0\xee\x00\x00\u07d4\xf9\xe7\"/\xaa\xf0\xf4\xda@\xc1\u0124\x0607:\t\xbe\u05f6\x89\x9bO\u0730\x94V$\x00\x00\u07d4\xf9\xec\xe0\"\xbc\xcd,\x924i\x11\xe7\x9d\xd5\x03\x03\xc0\x1e\x01\x88\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfa\x00\xc3v\xe8\x9c\x05\u81c1z\x9d\xd0t\x8d\x96\xf3A\xaa\x89\x89\x10M\r\x00\u04b7\xf6\x00\x00\u07d4\xfa\f\x1a\x98\x8c\x8a\x17\xad5(\xeb(\xb3@\x9d\xaaX\"_&\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xfa\x10_\x1a\x11\xb6\xe4\xb1\xf5`\x12\xa2y\"\xe2\xac-\xa4\x81/\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xfa\x14/\xe4~\u0697\xe6P;8k\x18\xa2\xbe\xdds\u0335\xb1\x89.\x15:\xd8\x15H\x10\x00\x00\u07d4\xfa\x14\xb5f#J\xbe\xe70B\xc3\x1d!qq\x82\u02e1J\xa1\x89\x11\xc7\xea\x16.x \x00\x00\u07d4\xfa\x19\xd6\xf7\xa5\x0fO\a\x98\x93\xd1g\xbf\x14\xe2\x1d\x00s\u0456\x89\x1c\xbb:?\xf0\x8d\b\x00\x00\u07d4\xfa\x1f\x19q\xa7u\xc3PO\xefPy\xf6@\xc2\u013c\xe7\xac\x05\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfa'\x9b\xfd\x87g\xf9V\xbf\u007f\xa0\xbdV`\x16\x8d\xa7V\x86\xbd\x89\x90\xf54`\x8ar\x88\x00\x00\xe0\x94\xfa'\xccI\xd0\vl\x98s6\xa8u\xae9\xdaX\xfb\x04\x1b.\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xfa(2\x99`=\x87X\xe8\u02b0\x82\x12],\x8f}DT)\x8a\x01[\xca\xcb\x1e\x05\x01\xae\x80\x00\u07d4\xfa+\xbc\xa1]?\u37ca2\x8e\x91\xf9\r\xa1Oz\xc6%=\x89\n\u05ce\xbcZ\xc6 \x00\x00\xe0\x94\xfa/\u049d\x03\xfe\xe9\xa0x\x93\xdf:&\x9fV\xb7/.\x1ed\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xfa3U2\x85\xa9sq\x9a\r_\x95o\xf8a\xb2\u061e\xd3\x04\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfa:\fK\x90?n\xa5.\xa7\xab{\x88c\xb6\xa6\x16\xadfP\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfa:\x1a\xa4H\x8b5\x1a\xa7V\f\xf5\xeec\n/\xd4\\2\"\x89/\xa4~j\xa74\r\x00\x00\u07d4\xfaA\tq\xad\"\x9c06\xf4\x1a\u03c5/*\u0259(\x19P\x89\u0633\x11\xa8\xdd\xfa|\x00\x00\u07d4\xfaD\xa8U\xe4\x04\xc8m\f\xa8\xef3$%\x1d\xfb4\x9cS\x9e\x89T\"S\xa1&\xce@\x00\x00\xe0\x94\xfaR\x01\xfe\x13B\xaf\x110{\x91B\xa0A$<\xa9./\t\x8a 8\x11j:\xc0C\x98\x00\x00\xe0\x94\xfa`\x86\x8a\xaf\xd4\xffL\\W\x91K\x8e\u054bBWs\u07e9\x8a\x01\xcf\xe5\xc8\b\xf3\x9f\xbc\x00\x00\u07d4\xfag\xb6{O7\xa0\x15\t\x15\x11\x0e\xde\a;\x05\xb8S\xbd\xa2\x89#\x19\xba\x94sq\xad\x00\x00\u07d4\xfah\xe0\xcb>\xdfQ\xf0\xa6\xf2\x11\u0272\xcb^\a<\x9b\xff\xe6\x89\x0f\xc969(\x01\xc0\x00\x00\xe0\x94\xfaj7\xf0\x18\xe9yg\x93\u007f\xc5\xe8a{\xa1\u05c6\xdd_w\x8a\x04<0\xfb\b\x84\xa9l\x00\x00\u07d4\xfav\x06C[5l\xee%{\xd2\xfc\xd3\xd9\xea\xcb<\xd1\xc4\xe1\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xfaz\xdff\v\x8d\x99\xce\x15\x93=|_\a/<\xbe\xb9\x9d3\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\xfa\x86\xca'\xbf(T\u0648p\x83\u007f\xb6\xf6\xdf\xe4\xbfdS\xfc\x89\x11u~\x85%\xcf\x14\x80\x00\u07d4\xfa\x8c\xf4\xe6'i\x8c]W\x88\xab\xb7\x88\x04\x17\xe7P#\x13\x99\x89\xe6\x1a6\x96\xee\xf6\x10\x00\x00\u07d4\xfa\x8e;\x1f\x13C9\x00s}\xaa\xf1\xf6)\x9cH\x87\xf8[_\x89&\u009eG\u0104L\x00\x00\u07d4\xfa\x9e\xc8\xef\xe0\x86\x86\xfaX\xc1\x813Xr\xbai\x85`\ucac9lj\xccg\u05f1\xd4\x00\x00\u07d4\xfa\xad\x90]\x84|{#A\x8a\xee\xcb\xe3\xad\u06cd\xd3\xf8\x92J\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xfa\xae\xba\x8f\xc0\xbb\xdaU<\xa7.0\xef=s.&\xe8 A\x89H\x8d(*\xaf\xc9\xf6\x80\x00\u07d4\xfa\xb4\x87P\r\xf2\x0f\xb8>\xbe\xd9\x16y\x1dV\x17r\xad\xbe\xbf\x89lkLM\xa6\u077e\x00\x00\u07d4\xfa\xc5\u0294u\x80x\xfb\xfc\xcd\x19\xdb5X\xda~\u8827h\x897(\xa6+\r\xcf\xf6\x00\x00\u07d4\xfa\xd9j\xb6\xacv\x8a\xd5\t\x94R\xacGw\xbd\x1aG\xed\u010f\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xfa\xe7g\x19\xd9~\xacA\x87\x04(\xe9@'\x9d\x97\xddW\xb2\xf6\x8a\x14\u06f2\x19\\\xa2(\x90\x00\x00\u07d4\xfa\u8053pG\x89Zf\f\xf2)v\x0f'\xe6h(\xd6C\x89\t\xdd\xc1\xe3\xb9\x01\x18\x00\x00\u07d4\xfa\xe9,\x13p\xe9\u115a]\xf8;V\xd0\xf5\x86\xaa;@L\x89\x05\u0174\xf3\xd8C\x98\x00\x00\xe0\x94\xfa\xf5\xf0\xb7\xb6\xd5X\xf5\t\r\x9e\xa1\xfb-B%\x9cX`x\x8a\x01Z\xff\xb8B\fkd\x00\x00\xe0\x94\xfb\x12o\x0e\xc7i\xf4\x9d\xce\xfc\xa2\xf2\x00(dQX0\x84\xb8\x8a\x01\x0f\xcb\xc25\x03\x96\xbf\x00\x00\xe0\x94\xfb\x13^\xb1Z\x8b\xacr\xb6\x99\x154*`\xbb\xc0k~\a|\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xfb\"<\x1e\"\xea\xc1&\x9b2\xee\x15jS\x85\x92.\xd3o\xb8\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb7\xcfkO\x81\xa9\xe2\"\xfb\xa2.\x9b\xd2KP\x98\xb73\u03c9\x02\x1auJm\xc5(\x00\x00\u07d4\xfb8`\xf4\x12\x1cC.\xbd\xc8\xecj\x031\xb1\xb7\ty.\x90\x89 \x8c9J\xf1\u0208\x00\x00\u07d4\xfb9\x18\x9a\xf8v\xe7b\xc7\x1dl>t\x18\x93\xdf\"l\xed\u0589\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfb:\v\rkjq\x8fo\xc0)*\x82]\xc9$z\x90\xa5\u0409\n\xd6\xdd\x19\x9e\x97[\x00\x00\xe0\x94\xfb?\xa1\xac\b\xab\xa9\xcc;\xf0\xfe\x9dH8 h\x8fe\xb4\x10\x8a\x06ZM\xa2]0\x16\xc0\x00\x00\u07d4\xfb?\xe0\x9b\xb86\x86\x15)\xd7Q\x8d\xa2v5\xf58PV\x15\x89K\xe3\x92\x16\xfd\xa0p\x00\x00\xe0\x94\xfbQ%\xbf\x0f^\xb0\xb6\xf0 \xe5k\xfc/\xdf=@,\t~\x8a\x01@a\xb9\xd7z^\x98\x00\x00\u07d4\xfbU\x18qL\xef\xc3m\x04\x86]\xe5\x91^\xf0\xffG\xdf\xe7C\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb_\xfa\xa0\xf7aW&5x\x91GX\x18\x93\x9d 7\u03d6\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfbh\\\x15\xe49\x96^\xf6&\xbf\r\x83L\u0468\x9f+V\x95\x89\u0556{\xe4\xfc?\x10\x00\x00\u07d4\xfbtK\x95\x1d\tK1\x02b\xc8\xf9\x86\xc8`\u07da\xb1\xdee\x89\x02\xd1\xc5\x15\xf1\xcbJ\x80\x00\u07d4\xfby\xab\u06d2\\U\xb9\xf9\x8e\xfe\xefd\xcf\xc9\xeba\xf5\x1b\xb1\x89a@\xc0V\xfb\n\xc8\x00\x00\u07d4\xfb\x81\x13\xf9M\x91s\xee\xfdZ0s\xf5\x16\x80:\x10\xb2\x86\xae\x89\x04V9\x18$O@\x00\x00\u07d4\xfb\x84,\xa2\xc5\xef\x139\x17\xa26\xa0\u052c@i\x01\x10\xb08\x89\x10\x96\x9ab\xbe\x15\x88\x00\x00\u07d4\xfb\x91\xfb\x1aiUS\xf0\u018e!'m\xec\xf0\xb89\t\xb8m\x89\x05l\x006\x17\xafx\x00\x00\u07d4\xfb\x94s\xcfw\x125\n\x1f\xa09Rs\xfc\x80V\aR\xe4\xfb\x89\x06\xaf!\x98\xba\x85\xaa\x00\x00\xe0\x94\xfb\x94\x9cd\u007f\xdc\xfd%\x14\xc7\u054e1\xf2\x8aS-\x8cX3\x8a\x04<3\xc1\x93ud\x80\x00\x00\u07d4\xfb\xa5HmS\xc6\xe2@IBA\xab\xf8~C\xc7`\rA:\x89k\xbfaIIH4\x00\x00\u07d4\xfb\xb1a\xfe\x87_\t)\nK&+\xc6\x01\x10\x84\x8f\r\"&\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfb\xbb\xeb\u03fe#^W\xdd#\x06\xad\x1a\x9e\u0141\xc7\xf9\xf4\x8f\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xfb\xc0\x1d\xb5NG\xcd\xc3\xc48iJ\xb7\x17\xa8V\xc2?\xe6\xe9\x8a\x01\xcaqP\xab\x17OG\x00\x00\xe0\x94\xfb\xcf\xccJ{\x0f&\xcf&\xe9\xf33!2\xe2\xfcj#\af\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfb\xe7\x16\"\xbc\xbd1\xc1\xa3iv\xe7\xe5\xf6p\xc0\u007f\xfe\x16\u0789\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfb\xed\xe3,4\x9f3\x00\xefL\xd3;M\xe7\xdc\x18\xe4C\xd3&\x89\xabM\xcf9\x9a:`\x00\x00\u07d4\xfb\xf2\x04\xc8\x13\xf86\xd89b\u01c7\fx\b\xca4\u007f\xd3>\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xfb\xf7Y3\xe0\x1bu\xb1T\xef\x06i\ak\xe8\u007fb\xdf\xfa\xe1\x8a\x10\x84cr\xf2I\xd4\xc0\x00\x00\u07d4\xfc\x00\x96\xb2\x1e\x95\xac\xb8\xd6\x19\xd1v\xa4\xa1\xd8\xd5)\xba\xdb\xef\x89\x14\xd9i;\xcb\xec\x02\x80\x00\xe0\x94\xfc\x00\xa4 \xa3a\a\xdf\xd5\xf4\x95\x12\x8a_\u5af2\xdb\x0f4\x8a\x01C\x17\x9d\x86\x91\x10 \x00\x00\xe0\x94\xfc\x01\x8ai\n\xd6tm\xbe:\u03d7\x12\xdd\xcaR\xb6%\x009\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\u07d4\xfc\x02s@3\xe5\u007fpQ~\n\xfc~\xe6$a\xf0o\xad\x8e\x89\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xfc\x0e\xe6\xf7\u00b3qJ\xe9\x91lEVf\x05\xb6V\xf3$A\x89_h\xe8\x13\x1e\u03c0\x00\x00\u07d4\xfc\x10\xb7\xa6{2h\xd53\x1b\xfbj\x14\xde\xf5\xeaJ\x16,\xa3\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfc\x15\u02d9\xa8\xd1\x03\v\x12w\n\xdd\x03:y\xee\r\f\x90\x8c\x89\x12\xfa\x00\xbdR\xe6$\x00\x00\u07d4\xfc)R\xb4\u011f\xed\u043c\x05(\xa3\bI^mj\x1cq\u0589lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xfc,\x1f\x88\x96\x1d\x01\x9c>\x9e\xa30\t\x15.\x06\x93\xfb\xf8\x8a\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\xe0\x94\xfc6\x11\x05\u0750\xf9\xed\xe5fI\x9di\xe9\x13\x03\x95\xf1*\u020aS\xa4\xfe/ N\x80\xe0\x00\x00\u07d4\xfc7/\xf6\x92|\xb3\x96\xd9\xcf)\x805\x00\x11\r\xa62\xbcR\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc9\xbeA\tK\x19\x97\xd2\x16\x9e\x82d\xc2\u00fa\xa6\u025b\u0109lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc=\"k\xb3jX\xf5&V\x88W\xb0\xbb\x12\xd1\t\xec\x93\x01\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfcC\x82\x9a\u01c7\xff\x88\xaa\xf1\x83\xba5*\xad\xbfZ\x15\xb1\x93\x89\u05ac\n+\x05R\xe0\x00\x00\u07d4\xfcI\xc1C\x9aA\u05b3\xcf&\xbbg\xe06R$\xe5\xe3\x8f_\x8966\u05ef^\u024e\x00\x00\u07d4\xfcU\x00\x82Q\x05\xcfq*1\x8a^\x9c;\xfci\u021d\f\x12\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\xe0\x94\xfcf\xfa\xba'\u007fK]\xe6J\xd4^\xb1\x9c1\xe0\f\xed>\u054a\x011\xbe\xb9%\xff\xd3 \x00\x00\xe0\x94\xfc~\"\xa5\x03\xecZ\xbe\x9b\b\xc5\v\xd1I\x99\xf5 \xfaH\x84\x8a\x01ZG}\xfb\xe1\xea\x14\x80\x00\u07d4\xfc\x82\x15\xa0\xa6\x99\x13\xf6*C\xbf\x1c\x85\x90\xb9\xdd\xcd\r\x8d\u06c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xfc\x98\x9c\xb4\x87\xbf\x1a}\x17\xe4\xc1\xb7\u0137\xaa\xfd\xdak\n\x8d\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\xe0\x94\xfc\x9b4td\xb2\xf9\x92\x9d\x80~\x03\x9d\xaeH\xd3\u064d\xe3y\x8a\x02\xf6\xf1\a\x80\xd2,\xc0\x00\x00\u07d4\xfc\xa4;\xbc#\xa0\xd3!\xba\x9eF\xb9)s\\\xe7\xd8\xef\f\x18\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfc\xa7>\xff\x87q\xc0\x10;\xa3\xcc\x1a\x9c%\x94H\xc7*\xbf\v\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfc\xad\xa3\x00(?k\xcc\x13J\x91Eg`\xb0\xd7}\xe4\x10\xe0\x89lk\x93[\x8b\xbd@\x00\x00\xe0\x94\xfc\xbc\\q\xac\xe7\x97AE\v\x01,\xf6\xb8\xd3\xf1}\xb6\x8ap\x8a\x02\x05\xb4\u07e1\xeetx\x00\x00\u07d4\xfc\xbd\x85\xfe\xeajuO\xcf4ID\x9e7\xff\x97\x84\xf7w<\x89\xa7J\xdai\xab\xd7x\x00\x00\xe0\x94\xfc\xc9\u0524&.z\x02z\xb7Q\x91\x10\xd8\x02\u0115\xce\xea9\x8a\x01YQ\x82\"K&H\x00\x00\xe0\x94\xfc\xcd\r\x1e\xce\xe2z\xdd\xea\x95\xf6\x85z\xee\xc8\u01e0K(\xee\x8a\x02\x1e\x19\xe0\u027a\xb2@\x00\x00\xe0\x94\xfc\u0434\x82|\xd2\b\xff\xbf^u\x9d\xba\x8c<\xc6\x1d\x8c,<\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfc\xe0\x89c\\\xe9z\xba\xc0kD\x81\x9b\xe5\xbb\n>.\v7\x89\x05\x03\x92\nv0\xa7\x80\x00\u07d4\xfc\xf1\x99\xf8\xb8T\"/\x18.N\x1d\t\x9dN2>*\xae\x01\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfc\xfc:P\x04\xd6xa?\v6\xa6B&\x9a\u007f7\x1c?j\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfd\x19\x1a5\x15}x\x13s\xfbA\x1b\xf9\xf2R\x90\x04|^\xef\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfd\x1f\xaa4{\x0f\u0300L-\xa8l6\xd5\xf1\u044bp\x87\xbb\x89\x02\xd6\xeb$z\x96\xf6\x00\x00\u07d4\xfd\x1f\xb5\xa8\x9a\x89\xa7!\xb8yph\xfb\xc4\u007f>\x9dR\xe1I\x89\f\u0435\x83\u007f\xc6X\x00\x00\u07d4\xfd OOJ\xba%%\xbar\x8a\xfd\xf7\x87\x92\xcb\u07b75\xae\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd'W\xcc5Q\xa0\x95\x87\x8d\x97\x87V\x15\xfe\fj2\xaa\x8a\x89 m\xb1R\x99\xbe\xac\x00\x00\u07d4\xfd(r\u045eW\x85<\xfa\x16\xef\xfe\x93\u0431\xd4{O\x93\xfb\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfd))'\x1e\x9d \x95\xa2dv~{\r\xf5.\xa0\xd1\xd4\x00\x89\xa2\xa1\xeb%\x1bZ\xe4\x00\x00\u07d4\xfd7z8Rr\x90\f\xb46\xa3\xbbyb\xcd\xff\xe9?]\xad\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd@$+\xb3Jp\x85^\xf0\xfd\x90\xf3\x80-\xec!6\xb3'\x89h\xa8u\a>)$\x00\x00\xe0\x94\xfdE,9i\xec\xe3\x80\x1cT \xf1\xcd\u02a1\xc7\x1e\xd2=\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\u07d4\xfdKU\x1fo\xdb\u0366\xc5\x11\xb5\xbb7\"P\xa6\xb7\x83\xe54\x89\x01\x1d\xe1\xe6\xdbE\f\x00\x00\u07d4\xfdK\x98\x95X\xae\x11\xbe\f;6\xe2\xd6\xf2\xa5J\x93C\xca.\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfdM\xe8\xe3t\x8a(\x9c\xf7\xd0`Q}\x9d88\x8d\xb0\x1f\xb8\x89\r\x8drkqw\xa8\x00\x00\u07d4\xfdZc\x15\u007f\x91O\u04d8\uac5c\x13}\xd9U\v\xb7q\\\x89\x05k\xc7^-c\x10\x00\x00\u07d4\xfd`\u04b5\xaf=5\xf7\xaa\xf0\u00d3\x05.y\xc4\xd8#\u0645\x89\x03\x0e\xb5\r.\x14\b\x00\x00\u07d4\xfdhm\xe5?\xa9\u007f\x99c\x9e%hT\x97 \xbcX\x8c\x9e\xfc\x89j\xc5\xc6-\x94\x86\a\x00\x00\u07d4\xfd~\u078fR@\xa0eA\xebi\x9dx,/\x9a\xfb!p\xf6\x89Hz\x9a0E9D\x00\x00\u07d4\xfd\x81+\u019f\xb1p\xefW\xe22~\x80\xaf\xfd\x14\xf8\xe4\xb6\u0489lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\x88\xd1\x14\"\x0f\b\x1c\xb3\xd5\xe1[\xe8\x15*\xb0sfWj\x89\x10CV\x1a\x88)0\x00\x00\u07d4\xfd\x91\x856\xa8\xef\xa6\xf6\xce\xfe\x1f\xa1\x159\x95\xfe\xf5\xe3=;\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xfd\x92\x0fr&\x82\xaf\xb5\xafE\x1b\x05D\xd4\xf4\x1b;\x9dWB\x89~R\x05j\x12?<\x00\x00\u07d4\xfd\x95y\xf1\x19\xbb\xc8\x19\xa0+a\u3348\x03\xc9B\xf2M2\x89\x05\xb9~\x90\x81\xd9@\x00\x00\u07d4\xfd\xa0\xce\x153\a\a\xf1\v\xce2\x01\x17- \x18\xb9\xdd\xeat\x89\x02\xd0A\xd7\x05\xa2\xc6\x00\x00\xe0\x94\xfd\xa3\x04(\x19\xaf>f)\x00\xe1\xb9+CX\xed\xa6\xe9%\x90\x8a\x19\a\xa2\x84\u054fc\xe0\x00\x00\u07d4\xfd\xa6\x81\x0e\xa5\xac\x98]o\xfb\xf1\xc5\x11\xf1\xc1B\xed\xcf\xdd\xf7\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfd\xb39D\xf26\x06\x15\xe5\xbe#\x95w\u0221\x9b\xa5-\x98\x87\x89 \x9d\x92/RY\xc5\x00\x00\u07d4\xfd\xbaSY\xf7\xec;\xc7p\xacI\x97]\x84N\xc9qbV\xf1\x8965\u026d\xc5\u07a0\x00\x00\xe0\x94\xfd\xc4\xd4vZ\x94/[\xf9i1\xa9\xe8\xccz\xb8\xb7W\xffL\x8a\x12lG\x8a\x0e>\xa8`\x00\x00\xe0\x94\xfd\xcd]\x80\xb1\x05\x89zW\xab\xc4xev\x8b)\x00RB\x95\x8a\x01Z\xf1\u05cbX\xc4\x00\x00\x00\u0794\xfd\xd1\x19_y}O5q}\x15\xe6\xf9\x81\n\x9a?\xf5T`\x88\xfc\x93c\x92\x80\x1c\x00\x00\u07d4\xfd\xd5\x02\xa7N\x81;\u03e3U\xce\xda<\x17ojhq\xaf\u007f\x89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfd\u357c\vm\\\xbbL\x1d\x8f\xea>\vK\xffc^\x9d\xb7\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\xea\xac*\xcf\x1d\x13\x8e\x19\xf2\xfc?\x9f\xb7E\x92\xe3\ud04a\x89$=M\x18\"\x9c\xa2\x00\x00\u07d4\xfd\xec\xc8-\xdf\xc5a\x92\xe2oV<=h\xcbTJ\x96\xbf\xed\x89\x17\xda:\x04\u01f3\xe0\x00\x00\u07d4\xfd\xf4#C\x01\x9b\v\fk\xf2`\xb1s\xaf\xab~E\xb9\xd6!\x89lj\xccg\u05f1\xd4\x00\x00\u07d4\xfd\xf4I\xf1\b\xc6\xfbOZ+\b\x1e\xed~E\u645eM%\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfd\xfda4\xc0J\x8a\xb7\xeb\x16\xf0\x06C\xf8\xfe\xd7\u06aa\ucc89\x15\xaf\x1dx\xb5\x8c@\x00\x00\u07d4\xfe\x00\xbfC\x99\x11\xa5S\x98-\xb68\x03\x92E\xbc\xf02\xdb\u0709\x15[\xd90\u007f\x9f\xe8\x00\x00\u07d4\xfe\x01n\xc1~\xc5\xf1\x0e;\xb9\x8f\xf4\xa1\xed\xa0E\x15v\x82\xab\x89\x14_T\x02\xe7\xb2\xe6\x00\x00\u07d4\xfe\x0e0\xe2\x14)\rt=\xd3\x0e\xb0\x82\xf1\xf0\xa5\"Z\xdea\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfe!\v\x8f\x04\xdcmOv!j\xcf\xcb\u055b\xa8;\xe9\xb60\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfe\"\xa0\xb3\x88f\x8d\x1a\xe2d>w\x1d\xac\xf3\x8aCB#\u0309\xd8\xdb^\xbd{&8\x00\x00\u07d4\xfe6&\x88\x84_\xa2D\u0300~K\x110\xeb7A\xa8\x05\x1e\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfe8'\xd5v0\u03c7a\xd5\x12y{\v\x85\x8eG\x8b\xbd\x12\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfeA\x8bB\x1a\x9cm76\x02y\x04u\xd20>\x11\xa7Y0\x897\b\xba\xed=h\x90\x00\x00\u07d4\xfeBI\x12yP\xe2\xf8\x96\xec\x0e~.=\x05Z\xab\x10U\x0f\x89$=M\x18\"\x9c\xa2\x00\x00\xe0\x94\xfeM\x84\x03!o\xd5qW+\xf1\xbd\xb0\x1d\x00W\x89x\u0588\x8a\x02\x15\xf85\xbcv\x9d\xa8\x00\x00\u07d4\xfeS\xb9I\x89\u0619d\xda aS\x95&\xbb\xe9y\xdd.\xa9\x89h\xa8u\a>)$\x00\x00\u07d4\xfeT\x9b\xbf\xe6G@\x18\x98\x92\x93%8\u06afF\u04b6\x1dO\x89\x02+\x1c\x8c\x12'\xa0\x00\x00\xe0\x94\xfea]\x97\\\b\x87\xe0\xc9\x11>\xc7)\x84 \xa7\x93\xaf\x8b\x96\x8a\x01\xb1\xaeMn.\xf5\x00\x00\x00\u07d4\xfee\xc4\x18\x8dy\"Wi\td D\xfd\xc5#\x95V\x01e\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfei\u007f\xf2,\xa5G\xbf\xc9^3\xd9`\xda`\\gc\xf3[\x89G\xd4\x11\x9f\xd9`\x94\x00\x00\u07d4\xfej\x89[y\\\xb4\xbf\x85\x90=<\xe0\x9cZ\xa49S\u04ff\x89\xb8Pz\x82\a( \x00\x00\u07d4\xfeo_B\xb6\x19;\x1a\xd1b\x06\u4bf5#\x9dM}\xb4^\x89]\u0212\xaa\x111\xc8\x00\x00\u07d4\xfep\x11\xb6\x98\xbf3q\x13-tE\xb1\x9e\xb5\xb0\x945j\xee\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x80\xe9#-\xea\xff\x19\xba\xf9\x98i\x88:K\xdf\x00\x04\xe5<\x89.b\xf2\ni\xbe@\x00\x00\u07d4\xfe\x8en6eW\r\xffz\x1b\xdaiz\xa5\x89\xc0\xb4\xe9\x02J\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x8f\x1f\u072b\u007f\xbe\u0266\xa3\xfc\xc5\aa\x96\x00P\\6\xa3\x89\x01\x11du\x9f\xfb2\x00\x00\u07d4\xfe\x91\xec\xcf+\xd5f\xaf\xa1\x16\x96\xc5\x04\x9f\xa8Lic\nR\x89i*\xe8\x89p\x81\xd0\x00\x00\u07d4\xfe\x96\xc4\xcd8\x15b@\x1a\xa3*\x86\xe6[\x9dR\xfa\x8a\xee'\x89\x8f\x1d\\\x1c\xae7@\x00\x00\u07d4\xfe\x98\xc6d\xc3\xe4G\xa9^i\xbdX!q\xb7\x17n\xa2\xa6\x85\x89\xd8\xd7&\xb7\x17z\x80\x00\x00\u07d4\xfe\x9a\xd1.\xf0]m\x90&\x1f\x96\xc84\n\x03\x81\x97M\xf4w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\x9c\x0f\xff\xef\xb8\x03\b\x12V\xc0\xcfMfY\xe6\xd3>\xb4\xfb\x89R\xd5B\x80O\x1c\xe0\x00\x00\u07d4\xfe\x9c\xfc;\xb2\x93\u0772\x85\xe6%\xf3X/t\xa6\xb0\xa5\xa6\u0349j\xcb=\xf2~\x1f\x88\x00\x00\xe0\x94\xfe\x9e\x11\x97\u05d7JvH\xdc\u01e01\x12\xa8\x8e\xdb\xc9\x04]\x8a\x01\n\xfc\x1a\xde;N\xd4\x00\x00\xe0\x94\xfe\xac\xa2\xactbK\xf3H\xda\u0258QC\xcf\xd6R\xa4\xbeU\x8a\x05\x89\u007f\u02f0)\x14\b\x80\x00\u07d4\xfe\xad\x18\x03\xe5\xe77\xa6\x8e\x18G-\x9a\xc7\x15\xf0\x99L\u00be\x89\x1b\x1a\xe4\xd6\xe2\xefP\x00\x00\u07d4\xfe\xb8\xb8\xe2\xafqj\xe4\x1f\xc7\xc0K\xcf)T\x01VF\x1ek\x89TQt\xa5(\xa7z\x00\x00\u07d4\xfe\xb9-0\xbf\x01\xff\x9a\x19\x01flUsS+\xfa\a\xee\xec\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xfe\xbc1s\xbc\x90r\x13cT\x00+{O\xb3\xbf\xc5?\"\xf1\x89\x14\x0e\xc8\x0f\xa7\xee\x88\x00\x00\u07d4\xfe\xbdH\xd0\xff\xdb\xd5el\xd5\xe6\x866:a\x14R(\xf2y\x89\x97\xc9\xceL\xf6\xd5\xc0\x00\x00\u07d4\xfe\xbd\x9f\x81\xcfx\xbd_\xb6\u0139\xa2K\xd4\x14\xbb\x9b\xfaLN\x89k\xe1\x0f\xb8\xedn\x13\x80\x00\u07d4\xfe\xc0o\xe2{D\u01c4\xb29n\xc9/{\x92:\xd1~\x90w\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xfe\xc1NT\x85\xde+>\xef^t\xc4aF\u06ceEN\x035\x89\t\xb4\x1f\xbf\x9e\n\xec\x00\x00\u07d4\xfe\xd8Gm\x10\u0544\xb3\x8b\xfag7`\x0e\xf1\x9d5\xc4\x1e\u0609b\xa9\x92\xe5:\n\xf0\x00\x00\u07d4\xfe\xef;n\xab\xc9J\xff\xd31\f\x1cM\x0ee7^\x13\x11\x19\x89\x01\x15\x8eF\t\x13\xd0\x00\x00\u07d4\xfe\xf0\x9dp$?9\xed\x8c\xd8\x00\xbf\x96QG\x9e\x8fJ\xca<\x89\n\u05ce\xbcZ\xc6 \x00\x00\u07d4\xfe\xf3\xb3\u07ad\x1ai&\u051a\xa3+\x12\xc2*\xf5M\x9f\xf9\x85\x8965\u026d\xc5\u07a0\x00\x00\u07d4\xff\v|\xb7\x1d\xa9\xd4\xc1\xean\xcc(\xeb\xdaPLc\xf8/\u04498\x8a\x88]\xf2\xfcl\x00\x00\u07d4\xff\f\xc6\u73c9lk\x93[\x8b\xbd@\x00\x00\u07d4\xff'&)AH\xb8lx\xa97$\x97\xe4Y\x89\x8e\xd3\xfe\xe3\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xff=\xedz@\u04ef\xf0\u05e8\xc4_\xa6\x13j\xa0C=\xb4W\x89lh\xcc\u041b\x02,\x00\x00\u07d4\xff>\xeeW\xc3Mm\xae\x97\r\x8b1\x11\x17\xc55\x86\xcd5\x02\x89\\(=A\x03\x94\x10\x00\x00\u07d4\xff>\xf6\xba\x15\x1c!\xb5\x99\x86\xaed\xf6\xe8\"\x8b\u0262\xc73\x89lk\x93[\x8b\xbd@\x00\x00\u07d4\xffA\xd9\xe1\xb4\xef\xfe\x18\u0630\xd1\xf6?\xc4%_\xb4\xe0l=\x89Hz\x9a0E9D\x00\x00\u07d4\xffE\xcb4\xc9(6M\x9c\xc9\u063b\x0074ta\x8f\x06\xf3\x89\x05k\xc7^-c\x10\x00\x00\xe0\x94\xffI\xa7u\x81N\xc0\x00Q\xa7\x95\xa8u\xde$Y.\xa4\x00\u050a*Z\x05\x8f\u0095\xed\x00\x00\x00\u07d4\xffJ@\x8fP\xe9\xe7!F\xa2\x8c\xe4\xfc\x8d\x90'\x1f\x11n\x84\x89j\xcb=\xf2~\x1f\x88\x00\x00\u07d4\xffM\x9c\x84\x84\xc4\x10T\x89H\xa4\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" +const ropstenAllocData = "\xf9\x03\xa4\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x80\xc2\v\x80\xc2\f\x80\xc2\r\x80\xc2\x0e\x80\xc2\x0f\x80\xc2\x10\x80\xc2\x11\x80\xc2\x12\x80\xc2\x13\x80\xc2\x14\x80\xc2\x15\x80\xc2\x16\x80\xc2\x17\x80\xc2\x18\x80\xc2\x19\x80\xc2\x1a\x80\xc2\x1b\x80\xc2\x1c\x80\xc2\x1d\x80\xc2\x1e\x80\xc2\x1f\x80\xc2 \x80\xc2!\x80\xc2\"\x80\xc2#\x80\xc2$\x80\xc2%\x80\xc2&\x80\xc2'\x80\xc2(\x80\xc2)\x80\xc2*\x80\xc2+\x80\xc2,\x80\xc2-\x80\xc2.\x80\xc2/\x80\xc20\x80\xc21\x80\xc22\x80\xc23\x80\xc24\x80\xc25\x80\xc26\x80\xc27\x80\xc28\x80\xc29\x80\xc2:\x80\xc2;\x80\xc2<\x80\xc2=\x80\xc2>\x80\xc2?\x80\xc2@\x80\xc2A\x80\xc2B\x80\xc2C\x80\xc2D\x80\xc2E\x80\xc2F\x80\xc2G\x80\xc2H\x80\xc2I\x80\xc2J\x80\xc2K\x80\xc2L\x80\xc2M\x80\xc2N\x80\xc2O\x80\xc2P\x80\xc2Q\x80\xc2R\x80\xc2S\x80\xc2T\x80\xc2U\x80\xc2V\x80\xc2W\x80\xc2X\x80\xc2Y\x80\xc2Z\x80\xc2[\x80\xc2\\\x80\xc2]\x80\xc2^\x80\xc2_\x80\xc2`\x80\xc2a\x80\xc2b\x80\xc2c\x80\xc2d\x80\xc2e\x80\xc2f\x80\xc2g\x80\xc2h\x80\xc2i\x80\xc2j\x80\xc2k\x80\xc2l\x80\xc2m\x80\xc2n\x80\xc2o\x80\xc2p\x80\xc2q\x80\xc2r\x80\xc2s\x80\xc2t\x80\xc2u\x80\xc2v\x80\xc2w\x80\xc2x\x80\xc2y\x80\xc2z\x80\xc2{\x80\xc2|\x80\xc2}\x80\xc2~\x80\xc2\u007f\x80\u00c1\x80\x80\u00c1\x81\x80\u00c1\x82\x80\u00c1\x83\x80\u00c1\x84\x80\u00c1\x85\x80\u00c1\x86\x80\u00c1\x87\x80\u00c1\x88\x80\u00c1\x89\x80\u00c1\x8a\x80\u00c1\x8b\x80\u00c1\x8c\x80\u00c1\x8d\x80\u00c1\x8e\x80\u00c1\x8f\x80\u00c1\x90\x80\u00c1\x91\x80\u00c1\x92\x80\u00c1\x93\x80\u00c1\x94\x80\u00c1\x95\x80\u00c1\x96\x80\u00c1\x97\x80\u00c1\x98\x80\u00c1\x99\x80\u00c1\x9a\x80\u00c1\x9b\x80\u00c1\x9c\x80\u00c1\x9d\x80\u00c1\x9e\x80\u00c1\x9f\x80\u00c1\xa0\x80\u00c1\xa1\x80\u00c1\xa2\x80\u00c1\xa3\x80\u00c1\xa4\x80\u00c1\xa5\x80\u00c1\xa6\x80\u00c1\xa7\x80\u00c1\xa8\x80\u00c1\xa9\x80\u00c1\xaa\x80\u00c1\xab\x80\u00c1\xac\x80\u00c1\xad\x80\u00c1\xae\x80\u00c1\xaf\x80\u00c1\xb0\x80\u00c1\xb1\x80\u00c1\xb2\x80\u00c1\xb3\x80\u00c1\xb4\x80\u00c1\xb5\x80\u00c1\xb6\x80\u00c1\xb7\x80\u00c1\xb8\x80\u00c1\xb9\x80\u00c1\xba\x80\u00c1\xbb\x80\u00c1\xbc\x80\u00c1\xbd\x80\u00c1\xbe\x80\u00c1\xbf\x80\u00c1\xc0\x80\u00c1\xc1\x80\u00c1\u0080\u00c1\u00c0\u00c1\u0100\u00c1\u0140\u00c1\u0180\u00c1\u01c0\u00c1\u0200\u00c1\u0240\u00c1\u0280\u00c1\u02c0\u00c1\u0300\u00c1\u0340\u00c1\u0380\u00c1\u03c0\u00c1\u0400\u00c1\u0440\u00c1\u0480\u00c1\u04c0\u00c1\u0500\u00c1\u0540\u00c1\u0580\u00c1\u05c0\u00c1\u0600\u00c1\u0640\u00c1\u0680\u00c1\u06c0\u00c1\u0700\u00c1\u0740\u00c1\u0780\u00c1\u07c0\u00c1\xe0\x80\u00c1\xe1\x80\u00c1\xe2\x80\u00c1\xe3\x80\u00c1\xe4\x80\u00c1\xe5\x80\u00c1\xe6\x80\u00c1\xe7\x80\u00c1\xe8\x80\u00c1\xe9\x80\u00c1\xea\x80\u00c1\xeb\x80\u00c1\xec\x80\u00c1\xed\x80\u00c1\xee\x80\u00c1\xef\x80\u00c1\xf0\x80\u00c1\xf1\x80\u00c1\xf2\x80\u00c1\xf3\x80\u00c1\xf4\x80\u00c1\xf5\x80\u00c1\xf6\x80\u00c1\xf7\x80\u00c1\xf8\x80\u00c1\xf9\x80\u00c1\xfa\x80\u00c1\xfb\x80\u00c1\xfc\x80\u00c1\xfd\x80\u00c1\xfe\x80\u00c1\xff\x80\u3507KT\xa8\xbd\x15)f\xd6?pk\xae\x1f\xfe\xb0A\x19!\xe5\x8d\f\x9f,\x9c\xd0Ft\xed\xea@\x00\x00\x00" const rinkebyAllocData = "\xf9\x03\xb7\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xf6\x941\xb9\x8d\x14\x00{\xde\xe67)\x80\x86\x98\x8a\v\xbd1\x18E#\xa0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" const goerliAllocData = "\xf9\x04\x06\u0080\x01\xc2\x01\x01\xc2\x02\x01\xc2\x03\x01\xc2\x04\x01\xc2\x05\x01\xc2\x06\x01\xc2\a\x01\xc2\b\x01\xc2\t\x01\xc2\n\x01\xc2\v\x01\xc2\f\x01\xc2\r\x01\xc2\x0e\x01\xc2\x0f\x01\xc2\x10\x01\xc2\x11\x01\xc2\x12\x01\xc2\x13\x01\xc2\x14\x01\xc2\x15\x01\xc2\x16\x01\xc2\x17\x01\xc2\x18\x01\xc2\x19\x01\xc2\x1a\x01\xc2\x1b\x01\xc2\x1c\x01\xc2\x1d\x01\xc2\x1e\x01\xc2\x1f\x01\xc2 \x01\xc2!\x01\xc2\"\x01\xc2#\x01\xc2$\x01\xc2%\x01\xc2&\x01\xc2'\x01\xc2(\x01\xc2)\x01\xc2*\x01\xc2+\x01\xc2,\x01\xc2-\x01\xc2.\x01\xc2/\x01\xc20\x01\xc21\x01\xc22\x01\xc23\x01\xc24\x01\xc25\x01\xc26\x01\xc27\x01\xc28\x01\xc29\x01\xc2:\x01\xc2;\x01\xc2<\x01\xc2=\x01\xc2>\x01\xc2?\x01\xc2@\x01\xc2A\x01\xc2B\x01\xc2C\x01\xc2D\x01\xc2E\x01\xc2F\x01\xc2G\x01\xc2H\x01\xc2I\x01\xc2J\x01\xc2K\x01\xc2L\x01\xc2M\x01\xc2N\x01\xc2O\x01\xc2P\x01\xc2Q\x01\xc2R\x01\xc2S\x01\xc2T\x01\xc2U\x01\xc2V\x01\xc2W\x01\xc2X\x01\xc2Y\x01\xc2Z\x01\xc2[\x01\xc2\\\x01\xc2]\x01\xc2^\x01\xc2_\x01\xc2`\x01\xc2a\x01\xc2b\x01\xc2c\x01\xc2d\x01\xc2e\x01\xc2f\x01\xc2g\x01\xc2h\x01\xc2i\x01\xc2j\x01\xc2k\x01\xc2l\x01\xc2m\x01\xc2n\x01\xc2o\x01\xc2p\x01\xc2q\x01\xc2r\x01\xc2s\x01\xc2t\x01\xc2u\x01\xc2v\x01\xc2w\x01\xc2x\x01\xc2y\x01\xc2z\x01\xc2{\x01\xc2|\x01\xc2}\x01\xc2~\x01\xc2\u007f\x01\u00c1\x80\x01\u00c1\x81\x01\u00c1\x82\x01\u00c1\x83\x01\u00c1\x84\x01\u00c1\x85\x01\u00c1\x86\x01\u00c1\x87\x01\u00c1\x88\x01\u00c1\x89\x01\u00c1\x8a\x01\u00c1\x8b\x01\u00c1\x8c\x01\u00c1\x8d\x01\u00c1\x8e\x01\u00c1\x8f\x01\u00c1\x90\x01\u00c1\x91\x01\u00c1\x92\x01\u00c1\x93\x01\u00c1\x94\x01\u00c1\x95\x01\u00c1\x96\x01\u00c1\x97\x01\u00c1\x98\x01\u00c1\x99\x01\u00c1\x9a\x01\u00c1\x9b\x01\u00c1\x9c\x01\u00c1\x9d\x01\u00c1\x9e\x01\u00c1\x9f\x01\u00c1\xa0\x01\u00c1\xa1\x01\u00c1\xa2\x01\u00c1\xa3\x01\u00c1\xa4\x01\u00c1\xa5\x01\u00c1\xa6\x01\u00c1\xa7\x01\u00c1\xa8\x01\u00c1\xa9\x01\u00c1\xaa\x01\u00c1\xab\x01\u00c1\xac\x01\u00c1\xad\x01\u00c1\xae\x01\u00c1\xaf\x01\u00c1\xb0\x01\u00c1\xb1\x01\u00c1\xb2\x01\u00c1\xb3\x01\u00c1\xb4\x01\u00c1\xb5\x01\u00c1\xb6\x01\u00c1\xb7\x01\u00c1\xb8\x01\u00c1\xb9\x01\u00c1\xba\x01\u00c1\xbb\x01\u00c1\xbc\x01\u00c1\xbd\x01\u00c1\xbe\x01\u00c1\xbf\x01\u00c1\xc0\x01\u00c1\xc1\x01\u00c1\xc2\x01\u00c1\xc3\x01\u00c1\xc4\x01\u00c1\xc5\x01\u00c1\xc6\x01\u00c1\xc7\x01\u00c1\xc8\x01\u00c1\xc9\x01\u00c1\xca\x01\u00c1\xcb\x01\u00c1\xcc\x01\u00c1\xcd\x01\u00c1\xce\x01\u00c1\xcf\x01\u00c1\xd0\x01\u00c1\xd1\x01\u00c1\xd2\x01\u00c1\xd3\x01\u00c1\xd4\x01\u00c1\xd5\x01\u00c1\xd6\x01\u00c1\xd7\x01\u00c1\xd8\x01\u00c1\xd9\x01\u00c1\xda\x01\u00c1\xdb\x01\u00c1\xdc\x01\u00c1\xdd\x01\u00c1\xde\x01\u00c1\xdf\x01\u00c1\xe0\x01\u00c1\xe1\x01\u00c1\xe2\x01\u00c1\xe3\x01\u00c1\xe4\x01\u00c1\xe5\x01\u00c1\xe6\x01\u00c1\xe7\x01\u00c1\xe8\x01\u00c1\xe9\x01\u00c1\xea\x01\u00c1\xeb\x01\u00c1\xec\x01\u00c1\xed\x01\u00c1\xee\x01\u00c1\xef\x01\u00c1\xf0\x01\u00c1\xf1\x01\u00c1\xf2\x01\u00c1\xf3\x01\u00c1\xf4\x01\u00c1\xf5\x01\u00c1\xf6\x01\u00c1\xf7\x01\u00c1\xf8\x01\u00c1\xf9\x01\u00c1\xfa\x01\u00c1\xfb\x01\u00c1\xfc\x01\u00c1\xfd\x01\u00c1\xfe\x01\u00c1\xff\x01\xe0\x94L*\xe4\x82Y5\x05\xf0\x16<\xde\xfc\a>\x81\xc6<\xdaA\a\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe0\x94\xa8\xe8\xf1G2e\x8eKQ\xe8q\x191\x05:\x8ai\xba\xf2\xb1\x8a\x15-\x02\xc7\xe1J\xf6\x80\x00\x00\xe1\x94\u0665\x17\x9f\t\x1d\x85\x05\x1d<\x98'\x85\xef\xd1E\\\uc199\x8b\bE\x95\x16\x14\x01HJ\x00\x00\x00\xe1\x94\u08bdBX\xd2v\x887\xba\xa2j(\xfeq\xdc\a\x9f\x84\u01cbJG\xe3\xc1$H\xf4\xad\x00\x00\x00" diff --git a/core/genesis_test.go b/core/genesis_test.go index c6bcd0aa54..06a1698682 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -35,9 +35,9 @@ func TestDefaultGenesisBlock(t *testing.T) { if block.Hash() != params.MainnetGenesisHash { t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash) } - block = DefaultTestnetGenesisBlock().ToBlock(nil) - if block.Hash() != params.TestnetGenesisHash { - t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash(), params.TestnetGenesisHash) + block = DefaultRopstenGenesisBlock().ToBlock(nil) + if block.Hash() != params.RopstenGenesisHash { + t.Errorf("wrong ropsten genesis hash, got %v, want %v", block.Hash(), params.RopstenGenesisHash) } } @@ -95,14 +95,14 @@ func TestSetupGenesis(t *testing.T) { wantConfig: customg.Config, }, { - name: "custom block in DB, genesis == testnet", + name: "custom block in DB, genesis == ropsten", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { customg.MustCommit(db) - return SetupGenesisBlock(db, DefaultTestnetGenesisBlock()) + return SetupGenesisBlock(db, DefaultRopstenGenesisBlock()) }, - wantErr: &GenesisMismatchError{Stored: customghash, New: params.TestnetGenesisHash}, - wantHash: params.TestnetGenesisHash, - wantConfig: params.TestnetChainConfig, + wantErr: &GenesisMismatchError{Stored: customghash, New: params.RopstenGenesisHash}, + wantHash: params.RopstenGenesisHash, + wantConfig: params.RopstenChainConfig, }, { name: "compatible config in DB", diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 988a15c488..94d5b30aa5 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -133,7 +133,7 @@ func main() { // makeGenesis creates a custom Ethash genesis block based on some pre-defined // faucet accounts. func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { - genesis := core.DefaultTestnetGenesisBlock() + genesis := core.DefaultRopstenGenesisBlock() genesis.Difficulty = params.MinimumDifficulty genesis.GasLimit = 25000000 diff --git a/mobile/geth.go b/mobile/geth.go index edcbfdbdbe..27f1c4ed97 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -146,13 +146,27 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { if err := json.Unmarshal([]byte(config.EthereumGenesis), genesis); err != nil { return nil, fmt.Errorf("invalid genesis spec: %v", err) } - // If we have the testnet, hard code the chain configs too - if config.EthereumGenesis == TestnetGenesis() { - genesis.Config = params.TestnetChainConfig + // If we have the Ropsten testnet, hard code the chain configs too + if config.EthereumGenesis == RopstenGenesis() { + genesis.Config = params.RopstenChainConfig if config.EthereumNetworkID == 1 { config.EthereumNetworkID = 3 } } + // If we have the Rinkeby testnet, hard code the chain configs too + if config.EthereumGenesis == RinkebyGenesis() { + genesis.Config = params.RinkebyChainConfig + if config.EthereumNetworkID == 1 { + config.EthereumNetworkID = 4 + } + } + // If we have the Goerli testnet, hard code the chain configs too + if config.EthereumGenesis == GoerliGenesis() { + genesis.Config = params.GoerliChainConfig + if config.EthereumNetworkID == 1 { + config.EthereumNetworkID = 5 + } + } } // Register the Ethereum protocol if requested if config.EthereumEnabled { diff --git a/mobile/params.go b/mobile/params.go index 45fe870ee3..25d3b8565b 100644 --- a/mobile/params.go +++ b/mobile/params.go @@ -32,9 +32,9 @@ func MainnetGenesis() string { return "" } -// TestnetGenesis returns the JSON spec to use for the Ethereum test network. -func TestnetGenesis() string { - enc, err := json.Marshal(core.DefaultTestnetGenesisBlock()) +// RopstenGenesis returns the JSON spec to use for the Ropsten test network. +func RopstenGenesis() string { + enc, err := json.Marshal(core.DefaultRopstenGenesisBlock()) if err != nil { panic(err) } @@ -50,6 +50,15 @@ func RinkebyGenesis() string { return string(enc) } +// GoerliGenesis returns the JSON spec to use for the Goerli test network +func GoerliGenesis() string { + enc, err := json.Marshal(core.DefaultGoerliGenesisBlock()) + if err != nil { + panic(err) + } + return string(enc) +} + // FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated // by the foundation running the V5 discovery protocol. func FoundationBootnodes() *Enodes { diff --git a/params/bootnodes.go b/params/bootnodes.go index f27e5b7d18..5b4c962530 100644 --- a/params/bootnodes.go +++ b/params/bootnodes.go @@ -32,9 +32,9 @@ var MainnetBootnodes = []string{ "enode://5d6d7cd20d6da4bb83a1d28cadb5d409b64edf314c0335df658c1a54e32c7c4a7ab7823d57c39b6a757556e68ff1df17c748b698544a55cb488b52479a92b60f@104.42.217.25:30303", // bootnode-azure-westus-001 } -// TestnetBootnodes are the enode URLs of the P2P bootstrap nodes running on the +// RopstenBootnodes are the enode URLs of the P2P bootstrap nodes running on the // Ropsten test network. -var TestnetBootnodes = []string{ +var RopstenBootnodes = []string{ "enode://30b7ab30a01c124a6cceca36863ece12c4f5fa68e3ba9b0b51407ccc002eeed3b3102d20a88f1c1d3c3154e2449317b8ef95090e77b312d5cc39354f86d5d606@52.176.7.10:30303", // US-Azure geth "enode://865a63255b3bb68023b6bffd5095118fcc13e79dcf014fe4e47e065c350c7cc72af2e53eff895f11ba1bbb6a2b33271c1116ee870f266618eadfc2e78aa7349c@52.176.100.77:30303", // US-Azure parity "enode://6332792c4a00e3e4ee0926ed89e0d27ef985424d97b6a45bf0f23e51f0dcb5e66b875777506458aea7af6f9e4ffb69f43f3778ee73c81ed9d34c51c4b16b0b0f@52.232.243.152:30303", // Parity @@ -78,7 +78,7 @@ const dnsPrefix = "enrtree://AKA3AM6LPBYEUDMVNU3BSVQJ5AD45Y7YPOHJLEF6W26QOE4VTUD // See https://github.com/ethereum/discv4-dns-lists for more information. var KnownDNSNetworks = map[common.Hash]string{ MainnetGenesisHash: dnsPrefix + "all.mainnet.ethdisco.net", - TestnetGenesisHash: dnsPrefix + "all.ropsten.ethdisco.net", + RopstenGenesisHash: dnsPrefix + "all.ropsten.ethdisco.net", RinkebyGenesisHash: dnsPrefix + "all.rinkeby.ethdisco.net", GoerliGenesisHash: dnsPrefix + "all.goerli.ethdisco.net", } diff --git a/params/config.go b/params/config.go index bcbde40b86..e6d2fc7605 100644 --- a/params/config.go +++ b/params/config.go @@ -28,7 +28,7 @@ import ( // Genesis hashes to enforce below configs on. var ( MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") + RopstenGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177") GoerliGenesisHash = common.HexToHash("0xbf7e331f7f7c1dd2e05159666b3bf8bc7a8a3a9eb1d518969eab529dd9b88c1a") ) @@ -37,7 +37,7 @@ var ( // the chain it belongs to. var TrustedCheckpoints = map[common.Hash]*TrustedCheckpoint{ MainnetGenesisHash: MainnetTrustedCheckpoint, - TestnetGenesisHash: TestnetTrustedCheckpoint, + RopstenGenesisHash: RopstenTrustedCheckpoint, RinkebyGenesisHash: RinkebyTrustedCheckpoint, GoerliGenesisHash: GoerliTrustedCheckpoint, } @@ -46,7 +46,7 @@ var TrustedCheckpoints = map[common.Hash]*TrustedCheckpoint{ // the chain it belongs to. var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{ MainnetGenesisHash: MainnetCheckpointOracle, - TestnetGenesisHash: TestnetCheckpointOracle, + RopstenGenesisHash: RopstenCheckpointOracle, RinkebyGenesisHash: RinkebyCheckpointOracle, GoerliGenesisHash: GoerliCheckpointOracle, } @@ -91,8 +91,8 @@ var ( Threshold: 2, } - // TestnetChainConfig contains the chain parameters to run a node on the Ropsten test network. - TestnetChainConfig = &ChainConfig{ + // RopstenChainConfig contains the chain parameters to run a node on the Ropsten test network. + RopstenChainConfig = &ChainConfig{ ChainID: big.NewInt(3), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, @@ -109,16 +109,16 @@ var ( Ethash: new(EthashConfig), } - // TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. - TestnetTrustedCheckpoint = &TrustedCheckpoint{ + // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. + RopstenTrustedCheckpoint = &TrustedCheckpoint{ SectionIndex: 223, SectionHead: common.HexToHash("0x9aa51ca383f5075f816e0b8ce7125075cd562b918839ee286c03770722147661"), CHTRoot: common.HexToHash("0x755c6a5931b7bd36e55e47f3f1e81fa79c930ae15c55682d3a85931eedaf8cf2"), BloomRoot: common.HexToHash("0xabc37762d11b29dc7dde11b89846e2308ba681eeb015b6a202ef5e242bc107e8"), } - // TestnetCheckpointOracle contains a set of configs for the Ropsten test network oracle. - TestnetCheckpointOracle = &CheckpointOracleConfig{ + // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. + RopstenCheckpointOracle = &CheckpointOracleConfig{ Address: common.HexToAddress("0xEF79475013f154E6A65b54cB2742867791bf0B84"), Signers: []common.Address{ common.HexToAddress("0x32162F3581E88a5f62e8A61892B42C46E2c18f7b"), // Peter diff --git a/tests/difficulty_test.go b/tests/difficulty_test.go index 4bdad0ea5d..e80cd248bc 100644 --- a/tests/difficulty_test.go +++ b/tests/difficulty_test.go @@ -55,8 +55,8 @@ func TestDifficulty(t *testing.T) { dt.skipLoad("difficultyMorden\\.json") dt.skipLoad("difficultyOlimpic\\.json") - dt.config("Ropsten", *params.TestnetChainConfig) - dt.config("Morden", *params.TestnetChainConfig) + dt.config("Ropsten", *params.RopstenChainConfig) + dt.config("Morden", *params.RopstenChainConfig) dt.config("Frontier", params.ChainConfig{}) dt.config("Homestead", params.ChainConfig{ @@ -67,7 +67,7 @@ func TestDifficulty(t *testing.T) { ByzantiumBlock: big.NewInt(0), }) - dt.config("Frontier", *params.TestnetChainConfig) + dt.config("Frontier", *params.RopstenChainConfig) dt.config("MainNetwork", mainnetChainConfig) dt.config("CustomMainNetwork", mainnetChainConfig) dt.config("Constantinople", params.ChainConfig{ From 0851646e480f3fff0d6cdd900fc1034960b993f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felf=C3=B6ldi=20Zsolt?= Date: Thu, 9 Apr 2020 11:55:32 +0200 Subject: [PATCH 096/103] les, les/lespay/client: add service value statistics and API (#20837) This PR adds service value measurement statistics to the light client. It also adds a private API that makes these statistics accessible. A follow-up PR will add the new server pool which uses these statistics to select servers with good performance. This document describes the function of the new components: https://gist.github.com/zsfelfoldi/3c7ace895234b7b345ab4f71dab102d4 Co-authored-by: rjl493456442 Co-authored-by: rjl493456442 --- internal/web3ext/web3ext.go | 32 ++ les/benchmark.go | 2 +- les/client.go | 50 ++- les/client_handler.go | 8 + les/lespay/client/api.go | 107 +++++ les/lespay/client/requestbasket.go | 285 +++++++++++++ les/lespay/client/requestbasket_test.go | 161 ++++++++ les/lespay/client/timestats.go | 237 +++++++++++ les/lespay/client/timestats_test.go | 137 +++++++ les/lespay/client/valuetracker.go | 515 ++++++++++++++++++++++++ les/lespay/client/valuetracker_test.go | 135 +++++++ les/peer.go | 122 +++++- les/protocol.go | 63 ++- les/txrelay.go | 2 +- les/utils/expiredvalue.go | 202 ++++++++++ les/utils/expiredvalue_test.go | 116 ++++++ p2p/enode/node.go | 6 +- 17 files changed, 2142 insertions(+), 38 deletions(-) create mode 100644 les/lespay/client/api.go create mode 100644 les/lespay/client/requestbasket.go create mode 100644 les/lespay/client/requestbasket_test.go create mode 100644 les/lespay/client/timestats.go create mode 100644 les/lespay/client/timestats_test.go create mode 100644 les/lespay/client/valuetracker.go create mode 100644 les/lespay/client/valuetracker_test.go create mode 100644 les/utils/expiredvalue.go create mode 100644 les/utils/expiredvalue_test.go diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 71f0a2ed5d..4d85f0597d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -33,6 +33,7 @@ var Modules = map[string]string{ "swarmfs": SwarmfsJs, "txpool": TxpoolJs, "les": LESJs, + "lespay": LESPayJs, } const ChequebookJs = ` @@ -856,3 +857,34 @@ web3._extend({ ] }); ` + +const LESPayJs = ` +web3._extend({ + property: 'lespay', + methods: + [ + new web3._extend.Method({ + name: 'distribution', + call: 'lespay_distribution', + params: 2 + }), + new web3._extend.Method({ + name: 'timeout', + call: 'lespay_timeout', + params: 2 + }), + new web3._extend.Method({ + name: 'value', + call: 'lespay_value', + params: 2 + }), + ], + properties: + [ + new web3._extend.Property({ + name: 'requestStats', + getter: 'lespay_requestStats' + }), + ] +}); +` diff --git a/les/benchmark.go b/les/benchmark.go index dbb10a5c2a..a146de2fed 100644 --- a/les/benchmark.go +++ b/les/benchmark.go @@ -191,7 +191,7 @@ func (b *benchmarkTxSend) init(h *serverHandler, count int) error { func (b *benchmarkTxSend) request(peer *serverPeer, index int) error { enc, _ := rlp.EncodeToBytes(types.Transactions{b.txs[index]}) - return peer.sendTxs(0, enc) + return peer.sendTxs(0, 1, enc) } // benchmarkTxStatus implements requestBenchmark diff --git a/les/client.go b/les/client.go index dfd0909778..94ac4ce847 100644 --- a/les/client.go +++ b/les/client.go @@ -19,6 +19,7 @@ package les import ( "fmt" + "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -37,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/les/checkpointoracle" + lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" @@ -49,15 +51,16 @@ import ( type LightEthereum struct { lesCommons - peers *serverPeerSet - reqDist *requestDistributor - retriever *retrieveManager - odr *LesOdr - relay *lesTxRelay - handler *clientHandler - txPool *light.TxPool - blockchain *light.LightChain - serverPool *serverPool + peers *serverPeerSet + reqDist *requestDistributor + retriever *retrieveManager + odr *LesOdr + relay *lesTxRelay + handler *clientHandler + txPool *light.TxPool + blockchain *light.LightChain + serverPool *serverPool + valueTracker *lpc.ValueTracker bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests bloomIndexer *core.ChainIndexer // Bloom indexer operating during block imports @@ -74,6 +77,10 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { if err != nil { return nil, err } + lespayDb, err := ctx.OpenDatabase("lespay", 0, 0, "eth/db/lespay") + if err != nil { + return nil, err + } chainConfig, genesisHash, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.OverrideIstanbul, config.OverrideMuirGlacier) if _, isCompat := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !isCompat { @@ -99,7 +106,9 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { bloomRequests: make(chan chan *bloombits.Retrieval), bloomIndexer: eth.NewBloomIndexer(chainDb, params.BloomBitsBlocksClient, params.HelperTrieConfirmations), serverPool: newServerPool(chainDb, config.UltraLightServers), + valueTracker: lpc.NewValueTracker(lespayDb, &mclock.System{}, requestList, time.Minute, 1/float64(time.Hour), 1/float64(time.Hour*100), 1/float64(time.Hour*1000)), } + peers.subscribe((*vtSubscription)(leth.valueTracker)) leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool) leth.relay = newLesTxRelay(peers, leth.retriever) @@ -154,6 +163,23 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) { return leth, nil } +// vtSubscription implements serverPeerSubscriber +type vtSubscription lpc.ValueTracker + +// registerPeer implements serverPeerSubscriber +func (v *vtSubscription) registerPeer(p *serverPeer) { + vt := (*lpc.ValueTracker)(v) + p.setValueTracker(vt, vt.Register(p.ID())) + p.updateVtParams() +} + +// unregisterPeer implements serverPeerSubscriber +func (v *vtSubscription) unregisterPeer(p *serverPeer) { + vt := (*lpc.ValueTracker)(v) + vt.Unregister(p.ID()) + p.setValueTracker(nil, nil) +} + type LightDummyAPI struct{} // Etherbase is the address that mining rewards will be send to @@ -207,6 +233,11 @@ func (s *LightEthereum) APIs() []rpc.API { Version: "1.0", Service: NewPrivateLightAPI(&s.lesCommons), Public: false, + }, { + Namespace: "lespay", + Version: "1.0", + Service: lpc.NewPrivateClientAPI(s.valueTracker), + Public: false, }, }...) } @@ -266,6 +297,7 @@ func (s *LightEthereum) Stop() error { s.engine.Close() s.eventMux.Stop() s.serverPool.stop() + s.valueTracker.Stop() s.chainDb.Close() s.wg.Wait() log.Info("Light ethereum stopped") diff --git a/les/client_handler.go b/les/client_handler.go index 9d8b989012..6367fdb6be 100644 --- a/les/client_handler.go +++ b/les/client_handler.go @@ -180,6 +180,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrRequestRejected, "") } p.updateFlowControl(update) + p.updateVtParams() if req.Hash != (common.Hash{}) { if p.announceType == announceTypeNone { @@ -205,6 +206,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) if h.fetcher.requestedID(resp.ReqID) { h.fetcher.deliverHeaders(p, resp.ReqID, resp.Headers) } else { @@ -222,6 +224,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgBlockBodies, ReqID: resp.ReqID, @@ -237,6 +240,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgCode, ReqID: resp.ReqID, @@ -252,6 +256,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgReceipts, ReqID: resp.ReqID, @@ -267,6 +272,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgProofsV2, ReqID: resp.ReqID, @@ -282,6 +288,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgHelperTrieProofs, ReqID: resp.ReqID, @@ -297,6 +304,7 @@ func (h *clientHandler) handleMsg(p *serverPeer) error { return errResp(ErrDecode, "msg %v: %v", msg, err) } p.fcServer.ReceivedReply(resp.ReqID, resp.BV) + p.answeredRequest(resp.ReqID) deliverMsg = &Msg{ MsgType: MsgTxStatus, ReqID: resp.ReqID, diff --git a/les/lespay/client/api.go b/les/lespay/client/api.go new file mode 100644 index 0000000000..5ad6ffd77e --- /dev/null +++ b/les/lespay/client/api.go @@ -0,0 +1,107 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/p2p/enode" +) + +// PrivateClientAPI implements the lespay client side API +type PrivateClientAPI struct { + vt *ValueTracker +} + +// NewPrivateClientAPI creates a PrivateClientAPI +func NewPrivateClientAPI(vt *ValueTracker) *PrivateClientAPI { + return &PrivateClientAPI{vt} +} + +// parseNodeStr converts either an enode address or a plain hex node id to enode.ID +func parseNodeStr(nodeStr string) (enode.ID, error) { + if id, err := enode.ParseID(nodeStr); err == nil { + return id, nil + } + if node, err := enode.Parse(enode.ValidSchemes, nodeStr); err == nil { + return node.ID(), nil + } else { + return enode.ID{}, err + } +} + +// RequestStats returns the current contents of the reference request basket, with +// request values meaning average per request rather than total. +func (api *PrivateClientAPI) RequestStats() []RequestStatsItem { + return api.vt.RequestStats() +} + +// Distribution returns a distribution as a series of (X, Y) chart coordinates, +// where the X axis is the response time in seconds while the Y axis is the amount of +// service value received with a response time close to the X coordinate. +// The distribution is optionally normalized to a sum of 1. +// If nodeStr == "" then the global distribution is returned, otherwise the individual +// distribution of the specified server node. +func (api *PrivateClientAPI) Distribution(nodeStr string, normalized bool) (RtDistribution, error) { + var expFactor utils.ExpirationFactor + if !normalized { + expFactor = utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) + } + if nodeStr == "" { + return api.vt.RtStats().Distribution(normalized, expFactor), nil + } + if id, err := parseNodeStr(nodeStr); err == nil { + return api.vt.GetNode(id).RtStats().Distribution(normalized, expFactor), nil + } else { + return RtDistribution{}, err + } +} + +// Timeout suggests a timeout value based on either the global distribution or the +// distribution of the specified node. The parameter is the desired rate of timeouts +// assuming a similar distribution in the future. +// Note that the actual timeout should have a sensible minimum bound so that operating +// under ideal working conditions for a long time (for example, using a local server +// with very low response times) will not make it very hard for the system to accommodate +// longer response times in the future. +func (api *PrivateClientAPI) Timeout(nodeStr string, failRate float64) (float64, error) { + if nodeStr == "" { + return float64(api.vt.RtStats().Timeout(failRate)) / float64(time.Second), nil + } + if id, err := parseNodeStr(nodeStr); err == nil { + return float64(api.vt.GetNode(id).RtStats().Timeout(failRate)) / float64(time.Second), nil + } else { + return 0, err + } +} + +// Value calculates the total service value provided either globally or by the specified +// server node, using a weight function based on the given timeout. +func (api *PrivateClientAPI) Value(nodeStr string, timeout float64) (float64, error) { + wt := TimeoutWeights(time.Duration(timeout * float64(time.Second))) + expFactor := utils.ExpFactor(api.vt.StatsExpirer().LogOffset(mclock.Now())) + if nodeStr == "" { + return api.vt.RtStats().Value(wt, expFactor), nil + } + if id, err := parseNodeStr(nodeStr); err == nil { + return api.vt.GetNode(id).RtStats().Value(wt, expFactor), nil + } else { + return 0, err + } +} diff --git a/les/lespay/client/requestbasket.go b/les/lespay/client/requestbasket.go new file mode 100644 index 0000000000..55d4b165df --- /dev/null +++ b/les/lespay/client/requestbasket.go @@ -0,0 +1,285 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "io" + + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/rlp" +) + +const basketFactor = 1000000 // reference basket amount and value scale factor + +// referenceBasket keeps track of global request usage statistics and the usual prices +// of each used request type relative to each other. The amounts in the basket are scaled +// up by basketFactor because of the exponential expiration of long-term statistical data. +// Values are scaled so that the sum of all amounts and the sum of all values are equal. +// +// reqValues represent the internal relative value estimates for each request type and are +// calculated as value / amount. The average reqValue of all used requests is 1. +// In other words: SUM(refBasket[type].amount * reqValue[type]) = SUM(refBasket[type].amount) +type referenceBasket struct { + basket requestBasket + reqValues []float64 // contents are read only, new slice is created for each update +} + +// serverBasket collects served request amount and value statistics for a single server. +// +// Values are gradually transferred to the global reference basket with a long time +// constant so that each server basket represents long term usage and price statistics. +// When the transferred part is added to the reference basket the values are scaled so +// that their sum equals the total value calculated according to the previous reqValues. +// The ratio of request values coming from the server basket represent the pricing of +// the specific server and modify the global estimates with a weight proportional to +// the amount of service provided by the server. +type serverBasket struct { + basket requestBasket + rvFactor float64 +} + +type ( + // requestBasket holds amounts and values for each request type. + // These values are exponentially expired (see utils.ExpiredValue). The power of 2 + // exponent is applicable to all values within. + requestBasket struct { + items []basketItem + exp uint64 + } + // basketItem holds amount and value for a single request type. Value is the total + // relative request value accumulated for served requests while amount is the counter + // for each request type. + // Note that these values are both scaled up by basketFactor because of the exponential + // expiration. + basketItem struct { + amount, value uint64 + } +) + +// setExp sets the power of 2 exponent of the structure, scaling base values (the amounts +// and request values) up or down if necessary. +func (b *requestBasket) setExp(exp uint64) { + if exp > b.exp { + shift := exp - b.exp + for i, item := range b.items { + item.amount >>= shift + item.value >>= shift + b.items[i] = item + } + b.exp = exp + } + if exp < b.exp { + shift := b.exp - exp + for i, item := range b.items { + item.amount <<= shift + item.value <<= shift + b.items[i] = item + } + b.exp = exp + } +} + +// init initializes a new server basket with the given service vector size (number of +// different request types) +func (s *serverBasket) init(size int) { + if s.basket.items == nil { + s.basket.items = make([]basketItem, size) + } +} + +// add adds the give type and amount of requests to the basket. Cost is calculated +// according to the server's own cost table. +func (s *serverBasket) add(reqType, reqAmount uint32, reqCost uint64, expFactor utils.ExpirationFactor) { + s.basket.setExp(expFactor.Exp) + i := &s.basket.items[reqType] + i.amount += uint64(float64(uint64(reqAmount)*basketFactor) * expFactor.Factor) + i.value += uint64(float64(reqCost) * s.rvFactor * expFactor.Factor) +} + +// updateRvFactor updates the request value factor that scales server costs into the +// local value dimensions. +func (s *serverBasket) updateRvFactor(rvFactor float64) { + s.rvFactor = rvFactor +} + +// transfer decreases amounts and values in the basket with the given ratio and +// moves the removed amounts into a new basket which is returned and can be added +// to the global reference basket. +func (s *serverBasket) transfer(ratio float64) requestBasket { + res := requestBasket{ + items: make([]basketItem, len(s.basket.items)), + exp: s.basket.exp, + } + for i, v := range s.basket.items { + ta := uint64(float64(v.amount) * ratio) + tv := uint64(float64(v.value) * ratio) + if ta > v.amount { + ta = v.amount + } + if tv > v.value { + tv = v.value + } + s.basket.items[i] = basketItem{v.amount - ta, v.value - tv} + res.items[i] = basketItem{ta, tv} + } + return res +} + +// init initializes the reference basket with the given service vector size (number of +// different request types) +func (r *referenceBasket) init(size int) { + r.reqValues = make([]float64, size) + r.normalize() + r.updateReqValues() +} + +// add adds the transferred part of a server basket to the reference basket while scaling +// value amounts so that their sum equals the total value calculated according to the +// previous reqValues. +func (r *referenceBasket) add(newBasket requestBasket) { + r.basket.setExp(newBasket.exp) + // scale newBasket to match service unit value + var ( + totalCost uint64 + totalValue float64 + ) + for i, v := range newBasket.items { + totalCost += v.value + totalValue += float64(v.amount) * r.reqValues[i] + } + if totalCost > 0 { + // add to reference with scaled values + scaleValues := totalValue / float64(totalCost) + for i, v := range newBasket.items { + r.basket.items[i].amount += v.amount + r.basket.items[i].value += uint64(float64(v.value) * scaleValues) + } + } + r.updateReqValues() +} + +// updateReqValues recalculates reqValues after adding transferred baskets. Note that +// values should be normalized first. +func (r *referenceBasket) updateReqValues() { + r.reqValues = make([]float64, len(r.reqValues)) + for i, b := range r.basket.items { + if b.amount > 0 { + r.reqValues[i] = float64(b.value) / float64(b.amount) + } else { + r.reqValues[i] = 0 + } + } +} + +// normalize ensures that the sum of values equal the sum of amounts in the basket. +func (r *referenceBasket) normalize() { + var sumAmount, sumValue uint64 + for _, b := range r.basket.items { + sumAmount += b.amount + sumValue += b.value + } + add := float64(int64(sumAmount-sumValue)) / float64(sumValue) + for i, b := range r.basket.items { + b.value += uint64(int64(float64(b.value) * add)) + r.basket.items[i] = b + } +} + +// reqValueFactor calculates the request value factor applicable to the server with +// the given announced request cost list +func (r *referenceBasket) reqValueFactor(costList []uint64) float64 { + var ( + totalCost float64 + totalValue uint64 + ) + for i, b := range r.basket.items { + totalCost += float64(costList[i]) * float64(b.amount) // use floats to avoid overflow + totalValue += b.value + } + if totalCost < 1 { + return 0 + } + return float64(totalValue) * basketFactor / totalCost +} + +// EncodeRLP implements rlp.Encoder +func (b *basketItem) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{b.amount, b.value}) +} + +// DecodeRLP implements rlp.Decoder +func (b *basketItem) DecodeRLP(s *rlp.Stream) error { + var item struct { + Amount, Value uint64 + } + if err := s.Decode(&item); err != nil { + return err + } + b.amount, b.value = item.Amount, item.Value + return nil +} + +// EncodeRLP implements rlp.Encoder +func (r *requestBasket) EncodeRLP(w io.Writer) error { + return rlp.Encode(w, []interface{}{r.items, r.exp}) +} + +// DecodeRLP implements rlp.Decoder +func (r *requestBasket) DecodeRLP(s *rlp.Stream) error { + var enc struct { + Items []basketItem + Exp uint64 + } + if err := s.Decode(&enc); err != nil { + return err + } + r.items, r.exp = enc.Items, enc.Exp + return nil +} + +// convertMapping converts a basket loaded from the database into the current format. +// If the available request types and their mapping into the service vector differ from +// the one used when saving the basket then this function reorders old fields and fills +// in previously unknown fields by scaling up amounts and values taken from the +// initialization basket. +func (r requestBasket) convertMapping(oldMapping, newMapping []string, initBasket requestBasket) requestBasket { + nameMap := make(map[string]int) + for i, name := range oldMapping { + nameMap[name] = i + } + rc := requestBasket{items: make([]basketItem, len(newMapping))} + var scale, oldScale, newScale float64 + for i, name := range newMapping { + if ii, ok := nameMap[name]; ok { + rc.items[i] = r.items[ii] + oldScale += float64(initBasket.items[i].amount) * float64(initBasket.items[i].amount) + newScale += float64(rc.items[i].amount) * float64(initBasket.items[i].amount) + } + } + if oldScale > 1e-10 { + scale = newScale / oldScale + } else { + scale = 1 + } + for i, name := range newMapping { + if _, ok := nameMap[name]; !ok { + rc.items[i].amount = uint64(float64(initBasket.items[i].amount) * scale) + rc.items[i].value = uint64(float64(initBasket.items[i].value) * scale) + } + } + return rc +} diff --git a/les/lespay/client/requestbasket_test.go b/les/lespay/client/requestbasket_test.go new file mode 100644 index 0000000000..7c5f87c618 --- /dev/null +++ b/les/lespay/client/requestbasket_test.go @@ -0,0 +1,161 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "math/rand" + "testing" + + "github.com/ethereum/go-ethereum/les/utils" +) + +func checkU64(t *testing.T, name string, value, exp uint64) { + if value != exp { + t.Errorf("Incorrect value for %s: got %d, expected %d", name, value, exp) + } +} + +func checkF64(t *testing.T, name string, value, exp, tol float64) { + if value < exp-tol || value > exp+tol { + t.Errorf("Incorrect value for %s: got %f, expected %f", name, value, exp) + } +} + +func TestServerBasket(t *testing.T) { + var s serverBasket + s.init(2) + // add some requests with different request value factors + s.updateRvFactor(1) + noexp := utils.ExpirationFactor{Factor: 1} + s.add(0, 1000, 10000, noexp) + s.add(1, 3000, 60000, noexp) + s.updateRvFactor(10) + s.add(0, 4000, 4000, noexp) + s.add(1, 2000, 4000, noexp) + s.updateRvFactor(10) + // check basket contents directly + checkU64(t, "s.basket[0].amount", s.basket.items[0].amount, 5000*basketFactor) + checkU64(t, "s.basket[0].value", s.basket.items[0].value, 50000) + checkU64(t, "s.basket[1].amount", s.basket.items[1].amount, 5000*basketFactor) + checkU64(t, "s.basket[1].value", s.basket.items[1].value, 100000) + // transfer 50% of the contents of the basket + transfer1 := s.transfer(0.5) + checkU64(t, "transfer1[0].amount", transfer1.items[0].amount, 2500*basketFactor) + checkU64(t, "transfer1[0].value", transfer1.items[0].value, 25000) + checkU64(t, "transfer1[1].amount", transfer1.items[1].amount, 2500*basketFactor) + checkU64(t, "transfer1[1].value", transfer1.items[1].value, 50000) + // add more requests + s.updateRvFactor(100) + s.add(0, 1000, 100, noexp) + // transfer 25% of the contents of the basket + transfer2 := s.transfer(0.25) + checkU64(t, "transfer2[0].amount", transfer2.items[0].amount, (2500+1000)/4*basketFactor) + checkU64(t, "transfer2[0].value", transfer2.items[0].value, (25000+10000)/4) + checkU64(t, "transfer2[1].amount", transfer2.items[1].amount, 2500/4*basketFactor) + checkU64(t, "transfer2[1].value", transfer2.items[1].value, 50000/4) +} + +func TestConvertMapping(t *testing.T) { + b := requestBasket{items: []basketItem{{3, 3}, {1, 1}, {2, 2}}} + oldMap := []string{"req3", "req1", "req2"} + newMap := []string{"req1", "req2", "req3", "req4"} + init := requestBasket{items: []basketItem{{2, 2}, {4, 4}, {6, 6}, {8, 8}}} + bc := b.convertMapping(oldMap, newMap, init) + checkU64(t, "bc[0].amount", bc.items[0].amount, 1) + checkU64(t, "bc[1].amount", bc.items[1].amount, 2) + checkU64(t, "bc[2].amount", bc.items[2].amount, 3) + checkU64(t, "bc[3].amount", bc.items[3].amount, 4) // 8 should be scaled down to 4 +} + +func TestReqValueFactor(t *testing.T) { + var ref referenceBasket + ref.basket = requestBasket{items: make([]basketItem, 4)} + for i := range ref.basket.items { + ref.basket.items[i].amount = uint64(i+1) * basketFactor + ref.basket.items[i].value = uint64(i+1) * basketFactor + } + ref.init(4) + rvf := ref.reqValueFactor([]uint64{1000, 2000, 3000, 4000}) + // expected value is (1000000+2000000+3000000+4000000) / (1*1000+2*2000+3*3000+4*4000) = 10000000/30000 = 333.333 + checkF64(t, "reqValueFactor", rvf, 333.333, 1) +} + +func TestNormalize(t *testing.T) { + for cycle := 0; cycle < 100; cycle += 1 { + // Initialize data for testing + valueRange, lower := 1000000, 1000000 + ref := referenceBasket{basket: requestBasket{items: make([]basketItem, 10)}} + for i := 0; i < 10; i++ { + ref.basket.items[i].amount = uint64(rand.Intn(valueRange) + lower) + ref.basket.items[i].value = uint64(rand.Intn(valueRange) + lower) + } + ref.normalize() + + // Check whether SUM(amount) ~= SUM(value) + var sumAmount, sumValue uint64 + for i := 0; i < 10; i++ { + sumAmount += ref.basket.items[i].amount + sumValue += ref.basket.items[i].value + } + var epsilon = 0.01 + if float64(sumAmount)*(1+epsilon) < float64(sumValue) || float64(sumAmount)*(1-epsilon) > float64(sumValue) { + t.Fatalf("Failed to normalize sumAmount: %d sumValue: %d", sumAmount, sumValue) + } + } +} + +func TestReqValueAdjustment(t *testing.T) { + var s1, s2 serverBasket + s1.init(3) + s2.init(3) + cost1 := []uint64{30000, 60000, 90000} + cost2 := []uint64{100000, 200000, 300000} + var ref referenceBasket + ref.basket = requestBasket{items: make([]basketItem, 3)} + for i := range ref.basket.items { + ref.basket.items[i].amount = 123 * basketFactor + ref.basket.items[i].value = 123 * basketFactor + } + ref.init(3) + // initial reqValues are expected to be {1, 1, 1} + checkF64(t, "reqValues[0]", ref.reqValues[0], 1, 0.01) + checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01) + checkF64(t, "reqValues[2]", ref.reqValues[2], 1, 0.01) + var logOffset utils.Fixed64 + for period := 0; period < 1000; period++ { + exp := utils.ExpFactor(logOffset) + s1.updateRvFactor(ref.reqValueFactor(cost1)) + s2.updateRvFactor(ref.reqValueFactor(cost2)) + // throw in random requests into each basket using their internal pricing + for i := 0; i < 1000; i++ { + reqType, reqAmount := uint32(rand.Intn(3)), uint32(rand.Intn(10)+1) + reqCost := uint64(reqAmount) * cost1[reqType] + s1.add(reqType, reqAmount, reqCost, exp) + reqType, reqAmount = uint32(rand.Intn(3)), uint32(rand.Intn(10)+1) + reqCost = uint64(reqAmount) * cost2[reqType] + s2.add(reqType, reqAmount, reqCost, exp) + } + ref.add(s1.transfer(0.1)) + ref.add(s2.transfer(0.1)) + ref.normalize() + ref.updateReqValues() + logOffset += utils.Float64ToFixed64(0.1) + } + checkF64(t, "reqValues[0]", ref.reqValues[0], 0.5, 0.01) + checkF64(t, "reqValues[1]", ref.reqValues[1], 1, 0.01) + checkF64(t, "reqValues[2]", ref.reqValues[2], 1.5, 0.01) +} diff --git a/les/lespay/client/timestats.go b/les/lespay/client/timestats.go new file mode 100644 index 0000000000..7f1ffdbe26 --- /dev/null +++ b/les/lespay/client/timestats.go @@ -0,0 +1,237 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "io" + "math" + "time" + + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + minResponseTime = time.Millisecond * 50 + maxResponseTime = time.Second * 10 + timeStatLength = 32 + weightScaleFactor = 1000000 +) + +// ResponseTimeStats is the response time distribution of a set of answered requests, +// weighted with request value, either served by a single server or aggregated for +// multiple servers. +// It it a fixed length (timeStatLength) distribution vector with linear interpolation. +// The X axis (the time values) are not linear, they should be transformed with +// TimeToStatScale and StatScaleToTime. +type ( + ResponseTimeStats struct { + stats [timeStatLength]uint64 + exp uint64 + } + ResponseTimeWeights [timeStatLength]float64 +) + +var timeStatsLogFactor = (timeStatLength - 1) / (math.Log(float64(maxResponseTime)/float64(minResponseTime)) + 1) + +// TimeToStatScale converts a response time to a distribution vector index. The index +// is represented by a float64 so that linear interpolation can be applied. +func TimeToStatScale(d time.Duration) float64 { + if d < 0 { + return 0 + } + r := float64(d) / float64(minResponseTime) + if r > 1 { + r = math.Log(r) + 1 + } + r *= timeStatsLogFactor + if r > timeStatLength-1 { + return timeStatLength - 1 + } + return r +} + +// StatScaleToTime converts a distribution vector index to a response time. The index +// is represented by a float64 so that linear interpolation can be applied. +func StatScaleToTime(r float64) time.Duration { + r /= timeStatsLogFactor + if r > 1 { + r = math.Exp(r - 1) + } + return time.Duration(r * float64(minResponseTime)) +} + +// TimeoutWeights calculates the weight function used for calculating service value +// based on the response time distribution of the received service. +// It is based on the request timeout value of the system. It consists of a half cosine +// function starting with 1, crossing zero at timeout and reaching -1 at 2*timeout. +// After 2*timeout the weight is constant -1. +func TimeoutWeights(timeout time.Duration) (res ResponseTimeWeights) { + for i := range res { + t := StatScaleToTime(float64(i)) + if t < 2*timeout { + res[i] = math.Cos(math.Pi / 2 * float64(t) / float64(timeout)) + } else { + res[i] = -1 + } + } + return +} + +// EncodeRLP implements rlp.Encoder +func (rt *ResponseTimeStats) EncodeRLP(w io.Writer) error { + enc := struct { + Stats [timeStatLength]uint64 + Exp uint64 + }{rt.stats, rt.exp} + return rlp.Encode(w, &enc) +} + +// DecodeRLP implements rlp.Decoder +func (rt *ResponseTimeStats) DecodeRLP(s *rlp.Stream) error { + var enc struct { + Stats [timeStatLength]uint64 + Exp uint64 + } + if err := s.Decode(&enc); err != nil { + return err + } + rt.stats, rt.exp = enc.Stats, enc.Exp + return nil +} + +// Add adds a new response time with the given weight to the distribution. +func (rt *ResponseTimeStats) Add(respTime time.Duration, weight float64, expFactor utils.ExpirationFactor) { + rt.setExp(expFactor.Exp) + weight *= expFactor.Factor * weightScaleFactor + r := TimeToStatScale(respTime) + i := int(r) + r -= float64(i) + rt.stats[i] += uint64(weight * (1 - r)) + if i < timeStatLength-1 { + rt.stats[i+1] += uint64(weight * r) + } +} + +// setExp sets the power of 2 exponent of the structure, scaling base values (the vector +// itself) up or down if necessary. +func (rt *ResponseTimeStats) setExp(exp uint64) { + if exp > rt.exp { + shift := exp - rt.exp + for i, v := range rt.stats { + rt.stats[i] = v >> shift + } + rt.exp = exp + } + if exp < rt.exp { + shift := rt.exp - exp + for i, v := range rt.stats { + rt.stats[i] = v << shift + } + rt.exp = exp + } +} + +// Value calculates the total service value based on the given distribution, using the +// specified weight function. +func (rt ResponseTimeStats) Value(weights ResponseTimeWeights, expFactor utils.ExpirationFactor) float64 { + var v float64 + for i, s := range rt.stats { + v += float64(s) * weights[i] + } + if v < 0 { + return 0 + } + return expFactor.Value(v, rt.exp) / weightScaleFactor +} + +// AddStats adds the given ResponseTimeStats to the current one. +func (rt *ResponseTimeStats) AddStats(s *ResponseTimeStats) { + rt.setExp(s.exp) + for i, v := range s.stats { + rt.stats[i] += v + } +} + +// SubStats subtracts the given ResponseTimeStats from the current one. +func (rt *ResponseTimeStats) SubStats(s *ResponseTimeStats) { + rt.setExp(s.exp) + for i, v := range s.stats { + if v < rt.stats[i] { + rt.stats[i] -= v + } else { + rt.stats[i] = 0 + } + } +} + +// Timeout suggests a timeout value based on the previous distribution. The parameter +// is the desired rate of timeouts assuming a similar distribution in the future. +// Note that the actual timeout should have a sensible minimum bound so that operating +// under ideal working conditions for a long time (for example, using a local server +// with very low response times) will not make it very hard for the system to accommodate +// longer response times in the future. +func (rt ResponseTimeStats) Timeout(failRatio float64) time.Duration { + var sum uint64 + for _, v := range rt.stats { + sum += v + } + s := uint64(float64(sum) * failRatio) + i := timeStatLength - 1 + for i > 0 && s >= rt.stats[i] { + s -= rt.stats[i] + i-- + } + r := float64(i) + 0.5 + if rt.stats[i] > 0 { + r -= float64(s) / float64(rt.stats[i]) + } + if r < 0 { + r = 0 + } + th := StatScaleToTime(r) + if th > maxResponseTime { + th = maxResponseTime + } + return th +} + +// RtDistribution represents a distribution as a series of (X, Y) chart coordinates, +// where the X axis is the response time in seconds while the Y axis is the amount of +// service value received with a response time close to the X coordinate. +type RtDistribution [timeStatLength][2]float64 + +// Distribution returns a RtDistribution, optionally normalized to a sum of 1. +func (rt ResponseTimeStats) Distribution(normalized bool, expFactor utils.ExpirationFactor) (res RtDistribution) { + var mul float64 + if normalized { + var sum uint64 + for _, v := range rt.stats { + sum += v + } + if sum > 0 { + mul = 1 / float64(sum) + } + } else { + mul = expFactor.Value(float64(1)/weightScaleFactor, rt.exp) + } + for i, v := range rt.stats { + res[i][0] = float64(StatScaleToTime(float64(i))) / float64(time.Second) + res[i][1] = float64(v) * mul + } + return +} diff --git a/les/lespay/client/timestats_test.go b/les/lespay/client/timestats_test.go new file mode 100644 index 0000000000..a28460171e --- /dev/null +++ b/les/lespay/client/timestats_test.go @@ -0,0 +1,137 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "math" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/les/utils" +) + +func TestTransition(t *testing.T) { + var epsilon = 0.01 + var cases = []time.Duration{ + time.Millisecond, minResponseTime, + time.Second, time.Second * 5, maxResponseTime, + } + for _, c := range cases { + got := StatScaleToTime(TimeToStatScale(c)) + if float64(got)*(1+epsilon) < float64(c) || float64(got)*(1-epsilon) > float64(c) { + t.Fatalf("Failed to transition back") + } + } + // If the time is too large(exceeds the max response time. + got := StatScaleToTime(TimeToStatScale(2 * maxResponseTime)) + if float64(got)*(1+epsilon) < float64(maxResponseTime) || float64(got)*(1-epsilon) > float64(maxResponseTime) { + t.Fatalf("Failed to transition back") + } +} + +var maxResponseWeights = TimeoutWeights(maxResponseTime) + +func TestValue(t *testing.T) { + noexp := utils.ExpirationFactor{Factor: 1} + for i := 0; i < 1000; i++ { + max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) + min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime))) + timeout := max/2 + time.Duration(rand.Int63n(int64(maxResponseTime-max/2))) + s := makeRangeStats(min, max, 1000, noexp) + value := s.Value(TimeoutWeights(timeout), noexp) + // calculate the average weight (the average of the given range of the half cosine + // weight function). + minx := math.Pi / 2 * float64(min) / float64(timeout) + maxx := math.Pi / 2 * float64(max) / float64(timeout) + avgWeight := (math.Sin(maxx) - math.Sin(minx)) / (maxx - minx) + expv := 1000 * avgWeight + if expv < 0 { + expv = 0 + } + if value < expv-10 || value > expv+10 { + t.Errorf("Value failed (expected %v, got %v)", expv, value) + } + } +} + +func TestAddSubExpire(t *testing.T) { + var ( + sum1, sum2 ResponseTimeStats + sum1ValueExp, sum2ValueExp float64 + logOffset utils.Fixed64 + ) + for i := 0; i < 1000; i++ { + exp := utils.ExpFactor(logOffset) + max := minResponseTime + time.Duration(rand.Int63n(int64(maxResponseTime-minResponseTime))) + min := minResponseTime + time.Duration(rand.Int63n(int64(max-minResponseTime))) + s := makeRangeStats(min, max, 1000, exp) + value := s.Value(maxResponseWeights, exp) + sum1.AddStats(&s) + sum1ValueExp += value + if rand.Intn(2) == 1 { + sum2.AddStats(&s) + sum2ValueExp += value + } + logOffset += utils.Float64ToFixed64(0.001 / math.Log(2)) + sum1ValueExp -= sum1ValueExp * 0.001 + sum2ValueExp -= sum2ValueExp * 0.001 + } + exp := utils.ExpFactor(logOffset) + sum1Value := sum1.Value(maxResponseWeights, exp) + if sum1Value < sum1ValueExp*0.99 || sum1Value > sum1ValueExp*1.01 { + t.Errorf("sum1Value failed (expected %v, got %v)", sum1ValueExp, sum1Value) + } + sum2Value := sum2.Value(maxResponseWeights, exp) + if sum2Value < sum2ValueExp*0.99 || sum2Value > sum2ValueExp*1.01 { + t.Errorf("sum2Value failed (expected %v, got %v)", sum2ValueExp, sum2Value) + } + diff := sum1 + diff.SubStats(&sum2) + diffValue := diff.Value(maxResponseWeights, exp) + diffValueExp := sum1ValueExp - sum2ValueExp + if diffValue < diffValueExp*0.99 || diffValue > diffValueExp*1.01 { + t.Errorf("diffValue failed (expected %v, got %v)", diffValueExp, diffValue) + } +} + +func TestTimeout(t *testing.T) { + testTimeoutRange(t, 0, time.Second) + testTimeoutRange(t, time.Second, time.Second*2) + testTimeoutRange(t, time.Second, maxResponseTime) +} + +func testTimeoutRange(t *testing.T, min, max time.Duration) { + s := makeRangeStats(min, max, 1000, utils.ExpirationFactor{Factor: 1}) + for i := 2; i < 9; i++ { + to := s.Timeout(float64(i) / 10) + exp := max - (max-min)*time.Duration(i)/10 + tol := (max - min) / 50 + if to < exp-tol || to > exp+tol { + t.Errorf("Timeout failed (expected %v, got %v)", exp, to) + } + } +} + +func makeRangeStats(min, max time.Duration, amount float64, exp utils.ExpirationFactor) ResponseTimeStats { + var s ResponseTimeStats + amount /= 1000 + for i := 0; i < 1000; i++ { + s.Add(min+(max-min)*time.Duration(i)/999, amount, exp) + } + return s +} diff --git a/les/lespay/client/valuetracker.go b/les/lespay/client/valuetracker.go new file mode 100644 index 0000000000..92bfd694ed --- /dev/null +++ b/les/lespay/client/valuetracker.go @@ -0,0 +1,515 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "bytes" + "fmt" + "math" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/les/utils" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/rlp" +) + +const ( + vtVersion = 1 // database encoding format for ValueTracker + nvtVersion = 1 // database encoding format for NodeValueTracker +) + +var ( + vtKey = []byte("vt:") + vtNodeKey = []byte("vtNode:") +) + +// NodeValueTracker collects service value statistics for a specific server node +type NodeValueTracker struct { + lock sync.Mutex + + rtStats, lastRtStats ResponseTimeStats + lastTransfer mclock.AbsTime + basket serverBasket + reqCosts []uint64 + reqValues *[]float64 +} + +// init initializes a NodeValueTracker. +// Note that the contents of the referenced reqValues slice will not change; a new +// reference is passed if the values are updated by ValueTracker. +func (nv *NodeValueTracker) init(now mclock.AbsTime, reqValues *[]float64) { + reqTypeCount := len(*reqValues) + nv.reqCosts = make([]uint64, reqTypeCount) + nv.lastTransfer = now + nv.reqValues = reqValues + nv.basket.init(reqTypeCount) +} + +// updateCosts updates the request cost table of the server. The request value factor +// is also updated based on the given cost table and the current reference basket. +// Note that the contents of the referenced reqValues slice will not change; a new +// reference is passed if the values are updated by ValueTracker. +func (nv *NodeValueTracker) updateCosts(reqCosts []uint64, reqValues *[]float64, rvFactor float64) { + nv.lock.Lock() + defer nv.lock.Unlock() + + nv.reqCosts = reqCosts + nv.reqValues = reqValues + nv.basket.updateRvFactor(rvFactor) +} + +// transferStats returns request basket and response time statistics that should be +// added to the global statistics. The contents of the server's own request basket are +// gradually transferred to the main reference basket and removed from the server basket +// with the specified transfer rate. +// The response time statistics are retained at both places and therefore the global +// distribution is always the sum of the individual server distributions. +func (nv *NodeValueTracker) transferStats(now mclock.AbsTime, transferRate float64) (requestBasket, ResponseTimeStats) { + nv.lock.Lock() + defer nv.lock.Unlock() + + dt := now - nv.lastTransfer + nv.lastTransfer = now + if dt < 0 { + dt = 0 + } + recentRtStats := nv.rtStats + recentRtStats.SubStats(&nv.lastRtStats) + nv.lastRtStats = nv.rtStats + return nv.basket.transfer(-math.Expm1(-transferRate * float64(dt))), recentRtStats +} + +// RtStats returns the node's own response time distribution statistics +func (nv *NodeValueTracker) RtStats() ResponseTimeStats { + nv.lock.Lock() + defer nv.lock.Unlock() + + return nv.rtStats +} + +// ValueTracker coordinates service value calculation for individual servers and updates +// global statistics +type ValueTracker struct { + clock mclock.Clock + lock sync.Mutex + quit chan chan struct{} + db ethdb.KeyValueStore + connected map[enode.ID]*NodeValueTracker + reqTypeCount int + + refBasket referenceBasket + mappings [][]string + currentMapping int + initRefBasket requestBasket + rtStats ResponseTimeStats + + transferRate float64 + statsExpLock sync.RWMutex + statsExpRate, offlineExpRate float64 + statsExpirer utils.Expirer + statsExpFactor utils.ExpirationFactor +} + +type valueTrackerEncV1 struct { + Mappings [][]string + RefBasketMapping uint + RefBasket requestBasket + RtStats ResponseTimeStats + ExpOffset, SavedAt uint64 +} + +type nodeValueTrackerEncV1 struct { + RtStats ResponseTimeStats + ServerBasketMapping uint + ServerBasket requestBasket +} + +// RequestInfo is an initializer structure for the service vector. +type RequestInfo struct { + // Name identifies the request type and is used for re-mapping the service vector if necessary + Name string + // InitAmount and InitValue are used to initialize the reference basket + InitAmount, InitValue float64 +} + +// NewValueTracker creates a new ValueTracker and loads its previously saved state from +// the database if possible. +func NewValueTracker(db ethdb.KeyValueStore, clock mclock.Clock, reqInfo []RequestInfo, updatePeriod time.Duration, transferRate, statsExpRate, offlineExpRate float64) *ValueTracker { + now := clock.Now() + + initRefBasket := requestBasket{items: make([]basketItem, len(reqInfo))} + mapping := make([]string, len(reqInfo)) + + var sumAmount, sumValue float64 + for _, req := range reqInfo { + sumAmount += req.InitAmount + sumValue += req.InitAmount * req.InitValue + } + scaleValues := sumAmount * basketFactor / sumValue + for i, req := range reqInfo { + mapping[i] = req.Name + initRefBasket.items[i].amount = uint64(req.InitAmount * basketFactor) + initRefBasket.items[i].value = uint64(req.InitAmount * req.InitValue * scaleValues) + } + + vt := &ValueTracker{ + clock: clock, + connected: make(map[enode.ID]*NodeValueTracker), + quit: make(chan chan struct{}), + db: db, + reqTypeCount: len(initRefBasket.items), + initRefBasket: initRefBasket, + transferRate: transferRate, + statsExpRate: statsExpRate, + offlineExpRate: offlineExpRate, + } + if vt.loadFromDb(mapping) != nil { + // previous state not saved or invalid, init with default values + vt.refBasket.basket = initRefBasket + vt.mappings = [][]string{mapping} + vt.currentMapping = 0 + } + vt.statsExpirer.SetRate(now, statsExpRate) + vt.refBasket.init(vt.reqTypeCount) + vt.periodicUpdate() + + go func() { + for { + select { + case <-clock.After(updatePeriod): + vt.lock.Lock() + vt.periodicUpdate() + vt.lock.Unlock() + case quit := <-vt.quit: + close(quit) + return + } + } + }() + return vt +} + +// StatsExpirer returns the statistics expirer so that other values can be expired +// with the same rate as the service value statistics. +func (vt *ValueTracker) StatsExpirer() *utils.Expirer { + return &vt.statsExpirer +} + +// loadFromDb loads the value tracker's state from the database and converts saved +// request basket index mapping if it does not match the specified index to name mapping. +func (vt *ValueTracker) loadFromDb(mapping []string) error { + enc, err := vt.db.Get(vtKey) + if err != nil { + return err + } + r := bytes.NewReader(enc) + var version uint + if err := rlp.Decode(r, &version); err != nil { + log.Error("Decoding value tracker state failed", "err", err) + return err + } + if version != vtVersion { + log.Error("Unknown ValueTracker version", "stored", version, "current", nvtVersion) + return fmt.Errorf("Unknown ValueTracker version %d (current version is %d)", version, vtVersion) + } + var vte valueTrackerEncV1 + if err := rlp.Decode(r, &vte); err != nil { + log.Error("Decoding value tracker state failed", "err", err) + return err + } + logOffset := utils.Fixed64(vte.ExpOffset) + dt := time.Now().UnixNano() - int64(vte.SavedAt) + if dt > 0 { + logOffset += utils.Float64ToFixed64(float64(dt) * vt.offlineExpRate / math.Log(2)) + } + vt.statsExpirer.SetLogOffset(vt.clock.Now(), logOffset) + vt.rtStats = vte.RtStats + vt.mappings = vte.Mappings + vt.currentMapping = -1 +loop: + for i, m := range vt.mappings { + if len(m) != len(mapping) { + continue loop + } + for j, s := range mapping { + if m[j] != s { + continue loop + } + } + vt.currentMapping = i + break + } + if vt.currentMapping == -1 { + vt.currentMapping = len(vt.mappings) + vt.mappings = append(vt.mappings, mapping) + } + if int(vte.RefBasketMapping) == vt.currentMapping { + vt.refBasket.basket = vte.RefBasket + } else { + if vte.RefBasketMapping >= uint(len(vt.mappings)) { + log.Error("Unknown request basket mapping", "stored", vte.RefBasketMapping, "current", vt.currentMapping) + return fmt.Errorf("Unknown request basket mapping %d (current version is %d)", vte.RefBasketMapping, vt.currentMapping) + } + vt.refBasket.basket = vte.RefBasket.convertMapping(vt.mappings[vte.RefBasketMapping], mapping, vt.initRefBasket) + } + return nil +} + +// saveToDb saves the value tracker's state to the database +func (vt *ValueTracker) saveToDb() { + vte := valueTrackerEncV1{ + Mappings: vt.mappings, + RefBasketMapping: uint(vt.currentMapping), + RefBasket: vt.refBasket.basket, + RtStats: vt.rtStats, + ExpOffset: uint64(vt.statsExpirer.LogOffset(vt.clock.Now())), + SavedAt: uint64(time.Now().UnixNano()), + } + enc1, err := rlp.EncodeToBytes(uint(vtVersion)) + if err != nil { + log.Error("Encoding value tracker state failed", "err", err) + return + } + enc2, err := rlp.EncodeToBytes(&vte) + if err != nil { + log.Error("Encoding value tracker state failed", "err", err) + return + } + if err := vt.db.Put(vtKey, append(enc1, enc2...)); err != nil { + log.Error("Saving value tracker state failed", "err", err) + } +} + +// Stop saves the value tracker's state and each loaded node's individual state and +// returns after shutting the internal goroutines down. +func (vt *ValueTracker) Stop() { + quit := make(chan struct{}) + vt.quit <- quit + <-quit + vt.lock.Lock() + vt.periodicUpdate() + for id, nv := range vt.connected { + vt.saveNode(id, nv) + } + vt.connected = nil + vt.saveToDb() + vt.lock.Unlock() +} + +// Register adds a server node to the value tracker +func (vt *ValueTracker) Register(id enode.ID) *NodeValueTracker { + vt.lock.Lock() + defer vt.lock.Unlock() + + if vt.connected == nil { + // ValueTracker has already been stopped + return nil + } + nv := vt.loadOrNewNode(id) + nv.init(vt.clock.Now(), &vt.refBasket.reqValues) + vt.connected[id] = nv + return nv +} + +// Unregister removes a server node from the value tracker +func (vt *ValueTracker) Unregister(id enode.ID) { + vt.lock.Lock() + defer vt.lock.Unlock() + + if nv := vt.connected[id]; nv != nil { + vt.saveNode(id, nv) + delete(vt.connected, id) + } +} + +// GetNode returns an individual server node's value tracker. If it did not exist before +// then a new node is created. +func (vt *ValueTracker) GetNode(id enode.ID) *NodeValueTracker { + vt.lock.Lock() + defer vt.lock.Unlock() + + return vt.loadOrNewNode(id) +} + +// loadOrNewNode returns an individual server node's value tracker. If it did not exist before +// then a new node is created. +func (vt *ValueTracker) loadOrNewNode(id enode.ID) *NodeValueTracker { + if nv, ok := vt.connected[id]; ok { + return nv + } + nv := &NodeValueTracker{lastTransfer: vt.clock.Now()} + enc, err := vt.db.Get(append(vtNodeKey, id[:]...)) + if err != nil { + return nv + } + r := bytes.NewReader(enc) + var version uint + if err := rlp.Decode(r, &version); err != nil { + log.Error("Failed to decode node value tracker", "id", id, "err", err) + return nv + } + if version != nvtVersion { + log.Error("Unknown NodeValueTracker version", "stored", version, "current", nvtVersion) + return nv + } + var nve nodeValueTrackerEncV1 + if err := rlp.Decode(r, &nve); err != nil { + log.Error("Failed to decode node value tracker", "id", id, "err", err) + return nv + } + nv.rtStats = nve.RtStats + nv.lastRtStats = nve.RtStats + if int(nve.ServerBasketMapping) == vt.currentMapping { + nv.basket.basket = nve.ServerBasket + } else { + if nve.ServerBasketMapping >= uint(len(vt.mappings)) { + log.Error("Unknown request basket mapping", "stored", nve.ServerBasketMapping, "current", vt.currentMapping) + return nv + } + nv.basket.basket = nve.ServerBasket.convertMapping(vt.mappings[nve.ServerBasketMapping], vt.mappings[vt.currentMapping], vt.initRefBasket) + } + return nv +} + +// saveNode saves a server node's value tracker to the database +func (vt *ValueTracker) saveNode(id enode.ID, nv *NodeValueTracker) { + recentRtStats := nv.rtStats + recentRtStats.SubStats(&nv.lastRtStats) + vt.rtStats.AddStats(&recentRtStats) + nv.lastRtStats = nv.rtStats + + nve := nodeValueTrackerEncV1{ + RtStats: nv.rtStats, + ServerBasketMapping: uint(vt.currentMapping), + ServerBasket: nv.basket.basket, + } + enc1, err := rlp.EncodeToBytes(uint(nvtVersion)) + if err != nil { + log.Error("Failed to encode service value information", "id", id, "err", err) + return + } + enc2, err := rlp.EncodeToBytes(&nve) + if err != nil { + log.Error("Failed to encode service value information", "id", id, "err", err) + return + } + if err := vt.db.Put(append(vtNodeKey, id[:]...), append(enc1, enc2...)); err != nil { + log.Error("Failed to save service value information", "id", id, "err", err) + } +} + +// UpdateCosts updates the node value tracker's request cost table +func (vt *ValueTracker) UpdateCosts(nv *NodeValueTracker, reqCosts []uint64) { + vt.lock.Lock() + defer vt.lock.Unlock() + + nv.updateCosts(reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(reqCosts)) +} + +// RtStats returns the global response time distribution statistics +func (vt *ValueTracker) RtStats() ResponseTimeStats { + vt.lock.Lock() + defer vt.lock.Unlock() + + vt.periodicUpdate() + return vt.rtStats +} + +// periodicUpdate transfers individual node data to the global statistics, normalizes +// the reference basket and updates request values. The global state is also saved to +// the database with each update. +func (vt *ValueTracker) periodicUpdate() { + now := vt.clock.Now() + vt.statsExpLock.Lock() + vt.statsExpFactor = utils.ExpFactor(vt.statsExpirer.LogOffset(now)) + vt.statsExpLock.Unlock() + + for _, nv := range vt.connected { + basket, rtStats := nv.transferStats(now, vt.transferRate) + vt.refBasket.add(basket) + vt.rtStats.AddStats(&rtStats) + } + vt.refBasket.normalize() + vt.refBasket.updateReqValues() + for _, nv := range vt.connected { + nv.updateCosts(nv.reqCosts, &vt.refBasket.reqValues, vt.refBasket.reqValueFactor(nv.reqCosts)) + } + vt.saveToDb() +} + +type ServedRequest struct { + ReqType, Amount uint32 +} + +// Served adds a served request to the node's statistics. An actual request may be composed +// of one or more request types (service vector indices). +func (vt *ValueTracker) Served(nv *NodeValueTracker, reqs []ServedRequest, respTime time.Duration) { + vt.statsExpLock.RLock() + expFactor := vt.statsExpFactor + vt.statsExpLock.RUnlock() + + nv.lock.Lock() + defer nv.lock.Unlock() + + var value float64 + for _, r := range reqs { + nv.basket.add(r.ReqType, r.Amount, nv.reqCosts[r.ReqType]*uint64(r.Amount), expFactor) + value += (*nv.reqValues)[r.ReqType] * float64(r.Amount) + } + nv.rtStats.Add(respTime, value, vt.statsExpFactor) +} + +type RequestStatsItem struct { + Name string + ReqAmount, ReqValue float64 +} + +// RequestStats returns the current contents of the reference request basket, with +// request values meaning average per request rather than total. +func (vt *ValueTracker) RequestStats() []RequestStatsItem { + vt.statsExpLock.RLock() + expFactor := vt.statsExpFactor + vt.statsExpLock.RUnlock() + vt.lock.Lock() + defer vt.lock.Unlock() + + vt.periodicUpdate() + res := make([]RequestStatsItem, len(vt.refBasket.basket.items)) + for i, item := range vt.refBasket.basket.items { + res[i].Name = vt.mappings[vt.currentMapping][i] + res[i].ReqAmount = expFactor.Value(float64(item.amount)/basketFactor, vt.refBasket.basket.exp) + res[i].ReqValue = vt.refBasket.reqValues[i] + } + return res +} + +// TotalServiceValue returns the total service value provided by the given node (as +// a function of the weights which are calculated from the request timeout value). +func (vt *ValueTracker) TotalServiceValue(nv *NodeValueTracker, weights ResponseTimeWeights) float64 { + vt.statsExpLock.RLock() + expFactor := vt.statsExpFactor + vt.statsExpLock.RUnlock() + + nv.lock.Lock() + defer nv.lock.Unlock() + + return nv.rtStats.Value(weights, expFactor) +} diff --git a/les/lespay/client/valuetracker_test.go b/les/lespay/client/valuetracker_test.go new file mode 100644 index 0000000000..ad398749e9 --- /dev/null +++ b/les/lespay/client/valuetracker_test.go @@ -0,0 +1,135 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package client + +import ( + "math" + "math/rand" + "strconv" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/ethdb/memorydb" + "github.com/ethereum/go-ethereum/p2p/enode" + + "github.com/ethereum/go-ethereum/les/utils" +) + +const ( + testReqTypes = 3 + testNodeCount = 5 + testReqCount = 10000 + testRounds = 10 +) + +func TestValueTracker(t *testing.T) { + db := memorydb.New() + clock := &mclock.Simulated{} + requestList := make([]RequestInfo, testReqTypes) + relPrices := make([]float64, testReqTypes) + totalAmount := make([]uint64, testReqTypes) + for i := range requestList { + requestList[i] = RequestInfo{Name: "testreq" + strconv.Itoa(i), InitAmount: 1, InitValue: 1} + totalAmount[i] = 1 + relPrices[i] = rand.Float64() + 0.1 + } + nodes := make([]*NodeValueTracker, testNodeCount) + for round := 0; round < testRounds; round++ { + makeRequests := round < testRounds-2 + useExpiration := round == testRounds-1 + var expRate float64 + if useExpiration { + expRate = math.Log(2) / float64(time.Hour*100) + } + + vt := NewValueTracker(db, clock, requestList, time.Minute, 1/float64(time.Hour), expRate, expRate) + updateCosts := func(i int) { + costList := make([]uint64, testReqTypes) + baseCost := rand.Float64()*10000000 + 100000 + for j := range costList { + costList[j] = uint64(baseCost * relPrices[j]) + } + vt.UpdateCosts(nodes[i], costList) + } + for i := range nodes { + nodes[i] = vt.Register(enode.ID{byte(i)}) + updateCosts(i) + } + if makeRequests { + for i := 0; i < testReqCount; i++ { + reqType := rand.Intn(testReqTypes) + reqAmount := rand.Intn(10) + 1 + node := rand.Intn(testNodeCount) + respTime := time.Duration((rand.Float64() + 1) * float64(time.Second) * float64(node+1) / testNodeCount) + totalAmount[reqType] += uint64(reqAmount) + vt.Served(nodes[node], []ServedRequest{{uint32(reqType), uint32(reqAmount)}}, respTime) + clock.Run(time.Second) + } + } else { + clock.Run(time.Hour * 100) + if useExpiration { + for i, a := range totalAmount { + totalAmount[i] = a / 2 + } + } + } + vt.Stop() + var sumrp, sumrv float64 + for i, rp := range relPrices { + sumrp += rp + sumrv += vt.refBasket.reqValues[i] + } + for i, rp := range relPrices { + ratio := vt.refBasket.reqValues[i] * sumrp / (rp * sumrv) + if ratio < 0.99 || ratio > 1.01 { + t.Errorf("reqValues (%v) does not match relPrices (%v)", vt.refBasket.reqValues, relPrices) + break + } + } + exp := utils.ExpFactor(vt.StatsExpirer().LogOffset(clock.Now())) + basketAmount := make([]uint64, testReqTypes) + for i, bi := range vt.refBasket.basket.items { + basketAmount[i] += uint64(exp.Value(float64(bi.amount), vt.refBasket.basket.exp)) + } + if makeRequests { + // if we did not make requests in this round then we expect all amounts to be + // in the reference basket + for _, node := range nodes { + for i, bi := range node.basket.basket.items { + basketAmount[i] += uint64(exp.Value(float64(bi.amount), node.basket.basket.exp)) + } + } + } + for i, a := range basketAmount { + amount := a / basketFactor + if amount+10 < totalAmount[i] || amount > totalAmount[i]+10 { + t.Errorf("totalAmount[%d] mismatch in round %d (expected %d, got %d)", i, round, totalAmount[i], amount) + } + } + var sumValue float64 + for _, node := range nodes { + s := node.RtStats() + sumValue += s.Value(maxResponseWeights, exp) + } + s := vt.RtStats() + mainValue := s.Value(maxResponseWeights, exp) + if sumValue < mainValue-10 || sumValue > mainValue+10 { + t.Errorf("Main rtStats value does not match sum of node rtStats values in round %d (main %v, sum %v)", round, mainValue, sumValue) + } + } +} diff --git a/les/peer.go b/les/peer.go index c2f4235eb4..d14a5e0d1e 100644 --- a/les/peer.go +++ b/les/peer.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/les/flowcontrol" + lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/les/utils" "github.com/ethereum/go-ethereum/light" "github.com/ethereum/go-ethereum/p2p" @@ -356,8 +357,12 @@ type serverPeer struct { checkpointNumber uint64 // The block height which the checkpoint is registered. checkpoint params.TrustedCheckpoint // The advertised checkpoint sent by server. - poolEntry *poolEntry // Statistic for server peer. - fcServer *flowcontrol.ServerNode // Client side mirror token bucket. + poolEntry *poolEntry // Statistic for server peer. + fcServer *flowcontrol.ServerNode // Client side mirror token bucket. + vtLock sync.Mutex + valueTracker *lpc.ValueTracker + nodeValueTracker *lpc.NodeValueTracker + sentReqs map[uint64]sentReqEntry // Statistics errCount int // Counter the invalid responses server has replied @@ -428,62 +433,71 @@ func sendRequest(w p2p.MsgWriter, msgcode, reqID uint64, data interface{}) error return p2p.Send(w, msgcode, req{reqID, data}) } +func (p *serverPeer) sendRequest(msgcode, reqID uint64, data interface{}, amount int) error { + p.sentRequest(reqID, uint32(msgcode), uint32(amount)) + return sendRequest(p.rw, msgcode, reqID, data) +} + // requestHeadersByHash fetches a batch of blocks' headers corresponding to the // specified header query, based on the hash of an origin block. func (p *serverPeer) requestHeadersByHash(reqID uint64, origin common.Hash, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromhash", origin, "skip", skip, "reverse", reverse) - return sendRequest(p.rw, GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) + return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Hash: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestHeadersByNumber fetches a batch of blocks' headers corresponding to the // specified header query, based on the number of an origin block. func (p *serverPeer) requestHeadersByNumber(reqID, origin uint64, amount int, skip int, reverse bool) error { p.Log().Debug("Fetching batch of headers", "count", amount, "fromnum", origin, "skip", skip, "reverse", reverse) - return sendRequest(p.rw, GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}) + return p.sendRequest(GetBlockHeadersMsg, reqID, &getBlockHeadersData{Origin: hashOrNumber{Number: origin}, Amount: uint64(amount), Skip: uint64(skip), Reverse: reverse}, amount) } // requestBodies fetches a batch of blocks' bodies corresponding to the hashes // specified. func (p *serverPeer) requestBodies(reqID uint64, hashes []common.Hash) error { p.Log().Debug("Fetching batch of block bodies", "count", len(hashes)) - return sendRequest(p.rw, GetBlockBodiesMsg, reqID, hashes) + return p.sendRequest(GetBlockBodiesMsg, reqID, hashes, len(hashes)) } // requestCode fetches a batch of arbitrary data from a node's known state // data, corresponding to the specified hashes. func (p *serverPeer) requestCode(reqID uint64, reqs []CodeReq) error { p.Log().Debug("Fetching batch of codes", "count", len(reqs)) - return sendRequest(p.rw, GetCodeMsg, reqID, reqs) + return p.sendRequest(GetCodeMsg, reqID, reqs, len(reqs)) } // requestReceipts fetches a batch of transaction receipts from a remote node. func (p *serverPeer) requestReceipts(reqID uint64, hashes []common.Hash) error { p.Log().Debug("Fetching batch of receipts", "count", len(hashes)) - return sendRequest(p.rw, GetReceiptsMsg, reqID, hashes) + return p.sendRequest(GetReceiptsMsg, reqID, hashes, len(hashes)) } // requestProofs fetches a batch of merkle proofs from a remote node. func (p *serverPeer) requestProofs(reqID uint64, reqs []ProofReq) error { p.Log().Debug("Fetching batch of proofs", "count", len(reqs)) - return sendRequest(p.rw, GetProofsV2Msg, reqID, reqs) + return p.sendRequest(GetProofsV2Msg, reqID, reqs, len(reqs)) } // requestHelperTrieProofs fetches a batch of HelperTrie merkle proofs from a remote node. func (p *serverPeer) requestHelperTrieProofs(reqID uint64, reqs []HelperTrieReq) error { p.Log().Debug("Fetching batch of HelperTrie proofs", "count", len(reqs)) - return sendRequest(p.rw, GetHelperTrieProofsMsg, reqID, reqs) + return p.sendRequest(GetHelperTrieProofsMsg, reqID, reqs, len(reqs)) } // requestTxStatus fetches a batch of transaction status records from a remote node. func (p *serverPeer) requestTxStatus(reqID uint64, txHashes []common.Hash) error { p.Log().Debug("Requesting transaction status", "count", len(txHashes)) - return sendRequest(p.rw, GetTxStatusMsg, reqID, txHashes) + return p.sendRequest(GetTxStatusMsg, reqID, txHashes, len(txHashes)) } // SendTxStatus creates a reply with a batch of transactions to be added to the remote transaction pool. -func (p *serverPeer) sendTxs(reqID uint64, txs rlp.RawValue) error { - p.Log().Debug("Sending batch of transactions", "size", len(txs)) - return sendRequest(p.rw, SendTxV2Msg, reqID, txs) +func (p *serverPeer) sendTxs(reqID uint64, amount int, txs rlp.RawValue) error { + p.Log().Debug("Sending batch of transactions", "amount", amount, "size", len(txs)) + sizeFactor := (len(txs) + txSizeCostLimit/2) / txSizeCostLimit + if sizeFactor > amount { + amount = sizeFactor + } + return p.sendRequest(SendTxV2Msg, reqID, txs, amount) } // waitBefore implements distPeer interface @@ -532,6 +546,7 @@ func (p *serverPeer) getTxRelayCost(amount, size int) uint64 { // HasBlock checks if the peer has a given block func (p *serverPeer) HasBlock(hash common.Hash, number uint64, hasState bool) bool { p.lock.RLock() + head := p.headInfo.Number var since, recent uint64 if hasState { @@ -630,6 +645,87 @@ func (p *serverPeer) Handshake(td *big.Int, head common.Hash, headNum uint64, ge }) } +// setValueTracker sets the value tracker references for connected servers. Note that the +// references should be removed upon disconnection by setValueTracker(nil, nil). +func (p *serverPeer) setValueTracker(vt *lpc.ValueTracker, nvt *lpc.NodeValueTracker) { + p.vtLock.Lock() + p.valueTracker = vt + p.nodeValueTracker = nvt + if nvt != nil { + p.sentReqs = make(map[uint64]sentReqEntry) + } else { + p.sentReqs = nil + } + p.vtLock.Unlock() +} + +// updateVtParams updates the server's price table in the value tracker. +func (p *serverPeer) updateVtParams() { + p.vtLock.Lock() + defer p.vtLock.Unlock() + + if p.nodeValueTracker == nil { + return + } + reqCosts := make([]uint64, len(requestList)) + for code, costs := range p.fcCosts { + if m, ok := requestMapping[uint32(code)]; ok { + reqCosts[m.first] = costs.baseCost + costs.reqCost + if m.rest != -1 { + reqCosts[m.rest] = costs.reqCost + } + } + } + p.valueTracker.UpdateCosts(p.nodeValueTracker, reqCosts) +} + +// sentReqEntry remembers sent requests and their sending times +type sentReqEntry struct { + reqType, amount uint32 + at mclock.AbsTime +} + +// sentRequest marks a request sent at the current moment to this server. +func (p *serverPeer) sentRequest(id uint64, reqType, amount uint32) { + p.vtLock.Lock() + if p.sentReqs != nil { + p.sentReqs[id] = sentReqEntry{reqType, amount, mclock.Now()} + } + p.vtLock.Unlock() +} + +// answeredRequest marks a request answered at the current moment by this server. +func (p *serverPeer) answeredRequest(id uint64) { + p.vtLock.Lock() + if p.sentReqs == nil { + p.vtLock.Unlock() + return + } + e, ok := p.sentReqs[id] + delete(p.sentReqs, id) + vt := p.valueTracker + nvt := p.nodeValueTracker + p.vtLock.Unlock() + if !ok { + return + } + var ( + vtReqs [2]lpc.ServedRequest + reqCount int + ) + m := requestMapping[e.reqType] + if m.rest == -1 || e.amount <= 1 { + reqCount = 1 + vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: e.amount} + } else { + reqCount = 2 + vtReqs[0] = lpc.ServedRequest{ReqType: uint32(m.first), Amount: 1} + vtReqs[1] = lpc.ServedRequest{ReqType: uint32(m.rest), Amount: e.amount - 1} + } + dt := time.Duration(mclock.Now() - e.at) + vt.Served(nvt, vtReqs[:reqCount], dt) +} + // clientPeer represents each node to which the les server is connected. // The node here refers to the light client. type clientPeer struct { diff --git a/les/protocol.go b/les/protocol.go index 36af88aea6..f8ad94a7b5 100644 --- a/les/protocol.go +++ b/les/protocol.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + lpc "github.com/ethereum/go-ethereum/les/lespay/client" "github.com/ethereum/go-ethereum/p2p/enode" "github.com/ethereum/go-ethereum/rlp" ) @@ -77,19 +78,59 @@ const ( ) type requestInfo struct { - name string - maxCount uint64 + name string + maxCount uint64 + refBasketFirst, refBasketRest float64 } -var requests = map[uint64]requestInfo{ - GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch}, - GetBlockBodiesMsg: {"GetBlockBodies", MaxBodyFetch}, - GetReceiptsMsg: {"GetReceipts", MaxReceiptFetch}, - GetCodeMsg: {"GetCode", MaxCodeFetch}, - GetProofsV2Msg: {"GetProofsV2", MaxProofsFetch}, - GetHelperTrieProofsMsg: {"GetHelperTrieProofs", MaxHelperTrieProofsFetch}, - SendTxV2Msg: {"SendTxV2", MaxTxSend}, - GetTxStatusMsg: {"GetTxStatus", MaxTxStatus}, +// reqMapping maps an LES request to one or two lespay service vector entries. +// If rest != -1 and the request type is used with amounts larger than one then the +// first one of the multi-request is mapped to first while the rest is mapped to rest. +type reqMapping struct { + first, rest int +} + +var ( + // requests describes the available LES request types and their initializing amounts + // in the lespay/client.ValueTracker reference basket. Initial values are estimates + // based on the same values as the server's default cost estimates (reqAvgTimeCost). + requests = map[uint64]requestInfo{ + GetBlockHeadersMsg: {"GetBlockHeaders", MaxHeaderFetch, 10, 1000}, + GetBlockBodiesMsg: {"GetBlockBodies", MaxBodyFetch, 1, 0}, + GetReceiptsMsg: {"GetReceipts", MaxReceiptFetch, 1, 0}, + GetCodeMsg: {"GetCode", MaxCodeFetch, 1, 0}, + GetProofsV2Msg: {"GetProofsV2", MaxProofsFetch, 10, 0}, + GetHelperTrieProofsMsg: {"GetHelperTrieProofs", MaxHelperTrieProofsFetch, 10, 100}, + SendTxV2Msg: {"SendTxV2", MaxTxSend, 1, 0}, + GetTxStatusMsg: {"GetTxStatus", MaxTxStatus, 10, 0}, + } + requestList []lpc.RequestInfo + requestMapping map[uint32]reqMapping +) + +// init creates a request list and mapping between protocol message codes and lespay +// service vector indices. +func init() { + requestMapping = make(map[uint32]reqMapping) + for code, req := range requests { + cost := reqAvgTimeCost[code] + rm := reqMapping{len(requestList), -1} + requestList = append(requestList, lpc.RequestInfo{ + Name: req.name + ".first", + InitAmount: req.refBasketFirst, + InitValue: float64(cost.baseCost + cost.reqCost), + }) + if req.refBasketRest != 0 { + rm.rest = len(requestList) + requestList = append(requestList, lpc.RequestInfo{ + Name: req.name + ".rest", + InitAmount: req.refBasketRest, + InitValue: float64(cost.reqCost), + }) + } + requestMapping[uint32(code)] = rm + } + } type errCode int diff --git a/les/txrelay.go b/les/txrelay.go index 595c4d5808..4f6c15025e 100644 --- a/les/txrelay.go +++ b/les/txrelay.go @@ -144,7 +144,7 @@ func (ltrx *lesTxRelay) send(txs types.Transactions, count int) { peer := dp.(*serverPeer) cost := peer.getTxRelayCost(len(ll), len(enc)) peer.fcServer.QueuedRequest(reqID, cost) - return func() { peer.sendTxs(reqID, enc) } + return func() { peer.sendTxs(reqID, len(ll), enc) } }, } go ltrx.retriever.retrieve(context.Background(), reqID, rq, func(p distPeer, msg *Msg) error { return nil }, ltrx.stop) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go new file mode 100644 index 0000000000..2d6e5d4bb8 --- /dev/null +++ b/les/utils/expiredvalue.go @@ -0,0 +1,202 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import ( + "math" + + "github.com/ethereum/go-ethereum/common/mclock" +) + +// ExpiredValue is a scalar value that is continuously expired (decreased +// exponentially) based on the provided logarithmic expiration offset value. +// +// The formula for value calculation is: base*2^(exp-logOffset). In order to +// simplify the calculation of ExpiredValue, its value is expressed in the form +// of an exponent with a base of 2. +// +// Also here is a trick to reduce a lot of calculations. In theory, when a value X +// decays over time and then a new value Y is added, the final result should be +// X*2^(exp-logOffset)+Y. However it's very hard to represent in memory. +// So the trick is using the idea of inflation instead of exponential decay. At this +// moment the temporary value becomes: X*2^exp+Y*2^logOffset_1, apply the exponential +// decay when we actually want to calculate the value. +// +// e.g. +// t0: V = 100 +// t1: add 30, inflationary value is: 100 + 30/0.3, 0.3 is the decay coefficient +// t2: get value, decay coefficient is 0.2 now, final result is: 200*0.2 = 40 +type ExpiredValue struct { + Base, Exp uint64 // rlp encoding works by default +} + +// ExpirationFactor is calculated from logOffset. 1 <= Factor < 2 and Factor*2^Exp +// describes the multiplier applicable for additions and the divider for readouts. +// If logOffset changes slowly then it saves some expensive operations to not calculate +// them for each addition and readout but cache this intermediate form for some time. +// It is also useful for structures where multiple values are expired with the same +// Expirer. +type ExpirationFactor struct { + Exp uint64 + Factor float64 +} + +// ExpFactor calculates ExpirationFactor based on logOffset +func ExpFactor(logOffset Fixed64) ExpirationFactor { + return ExpirationFactor{Exp: logOffset.ToUint64(), Factor: logOffset.Fraction().Pow2()} +} + +// Value calculates the expired value based on a floating point base and integer +// power-of-2 exponent. This function should be used by multi-value expired structures. +func (e ExpirationFactor) Value(base float64, exp uint64) float64 { + res := base / e.Factor + if exp > e.Exp { + res *= float64(uint64(1) << (exp - e.Exp)) + } + if exp < e.Exp { + res /= float64(uint64(1) << (e.Exp - exp)) + } + return res +} + +// value calculates the value at the given moment. +func (e ExpiredValue) Value(logOffset Fixed64) uint64 { + offset := Uint64ToFixed64(e.Exp) - logOffset + return uint64(float64(e.Base) * offset.Pow2()) +} + +// add adds a signed value at the given moment +func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { + integer, frac := logOffset.ToUint64(), logOffset.Fraction() + factor := frac.Pow2() + base := factor * float64(amount) + if integer < e.Exp { + base /= math.Pow(2, float64(e.Exp-integer)) + } + if integer > e.Exp { + e.Base >>= (integer - e.Exp) + e.Exp = integer + } + if base >= 0 || uint64(-base) <= e.Base { + e.Base += uint64(base) + return amount + } + net := int64(-float64(e.Base) / factor) + e.Base = 0 + return net +} + +// addExp adds another ExpiredValue +func (e *ExpiredValue) AddExp(a ExpiredValue) { + if e.Exp > a.Exp { + a.Base >>= (e.Exp - a.Exp) + } + if e.Exp < a.Exp { + e.Base >>= (a.Exp - e.Exp) + e.Exp = a.Exp + } + e.Base += a.Base +} + +// subExp subtracts another ExpiredValue +func (e *ExpiredValue) SubExp(a ExpiredValue) { + if e.Exp > a.Exp { + a.Base >>= (e.Exp - a.Exp) + } + if e.Exp < a.Exp { + e.Base >>= (a.Exp - e.Exp) + e.Exp = a.Exp + } + if e.Base > a.Base { + e.Base -= a.Base + } else { + e.Base = 0 + } +} + +// Expirer changes logOffset with a linear rate which can be changed during operation. +// It is not thread safe, if access by multiple goroutines is needed then it should be +// encapsulated into a locked structure. +// Note that if neither SetRate nor SetLogOffset are used during operation then LogOffset +// is thread safe. +type Expirer struct { + logOffset Fixed64 + rate float64 + lastUpdate mclock.AbsTime +} + +// SetRate changes the expiration rate which is the inverse of the time constant in +// nanoseconds. +func (e *Expirer) SetRate(now mclock.AbsTime, rate float64) { + dt := now - e.lastUpdate + if dt > 0 { + e.logOffset += Fixed64(logToFixedFactor * float64(dt) * e.rate) + } + e.lastUpdate = now + e.rate = rate +} + +// SetLogOffset sets logOffset instantly. +func (e *Expirer) SetLogOffset(now mclock.AbsTime, logOffset Fixed64) { + e.lastUpdate = now + e.logOffset = logOffset +} + +// LogOffset returns the current logarithmic offset. +func (e *Expirer) LogOffset(now mclock.AbsTime) Fixed64 { + dt := now - e.lastUpdate + if dt <= 0 { + return e.logOffset + } + return e.logOffset + Fixed64(logToFixedFactor*float64(dt)*e.rate) +} + +// fixedFactor is the fixed point multiplier factor used by Fixed64. +const fixedFactor = 0x1000000 + +// Fixed64 implements 64-bit fixed point arithmetic functions. +type Fixed64 int64 + +// Uint64ToFixed64 converts uint64 integer to Fixed64 format. +func Uint64ToFixed64(f uint64) Fixed64 { + return Fixed64(f * fixedFactor) +} + +// float64ToFixed64 converts float64 to Fixed64 format. +func Float64ToFixed64(f float64) Fixed64 { + return Fixed64(f * fixedFactor) +} + +// toUint64 converts Fixed64 format to uint64. +func (f64 Fixed64) ToUint64() uint64 { + return uint64(f64) / fixedFactor +} + +// fraction returns the fractional part of a Fixed64 value. +func (f64 Fixed64) Fraction() Fixed64 { + return f64 % fixedFactor +} + +var ( + logToFixedFactor = float64(fixedFactor) / math.Log(2) + fixedToLogFactor = math.Log(2) / float64(fixedFactor) +) + +// pow2Fixed returns the base 2 power of the fixed point value. +func (f64 Fixed64) Pow2() float64 { + return math.Exp(float64(f64) * fixedToLogFactor) +} diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go new file mode 100644 index 0000000000..4894a12b12 --- /dev/null +++ b/les/utils/expiredvalue_test.go @@ -0,0 +1,116 @@ +// Copyright 2020 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package utils + +import "testing" + +func TestValueExpiration(t *testing.T) { + var cases = []struct { + input ExpiredValue + timeOffset Fixed64 + expect uint64 + }{ + {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128}, + {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64}, + {ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(2), 32}, + {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(2), 128}, + {ExpiredValue{Base: 128, Exp: 2}, Uint64ToFixed64(3), 64}, + } + for _, c := range cases { + if got := c.input.Value(c.timeOffset); got != c.expect { + t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) + } + } +} + +func TestValueAddition(t *testing.T) { + var cases = []struct { + input ExpiredValue + addend int64 + timeOffset Fixed64 + expect uint64 + expectNet int64 + }{ + // Addition + {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(0), 256, 128}, + {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(0), 640, 128}, + + // Addition with offset + {ExpiredValue{Base: 128, Exp: 0}, 128, Uint64ToFixed64(1), 192, 128}, + {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(1), 384, 128}, + {ExpiredValue{Base: 128, Exp: 2}, 128, Uint64ToFixed64(3), 192, 128}, + + // Subtraction + {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(0), 64, -64}, + {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(0), 0, -128}, + {ExpiredValue{Base: 128, Exp: 0}, -192, Uint64ToFixed64(0), 0, -128}, + + // Subtraction with offset + {ExpiredValue{Base: 128, Exp: 0}, -64, Uint64ToFixed64(1), 0, -64}, + {ExpiredValue{Base: 128, Exp: 0}, -128, Uint64ToFixed64(1), 0, -64}, + {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(1), 128, -128}, + {ExpiredValue{Base: 128, Exp: 2}, -128, Uint64ToFixed64(2), 0, -128}, + } + for _, c := range cases { + if net := c.input.Add(c.addend, c.timeOffset); net != c.expectNet { + t.Fatalf("Net amount mismatch, want=%d, got=%d", c.expectNet, net) + } + if got := c.input.Value(c.timeOffset); got != c.expect { + t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) + } + } +} + +func TestExpiredValueAddition(t *testing.T) { + var cases = []struct { + input ExpiredValue + another ExpiredValue + timeOffset Fixed64 + expect uint64 + }{ + {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 256}, + {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 384}, + {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 384}, + {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 128}, + } + for _, c := range cases { + c.input.AddExp(c.another) + if got := c.input.Value(c.timeOffset); got != c.expect { + t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) + } + } +} + +func TestExpiredValueSubtraction(t *testing.T) { + var cases = []struct { + input ExpiredValue + another ExpiredValue + timeOffset Fixed64 + expect uint64 + }{ + {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 0}, + {ExpiredValue{Base: 128, Exp: 0}, ExpiredValue{Base: 128, Exp: 1}, Uint64ToFixed64(0), 0}, + {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(0), 128}, + {ExpiredValue{Base: 128, Exp: 1}, ExpiredValue{Base: 128, Exp: 0}, Uint64ToFixed64(1), 64}, + } + for _, c := range cases { + c.input.SubExp(c.another) + if got := c.input.Value(c.timeOffset); got != c.expect { + t.Fatalf("Value mismatch, want=%d, got=%d", c.expect, got) + } + } +} diff --git a/p2p/enode/node.go b/p2p/enode/node.go index 9eb2544ffe..3f6cda6d4a 100644 --- a/p2p/enode/node.go +++ b/p2p/enode/node.go @@ -217,7 +217,7 @@ func (n ID) MarshalText() ([]byte, error) { // UnmarshalText implements the encoding.TextUnmarshaler interface. func (n *ID) UnmarshalText(text []byte) error { - id, err := parseID(string(text)) + id, err := ParseID(string(text)) if err != nil { return err } @@ -229,14 +229,14 @@ func (n *ID) UnmarshalText(text []byte) error { // The string may be prefixed with 0x. // It panics if the string is not a valid ID. func HexID(in string) ID { - id, err := parseID(in) + id, err := ParseID(in) if err != nil { panic(err) } return id } -func parseID(in string) (ID, error) { +func ParseID(in string) (ID, error) { var id ID b, err := hex.DecodeString(strings.TrimPrefix(in, "0x")) if err != nil { From 5a20cc0de6f58f09e68c13154702b278b7aeeedf Mon Sep 17 00:00:00 2001 From: ligi Date: Tue, 14 Apr 2020 10:08:27 +0200 Subject: [PATCH 097/103] README: update min go version to 1.13 (#20911) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbcf3e4ce2..4974e499a7 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ archives are published at https://geth.ethereum.org/downloads/. For prerequisites and detailed build instructions please read the [Installation Instructions](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) on the wiki. -Building `geth` requires both a Go (version 1.10 or later) and a C compiler. You can install +Building `geth` requires both a Go (version 1.13 or later) and a C compiler. You can install them using your favourite package manager. Once the dependencies are installed, run ```shell From eb2fd823b2fd8d6745cceb25de19fde0acd53d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 14 Apr 2020 14:03:18 +0300 Subject: [PATCH 098/103] travis, appveyor, build, Dockerfile: bump Go to 1.14.2 (#20913) * travis, appveyor, build, Dockerfile: bump Go to 1.14.2 * travis, appveyor: force GO111MODULE=on for every build --- .travis.yml | 46 +++++++++++++++++++++++++++++++++------------ Dockerfile | 2 +- Dockerfile.alltools | 2 +- appveyor.yml | 5 +++-- build/checksums.txt | 2 +- 5 files changed, 40 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index 82684a701c..6f4483c4b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ jobs: allow_failures: - stage: build os: osx - go: 1.13.x + go: 1.14.x env: - azure-osx - azure-ios @@ -16,7 +16,7 @@ jobs: - stage: lint os: linux dist: xenial - go: 1.13.x + go: 1.14.x env: - lint git: @@ -44,12 +44,24 @@ jobs: - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES + - stage: build + os: linux + dist: xenial + go: 1.13.x + env: + - GO111MODULE=on + script: + - go run build/ci.go install + - go run build/ci.go test -coverage $TEST_PACKAGES + # These are the latest Go versions. - stage: build os: linux arch: amd64 dist: xenial - go: 1.13.x + go: 1.14.x + env: + - GO111MODULE=on script: - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES @@ -59,7 +71,9 @@ jobs: os: linux arch: arm64 dist: xenial - go: 1.13.x + go: 1.14.x + env: + - GO111MODULE=on script: - go run build/ci.go install - go run build/ci.go test -coverage $TEST_PACKAGES @@ -67,7 +81,9 @@ jobs: - stage: build os: osx osx_image: xcode11.3 - go: 1.13.x + go: 1.14.x + env: + - GO111MODULE=on script: - echo "Increase the maximum number of open file descriptors on macOS" - NOFILE=20480 @@ -86,9 +102,10 @@ jobs: if: type = push os: linux dist: xenial - go: 1.13.x + go: 1.14.x env: - ubuntu-ppa + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests addons: @@ -102,7 +119,7 @@ jobs: - python-paramiko script: - echo '|1|7SiYPr9xl3uctzovOTj4gMwAC1M=|t6ReES75Bo/PxlOPJ6/GsGbTrM0= ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0aKz5UTUndYgIGG7dQBV+HaeuEZJ2xPHo2DS2iSKvUL4xNMSAY4UguNW+pX56nAQmZKIZZ8MaEvSj6zMEDiq6HFfn5JcTlM80UwlnyKe8B8p7Nk06PPQLrnmQt5fh0HmEcZx+JU9TZsfCHPnX7MNz4ELfZE6cFsclClrKim3BHUIGq//t93DllB+h4O9LHjEUsQ1Sr63irDLSutkLJD6RXchjROXkNirlcNVHH/jwLWR5RcYilNX7S5bIkK8NlWPjsn/8Ua5O7I9/YoE97PpO6i73DTGLh5H9JN/SITwCKBkgSDWUt61uPK3Y11Gty7o2lWsBjhBUm2Y38CBsoGmBw==' >> ~/.ssh/known_hosts - - go run build/ci.go debsrc -goversion 1.13.8 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " + - go run build/ci.go debsrc -goversion 1.14.2 -upload ethereum/ethereum -sftp-user geth-ci -signer "Go Ethereum Linux Builder " # This builder does the Linux Azure uploads - stage: build @@ -110,9 +127,10 @@ jobs: os: linux dist: xenial sudo: required - go: 1.13.x + go: 1.14.x env: - azure-linux + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests addons: @@ -146,9 +164,10 @@ jobs: dist: xenial services: - docker - go: 1.13.x + go: 1.14.x env: - azure-linux-mips + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: @@ -189,10 +208,11 @@ jobs: env: - azure-android - maven-android + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests before_install: - - curl https://dl.google.com/go/go1.13.8.linux-amd64.tar.gz | tar -xz + - curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -xz - export PATH=`pwd`/go/bin:$PATH - export GOROOT=`pwd`/go - export GOPATH=$HOME/go @@ -210,11 +230,12 @@ jobs: - stage: build if: type = push os: osx - go: 1.13.x + go: 1.14.x env: - azure-osx - azure-ios - cocoapods-ios + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: @@ -241,9 +262,10 @@ jobs: if: type = cron os: linux dist: xenial - go: 1.13.x + go: 1.14.x env: - azure-purge + - GO111MODULE=on git: submodules: false # avoid cloning ethereum/tests script: diff --git a/Dockerfile b/Dockerfile index 114e762058..54453c4df5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.13-alpine as builder +FROM golang:1.14-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git diff --git a/Dockerfile.alltools b/Dockerfile.alltools index 2f661ba01c..9c28979a1e 100644 --- a/Dockerfile.alltools +++ b/Dockerfile.alltools @@ -1,5 +1,5 @@ # Build Geth in a stock Go builder container -FROM golang:1.13-alpine as builder +FROM golang:1.14-alpine as builder RUN apk add --no-cache make gcc musl-dev linux-headers git diff --git a/appveyor.yml b/appveyor.yml index 90a862abe7..fe15cc7f0e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,7 @@ clone_depth: 5 version: "{branch}.{build}" environment: global: + GO111MODULE: on GOPATH: C:\gopath CC: gcc.exe matrix: @@ -23,8 +24,8 @@ environment: install: - git submodule update --init - rmdir C:\go /s /q - - appveyor DownloadFile https://dl.google.com/go/go1.13.8.windows-%GETH_ARCH%.zip - - 7z x go1.13.8.windows-%GETH_ARCH%.zip -y -oC:\ > NUL + - appveyor DownloadFile https://dl.google.com/go/go1.14.2.windows-%GETH_ARCH%.zip + - 7z x go1.14.2.windows-%GETH_ARCH%.zip -y -oC:\ > NUL - go version - gcc --version diff --git a/build/checksums.txt b/build/checksums.txt index 394d32f4e9..2605abbe0d 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -1,6 +1,6 @@ # This file contains sha256 checksums of optional build dependencies. -b13bf04633d4d8cf53226ebeaace8d4d2fd07ae6fa676d0844a688339debec34 go1.13.8.src.tar.gz +98de84e69726a66da7b4e58eac41b99cbe274d7e8906eeb8a5b7eb0aadee7f7c go1.14.2.src.tar.gz aeaa5498682246b87d0b77ece283897348ea03d98e816760a074058bfca60b2a golangci-lint-1.24.0-windows-amd64.zip 7e854a70d449fe77b7a91583ec88c8603eb3bf96c45d52797dc4ba3f2f278dbe golangci-lint-1.24.0-darwin-386.tar.gz From 2a836bb259c03626e5ef8435f99f341ea911bfff Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Tue, 14 Apr 2020 17:13:47 +0200 Subject: [PATCH 099/103] core/rawdb: fix data race between Retrieve and Close (#20919) * core/rawdb: fixed data race between retrieve and close closes https://github.com/ethereum/go-ethereum/issues/20420 * core/rawdb: use non-atomic load while holding mutex --- core/rawdb/freezer_table.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index 9fb341f025..9cff4216ca 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -541,20 +541,22 @@ func (t *freezerTable) getBounds(item uint64) (uint32, uint32, uint32, error) { // Retrieve looks up the data offset of an item with the given number and retrieves // the raw binary blob from the data file. func (t *freezerTable) Retrieve(item uint64) ([]byte, error) { + t.lock.RLock() // Ensure the table and the item is accessible if t.index == nil || t.head == nil { + t.lock.RUnlock() return nil, errClosed } if atomic.LoadUint64(&t.items) <= item { + t.lock.RUnlock() return nil, errOutOfBounds } // Ensure the item was not deleted from the tail either - offset := atomic.LoadUint32(&t.itemOffset) - if uint64(offset) > item { + if uint64(t.itemOffset) > item { + t.lock.RUnlock() return nil, errOutOfBounds } - t.lock.RLock() - startOffset, endOffset, filenum, err := t.getBounds(item - uint64(offset)) + startOffset, endOffset, filenum, err := t.getBounds(item - uint64(t.itemOffset)) if err != nil { t.lock.RUnlock() return nil, err From 00064ddcfb76c194997c261ca85486b2c8fd8784 Mon Sep 17 00:00:00 2001 From: gary rong Date: Wed, 15 Apr 2020 15:23:58 +0800 Subject: [PATCH 100/103] accounts/abi: implement new fallback functions (#20764) * accounts/abi: implement new fackball functions In Solidity v0.6.0, the original fallback is separated into two different sub types: fallback and receive. This PR addes the support for parsing new format abi and the relevant abigen functionalities. * accounts/abi: fix unit tests * accounts/abi: minor fixes * accounts/abi, mobile: support jave binding * accounts/abi: address marius's comment * accounts/abi: Work around the uin64 conversion issue Co-authored-by: Guillaume Ballet --- accounts/abi/abi.go | 116 ++++++++++++++++++++++++++----- accounts/abi/abi_test.go | 50 +++++++------- accounts/abi/bind/base.go | 12 ++++ accounts/abi/bind/bind.go | 35 ++++++++-- accounts/abi/bind/bind_test.go | 121 +++++++++++++++++++++++++++++---- accounts/abi/bind/template.go | 66 ++++++++++++++++++ accounts/abi/method.go | 56 ++++++++++++--- accounts/abi/method_test.go | 29 ++++++-- accounts/abi/unpack_test.go | 36 +++++----- les/utils/expiredvalue.go | 5 +- les/utils/expiredvalue_test.go | 4 +- mobile/bind.go | 9 +++ 12 files changed, 445 insertions(+), 94 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index fdb4c48b39..4b88a52cef 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -19,6 +19,7 @@ package abi import ( "bytes" "encoding/json" + "errors" "fmt" "io" @@ -32,6 +33,12 @@ type ABI struct { Constructor Method Methods map[string]Method Events map[string]Event + + // Additional "special" functions introduced in solidity v0.6.0. + // It's separated from the original default fallback. Each contract + // can only define one fallback and receive function. + Fallback Method // Note it's also used to represent legacy fallback before v0.6.0 + Receive Method } // JSON returns a parsed ABI interface and error if it failed. @@ -42,7 +49,6 @@ func JSON(reader io.Reader) (ABI, error) { if err := dec.Decode(&abi); err != nil { return ABI{}, err } - return abi, nil } @@ -108,13 +114,22 @@ func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) // UnmarshalJSON implements json.Unmarshaler interface func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { - Type string - Name string - Constant bool + Type string + Name string + Inputs []Argument + Outputs []Argument + + // Status indicator which can be: "pure", "view", + // "nonpayable" or "payable". StateMutability string - Anonymous bool - Inputs []Argument - Outputs []Argument + + // Deprecated Status indicators, but removed in v0.6.0. + Constant bool // True if function is either pure or view + Payable bool // True if function is payable + + // Event relevant indicator represents the event is + // declared as anonymous. + Anonymous bool } if err := json.Unmarshal(data, &fields); err != nil { return err @@ -126,22 +141,82 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { case "constructor": abi.Constructor = Method{ Inputs: field.Inputs, + + // Note for constructor the `StateMutability` can only + // be payable or nonpayable according to the output of + // compiler. So constant is always false. + StateMutability: field.StateMutability, + + // Legacy fields, keep them for backward compatibility + Constant: field.Constant, + Payable: field.Payable, } - // empty defaults to function according to the abi spec - case "function", "": + case "function": name := field.Name _, ok := abi.Methods[name] for idx := 0; ok; idx++ { name = fmt.Sprintf("%s%d", field.Name, idx) _, ok = abi.Methods[name] } - isConst := field.Constant || field.StateMutability == "pure" || field.StateMutability == "view" abi.Methods[name] = Method{ - Name: name, - RawName: field.Name, - Const: isConst, - Inputs: field.Inputs, - Outputs: field.Outputs, + Name: name, + RawName: field.Name, + StateMutability: field.StateMutability, + Inputs: field.Inputs, + Outputs: field.Outputs, + + // Legacy fields, keep them for backward compatibility + Constant: field.Constant, + Payable: field.Payable, + } + case "fallback": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasFallback() { + return errors.New("only single fallback is allowed") + } + abi.Fallback = Method{ + Name: "", + RawName: "", + + // The `StateMutability` can only be payable or nonpayable, + // so the constant is always false. + StateMutability: field.StateMutability, + IsFallback: true, + + // Fallback doesn't have any input or output + Inputs: nil, + Outputs: nil, + + // Legacy fields, keep them for backward compatibility + Constant: field.Constant, + Payable: field.Payable, + } + case "receive": + // New introduced function type in v0.6.0, check more detail + // here https://solidity.readthedocs.io/en/v0.6.0/contracts.html#fallback-function + if abi.HasReceive() { + return errors.New("only single receive is allowed") + } + if field.StateMutability != "payable" { + return errors.New("the statemutability of receive can only be payable") + } + abi.Receive = Method{ + Name: "", + RawName: "", + + // The `StateMutability` can only be payable, so constant + // is always true while payable is always false. + StateMutability: field.StateMutability, + IsReceive: true, + + // Receive doesn't have any input or output + Inputs: nil, + Outputs: nil, + + // Legacy fields, keep them for backward compatibility + Constant: field.Constant, + Payable: field.Payable, } case "event": name := field.Name @@ -158,7 +233,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error { } } } - return nil } @@ -186,3 +260,13 @@ func (abi *ABI) EventByID(topic common.Hash) (*Event, error) { } return nil, fmt.Errorf("no event with id: %#x", topic.Hex()) } + +// HasFallback returns an indicator whether a fallback function is included. +func (abi *ABI) HasFallback() bool { + return abi.Fallback.IsFallback +} + +// HasReceive returns an indicator whether a receive function is included. +func (abi *ABI) HasReceive() bool { + return abi.Receive.IsReceive +} diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 61ab70eb83..352006cf5f 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -31,29 +31,29 @@ import ( const jsondata = ` [ - { "type" : "function", "name" : "balance", "constant" : true }, - { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "stateMutability" : "view" }, + { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` const jsondata2 = ` [ - { "type" : "function", "name" : "balance", "constant" : true }, - { "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, - { "type" : "function", "name" : "test", "constant" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, - { "type" : "function", "name" : "string", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, - { "type" : "function", "name" : "bool", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, - { "type" : "function", "name" : "address", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, - { "type" : "function", "name" : "uint64[2]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, - { "type" : "function", "name" : "uint64[]", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, - { "type" : "function", "name" : "foo", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, - { "type" : "function", "name" : "bar", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, - { "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, - { "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, - { "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, - { "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, - { "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, - { "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, - { "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] } + { "type" : "function", "name" : "balance", "stateMutability" : "view" }, + { "type" : "function", "name" : "send", "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "uint64[2]", "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "foo", "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] }, + { "type" : "function", "name" : "sliceAddress", "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] }, + { "type" : "function", "name" : "sliceMultiAddress", "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray", "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] }, + { "type" : "function", "name" : "nestedArray2", "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] }, + { "type" : "function", "name" : "nestedSlice", "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] } ]` func TestReader(t *testing.T) { @@ -61,10 +61,10 @@ func TestReader(t *testing.T) { exp := ABI{ Methods: map[string]Method{ "balance": { - "balance", "balance", true, nil, nil, + "balance", "balance", "view", false, false, false, false, nil, nil, }, "send": { - "send", "send", false, []Argument{ + "send", "send", "", false, false, false, false, []Argument{ {"amount", Uint256, false}, }, nil, }, @@ -173,7 +173,7 @@ func TestTestSlice(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string", "", nil) - m := Method{"foo", "foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} + m := Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil} exp := "foo(string,string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -185,7 +185,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint256", "", nil) - m = Method{"foo", "foo", false, []Argument{{"bar", uintt, false}}, nil} + m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -204,7 +204,7 @@ func TestMethodSignature(t *testing.T) { {Name: "y", Type: "int256"}, }}, }) - m = Method{"foo", "foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} + m = Method{"foo", "foo", "", false, false, false, false, []Argument{{"s", s, false}, {"bar", String, false}}, nil} exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -582,7 +582,7 @@ func TestInputFixedArrayAndVariableInputLength(t *testing.T) { } func TestDefaultFunctionParsing(t *testing.T) { - const definition = `[{ "name" : "balance" }]` + const definition = `[{ "name" : "balance", "type" : "function" }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 499b4bda07..e69e3afa54 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -171,12 +171,24 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, params ...in if err != nil { return nil, err } + // todo(rjl493456442) check the method is payable or not, + // reject invalid transaction at the first place return c.transact(opts, &c.address, input) } +// RawTransact initiates a transaction with the given raw calldata as the input. +// It's usually used to initiates transaction for invoking **Fallback** function. +func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (*types.Transaction, error) { + // todo(rjl493456442) check the method is payable or not, + // reject invalid transaction at the first place + return c.transact(opts, &c.address, calldata) +} + // Transfer initiates a plain transaction to move funds to the contract, calling // its default method if one is available. func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) { + // todo(rjl493456442) check the payable fallback or receive is defined + // or not, reject invalid transaction at the first place return c.transact(opts, &c.address, nil) } diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 13ac286b45..c98f8b4d4c 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -77,6 +77,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] calls = make(map[string]*tmplMethod) transacts = make(map[string]*tmplMethod) events = make(map[string]*tmplEvent) + fallback *tmplMethod + receive *tmplMethod // identifiers are used to detect duplicated identifier of function // and event. For all calls, transacts and events, abigen will generate @@ -92,7 +94,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] normalizedName := methodNormalizer[lang](alias(aliases, original.Name)) // Ensure there is no duplicated identifier var identifiers = callIdentifiers - if !original.Const { + if !original.IsConstant() { identifiers = transactIdentifiers } if identifiers[normalizedName] { @@ -121,7 +123,7 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] } } // Append the methods to the call or transact lists - if original.Const { + if original.IsConstant() { calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } else { transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} @@ -156,7 +158,13 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] // Append the event to the accumulator list events[original.Name] = &tmplEvent{Original: original, Normalized: normalized} } - + // Add two special fallback functions if they exist + if evmABI.HasFallback() { + fallback = &tmplMethod{Original: evmABI.Fallback} + } + if evmABI.HasReceive() { + receive = &tmplMethod{Original: evmABI.Receive} + } // There is no easy way to pass arbitrary java objects to the Go side. if len(structs) > 0 && lang == LangJava { return "", errors.New("java binding for tuple arguments is not supported yet") @@ -169,6 +177,8 @@ func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string] Constructor: evmABI.Constructor, Calls: calls, Transacts: transacts, + Fallback: fallback, + Receive: receive, Events: events, Libraries: make(map[string]string), } @@ -619,11 +629,22 @@ func formatMethod(method abi.Method, structs map[string]*tmplStruct) string { outputs[i] += fmt.Sprintf(" %v", output.Name) } } - constant := "" - if method.Const { - constant = "constant " + // Extract meaningful state mutability of solidity method. + // If it's default value, never print it. + state := method.StateMutability + if state == "nonpayable" { + state = "" } - return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", ")) + if state != "" { + state = state + " " + } + identity := fmt.Sprintf("function %v", method.RawName) + if method.IsFallback { + identity = "fallback" + } else if method.IsReceive { + identity = "receive" + } + return fmt.Sprintf("%s(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", ")) } // formatEvent transforms raw event representation into a user friendly one. diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index bbc6d8a45a..7add7110a0 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -1585,6 +1585,99 @@ var bindTests = []struct { nil, nil, }, + // Test fallback separation introduced in v0.6.0 + { + `NewFallbacks`, + ` + pragma solidity >=0.6.0 <0.7.0; + + contract NewFallbacks { + event Fallback(bytes data); + fallback() external { + bytes memory data; + assembly { + calldatacopy(data, 0, calldatasize()) + } + emit Fallback(data); + } + + event Received(address addr, uint value); + receive() external payable { + emit Received(msg.sender, msg.value); + } + } + `, + []string{"60806040523480156100115760006000fd5b50610017565b61016e806100266000396000f3fe60806040526004361061000d575b36610081575b7f88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f885258743334604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a15b005b34801561008e5760006000fd5b505b606036600082377f9043988963722edecc2099c75b0af0ff76af14ffca42ed6bce059a20a2a9f986816040518080602001828103825283818151815260200191508051906020019080838360005b838110156100fa5780820151818401525b6020810190506100de565b50505050905090810190601f1680156101275780820380516001836020036101000a031916815260200191505b509250505060405180910390a1505b00fea26469706673582212205643ca37f40c2b352dc541f42e9e6720de065de756324b7fcc9fb1d67eda4a7d64736f6c63430006040033"}, + []string{`[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Fallback","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Received","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]`}, + ` + "bytes" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/crypto" + `, + ` + key, _ := crypto.GenerateKey() + addr := crypto.PubkeyToAddress(key.PublicKey) + + sim := backends.NewSimulatedBackend(core.GenesisAlloc{addr: {Balance: big.NewInt(1000000000)}}, 1000000) + defer sim.Close() + + opts := bind.NewKeyedTransactor(key) + _, _, c, err := DeployNewFallbacks(opts, sim) + if err != nil { + t.Fatalf("Failed to deploy contract: %v", err) + } + sim.Commit() + + // Test receive function + opts.Value = big.NewInt(100) + c.Receive(opts) + sim.Commit() + + var gotEvent bool + iter, _ := c.FilterReceived(nil) + defer iter.Close() + for iter.Next() { + if iter.Event.Addr != addr { + t.Fatal("Msg.sender mismatch") + } + if iter.Event.Value.Uint64() != 100 { + t.Fatal("Msg.value mismatch") + } + gotEvent = true + break + } + if !gotEvent { + t.Fatal("Expect to receive event emitted by receive") + } + + // Test fallback function + opts.Value = nil + calldata := []byte{0x01, 0x02, 0x03} + c.Fallback(opts, calldata) + sim.Commit() + + iter2, _ := c.FilterFallback(nil) + defer iter2.Close() + for iter2.Next() { + if !bytes.Equal(iter2.Event.Data, calldata) { + t.Fatal("calldata mismatch") + } + gotEvent = true + break + } + if !gotEvent { + t.Fatal("Expect to receive event emitted by fallback") + } + `, + nil, + nil, + nil, + nil, + }, } // Tests that packages generated by the binder can be successfully compiled and @@ -1720,13 +1813,10 @@ package bindtest; import org.ethereum.geth.*; import java.util.*; - - public class Test { // ABI is the input ABI used to generate the binding from. public final static String ABI = "[{\"constant\":false,\"inputs\":[{\"name\":\"u16\",\"type\":\"uint16\"}],\"name\":\"setUint16\",\"outputs\":[{\"name\":\"\",\"type\":\"uint16\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b_a\",\"type\":\"bool[2]\"}],\"name\":\"setBoolArray\",\"outputs\":[{\"name\":\"\",\"type\":\"bool[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a_a\",\"type\":\"address[2]\"}],\"name\":\"setAddressArray\",\"outputs\":[{\"name\":\"\",\"type\":\"address[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs_l\",\"type\":\"bytes[]\"}],\"name\":\"setBytesList\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u8\",\"type\":\"uint8\"}],\"name\":\"setUint8\",\"outputs\":[{\"name\":\"\",\"type\":\"uint8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u32\",\"type\":\"uint32\"}],\"name\":\"setUint32\",\"outputs\":[{\"name\":\"\",\"type\":\"uint32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b\",\"type\":\"bool\"}],\"name\":\"setBool\",\"outputs\":[{\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256_l\",\"type\":\"int256[]\"}],\"name\":\"setInt256List\",\"outputs\":[{\"name\":\"\",\"type\":\"int256[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256_a\",\"type\":\"uint256[2]\"}],\"name\":\"setUint256Array\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b_l\",\"type\":\"bool[]\"}],\"name\":\"setBoolList\",\"outputs\":[{\"name\":\"\",\"type\":\"bool[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs_a\",\"type\":\"bytes[2]\"}],\"name\":\"setBytesArray\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a_l\",\"type\":\"address[]\"}],\"name\":\"setAddressList\",\"outputs\":[{\"name\":\"\",\"type\":\"address[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256_a\",\"type\":\"int256[2]\"}],\"name\":\"setInt256Array\",\"outputs\":[{\"name\":\"\",\"type\":\"int256[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s_a\",\"type\":\"string[2]\"}],\"name\":\"setStringArray\",\"outputs\":[{\"name\":\"\",\"type\":\"string[2]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s\",\"type\":\"string\"}],\"name\":\"setString\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u64\",\"type\":\"uint64\"}],\"name\":\"setUint64\",\"outputs\":[{\"name\":\"\",\"type\":\"uint64\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i16\",\"type\":\"int16\"}],\"name\":\"setInt16\",\"outputs\":[{\"name\":\"\",\"type\":\"int16\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i8\",\"type\":\"int8\"}],\"name\":\"setInt8\",\"outputs\":[{\"name\":\"\",\"type\":\"int8\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256_l\",\"type\":\"uint256[]\"}],\"name\":\"setUint256List\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i256\",\"type\":\"int256\"}],\"name\":\"setInt256\",\"outputs\":[{\"name\":\"\",\"type\":\"int256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i32\",\"type\":\"int32\"}],\"name\":\"setInt32\",\"outputs\":[{\"name\":\"\",\"type\":\"int32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b32\",\"type\":\"bytes32\"}],\"name\":\"setBytes32\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"s_l\",\"type\":\"string[]\"}],\"name\":\"setStringList\",\"outputs\":[{\"name\":\"\",\"type\":\"string[]\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"u256\",\"type\":\"uint256\"}],\"name\":\"setUint256\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"bs\",\"type\":\"bytes\"}],\"name\":\"setBytes\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"a\",\"type\":\"address\"}],\"name\":\"setAddress\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"i64\",\"type\":\"int64\"}],\"name\":\"setInt64\",\"outputs\":[{\"name\":\"\",\"type\":\"int64\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"b1\",\"type\":\"bytes1\"}],\"name\":\"setBytes1\",\"outputs\":[{\"name\":\"\",\"type\":\"bytes1\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]"; - // BYTECODE is the compiled bytecode used for deploying new contracts. public final static String BYTECODE = "0x608060405234801561001057600080fd5b5061265a806100206000396000f3fe608060405234801561001057600080fd5b50600436106101e1576000357c0100000000000000000000000000000000000000000000000000000000900480637fcaf66611610116578063c2b12a73116100b4578063da359dc81161008e578063da359dc814610666578063e30081a014610696578063e673eb32146106c6578063fba1a1c3146106f6576101e1565b8063c2b12a73146105d6578063c577796114610606578063d2282dc514610636576101e1565b80639a19a953116100f05780639a19a95314610516578063a0709e1914610546578063a53b1c1e14610576578063b7d5df31146105a6576101e1565b80637fcaf66614610486578063822cba69146104b657806386114cea146104e6576101e1565b806322722302116101835780635119655d1161015d5780635119655d146103c65780635be6b37e146103f65780636aa482fc146104265780637173b69514610456576101e1565b806322722302146103365780632766a755146103665780634d5ee6da14610396576101e1565b806316c105e2116101bf57806316c105e2146102765780631774e646146102a65780631c9352e2146102d65780631e26fd3314610306576101e1565b80630477988a146101e6578063118a971814610216578063151f547114610246575b600080fd5b61020060048036036101fb9190810190611599565b610726565b60405161020d9190611f01565b60405180910390f35b610230600480360361022b919081019061118d565b61072d565b60405161023d9190611ca6565b60405180910390f35b610260600480360361025b9190810190611123565b61073a565b60405161026d9190611c69565b60405180910390f35b610290600480360361028b9190810190611238565b610747565b60405161029d9190611d05565b60405180910390f35b6102c060048036036102bb919081019061163d565b61074e565b6040516102cd9190611f6d565b60405180910390f35b6102f060048036036102eb91908101906115eb565b610755565b6040516102fd9190611f37565b60405180910390f35b610320600480360361031b91908101906113cf565b61075c565b60405161032d9190611de5565b60405180910390f35b610350600480360361034b91908101906112a2565b610763565b60405161035d9190611d42565b60405180910390f35b610380600480360361037b9190810190611365565b61076a565b60405161038d9190611da8565b60405180910390f35b6103b060048036036103ab91908101906111b6565b610777565b6040516103bd9190611cc1565b60405180910390f35b6103e060048036036103db91908101906111f7565b61077e565b6040516103ed9190611ce3565b60405180910390f35b610410600480360361040b919081019061114c565b61078b565b60405161041d9190611c84565b60405180910390f35b610440600480360361043b9190810190611279565b610792565b60405161044d9190611d27565b60405180910390f35b610470600480360361046b91908101906112e3565b61079f565b60405161047d9190611d64565b60405180910390f35b6104a0600480360361049b9190810190611558565b6107ac565b6040516104ad9190611edf565b60405180910390f35b6104d060048036036104cb9190810190611614565b6107b3565b6040516104dd9190611f52565b60405180910390f35b61050060048036036104fb919081019061148b565b6107ba565b60405161050d9190611e58565b60405180910390f35b610530600480360361052b919081019061152f565b6107c1565b60405161053d9190611ec4565b60405180910390f35b610560600480360361055b919081019061138e565b6107c8565b60405161056d9190611dc3565b60405180910390f35b610590600480360361058b91908101906114b4565b6107cf565b60405161059d9190611e73565b60405180910390f35b6105c060048036036105bb91908101906114dd565b6107d6565b6040516105cd9190611e8e565b60405180910390f35b6105f060048036036105eb9190810190611421565b6107dd565b6040516105fd9190611e1b565b60405180910390f35b610620600480360361061b9190810190611324565b6107e4565b60405161062d9190611d86565b60405180910390f35b610650600480360361064b91908101906115c2565b6107eb565b60405161065d9190611f1c565b60405180910390f35b610680600480360361067b919081019061144a565b6107f2565b60405161068d9190611e36565b60405180910390f35b6106b060048036036106ab91908101906110fa565b6107f9565b6040516106bd9190611c4e565b60405180910390f35b6106e060048036036106db9190810190611506565b610800565b6040516106ed9190611ea9565b60405180910390f35b610710600480360361070b91908101906113f8565b610807565b60405161071d9190611e00565b60405180910390f35b6000919050565b61073561080e565b919050565b610742610830565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b610772610852565b919050565b6060919050565b610786610874565b919050565b6060919050565b61079a61089b565b919050565b6107a76108bd565b919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6060919050565b6000919050565b6060919050565b6000919050565b6000919050565b6000919050565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108835790505090565b6040805190810160405280600290602082028038833980820191505090505090565b60408051908101604052806002905b60608152602001906001900390816108cc5790505090565b60006108f082356124f2565b905092915050565b600082601f830112151561090b57600080fd5b600261091e61091982611fb5565b611f88565b9150818385602084028201111561093457600080fd5b60005b83811015610964578161094a88826108e4565b845260208401935060208301925050600181019050610937565b5050505092915050565b600082601f830112151561098157600080fd5b813561099461098f82611fd7565b611f88565b915081818352602084019350602081019050838560208402820111156109b957600080fd5b60005b838110156109e957816109cf88826108e4565b8452602084019350602083019250506001810190506109bc565b5050505092915050565b600082601f8301121515610a0657600080fd5b6002610a19610a1482611fff565b611f88565b91508183856020840282011115610a2f57600080fd5b60005b83811015610a5f5781610a458882610e9e565b845260208401935060208301925050600181019050610a32565b5050505092915050565b600082601f8301121515610a7c57600080fd5b8135610a8f610a8a82612021565b611f88565b91508181835260208401935060208101905083856020840282011115610ab457600080fd5b60005b83811015610ae45781610aca8882610e9e565b845260208401935060208301925050600181019050610ab7565b5050505092915050565b600082601f8301121515610b0157600080fd5b6002610b14610b0f82612049565b611f88565b9150818360005b83811015610b4b5781358601610b318882610eda565b845260208401935060208301925050600181019050610b1b565b5050505092915050565b600082601f8301121515610b6857600080fd5b8135610b7b610b768261206b565b611f88565b9150818183526020840193506020810190508360005b83811015610bc15781358601610ba78882610eda565b845260208401935060208301925050600181019050610b91565b5050505092915050565b600082601f8301121515610bde57600080fd5b6002610bf1610bec82612093565b611f88565b91508183856020840282011115610c0757600080fd5b60005b83811015610c375781610c1d8882610f9a565b845260208401935060208301925050600181019050610c0a565b5050505092915050565b600082601f8301121515610c5457600080fd5b8135610c67610c62826120b5565b611f88565b91508181835260208401935060208101905083856020840282011115610c8c57600080fd5b60005b83811015610cbc5781610ca28882610f9a565b845260208401935060208301925050600181019050610c8f565b5050505092915050565b600082601f8301121515610cd957600080fd5b6002610cec610ce7826120dd565b611f88565b9150818360005b83811015610d235781358601610d098882610fea565b845260208401935060208301925050600181019050610cf3565b5050505092915050565b600082601f8301121515610d4057600080fd5b8135610d53610d4e826120ff565b611f88565b9150818183526020840193506020810190508360005b83811015610d995781358601610d7f8882610fea565b845260208401935060208301925050600181019050610d69565b5050505092915050565b600082601f8301121515610db657600080fd5b6002610dc9610dc482612127565b611f88565b91508183856020840282011115610ddf57600080fd5b60005b83811015610e0f5781610df588826110aa565b845260208401935060208301925050600181019050610de2565b5050505092915050565b600082601f8301121515610e2c57600080fd5b8135610e3f610e3a82612149565b611f88565b91508181835260208401935060208101905083856020840282011115610e6457600080fd5b60005b83811015610e945781610e7a88826110aa565b845260208401935060208301925050600181019050610e67565b5050505092915050565b6000610eaa8235612504565b905092915050565b6000610ebe8235612510565b905092915050565b6000610ed2823561253c565b905092915050565b600082601f8301121515610eed57600080fd5b8135610f00610efb82612171565b611f88565b91508082526020830160208301858383011115610f1c57600080fd5b610f278382846125cd565b50505092915050565b600082601f8301121515610f4357600080fd5b8135610f56610f518261219d565b611f88565b91508082526020830160208301858383011115610f7257600080fd5b610f7d8382846125cd565b50505092915050565b6000610f928235612546565b905092915050565b6000610fa68235612553565b905092915050565b6000610fba823561255d565b905092915050565b6000610fce823561256a565b905092915050565b6000610fe28235612577565b905092915050565b600082601f8301121515610ffd57600080fd5b813561101061100b826121c9565b611f88565b9150808252602083016020830185838301111561102c57600080fd5b6110378382846125cd565b50505092915050565b600082601f830112151561105357600080fd5b8135611066611061826121f5565b611f88565b9150808252602083016020830185838301111561108257600080fd5b61108d8382846125cd565b50505092915050565b60006110a28235612584565b905092915050565b60006110b68235612592565b905092915050565b60006110ca823561259c565b905092915050565b60006110de82356125ac565b905092915050565b60006110f282356125c0565b905092915050565b60006020828403121561110c57600080fd5b600061111a848285016108e4565b91505092915050565b60006040828403121561113557600080fd5b6000611143848285016108f8565b91505092915050565b60006020828403121561115e57600080fd5b600082013567ffffffffffffffff81111561117857600080fd5b6111848482850161096e565b91505092915050565b60006040828403121561119f57600080fd5b60006111ad848285016109f3565b91505092915050565b6000602082840312156111c857600080fd5b600082013567ffffffffffffffff8111156111e257600080fd5b6111ee84828501610a69565b91505092915050565b60006020828403121561120957600080fd5b600082013567ffffffffffffffff81111561122357600080fd5b61122f84828501610aee565b91505092915050565b60006020828403121561124a57600080fd5b600082013567ffffffffffffffff81111561126457600080fd5b61127084828501610b55565b91505092915050565b60006040828403121561128b57600080fd5b600061129984828501610bcb565b91505092915050565b6000602082840312156112b457600080fd5b600082013567ffffffffffffffff8111156112ce57600080fd5b6112da84828501610c41565b91505092915050565b6000602082840312156112f557600080fd5b600082013567ffffffffffffffff81111561130f57600080fd5b61131b84828501610cc6565b91505092915050565b60006020828403121561133657600080fd5b600082013567ffffffffffffffff81111561135057600080fd5b61135c84828501610d2d565b91505092915050565b60006040828403121561137757600080fd5b600061138584828501610da3565b91505092915050565b6000602082840312156113a057600080fd5b600082013567ffffffffffffffff8111156113ba57600080fd5b6113c684828501610e19565b91505092915050565b6000602082840312156113e157600080fd5b60006113ef84828501610e9e565b91505092915050565b60006020828403121561140a57600080fd5b600061141884828501610eb2565b91505092915050565b60006020828403121561143357600080fd5b600061144184828501610ec6565b91505092915050565b60006020828403121561145c57600080fd5b600082013567ffffffffffffffff81111561147657600080fd5b61148284828501610f30565b91505092915050565b60006020828403121561149d57600080fd5b60006114ab84828501610f86565b91505092915050565b6000602082840312156114c657600080fd5b60006114d484828501610f9a565b91505092915050565b6000602082840312156114ef57600080fd5b60006114fd84828501610fae565b91505092915050565b60006020828403121561151857600080fd5b600061152684828501610fc2565b91505092915050565b60006020828403121561154157600080fd5b600061154f84828501610fd6565b91505092915050565b60006020828403121561156a57600080fd5b600082013567ffffffffffffffff81111561158457600080fd5b61159084828501611040565b91505092915050565b6000602082840312156115ab57600080fd5b60006115b984828501611096565b91505092915050565b6000602082840312156115d457600080fd5b60006115e2848285016110aa565b91505092915050565b6000602082840312156115fd57600080fd5b600061160b848285016110be565b91505092915050565b60006020828403121561162657600080fd5b6000611634848285016110d2565b91505092915050565b60006020828403121561164f57600080fd5b600061165d848285016110e6565b91505092915050565b61166f816123f7565b82525050565b61167e816122ab565b61168782612221565b60005b828110156116b95761169d858351611666565b6116a68261235b565b915060208501945060018101905061168a565b5050505050565b60006116cb826122b6565b8084526020840193506116dd8361222b565b60005b8281101561170f576116f3868351611666565b6116fc82612368565b91506020860195506001810190506116e0565b50849250505092915050565b611724816122c1565b61172d82612238565b60005b8281101561175f57611743858351611ab3565b61174c82612375565b9150602085019450600181019050611730565b5050505050565b6000611771826122cc565b80845260208401935061178383612242565b60005b828110156117b557611799868351611ab3565b6117a282612382565b9150602086019550600181019050611786565b50849250505092915050565b60006117cc826122d7565b836020820285016117dc8561224f565b60005b848110156118155783830388526117f7838351611b16565b92506118028261238f565b91506020880197506001810190506117df565b508196508694505050505092915050565b6000611831826122e2565b8084526020840193508360208202850161184a85612259565b60005b84811015611883578383038852611865838351611b16565b92506118708261239c565b915060208801975060018101905061184d565b508196508694505050505092915050565b61189d816122ed565b6118a682612266565b60005b828110156118d8576118bc858351611b5b565b6118c5826123a9565b91506020850194506001810190506118a9565b5050505050565b60006118ea826122f8565b8084526020840193506118fc83612270565b60005b8281101561192e57611912868351611b5b565b61191b826123b6565b91506020860195506001810190506118ff565b50849250505092915050565b600061194582612303565b836020820285016119558561227d565b60005b8481101561198e578383038852611970838351611bcd565b925061197b826123c3565b9150602088019750600181019050611958565b508196508694505050505092915050565b60006119aa8261230e565b808452602084019350836020820285016119c385612287565b60005b848110156119fc5783830388526119de838351611bcd565b92506119e9826123d0565b91506020880197506001810190506119c6565b508196508694505050505092915050565b611a1681612319565b611a1f82612294565b60005b82811015611a5157611a35858351611c12565b611a3e826123dd565b9150602085019450600181019050611a22565b5050505050565b6000611a6382612324565b808452602084019350611a758361229e565b60005b82811015611aa757611a8b868351611c12565b611a94826123ea565b9150602086019550600181019050611a78565b50849250505092915050565b611abc81612409565b82525050565b611acb81612415565b82525050565b611ada81612441565b82525050565b6000611aeb8261233a565b808452611aff8160208601602086016125dc565b611b088161260f565b602085010191505092915050565b6000611b218261232f565b808452611b358160208601602086016125dc565b611b3e8161260f565b602085010191505092915050565b611b558161244b565b82525050565b611b6481612458565b82525050565b611b7381612462565b82525050565b611b828161246f565b82525050565b611b918161247c565b82525050565b6000611ba282612350565b808452611bb68160208601602086016125dc565b611bbf8161260f565b602085010191505092915050565b6000611bd882612345565b808452611bec8160208601602086016125dc565b611bf58161260f565b602085010191505092915050565b611c0c81612489565b82525050565b611c1b816124b7565b82525050565b611c2a816124c1565b82525050565b611c39816124d1565b82525050565b611c48816124e5565b82525050565b6000602082019050611c636000830184611666565b92915050565b6000604082019050611c7e6000830184611675565b92915050565b60006020820190508181036000830152611c9e81846116c0565b905092915050565b6000604082019050611cbb600083018461171b565b92915050565b60006020820190508181036000830152611cdb8184611766565b905092915050565b60006020820190508181036000830152611cfd81846117c1565b905092915050565b60006020820190508181036000830152611d1f8184611826565b905092915050565b6000604082019050611d3c6000830184611894565b92915050565b60006020820190508181036000830152611d5c81846118df565b905092915050565b60006020820190508181036000830152611d7e818461193a565b905092915050565b60006020820190508181036000830152611da0818461199f565b905092915050565b6000604082019050611dbd6000830184611a0d565b92915050565b60006020820190508181036000830152611ddd8184611a58565b905092915050565b6000602082019050611dfa6000830184611ab3565b92915050565b6000602082019050611e156000830184611ac2565b92915050565b6000602082019050611e306000830184611ad1565b92915050565b60006020820190508181036000830152611e508184611ae0565b905092915050565b6000602082019050611e6d6000830184611b4c565b92915050565b6000602082019050611e886000830184611b5b565b92915050565b6000602082019050611ea36000830184611b6a565b92915050565b6000602082019050611ebe6000830184611b79565b92915050565b6000602082019050611ed96000830184611b88565b92915050565b60006020820190508181036000830152611ef98184611b97565b905092915050565b6000602082019050611f166000830184611c03565b92915050565b6000602082019050611f316000830184611c12565b92915050565b6000602082019050611f4c6000830184611c21565b92915050565b6000602082019050611f676000830184611c30565b92915050565b6000602082019050611f826000830184611c3f565b92915050565b6000604051905081810181811067ffffffffffffffff82111715611fab57600080fd5b8060405250919050565b600067ffffffffffffffff821115611fcc57600080fd5b602082029050919050565b600067ffffffffffffffff821115611fee57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561201657600080fd5b602082029050919050565b600067ffffffffffffffff82111561203857600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561206057600080fd5b602082029050919050565b600067ffffffffffffffff82111561208257600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120aa57600080fd5b602082029050919050565b600067ffffffffffffffff8211156120cc57600080fd5b602082029050602081019050919050565b600067ffffffffffffffff8211156120f457600080fd5b602082029050919050565b600067ffffffffffffffff82111561211657600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561213e57600080fd5b602082029050919050565b600067ffffffffffffffff82111561216057600080fd5b602082029050602081019050919050565b600067ffffffffffffffff82111561218857600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121b457600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff8211156121e057600080fd5b601f19601f8301169050602081019050919050565b600067ffffffffffffffff82111561220c57600080fd5b601f19601f8301169050602081019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b6000819050919050565b6000602082019050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600060029050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b600081519050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b6000602082019050919050565b600061240282612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b60006124fd82612497565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60008160010b9050919050565b6000819050919050565b60008160030b9050919050565b60008160070b9050919050565b60008160000b9050919050565b600061ffff82169050919050565b6000819050919050565b600063ffffffff82169050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b82818337600083830152505050565b60005b838110156125fa5780820151818401526020810190506125df565b83811115612609576000848401525b50505050565b6000601f19601f830116905091905056fea265627a7a723058206fe37171cf1b10ebd291cfdca61d67e7fc3c208795e999c833c42a14d86cf00d6c6578706572696d656e74616cf50037"; @@ -1734,8 +1824,6 @@ public class Test { public static Test deploy(TransactOpts auth, EthereumClient client) throws Exception { Interfaces args = Geth.newInterfaces(0); String bytecode = BYTECODE; - - return new Test(Geth.deployContract(auth, ABI, Geth.decodeFromHex(bytecode), client, args)); } @@ -1746,7 +1834,6 @@ public class Test { this.Contract = deployment; } - // Ethereum address where this contract is located at. public final Address Address; @@ -1761,9 +1848,6 @@ public class Test { this(Geth.bindContract(address, ABI, client)); } - - - // setAddress is a paid mutator transaction binding the contract method 0xe30081a0. // // Solidity: function setAddress(address a) returns(address) @@ -2043,9 +2127,7 @@ public class Test { return this.Contract.transact(opts, "setUint8" , args); } - } - `, }, } @@ -2054,7 +2136,22 @@ public class Test { if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } - if binding != c.expected { + // Remove empty lines + removeEmptys := func(input string) string { + lines := strings.Split(input, "\n") + var index int + for _, line := range lines { + if strings.TrimSpace(line) != "" { + lines[index] = line + index += 1 + } + } + lines = lines[:index] + return strings.Join(lines, "\n") + } + binding = removeEmptys(binding) + expect := removeEmptys(c.expected) + if binding != expect { t.Fatalf("test %d: generated binding mismatch, has %s, want %s", i, binding, c.expected) } } diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index c96dd1b995..c361a49f84 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -35,6 +35,8 @@ type tmplContract struct { Constructor abi.Method // Contract constructor for deploy parametrization Calls map[string]*tmplMethod // Contract calls that only read state data Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function Events map[string]*tmplEvent // Contract events accessors Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs Library bool // Indicator whether the contract is a library @@ -351,6 +353,52 @@ var ( } {{end}} + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{formatmethod .Fallback.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Fallback(opts *bind.TransactOpts, calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{formatmethod .Fallback.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{formatmethod .Fallback.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Fallback(calldata []byte) (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Fallback(&_{{$contract.Type}}.TransactOpts, calldata) + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{formatmethod .Receive.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}Transactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) { + return _{{$contract.Type}}.contract.RawTransact(opts, nil) // calldata is disallowed for receive function + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{formatmethod .Receive.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}Session) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{formatmethod .Receive.Original $structs}} + func (_{{$contract.Type}} *{{$contract.Type}}TransactorSession) Receive() (*types.Transaction, error) { + return _{{$contract.Type}}.Contract.Receive(&_{{$contract.Type}}.TransactOpts) + } + {{end}} + {{range .Events}} // {{$contract.Type}}{{.Normalized.Name}}Iterator is returned from Filter{{.Normalized.Name}} and is used to iterate over the raw logs and unpacked data for {{.Normalized.Name}} events raised by the {{$contract.Type}} contract. type {{$contract.Type}}{{.Normalized.Name}}Iterator struct { @@ -611,6 +659,24 @@ import java.util.*; return this.Contract.transact(opts, "{{.Original.Name}}" , args); } {{end}} + + {{if .Fallback}} + // Fallback is a paid mutator transaction binding the contract fallback function. + // + // Solidity: {{formatmethod .Fallback.Original $structs}} + public Transaction Fallback(TransactOpts opts, byte[] calldata) throws Exception { + return this.Contract.rawTransact(opts, calldata); + } + {{end}} + + {{if .Receive}} + // Receive is a paid mutator transaction binding the contract receive function. + // + // Solidity: {{formatmethod .Receive.Original $structs}} + public Transaction Receive(TransactOpts opts) throws Exception { + return this.Contract.rawTransact(opts, null); + } + {{end}} } {{end}} ` diff --git a/accounts/abi/method.go b/accounts/abi/method.go index 7da2e18fc6..217c3d2e68 100644 --- a/accounts/abi/method.go +++ b/accounts/abi/method.go @@ -41,10 +41,23 @@ type Method struct { // * foo(uint,uint) // The method name of the first one will be resolved as foo while the second one // will be resolved as foo0. - Name string - // RawName is the raw method name parsed from ABI. - RawName string - Const bool + Name string + RawName string // RawName is the raw method name parsed from ABI + + // StateMutability indicates the mutability state of method, + // the default value is nonpayable. It can be empty if the abi + // is generated by legacy compiler. + StateMutability string + + // Legacy indicators generated by compiler before v0.6.0 + Constant bool + Payable bool + + // The following two flags indicates whether the method is a + // special fallback introduced in solidity v0.6.0 + IsFallback bool + IsReceive bool + Inputs Arguments Outputs Arguments } @@ -57,6 +70,11 @@ type Method struct { // // Please note that "int" is substitute for its canonical representation "int256" func (method Method) Sig() string { + // Short circuit if the method is special. Fallback + // and Receive don't have signature at all. + if method.IsFallback || method.IsReceive { + return "" + } types := make([]string, len(method.Inputs)) for i, input := range method.Inputs { types[i] = input.Type.String() @@ -76,11 +94,22 @@ func (method Method) String() string { outputs[i] += fmt.Sprintf(" %v", output.Name) } } - constant := "" - if method.Const { - constant = "constant " + // Extract meaningful state mutability of solidity method. + // If it's default value, never print it. + state := method.StateMutability + if state == "nonpayable" { + state = "" } - return fmt.Sprintf("function %v(%v) %sreturns(%v)", method.RawName, strings.Join(inputs, ", "), constant, strings.Join(outputs, ", ")) + if state != "" { + state = state + " " + } + identity := fmt.Sprintf("function %v", method.RawName) + if method.IsFallback { + identity = "fallback" + } else if method.IsReceive { + identity = "receive" + } + return fmt.Sprintf("%v(%v) %sreturns(%v)", identity, strings.Join(inputs, ", "), state, strings.Join(outputs, ", ")) } // ID returns the canonical representation of the method's signature used by the @@ -88,3 +117,14 @@ func (method Method) String() string { func (method Method) ID() []byte { return crypto.Keccak256([]byte(method.Sig()))[:4] } + +// IsConstant returns the indicator whether the method is read-only. +func (method Method) IsConstant() bool { + return method.StateMutability == "view" || method.StateMutability == "pure" || method.Constant +} + +// IsPayable returns the indicator whether the method can process +// plain ether transfers. +func (method Method) IsPayable() bool { + return method.StateMutability == "payable" || method.Payable +} diff --git a/accounts/abi/method_test.go b/accounts/abi/method_test.go index 3ffdb702b3..ea176bf4e0 100644 --- a/accounts/abi/method_test.go +++ b/accounts/abi/method_test.go @@ -23,13 +23,15 @@ import ( const methoddata = ` [ - {"type": "function", "name": "balance", "constant": true }, - {"type": "function", "name": "send", "constant": false, "inputs": [{ "name": "amount", "type": "uint256" }]}, - {"type": "function", "name": "transfer", "constant": false, "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]}, + {"type": "function", "name": "balance", "stateMutability": "view"}, + {"type": "function", "name": "send", "inputs": [{ "name": "amount", "type": "uint256" }]}, + {"type": "function", "name": "transfer", "inputs": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}], "outputs": [{"name": "success", "type": "bool"}]}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple"}],"name":"tuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[]"}],"name":"tupleSlice","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5]"}],"name":"tupleArray","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, - {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"} + {"constant":false,"inputs":[{"components":[{"name":"x","type":"uint256"},{"name":"y","type":"uint256"}],"name":"a","type":"tuple[5][]"}],"name":"complexTuple","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}, + {"stateMutability":"nonpayable","type":"fallback"}, + {"stateMutability":"payable","type":"receive"} ]` func TestMethodString(t *testing.T) { @@ -39,7 +41,7 @@ func TestMethodString(t *testing.T) { }{ { method: "balance", - expectation: "function balance() constant returns()", + expectation: "function balance() view returns()", }, { method: "send", @@ -65,6 +67,14 @@ func TestMethodString(t *testing.T) { method: "complexTuple", expectation: "function complexTuple((uint256,uint256)[5][] a) returns()", }, + { + method: "fallback", + expectation: "fallback() returns()", + }, + { + method: "receive", + expectation: "receive() payable returns()", + }, } abi, err := JSON(strings.NewReader(methoddata)) @@ -73,7 +83,14 @@ func TestMethodString(t *testing.T) { } for _, test := range table { - got := abi.Methods[test.method].String() + var got string + if test.method == "fallback" { + got = abi.Fallback.String() + } else if test.method == "receive" { + got = abi.Receive.String() + } else { + got = abi.Methods[test.method].String() + } if got != test.expectation { t.Errorf("expected string to be %s, got %s", test.expectation, got) } diff --git a/accounts/abi/unpack_test.go b/accounts/abi/unpack_test.go index ab343042b5..0622e0adce 100644 --- a/accounts/abi/unpack_test.go +++ b/accounts/abi/unpack_test.go @@ -443,7 +443,7 @@ var unpackTests = []unpackTest{ func TestUnpack(t *testing.T) { for i, test := range unpackTests { t.Run(strconv.Itoa(i), func(t *testing.T) { - def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) if err != nil { t.Fatalf("invalid ABI definition %s: %v", def, err) @@ -522,7 +522,7 @@ type methodMultiOutput struct { func methodMultiReturn(require *require.Assertions) (ABI, []byte, methodMultiOutput) { const definition = `[ - { "name" : "multi", "constant" : false, "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` + { "name" : "multi", "type": "function", "outputs": [ { "name": "Int", "type": "uint256" }, { "name": "String", "type": "string" } ] }]` var expected = methodMultiOutput{big.NewInt(1), "hello"} abi, err := JSON(strings.NewReader(definition)) @@ -611,7 +611,7 @@ func TestMethodMultiReturn(t *testing.T) { } func TestMultiReturnWithArray(t *testing.T) { - const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` + const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3]"}, {"type": "uint64"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) @@ -634,7 +634,7 @@ func TestMultiReturnWithArray(t *testing.T) { } func TestMultiReturnWithStringArray(t *testing.T) { - const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` + const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) @@ -664,7 +664,7 @@ func TestMultiReturnWithStringArray(t *testing.T) { } func TestMultiReturnWithStringSlice(t *testing.T) { - const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` + const definition = `[{"name" : "multi", "type": "function", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) @@ -700,7 +700,7 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { // values of nested static arrays count towards the size as well, and any element following // after such nested array argument should be read with the correct offset, // so that it does not read content from the previous array argument. - const definition = `[{"name" : "multi", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` + const definition = `[{"name" : "multi", "type": "function", "outputs": [{"type": "uint64[3][2][4]"}, {"type": "uint64"}]}]` abi, err := JSON(strings.NewReader(definition)) if err != nil { t.Fatal(err) @@ -737,15 +737,15 @@ func TestMultiReturnWithDeeplyNestedArray(t *testing.T) { func TestUnmarshal(t *testing.T) { const definition = `[ - { "name" : "int", "constant" : false, "outputs": [ { "type": "uint256" } ] }, - { "name" : "bool", "constant" : false, "outputs": [ { "type": "bool" } ] }, - { "name" : "bytes", "constant" : false, "outputs": [ { "type": "bytes" } ] }, - { "name" : "fixed", "constant" : false, "outputs": [ { "type": "bytes32" } ] }, - { "name" : "multi", "constant" : false, "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, - { "name" : "intArraySingle", "constant" : false, "outputs": [ { "type": "uint256[3]" } ] }, - { "name" : "addressSliceSingle", "constant" : false, "outputs": [ { "type": "address[]" } ] }, - { "name" : "addressSliceDouble", "constant" : false, "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, - { "name" : "mixedBytes", "constant" : true, "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` + { "name" : "int", "type": "function", "outputs": [ { "type": "uint256" } ] }, + { "name" : "bool", "type": "function", "outputs": [ { "type": "bool" } ] }, + { "name" : "bytes", "type": "function", "outputs": [ { "type": "bytes" } ] }, + { "name" : "fixed", "type": "function", "outputs": [ { "type": "bytes32" } ] }, + { "name" : "multi", "type": "function", "outputs": [ { "type": "bytes" }, { "type": "bytes" } ] }, + { "name" : "intArraySingle", "type": "function", "outputs": [ { "type": "uint256[3]" } ] }, + { "name" : "addressSliceSingle", "type": "function", "outputs": [ { "type": "address[]" } ] }, + { "name" : "addressSliceDouble", "type": "function", "outputs": [ { "name": "a", "type": "address[]" }, { "name": "b", "type": "address[]" } ] }, + { "name" : "mixedBytes", "type": "function", "stateMutability" : "view", "outputs": [ { "name": "a", "type": "bytes" }, { "name": "b", "type": "bytes32" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -985,7 +985,7 @@ func TestUnmarshal(t *testing.T) { } func TestUnpackTuple(t *testing.T) { - const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` + const simpleTuple = `[{"name":"tuple","type":"function","outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]` abi, err := JSON(strings.NewReader(simpleTuple)) if err != nil { t.Fatal(err) @@ -1014,7 +1014,7 @@ func TestUnpackTuple(t *testing.T) { } // Test nested tuple - const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[ + const nestedTuple = `[{"name":"tuple","type":"function","outputs":[ {"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]}, {"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}, {"type":"uint256","name":"a"} @@ -1136,7 +1136,7 @@ func TestOOMMaliciousInput(t *testing.T) { }, } for i, test := range oomTests { - def := fmt.Sprintf(`[{ "name" : "method", "outputs": %s}]`, test.def) + def := fmt.Sprintf(`[{ "name" : "method", "type": "function", "outputs": %s}]`, test.def) abi, err := JSON(strings.NewReader(def)) if err != nil { t.Fatalf("invalid ABI definition %s: %v", def, err) diff --git a/les/utils/expiredvalue.go b/les/utils/expiredvalue.go index 2d6e5d4bb8..85f9b88b7b 100644 --- a/les/utils/expiredvalue.go +++ b/les/utils/expiredvalue.go @@ -92,7 +92,10 @@ func (e *ExpiredValue) Add(amount int64, logOffset Fixed64) int64 { e.Exp = integer } if base >= 0 || uint64(-base) <= e.Base { - e.Base += uint64(base) + // This is a temporary fix to circumvent a golang + // uint conversion issue on arm64, which needs to + // be investigated further. FIXME + e.Base = uint64(int64(e.Base) + int64(base)) return amount } net := int64(-float64(e.Base) / factor) diff --git a/les/utils/expiredvalue_test.go b/les/utils/expiredvalue_test.go index 4894a12b12..fa22d58274 100644 --- a/les/utils/expiredvalue_test.go +++ b/les/utils/expiredvalue_test.go @@ -16,7 +16,9 @@ package utils -import "testing" +import ( + "testing" +) func TestValueExpiration(t *testing.T) { var cases = []struct { diff --git a/mobile/bind.go b/mobile/bind.go index dc3f32b152..9b4c0bb0e5 100644 --- a/mobile/bind.go +++ b/mobile/bind.go @@ -197,6 +197,15 @@ func (c *BoundContract) Transact(opts *TransactOpts, method string, args *Interf return &Transaction{rawTx}, nil } +// RawTransact invokes the (paid) contract method with raw calldata as input values. +func (c *BoundContract) RawTransact(opts *TransactOpts, calldata []byte) (tx *Transaction, _ error) { + rawTx, err := c.contract.RawTransact(&opts.opts, calldata) + if err != nil { + return nil, err + } + return &Transaction{rawTx}, nil +} + // Transfer initiates a plain transaction to move funds to the contract, calling // its default method if one is available. func (c *BoundContract) Transfer(opts *TransactOpts) (tx *Transaction, _ error) { From 6402c42b6748c0e8494e6432f40fe4527650076b Mon Sep 17 00:00:00 2001 From: Martin Holst Swende Date: Wed, 15 Apr 2020 13:08:53 +0200 Subject: [PATCH 101/103] all: simplify and fix database iteration with prefix/start (#20808) * core/state/snapshot: start fixing disk iterator seek * ethdb, rawdb, leveldb, memorydb: implement iterators with prefix and start * les, core/state/snapshot: iterator fixes * all: remove two iterator methods * all: rename Iteratee.NewIteratorWith -> NewIterator * ethdb: fix review concerns --- cmd/utils/cmd.go | 2 +- common/bytes.go | 11 ++++ common/bytes_test.go | 19 +++++++ core/rawdb/accessors_chain.go | 2 +- core/rawdb/accessors_snapshot.go | 2 +- core/rawdb/database.go | 2 +- core/rawdb/table.go | 27 +++------- core/rawdb/table_test.go | 67 +++++++++-------------- core/state/iterator_test.go | 2 +- core/state/snapshot/disklayer_test.go | 76 +++++++++++++++++++++++++++ core/state/snapshot/iterator.go | 4 +- core/state/snapshot/wipe.go | 5 +- core/state/snapshot/wipe_test.go | 10 ++-- core/state/statedb_test.go | 6 +-- eth/filters/bench_test.go | 2 +- eth/handler_test.go | 2 +- ethdb/dbtest/testsuite.go | 73 ++++++++++++++----------- ethdb/iterator.go | 19 +++---- ethdb/leveldb/leveldb.go | 31 +++++------ ethdb/memorydb/memorydb.go | 49 ++++------------- les/clientpool.go | 14 ++++- trie/iterator_test.go | 4 +- trie/proof_test.go | 2 +- trie/sync_bloom.go | 4 +- 24 files changed, 248 insertions(+), 187 deletions(-) diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index a3ee45ba7f..a40978bcb7 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -301,7 +301,7 @@ func ExportPreimages(db ethdb.Database, fn string) error { defer writer.(*gzip.Writer).Close() } // Iterate over the preimages and export them - it := db.NewIteratorWithPrefix([]byte("secure-key-")) + it := db.NewIterator([]byte("secure-key-"), nil) defer it.Release() for it.Next() { diff --git a/common/bytes.go b/common/bytes.go index fa457b92cf..634041804d 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -145,3 +145,14 @@ func TrimLeftZeroes(s []byte) []byte { } return s[idx:] } + +// TrimRightZeroes returns a subslice of s without trailing zeroes +func TrimRightZeroes(s []byte) []byte { + idx := len(s) + for ; idx > 0; idx-- { + if s[idx-1] != 0 { + break + } + } + return s[:idx] +} diff --git a/common/bytes_test.go b/common/bytes_test.go index 7cf6553377..0e3ec974ee 100644 --- a/common/bytes_test.go +++ b/common/bytes_test.go @@ -105,3 +105,22 @@ func TestNoPrefixShortHexOddLength(t *testing.T) { t.Errorf("Expected %x got %x", expected, result) } } + +func TestTrimRightZeroes(t *testing.T) { + tests := []struct { + arr []byte + exp []byte + }{ + {FromHex("0x00ffff00ff0000"), FromHex("0x00ffff00ff")}, + {FromHex("0x00000000000000"), []byte{}}, + {FromHex("0xff"), FromHex("0xff")}, + {[]byte{}, []byte{}}, + {FromHex("0x00ffffffffffff"), FromHex("0x00ffffffffffff")}, + } + for i, test := range tests { + got := TrimRightZeroes(test.arr) + if !bytes.Equal(got, test.exp) { + t.Errorf("test %d, got %x exp %x", i, got, test.exp) + } + } +} diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 4d064a3390..9fa327062a 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -69,7 +69,7 @@ func ReadAllHashes(db ethdb.Iteratee, number uint64) []common.Hash { prefix := headerKeyPrefix(number) hashes := make([]common.Hash, 0, 1) - it := db.NewIteratorWithPrefix(prefix) + it := db.NewIterator(prefix, nil) defer it.Release() for it.Next() { diff --git a/core/rawdb/accessors_snapshot.go b/core/rawdb/accessors_snapshot.go index 3a8d6c779e..ecd4e65978 100644 --- a/core/rawdb/accessors_snapshot.go +++ b/core/rawdb/accessors_snapshot.go @@ -93,7 +93,7 @@ func DeleteStorageSnapshot(db ethdb.KeyValueWriter, accountHash, storageHash com // IterateStorageSnapshots returns an iterator for walking the entire storage // space of a specific account. func IterateStorageSnapshots(db ethdb.Iteratee, accountHash common.Hash) ethdb.Iterator { - return db.NewIteratorWithPrefix(storageSnapshotsKey(accountHash)) + return db.NewIterator(storageSnapshotsKey(accountHash), nil) } // ReadSnapshotJournal retrieves the serialized in-memory diff layers saved at diff --git a/core/rawdb/database.go b/core/rawdb/database.go index b74d8e2e39..cc05491b84 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -221,7 +221,7 @@ func NewLevelDBDatabaseWithFreezer(file string, cache int, handles int, freezer // InspectDatabase traverses the entire database and checks the size // of all different categories of data. func InspectDatabase(db ethdb.Database) error { - it := db.NewIterator() + it := db.NewIterator(nil, nil) defer it.Release() var ( diff --git a/core/rawdb/table.go b/core/rawdb/table.go index 092341fbfe..323ef6293c 100644 --- a/core/rawdb/table.go +++ b/core/rawdb/table.go @@ -103,27 +103,12 @@ func (t *table) Delete(key []byte) error { return t.db.Delete(append([]byte(t.prefix), key...)) } -// NewIterator creates a binary-alphabetical iterator over the entire keyspace -// contained within the database. -func (t *table) NewIterator() ethdb.Iterator { - return t.NewIteratorWithPrefix(nil) -} - -// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of -// database content starting at a particular initial key (or after, if it does -// not exist). -func (t *table) NewIteratorWithStart(start []byte) ethdb.Iterator { - iter := t.db.NewIteratorWithStart(append([]byte(t.prefix), start...)) - return &tableIterator{ - iter: iter, - prefix: t.prefix, - } -} - -// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset -// of database content with a particular key prefix. -func (t *table) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator { - iter := t.db.NewIteratorWithPrefix(append([]byte(t.prefix), prefix...)) +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (t *table) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + innerPrefix := append([]byte(t.prefix), prefix...) + iter := t.db.NewIterator(innerPrefix, start) return &tableIterator{ iter: iter, prefix: t.prefix, diff --git a/core/rawdb/table_test.go b/core/rawdb/table_test.go index 977eb4bf05..aa6adf3e72 100644 --- a/core/rawdb/table_test.go +++ b/core/rawdb/table_test.go @@ -19,6 +19,8 @@ package rawdb import ( "bytes" "testing" + + "github.com/ethereum/go-ethereum/ethdb" ) func TestTableDatabase(t *testing.T) { testTableDatabase(t, "prefix") } @@ -96,48 +98,31 @@ func testTableDatabase(t *testing.T, prefix string) { } } + check := func(iter ethdb.Iterator, expCount, index int) { + count := 0 + for iter.Next() { + key, value := iter.Key(), iter.Value() + if !bytes.Equal(key, entries[index].key) { + t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) + } + if !bytes.Equal(value, entries[index].value) { + t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) + } + index += 1 + count++ + } + if count != expCount { + t.Fatalf("Wrong number of elems, exp %d got %d", expCount, count) + } + iter.Release() + } // Test iterators - iter := db.NewIterator() - var index int - for iter.Next() { - key, value := iter.Key(), iter.Value() - if !bytes.Equal(key, entries[index].key) { - t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) - } - if !bytes.Equal(value, entries[index].value) { - t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) - } - index += 1 - } - iter.Release() - + check(db.NewIterator(nil, nil), 6, 0) // Test iterators with prefix - iter = db.NewIteratorWithPrefix([]byte{0xff, 0xff}) - index = 3 - for iter.Next() { - key, value := iter.Key(), iter.Value() - if !bytes.Equal(key, entries[index].key) { - t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) - } - if !bytes.Equal(value, entries[index].value) { - t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) - } - index += 1 - } - iter.Release() - + check(db.NewIterator([]byte{0xff, 0xff}, nil), 3, 3) // Test iterators with start point - iter = db.NewIteratorWithStart([]byte{0xff, 0xff, 0x02}) - index = 4 - for iter.Next() { - key, value := iter.Key(), iter.Value() - if !bytes.Equal(key, entries[index].key) { - t.Fatalf("Key mismatch: want=%v, got=%v", entries[index].key, key) - } - if !bytes.Equal(value, entries[index].value) { - t.Fatalf("Value mismatch: want=%v, got=%v", entries[index].value, value) - } - index += 1 - } - iter.Release() + check(db.NewIterator(nil, []byte{0xff, 0xff, 0x02}), 2, 4) + // Test iterators with prefix and start point + check(db.NewIterator([]byte{0xee}, nil), 0, 0) + check(db.NewIterator(nil, []byte{0x00}), 6, 0) } diff --git a/core/state/iterator_test.go b/core/state/iterator_test.go index e9946e9b31..5060f7a651 100644 --- a/core/state/iterator_test.go +++ b/core/state/iterator_test.go @@ -51,7 +51,7 @@ func TestNodeIteratorCoverage(t *testing.T) { t.Errorf("state entry not reported %x", hash) } } - it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator() + it := db.TrieDB().DiskDB().(ethdb.Database).NewIterator(nil, nil) for it.Next() { key := it.Key() if bytes.HasPrefix(key, []byte("secure-key-")) { diff --git a/core/state/snapshot/disklayer_test.go b/core/state/snapshot/disklayer_test.go index aae2aa6b56..15f3a6b1fa 100644 --- a/core/state/snapshot/disklayer_test.go +++ b/core/state/snapshot/disklayer_test.go @@ -18,11 +18,15 @@ package snapshot import ( "bytes" + "io/ioutil" + "os" "testing" "github.com/VictoriaMetrics/fastcache" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/ethdb/leveldb" "github.com/ethereum/go-ethereum/ethdb/memorydb" ) @@ -432,4 +436,76 @@ func TestDiskPartialMerge(t *testing.T) { // This test case is a tiny specialized case of TestDiskPartialMerge, which tests // some very specific cornercases that random tests won't ever trigger. func TestDiskMidAccountPartialMerge(t *testing.T) { + // TODO(@karalabe) ? +} + +// TestDiskSeek tests that seek-operations work on the disk layer +func TestDiskSeek(t *testing.T) { + // Create some accounts in the disk layer + var db ethdb.Database + + if dir, err := ioutil.TempDir("", "disklayer-test"); err != nil { + t.Fatal(err) + } else { + defer os.RemoveAll(dir) + diskdb, err := leveldb.New(dir, 256, 0, "") + if err != nil { + t.Fatal(err) + } + db = rawdb.NewDatabase(diskdb) + } + // Fill even keys [0,2,4...] + for i := 0; i < 0xff; i += 2 { + acc := common.Hash{byte(i)} + rawdb.WriteAccountSnapshot(db, acc, acc[:]) + } + // Add an 'higher' key, with incorrect (higher) prefix + highKey := []byte{rawdb.SnapshotAccountPrefix[0] + 1} + db.Put(highKey, []byte{0xff, 0xff}) + + baseRoot := randomHash() + rawdb.WriteSnapshotRoot(db, baseRoot) + + snaps := &Tree{ + layers: map[common.Hash]snapshot{ + baseRoot: &diskLayer{ + diskdb: db, + cache: fastcache.New(500 * 1024), + root: baseRoot, + }, + }, + } + // Test some different seek positions + type testcase struct { + pos byte + expkey byte + } + var cases = []testcase{ + {0xff, 0x55}, // this should exit immediately without checking key + {0x01, 0x02}, + {0xfe, 0xfe}, + {0xfd, 0xfe}, + {0x00, 0x00}, + } + for i, tc := range cases { + it, err := snaps.AccountIterator(baseRoot, common.Hash{tc.pos}) + if err != nil { + t.Fatalf("case %d, error: %v", i, err) + } + count := 0 + for it.Next() { + k, v, err := it.Hash()[0], it.Account()[0], it.Error() + if err != nil { + t.Fatalf("test %d, item %d, error: %v", i, count, err) + } + // First item in iterator should have the expected key + if count == 0 && k != tc.expkey { + t.Fatalf("test %d, item %d, got %v exp %v", i, count, k, tc.expkey) + } + count++ + if v != k { + t.Fatalf("test %d, item %d, value wrong, got %v exp %v", i, count, v, k) + } + } + } } diff --git a/core/state/snapshot/iterator.go b/core/state/snapshot/iterator.go index e062298fa4..84cc5c3bca 100644 --- a/core/state/snapshot/iterator.go +++ b/core/state/snapshot/iterator.go @@ -148,10 +148,10 @@ type diskAccountIterator struct { // AccountIterator creates an account iterator over a disk layer. func (dl *diskLayer) AccountIterator(seek common.Hash) AccountIterator { - // TODO: Fix seek position, or remove seek parameter + pos := common.TrimRightZeroes(seek[:]) return &diskAccountIterator{ layer: dl, - it: dl.diskdb.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix), + it: dl.diskdb.NewIterator(rawdb.SnapshotAccountPrefix, pos), } } diff --git a/core/state/snapshot/wipe.go b/core/state/snapshot/wipe.go index 052af6f1f1..53eb18a2d1 100644 --- a/core/state/snapshot/wipe.go +++ b/core/state/snapshot/wipe.go @@ -92,7 +92,7 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int // Iterate over the key-range and delete all of them start, logged := time.Now(), time.Now() - it := db.NewIteratorWithStart(prefix) + it := db.NewIterator(prefix, nil) for it.Next() { // Skip any keys with the correct prefix but wrong lenth (trie nodes) key := it.Key() @@ -113,7 +113,8 @@ func wipeKeyRange(db ethdb.KeyValueStore, kind string, prefix []byte, keylen int return err } batch.Reset() - it = db.NewIteratorWithStart(key) + seekPos := key[len(prefix):] + it = db.NewIterator(prefix, seekPos) if time.Since(logged) > 8*time.Second { log.Info("Deleting state snapshot leftovers", "kind", kind, "wiped", items, "elapsed", common.PrettyDuration(time.Since(start))) diff --git a/core/state/snapshot/wipe_test.go b/core/state/snapshot/wipe_test.go index cb6e174b31..2c45652a96 100644 --- a/core/state/snapshot/wipe_test.go +++ b/core/state/snapshot/wipe_test.go @@ -60,7 +60,7 @@ func TestWipe(t *testing.T) { // Sanity check that all the keys are present var items int - it := db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) + it := db.NewIterator(rawdb.SnapshotAccountPrefix, nil) defer it.Release() for it.Next() { @@ -69,7 +69,7 @@ func TestWipe(t *testing.T) { items++ } } - it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) + it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil) defer it.Release() for it.Next() { @@ -88,7 +88,7 @@ func TestWipe(t *testing.T) { <-wipeSnapshot(db, true) // Iterate over the database end ensure no snapshot information remains - it = db.NewIteratorWithPrefix(rawdb.SnapshotAccountPrefix) + it = db.NewIterator(rawdb.SnapshotAccountPrefix, nil) defer it.Release() for it.Next() { @@ -97,7 +97,7 @@ func TestWipe(t *testing.T) { t.Errorf("snapshot entry remained after wipe: %x", key) } } - it = db.NewIteratorWithPrefix(rawdb.SnapshotStoragePrefix) + it = db.NewIterator(rawdb.SnapshotStoragePrefix, nil) defer it.Release() for it.Next() { @@ -112,7 +112,7 @@ func TestWipe(t *testing.T) { // Iterate over the database and ensure miscellaneous items are present items = 0 - it = db.NewIterator() + it = db.NewIterator(nil, nil) defer it.Release() for it.Next() { diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index ad6aeb22e7..1cb73f015e 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -60,7 +60,7 @@ func TestUpdateLeaks(t *testing.T) { } // Ensure that no data was leaked into the database - it := db.NewIterator() + it := db.NewIterator(nil, nil) for it.Next() { t.Errorf("State leaked into database: %x -> %x", it.Key(), it.Value()) } @@ -118,7 +118,7 @@ func TestIntermediateLeaks(t *testing.T) { t.Errorf("can not commit trie %v to persistent database", finalRoot.Hex()) } - it := finalDb.NewIterator() + it := finalDb.NewIterator(nil, nil) for it.Next() { key, fvalue := it.Key(), it.Value() tvalue, err := transDb.Get(key) @@ -131,7 +131,7 @@ func TestIntermediateLeaks(t *testing.T) { } it.Release() - it = transDb.NewIterator() + it = transDb.NewIterator(nil, nil) for it.Next() { key, tvalue := it.Key(), it.Value() fvalue, err := finalDb.Get(key) diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 584ea23d02..4f70d6d04a 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -147,7 +147,7 @@ var bloomBitsPrefix = []byte("bloomBits-") func clearBloomBits(db ethdb.Database) { fmt.Println("Clearing bloombits data...") - it := db.NewIteratorWithPrefix(bloomBitsPrefix) + it := db.NewIterator(bloomBitsPrefix, nil) for it.Next() { db.Delete(it.Key()) } diff --git a/eth/handler_test.go b/eth/handler_test.go index e5449c3420..1b398a8c08 100644 --- a/eth/handler_test.go +++ b/eth/handler_test.go @@ -317,7 +317,7 @@ func testGetNodeData(t *testing.T, protocol int) { // Fetch for now the entire chain db hashes := []common.Hash{} - it := db.NewIterator() + it := db.NewIterator(nil, nil) for it.Next() { if key := it.Key(); len(key) == common.HashLength { hashes = append(hashes, common.BytesToHash(key)) diff --git a/ethdb/dbtest/testsuite.go b/ethdb/dbtest/testsuite.go index dce2ba2a1f..06ee2211e6 100644 --- a/ethdb/dbtest/testsuite.go +++ b/ethdb/dbtest/testsuite.go @@ -32,31 +32,32 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { tests := []struct { content map[string]string prefix string + start string order []string }{ // Empty databases should be iterable - {map[string]string{}, "", nil}, - {map[string]string{}, "non-existent-prefix", nil}, + {map[string]string{}, "", "", nil}, + {map[string]string{}, "non-existent-prefix", "", nil}, // Single-item databases should be iterable - {map[string]string{"key": "val"}, "", []string{"key"}}, - {map[string]string{"key": "val"}, "k", []string{"key"}}, - {map[string]string{"key": "val"}, "l", nil}, + {map[string]string{"key": "val"}, "", "", []string{"key"}}, + {map[string]string{"key": "val"}, "k", "", []string{"key"}}, + {map[string]string{"key": "val"}, "l", "", nil}, // Multi-item databases should be fully iterable { map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, - "", + "", "", []string{"k1", "k2", "k3", "k4", "k5"}, }, { map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, - "k", + "k", "", []string{"k1", "k2", "k3", "k4", "k5"}, }, { map[string]string{"k1": "v1", "k5": "v5", "k2": "v2", "k4": "v4", "k3": "v3"}, - "l", + "l", "", nil, }, // Multi-item databases should be prefix-iterable @@ -65,7 +66,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", }, - "ka", + "ka", "", []string{"ka1", "ka2", "ka3", "ka4", "ka5"}, }, { @@ -73,7 +74,24 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", }, - "kc", + "kc", "", + nil, + }, + // Multi-item databases should be prefix-iterable with start position + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "ka", "3", + []string{"ka3", "ka4", "ka5"}, + }, + { + map[string]string{ + "ka1": "va1", "ka5": "va5", "ka2": "va2", "ka4": "va4", "ka3": "va3", + "kb1": "vb1", "kb5": "vb5", "kb2": "vb2", "kb4": "vb4", "kb3": "vb3", + }, + "ka", "8", nil, }, } @@ -86,7 +104,7 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { } } // Iterate over the database with the given configs and verify the results - it, idx := db.NewIteratorWithPrefix([]byte(tt.prefix)), 0 + it, idx := db.NewIterator([]byte(tt.prefix), []byte(tt.start)), 0 for it.Next() { if len(tt.order) <= idx { t.Errorf("test %d: prefix=%q more items than expected: checking idx=%d (key %q), expecting len=%d", i, tt.prefix, idx, it.Key(), len(tt.order)) @@ -124,62 +142,57 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { } { - it := db.NewIterator() + it := db.NewIterator(nil, nil) got, want := iterateKeys(it), keys if err := it.Error(); err != nil { t.Fatal(err) } - it.Release() if !reflect.DeepEqual(got, want) { t.Errorf("Iterator: got: %s; want: %s", got, want) } } { - it := db.NewIteratorWithPrefix([]byte("1")) + it := db.NewIterator([]byte("1"), nil) got, want := iterateKeys(it), []string{"1", "10", "11", "12"} if err := it.Error(); err != nil { t.Fatal(err) } - it.Release() if !reflect.DeepEqual(got, want) { - t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want) + t.Errorf("IteratorWith(1,nil): got: %s; want: %s", got, want) } } { - it := db.NewIteratorWithPrefix([]byte("5")) + it := db.NewIterator([]byte("5"), nil) got, want := iterateKeys(it), []string{} if err := it.Error(); err != nil { t.Fatal(err) } - it.Release() if !reflect.DeepEqual(got, want) { - t.Errorf("IteratorWithPrefix(1): got: %s; want: %s", got, want) + t.Errorf("IteratorWith(5,nil): got: %s; want: %s", got, want) } } { - it := db.NewIteratorWithStart([]byte("2")) + it := db.NewIterator(nil, []byte("2")) got, want := iterateKeys(it), []string{"2", "20", "21", "22", "3", "4", "6"} if err := it.Error(); err != nil { t.Fatal(err) } - it.Release() if !reflect.DeepEqual(got, want) { - t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want) + t.Errorf("IteratorWith(nil,2): got: %s; want: %s", got, want) } } { - it := db.NewIteratorWithStart([]byte("5")) + it := db.NewIterator(nil, []byte("5")) got, want := iterateKeys(it), []string{"6"} if err := it.Error(); err != nil { t.Fatal(err) } - it.Release() if !reflect.DeepEqual(got, want) { - t.Errorf("IteratorWithStart(2): got: %s; want: %s", got, want) + t.Errorf("IteratorWith(nil,5): got: %s; want: %s", got, want) } } }) @@ -246,11 +259,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { } { - it := db.NewIterator() + it := db.NewIterator(nil, nil) if got, want := iterateKeys(it), []string{"1", "2", "3", "4"}; !reflect.DeepEqual(got, want) { t.Errorf("got: %s; want: %s", got, want) } - it.Release() } b.Reset() @@ -267,11 +279,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { } { - it := db.NewIterator() + it := db.NewIterator(nil, nil) if got, want := iterateKeys(it), []string{"2", "3", "4", "5", "6"}; !reflect.DeepEqual(got, want) { t.Errorf("got: %s; want: %s", got, want) } - it.Release() } }) @@ -296,11 +307,10 @@ func TestDatabaseSuite(t *testing.T, New func() ethdb.KeyValueStore) { t.Fatal(err) } - it := db.NewIterator() + it := db.NewIterator(nil, nil) if got := iterateKeys(it); !reflect.DeepEqual(got, want) { t.Errorf("got: %s; want: %s", got, want) } - it.Release() }) } @@ -311,5 +321,6 @@ func iterateKeys(it ethdb.Iterator) []string { keys = append(keys, string(it.Key())) } sort.Strings(keys) + it.Release() return keys } diff --git a/ethdb/iterator.go b/ethdb/iterator.go index 419e9bdfc2..2b49c93a96 100644 --- a/ethdb/iterator.go +++ b/ethdb/iterator.go @@ -51,16 +51,11 @@ type Iterator interface { // Iteratee wraps the NewIterator methods of a backing data store. type Iteratee interface { - // NewIterator creates a binary-alphabetical iterator over the entire keyspace - // contained within the key-value database. - NewIterator() Iterator - - // NewIteratorWithStart creates a binary-alphabetical iterator over a subset of - // database content starting at a particular initial key (or after, if it does - // not exist). - NewIteratorWithStart(start []byte) Iterator - - // NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset - // of database content with a particular key prefix. - NewIteratorWithPrefix(prefix []byte) Iterator + // NewIterator creates a binary-alphabetical iterator over a subset + // of database content with a particular key prefix, starting at a particular + // initial key (or after, if it does not exist). + // + // Note: This method assumes that the prefix is NOT part of the start, so there's + // no need for the caller to prepend the prefix to the start + NewIterator(prefix []byte, start []byte) Iterator } diff --git a/ethdb/leveldb/leveldb.go b/ethdb/leveldb/leveldb.go index 378d4c3cd2..7ff52e59f6 100644 --- a/ethdb/leveldb/leveldb.go +++ b/ethdb/leveldb/leveldb.go @@ -183,23 +183,11 @@ func (db *Database) NewBatch() ethdb.Batch { } } -// NewIterator creates a binary-alphabetical iterator over the entire keyspace -// contained within the leveldb database. -func (db *Database) NewIterator() ethdb.Iterator { - return db.db.NewIterator(new(util.Range), nil) -} - -// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of -// database content starting at a particular initial key (or after, if it does -// not exist). -func (db *Database) NewIteratorWithStart(start []byte) ethdb.Iterator { - return db.db.NewIterator(&util.Range{Start: start}, nil) -} - -// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset -// of database content with a particular key prefix. -func (db *Database) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator { - return db.db.NewIterator(util.BytesPrefix(prefix), nil) +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + return db.db.NewIterator(bytesPrefixRange(prefix, start), nil) } // Stat returns a particular internal stat of the database. @@ -488,3 +476,12 @@ func (r *replayer) Delete(key []byte) { } r.failure = r.writer.Delete(key) } + +// bytesPrefixRange returns key range that satisfy +// - the given prefix, and +// - the given seek position +func bytesPrefixRange(prefix, start []byte) *util.Range { + r := util.BytesPrefix(prefix) + r.Start = append(r.Start, start...) + return r +} diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 346edc4381..4c5e1a84de 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -129,55 +129,26 @@ func (db *Database) NewBatch() ethdb.Batch { } } -// NewIterator creates a binary-alphabetical iterator over the entire keyspace -// contained within the memory database. -func (db *Database) NewIterator() ethdb.Iterator { - return db.NewIteratorWithStart(nil) -} - -// NewIteratorWithStart creates a binary-alphabetical iterator over a subset of -// database content starting at a particular initial key (or after, if it does -// not exist). -func (db *Database) NewIteratorWithStart(start []byte) ethdb.Iterator { - db.lock.RLock() - defer db.lock.RUnlock() - - var ( - st = string(start) - keys = make([]string, 0, len(db.db)) - values = make([][]byte, 0, len(db.db)) - ) - // Collect the keys from the memory database corresponding to the given start - for key := range db.db { - if key >= st { - keys = append(keys, key) - } - } - // Sort the items and retrieve the associated values - sort.Strings(keys) - for _, key := range keys { - values = append(values, db.db[key]) - } - return &iterator{ - keys: keys, - values: values, - } -} - -// NewIteratorWithPrefix creates a binary-alphabetical iterator over a subset -// of database content with a particular key prefix. -func (db *Database) NewIteratorWithPrefix(prefix []byte) ethdb.Iterator { +// NewIterator creates a binary-alphabetical iterator over a subset +// of database content with a particular key prefix, starting at a particular +// initial key (or after, if it does not exist). +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { db.lock.RLock() defer db.lock.RUnlock() var ( pr = string(prefix) + st = string(append(prefix, start...)) keys = make([]string, 0, len(db.db)) values = make([][]byte, 0, len(db.db)) ) // Collect the keys from the memory database corresponding to the given prefix + // and start for key := range db.db { - if strings.HasPrefix(key, pr) { + if !strings.HasPrefix(key, pr) { + continue + } + if key >= st { keys = append(keys, key) } } diff --git a/les/clientpool.go b/les/clientpool.go index b01c825a7a..c862649501 100644 --- a/les/clientpool.go +++ b/les/clientpool.go @@ -691,6 +691,14 @@ func (db *nodeDB) close() { close(db.closeCh) } +func (db *nodeDB) getPrefix(neg bool) []byte { + prefix := positiveBalancePrefix + if neg { + prefix = negativeBalancePrefix + } + return append(db.verbuf[:], prefix...) +} + func (db *nodeDB) key(id []byte, neg bool) []byte { prefix := positiveBalancePrefix if neg { @@ -761,7 +769,8 @@ func (db *nodeDB) getPosBalanceIDs(start, stop enode.ID, maxCount int) (result [ if maxCount <= 0 { return } - it := db.db.NewIteratorWithStart(db.key(start.Bytes(), false)) + prefix := db.getPrefix(false) + it := db.db.NewIterator(prefix, start.Bytes()) defer it.Release() for i := len(stop[:]) - 1; i >= 0; i-- { stop[i]-- @@ -840,8 +849,9 @@ func (db *nodeDB) expireNodes() { visited int deleted int start = time.Now() + prefix = db.getPrefix(true) ) - iter := db.db.NewIteratorWithPrefix(append(db.verbuf[:], negativeBalancePrefix...)) + iter := db.db.NewIterator(prefix, nil) for iter.Next() { visited += 1 var balance negBalance diff --git a/trie/iterator_test.go b/trie/iterator_test.go index 88b8103fb3..54dd3069f9 100644 --- a/trie/iterator_test.go +++ b/trie/iterator_test.go @@ -120,7 +120,7 @@ func TestNodeIteratorCoverage(t *testing.T) { } } } - it := db.diskdb.NewIterator() + it := db.diskdb.NewIterator(nil, nil) for it.Next() { key := it.Key() if _, ok := hashes[common.BytesToHash(key)]; !ok { @@ -312,7 +312,7 @@ func testIteratorContinueAfterError(t *testing.T, memonly bool) { if memonly { memKeys = triedb.Nodes() } else { - it := diskdb.NewIterator() + it := diskdb.NewIterator(nil, nil) for it.Next() { diskKeys = append(diskKeys, it.Key()) } diff --git a/trie/proof_test.go b/trie/proof_test.go index c488f342c8..4caae73381 100644 --- a/trie/proof_test.go +++ b/trie/proof_test.go @@ -106,7 +106,7 @@ func TestBadProof(t *testing.T) { if proof == nil { t.Fatalf("prover %d: nil proof", i) } - it := proof.NewIterator() + it := proof.NewIterator(nil, nil) for i, d := 0, mrand.Intn(proof.Len()); i <= d; i++ { it.Next() } diff --git a/trie/sync_bloom.go b/trie/sync_bloom.go index 2182d1c437..3108b05935 100644 --- a/trie/sync_bloom.go +++ b/trie/sync_bloom.go @@ -99,7 +99,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { // Note, this is fine, because everything inserted into leveldb by fast sync is // also pushed into the bloom directly, so we're not missing anything when the // iterator is swapped out for a new one. - it := database.NewIterator() + it := database.NewIterator(nil, nil) var ( start = time.Now() @@ -116,7 +116,7 @@ func (b *SyncBloom) init(database ethdb.Iteratee) { key := common.CopyBytes(it.Key()) it.Release() - it = database.NewIteratorWithStart(key) + it = database.NewIterator(nil, key) log.Info("Initializing fast sync bloom", "items", b.bloom.N(), "errorrate", b.errorRate(), "elapsed", common.PrettyDuration(time.Since(start))) swap = time.Now() From d77d35a4a943bac035d7b380d29483ddc1259e8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Wed, 15 Apr 2020 18:03:10 +0300 Subject: [PATCH 102/103] params: update CHTs for the 1.9.13 release --- params/config.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/params/config.go b/params/config.go index e6d2fc7605..fd9717ef3e 100644 --- a/params/config.go +++ b/params/config.go @@ -72,10 +72,10 @@ var ( // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. MainnetTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 289, - SectionHead: common.HexToHash("0x5a95eed1a6e01d58b59f86c754cda88e8d6bede65428530eb0bec03267cda6a9"), - CHTRoot: common.HexToHash("0x6d4abf2b0f3c015952e6a3cbd5cc9885aacc29b8e55d4de662d29783c74a62bf"), - BloomRoot: common.HexToHash("0x1af2a8abbaca8048136b02f782cb6476ab546313186a1d1bd2b02df88ea48e7e"), + SectionIndex: 300, + SectionHead: common.HexToHash("0x022d252ffcd289444eed5a4b8c58018aecb2afc9ab0da5fe059a69a7fb618702"), + CHTRoot: common.HexToHash("0xe7044c70ae068969573c7f5abe58ef23d9d82d4ee9152ec88b7c6d0cc8ee2714"), + BloomRoot: common.HexToHash("0xe22600caa25653abaef00d0c112b07b90f4e3395ce0c1f5f7f791cdd6d30a408"), } // MainnetCheckpointOracle contains a set of configs for the main network oracle. @@ -111,10 +111,10 @@ var ( // RopstenTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. RopstenTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 223, - SectionHead: common.HexToHash("0x9aa51ca383f5075f816e0b8ce7125075cd562b918839ee286c03770722147661"), - CHTRoot: common.HexToHash("0x755c6a5931b7bd36e55e47f3f1e81fa79c930ae15c55682d3a85931eedaf8cf2"), - BloomRoot: common.HexToHash("0xabc37762d11b29dc7dde11b89846e2308ba681eeb015b6a202ef5e242bc107e8"), + SectionIndex: 234, + SectionHead: common.HexToHash("0x34659b817e99e6de868b0d4c5321bcff7e36c2cf79307386a2f5053361794d95"), + CHTRoot: common.HexToHash("0x249401cd2b07e3f64892729d3f6198514cd11001231a1c001c2e7245659b26e0"), + BloomRoot: common.HexToHash("0x37657aa58a07ac3fa13f421c3e5500a944a76def5a11c6d57f17a85f5b33c129"), } // RopstenCheckpointOracle contains a set of configs for the Ropsten test network oracle. @@ -152,10 +152,10 @@ var ( // RinkebyTrustedCheckpoint contains the light client trusted checkpoint for the Rinkeby test network. RinkebyTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 181, - SectionHead: common.HexToHash("0xdda275f3e9ecadf4834a6a682db1ca3db6945fa4014c82dadcad032fc5c1aefa"), - CHTRoot: common.HexToHash("0x0fdfdbdb12e947e838fe26dd3ada4cc3092d6fa22aefec719b83f16004b5e596"), - BloomRoot: common.HexToHash("0xfd8dc404a438eaa5cf93dd58dbaeed648aa49d563b511892262acff77c5db7db"), + SectionIndex: 191, + SectionHead: common.HexToHash("0xfdf3085848b4126048caf176634fd96a208d8a3b055c643e9e32690420df36d5"), + CHTRoot: common.HexToHash("0x48059ceb7e0bd25708cc736e5603d28a6f173a3bb904e6e1b3511a97fa30ca97"), + BloomRoot: common.HexToHash("0x3566c2b173c0591d5bb4f3ef7e341d82da7577c125fca94e9b51fb7134a676d7"), } // RinkebyCheckpointOracle contains a set of configs for the Rinkeby test network oracle. @@ -191,10 +191,10 @@ var ( // GoerliTrustedCheckpoint contains the light client trusted checkpoint for the Görli test network. GoerliTrustedCheckpoint = &TrustedCheckpoint{ - SectionIndex: 66, - SectionHead: common.HexToHash("0xeea3a7b2cb275956f3049dd27e6cdacd8a6ef86738d593d556efee5361019475"), - CHTRoot: common.HexToHash("0x11712af50b4083dc5910e452ca69fbfc0f2940770b9846200a573f87a0af94e6"), - BloomRoot: common.HexToHash("0x331b7a7b273e81daeac8cafb9952a16669d7facc7be3b0ebd3a792b4d8b95cc5"), + SectionIndex: 76, + SectionHead: common.HexToHash("0xf56ca390d1131767b924d85ee8e039c8a4c4a498cfaf017c1a9abf63ef01ff17"), + CHTRoot: common.HexToHash("0x78ffc5eecf514eed42f61e6f6df1bdcd79f9296c462faf6f33bd600f70a2e8b9"), + BloomRoot: common.HexToHash("0x5186111a2d6c459cc341319398f7d14fa2c973b1ba846b7f2ec678129c7115fd"), } // GoerliCheckpointOracle contains a set of configs for the Goerli test network oracle. From cbc4ac264e9671898bff6ec7e434da3eca4bd273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 16 Apr 2020 10:39:22 +0300 Subject: [PATCH 103/103] params: release Geth v1.9.13 --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 9c1374c224..ca45179930 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 1 // Major version component of the current release - VersionMinor = 9 // Minor version component of the current release - VersionPatch = 13 // Patch version component of the current release - VersionMeta = "unstable" // Version metadata to append to the version string + VersionMajor = 1 // Major version component of the current release + VersionMinor = 9 // Minor version component of the current release + VersionPatch = 13 // Patch version component of the current release + VersionMeta = "stable" // Version metadata to append to the version string ) // Version holds the textual version string.