diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index c5d836e0ea..196e86d591 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -92,7 +92,7 @@ func blockTestCmd(ctx *cli.Context) error { fmt.Println(string(state.Dump(nil))) } } - }); err != nil { + }, "", true); err != nil { return fmt.Errorf("test %v: %w", name, err) } } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index c8ad9de1a2..f84a673e26 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -169,6 +169,12 @@ var ( utils.RollupComputePendingBlock, utils.RollupHaltOnIncompatibleProtocolVersionFlag, utils.RollupSuperchainUpgradesFlag, + utils.ParallelTxFlag, + utils.ParallelTxUnorderedMergeFlag, + utils.ParallelTxNumFlag, + utils.ParallelTxDAGFlag, + utils.ParallelTxDAGFileFlag, + utils.ParallelTxDAGSenderPrivFlag, configFileFlag, utils.LogDebugFlag, utils.LogBacktraceAtFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 7e44853681..ebe04b22ed 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -23,7 +23,6 @@ import ( "encoding/hex" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "math" "math/big" "net" @@ -35,6 +34,8 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + pcsclite "github.com/gballet/go-libpcsclite" gopsutil "github.com/shirou/gopsutil/mem" "github.com/urfave/cli/v2" @@ -1093,11 +1094,49 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Category: flags.MetricsCategory, } + ParallelTxFlag = &cli.BoolFlag{ + Name: "parallel", + Usage: "Enable the experimental parallel transaction execution mode, only valid in full sync mode (default = false)", + Category: flags.VMCategory, + } + + ParallelTxUnorderedMergeFlag = &cli.BoolFlag{ + Name: "parallel.unordered-merge", + Usage: "Enable unordered merge mode, during the parallel confirm phase, merge transaction execution results without following the transaction order.", + Category: flags.VMCategory, + } + + ParallelTxNumFlag = &cli.IntFlag{ + Name: "parallel.num", + Usage: "Number of slot for transaction execution, only valid in parallel mode (runtime calculated, no fixed default value)", + Category: flags.VMCategory, + } + + ParallelTxDAGFlag = &cli.BoolFlag{ + Name: "parallel.txdag", + Usage: "Enable the experimental parallel TxDAG generation, only valid in full sync mode (default = false)", + Category: flags.VMCategory, + } + + ParallelTxDAGFileFlag = &cli.StringFlag{ + Name: "parallel.txdagfile", + Usage: "It indicates the TxDAG file path", + Value: "./parallel-txdag-output.csv", + Category: flags.VMCategory, + } + VMOpcodeOptimizeFlag = &cli.BoolFlag{ Name: "vm.opcode.optimize", Usage: "enable opcode optimization", Category: flags.VMCategory, } + + ParallelTxDAGSenderPrivFlag = &cli.StringFlag{ + Name: "parallel.txdagsenderpriv", + Usage: "private key of the sender who sends the TxDAG transactions", + Value: "", + Category: flags.VMCategory, + } ) var ( @@ -1983,6 +2022,33 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { cfg.EnablePreimageRecording = ctx.Bool(VMEnableDebugFlag.Name) } + if ctx.IsSet(ParallelTxFlag.Name) { + cfg.ParallelTxMode = ctx.Bool(ParallelTxFlag.Name) + } + + if ctx.IsSet(ParallelTxUnorderedMergeFlag.Name) { + cfg.ParallelTxUnorderedMerge = ctx.Bool(ParallelTxUnorderedMergeFlag.Name) + } + + if ctx.IsSet(ParallelTxNumFlag.Name) { + cfg.ParallelTxNum = ctx.Int(ParallelTxNumFlag.Name) + } + + if ctx.IsSet(ParallelTxDAGFlag.Name) { + cfg.EnableParallelTxDAG = ctx.Bool(ParallelTxDAGFlag.Name) + } + + if ctx.IsSet(ParallelTxDAGFileFlag.Name) { + cfg.ParallelTxDAGFile = ctx.String(ParallelTxDAGFileFlag.Name) + } + + if ctx.IsSet(ParallelTxDAGSenderPrivFlag.Name) { + priHex := ctx.String(ParallelTxDAGSenderPrivFlag.Name) + if cfg.Miner.ParallelTxDAGSenderPriv, err = crypto.HexToECDSA(priHex); err != nil { + Fatalf("Failed to parse txdag private key of %s, err: %v", ParallelTxDAGSenderPrivFlag.Name, err) + } + } + if ctx.IsSet(VMOpcodeOptimizeFlag.Name) { cfg.EnableOpcodeOptimizing = ctx.Bool(VMOpcodeOptimizeFlag.Name) if cfg.EnableOpcodeOptimizing { diff --git a/core/blockchain.go b/core/blockchain.go index 7e4b81b153..a76a213861 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -18,11 +18,16 @@ package core import ( + "bufio" + "bytes" + "encoding/hex" "errors" "fmt" "io" "math/big" + "os" "runtime" + "strconv" "strings" "sync" "sync/atomic" @@ -91,9 +96,21 @@ var ( triedbCommitExternalTimer = metrics.NewRegisteredTimer("chain/triedb/commit/external", nil) innerExecutionTimer = metrics.NewRegisteredTimer("chain/inner/execution", nil) + txDAGGenerateTimer = metrics.NewRegisteredTimer("chain/block/txdag/gen", nil) + + parallelTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/txs", nil) + parallelConflictTxNumMeter = metrics.NewRegisteredMeter("chain/parallel/conflicttxs", nil) + parallelExecutionTimer = metrics.NewRegisteredTimer("chain/parallel/exec", nil) + parallelConfirmTimer = metrics.NewRegisteredTimer("chain/parallel/confirm", nil) + parallelTxLevelsSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/size", nil) + parallelTxLevelTxSizeMeter = metrics.NewRegisteredGauge("chain/parallel/txlevel/txsize", nil) + blockGasUsedGauge = metrics.NewRegisteredGauge("chain/block/gas/used", nil) mgaspsGauge = metrics.NewRegisteredGauge("chain/mgas/ps", nil) + pevmBuildLevelsTimer = metrics.NewRegisteredTimer("chain/pevm/buildlevels", nil) + pevmRunTimer = metrics.NewRegisteredTimer("chain/pevm/run", nil) + blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) @@ -294,6 +311,12 @@ type BlockChain struct { processor Processor // Block transaction processor interface forker *ForkChoice vmConfig vm.Config + + parallelExecution bool + enableTxDAG bool + txDAGWriteCh chan TxDAGOutputItem + txDAGReader *TxDAGFileReader + serialProcessor Processor } // NewBlockChain returns a fully initialised block chain using information @@ -358,7 +381,6 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) bc.validator = NewBlockValidator(chainConfig, bc, engine) bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) - bc.processor = NewStateProcessor(chainConfig, bc, engine) err := proofKeeper.Start(bc, db) if err != nil { @@ -512,6 +534,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis bc.snaps, _ = snapshot.New(snapconfig, bc.db, bc.triedb, head.Root) } + if bc.vmConfig.EnableParallelExec { + bc.processor = newPEVMProcessor(chainConfig, bc, engine) + bc.serialProcessor = NewStateProcessor(chainConfig, bc, engine) + log.Info("Parallel V2 enabled", "parallelNum", ParallelNum()) + } else { + bc.processor = NewStateProcessor(chainConfig, bc, engine) + } // Start future block processor. bc.wg.Add(1) go bc.updateFutureBlocks() @@ -861,6 +890,10 @@ func (bc *BlockChain) setHeadBeyondRoot(head uint64, time uint64, root common.Ha bc.miningStateCache.Purge() bc.futureBlocks.Purge() + if bc.txDAGReader != nil { + bc.txDAGReader.Reset(head) + } + // Clear safe block, finalized block if needed if safe := bc.CurrentSafeBlock(); safe != nil && head < safe.Number.Uint64() { log.Warn("SetHead invalidated safe block") @@ -1045,6 +1078,9 @@ func (bc *BlockChain) stopWithoutSaving() { // Stop stops the blockchain service. If any imports are currently in progress // it will abort them using the procInterrupt. func (bc *BlockChain) Stop() { + if bc.txDAGReader != nil { + bc.txDAGReader.Close() + } bc.stopWithoutSaving() // Ensure that the entirety of the state snapshot is journaled to disk. @@ -1738,7 +1774,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } lastCanon = block - block, err = it.next() } // Falls through to the block import @@ -1876,9 +1911,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) statedb.StartPrefetcher("chain") activeState = statedb + if bc.vmConfig.EnableParallelExec { + bc.parseTxDAG(block) + } // If we have a followup block, run that against the current state to pre-cache // transactions and probabilistically some of the account/storage trie nodes. - if !bc.cacheConfig.TrieCleanNoPrefetch { + // parallel mode has a pipeline, similar to this prefetch, to save CPU we disable this prefetch for parallel + if !bc.cacheConfig.TrieCleanNoPrefetch && !bc.parallelExecution { if followup, err := it.peek(); followup != nil && err == nil { throwaway, _ := state.New(parent.Root, bc.stateCache, bc.snaps) @@ -1897,7 +1936,11 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // Process block using the parent state as reference point pstart = time.Now() - receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) + if bc.vmConfig.TxDAG == nil && bc.vmConfig.EnableParallelUnorderedMerge { + receipts, logs, usedGas, err = bc.serialProcessor.Process(block, statedb, bc.vmConfig) + } else { + receipts, logs, usedGas, err = bc.processor.Process(block, statedb, bc.vmConfig) + } if err != nil { bc.reportBlock(block, receipts, err) followupInterrupt.Store(true) @@ -1912,9 +1955,31 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) followupInterrupt.Store(true) return it.index, err } + vtime := time.Since(vstart) proctime := time.Since(start) // processing + validation + if bc.enableTxDAG && !bc.vmConfig.EnableParallelExec { + // compare input TxDAG when it enable in consensus + dag, err := statedb.ResolveTxDAG(len(block.Transactions()), []common.Address{block.Coinbase(), params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if err == nil { + // TODO(galaio): check TxDAG correctness? + log.Debug("Process TxDAG result", "block", block.NumberU64(), "txDAG", dag) + if metrics.EnabledExpensive { + go types.EvaluateTxDAGPerformance(dag, statedb.ResolveStats()) + } + // try to write txDAG into file + if bc.txDAGWriteCh != nil && dag != nil { + bc.txDAGWriteCh <- TxDAGOutputItem{ + blockNumber: block.NumberU64(), + txDAG: dag, + } + } + } else { + log.Error("ResolveTxDAG err", "block", block.NumberU64(), "tx", len(block.Transactions()), "err", err) + } + } + // Update the metrics touched during block processing and validation accountReadTimer.Update(statedb.AccountReads) // Account reads are complete(in processing) storageReadTimer.Update(statedb.StorageReads) // Storage reads are complete(in processing) @@ -1924,8 +1989,9 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) storageUpdateTimer.Update(statedb.StorageUpdates) // Storage updates are complete(in validation) accountHashTimer.Update(statedb.AccountHashes) // Account hashes are complete(in validation) storageHashTimer.Update(statedb.StorageHashes) // Storage hashes are complete(in validation) - blockExecutionTimer.Update(ptime) // The time spent on block execution - blockValidationTimer.Update(vtime) // The time spent on block validation + txDAGGenerateTimer.Update(statedb.TxDAGGenerate) + blockExecutionTimer.Update(ptime) // The time spent on block execution + blockValidationTimer.Update(vtime) // The time spent on block validation innerExecutionTimer.Update(DebugInnerExecutionDuration) @@ -2025,6 +2091,39 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) return it.index, err } +func (bc *BlockChain) parseTxDAG(block *types.Block) { + if !bc.enableTxDAG { + return + } + var ( + txDAG types.TxDAG + err error + ) + if bc.txDAGReader != nil { + // load cache txDAG from file first + txDAG = bc.txDAGReader.TxDAG(block.NumberU64()) + } else { + // load TxDAG from block + txDAG, err = types.GetTxDAG(block) + if err != nil { + log.Warn("pevm decode txdag failed", "block", block.NumberU64(), "err", err) + } + } + if err := types.ValidateTxDAG(txDAG, len(block.Transactions())); err != nil { + log.Warn("pevm cannot apply wrong txdag", + "block", block.NumberU64(), "txs", len(block.Transactions()), "err", err) + txDAG = nil + } + bc.vmConfig.TxDAG = txDAG +} + +func (bc *BlockChain) isEmptyTxDAG() bool { + if bc.vmConfig.TxDAG != nil && bc.vmConfig.TxDAG.Type() == types.EmptyTxDAGType { + return true + } + return false +} + // insertSideChain is called when an import batch hits upon a pruned ancestor // error, which happens when a sidechain with a sufficiently old fork-block is // found. @@ -2628,3 +2727,215 @@ func createDelFn(bc *BlockChain) func(db ethdb.KeyValueWriter, hash common.Hash, func (bc *BlockChain) HeaderChainForceSetHead(headNumber uint64) { bc.hc.SetHead(headNumber, nil, createDelFn(bc)) } + +func (bc *BlockChain) TxDAGEnabledWhenMine() bool { + return bc.enableTxDAG && bc.txDAGWriteCh == nil && bc.txDAGReader == nil && !bc.vmConfig.EnableParallelExec +} + +func (bc *BlockChain) SetupTxDAGGeneration(output string, readFile bool) { + log.Info("node enable TxDAG feature", "output", output) + bc.enableTxDAG = true + if len(output) == 0 { + return + } + // read TxDAG file, and cache in mem + if readFile { + var err error + bc.txDAGReader, err = NewTxDAGFileReader(output) + if err != nil { + log.Error("read TxDAG err", "err", err) + } + // startup with latest block + curHeader := bc.CurrentHeader() + if curHeader != nil && bc.txDAGReader != nil { + bc.txDAGReader.TxDAG(curHeader.Number.Uint64()) + log.Info("load TxDAG from file", "output", output, "block", curHeader.Number, "latest", bc.txDAGReader.Latest()) + } + return + } + + // write handler + go func() { + writeHandle, err := os.OpenFile(output, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + log.Error("OpenFile when open the txDAG output file", "file", output, "err", err) + return + } + bc.txDAGWriteCh = make(chan TxDAGOutputItem, 10000) + defer writeHandle.Close() + for { + select { + case <-bc.quit: + return + case item := <-bc.txDAGWriteCh: + if err := writeTxDAGToFile(writeHandle, item); err != nil { + log.Error("encode TxDAG err in OutputHandler", "err", err) + continue + } + } + } + }() +} + +type TxDAGOutputItem struct { + blockNumber uint64 + txDAG types.TxDAG +} + +func writeTxDAGToFile(writeHandle *os.File, item TxDAGOutputItem) error { + var buf bytes.Buffer + buf.WriteString(strconv.FormatUint(item.blockNumber, 10)) + buf.WriteByte(',') + enc, err := types.EncodeTxDAG(item.txDAG) + if err != nil { + return err + } + buf.WriteString(hex.EncodeToString(enc)) + buf.WriteByte('\n') + _, err = writeHandle.Write(buf.Bytes()) + return err +} + +var TxDAGCacheSize = uint64(10000) + +type TxDAGFileReader struct { + output string + file *os.File + scanner *bufio.Scanner + cache map[uint64]types.TxDAG + latest uint64 + lock sync.RWMutex +} + +func NewTxDAGFileReader(output string) (*TxDAGFileReader, error) { + reader := &TxDAGFileReader{output: output} + err := reader.openFile(output) + if err != nil { + return nil, err + } + return reader, nil +} + +func (t *TxDAGFileReader) Close() { + t.lock.Lock() + defer t.lock.Unlock() + t.closeFile() +} + +func (t *TxDAGFileReader) openFile(output string) error { + file, err := os.Open(output) + if err != nil { + return err + } + scanner := bufio.NewScanner(file) + scanner.Buffer(make([]byte, 5*1024*1024), 5*1024*1024) + t.file = file + t.scanner = scanner + return nil +} + +func (t *TxDAGFileReader) closeFile() { + if t.scanner != nil { + t.scanner = nil + } + if t.file != nil { + t.file.Close() + t.file = nil + } +} + +func (t *TxDAGFileReader) Latest() uint64 { + t.lock.RLock() + defer t.lock.RUnlock() + return t.latest +} + +func (t *TxDAGFileReader) TxDAG(expect uint64) types.TxDAG { + t.lock.Lock() + defer t.lock.Unlock() + + if t.cache != nil && t.latest >= expect { + return t.cache[expect] + } + if t.scanner == nil { + return nil + } + + logTime := time.Now() + t.cache = make(map[uint64]types.TxDAG, TxDAGCacheSize) + for t.scanner.Scan() { + num, dag, err := readTxDAGItemFromLine(t.scanner.Text()) + if err != nil { + log.Error("query TxDAG error", "latest", t.latest, "err", err) + continue + } + if time.Since(logTime) > 10*time.Second { + logTime = time.Now() + log.Info("try load TxDAG from file", "num", num, "expect", expect, "cached", len(t.cache)) + } + // skip lower blocks + if expect > num { + continue + } + t.cache[num] = dag + t.latest = num + if uint64(len(t.cache)) >= TxDAGCacheSize { + break + } + } + if t.scanner.Err() != nil { + log.Error("scan TxDAG file got err", "expect", expect, "err", t.scanner.Err()) + } + + if time.Since(logTime) > 10*time.Second { + log.Info("try load TxDAG from file", "expect", expect, "cached", len(t.cache)) + } + return t.cache[expect] +} + +func (t *TxDAGFileReader) Reset(number uint64) error { + t.lock.Lock() + defer t.lock.Unlock() + if t.latest-TxDAGCacheSize <= number { + return nil + } + t.closeFile() + if err := t.openFile(t.output); err != nil { + return err + } + t.latest = 0 + t.cache = nil + return nil +} + +func readTxDAGItemFromLine(line string) (uint64, types.TxDAG, error) { + tokens := strings.Split(line, ",") + if len(tokens) != 2 { + return 0, nil, errors.New("txDAG output contain wrong size") + } + num, err := strconv.Atoi(tokens[0]) + if err != nil { + return 0, nil, err + } + enc, err := hex.DecodeString(tokens[1]) + if err != nil { + return 0, nil, err + } + txDAG, err := types.DecodeTxDAG(enc) + if err != nil { + return 0, nil, err + } + return uint64(num), txDAG, nil +} + +func readTxDAGBlockFromLine(line string) (uint64, error) { + tokens := strings.Split(line, ",") + if len(tokens) != 2 { + return 0, errors.New("txDAG output contain wrong size") + } + num, err := strconv.Atoi(tokens[0]) + if err != nil { + return 0, err + } + return uint64(num), nil +} diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 82a480d0be..2667323c1e 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -60,7 +60,7 @@ func (st *insertStats) report(chain []*types.Block, index int, snapDiffItems, sn "blocks", st.processed, "txs", txs, "mgas", float64(st.usedGas) / 1000000, "elapsed", common.PrettyDuration(elapsed), "mgasps", float64(st.usedGas) * 1000 / float64(elapsed), } - mgaspsGauge.Update(int64(st.usedGas)*1000/int64(elapsed)) + mgaspsGauge.Update(int64(st.usedGas) * 1000 / int64(elapsed)) if timestamp := time.Unix(int64(end.Time()), 0); time.Since(timestamp) > time.Minute { context = append(context, []interface{}{"age", common.PrettyAge(timestamp)}...) } diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 22db20a23e..afc664171d 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -22,10 +22,13 @@ import ( "math/big" "math/rand" "os" + "path/filepath" "sync" "testing" "time" + "github.com/stretchr/testify/require" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" @@ -1631,7 +1634,6 @@ func testEIP155Transition(t *testing.T, scheme string) { block.AddTx(tx) } }) - blockchain, _ := NewBlockChain(rawdb.NewMemoryDatabase(), DefaultCacheConfigWithScheme(scheme), gspec, nil, ethash.NewFaker(), vm.Config{}, nil, nil) defer blockchain.Stop() @@ -4327,3 +4329,96 @@ func TestEIP3651(t *testing.T) { t.Fatalf("sender balance incorrect: expected %d, got %d", expected, actual) } } + +func TestTxDAGFile_ReadWrite(t *testing.T) { + path := filepath.Join(os.TempDir(), "test.csv") + defer func() { + os.Remove(path) + }() + except := map[uint64]types.TxDAG{ + 0: types.NewEmptyTxDAG(), + 1: makeEmptyPlainTxDAG(1), + 2: makeEmptyPlainTxDAG(2, types.NonDependentRelFlag), + } + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + require.NoError(t, err) + for num, dag := range except { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) + } + writeFile.Close() + + except2 := map[uint64]types.TxDAG{ + 3: types.NewEmptyTxDAG(), + 4: makeEmptyPlainTxDAG(4, types.NonDependentRelFlag, types.ExcludedTxFlag), + 5: makeEmptyPlainTxDAG(5, types.NonDependentRelFlag, types.ExcludedTxFlag), + } + writeFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + require.NoError(t, err) + for num, dag := range except2 { + if num == 5 { + writeFile.WriteString("num,txdag\n") + continue + } + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: dag})) + } + writeFile.Close() + + reader, err := NewTxDAGFileReader(path) + require.NoError(t, err) + for i := 0; i < 5; i++ { + num := uint64(i) + if except[num] != nil { + require.Equal(t, except[num], reader.TxDAG(num)) + continue + } + require.Equal(t, except2[num], reader.TxDAG(num)) + } +} + +func TestTxDAGFile_LargeRead(t *testing.T) { + path := filepath.Join(os.TempDir(), "test.csv") + defer func() { + os.Remove(path) + }() + TxDAGCacheSize = 10 + totalSize := uint64(100) + except := map[uint64]types.TxDAG{} + for i := uint64(0); i < totalSize-1; i++ { + except[i] = makeEmptyPlainTxDAG(1) + } + except[totalSize-1] = makeEmptyPlainTxDAG(510 * 1024) + writeFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + require.NoError(t, err) + for num := uint64(0); num < totalSize; num++ { + require.NoError(t, writeTxDAGToFile(writeFile, TxDAGOutputItem{blockNumber: num, txDAG: except[num]})) + } + writeFile.Close() + + reader, err := NewTxDAGFileReader(path) + require.NoError(t, err) + for i := uint64(0); i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } + + // test reset to genesis + err = reader.Reset(0) + require.NoError(t, err) + for i := uint64(0); i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } + + // test reset skip + err = reader.Reset(totalSize - TxDAGCacheSize) + require.NoError(t, err) + for i := totalSize - TxDAGCacheSize; i < totalSize; i++ { + require.Equal(t, except[i], reader.TxDAG(i), i) + } +} + +func makeEmptyPlainTxDAG(cnt int, flags ...uint8) *types.PlainTxDAG { + dag := types.NewPlainTxDAG(cnt) + for i := range dag.TxDeps { + dag.TxDeps[i] = types.NewTxDep(make([]uint64, 0), flags...) + } + return dag +} diff --git a/core/error.go b/core/error.go index 8c691b17ff..7fa4556fdf 100644 --- a/core/error.go +++ b/core/error.go @@ -113,4 +113,7 @@ var ( // ErrSystemTxNotSupported is returned for any deposit tx with IsSystemTx=true after the Regolith fork ErrSystemTxNotSupported = errors.New("system tx not supported") + + // ErrParallelUnexpectedConflict is returned when execution finally get conflict error that should not occur + ErrParallelUnexpectedConflict = errors.New("parallel execution unexpected conflict") ) diff --git a/core/parallel_state_scheduler.go b/core/parallel_state_scheduler.go new file mode 100644 index 0000000000..4ad229c399 --- /dev/null +++ b/core/parallel_state_scheduler.go @@ -0,0 +1,354 @@ +package core + +import ( + "fmt" + "runtime" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +var runner chan func() +var runnerOnce sync.Once + +func initParallelRunner(targetNum int) { + runnerOnce.Do(func() { + if targetNum == 0 { + targetNum = runtime.GOMAXPROCS(0) + } + runner = make(chan func(), targetNum) + for i := 0; i < targetNum; i++ { + go func() { + for f := range runner { + f() + } + }() + } + }) +} + +func ParallelNum() int { + return cap(runner) +} + +// TxLevel contains all transactions who are independent to each other +type TxLevel []*PEVMTxRequest + +func (tl TxLevel) SplitBy(chunkSize int) []TxLevel { + if len(tl) == 0 { + return nil + } + if chunkSize <= 0 { + chunkSize = 1 + } + result := make([]TxLevel, 0, len(tl)/chunkSize+1) + for i := 0; i < len(tl); i += chunkSize { + end := i + chunkSize + if end > len(tl) { + end = len(tl) + } + result = append(result, tl[i:end]) + } + return result +} + +func (tl TxLevel) Split(chunks int) []TxLevel { + if len(tl) == 0 { + return nil + } + if chunks <= 0 { + chunks = 1 + } + result := make([]TxLevel, 0, chunks) + var chunkSize int + if len(tl)%chunks == 0 { + chunkSize = len(tl) / chunks + } else { + chunkSize = len(tl)/chunks + 1 + } + for i := 0; i < chunks; i++ { + start := i * chunkSize + end := min(start+chunkSize, len(tl)) + if start > len(tl)-1 || start >= end { + break + } + result = append(result, tl[start:end]) + } + return result +} + +// TxLevels indicates the levels of transactions +// the levels are ordered by the dependencies, and generated by the TxDAG +type TxLevels []TxLevel + +type confirmQueue struct { + queue []confirmation + confirmed int // need to be set to -1 originally +} + +type confirmation struct { + result *PEVMTxResult + executed error // error from execution in parallel + confirmed error // error from confirmation in sequence (like conflict) +} + +// put into the right position (txIndex) +func (cq *confirmQueue) collect(result *PEVMTxResult) error { + if result.txReq.txIndex >= len(cq.queue) { + // TODO add metrics + return fmt.Errorf("txIndex outof range, req.index:%d, len(queue):%d", result.txReq.txIndex, len(cq.queue)) + } + i := result.txReq.txIndex + cq.queue[i].result, cq.queue[i].executed, cq.queue[i].confirmed = result, result.err, nil + return nil +} + +func (cq *confirmQueue) confirmWithUnordered(level TxLevel, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { + // find all able-to-confirm transactions, and try to confirm them + for _, tx := range level { + i := tx.txIndex + toConfirm := cq.queue[i] + // the tx has not been executed yet, which means the higher-index transactions can not be confirmed before it + // so stop the loop. + if toConfirm.result == nil { + break + } + switch true { + case toConfirm.executed != nil: + if err := cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + + default: + //try the first confirm + if err := confirm(toConfirm.result); err != nil { + // TODO add logs for err + if err = cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + } + } + cq.confirmed = i + } + return nil, 0 +} + +// try to confirm txs as much as possible, they will be confirmed in a sequencial order. +func (cq *confirmQueue) confirm(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) (error, int) { + // find all able-to-confirm transactions, and try to confirm them + for i := cq.confirmed + 1; i < len(cq.queue); i++ { + toConfirm := cq.queue[i] + // the tx has not been executed yet, which means the higher-index transactions can not be confirmed before it + // so stop the loop. + if toConfirm.result == nil { + break + } + switch true { + case toConfirm.executed != nil: + if err := cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + + default: + //try the first confirm + if err := confirm(toConfirm.result); err != nil { + // TODO add logs for err + if err = cq.rerun(i, execute, confirm); err != nil { + // TODO add logs for err + // rerun failed, something very wrong. + return err, toConfirm.result.txReq.txIndex + } + } + } + cq.confirmed = i + } + return nil, 0 +} + +// rerun executes the transaction of index 'i', and confirms it. +func (cq *confirmQueue) rerun(i int, execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error) error { + //reset the result + cq.queue[i].result.err, cq.queue[i].executed, cq.queue[i].confirmed = nil, nil, nil + // failed, rerun and reconfirm, the rerun should alway success. + rerun := execute(cq.queue[i].result.txReq) + if rerun.err != nil { + // TODO add metrics, add error logs. + return rerun.err + } + cq.queue[i].result, cq.queue[i].executed, cq.queue[i].confirmed = rerun, nil, confirm(rerun) + if cq.queue[i].confirmed != nil { + // TODO add metrics, add error logs. + return cq.queue[i].confirmed + } + return nil +} + +var goMaxProcs = runtime.GOMAXPROCS(0) + +// run runs the transactions in parallel +// execute must return a non-nil result, otherwise it panics. +func (tls TxLevels) Run(execute func(*PEVMTxRequest) *PEVMTxResult, confirm func(*PEVMTxResult) error, unorderedMerge bool) (error, int) { + toConfirm := &confirmQueue{ + queue: make([]confirmation, tls.txCount()), + confirmed: -1, + } + + totalExecutionTime := int64(0) + totalConfirmTime := int64(0) + maxLevelTxCount := 0 + + // execute all transactions in parallel + for _, txLevel := range tls { + start := time.Now() + log.Debug("txLevel tx count", "tx count", len(txLevel)) + if len(txLevel) > maxLevelTxCount { + maxLevelTxCount = len(txLevel) + } + wait := sync.WaitGroup{} + trunks := txLevel.Split(goMaxProcs) + wait.Add(len(trunks)) + // split tx into chunks, to save the cost of channel communication + for _, txs := range trunks { + // execute the transactions in parallel + temp := txs + run := func() { + for _, tx := range temp { + res := execute(tx) + toConfirm.collect(res) + } + wait.Done() + } + //go run() + runner <- run + } + wait.Wait() + totalExecutionTime += time.Since(start).Nanoseconds() + start = time.Now() + // all transactions of current level are executed, now try to confirm. + if unorderedMerge { + if err, txIndex := toConfirm.confirmWithUnordered(txLevel, execute, confirm); err != nil { + // something very wrong, stop the process + return err, txIndex + } + } else { + if err, txIndex := toConfirm.confirm(execute, confirm); err != nil { + // something very wrong, stop the process + return err, txIndex + } + } + totalConfirmTime += time.Since(start).Nanoseconds() + } + parallelTxLevelTxSizeMeter.Update(int64(maxLevelTxCount)) + parallelExecutionTimer.Update(time.Duration(totalExecutionTime)) + parallelConfirmTimer.Update(time.Duration(totalConfirmTime)) + return nil, 0 +} + +func (tls TxLevels) txCount() int { + count := 0 + for _, txlevel := range tls { + count += len(txlevel) + } + return count +} + +// predictTxDAG predicts the TxDAG by their from address and to address, and generates the levels of transactions +func (tl TxLevel) predictTxDAG(dag types.TxDAG) { + marked := make(map[common.Address]int, len(tl)) + for _, tx := range tl { + var deps []uint64 + var tfrom, tto = -1, -1 + if ti, ok := marked[tx.msg.From]; ok { + tfrom = ti + } + if ti, ok := marked[*tx.msg.To]; ok { + tto = ti + } + if tfrom >= 0 && tto >= 0 && tfrom > tto { + // keep deps ordered by the txIndex + tfrom, tto = tto, tfrom + } + if tfrom >= 0 { + deps = append(deps, uint64(tfrom)) + } + if tto >= 0 { + deps = append(deps, uint64(tto)) + } + dag.SetTxDep(tx.txIndex, types.TxDep{TxIndexes: deps}) + marked[tx.msg.From] = tx.txIndex + marked[*tx.msg.To] = tx.txIndex + } +} + +func NewTxLevels(all []*PEVMTxRequest, dag types.TxDAG) TxLevels { + var levels TxLevels = make(TxLevels, 0, 8) + var currLevel int = 0 + + var enlargeLevelsIfNeeded = func(currLevel int, levels *TxLevels) { + if len(*levels) <= currLevel { + for i := len(*levels); i <= currLevel; i++ { + *levels = append(*levels, TxLevel{}) + } + } + } + + if len(all) == 0 { + return nil + } + if dag == nil { + return TxLevels{all} + } + + marked := make(map[int]int, len(all)) + for _, tx := range all { + dep := dag.TxDep(tx.txIndex) + switch true { + case dep != nil && dep.CheckFlag(types.ExcludedTxFlag), + dep != nil && dep.CheckFlag(types.NonDependentRelFlag): + // excluted tx, occupies the whole level + // or dependent-to-all tx, occupies the whole level, too + levels = append(levels, TxLevel{tx}) + marked[tx.txIndex], currLevel = len(levels)-1, len(levels) + + case dep == nil || len(dep.TxIndexes) == 0: + // dependent on none + enlargeLevelsIfNeeded(currLevel, &levels) + levels[currLevel] = append(levels[currLevel], tx) + marked[tx.txIndex] = currLevel + + case dep != nil && len(dep.TxIndexes) > 0: + // dependent on others + // findout the correct level that the tx should be put + prevLevel := -1 + for _, txIndex := range dep.TxIndexes { + if pl, ok := marked[int(txIndex)]; ok && pl > prevLevel { + prevLevel = pl + } + } + if prevLevel < 0 { + // broken DAG, just ignored it + enlargeLevelsIfNeeded(currLevel, &levels) + levels[currLevel] = append(levels[currLevel], tx) + marked[tx.txIndex] = currLevel + continue + } + enlargeLevelsIfNeeded(prevLevel+1, &levels) + levels[prevLevel+1] = append(levels[prevLevel+1], tx) + // record the level of this tx + marked[tx.txIndex] = prevLevel + 1 + + default: + panic("unexpected case") + } + } + return levels +} diff --git a/core/parallel_state_scheduler_test.go b/core/parallel_state_scheduler_test.go new file mode 100644 index 0000000000..6908e00fa8 --- /dev/null +++ b/core/parallel_state_scheduler_test.go @@ -0,0 +1,887 @@ +package core + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/big" + "os" + "runtime" + "sync" + "sync/atomic" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" +) + +// mock transaction via ParallelTxRequest and ParallelTxResult. and the methods we need to mock is "transfer balance from on address to other" +// ParallelTxRequest: +// 1. usedGas -> value +// 2. staticSlotIndex -> fromAddress +// 3. runnable -> toAddress +// +// a mock transaction do the following steps when "execute" in parallel: +// 1. read balance of "fromAddress" from maindb +// 2. check balance and sub the amount of "value", records the "reads"(fromAddr->balance), write balance back into "slotDB" +// 3. read balance of "toAddress" from maindb +// 4. add the "value" to toAddress, records the "reads"(toAddr->balance), write it into "slotDB" +// +// a mock transaction do the following steps when "confirm": +// 1. check the "reads"(addresses->balances) in maindb +// 2. merge the "slotDB"(addresses->balances) into maindb + +type mockTx struct { + req *PEVMTxRequest + reads map[int]int + slotDB map[int]int +} + +var mockMainDB sync.Map + +func putMainDB(data map[int]int) { + for k, v := range data { + mockMainDB.Store(k, v) + } +} + +func checkMainDB(data map[int]int) bool { + for k, v := range data { + val, ok := mockMainDB.Load(k) + if !ok { + return false + } + if val.(int) != v { + return false + } + } + return true +} + +func newTxReq(from, to, value int) *PEVMTxRequest { + usedGas := uint64(value) + return &PEVMTxRequest{ + usedGas: &usedGas, + gasLimit: uint64(from), + value: to, + } +} + +func (mt *mockTx) Value() int { + return int(*mt.req.usedGas) +} + +func (mt *mockTx) From() int { + return int(mt.req.gasLimit) +} + +func (mt *mockTx) To() int { + return int(mt.req.value) +} + +func init() { + initParallelRunner(runtime.GOMAXPROCS(0)) +} + +func (mt *mockTx) execute(req *PEVMTxRequest) *PEVMTxResult { + result := &PEVMTxResult{ + txReq: req, + err: nil, + } + mt.req = req + // handle fromAddress + balance, ok := mockMainDB.Load(mt.From()) + if !ok { + result.err = fmt.Errorf("no balance from '%d'", mt.From()) + return result + } + if balance.(int) < mt.Value() { + result.err = fmt.Errorf("insufficient found, need '%d', have '%d', fromaddr:%d", mt.Value(), balance.(int), mt.From()) + return result + } + mt.reads[mt.From()] = balance.(int) + mt.slotDB[mt.From()] = balance.(int) - mt.Value() + + // handle toAddress + balance, ok = mockMainDB.Load(mt.To()) + if !ok { + result.err = fmt.Errorf("no balance from '%d'", mt.To()) + return result + } + mt.reads[mt.To()] = balance.(int) + mt.slotDB[mt.To()] = balance.(int) + mt.Value() + return result +} + +func (mt *mockTx) confirm(result *PEVMTxResult) error { + // check conflict + fromBalance, _ := mockMainDB.Load(mt.From()) + toBalance, _ := mockMainDB.Load(mt.To()) + if fromBalance.(int) != mt.reads[mt.From()] { + return fmt.Errorf("fromAddress state invalid") + } + if toBalance.(int) != mt.reads[mt.To()] { + return fmt.Errorf("toAddress state invalid") + } + // merge slotDB into mainDB + putMainDB(mt.slotDB) + return nil +} + +type caller struct { + lock sync.Mutex + txs map[*PEVMTxRequest]*mockTx + conflictNum int32 +} + +func (c *caller) execute(req *PEVMTxRequest) *PEVMTxResult { + mocktx := &mockTx{ + reads: make(map[int]int), + slotDB: make(map[int]int), + } + result := mocktx.execute(req) + c.lock.Lock() + c.txs[req] = mocktx + if result.err != nil { + atomic.AddInt32(&c.conflictNum, 1) + } + c.lock.Unlock() + return result +} + +func (c *caller) confirm(result *PEVMTxResult) error { + c.lock.Lock() + mtx := c.txs[result.txReq] + c.lock.Unlock() + err := mtx.confirm(result) + if err != nil { + atomic.AddInt32(&c.conflictNum, 1) + } + return err +} + +func TestParallelProcess(t *testing.T) { + // mock a state db,which provide: + // KV read/write (in memory) + // State read/write (in memory) ? + // Trie read/write (in memory) ? +} + +func TestTxLevelSplit(t *testing.T) { + // case 1: split empty levels + // case 2: split 1 level into 1 chunk + // case 3: split 1 level into 2 chunks, each have 1 tx + // case 4: split 1 level into 3 + txLevels := TxLevel{} + if len(txLevels.Split(1)) != 0 { + t.Fatalf("invalid split, expected:0, got:%d", len(txLevels.Split(1))) + } + txLevels = TxLevel{newTxReq(1, 2, 10)} + chunks := txLevels.Split(1) + if len(chunks) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + mock := &mockTx{req: chunks[0][0]} + if mock.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock.Value()) + } + + txLevels = TxLevel{newTxReq(1, 2, 10), newTxReq(2, 3, 20)} + chunks = txLevels.Split(2) + if len(chunks) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + mock1, mock2 := &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[1][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + + txLevels = TxLevel{newTxReq(1, 2, 10), newTxReq(2, 3, 20), newTxReq(3, 4, 30)} + chunks = txLevels.Split(2) + if len(chunks) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks)) + } + if len(chunks[0]) != 2 { + t.Fatalf("invalid split, expected:2, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + mock1, mock2, mock3 := &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[0][1]}, &mockTx{req: chunks[1][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + if mock3.Value() != 30 { + t.Fatalf("invalid split, expected:30, got:%d", mock3.Value()) + } + + chunks = txLevels.Split(5) + if len(chunks) != 3 { + t.Fatalf("invalid split, expected:3, got:%d", len(chunks)) + } + if len(chunks[0]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[0])) + } + if len(chunks[1]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[1])) + } + if len(chunks[2]) != 1 { + t.Fatalf("invalid split, expected:1, got:%d", len(chunks[2])) + } + mock1, mock2, mock3 = &mockTx{req: chunks[0][0]}, &mockTx{req: chunks[1][0]}, &mockTx{req: chunks[2][0]} + if mock1.Value() != 10 { + t.Fatalf("invalid split, expected:10, got:%d", mock1.Value()) + } + if mock2.Value() != 20 { + t.Fatalf("invalid split, expected:20, got:%d", mock2.Value()) + } + if mock3.Value() != 30 { + t.Fatalf("invalid split, expected:30, got:%d", mock3.Value()) + } + +} + +func setTxIndex(allReq []*PEVMTxRequest) { + for i, req := range allReq { + req.txIndex = i + } +} + +func TestTxLevelRun(t *testing.T) { + // case 1: empty txs + case1 := func() { + levels([]uint64{}, [][]int{}).Run(func(*PEVMTxRequest) *PEVMTxResult { return nil }, func(*PEVMTxResult) error { return nil }, false) + } + // case 2: 4 txs with no dependencies, no conflicts + case2 := func() { + // mainDB: [1: 10, 2: 20, 3: 30, 4:40, 5:1, 6:1, 7:1, 8:1] + // txs: [1,2,3,4] ->(all balances)-> [5,6,7,8] + // true txdag: [][]int{nil, nil, nil, nil} + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21, 7:31, 8:41] + // conflictNum: 0 + putMainDB(map[int]int{1: 10, 2: 20, 3: 30, 4: 40, 5: 1, 6: 1, 7: 1, 8: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 5, 10), + newTxReq(2, 6, 20), + newTxReq(3, 7, 30), + newTxReq(4, 8, 40), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + nil, nil, nil, nil, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21, 7: 31, 8: 41}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // case 3: 4 txs with 2 dependencies, no conflict + // actual: t1->t0, t3->t2 + // true txdag: [t0, t2], [t3, t4] + // result: all of t1~t4 need rerun + case3 := func() { + // mainDB: [1: 10, 2: 20, 3: 0, 4:0, 5:1, 6:1] + // txs: [1->3:10, 2->4:20, 3->5:10, 4->6:20] + // true txdag: [][]int{nil, nil, {0}, {1}}) + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21] + // conflictNum: 0 + putMainDB(map[int]int{1: 10, 2: 20, 3: 0, 4: 0, 5: 1, 6: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 3, 10), + newTxReq(2, 4, 20), + newTxReq(3, 5, 10), + newTxReq(4, 6, 20), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + nil, nil, {0}, {1}, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // case 4: 4 txs with 2 dependencies, with 4 conflict + // actual: t1->t0, t3->t2 + // false txdag: [t4],[t3],[t0, t1] + // result: all of t1~t4 need rerun + case4 := func() { + // mainDB: [1: 10, 2: 20, 3: 0, 4:0, 5:1, 6:1] + // txs: [1->3:10, 2->4:20, 3->5:10, 4->6:20] + // false txdag: {0}, nil, {-1}, {-1}, + // result: + // mainDB: [1: 0, 2: 0, 3: 0, 4:0, 5:11, 6:21] + // conflictNum: 2 + putMainDB(map[int]int{1: 10, 2: 20, 3: 0, 4: 0, 5: 1, 6: 1}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 3, 10), + newTxReq(2, 4, 20), + newTxReq(3, 5, 10), + newTxReq(4, 6, 20), + } + setTxIndex(allReqs) + txdag := int2txdag([][]int{ + {0}, nil, {-1}, {-1}, + }) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, txdag).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(map[int]int{1: 0, 2: 0, 3: 0, 4: 0, 5: 11, 6: 21}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + } + + // smoking test + case5 := func() { + // addressSetA: 1~1000, addressSetB: 1001~2000, addressSetC: 2001~3000 + // store balance 1~1000 for addressSetA into mainDB + // A -> B -> C + // check balance of C + // txdag: all in level 0, some conflict will come up + storeBalance := func(from, to int, value int) { + for i := from; i <= to; i++ { + if value == 0 { + mockMainDB.Store(i, 0) + } else { + mockMainDB.Store(i, i) + } + } + } + storeBalance(1, 1000, 1) + storeBalance(1001, 2000, 0) + storeBalance(2001, 3000, 0) + allReqs := make([]*PEVMTxRequest, 0, 2000) + // addressSetA -> addressSetB + for i := 1; i <= 1000; i++ { + allReqs = append(allReqs, newTxReq(i, i+1000, i)) + } + // addressSetB -> addressSetC + for i := 1; i <= 1000; i++ { + allReqs = append(allReqs, newTxReq(i+1000, i+2000, i)) + } + setTxIndex(allReqs) + // result of addressSetC + res := make(map[int]int, 1000) + for i := 1; i <= 1000; i++ { + res[i+2000] = i + } + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(res) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum <= 0 { + t.Fatalf("conflict not found, conflict:%d", caller.conflictNum) + } + } + // case 6: txs with dependencies, but no txdag + case6 := func() { + // mainDB: [1: 15, 2: 20, 3: 0] + // txs: [1->2:10, 2->3:10] + // txdag: nil + // result: + // mainDB: [1: 5, 2: 20, 3: 10] + putMainDB(map[int]int{1: 15, 2: 20, 3: 0}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 2, 10), + newTxReq(2, 3, 10), + } + setTxIndex(allReqs) + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + err, _ := NewTxLevels(allReqs, nil).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + } + + // case 7: txs with dependencies, with default txdag (deposit tx + all comment txs) + case7 := func() { + //dag := &types.PlainTxDAG{} + //dag.SetTxDep(0, types.TxDep{TxIndexes: nil, Flags: &types.ExcludedTxFlag}) + dag := int2txdag([][]int{ + {-1}, nil, + }) + // mainDB: [1: 15, 2: 20, 3: 0] + // txs: [1->2:10, 2->3:10] + // txdag: [-1, nil] + // result: + // mainDB: [1: 5, 2: 20, 3: 10] + putMainDB(map[int]int{1: 15, 2: 20, 3: 0}) + allReqs := []*PEVMTxRequest{ + newTxReq(1, 2, 10), + newTxReq(2, 3, 10), + } + caller := caller{txs: make(map[*PEVMTxRequest]*mockTx)} + setTxIndex(allReqs) + err, _ := NewTxLevels(allReqs, dag).Run(caller.execute, caller.confirm, false) + ok := checkMainDB(map[int]int{1: 5, 2: 20, 3: 10}) + if err != nil { + t.Fatalf("failed, err:%v", err) + } + if !ok { + t.Fatalf("invalid mainDB state") + } + if caller.conflictNum != 0 { + t.Fatalf("conflict found, conflict:%d", caller.conflictNum) + } + + } + + case1() + case2() + case3() + case4() + case5() + case6() + case7() +} + +func TestPredictTxDAG(t *testing.T) { + var buildTxReq = func(from, to common.Address, txIndex int) *PEVMTxRequest { + return &PEVMTxRequest{ + txIndex: txIndex, + msg: &Message{ + From: from, + To: &to, + }, + } + } + + var buildLevels = func(addresses [][2]common.Address) TxLevel { + tl := make(TxLevel, 0, len(addresses)) + for i, addr := range addresses { + tl = append(tl, buildTxReq(addr[0], addr[1], i)) + } + return tl + } + + // case 1. empty txs + var addr1, addr2, addr3, addr4 = common.Address{1}, common.Address{2}, common.Address{3}, common.Address{4} + var dag = &types.PlainTxDAG{} + txs := buildLevels([][2]common.Address{}) + txs.predictTxDAG(dag) + newLevel := NewTxLevels(txs, dag) + if len(newLevel) != 0 { + t.Fatalf("failed, expected empty") + } + + // case 2. 1 tx with no dependencies + txs = buildLevels([][2]common.Address{{addr1, addr2}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 1 { + t.Fatalf("failed, expected 1, got:%d", len(newLevel)) + } + if newLevel[0][0].msg.From != addr1 { + t.Fatalf("failed, expected addr1, got:%v", newLevel[0][0].msg.From) + } + // case 3. 3 txs with 2 dependencies, a->b, b->c + txs = buildLevels([][2]common.Address{{addr1, addr2}, {addr2, addr3}, {addr3, addr4}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 3 { + t.Fatalf("failed, expected 3, got:%d", len(newLevel)) + } + if newLevel[0][0].msg.From != addr1 || newLevel[1][0].msg.From != addr2 || newLevel[2][0].msg.From != addr3 { + t.Fatalf("failed, expected addr1, addr2, addr3, got:%v, %v, %v", newLevel[0][0].msg.From, newLevel[1][0].msg.From, newLevel[2][0].msg.From) + } + // case 4. 4 txs with 3 dependencies, a->b, b->c, d->b + txs = buildLevels([][2]common.Address{{addr1, addr2}, {addr2, addr3}, {addr4, addr2}, {addr3, addr1}}) + dag = &types.PlainTxDAG{} + txs.predictTxDAG(dag) + newLevel = NewTxLevels(txs, dag) + if len(newLevel) != 3 { + t.Fatalf("failed, expected 3, got:%d", len(newLevel)) + } + if len(newLevel[0]) != 1 || len(newLevel[1]) != 1 || len(newLevel[2]) != 2 { + t.Fatalf("failed, expected 1, 1, 2, got:%d, %d, %d", len(newLevel[0]), len(newLevel[1]), len(newLevel[2])) + } + if newLevel[0][0].msg.From != addr1 || newLevel[1][0].msg.From != addr2 { + t.Fatalf("failed, expected addr1, addr2, got:%v, %v", newLevel[0][0].msg.From, newLevel[1][0].msg.From) + } +} + +func TestNewTxLevels(t *testing.T) { + // definition of dependencies: + // {-1} means dependent all txs + // {-2} means excluded tx + // nil means no dependencies + // {0,1} means dependent on tx[0] and tx[1] + + // case 1: empty txs + assertEqual(levels([]uint64{}, [][]int{}), [][]uint64{}, t) + + // case 2: txs with no dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] has no dependencies, tx[1].Nonce() == 2 + // tx[2] has no dependencies, tx[2].Nonce() == 3 + // tx[3] has no dependencies, tx[3].Nonce() == 4 + assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{nil, nil, nil, nil}), [][]uint64{{1, 2, 3, 4}}, t) + + // case 3: txs with dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] depends on tx[0], tx[1].Nonce() == 2 + // tx[2] depends on tx[1], tx[2].Nonce() == 3 + assertEqual(levels([]uint64{1, 2, 3}, [][]int{nil, {0}, {1}}), [][]uint64{{1}, {2}, {3}}, t) + + // case 4: txs with dependencies and no dependencies + // tx[0] has no dependencies, tx[0].Nonce() == 1 + // tx[1] has no dependencies, tx[1].Nonce() == 2 + // tx[2] dependents on t[0], tx[2].Nonce() == 3 + // tx[3] dependents on t[0], tx[3].Nonce() == 4 + // tx[4] dependents on t[2], tx[4].Nonce() == 5 + // tx[5] dependents on t[3], tx[5].Nonce() == 6 + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6}, [][]int{nil, nil, {0}, {1}, {2}, {3}}), [][]uint64{{1, 2}, {3, 4}, {5, 6}}, t) + + // case 5: 1 excluded tx + n no dependencies tx + assertEqual(levels([]uint64{1, 2, 3}, [][]int{{-1}, nil, nil}), [][]uint64{{1}, {2, 3}}, t) + + // case 6: 1 excluded tx + n no dependencies txs + n all-dependencies txs + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6}, [][]int{{-1}, nil, nil, nil, {-2}, {-2}}), [][]uint64{{1}, {2, 3, 4}, {5}, {6}}, t) + + // case 7: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7}, [][]int{{-1}, nil, nil, nil, {0, 1}, {2}, {-2}}), [][]uint64{{1}, {2, 3, 4}, {5, 6}, {7}}, t) + + // case 8: n no dependencies txs + n all-dependencies txs + assertEqual(levels([]uint64{1, 2, 3, 4, 5}, [][]int{nil, nil, nil, {-2}, {-2}}), [][]uint64{{1, 2, 3}, {4}, {5}}, t) + + // case 9: loop-back txdag + assertEqual(levels([]uint64{1, 2, 3, 4}, [][]int{{1}, nil, {0}, nil}), [][]uint64{{1, 2, 4}, {3}}, t) +} + +func TestMultiLevel(t *testing.T) { + // case 7: 1 excluded tx + n no dependencies txs + n dependencies txs + 1 all-dependencies tx + assertEqual(levels([]uint64{1, 2, 3, 4, 5, 6, 7, 8}, [][]int{nil, nil, nil, {0}, nil, {1}, nil, {2}}), [][]uint64{{1, 2, 3, 5, 7}, {4, 6, 8}}, t) +} + +func levels(nonces []uint64, txdag [][]int) TxLevels { + return NewTxLevels(nonces2txs(nonces), int2txdag(txdag)) +} + +func nonces2txs(nonces []uint64) []*PEVMTxRequest { + rq := make([]*PEVMTxRequest, len(nonces)) + for i, nonce := range nonces { + if nonce == 0 { + rq[i] = nil + } else { + rq[i] = &PEVMTxRequest{tx: types.NewTransaction(nonce, common.Address{}, nil, 0, nil, nil), txIndex: i} + } + } + return rq +} + +func int2txdag(txdag [][]int) types.TxDAG { + dag := types.PlainTxDAG{} + for i, deps := range txdag { + var dep types.TxDep + switch true { + case len(deps) == 0: + dep = types.TxDep{TxIndexes: nil, Flags: nil} + case deps[0] == -1: + dep = types.TxDep{TxIndexes: nil, Flags: &types.ExcludedTxFlag} + case deps[0] == -2: + dep = types.TxDep{TxIndexes: nil, Flags: &types.NonDependentRelFlag} + default: + converted := make([]uint64, len(deps)) + for j, dep := range deps { + converted[j] = uint64(dep) + } + dep = types.TxDep{TxIndexes: converted, Flags: nil} + } + if err := dag.SetTxDep(i, dep); err != nil { + panic("wrong txdag") + } + } + return &dag +} + +func assertEqual(actual TxLevels, expected [][]uint64, t *testing.T) { + if len(actual) != len(expected) { + t.Fatalf("expected %d levels, got %d levels", len(expected), len(actual)) + return + } + for i, txLevel := range actual { + if len(txLevel) != len(expected[i]) { + t.Fatalf("expected %d txs in level %d, got %d txs", len(expected[i]), i, len(txLevel)) + return + } + for j, tx := range txLevel { + if tx.tx.Nonce() != expected[i][j] { + t.Fatalf("expected nonce: %d, got nonce: %d", tx.tx.Nonce(), expected[i][j]) + } + } + } +} + +func assertEqual2(actual TxLevels, expected [][]uint64, t *testing.T) { + if len(actual) != len(expected) { + t.Fatalf("expected %d levels, got %d levels", len(expected), len(actual)) + return + } + // reverse all levels + for i := 0; i < len(actual); i++ { + for j := 0; j < len(actual[i])/2; j++ { + actual[i][j], actual[i][len(actual[i])-1-j] = actual[i][len(actual[i])-1-j], actual[i][j] + } + } + for i, txLevel := range actual { + if len(txLevel) != len(expected[i]) { + t.Fatalf("expected %d txs in level %d, got %d txs", len(expected[i]), i, len(txLevel)) + return + } + for j, tx := range txLevel { + if tx.tx.Nonce() != expected[i][j] { + t.Fatalf("expected nonce: %d, got nonce: %d", tx.tx.Nonce(), expected[i][j]) + } + } + } +} + +func getBlockTransactions(url string, blockNumber int64) (types.Transactions, error) { + client, err := ethclient.Dial(url) + if err != nil { + return nil, fmt.Errorf("failed to connect to the Ethereum client: %v", err) + } + + block, err := client.BlockByNumber(context.Background(), big.NewInt(blockNumber)) + if err != nil { + return nil, fmt.Errorf("failed to get block: %v", err) + } + + return block.Transactions(), nil +} + +func loadChainConfigFromGenesis(filePath string) (*params.ChainConfig, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, fmt.Errorf("failed to open genesis file: %v", err) + } + defer file.Close() + + byteValue, err := ioutil.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("failed to read genesis file: %v", err) + } + + var genesis Genesis + if err := json.Unmarshal(byteValue, &genesis); err != nil { + return nil, fmt.Errorf("failed to unmarshal genesis file: %v", err) + } + + return genesis.Config, nil +} + +func TestShowDAG(t *testing.T) { + //// Example usage + //genesisFilePath := "/Users/awen/Desktop/Nodereal/aweneagle_projects/chain-infra/qa/gitops/qa-us/opbnb-qanet-ec-5/contracts-info/genesis.json" + //chainConfig, err := loadChainConfigFromGenesis(genesisFilePath) + //if err != nil { + // log.Fatalf("Failed to load chain config: %v", err) + //} + //signer := types.LatestSigner(chainConfig) + //_, dag, err := readTxDAGItemFromLine(block8966()) + //if err != nil { + // panic(err) + //} + //txs, err := getBlockTransactions("https://opbnb-qanet-ec-5-seq-pevm-2.bk.nodereal.cc", 8966) + //if err != nil { + // panic(err) + //} + ////check dag[0]; it should be a deposit tx, and should have a exclude flag + //if !dag.TxDep(0).CheckFlag(types.ExcludedTxFlag) { + // panic("broken dag, deposit tx should have exclude flag") + //} + //var dependTo = func(a *types.Transaction, b *types.Transaction) bool { + // if a.Nonce() <= b.Nonce() { + // return false + // } + // aFromAddr, err := types.Sender(signer, a) + // if err != nil { + // panic(err) + // } + // bFromAddr, err := types.Sender(signer, b) + // if err != nil { + // panic(err) + // } + // aToAddr, bToAddr := *(a.To()), *(b.To()) + + // if aFromAddr.Cmp(bFromAddr) == 0 || aFromAddr.Cmp(bToAddr) == 0 { + // return true + // } + // return aToAddr.Cmp(bFromAddr) == 0 || aToAddr.Cmp(bToAddr) == 0 + //} + //fmt.Printf("total tx:%d, dag tx:%d\n", len(txs), dag.TxCount()) + //depCorrect, depWrong := 1, 0 + //for i := 1; i < len(txs); i++ { + // var deps = map[int]struct{}{} + // // no need to add tx[0], must be dependent on tx[0] + // for j := 1; j < i; j++ { + // if dependTo(txs[i], txs[j]) { + // deps[j] = struct{}{} + // } + // } + // ofrom, _ := types.Sender(signer, txs[i]) + // oto := txs[i].To() + // temp := dag.TxDep(i).TxIndexes + // // filter out 0 + // originDeps := make([]uint64, 0, len(temp)) + // for _, j := range temp { + // if j != 0 { + // originDeps = append(originDeps, j) + // } + // } + // passed := true + // if len(deps) != len(originDeps) { + // fmt.Printf("tx[%d:%s] has %d deps, but dag has %d deps\n", i, txs[i].Hash().String(), len(deps), len(originDeps)) + // passed = false + // //expected: + // for ti := range originDeps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("expected tindex=%d, from=%s, to=%s => tindex=%d, from=%s, to=%s\n", i, ofrom, oto, ti, from, to) + // } + // //actual: + // if len(deps) > 0 { + // for ti := range deps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("tx[%d] actual from=%s, to=%s, tindex=%d, from=%s, to=%s txhash=%s dag:%v, dagFlag:%d\n", i, ofrom, oto, ti, from, to, txs[ti].Hash().String(), dag.TxDep(ti).TxIndexes, dag.TxDep(ti).Flags) + // } + // } else { + // fmt.Printf("actual tindex=%d, from=%s, to=%s => no txs\n", i, ofrom, oto) + // } + // } else { + // for _, j := range originDeps { + // if _, ok := deps[int(j)]; !ok { + // fmt.Printf("tx[%d:%s] has %d deps, but dag has %d deps\n", i, txs[i].Hash().String(), len(deps), len(originDeps)) + // passed = false + // } + // } + // if !passed { + // //expected: + // for ti := range originDeps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("expected tindex=%d, from=%s, to=%s => tindex=%d, from=%s, to=%s\n", i, ofrom, oto, ti, from, to) + // } + // //actual: + // if len(deps) > 0 { + // for ti := range deps { + // from, _ := types.Sender(signer, txs[ti]) + // to := txs[ti].To() + // fmt.Printf("tx[%d] actual from=%s, to=%s, tindex=%d, from=%s, to=%s txhash=%s dag:%v, dagFlag:%d\n", i, ofrom, oto, ti, from, to, txs[ti].Hash().String(), dag.TxDep(ti).TxIndexes, dag.TxDep(ti).Flags) + // } + // } else { + // fmt.Printf("actual tindex=%d, from=%s, to=%s => no txs\n", i, ofrom, oto) + // } + // } + // } + // if passed { + // depCorrect++ + // fmt.Printf("tx[%d:%s] passed =====\n", i, txs[i].Hash().String()) + // } else { + // fmt.Printf("tx[%d:%s] failed =====\n", i, txs[i].Hash().String()) + // depWrong++ + // } + //} + + //fmt.Printf("total tx:%d, total dep:%d, depCorrect:%d, depWrong:%d\n", len(txs), dag.TxCount(), depCorrect, depWrong) +} + +func TestNewLevel(t *testing.T) { + //fetchTxIndexes := func(level TxLevel) []int { + // indexes := make([]int, len(level)) + // for i, tx := range level { + // indexes[i] = tx.txIndex + // } + // return indexes + //} + //dag := txDAG(txDAGData()) + //fmt.Printf("txCount:%d\n", dag.TxCount()) + //nonces := make([]uint64, dag.TxCount()) + //for i := 0; i < dag.TxCount(); i++ { + // nonces[i] = uint64(i) + 1 + //} + //txs := nonces2txs(nonces) + //fmt.Printf("txs:%d\n", len(txs)) + //levels := NewTxLevels(txs, dag) + //fmt.Printf("levels:%d\n", len(levels)) + //for i := 0; i < len(levels); i++ { + // fmt.Printf("level [%d]: len=%d, elements:%v\n", i, len(levels[i]), fetchTxIndexes(levels[i])) + //} +} + +func txDAG(encode []byte) types.TxDAG { + if dag, err := types.DecodeTxDAGCalldata(encode); err != nil { + panic(err) + } else { + return dag + } +} + +func txDAGData() []byte { + data := string("0x5517ed8c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000012bd01f912b9f912b6c2c002c1c0c1c0c2c102c2c103c2c104c2c105c2c106c2c107c2c108c2c109c2c10ac2c10bc2c10cc2c10dc2c10ec2c10fc2c110c2c111c2c112c2c113c2c114c2c115c2c116c2c117c2c118c2c119c2c11ac2c11bc2c11cc2c11dc2c11ec2c11fc2c120c2c121c2c122c2c123c2c124c2c125c2c126c2c127c2c128c2c129c2c12ac2c12bc2c12cc2c12dc2c12ec2c12fc2c130c2c131c2c132c2c133c2c134c2c135c2c136c2c137c2c138c2c139c2c13ac2c13bc2c13cc2c13dc2c13ec2c13fc2c140c2c141c2c142c2c143c2c144c2c145c2c146c2c147c2c148c2c149c2c14ac2c14bc2c14cc2c14dc2c14ec2c14fc2c150c2c151c2c152c2c153c2c154c2c155c2c156c2c157c2c158c2c159c2c15ac2c15bc2c15cc2c15dc2c15ec2c15fc2c160c2c161c2c162c2c163c2c164c2c165c2c166c2c167c2c168c2c169c2c16ac2c16bc2c16cc2c16dc2c16ec2c16fc2c170c2c171c2c172c2c173c2c174c2c175c2c176c2c177c2c178c2c179c2c17ac2c17bc2c17cc2c17dc2c17ec2c17fc3c28180c3c28181c3c28182c3c28183c3c28184c3c28185c3c28186c3c28187c3c28188c3c28189c3c2818ac3c2818bc3c2818cc3c2818dc3c2818ec3c2818fc3c28190c3c28191c3c28192c3c28193c3c28194c3c28195c3c28196c3c28197c3c28198c3c28199c3c2819ac3c2819bc3c2819cc3c2819dc3c2819ec3c2819fc3c281a0c3c281a1c3c281a2c3c281a3c3c281a4c3c281a5c3c281a6c3c281a7c3c281a8c3c281a9c3c281aac3c281abc3c281acc3c281adc3c281aec3c281afc3c281b0c3c281b1c3c281b2c3c281b3c3c281b4c3c281b5c3c281b6c3c281b7c3c281b8c3c281b9c3c281bac3c281bbc3c281bcc3c281bdc3c281bec3c281bfc3c281c0c3c281c1c3c281c2c3c281c3c3c281c4c3c281c5c3c281c6c3c281c7c3c281c8c3c281c9c3c281cac3c281cbc3c281ccc3c281cdc3c281cec3c281cfc3c281d0c3c281d1c3c281d2c3c281d3c3c281d4c3c281d5c3c281d6c3c281d7c3c281d8c3c281d9c3c281dac3c281dbc3c281dcc3c281ddc3c281dec3c281dfc3c281e0c3c281e1c3c281e2c3c281e3c3c281e4c3c281e5c3c281e6c3c281e7c3c281e8c3c281e9c3c281eac3c281ebc3c281ecc3c281edc3c281eec3c281efc3c281f0c3c281f1c3c281f2c3c281f3c3c281f4c3c281f5c3c281f6c3c281f7c3c281f8c3c281f9c3c281fac3c281fbc3c281fcc3c281fdc3c281fec3c281ffc4c3820100c4c3820101c4c3820102c4c3820103c4c3820104c4c3820105c4c3820106c4c3820107c4c3820108c4c3820109c4c382010ac4c382010bc4c382010cc4c382010dc4c382010ec4c382010fc4c3820110c4c3820111c4c3820112c4c3820113c4c3820114c4c3820115c4c3820116c4c3820117c4c3820118c4c3820119c4c382011ac4c382011bc4c382011cc4c382011dc4c382011ec4c382011fc4c3820120c4c3820121c4c3820122c4c3820123c4c3820124c4c3820125c4c3820126c4c3820127c4c3820128c4c3820129c4c382012ac4c382012bc4c382012cc4c382012dc4c382012ec4c382012fc4c3820130c4c3820131c4c3820132c4c3820133c4c3820134c4c3820135c4c3820136c4c3820137c4c3820138c4c3820139c4c382013ac4c382013bc4c382013cc4c382013dc4c382013ec4c382013fc4c3820140c4c3820141c4c3820142c4c3820143c4c3820144c4c3820145c4c3820146c4c3820147c4c3820148c4c3820149c4c382014ac4c382014bc4c382014cc4c382014dc4c382014ec4c382014fc4c3820150c4c3820151c4c3820152c4c3820153c4c3820154c4c3820155c4c3820156c4c3820157c4c3820158c4c3820159c4c382015ac4c382015bc4c382015cc4c382015dc4c382015ec4c382015fc4c3820160c4c3820161c4c3820162c4c3820163c4c3820164c4c3820165c4c3820166c4c3820167c4c3820168c4c3820169c4c382016ac4c382016bc4c382016cc4c382016dc4c382016ec4c382016fc4c3820170c4c3820171c4c3820172c4c3820173c4c3820174c4c3820175c4c3820176c4c3820177c4c3820178c4c3820179c4c382017ac4c382017bc4c382017cc4c382017dc4c382017ec4c382017fc4c3820180c4c3820181c4c3820182c4c3820183c4c3820184c4c3820185c4c3820186c4c3820187c4c3820188c4c3820189c4c382018ac4c382018bc4c382018cc4c382018dc4c382018ec4c382018fc4c3820190c4c3820191c4c3820192c4c3820193c4c3820194c4c3820195c4c3820196c4c3820197c4c3820198c4c3820199c4c382019ac4c382019bc4c382019cc4c382019dc4c382019ec4c382019fc4c38201a0c4c38201a1c4c38201a2c4c38201a3c4c38201a4c4c38201a5c4c38201a6c4c38201a7c4c38201a8c4c38201a9c4c38201aac4c38201abc4c38201acc4c38201adc4c38201aec4c38201afc4c38201b0c4c38201b1c4c38201b2c4c38201b3c4c38201b4c4c38201b5c4c38201b6c4c38201b7c4c38201b8c4c38201b9c4c38201bac4c38201bbc4c38201bcc4c38201bdc4c38201bec4c38201bfc4c38201c0c4c38201c1c4c38201c2c4c38201c3c4c38201c4c4c38201c5c4c38201c6c4c38201c7c4c38201c8c4c38201c9c4c38201cac4c38201cbc4c38201ccc4c38201cdc4c38201cec4c38201cfc4c38201d0c4c38201d1c4c38201d2c4c38201d3c4c38201d4c4c38201d5c4c38201d6c4c38201d7c4c38201d8c4c38201d9c4c38201dac4c38201dbc4c38201dcc4c38201ddc4c38201dec4c38201dfc4c38201e0c4c38201e1c4c38201e2c4c38201e3c4c38201e4c4c38201e5c4c38201e6c4c38201e7c4c38201e8c4c38201e9c4c38201eac4c38201ebc4c38201ecc4c38201edc4c38201eec4c38201efc4c38201f0c4c38201f1c4c38201f2c4c38201f3c4c38201f4c4c38201f5c4c38201f6c4c38201f7c4c38201f8c4c38201f9c4c38201fac4c38201fbc4c38201fcc4c38201fdc4c38201fec4c38201ffc4c3820200c4c3820201c4c3820202c4c3820203c4c3820204c4c3820205c4c3820206c4c3820207c4c3820208c4c3820209c4c382020ac4c382020bc4c382020cc4c382020dc4c382020ec4c382020fc4c3820210c4c3820211c4c3820212c4c3820213c4c3820214c4c3820215c4c3820216c4c3820217c4c3820218c4c3820219c4c382021ac4c382021bc4c382021cc4c382021dc4c382021ec4c382021fc4c3820220c4c3820221c4c3820222c4c3820223c4c3820224c4c3820225c4c3820226c4c3820227c4c3820228c4c3820229c4c382022ac4c382022bc4c382022cc4c382022dc4c382022ec4c382022fc4c3820230c4c3820231c4c3820232c4c3820233c4c3820234c4c3820235c4c3820236c4c3820237c4c3820238c4c3820239c4c382023ac4c382023bc4c382023cc4c382023dc4c382023ec4c382023fc4c3820240c4c3820241c4c3820242c4c3820243c4c3820244c4c3820245c4c3820246c4c3820247c4c3820248c4c3820249c4c382024ac4c382024bc4c382024cc4c382024dc4c382024ec4c382024fc4c3820250c4c3820251c4c3820252c4c3820253c4c3820254c4c3820255c4c3820256c4c3820257c4c3820258c4c3820259c4c382025ac4c382025bc4c382025cc4c382025dc4c382025ec4c382025fc4c3820260c4c3820261c4c3820262c4c3820263c4c3820264c4c3820265c4c3820266c4c3820267c4c3820268c4c3820269c4c382026ac4c382026bc4c382026cc4c382026dc4c382026ec4c382026fc4c3820270c4c3820271c4c3820272c4c3820273c4c3820274c4c3820275c4c3820276c4c3820277c4c3820278c4c3820279c4c382027ac4c382027bc4c382027cc4c382027dc4c382027ec4c382027fc4c3820280c4c3820281c4c3820282c4c3820283c4c3820284c4c3820285c4c3820286c4c3820287c4c3820288c4c3820289c4c382028ac4c382028bc4c382028cc4c382028dc4c382028ec4c382028fc4c3820290c4c3820291c4c3820292c4c3820293c4c3820294c4c3820295c4c3820296c4c3820297c4c3820298c4c3820299c4c382029ac4c382029bc4c382029cc4c382029dc4c382029ec4c382029fc4c38202a0c4c38202a1c4c38202a2c4c38202a3c4c38202a4c4c38202a5c4c38202a6c4c38202a7c4c38202a8c4c38202a9c4c38202aac4c38202abc4c38202acc4c38202adc4c38202aec4c38202afc4c38202b0c4c38202b1c4c38202b2c4c38202b3c4c38202b4c4c38202b5c4c38202b6c4c38202b7c4c38202b8c4c38202b9c4c38202bac4c38202bbc4c38202bcc4c38202bdc4c38202bec4c38202bfc4c38202c0c4c38202c1c4c38202c2c4c38202c3c4c38202c4c4c38202c5c4c38202c6c4c38202c7c4c38202c8c4c38202c9c4c38202cac4c38202cbc4c38202ccc4c38202cdc4c38202cec4c38202cfc4c38202d0c4c38202d1c4c38202d2c4c38202d3c4c38202d4c4c38202d5c4c38202d6c4c38202d7c4c38202d8c4c38202d9c4c38202dac4c38202dbc4c38202dcc4c38202ddc4c38202dec4c38202dfc4c38202e0c4c38202e1c4c38202e2c4c38202e3c4c38202e4c4c38202e5c4c38202e6c4c38202e7c4c38202e8c4c38202e9c4c38202eac4c38202ebc4c38202ecc4c38202edc4c38202eec4c38202efc4c38202f0c4c38202f1c4c38202f2c4c38202f3c4c38202f4c4c38202f5c4c38202f6c4c38202f7c4c38202f8c4c38202f9c4c38202fac4c38202fbc4c38202fcc4c38202fdc4c38202fec4c38202ffc4c3820300c4c3820301c4c3820302c4c3820303c4c3820304c4c3820305c4c3820306c4c3820307c4c3820308c4c3820309c4c382030ac4c382030bc4c382030cc4c382030dc4c382030ec4c382030fc4c3820310c4c3820311c4c3820312c4c3820313c4c3820314c4c3820315c4c3820316c4c3820317c4c3820318c4c3820319c4c382031ac4c382031bc4c382031cc4c382031dc4c382031ec4c382031fc4c3820320c4c3820321c4c3820322c4c3820323c4c3820324c4c3820325c4c3820326c4c3820327c4c3820328c4c3820329c4c382032ac4c382032bc4c382032cc4c382032dc4c382032ec4c382032fc4c3820330c4c3820331c4c3820332c4c3820333c4c3820334c4c3820335c4c3820336c4c3820337c4c3820338c4c3820339c4c382033ac4c382033bc4c382033cc4c382033dc4c382033ec4c382033fc4c3820340c4c3820341c4c3820342c4c3820343c4c3820344c4c3820345c4c3820346c4c3820347c4c3820348c4c3820349c4c382034ac4c382034bc4c382034cc4c382034dc4c382034ec4c382034fc4c3820350c4c3820351c4c3820352c4c3820353c4c3820354c4c3820355c4c3820356c4c3820357c4c3820358c4c3820359c4c382035ac4c382035bc4c382035cc4c382035dc4c382035ec4c382035fc4c3820360c4c3820361c4c3820362c4c3820363c4c3820364c4c3820365c4c3820366c4c3820367c4c3820368c4c3820369c4c382036ac4c382036bc4c382036cc4c382036dc4c382036ec4c382036fc4c3820370c4c3820371c4c3820372c4c3820373c4c3820374c4c3820375c4c3820376c4c3820377c4c3820378c4c3820379c4c382037ac4c382037bc4c382037cc4c382037dc4c382037ec4c382037fc4c3820380c4c3820381c4c3820382c4c3820383c4c3820384c4c3820385c4c3820386c4c3820387c4c3820388c4c3820389c4c382038ac4c382038bc4c382038cc4c382038dc4c382038ec4c382038fc4c3820390c4c3820391c4c3820392c4c3820393c4c3820394c4c3820395c4c3820396c4c3820397c4c3820398c4c3820399c4c382039ac4c382039bc4c382039cc4c382039dc4c382039ec4c382039fc4c38203a0c4c38203a1c4c38203a2c4c38203a3c4c38203a4c4c38203a5c4c38203a6c4c38203a7c4c38203a8c4c38203a9c4c38203aac4c38203abc4c38203acc4c38203adc4c38203aec4c38203afc4c38203b0c4c38203b1c4c38203b2c4c38203b3c4c38203b4c4c38203b5c4c38203b6c4c38203b7c4c38203b8c4c38203b9c4c38203bac4c38203bbc4c38203bcc4c38203bdc4c38203bec4c38203bfc4c38203c0c4c38203c1c4c38203c2c4c38203c3c4c38203c4c4c38203c5c4c38203c6c4c38203c7c4c38203c8c4c38203c9c4c38203cac4c38203cbc4c38203ccc4c38203cdc4c38203cec4c38203cfc4c38203d0c4c38203d1c4c38203d2c4c38203d3c4c38203d4c4c38203d5c4c38203d6c4c38203d7c4c38203d8c4c38203d9c4c38203dac4c38203dbc4c38203dcc4c38203ddc4c38203dec4c38203dfc4c38203e0c4c38203e1c4c38203e2c4c38203e3c4c38203e4c4c38203e5c4c38203e6c4c38203e7c4c38203e8c4c38203e9c4c38203eac4c38203ebc4c38203ecc4c38203edc4c38203eec4c38203efc4c38203f0c4c38203f1c4c38203f2c4c38203f3c4c38203f4c4c38203f5c4c38203f6c4c38203f7c4c38203f8c4c38203f9c4c38203fac4c38203fbc4c38203fcc4c38203fdc4c38203fec4c38203ffc4c3820400c4c3820401c4c3820402c4c3820403c4c3820404c4c3820405c4c3820406c4c3820407c4c3820408c4c3820409c2c001000000") + return common.Hex2Bytes(data[2:]) +} + +func block340670() string { + return string("340670,01f948a4f948a1c2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c10ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c119c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c16ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28195c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c144c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c111c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c132c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c170c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c126c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820134c1c0c3c281b0c1c0c1c0c1c0c4c382017bc2c144c1c0c3c281d8c1c0c1c0c3c281c5c3c28187c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382012ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382019ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820156c1c0c1c0c1c0c1c0c1c0c1c0c2c14bc1c0c1c0c2c115c1c0c3c281d3c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382010fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201a2c1c0c1c0c1c0c1c0c2c178c3c281e4c1c0c1c0c1c0c4c382018ec1c0c2c124c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820176c1c0c1c0c1c0c2c170c1c0c1c0c1c0c1c0c4c382021ac3c28197c1c0c1c0c4c38201bec1c0c1c0c7c68201c2820215c5c40c820165c1c0c1c0c2c15bc1c0c1c0c3c281a0c1c0c1c0c1c0c1c0c1c0c4c382011ac1c0c1c0c4c38201b1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382019dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382017ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201e4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c167c1c0c1c0c1c0c2c156c1c0c1c0c1c0c1c0c4c38201eac1c0c3c281efc4c382017fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820244c4c382022dc1c0c4c38201b7c1c0c1c0c1c0c4c382013ac1c0c1c0c1c0c4c382017ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201edc1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c153c1c0c4c3820141c1c0c4c3820164c1c0c1c0c1c0c4c38201d5c1c0c2c13bc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201acc1c0c4c382012dc1c0c1c0c1c0c1c0c1c0c2c15ac1c0c1c0c1c0c1c0c1c0c3c281a6c1c0c1c0c1c0c1c0c1c0c1c0c4c382018fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281e2c2c130c1c0c1c0c4c3820111c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819ec1c0c1c0c1c0c4c382026ec4c3820170c6c581bd820198c2c12cc1c0c4c3820173c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281afc1c0c1c0c1c0c1c0c1c0c4c382014bc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820298c1c0c1c0c4c3820104c1c0c1c0c1c0c1c0c1c0c3c2818cc3c281fdc1c0c1c0c1c0c1c0c1c0c1c0c4c382011fc1c0c4c382016fc1c0c1c0c1c0c4c3820313c1c0c1c0c1c0c1c0c1c0c4c38201bcc2c171c4c3820228c1c0c1c0c1c0c1c0c1c0c1c0c3c281eac1c0c1c0c1c0c1c0c1c0c1c0c4c3820216c6c581c1820213c1c0c1c0c1c0c1c0c2c10fc1c0c1c0c4c382034dc1c0c2c124c1c0c1c0c4c382024dc4c3820272c4c382029ec1c0c1c0c1c0c2c16dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202c1c1c0c2c163c1c0c1c0c1c0c1c0c1c0c1c0c3c281b2c1c0c1c0c1c0c1c0c4c38202eec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c115c1c0c1c0c2c16ac1c0c1c0c4c3820190c4c3820270c4c382011cc4c382022fc1c0c3c28184c1c0c4c38201d6c1c0c1c0c4c3820283c4c38201adc1c0c1c0c4c3820247c4c3820347c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c14dc1c0c1c0c1c0c1c0c1c0c2c130c1c0c1c0c3c281b8c1c0c1c0c1c0c1c0c1c0c4c382035dc1c0c1c0c1c0c4c3820344c1c0c1c0c1c0c1c0c1c0c1c0c4c38201bdc1c0c1c0c4c3820301c4c382015ec4c3820303c4c382014ac7c68201788201b5c1c0c1c0c1c0c1c0c1c0c4c3820153c1c0c4c3820219c1c0c1c0c4c3820244c1c0c4c38201fec1c0c1c0c1c0c3c2819ec1c0c1c0c1c0c2c142c1c0c1c0c1c0c4c382027cc1c0c4c3820237c1c0c3c28193c1c0c1c0c1c0c7c68202bc8203aac1c0c4c3820143c4c382028fc4c3820290c1c0c3c281c5c4c382010fc1c0c1c0c4c3820186c4c382036bc1c0c1c0c3c281c1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12bc4c3820120c1c0c1c0c1c0c1c0c1c0c2c17ec4c38202b1c1c0c4c3820253c2c11bc1c0c1c0c4c382016fc1c0c1c0c1c0c4c3820373c1c0c1c0c4c382016cc1c0c1c0c1c0c1c0c1c0c1c0c4c3820257c1c0c1c0c1c0c1c0c3c28188c4c382021dc4c3820280c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203f1c4c382023ec2c17fc4c38202b4c1c0c4c38203d5c1c0c1c0c1c0c1c0c1c0c1c0c2c154c1c0c1c0c1c0c1c0c1c0c1c0c4c38203dac1c0c1c0c4c3820416c4c3820386c4c38201f5c4c3820277c2c112c1c0c1c0c1c0c1c0c1c0c2c108c4c38201dcc1c0c4c3820403c3c2819fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203cbc1c0c2c15ac4c3820409c1c0c1c0c1c0c4c382025fc1c0c1c0c4c3820155c1c0c1c0c1c0c1c0c1c0c1c0c4c382023dc4c38201dac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201b5c4c3820232c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c13ec1c0c1c0c1c0c3c281ccc1c0c1c0c1c0c4c3820267c1c0c1c0c1c0c1c0c1c0c1c0c4c38202d1c4c3820205c2c178c1c0c1c0c1c0c5c481bc81ebc7c682018c820325c4c38201e5c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820298c4c3820163c5c44682011ac4c3820318c4c3820221c1c0c4c3820372c1c0c1c0c3c281a7c1c0c1c0c1c0c1c0c2c129c1c0c4c38202d7c1c0c2c12bc1c0c1c0c1c0c1c0c1c0c4c382046fc1c0c1c0c7c682014c820394c1c0c4c38202f5c1c0c1c0c1c0c1c0c1c0c2c13ac1c0c4c382018cc1c0c1c0c3c281b7c4c382011dc4c38203d8c1c0c4c38203a6c1c0c1c0c4c38203d2c1c0c1c0c4c3820194c4c38204bec7c68201058202acc1c0c3c281d5c1c0c1c0c7c682028182037fc2c171c1c0c1c0c4c38203c6c1c0c1c0c1c0c1c0c4c38201fcc4c3820439c1c0c1c0c4c3820463c4c3820457c4c3820110c4c3820188c1c0c4c3820351c1c0c1c0c1c0c4c38202aec1c0c1c0c1c0c2c102c1c0c4c382037bc1c0c7c6820144820482c1c0c1c0c1c0c1c0c1c0c3c281cfc4c3820113c4c3820484c4c3820257c4c38202a6c3c2819bc1c0c4c33581a2c1c0c4c38202b6c1c0c1c0c4c3820388c4c3820488c1c0c1c0c4c3820423c1c0c1c0c1c0c1c0c4c38201b9c1c0c1c0c3c281e3c4c38203cdc4c3820232c1c0c1c0c4c3820388c4c382042fc4c382050cc1c0c1c0c1c0c3c281f2c4c3820385c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382037bc1c0c1c0c1c0c3c281fbc1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c68201f48203cec1c0c1c0c1c0c1c0c1c0c7c682025682048ec1c0c7c68201678202d2c1c0c7c682033c8204a2c1c0c1c0c1c0c4c3820359c4c38201f7c1c0c4c3820406c1c0c4c38202b9c1c0c4c3820455c1c0c1c0c4c3820526c4c3820442c1c0c1c0c1c0c6c581b38202f6c4c3820114c1c0c1c0c1c0c4c38204d0c1c0c1c0c1c0c1c0c4c3820226c4c382033dc4c38203f4c1c0c1c0c3c281bdc1c0c1c0c3c281a2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281eec1c0c1c0c1c0c2c125c1c0c4c38201c4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203c1c1c0c4c382043dc4c38204a9c1c0c4c382055fc1c0c4c38202d1c1c0c1c0c4c382056dc4c3820559c1c0c1c0c1c0c4c382035fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c682011882013cc1c0c1c0c4c3820241c1c0c1c0c4c382053cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820107c1c0c4c3820122c1c0c4c382059dc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382020cc2c110c4c38201f0c4c3820363c1c0c2c159c1c0c1c0c1c0c1c0c4c38205a0c1c0c1c0c1c0c1c0c7c68201ae8205adc1c0c1c0c4c38202abc1c0c1c0c4c382055ec4c382013dc3c2818fc1c0c1c0c1c0c1c0c4c382021ec4c382058cc4c382026dc4c3820263c1c0c1c0c3c281a4c1c0c4c3820466c1c0c1c0c1c0c1c0c1c0c4c3820229c4c3820414c1c0c1c0c1c0c1c0c7c682046b8205a3c4c3820168c1c0c1c0c1c0c4c3820315c4c3820179c1c0c1c0c4c3820558c1c0c1c0c1c0c2c149c4c382033bc1c0c4c38204dcc1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205ccc1c0c4c3820485c1c0c4c38204c0c4c38202e5c4c38202b8c1c0c1c0c1c0c7c68203d78204c4c4c382022bc1c0c4c3820469c1c0c4c3820594c4c382014dc6c581b2820319c1c0c1c0c4c382012ec1c0c4c382046ac1c0c1c0c1c0c1c0c1c0c1c0c4c38202fec1c0c1c0c1c0c4c3820121c4c3820404c3c281dac4c38205d1c4c3820142c1c0c4c3820218c1c0c1c0c4c38205e7c1c0c1c0c1c0c1c0c4c38204cfc4c38203b5c4c3820147c1c0c4c38201f3c1c0c1c0c1c0c1c0c3c281d2c1c0c2c139c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382048ac1c0c1c0c1c0c1c0c1c0c4c3820494c1c0c1c0c2c141c1c0c2c13cc1c0c4c382027cc4c38203e2c1c0c1c0c1c0c1c0c1c0c1c0c4c3820135c2c166c4c382054cc1c0c1c0c1c0c4c38204b7c4c382063fc1c0c1c0c1c0c1c0c1c0c4c38205a8c1c0c1c0c4c38205e5c4c3820597c1c0c1c0c1c0c3c281cfc4c38201d4c4c382024bc1c0c1c0c2c17cc3c281e6c1c0c4c3820370c1c0c4c38202cbc1c0c1c0c4c38202c1c3c281e8c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203bec1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382012dc4c3820142c1c0c1c0c1c0c4c38203eac1c0c1c0c1c0c1c0c4c36681e2c1c0c4c3820166c4c3820147c1c0c4c3820307c4c38205c9c3c281efc1c0c4c38204f5c1c0c1c0c1c0c1c0c1c0c4c38203afc1c0c1c0c1c0c4c3820529c1c0c1c0c1c0c3c281fbc4c38203cfc1c0c1c0c4c38205bbc1c0c4c3820413c2c112c1c0c1c0c4c382058bc1c0c1c0c1c0c4c38201ecc1c0c4c38203ddc4c38204e6c4c38205ffc1c0c4c382018fc1c0c1c0c1c0c1c0c1c0c1c0c5c4078203a7c4c3820220c4c38204efc1c0c1c0c1c0c4c38203bec1c0c4c3820266c1c0c1c0c1c0c4c38202b7c1c0c1c0c1c0c1c0c4c3820639c1c0c4c3820514c1c0c1c0c4c3820472c6c581f9820316c4c38206cac1c0c1c0c7c6820196820656c1c0c4c382047dc7c682016e82045dc1c0c4c38205b7c4c382033ec1c0c4c3820544c1c0c4c38203dbc2c165c1c0c4c3820293c1c0c4c38205f0c4c38204c1c4c38202d5c1c0c1c0c2c122c1c0c4c38201dfc4c38204fdc4c382041cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820598c1c0c4c382030dc4c38201e2c4c382068cc1c0c4c38202bac4c3820693c1c0c4c38201e1c1c0c1c0c1c0c1c0c1c0c1c0c4c382063ac1c0c1c0c4c38204b2c1c0c4c3820593c4c382066dc4c382029fc1c0c7c68201cd820489c1c0c1c0c4c3820676c1c0c1c0c1c0c1c0c1c0c4c3820581c4c38204b0c1c0c7c68203398205afc1c0c4c3820400c4c3820243c7c68201fa8202f9c1c0c1c0c1c0c1c0c4c3820527c1c0c4c382042fc1c0c1c0c1c0c1c0c4c38203a4c4c382032ec1c0c4c38206fac1c0c7c68202808204b9c1c0c1c0c1c0c7c68201f382061ec4c38202f2c1c0c4c382055bc4c38205a4c1c0c1c0c4c38206b5c4c3820279c4c3820728c4c3820714c4c3820239c1c0c1c0c1c0c1c0c4c382072dc4c3820271c1c0c1c0c1c0c1c0c3c281f7c1c0c1c0c4c3820233c1c0c4c38203bbc4c38204a5c1c0c4c3820289c4c38204bcc1c0c1c0c4c38203d3c1c0c1c0c1c0c4c382065cc4c382075bc1c0c4c38205ccc3c281afc1c0c1c0c1c0c1c0c1c0c7c6820240820704c1c0c6c581fc8206b6c1c0c1c0c1c0c1c0c4c382051fc1c0c4c38205b3c6c5819482027ec7c682010d820273c4c382036dc7c68201288202dcc1c0c1c0c1c0c1c0c1c0c7c682032482074bc1c0c1c0c4c3820430c4c3820129c4c382070bc4c3820192c4c3820391c1c0c4c382068bc1c0c1c0c1c0c1c0c4c3820405c4c38203b9c7c682043182045bc1c0c4c3820674c1c0c1c0c4c3820305c4c3820571c1c0c1c0c1c0c4c382058ac1c0c1c0c4c38203edc1c0c4c3820132c1c0c1c0c4c38205c8c1c0c1c0c4c38205d0c1c0c5c448820690c4c3820783c1c0c4c38205dec4c3820522c1c0c4c3820166c4c3820537c4c382033bc4c38207a7c4c3820620c4c3820705c1c0c4c3820587c1c0c4c38205e4c1c0c1c0c4c382051bc4c38203e0c1c0c1c0c1c0c1c0c4c382014ec1c0c4c3820340c4c382067ec1c0c1c0c1c0c2c10bc1c0c4c38207bac1c0c1c0c4c3820634c1c0c2c10dc1c0c1c0c1c0c1c0c1c0c4c3820138c1c0c1c0c1c0c1c0c4c3820127c4c382010dc1c0c1c0c4c3820624c4c38202a7c1c0c1c0c6c581f18203f5c1c0c1c0c1c0c4c382042dc1c0c1c0c1c0c1c0c7c68201e18207bcc4c3820433c4c38203d2c1c0c1c0c1c0c1c0c1c0c1c0c5c4388207f0c1c0c1c0c1c0c4c3820564c1c0c1c0c3c2818ec1c0c4c3820772c7c682014682075cc4c38203f6c1c0c1c0c1c0c1c0c1c0c4c3820543c4c382079ac4c3820499c1c0c4c3820312c3c28198c4c3820589c4c38201f8c1c0c1c0c1c0c4c3820302c4c38202dcc4c382064ec4c382067fc1c0c4c382032dc4c382047cc1c0c7c682031d820749c1c0c1c0c4c382050dc1c0c1c0c1c0c1c0c7c68203bd82080cc4c38206d2c7c68202e9820425c1c0c1c0c4c3820786c1c0c4c38207d2c1c0c7c6820390820421c1c0c1c0c1c0c4c38207cbc1c0c4c38201c9c4c3820805c4c38202a7c1c0c1c0c1c0c1c0c1c0c1c0c4c3820789c4c3820460c4c3820456c1c0c4c38202ebc4c3820791c1c0c4c38202e8c4c38207b4c4c38205dbc1c0c1c0c1c0c1c0c1c0c4c382028cc1c0c4c3820296c1c0c1c0c4c3820465c4c382023ec1c0c1c0c1c0c1c0c4c38202fdc4c38205cac1c0c1c0c1c0c4c38203edc4c382045ac1c0c4c3820758c1c0c1c0c1c0c1c0c1c0c7c682031c820732c1c0c4c3820339c4c382080ec4c3820211c4c38207e4c4c3820781c1c0c4c3820288c1c0c1c0c1c0c1c0c1c0c4c38201afc4c3820721c4c3820199c4c3820458c1c0c1c0c4c38204c2c1c0c1c0c1c0c1c0c1c0c1c0c4c382071fc4c38203e3c1c0c4c38202cec1c0c4c382020cc1c0c1c0c4c38206bfc1c0c1c0c1c0c1c0c4c3820645c4c38202c0c1c0c4c382047ec7c682053282060fc1c0c4c38206c9c1c0c2c103c7c6820491820573c4c3820810c1c0c7c682048b8205cec1c0c4c38206bac4c3820254c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c154c4c382017bc4c382040fc4c38203bdc4c3820195c4c3820840c7c682036582063cc4c38204fbc1c0c7c6820575820661c4c38205f5c4c382044dc1c0c1c0c1c0c1c0c1c0c4c3820292c4c38203f9c1c0c4c3820876c2c15dc2c119c4c38206acc4c3820328c1c0c4c38204b5c4c382050dc1c0c1c0c4c38204d2c1c0c4c38205bfc1c0c4c382068ac1c0c2c14ac4c3820277c2c173c4c3820323c2c167c4c38204adc4c382026cc4c38202a9c1c0c4c3820692c4c382032ec1c0c4c382020bc1c0c1c0c1c0c1c0c4c3820292c1c0c1c0c1c0c1c0c4c3820131c2c16fc2c114c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820666c1c0c1c0c1c0c1c0c4c38207b5c1c0c1c0c4c3820360c1c0c4c3820577c4c3820578c1c0c1c0c1c0c1c0c4c3820643c4c382083fc4c38207c4c1c0c4c3820600c1c0c4c382073ac1c0c1c0c4c38205adc1c0c4c382059bc1c0c1c0c1c0c7c682017982052bc1c0c1c0c4c382038ac1c0c2c134c7c6820452820733c4c38207c8c4c38202cfc4c3820603c4c3820520c4c382089ac1c0c1c0c1c0c7c682017c820572c1c0c6c58183820791c1c0c1c0c4c38205e0c2c11dc4c382042ac1c0c4c382028bc1c0c4c3820515c4c3820853c1c0c4c38203c0c1c0c1c0c1c0c4c38203c7c1c0c4c38206e7c1c0c1c0c1c0c1c0c4c3820243c4c3820730c1c0c4c382016cc1c0c4c38202f9c4c382021bc4c3820555c4c38205d1c4c38204c2c4c38205a1c1c0c1c0c1c0c4c382029dc4c3820379c4c3820850c7c68201aa8205a6c7c68201d18205e6c1c0c7c68204c18204c6c4c3820527c1c0c4c382075ac4c382061cc1c0c3c2819bc1c0c4c3820169c7c68206ba82091bc1c0c4c3820605c2c12cc1c0c4c3820408c4c38201fdc1c0c1c0c4c3820482c4c3820856c4c3820424c5c4748205f8c1c0c4c38205b0c1c0c4c3820677c4c382030dc4c382051ec4c3820157c4c38204c5c7c68201a28204f6c1c0c1c0c1c0c4c382070cc1c0c1c0c1c0c4c3820867c1c0c4c382085fc7c68206af82079fc4c38207d0c4c38207a1c2c11ec4c382052ac7c682034f8204f0c4c3820445c4c3820908c4c3820943c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38208e9c1c0c1c0c1c0c1c0c4c3820230c1c0c1c0c1c0c1c0c4c38205e2c4c382042ec1c0c2c164c1c0c1c0c4c382074bc1c0c4c38207c6c1c0c4c382010cc4c3820381c4c3820696c4c38201b8c4c38207dec1c0c2c108c4c38203e7c4c382047ac4c38204eac4c3820104c4c382073cc7c682096c82098dc4c38208abc1c0c1c0c7c68201a082034ec4c38205d9c7c6820347820505c1c0c4c38208abc1c0c4c3820464c4c3820506c1c0c1c0c1c0c1c0c1c0c1c0c4c38202f4c2c128c1c0c4c38202f7c4c3820972c1c0c1c0c1c0c7c68204b3820768c1c0c4c3820886c1c0c4c382062dc1c0c4c38208f3c4c38206f9c4c38202a0c4c3820294c7c6820608820657c1c0c1c0c4c38205f3c4c382096cc4c3820560c4c3820621c1c0c3c281dec1c0c1c0c7c68201138205fec4c38201a1c4c38202a5c1c0c2c168c4c3820756c1c0c4c3820451c4c382043cc3c281dbc4c38206f8c1c0c7c6820177820259c7c6820439820985c1c0c4c38208aac1c0c4c38206b5c1c0c1c0c4c38209bac1c0c4c3820969c1c0c1c0c4c38202bbc1c0c4c3820968c1c0c4c382016bc4c38207efc4c3820497c4c3820464c1c0c1c0c1c0c1c0c1c0c7c68206b98208a3c1c0c1c0c1c0c1c0c1c0c1c0c4c38209c0c7c68203fb820797c4c38203c7c7c68201d8820203c4c3820269c1c0c4c38207b8c1c0c7c682037f8205e3c1c0c4c382019bc1c0c1c0c4c38209acc7c68207dc820864c1c0c4c382049ec1c0c1c0c1c0c1c0c1c0c1c0c7c6820429820622c1c0c4c38202d0c4c38201ebc7c6820572820973c1c0c4c382087ac3c281dfc4c382065bc1c0c4c38208d5c1c0c1c0c4c3820997c7c682056c82070fc1c0c1c0c7c68205f4820a00c1c0c4c38201b7c1c0c1c0c6c581ca820467c1c0c1c0c5c40d820640c1c0c4c382010cc4c382050bc4c382098fc1c0c4c382063bc1c0c1c0c1c0c7c68202bf8205e5c1c0c4c38202aec1c0c1c0c4c382028ec4c3820156c1c0c1c0c1c0c4c382051dc1c0c4c38209b9c4c38209a5c4c38209cbc3c2818dc1c0c1c0c4c38206ddc4c382061ac1c0c1c0c4c3820829c1c0c4c3820918c7c6820621820715c4c38206a0c2c11cc7c68208428208b9c1c0c1c0c1c0c1c0c4c3820473c4c3820849c1c0c1c0c4c3820420c1c0c4c3820404c4c3820970c4c382029bc3c281e8c4c38202e2c1c0c1c0c4c3820515c4c382022bc4c382023ac1c0c4c38201c7c4c3820121c4c38209f5c5c41e820212c1c0c1c0c4c38209d6c1c0c1c0c4c382090dc4c3820521c1c0c4c3820630c1c0c4c382065dc1c0c4c38204dec1c0c1c0c1c0c4c38206bfc4c3820435c7c682082a8209c4c4c38207ddc5c4278207acc4c3820a23c4c3820562c4c38201b4c4c38205ddc1c0c4c3820500c1c0c1c0c1c0c4c38209dbc1c0c4c38207edc1c0c4c38208bdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38204d6c1c0c1c0c4c3820548c4c382066bc4c382064fc4c3820534c4c38202fbc4c3820158c1c0c4c38209f4c4c3820282c3c281b7c7c682014c82075dc4c38208a2c1c0c4c38202ccc4c38201cbc1c0c4c38208f9c4c382037ac2c105c4c382079fc1c0c1c0c4c38207ffc7c68204e68209c5c4c3820979c4c38203a9c4c38203d0c1c0c4c3820261c1c0c4c38209fdc4c38208d4c1c0c7c682036f8205d8c1c0c4c3820964c4c38204dac4c3820855c1c0c1c0c1c0c7c682026e8203b3c1c0c4c38205c2c1c0c4c38204e8c1c0c4c3820902c7c682054d8206e2c5c4488202e3c4c382024ac1c0c7c682021b8207b9c4c3820914c4c3820a03c3c281bbc4c382069cc4c3820794c7c682036c82039ec1c0c4c382049dc4c38208e6c3c281bac4c38203b8c1c0c4c3820569c4c382017dc1c0c2c152c4c3820a1cc4c38203d9c7c68203a0820501c4c3820a6dc1c0c7c682024f8204c9c4c3820a01c7c682063282098cc4c382058bc1c0c4c3820223c7c68202ef820427c4c38207ddc1c0c7c682062e820672c1c0c1c0c4c3820554c4c382066ec4c3820acac4c3820480c4c382037dc4c3820570c1c0c4c38206ebc4c38206b1c4c382081fc1c0c1c0c4c3820633c5c40e82058cc4c3820546c6c581e7820190c1c0c4c382076dc7c682026f8209cdc1c0c1c0c4c3820508c1c0c1c0c1c0c7c68207d482099bc1c0c4c3820687c1c0c1c0c7c6820923820a71c4c38208f6c4c382039cc4c38202e6c7c68207bd820866c1c0c5c436820a92c1c0c1c0c1c0c4c382085cc4c382095ac4c382013cc1c0c4c382045bc4c3820516c7c68208698208d5c4c382022ac4c3820390c4c3820419c1c0c1c0c1c0c4c3820601c1c0c1c0c2c142c1c0c4c38208efc4c38209f1c3c281fec7c68209578209aec1c0c4c382056ec1c0c1c0c4c382029bc4c38208acc1c0c1c0c1c0c1c0c1c0c1c0c4c38204bbc4c382045ec1c0c1c0c4c38206a2c4c3820518c1c0c7c68203ee820a72c2c15cc4c38207c1c4c38203b1c4c382025dc1c0c4c3820109c1c0c7c68206d082080fc4c3820447c1c0c1c0c4c382048fc7c6820159820b32c4c3820738c1c0c4c382076ac1c0c1c0c4c38207e2c4c3820b11c7c68208b28209f9c1c0c7c682039a820825c4c3820348c7c6820507820875c1c0c4c3820229c1c0c4c3820a89c1c0c4c3820ae5c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205bec1c0c2c16ec4c3820145c4c382031fc1c0c4c38206bdc1c0c4c3820590c4c3820a9bc4c3820b0bc4c38203f8c4c3820b1bc7c682062b8207e7c7c68204bd8209a8c1c0c4c382081bc4c382047cc1c0c4c382019bc4c3820a95c4c3820553c7c6820255820397c4c38206c1c4c38206e9c1c0c5c458820835c1c0c4c3820a09c1c0c4c38201b6c3c281c0c4c3820595c4c3820547c1c0c1c0c4c382028dc1c0c4c382071ec4c3820167c1c0c4c3820a45c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820a84c4c3820b82c7c6820773820852c1c0c1c0c4c3820807c4c3820adcc1c0c1c0c2c172c1c0c4c38204e2c1c0c1c0c1c0c7c6820106820927c4c38207e7c4c382096bc4c3820a47c7c68201bb820948c4c38205c1c1c0c1c0c6c581f28202f3c4c3820ae7c4c382093ec1c0c1c0c4c3820246c4c38205cdc4c3820945c4c38202ebc7c6820423820730c7c6820295820975c4c38207cec1c0c4c3820599c7c68205c382071ac4c3820443c7c6820502820890c7c68203b88208ddc1c0c4c382090bc4c3820a2dc1c0c4c3820316c4c3820b57c4c38202a4c7c68204de8207c0c3c281eac4c38209e3c1c0c1c0c1c0c1c0c1c0c3c281b6c4c3820682c4c38203f3c3c281d1c4c3820644c1c0c7c68209d1820a06c4c38208e4c1c0c4c38205afc4c3820432c1c0c1c0c7c6820382820a87c7c682010e820ac5c1c0c1c0c4c38203dbc1c0c4c3820b68c1c0c7c682034582081dc1c0c4c3820999c1c0c2c17ac4c3820a25c4c38204f1c4c38202adc1c0c1c0c4c38204afc1c0c1c0c4c382058ec7c68205ae820b6cc7c682059682097bc7c68201e0820370c1c0c4c382094ec7c68203a78206f7c4c3820513c4c382088ec1c0c1c0c4c38209cfc4c3820aa9c7c68209fa820b64c4c3820ae0c7c682077b820874c4c3820b19c4c3820936c1c0c1c0c4c3820221c4c3820336c4c3820899c4c382074ac1c0c1c0c4c38201b1c7c6820279820a81c1c0c1c0c4c38209f4c1c0c4c382088bc4c38207a5c1c0c7c68201d382075bc4c3820ad6c1c0c1c0c1c0c2c105c1c0c4c3820877c1c0c1c0c3c281f8c7c6820236820874c4c38209b5c1c0c1c0c1c0c1c0c1c0c1c0c4c382031bc4c38205dfc4c3820752c1c0c1c0c1c0c1c0c4c3820544c7c682067e82074dc7c68201ca820380c3c281c6c4c3820b84c1c0c4c38208d1c7c68204348209b6c1c0c1c0c4c3820af0c1c0c4c382052bc7c68206bb82072bc4c3820a8cc1c0c1c0c1c0c4c3820a7bc4c3820b5ec1c0c7c68208ee820b73c5c47a8201e3c4c38207d6c4c3820a1fc7c6820870820a07c1c0c1c0c7c6820422820616c3c28196c4c3820b3ac1c0c4c38203ccc6c581dd82051bc5c4778207e8c1c0c1c0c4c3820abbc7c68204e0820615c1c0c4c38206fec1c0c4c38203c5c4c38201afc7c68202888206fbc4c38204f3c1c0c1c0c4c38202b8c1c0c1c0c4c3820749c2c120c1c0c1c0c4c38202cac4c3820a52c1c0c4c3820701c4c3820c62c1c0c4c382076fc3c281bec4c3820a75c1c0c4c3820293c1c0c4c38204e7c7c682096e820bfcc4c382073cc4c3820187c1c0c3c28191c1c0c4c3820b8cc2c175c4c3820b32c1c0c4c382021fc4c3820b21c4c382020ec4c38206e1c7c682065f8206d4c1c0c2c113c4c38205cdc4c382068fc7c6820476820991c4c382071cc1c0c1c0c4c3820beec1c0c4c38209c1c1c0c4c3820a53c1c0c1c0c1c0c4c38208e1c7c682067f820afec7c682059b820889c4c3820662c4c3820642c4c38201fbc4c3820b59c4c38204dfc4c3820585c4c3820150c5c47482045fc1c0c1c0c4c3820bd0c6c581d7820bc7c4c382055dc4c38203dcc1c0c4c38206a5c4c3820aacc1c0c1c0c4c38208e7c4c3820226c4c3820932c1c0c1c0c1c0c4c3820c6ec4c3820b77c4c382032cc1c0c4c382045cc3c281c3c7c6820636820b31c1c0c4c3820b24c3c281c2c1c0c4c3820b45c4c3820b66c4c38208eac7c682070e820a14c4c38208f0c4c3820b23c1c0c1c0c4c3820ac1c4c38209d0c1c0c7c6820101820524c7c682083a8209e1c1c0c1c0c4c3820c9cc1c0c7c68208ae820a6dc4c38203abc7c68204ca820c03c4c3820605c4c3820a4fc4c3820986c4c3820c0bc1c0c1c0c1c0c1c0c4c3820bd3c4c3820a68c4c3820b04c1c0c1c0c7c6820932820c86c4c3820c74c1c0c4c38208cac1c0c1c0c1c0c7c68206b8820a61c1c0c1c0c4c3820b2cc4c3820399c4c382040dc1c0c7c68209bf820a36c4c3820cadc1c0c7c68205db820c2fc1c0c7c6820a8a820aeec4c3820628c4c3820217c4c3820916c4c3820686c4c38209d7c4c3820607c7c6820675820965c1c0c7c68202ed8208b6c4c3820affc1c0c4c382083bc4c38202a2c4c3820181c1c0c4c3820111c4c38205b6c7c6820449820652c4c3820c4bc1c0c7c6820ab1820addc4c3820358c1c0c4c38203a1c1c0c4c38206eec4c3820ceec7c68207e0820bcfc4c3820353c1c0c6c5819c8205c5c7c682052f820baec4c3820a62c4c38203ebc1c0c7c682020282050ac4c382042bc1c0c1c0c4c3820311c4c382032bc4c382084dc1c0c1c0c1c0c1c0c7c682064c8208dbc4c3820646c4c38202f2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820958c4c3820561c1c0c4c3820a40c4c3820cccc4c3820358c7c6820392820649c4c382068fc5c46b82077ac4c3820709c1c0c4c3820ba2c4c3820330c1c0c4c38208ebc4c3820996c7c68207e48208cdc1c0c1c0c7c68205408207fdc6c581f58208a9c1c0c7c68205b582069dc4c3820803c4c382041ec1c0c1c0c1c0c7c682038e8207b2c7c682040e820549c1c0c4c3820cf2c4c38202d8c4c3820441c4c3820c1cc4c3820abec1c0c4c3820c97c4c382092cc1c0c4c3820105c7c68209f9820aa1c4c3820d05c4c38206ffc4c3820945c4c3820c0cc1c0c4c38207bfc1c0c1c0c2c17cc1c0c4c382011dc1c0c4c3820706c7c6820897820a91c1c0c4c3820c20c4c3820645c4c38207bec1c0c4c3820993c1c0c1c0c1c0c3c281dbc4c3820bf9c7c68207d8820b53c4c3820453c7c68203ef820504c7c6820584820b7fc4c3820b0dc4c3820637c1c0c4c3820b50c4c3820a22c4c382079cc7c6820503820a4ac4c3820b74c4c38203e6c1c0c1c0c4c3820921c1c0c4c3820920c4c3820b52c4c382095dc4c3820d5cc6c58189820d4bc4c382044cc1c0c1c0c4c3820c1dc7c6820123820306c4c3820a26c7c68204f0820af9c1c0c4c3820402c1c0c4c38201c9c4c3820386c7c68203108208d2c1c0c4c38207f4c1c0c4c3820606c4c3820412c7c682035a8204e8c4c38208d2c4c3820c3fc4c3820d89c4c3820278c4c3820a78c4c3820d26c7c68202a5820b41c1c0c7c6820a14820d5fc4c38209fec4c3820c51c4c3820cddc1c0c1c0c1c0c4c3820265c4c38206d2c4c382088dc4c38203f3c1c0c7c68203cb820748c4c38206c7c4c38209d3c4c3820959c4c382039fc1c0c1c0c1c0c1c0c4c3820b78c1c0c4c3820454c4c3820636c4c3820727c4c3820affc1c0c1c0c4c38207f6c4c3820c47c1c0c7c68201f2820630c4c3820981c1c0c4c382012ac3c28192c7c68205da8209fcc1c0c7c68201b8820c0cc1c0c4c3820711c4c382053dc1c0c4c38205f4c1c0c4c3820847c1c0c4c3820a48c7c682024c8206b7c1c0c7c68207fc820ad5c4c382073fc7c68205bd8207f5c4c38204d4c1c0c4c3820350c4c38207a4c4c3820b3fc7c682013682038ac4c3820b22c4c38206d5c4c3820adac3c281bac4c3820183c1c0c7c682044a820a85c4c382094ac1c0c1c0c1c0c7c6820ac0820d68c4c38208ccc4c38204aec4c3820a10c7c6820bab820d46c4c3820da9c4c3820311c4c3820c6ec4c38207f2c1c0c3c281e5c7c68202bf820dcdc7c6820802820c5fc1c0c4c3820d56c1c0c7c6820bcd820c53c1c0c1c0c4c38204afc7c68207a3820c1ac4c3820869c1c0c5c44382041ac4c3820b0bc1c0c1c0c1c0c4c3820c52c1c0c7c68201ee8202a4c1c0c7c68206688209e2c4c382044dc4c382041dc4c3820d9dc4c3820a1bc7c682058d820d4cc1c0c1c0c4c3820df4c4c3820565c1c0c1c0c7c6820225820747c4c38202c7c4c382056bc6c581e1820d4fc1c0c4c3820591c4c3820c87c1c0c4c3820a5ec1c0c4c3820745c4c382076bc7c6820969820c4ac4c38205a3c4c38204d3c7c68206e3820c19c7c68204c7820982c7c682064b820a5cc4c38204cbc1c0c1c0c7c682065e820b04c1c0c7c682085b8209e5c4c382059ac1c0c4c38209c4c4c3820821c1c0c1c0c1c0c4c3820a59c1c0c1c0c4c3820b8ec4c3820e07c1c0c7c682036e820612c1c0c4c3820b4dc4c3820938c4c3820d7ec4c3820abac4c3820470c4c3820aabc4c3820552c4c3820803c3c281dcc4c3820898c4c38201b0c4c38209d2c4c38208c9c7c6820ca6820cb9c1c0c1c0c1c0c4c382063dc1c0c7c682028b820c3ac4c38208f7c4c3820b20c4c3820750c1c0c1c0c4c3820c4bc7c6820141820681c1c0c4c3820dbac7c6820539820a18c4c38207afc4c3820b4bc7c68207d4820ddcc7c6820100820846c4c3820284c4c3820461c4c3820be7c7c68208ba820a58c4c3820919c4c3820d7ec4c3820799c7c6820c7f820dd9c1c0c4c3820968c1c0c4c38201bac4c3820bc8c1c0c7c68209d5820dfec4c38202e7c7c68203d4820bfec4c3820542c1c0c1c0c4c382062ec4c3820a1ac4c3820209c4c382070ac2c123c4c38204eec4c382041bc4c382069cc1c0c4c3820643c1c0c7c682090c820a67c4c3820d76c4c3820d36c4c382047ec4c38209e4c4c3820792c1c0c1c0c4c3820684c1c0c4c382019cc4c3820c89c4c3820bbcc7c6820583820cecc4c3820c8ac7c6820245820787c1c0c7c68206be82073dc1c0c4c382052ec4c38202c9c1c0c7c68204fc8208d3c4c3820b53c1c0c4c3820e5ac7c68207d7820856c1c0c6c581f9820d98c7c6820530820cc0c1c0c4c3820a74c7c68203308208d9c4c3820cc6c4c3820bedc7c6820887820a8cc1c0c4c38207acc1c0c4c3820a99c4c3820db6c7c68205d0820e47c1c0c7c6820524820939c4c3820822c7c682051f820ad9c4c3820959c7c682057b8207b6c1c0c7c682013b8207a9c1c0c4c3820983c4c38209a7c4c3820aedc2c11cc1c0c4c3820bbac4c3820175c7c68207de820858c7c68205b4820a66c1c0c4c3820262c1c0c1c0c1c0c4c3820498c1c0c6c581b0820be9c4c3820b69c4c3820df3c4c3820495c7c6820627820ae9c4c3820a04c4c3820ad8c7c68203df820b83c7c6820535820b06c1c0c4c3820b12c4c3820c9ec1c0c1c0c1c0c4c38201b2c1c0c1c0c4c3820d86c4c3820d6bc4c382012bc7c6820d15820df1c4c3820a6ec4c3820df6c1c0c4c3820c73c1c0c4c3820116c1c0c4c3820248c7c6820697820d32c1c0c1c0c7c68202b0820ebac1c0c1c0c7c6820442820e49c7c68204b48207f8c1c0c4c382041cc4c3820c66c4c3820d4cc7c682053f820721c1c0c4c3820ed5c7c6820bc9820d9ec4c3820534c1c0c7c682015a820ae6c7c6820357820c83c1c0c7c6820774820e57c3c281d9c4c3820726c4c3820185c1c0c1c0c4c382086ec1c0c1c0c4c3820c3ec1c0c7c682057482089ec4c3820ee9c7c6820477820a16c7c68208ce820c78c4c38203e5c4c3820685c1c0c1c0c7c68208e5820af7c4c3820bbec7c68201f6820a95c7c6820395820ab2c1c0c1c0c7c6820a30820bb7c4c3820646c2c160c1c0c4c382097bc4c3820d5ec7c68204e1820e5cc7c6820e7a820ec1c7c6820b35820d84c1c0c4c38209eec4c3820182c4c3820be3c4c3820b60c1c0c7c6820c7b820c85c4c3820a4dc7c68202b2820ecec4c382016dc4c38205a9c1c0c4c382031ac4c3820b28c4c3820e03c6c58190820acbc1c0c1c0c7c68205b98209b7c7c6820d21820d69c1c0c7c6820249820ee4c1c0c4c3820872c4c38202dac1c0c4c38201cac4c3820f03c1c0c1c0c4c3820c05c1c0c4c382048cc7c68209d5820c94c4c3820490c1c0c4c3820c8dc4c382023bc4c3820f21c1c0c4c3820619c1c0c4c3820839c1c0c4c3820ad1c7c6820389820702c1c0c7c6820450820768c1c0c1c0c1c0c7c6820165820e0cc4c38202dec1c0c4c3820e11c4c3820e85c1c0c7c6820ed2820f23c7c6820a64820cc7c4c3820a4ec4c3820667c1c0c7c682042c820b59c1c0c1c0c4c3820df0c4c3820c28c4c3820d09c4c3820e97c4c3820c13c4c38203d8c1c0c4c3820da7c4c3820cb3c1c0c4c38209d8c7c68206df820a3fc4c3820510c7c68202cd820d2ec4c3820304c4c3820c24c7c682067082095ec4c38202cfc7c68206ce820d60c7c6820c07820d0fc7c68204d4820638c1c0c4c3820a7ec4c3820623c1c0c7c6820505820f1ec4c3820863c4c3820433c7c682082f820efdc7c6820708820e61c4c3820e61c4c3820b9dc4c38207a3c5c43b8208fac4c382051ac4c38208c7c4c3820d2fc4c3820222c4c3820739c4c3820881c4c3820591c4c3820bd8c4c3820ad2c4c3820c8ec1c0c7c6820a5b820ce9c5c42d8204a2c1c0c4c38205efc1c0c7c68206d7820892c7c68208d8820b54c1c0c4c3820815c7c6820353820b1ac7c6820d1d820dbac4c3820d10c4c3820e5bc4c3820ce8c4c38209bac4c3820bf4c7c682065582098ec4c3820eafc1c0c7c6820b4a820c5ac4c3820f01c1c0c4c3820880c6c581be8201d1c4c382098bc4c3820342c1c0c4c3820b6fc7c6820dd1820f3cc4c3820d19c4c3820d3fc7c6820c06820f21c4c3820dbdc4c3820ee3c1c0c7c68207e1820e81c7c68204268207fcc4c38207c1c1c0c1c0c1c0c4c3820249c7c6820a9d820ad0c4c38204b8c4c3820cacc4c3820372c4c3820778c1c0c1c0c1c0c4c3820393c4c3820f9bc4c38207cfc7c682033f820712c4c3820579c1c0c7c68201b68207cac7c68207b1820d73c1c0c4c3820c00c7c6820832820952c4c3820154c4c3820fb6c4c3820b01c4c3820f82c7c6820556820d31c7c68202b38209d0c1c0c4c3820948c4c3820773c1c0c4c38205d7c4c3820f1cc7c6820b9d820f3dc7c68205dc8208a3c4c3820fa8c4c38205bac1c0c1c0c1c0c4c3820f07c1c0c1c0c1c0c4c3820af0c4c38204abc4c38208c5c4c3820f4ac7c682072b8209c6c1c0c4c3820cc5c4c3820a4ac4c3820dddc4c38209a9c4c3820e4ec4c3820d41c4c382076ec7c6820bd6820c48c1c0c4c3820414c7c6820c12820e37c4c3820653c7c68202e1820c61c1c0c1c0c7c6820aa1820cffc5c47282062cc7c68209af820a83c7c68208778209bbc4c382023ac1c0c1c0c4c3820f22c4c38201b4c1c0c7c68208f1820b7ec4c38201c1c1c0c4c3820671c4c3820bc2c1c0c4c38202e0c4c382027fc4c3820bebc4c3820a13c4c3820c9ac7c6820ab3820ee1c1c0c4c382054bc4c38209e9c4c3820571c5c447820f0bc4c3820300c2c169c7c682078b820a5cc1c0c4c3820e24c4c3820c41c5c45d820854c4c382010bc4c3820833c1c0c4c3820a73c4c3820821c1c0c4c3820313c7c6820eb7820fbcc1c0c1c0c4c3820f3ec4c3820461c4c3820f64c4c3820186c7c68202f1820905c4c3820550c7c682076c820912c3c281aac4c38203cec4c3820635c4c38209a4c7c68207c2820881c4c3820348c4c382084fc4c3820a2bc4c3820f69c4c3820800c4c38209f0c4c3820827c4c3820377c4c3820a61c1c0c4c3820287c4c38204cec1c0c4c3820f6ec4c3820148c1c0c1c0c1c0c4c3820ba1c4c3820e01c7c682056982080cc1c0c1c0c7c68202008209f7c1c0c4c3820aa6c1c0c1c0c1c0c4c3820ab0c4c3820ffdc1c0c7c682041e820782c4c382093cc4c382044bc4c382098ac1c0c4c3820b19c4c3820b2fc1c0c4c3820f25c4c3820567c1c0c4c3820331c4c3820f7fc4c38208c6c4c3820c31c4c38201a4c1c0c4c38207a8c7c6820765820d07c5c40c820c88c1c0c7c6820523820631c4c3820880c3c28199c4c382078dc4c382048bc3c28189c2c153c4c3820286c4c3820b23c1c0c4c3820722c1c0c4c3820f63c7c682067c820e27c4c38203a8c7c6820178820c35c7c682012c820f52c1c0c4c3820b7dc4c38206a7c4c3820e74c7c682065a82105ac2c111c4c3820804c6c581ae820e06c4c3820397c7c68207e3820dccc7c6820714820903c4c3820a7ac4c3820a19c1c0c4c382054ac7c682046c820606c7c68206c5820e58c4c3820c99c1c0c4c3821052c4c3820be0c1c0c4c382092ec3c2818ec4c38202f1c1c0c7c682061c820f40c1c0c4c3820db2c4c3820c15c4c3820d42c4c3820eebc4c3820dafc7c6820ee2820fa7c4c3820b94c1c0c4c3820951c4c3820a4dc4c3820888c4c3820535c7c68207be82107dc4c382031ec7c68206d9821004c4c3820170c1c0c4c382060fc4c38204c8c1c0c1c0c4c3821009c7c6820854820b0ec4c3820995c4c3820e2ec4c382074cc4c3820440c3c28196c4c382093dc7c6820b9e820be5c4c3820e9ac4c3820caec4c3820f86c7c6820a5f820ec6c4c38204cac7c682051d82084cc7c682021e820a1fc4c3820c7ac1c0c4c38205f1c4c3820c73c1c0c4c3820ba0c2c102c1c0c1c0c1c0c7c682095c8209abc7c68202108207f9c7c6820e0f820f9fc4c3820577c1c0c4c382094cc7c6820c56820ff8c4c3820eaec7c68203de8205a8c7c68206248208e4c4c38206d6c4c38205a7c4c3820296c4c38209cac1c0c4c3820a2fc1c0c4c382106bc4c3820e2ac7c6820e18820f05c4c382087cc7c6820c17820de5c4c3821030c6c581ed820f4cc4c382091ac1c0c1c0c7c682070082109cc3c281cac4c38204ddc7c6820912821000c4c3820f28c4c38201b3c4c38201a7c7c68205848208b0c1c0c1c0c4c3820b95c2c14ac1c0c7c6820827820edbc7c6820e22820f4dc4c3820b37c4c3820598c4c382087ec7c682093f820c01c4c38207d1c4c382057dc7c68206fc820fe7c7c682016d8206a8c1c0c4c38209ccc4c38207efc4c3821007c4c3820b25c7c682077d820a3dc7c6820462820dc1c7c68201c6820747c1c0c4c3820cbec1c0c4c382017cc1c0c4c3821031c1c0c1c0c4c38206abc4c3820df9c4c3820f68c4c3821091c1c0c1c0c4c3820de8c4c382084ec4c38202d6c4c3820278c4c3820aaac4c3820d57c1c0c7c6820e53821059c4c382106ec5c4098205acc4c3821049c4c3820ec5c1c0c4c3820a4ec1c0c4c382064ac3c281e7c7c682020d820da1c7c6820c92820e77c7c68204e9820b70c4c38205f6c4c3820ea3c4c38204f1c1c0c7c68205f0820e5ec1c0c4c3820b1fc1c0c1c0c1c0c1c0c4c3820a81c7c68202ab82072fc4c382048ec4c3820e98c1c0c1c0c7c6820369820e4dc1c0c7c6820e97820edec4c3820acdc4c38205f5c4c3820fd1c1c0c7c682067d8210f9c7c68201498208a1c4c3820337c1c0c7c682018d8206dac7c6820556820740c7c68204a5820ff1c7c6820b51820eb5c1c0c1c0c4c3820d22c3c281e6c4c3820ce1c4c3820efcc4c3821027c4c3820c36c4c3820125c7c68203d1820fb8c7c6820837820c77c4c3820f53c4c3820ffbc7c68202b7820bf0c4c3820f11c6c5819d820900c7c682078a8209ecc1c0c6c58182820d58c1c0c4c3820ba2c1c0c1c0c4c3820940c1c0c1c0c4c382082dc1c0c4c38210fbc1c0c4c38202f6c7c6820743820a87c1c0c4c382023cc4c3820b44c4c38206e0c7c6820ce082112cc4c38209adc4c3820d3bc1c0c7c6820c82821088c4c3821097c4c3820b05c4c3820608c1c0c4c3820abfc4c38208c3c7c6820144821095c7c6820213820d30c7c68206df820febc7c68202ff820d43c4c382063ec4c3820332c4c3820e9ec4c3820c5bc4c3820db9c4c3820ed3c4c3820ffdc1c0c7c6820bf7820ecfc4c382104ac7c68205a2821111c7c68203838207a2c4c3820e70c4c38206d1c4c38205d2c7c6820c5c820e16c4c3820cd7c3c28192c4c382056ac4c3820383c4c3820716c7c6820dac8210f3c1c0c4c382107cc4c3820f8cc4c38204d6c7c68203ec820b7cc4c3820f31c7c682018082038bc1c0c4c3820840c4c3820f85c4c3820117c4c38202b1c4c382030ac7c6820dc382110dc1c0c1c0c7c68207cd820feac4c38201f1c4c38204dbc4c3820e94c7c6820ba7820f73c4c382102bc1c0c4c3820ed2c1c0c1c0c4c3820baec3c281b1c3c28182c4c3820943c4c3820647c4c38202b9c7c6820a90820cd6c7c6820708820e91c7c6820c59820fa1c4c38209f7c1c0c7c682020e82071dc4c3820d71c4c3820a58c4c38209c9c7c6820764820cbdc4c3820fa0c7c6820a05821077c4c382015ec4c3820c3dc7c6820271820d51c4c3820b6dc4c382066ac7c6820c0982117cc1c0c4c3820f0ac4c3820e41c4c3820c93c4c3820e5dc5c421820de2c7c6820deb820f09c4c3820f27c4c3820146c7c68207ec820916c4c3820a17c4c3820c6fc4c3820a22c4c3820710c4c3820251c4c3820c43c4c38204aec4c3820446c4c38209c7c1c0c6c581c38203adc7c6820ca882117fc4c3820c57c4c3820304c4c3820981c7c6820dc482104fc4c3820460c4c382112ac4c3820941c7c6820c3f820e4bc7c6820660820896c1c0c4c3820cafc7c682040182113cc4c3821145c4c382086ac4c38207c2c1c0c4c3820f70c4c3821082c1c0c4c382067bc4c3821066c1c0c4c3820424c1c0c7c6820bcc8210f9c1c0c4c38211cbc1c0c7c68202178210b1c7c68204138210e2c7c6820edf82119dc1c0c5c45f820c14c4c38209fcc4c3820fbdc7c6820273820fa1c4c3820c5ec7c68207b3820c31c5c41b820a0ac7c6820242820e66c5c44f820641c4c3821024c1c0c4c3820361c4c3821150c4c38204f2c4c38201e0c4c3820873c4c3820fc7c4c3820e6ec7c6820d3f82119ac7c68205c2820f59c7c68208b8820cf3c4c38202c3c4c3821060c4c3820951c7c682071c820ff2c1c0c4c3820b74c5c418820543c4c3820327c1c0c4c38204c7c4c3820cdbc1c0c1c0c4c38210ddc4c38202f8c4c3820afec4c38208edc4c3820ea2c1c0c1c0c4c3820e09c4c3820ff9c4c38210c7c7c6820c0a820cf9c1c0c7c6820edc8210cfc7c6820c32820ed1c4c3820ed6c1c0c4c3820954c4c38202c6c4c3820e72c4c3820ec8c1c0c4c3820b26c4c3820e48c4c3820159c4c38209bdc7c6820dca8211c7c7c6820e9c821028c7c6820581820b29c4c38207ffc7c682110a8211d7c4c3820767c7c6820528820dd0c7c6820f34820f45c4c382118ac1c0c7c6820b93821169c4c382060ac7c6820254820b8ac1c0c1c0c4c3820e7fc7c68205298208d6c4c3820741c4c3820a21c1c0c4c3820ffcc7c682101c82105dc4c3820f7dc1c0c4c38209c3c4c38204a0c3c281f8c4c3820600c7c68201608202cac4c3820dfec7c68205a782118fc1c0c1c0c4c38210b8c4c3821050c1c0c7c6820276820a25c7c6820af8820c29c1c0c4c382011fc7c68209758210dcc4c3820616c4c3820d78c1c0c4c382046cc4c3820cb5c3c281f4c2c14bc1c0c1c0c1c0c7c6820823820c90c1c0c4c3820a83c7c6820b7382109ec7c682101f821121c1c0c7c68201dd820592c4c3820cf0c1c0c4c38204fdc4c3820b9ec4c38203b2c4c382086fc4c382029ac1c0c7c6820b2e820b9cc1c0c1c0c4c38206c6c4c3821161c4c3820b39c1c0c4c3820a43c4c3821153c4c38211aac7c6820d18820e26c4c3820fa4c4c3820c94c1c0c7c6820bef820df5c7c6820aca820fc1c7c6820e2d82124ec4c3820be4c4c3820f54c7c6820268820d85c4c3820a86c7c6820911820f99c7c682030c820992c4c3820425c4c38211fdc4c382093ac1c0c4c3820798c1c0c4c3820bf3c3c281a1c7c6820cc0820f65c4c38210cdc4c38205b8c4c3820d40c7c682067a820b30c7c682077e820b2bc1c0c7c6820a97820bfac4c38208f2c7c68201a8820d3ac4c38201bec4c38211d8c4c3820375c4c3820c48c1c0c4c38204fec5c4518204d0c4c38203f7c4c3820328c7c68202b28211b6c7c6820bfe821280c4c3820f89c4c3820df7c4c382033ac4c3821264c4c3820317c4c3820988c4c3820c29c1c0c1c0c4c3820740c7c68204f4821183c1c0c4c38208fcc4c3820ac2c1c0c4c3820e13c4c3820c2dc7c68205888205d9c4c3821241c4c3820804c7c682109d8210edc4c3820c10c4c3821116c4c3821207c4c3821033c4c3820568c4c3820e7bc4c3820d54c4c382102fc1c0c4c3820615c4c38207ebc1c0c4c382055ac4c3820b81c7c6820f92820fe0c2c145c4c3820a68c1c0c7c6820467821122c1c0c4c3820486c1c0c7c68202ee820681c4c3820795c4c3820509c4c38206bcc4c38212b2c1c0c4c3820d5cc4c38209e9c4c382061bc1c0c4c382102bc1c0c4c3820fddc1c0c4c3820d50c4c3820289c7c682048f820de9c4c3820cabc4c3820a24c3c28188c7c68207448211dec4c38203b5c4c3820825c1c0c4c3820601c4c3820e04c1c0c4c38208b1c4c382081cc4c3820ca3c4c3820658c4c38208a7c4c38201dec7c68201c282109fc1c0c4c3820299c1c0c4c3820500c4c38204aac1c0c1c0c1c0c1c0c7c68208bb8212bcc7c68205d6820f20c7c6820338820a8fc1c0c7c68202af82099dc7c68206958210e5c1c0c1c0c4c3820edac7c6820bec820dc1c4c3820946c1c0c7c68203e582060bc7c6820cd282101bc4c3820a4bc4c3820d35c7c68208e38209a2c1c0c1c0c4c3820f30c4c3820ebbc4c3820bd2c4c382080bc4c38210cbc7c68208b78208c0c4c3820b54c4c38202ddc4c382065bc4c3820b49c7c6820b6682116ac4c3820d64c4c382090bc4c3820ec1c6c581b4820489c7c6820335820a54c4c3821034c7c68201a98203fec7c68203ba821010c7c68203e7821186c1c0c4c382089fc4c3820b6dc7c6820612821196c7c6820f51821022c4c38211e1c3c281abc4c3820d27c4c3820d2fc4c3820860c4c38208fac4c3820c34c4c3820781c7c682032b820ecbc4c3820f2cc5c420820bd9c4c3820583c7c682086b820ae4c4c3820e44c7c6820274820ab0c7c6820d6d821233c1c0c7c6820d508212d4c7c6820b178211abc7c6820a02820bc6c2c13dc4c38210f4c7c6820437820dadc7c68204a8820862c1c0c1c0c7c68205fb821280c1c0c1c0c7c6820a8d8210abc4c38210a6c4c3820c93c4c3820921c4c38203e4c1c0c7c6820e6f821231c1c0c4c3820ee8c1c0c4c3820970c4c3820f8ec4c3820780c4c3820c22c1c0c1c0c4c3821311c4c38212fac4c38210dbc4c3820ba6c4c3820739c1c0c1c0c4c38205edc1c0c4c3820f82c1c0c7c68203748204d8c7c68204e4820d1ac4c3820c99c4c382096dc4c3821194c7c6820826820aa3c1c0c4c3820e5dc1c0c7c6820977820ff0c1c0c4c38202aac1c0c4c38205c6c1c0c1c0c7c6820a5a820dc6c5c47b820f8ec4c3820b34c4c3820673c7c6820fad821217c1c0c4c3821038c7c6820746820d44c4c382111fc4c382046ac4c3820a6ec4c3820ad9c7c6820be8820d53c7c6820634820a52c7c6820bf0820cddc1c0c4c38208f6c4c3820949") +} + +func block8966() string { + return string("8966,01fa01b490fa01b48cc2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c156c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c122c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c155c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c177c1c0c1c0c3c2819dc3c281c1c1c0c2c101c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c16bc1c0c1c0c3c281fdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2818cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820106c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c12ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28190c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820139c1c0c1c0c3c281bec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820158c1c0c1c0c2c162c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d9c2c137c1c0c1c0c2c126c1c0c3c28187c4c3820122c1c0c4c3820129c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820107c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382013cc1c0c4c3820122c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281e6c1c0c1c0c1c0c4c3820140c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281b1c1c0c1c0c1c0c3c281aac4c3820197c1c0c1c0c1c0c3c281d6c4c3820168c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820191c3c281cdc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201c1c1c0c1c0c1c0c4c382017cc1c0c1c0c3c281e3c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d4c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201e9c1c0c1c0c1c0c2c174c1c0c1c0c1c0c1c0c3c281bcc4c38201aac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c160c1c0c1c0c1c0c1c0c1c0c1c0c4c3820244c1c0c1c0c1c0c1c0c4c3820257c1c0c3c2818dc1c0c6c5818f820188c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382016ec1c0c4c382019bc6c58189820134c1c0c3c281f7c1c0c1c0c1c0c4c38201d0c1c0c1c0c1c0c4c3820154c1c0c1c0c1c0c2c179c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201d0c1c0c1c0c4c382018cc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820223c4c3820261c1c0c1c0c4c3820186c4c3820269c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202bbc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820256c1c0c4c38201f8c1c0c1c0c1c0c1c0c1c0c4c3820208c3c281afc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38201d6c1c0c1c0c1c0c1c0c4c3820277c1c0c1c0c3c281e9c6c581ce82014ec1c0c1c0c1c0c1c0c2c132c1c0c4c38201a4c4c38202e6c1c0c1c0c4c38202f9c1c0c1c0c4c3820271c1c0c4c38202f1c1c0c3c281f1c1c0c4c382025ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202cfc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c17bc1c0c2c157c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28181c1c0c1c0c1c0c1c0c1c0c1c0c3c281b3c1c0c4c3820105c1c0c1c0c4c38202a3c1c0c1c0c2c165c1c0c1c0c1c0c1c0c7c68201bc820252c1c0c1c0c1c0c1c0c1c0c4c38202c9c1c0c4c382020fc2c121c1c0c1c0c1c0c3c281e7c4c3820292c1c0c1c0c1c0c1c0c2c161c4c38201eac1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c7c682027c820344c4c38201c6c4c382020cc1c0c4c382015dc1c0c1c0c1c0c1c0c1c0c1c0c7c6820327820363c3c24c5ec1c0c4c382025cc1c0c1c0c1c0c1c0c3c281f0c1c0c1c0c7c68201208202b9c1c0c4c3820361c1c0c1c0c1c0c2c14ac1c0c4c382029bc4c3820110c4c382022ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819cc1c0c1c0c1c0c1c0c4c3820156c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281d7c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820240c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c28183c1c0c1c0c1c0c1c0c4c38203bfc4c382027fc1c0c1c0c1c0c4c382011ec4c3820371c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820176c1c0c1c0c1c0c2c154c1c0c1c0c1c0c4c382012bc7c682012382029ec1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820118c1c0c1c0c2c12ec2c106c4c382019dc1c0c1c0c3c281b7c4c38203e7c1c0c4c38202a9c4c3820136c1c0c4c38201c3c4c3820153c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382032dc1c0c1c0c4c3820139c1c0c4c38203d7c1c0c1c0c1c0c1c0c1c0c1c0c4c3820261c4c382032ec1c0c1c0c1c0c1c0c1c0c2c13cc1c0c4c38202e1c3c281f3c1c0c1c0c1c0c4c38201abc1c0c1c0c1c0c1c0c3c28191c1c0c1c0c1c0c1c0c4c382020ac1c0c4c38203e4c4c3820412c4c38202e8c1c0c4c382034ec3c281efc1c0c4c3820142c4c3820172c1c0c1c0c1c0c1c0c4c38203ffc1c0c1c0c1c0c1c0c1c0c1c0c4c382022dc1c0c3c281f2c1c0c1c0c1c0c4c382028cc1c0c1c0c1c0c7c68201b18202e2c1c0c7c68203128203f4c1c0c1c0c4c3820357c1c0c1c0c4c3820148c1c0c3c281f0c4c3820187c4c382035ac1c0c4c382017ec1c0c1c0c1c0c4c382033dc1c0c1c0c1c0c1c0c4c3820457c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820362c4c382014cc4c382013ec2c176c3c281e8c1c0c1c0c1c0c1c0c4c3820128c1c0c1c0c4c38203acc4c38203d1c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38202f8c1c0c1c0c1c0c1c0c4c382041ac1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382039cc1c0c4c38202e5c1c0c1c0c4c382027ac1c0c7c682028d820446c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820292c1c0c1c0c1c0c1c0c4c3820236c1c0c1c0c1c0c6c5818c8201ebc4c382035cc4c38203fcc1c0c1c0c1c0c4c3820401c2c163c3c2819ac1c0c4c382015ac1c0c1c0c4c382026cc1c0c1c0c1c0c7c6820250820459c2c137c1c0c1c0c1c0c1c0c4c382041cc1c0c1c0c1c0c1c0c1c0c4c38202fdc1c0c4c38202d2c2c10ac1c0c1c0c4c38203a5c1c0c1c0c4c3820315c1c0c1c0c1c0c1c0c1c0c4c38203f2c1c0c1c0c1c0c1c0c2c153c4c3820295c1c0c4c3820260c2c17fc3c281a5c4c382045dc1c0c1c0c1c0c4c3820427c1c0c1c0c1c0c1c0c3c281ccc6c581c1820263c4c382030cc1c0c4c38203fbc4c38201e4c1c0c1c0c1c0c1c0c1c0c1c0c2c121c1c0c4c382029bc1c0c3c24450c1c0c1c0c4c3820275c1c0c1c0c4c3820260c1c0c1c0c4c38204b2c1c0c1c0c1c0c1c0c1c0c1c0c4c382043dc4c3820386c4c38203ffc4c38203f9c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c382017fc7c682038482039fc1c0c7c68201818202b6c4c3820377c1c0c4c382044fc1c0c4c3820152c1c0c1c0c1c0c4c382033dc2c170c4c38201cfc4c382026fc4c3820175c4c38203e6c4c382025fc4c38202dfc1c0c1c0c4c382012ec1c0c1c0c1c0c1c0c1c0c1c0c4c3820126c3c28188c1c0c6c581e1820270c1c0c4c38204edc1c0c1c0c2c169c1c0c1c0c1c0c1c0c1c0c4c3820285c1c0c3c281b0c1c0c1c0c4c38203e6c4c3820227c1c0c1c0c1c0c1c0c1c0c1c0c1c0c6c581ab820297c4c38202f5c1c0c1c0c1c0c1c0c4c3820348c4c3820320c4c382020ec1c0c1c0c1c0c1c0c1c0c1c0c7c68203538203a0c4c38201fdc1c0c1c0c4c3820471c4c38202aac1c0c4c3820102c1c0c1c0c1c0c1c0c1c0c4c382056ac4c38204cac1c0c1c0c1c0c4c38203d0c1c0c1c0c1c0c1c0c1c0c4c38202b1c1c0c1c0c1c0c2c127c1c0c1c0c1c0c1c0c4c3820161c1c0c1c0c4c38204abc1c0c4c3820232c4c38203b2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c6c581fe8202c5c1c0c1c0c1c0c4c38201bdc1c0c1c0c4c382014ac7c68201b88201e8c1c0c1c0c1c0c4c382032cc1c0c1c0c1c0c1c0c4c3820157c4c3820483c1c0c1c0c4c38201f9c1c0c1c0c1c0c4c38204eac2c109c4c3820133c1c0c1c0c1c0c4c38204cac4c38202fac1c0c1c0c4c3820439c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38203f3c1c0c4c3820340c1c0c1c0c4c382017fc1c0c1c0c1c0c1c0c4c38205c9c3c281ebc1c0c1c0c1c0c4c3820488c4c382034cc4c3820319c1c0c4c3820467c1c0c4c38205abc4c3820198c1c0c4c38202e8c1c0c1c0c1c0c1c0c1c0c5c44282053fc2c171c1c0c1c0c1c0c1c0c4c382015ac4c3820341c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820155c1c0c1c0c4c3820124c1c0c7c682011182015bc1c0c1c0c2c159c1c0c3c281e4c1c0c4c382041ec4c3820201c1c0c2c11fc1c0c1c0c4c3820506c1c0c1c0c1c0c4c382050dc1c0c1c0c1c0c1c0c4c38204b5c1c0c1c0c2c10fc1c0c1c0c1c0c1c0c1c0c4c3820533c1c0c4c3820239c4c38201e2c1c0c1c0c1c0c1c0c4c38204a8c1c0c1c0c1c0c1c0c1c0c1c0c4c3820143c1c0c1c0c1c0c4c382052fc4c382025ec7c682015d820256c4c3820518c1c0c4c3820464c4c382038fc1c0c1c0c1c0c4c382051bc4c382052cc1c0c1c0c4c3820338c4c38205aac4c3820164c4c382013fc1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820431c1c0c1c0c1c0c4c3820193c1c0c4c3820552c7c682017782057dc4c38203b3c1c0c4c382023ac1c0c1c0c1c0c2c111c4c38201f8c1c0c3c28194c1c0c4c382053ec1c0c1c0c1c0c1c0c1c0c4c38205bbc1c0c2c133c7c68203b18204f7c1c0c1c0c1c0c2c15ac1c0c5c4648205cac1c0c4c3820175c4c382033fc2c116c1c0c1c0c2c12bc7c68201388202d0c4c382061cc4c382058fc1c0c1c0c1c0c7c68202798202c1c1c0c1c0c1c0c2c139c1c0c1c0c4c3820108c1c0c4c38204c0c1c0c4c3820296c4c382059cc1c0c1c0c4c3820253c1c0c4c3820462c4c38201b0c7c68201fc8204f8c1c0c4c3820634c1c0c1c0c1c0c1c0c1c0c1c0c4c3820341c1c0c5c43c8203a9c4c3820426c1c0c4c38202c0c1c0c3c281b8c4c3820401c4c3820615c1c0c4c38205edc1c0c1c0c1c0c1c0c1c0c4c38205acc4c3820410c4c3820583c1c0c1c0c4c38202e4c1c0c4c3820429c1c0c1c0c1c0c1c0c3c281b0c1c0c1c0c1c0c4c3820594c4c382028fc3c281d6c1c0c4c38204ecc1c0c1c0c4c3820676c1c0c1c0c1c0c4c3820205c1c0c1c0c4c382065fc1c0c4c38204fec1c0c4c382035bc1c0c1c0c1c0c4c382041dc1c0c4c382053fc4c38203cec4c38203b7c4c3820218c1c0c4c3820414c1c0c7c6820200820453c4c38204ecc4c382050ec1c0c1c0c4c3820690c1c0c4c38206b5c4c3820118c4c382010ac1c0c1c0c1c0c5c43682019cc1c0c1c0c7c682016582018fc7c682010982054fc7c68201bb820376c5c4638203e5c1c0c4c38203d4c1c0c1c0c6c581c68202f0c1c0c4c3820581c4c3820332c1c0c4c382027ec1c0c1c0c4c38203bbc4c38205bcc4c3820465c7c68205258205e6c3c281f8c1c0c4c382038dc1c0c4c382057dc4c382034ac4c3820391c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c2819bc4c3820592c1c0c1c0c7c68204728205fec4c38204d5c4c38202a6c4c38203e0c5c434820259c4c3820178c1c0c4c38205b8c1c0c1c0c1c0c1c0c1c0c1c0c4c38202bdc4c3820675c4c382050dc1c0c4c38205a7c1c0c4c38206a7c2c12ac1c0c3c281e5c4c38204e3c4c382057bc4c38203c7c4c38206ebc4c38204b9c1c0c1c0c1c0c1c0c4c3820542c4c3820229c1c0c1c0c1c0c1c0c1c0c4c3820202c4c38203b6c1c0c1c0c4c382021ac1c0c1c0c1c0c1c0c4c3820302c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c38205d4c4c382060dc1c0c1c0c4c3820449c1c0c4c3820658c4c3820530c4c382072dc4c38201abc1c0c6c581f682028dc4c3820594c1c0c1c0c6c5819e8201b0c6c5819e820180c4c3820209c7c68205a88205c3c3c281d5c4c382061dc1c0c4c3820679c1c0c1c0c4c38206e2c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c3c281c3c1c0c4c382032ac1c0c4c382019bc4c38204c7c1c0c1c0c1c0c1c0c4c382011ec1c0c1c0c4c3820321c4c382051cc4c3820515c1c0c4c38204abc4c3820639c1c0c1c0c4c3820387c1c0c4c382030fc7c68202ca8206b1c1c0c1c0c4c38205f7c1c0c4c382040bc1c0c1c0c4c3158195c1c0c1c0c1c0c4c38201e4c1c0c4c382066cc4c3820730c1c0c1c0c1c0c1c0c4c38206cdc1c0c4c3820618c1c0c1c0c1c0c2c10dc1c0c1c0c1c0c1c0c1c0c4c3820303c1c0c1c0c1c0c4c3820328c1c0c4c3820294c4c3820385c4c38201d6c1c0c1c0c1c0c1c0c7c68202ef820603c1c0c1c0c7c68205058206fec4c38203fac1c0c1c0c1c0c1c0c1c0c1c0c4c382060ec7c682011b82070ec3c28194c1c0c4c38206acc4c3820490c4c3820115c4c3820590c7c68206a38206a4c3c2819bc1c0c1c0c1c0c1c0c1c0c1c0c4c38205e4c1c0c1c0c1c0c1c0c1c0c1c0c7c682024582038bc1c0c4c382037bc1c0c1c0c1c0c1c0c2c13dc4c382071cc1c0c1c0c1c0c1c0c1c0c1c0c4c3820559c1c0c1c0c4c38204a3c1c0c4c382077bc1c0c1c0c1c0c1c0c1c0c1c0c7c6820370820782c1c0c4c38206c6c7c68201d48203adc4c382041ac7c682017982035dc4c382035dc4c38204e2c4c38203bcc4c382068cc1c0c1c0c7c682026a8204dec4c38206d2c1c0c4c3820117c1c0c4c3820190c4c38206d6c1c0c1c0c1c0c1c0c7c68203aa820705c4c382057cc1c0c1c0c1c0c1c0c1c0c4c38201a2c4c3820143c4c38207fdc4c38205bac4c382072bc1c0c1c0c6c5819d8207bbc4c3820736c4c3820149c1c0c1c0c1c0c1c0c1c0c4c382059bc1c0c3c28190c4c38205bfc4c3820596c4c38206b9c1c0c7c682040882072fc4c38205bbc1c0c1c0c4c382036fc4c3820719c4c38201f3c4c38203fec4c38204adc1c0c1c0c4c3820151c4c382051ec1c0c1c0c4c3820774c1c0c1c0c4c3820332c4c3820805c3c28199c1c0c4c382018bc1c0c1c0c1c0c2c13ac4c38205f3c1c0c4c3820267c1c0c1c0c1c0c1c0c1c0c3c281fcc4c38203abc4c38205f0c1c0c7c68204bc820724c1c0c4c38205c7c1c0c1c0c4c3820507c1c0c1c0c1c0c1c0c7c682042b8204d2c4c382024fc2c12bc4c3820458c4c3820735c4c3820317c1c0c4c3820793c1c0c4c38206d8c1c0c1c0c4c382031fc1c0c7c6820237820313c1c0c4c382070ac4c3820502c1c0c4c38203d8c4c38202ccc4c3820388c1c0c1c0c1c0c1c0c1c0c1c0c7c68205a98206e4c4c38201bdc1c0c4c3820367c4c3820255c1c0c1c0c7c68203ad8204a0c4c38201dfc1c0c1c0c1c0c1c0c1c0c2c147c7c6820624820693c1c0c3c281c6c4c38206dac4c38201fbc1c0c4c382060bc1c0c4c38201f9c1c0c1c0c4c3820754c1c0c1c0c1c0c1c0c4c38201d9c7c68202af8204b4c4c382070fc4c382078bc4c36a8197c1c0c1c0c1c0c4c382011fc4c38206eac1c0c5c45c82079cc1c0c1c0c4c3820807c4c38206f4c1c0c4c382043cc7c6820314820426c7c682043c820447c1c0c1c0c4c382066bc4c382086cc4c382017bc1c0c4c3820361c1c0c1c0c4c38202a4c2c143c1c0c1c0c4c38207e2c4c38204c5c4c3820693c1c0c4c382055ec4c3820845c4c3820599c3c2818bc3c281afc4c3820270c4c38207a4c4c3820127c1c0c1c0c7c6820238820857c4c3820572c4c3820524c1c0c1c0c1c0c4c3820219c4c38206f9c4c382088fc4c3820671c4c38201a5c1c0c7c6820226820286c2c166c4c3820632c1c0c1c0c1c0c1c0c4c3820566c4c3820733c1c0c4c382082ac4c3820669c1c0c4c38202e7c4c38205e0c4c382083bc1c0c3c281e8c4c38203d4c1c0c4c38207c7c4c3820771c4c38201f1c4c38204b7c3c281ccc4c3820626c5c47d8201f6c1c0c1c0c6c581f982010fc1c0c4c382064fc1c0c4c382042bc1c0c4c382068ac1c0c4c3820215c4c38208d2c1c0c7c68205498205dfc1c0c4c382085fc4c38204fac1c0c4c3820743c5c40182053ec1c0c1c0c7c68205cd820748c1c0c1c0c4c382087dc1c0c4c3820765c1c0c4c38206dec2c11cc4c38208aac1c0c4c38201cac1c0c1c0c4c3820235c7c682034e8205f2c4c382067bc1c0c1c0c4c38207cec1c0c4c38203aec4c3820276c1c0c1c0c1c0c4c382071dc1c0c1c0c4c382077fc4c382039ac1c0c1c0c1c0c1c0c1c0c4c3820479c1c0c7c68207c58207f6c1c0c4c38207d2c1c0c1c0c2c110c4c3820397c4c382033ac4c3820788c1c0c3c281ddc4c3820450c4c38205afc1c0c4c38206b4c1c0c7c68202dd8203ddc4c38206f8c1c0c4c38202cec1c0c6c581e282086ac1c0c7c68205db820685c1c0c1c0c1c0c1c0c1c0c4c38203f7c1c0c1c0c1c0c1c0c4c3820639c1c0c7c682087c8208dac4c3820785c1c0c4c382075cc1c0c4c3820792c4c38204a2c4c3820496c4c3820889c4c3820394c4c38208fec7c68202968205c8c1c0c1c0c1c0c4c3820142c1c0c1c0c4c3820128c1c0c1c0c2c14fc2c16ec3c28185c4c3820727c1c0c1c0c4c38201b5c1c0c4c3820539c1c0c4c382076dc1c0c1c0c7c68205608208f9c4c382047bc1c0c4c3820897c4c3820392c1c0c4c38207dcc4c38207b5c7c682044182079fc4c3820807c2c15ec1c0c3c281b3c1c0c1c0c1c0c7c6820221820804c1c0c7c68202cb820838c1c0c4c3820365c2c107c1c0c1c0c4c3820214c1c0c1c0c1c0c4c3820932c1c0c1c0c2c12cc4c38207e8c1c0c1c0c1c0c4c382019fc1c0c4c3820213c1c0c4c382049fc1c0c1c0c1c0c1c0c4c382068bc4c38204e9c1c0c1c0c1c0c4c382049bc4c38206b6c4c38205fcc4c3820739c3c281bdc4c3820810c4c3820959c3c281c9c7c682063b820755c4c3820104c1c0c4c3820468c1c0c4c3820425c1c0c4c38201c7c1c0c4c3820470c1c0c1c0c4c382064dc1c0c1c0c4c382076fc1c0c4c3820620c1c0c4c38208a5c1c0c1c0c4c382033ec4c38206d7c1c0c4c38203c4c1c0c1c0c4c3820879c1c0c4c38205ddc4c3820826c1c0c4c382030dc4c382018fc7c68203f2820513c1c0c1c0c4c38205a2c1c0c1c0c2c150c4c38207ddc3c281c3c4c3820430c4c3820991c1c0c1c0c4c38203d8c4c382068cc4c38208b6c1c0c4c38208a4c1c0c4c382087fc1c0c4c38203ecc1c0c1c0c4c3820544c1c0c1c0c4c382069dc7c68203b182094ac4c38206b8c1c0c4c3820795c6c581a282098bc1c0c4c38204dbc1c0c1c0c4c38205f7c1c0c2c10dc1c0c7c68208058208abc4c3820556c1c0c4c382014fc4c38207a5c1c0c4c38209b6c4c3820764c1c0c4c3820374c4c382060ec4c38207f0c7c682019882080bc1c0c2c102c1c0c1c0c1c0c4c38201f5c1c0c4c38208dfc4c3820972c1c0c4c3820121c4c38205ecc4c38209ecc4c38209cec1c0c4c38204e8c1c0c4c382024cc1c0c4c38206e3c4c382097dc7c682053782080ec1c0c1c0c1c0c1c0c7c682056182096bc1c0c4c3820451c4c3820775c1c0c1c0c4c3820297c4c38206d0c1c0c1c0c4c38208c0c7c6820477820696c4c38204aec4c382080dc1c0c4c38208e9c4c3820129c4c3820702c4c38208c5c4c382070bc4c38205e5c1c0c4c38207a2c4c38204e5c1c0c1c0c4c38205ccc4c382083fc1c0c1c0c4c3820337c4c3820205c1c0c4c38206f0c4c38208e1c1c0c4c3820436c4c382047ec1c0c2c128c1c0c4c3820411c1c0c4c38207d6c4c38202a4c1c0c1c0c4c382060dc2c143c4c3820641c4c382020ec4c3820147c4c382089ac4c38201a3c1c0c7c68202bf820770c1c0c4c38209bdc4c382054cc4c3820736c4c38209dac1c0c4c38203ebc1c0c1c0c4c382016cc1c0c7c68201928207b8c7c682058d8207bcc1c0c7c682030e82069bc1c0c4c38205cbc1c0c6c581ca8203a1c4c38204dcc4c3820475c4c38204dac1c0c1c0c4c3820336c4c3820225c5c4358206a9c4c38206bfc4c38203a7c4c3820166c4c3820797c1c0c1c0c4c382029fc4c38202b2c4c3820686c1c0c1c0c1c0c1c0c4c382086bc4c3820231c1c0c1c0c4c38205d2c7c68203308203d5c7c682069782099bc4c382066ac4c3820345c1c0c1c0c1c0c4c382061ec4c3820652c1c0c1c0c4c3820119c1c0c1c0c4c3820863c4c382099bc1c0c4c38208fdc1c0c1c0c1c0c4c38204d3c4c3820172c4c3820896c4c38207b7c7c6820335820602c1c0c4c38207efc1c0c1c0c1c0c7c682019282042ec4c382095ec1c0c6c581f382073bc4c3820a36c4c382099dc1c0c7c6820980820a54c4c3820112c4c38203bfc1c0c2c10ac4c3820925c4c3820718c1c0c1c0c4c3820757c4c3820955c1c0c4c3820633c1c0c7c682024b8202a5c7c682072f820a70c1c0c1c0c1c0c1c0c1c0c1c0c1c0c4c3820973c4c382058bc7c68203c182051fc4c382067ec1c0c1c0c4c382069ac4c3820448c1c0c4c3820116c4c38202e0c1c0c1c0c4c3820489c4c3820473c1c0c1c0c3c281d2c7c68205d8820656c4c38207aac4c3820896c1c0c1c0c4c38208fcc1c0c1c0c4c3820876c4c3820aaac4c38209e4c1c0c7c68203818207edc1c0c1c0c4c3820605c4c3820a4fc1c0c1c0c1c0c1c0c4c38203dec4c3820a1bc1c0c4c3820347c7c6820536820704c1c0c1c0c4c382026fc4c38203f8c1c0c4c3820613c1c0c1c0c4c3820640c4c38201e8c1c0c1c0c1c0c4c382089ec1c0c3c2818bc1c0c4c38203b5c1c0c1c0c5c469820925c1c0c4c3820266c7c68208ef82094cc4c3820766c4c3820a52c4c3820833c4c38206acc3c281dcc1c0c4c3820919c1c0c1c0c4c3820993c3c281eec4c3820590c7c682037c820ac0c4c38209c0c1c0c1c0c2c13bc1c0c4c38209dbc4c38201c9c1c0c4c3820709c1c0c7c68205e9820a7fc4c38207bdc1c0c1c0c4c38208f7c1c0c1c0c1c0c4c382034cc1c0c4c3820715c1c0c4c382026cc1c0c4c38206bac1c0c4c38206a1c2c130c4c38202bac3c28180c1c0c4c38209aac7c682038f8209d6c4c382047ec4c38207aec1c0c4c3820a78c4c382016dc4c3820a9dc4c3820965c4c382051dc7c68208608208e3c4c3820150c4c38209dfc7c682059a82080bc1c0c1c0c4c38202e5c5c41d820554c7c68208f9820956c4c3820a8fc4c3820378c1c0c4c382026dc4c3820278c1c0c4c382058ac4c3820723c3c281c8c7c682020b8209fec4c38204e0c1c0c1c0c4c38205f8c1c0c7c6820664820776c4c3820a60c1c0c4c382085dc4c3820517c7c682068f820998c1c0c1c0c1c0c1c0c4c382063ec7c6820419820992c4c382039ec1c0c1c0c1c0c7c68204a2820aa9c4c3820869c1c0c4c3820309c1c0c4c38207bac4c3820781c1c0c7c68201258207d8c1c0c7c6820817820922c4c38208d9c4c3820882c5c41a820364c4c3820406c4c3820641c1c0c4c3820169c1c0c4c382027dc7c68202b8820661c4c38206adc1c0c4c38205a5c7c682077f820969c4c38202b6c1c0c1c0c1c0c4c3820a5bc1c0c7c68201ce820309c4c38203c0c7c682053d82086ec4c3820832c1c0c1c0c4c382086ac4c3820687c1c0c4c382095bc1c0c7c6820904820a35c1c0c4c38208efc1c0c4c3820a11c1c0c1c0c4c3820232c1c0c4c38203a1c1c0c1c0c4c3820908c4c3820645c1c0c4c3820a88c4c38209a9c1c0c1c0c7c68206c88208c0c4c382040ec3c2818ac1c0c4c382094cc7c682037a8208a6c4c3820415c7c6820253820450c4c3820812c7c682021b8204eec4c382040dc1c0c4c3820a7ac4c38202cfc4c3820190c4c38206e3c4c3820a2dc4c3820745c1c0c4c3820867c4c3820a4fc1c0c4c3820267c4c38209cec4c3820b64c4c38208adc7c68204da8205e1c4c3820931c1c0c4c3820b80c1c0c1c0c4c3820753c4c382091bc1c0c1c0c1c0c4c3820760c4c38201d2c1c0c4c3820554c4c382043fc4c3820478c1c0c1c0c4c38203a4c1c0c4c3820984c4c3820a7cc7c6820120820ae0c4c382029ec4c3820542c2c107c1c0c1c0c4c38209f8c4c38206a6c4c3820940c4c38208eec1c0c1c0c4c38207e7c1c0c1c0c7c682027c8205a9c4c3820510c4c38207edc1c0c1c0c7c68209ec820bc9c1c0c4c382038ac7c6820398820474c1c0c4c382058fc1c0c3c281adc7c68201ef82066ec1c0c4c382054ec4c3820355c4c38203afc1c0c7c68206e6820940c1c0c3c281e5c1c0c1c0c1c0c7c68206bd8209a7c1c0c4c382092dc1c0c1c0c4c3820250c1c0c4c382068ac1c0c4c3820159c1c0c1c0c1c0c4c382021fc7c682046a820b18c4c3820902c1c0c7c6820908820b86c4c382045bc4c3820a62c4c38203c5c1c0c1c0c4c38209f5c4c3820a3dc1c0c1c0c1c0c4c38203b5c4c382066fc4c382031bc4c38201e6c1c0c1c0c4c38205e5c1c0c1c0c1c0c5c4788207c1c1c0c7c6820264820a6bc1c0c4c3820b3ac4c38207e0c4c3820938c4c3820a05c7c6820712820b26c4c382069cc7c682066d820c06c1c0c4c38208b3c4c382019ac1c0c2c13fc4c3820b9cc2c129c1c0c4c38205cac1c0c4c3820607c4c3820994c4c38207d3c1c0c1c0c4c382073dc4c3820637c7c682045682048ec1c0c1c0c4c38206e9c1c0c1c0c4c3820a93c1c0c1c0c4c3820618c4c38201c4c4c3820518c1c0c1c0c4c3820571c4c38205a4c1c0c4c38202c2c1c0c1c0c7c6820504820767c1c0c6c581868206c5c7c6820a02820be9c4c3820b01c4c3820112c1c0c1c0c1c0c4c38205acc4c382051dc1c0c7c6820468820bb3c1c0c1c0c1c0c4c3820965c4c38206bec4c382059fc1c0c1c0c4c3820865c4c38204fcc4c3820298c1c0c4c3820759c1c0c4c3820babc4c38204a1c4c382046ac4c38209fcc1c0c4c38208fec4c382044bc4c382082cc7c682061582084dc1c0c4c3820421c7c68208fd820a19c1c0c1c0c4c3820945c1c0c1c0c2c120c7c682010082090ac4c3820b3dc4c38204a9c1c0c1c0c4c3820517c1c0c3c281dec4c382017ac7c6820527820affc4c3820c48c4c38207e0c1c0c1c0c4c38205dcc4c3820135c3c281ffc4c3820c2bc1c0c1c0c7c682036f820844c1c0c3c281cfc1c0c4c382092cc7c6820756820c5cc1c0c4c38202c9c4c3820552c7c682046c8205fdc4c3820606c3c281d5c4c3820663c1c0c5c440820b58c4c38203a6c1c0c5c4758207b9c7c682077e820842c1c0c1c0c1c0c4c38209d8c4c3820461c1c0c1c0c1c0c4c38207e3c7c682065a820a09c7c68204738204ffc1c0c1c0c1c0c1c0c7c6820820820beac4c3820717c7c682040f820c84c4c3820c2ec4c3820ac9c4c3820953c1c0c1c0c4c38205b2c4c38208a8c1c0c4c38207bdc1c0c1c0c4c3820802c4c3820a06c1c0c4c3820986c4c3820798c1c0c1c0c1c0c4c38205b2c1c0c1c0c1c0c4c3820c94c1c0c3c281dcc4c382048ac4c38203f1c4c3820722c4c3820796c1c0c1c0c4c3820308c4c3820b0bc1c0c4c382079ec1c0c4c38207e1c1c0c1c0c4c3820557c7c682018a820744c1c0c1c0c7c682068982073ec4c382035fc1c0c1c0c1c0c4c3820aefc4c38206fcc3c281bcc6c5819382014bc1c0c1c0c1c0c4c3820cbdc4c37d818ec1c0c4c3820211c4c38202a1c1c0c4c3820695c4c3820239c4c382075bc1c0c4c3820379c4c3820be5c4c38202f3c1c0c1c0c4c382029cc7c68207c6820b03c7c68206fd82075ec1c0c4c3820339c7c68201f2820bd2c1c0c1c0c4c3820afac7c6820922820b06c4c3820968c4c3820393c7c682066b820c9ec4c38206fcc1c0c4c3820907c7c68202dc820a9ac4c38202dac4c382070cc4c38205bdc7c68203bc82098ac1c0c7c682050b820a68c1c0c4c3820b83c4c3820726c4c38208cdc4c3820775c4c3820874c4c38203dec1c0c7c6820c11820c26c1c0c1c0c4c382023cc4c3820754c4c3820856c4c38205b6c7c68207e88209cac4c3820638c1c0c4c3820963c4c38205c4c4c3820af3c4c3820984c4c3820c9dc4c38201adc1c0c1c0c4c38207a4c7c68205558207aac5c45f820392c4c3820ca8c4c38203a8c4c3820995c4c3820686c4c38207b6c4c382028ac1c0c4c38209d1c7c682068d8209b4c4c38208dfc4c3820375c1c0c1c0c4c3820baac7c682039d82075fc4c38204dfc1c0c1c0c1c0c4c3820af1c4c3820780c4c3820b64c1c0c1c0c4c3820cb6c4c38206adc1c0c4c38201c2c7c682096e820b96c4c3820636c4c3820d06c4c38209f1c7c68203268205ccc5c46d820b71c7c68206ec820cb7c4c38202b3c4c3820970c4c3820bbbc1c0c4c3820978c4c38202f1c4c3820a4bc7c68204b3820d13c7c6820915820a7dc1c0c4c3820429c7c68203ae8205c0c1c0c4c38208b4c7c682045c820484c1c0c6c581e28203e1c1c0c1c0c4c38206e4c4c3820536c4c382017dc1c0c4c3820adac4c3820b7ec1c0c4c3820227c1c0c1c0c4c38209d0c1c0c1c0c4c3820c80c4c3820a00c4c3820cf7c1c0c1c0c4c3820a34c1c0c4c38205fac1c0c4c38209aec4c382038cc7c6820481820cf0c7c68206b6820988c4c3820b34c7c682081a8209bbc4c3820532c4c3820aa4c4c3820526c1c0c4c3820208c4c3820694c1c0c1c0c4c3820c8cc1c0c1c0c1c0c1c0c4c38201e7c4c38209c5c4c3820d46c1c0c2c172c1c0c6c5819f820b2bc4c382028ec4c3820b09c4c3820784c7c682032b820b67c4c3820afac4c38205c7c4c3820acbc4c3820334c4c3820af8c7c682080c820a91c7c68202a78208f1c1c0c4c3820abbc1c0c4c3820c89c4c3820cd5c7c6820810820d6dc4c3820674c1c0c4c3820625c5c4178201f5c1c0c2c14ec2c15bc2c151c4c3820be4c1c0c4c3820b96c1c0c1c0c1c0c4c38207a0c4c3820d6ac1c0c4c3820c49c1c0c6c581f4820a1ec2c10cc1c0c1c0c4c3820152c1c0c7c68206a2820ae9c1c0c4c382054ec1c0c4c3820b68c1c0c7c68208c18208c6c4c38204e4c4c38205fcc1c0c4c38204b6c1c0c4c3820d11c4c382034ac4c38201ecc4c3820cbbc1c0c1c0c4c3820a63c4c3820911c4c3820cc5c4c3820d61c1c0c6c581ae8201e9c4c38201a6c2c16fc4c3820968c1c0c4c3820dadc1c0c1c0c4c3820ca5c7c682065e820b15c7c68205478207fbc4c3820857c4c382065ac1c0c6c581bf8206c2c3c281c4c2c17cc4c3820c0dc4c38201dac4c3820c9cc4c3820491c1c0c4c3820cf6c4c3820614c1c0c4c3820b83c4c3820be9c4c3820502c4c38207d7c7c6820249820c44c4c3820c56c4c38207e1c4c3820216c7c6820520820ac4c1c0c6c581ba8204e1c4c3820667c4c38201d3c3c281c8c4c3820879c1c0c4c382022cc4c3820d2bc1c0c4c38208bdc7c682025b820a95c4c3820268c4c38208ebc4c3820946c7c68202cd82064cc4c3820aedc6c581c7820b55c1c0c4c382026bc4c3820870c4c3820888c1c0c1c0c1c0c4c382039ec7c6820959820b5cc4c3820804c4c382072dc7c682075b820b6ec6c581c2820d2ec1c0c4c38207a8c4c3820d6dc4c3820503c4c3820d61c4c38204c4c4c3820de9c4c3820549c1c0c1c0c4c382062dc4c3820220c7c68203ed82081dc1c0c4c382011ac1c0c4c3820d4ac4c3820c84c4c38202a8c4c3820108c4c3820df3c1c0c1c0c1c0c4c3820c8bc1c0c7c6820400820848c1c0c4c3820c8dc7c68201c582031dc7c6820156820a80c1c0c1c0c4c3820c47c4c3820dd6c7c682048d8205c9c1c0c4c3820252c1c0c1c0c4c3820102c4c3820b1fc4c38208cbc1c0c1c0c4c38201ccc1c0c1c0c1c0c1c0c1c0c1c0c4c3820870c7c68206f3820cf2c1c0c4c3820544c7c68203da820c92c2c158c4c38208b7c4c382099ec7c68204c8820ca6c4c3820d45c4c38206a5c4c3820d20c1c0c7c682058e82071ec4c3820a17c1c0c4c3820524c1c0c3c281b9c4c3820da7c4c3820e44c1c0c7c6820836820b29c4c38204d5c3c281b4c4c38206c0c4c38205dbc4c38208a5c7c6820953820abdc1c0c7c6820514820d69c1c0c1c0c1c0c1c0c4c3820caec2c172c4c3820cfdc1c0c7c6820445820db8c4c38208cec1c0c7c6820352820d0cc7c68202c68202e6c4c3820b92c4c3820889c1c0c7c682049982061ac1c0c4c38206ccc4c3820121c1c0c4c38205a2c4c3820293c1c0c1c0c4c38205b3c7c68202b782097cc1c0c4c3820de3c1c0c7c68204bf82099ac1c0c4c382052ac7c6820543820760c1c0c4c382040dc1c0c4c3820349c3c281dfc4c382065ec4c3820b05c4c3820b24c7c68207e7820980c4c38204c3c4c38207f4c3c281c2c4c3820854c4c3820990c4c38201d4c1c0c4c3820179c7c68203e3820d47c1c0c1c0c5c431820d66c4c38204b1c4c3820869c4c3820668c7c6820ca1820d72c4c38208a0c7c682010c82078ec4c3820c02c4c3820aadc4c3820c05c7c682012e820ccec4c3820461c4c38202d6c7c682062a82097fc1c0c1c0c4c3820acec7c68208e6820d13c4c38207a0c4c38205c1c1c0c4c3820e32c7c682063a820cabc4c3820e18c7c682020b8203fbc4c38206aec1c0c7c6820650820df2c1c0c1c0c4c38206cfc4c3820da8c5c424820c2fc1c0c1c0c1c0c1c0c4c3820176c7c6820742820b13c7c68203db820c7fc5c47a820e54c4c3820b87c1c0c4c3820ce4c4c3820c83c1c0c7c68203ca820bebc1c0c4c3820a40c4c3820c36c7c682089b820ea9c1c0c7c6820a45820d8ac4c38204aac4c3820dd1c4c3820134c7c68203be820b0fc4c382028bc7c68201ee820aedc4c38209bfc4c3820301c7c682054a820800c4c3820883c4c3820e16c1c0c4c3820694c4c3820d23c4c38204c4c7c682011f820796c7c6820c13820e61c7c6820bbe820c2dc7c6820cd6820ce9c4c3820883c1c0c4c3820656c1c0c1c0c4c3820d91c1c0c2c12dc3c281cbc4c3820e0bc1c0c7c6820155820b85c1c0c1c0c1c0c1c0c1c0c4c3820334c4c3820dc8c4c3820ca4c4c3820928c7c682052d820bddc7c6820346820758c4c3820d5ec4c3820deac7c682046e820b25c4c38202eec4c382038cc4c3820823c7c68204b28205cfc1c0c1c0c7c682099c820ca2c1c0c4c3820eedc4c382076ec1c0c4c38204e4c7c6820380820663c4c3820cb1c1c0c4c3820e24c4c3820d12c7c6820706820afcc7c682021082049bc7c6820771820a84c4c3820da1c1c0c7c68201c3820e3dc1c0c7c6820e22820e6ec1c0c4c3820354c4c38202dcc4c382041fc7c68204ed820dd2c4c3820462c4c3820399c1c0c4c382043ac7c682022f8209a8c4c3820c04c4c3820d8fc7c6820aa0820e63c4c3820d74c7c6820ace820eb8c4c38201bec7c6820924820edfc4c3820932c4c3820180c7c682073c820885c4c3820e60c4c382071cc7c682058c820787c4c38209b8c7c6820914820e31c1c0c4c3820315c4c3820ef8c2c138c1c0c4c38203ccc1c0c4c38209cdc4c3820a9bc1c0c1c0c4c3820928c4c38209edc4c382038ec3c281c0c4c3820f29c4c3820eebc4c3820b5bc1c0c7c6820824820da7c1c0c4c38204c0c1c0c1c0c3c281c0c7c6820168820d71c4c3820347c4c38207dcc1c0c4c382044dc7c682081e820eb5c1c0c4c3820b4fc1c0c7c68209d4820d5ac1c0c1c0c1c0c4c38205e3c4c3820713c2c123c1c0c4c382048dc1c0c4c3820d0dc7c68202908203d6c4c3820cfbc7c68206d0820e73c7c6820937820e93c7c682056c8206e0c1c0c1c0c4c382030fc4c3820d86c4c3820847c4c382013bc7c682016182074ac7c6820b59820d99c4c3820ea2c1c0c4c3820814c4c38205f0c4c3820eadc4c3820ab4c4c38202d6c1c0c7c682093e820962c4c382022fc5c467820dd7c4c382092fc4c3820591c4c3820f5fc7c6820565820d19c4c3820712c4c3820369c7c68207a8820e0dc1c0c4c3820b1dc4c3820404c4c3820487c1c0c4c3820ad7c2c16cc4c3820d0ac7c682078e820cd5c4c3820e72c4c3820e40c1c0c4c38202b5c7c6820553820c00c1c0c4c3820cfac4c382099cc7c682059f820f23c4c3820275c1c0c1c0c4c3820350c4c3820bc7c4c3820721c4c382074bc7c6820ae1820b45c7c68207168207d5c4c382071bc4c3820a1ac4c38209bbc7c68206148208eec4c3820dddc1c0c1c0c3c281f6c7c6820a3f820ab2c7c682044c8208d3c2c119c4c38204d0c1c0c1c0c7c6820a01820ac2c4c3820dbbc1c0c1c0c7c68207808207b4c4c38206c2c4c38209eac4c382084ec4c3820886c7c6820543820f0ac1c0c5c41b820f02c4c3820d66c1c0c7c6820b08820e33c1c0c1c0c1c0c7c68204588206b9c4c3820a20c7c6820b6a820e9ac1c0c4c3820f9ec1c0c1c0c4c38205f9c4c382057fc4c382042cc4c3820ad4c4c3820d0ac1c0c4c3820ea8c4c38206e7c4c3820daac4c3820568c7c68209f2820b9ac4c382020fc4c38202d7c4c3820595c1c0c4c3820c2fc3c281fac1c0c4c3820802c4c3820d51c2c17bc4c382057ac1c0c1c0c7c6820be0820d95c4c3820a70c4c3820d55c4c38202e7c4c3820fa3c4c3820e64c1c0c4c3820b8bc7c682026e8205cfc4c3820d07c7c6820289820ce5c7c6820803820f6dc4c3820774c4c38204d2c4c3820509c4c3820d77c1c0c1c0c4c3820929c4c38206aec1c0c4c3820eacc7c68207da8208a1c4c3820cc2c4c3820dc6c7c6820e3a820f3bc1c0c1c0c4c382061fc4c3820307c4c382024bc1c0c3c281a9c1c0c1c0c7c682023c82054dc4c3820fc2c4c3820fa0c4c3820f3cc7c68201dc82037ac1c0c1c0c2c112c4c3820902c7c6820beb820fc4c4c38207ffc4c3820ad1c4c3820bd4c1c0c4c38202a6c7c68202f3820d0bc4c3820ed5c7c6820bbb820d8ec7c68206c0820f57c7c6820477820ed8c4c3820440c4c3820e6ec4c3820619c4c3820cb0c4c382096cc4c3820738c4c3820f11c1c0c4c3820e26c4c3820d83c2c17cc4c3820ec0c4c3820367c4c3820dcdc1c0c4c3820e1ac4c3820ae5c1c0c4c3820534c4c382037dc4c382063cc4c3820aaec1c0c7c6820ca5820e4ec4c3820a86c7c682084e820c3dc7c6820356820f5bc4c3820c0dc1c0c4c3820f90c4c382040ac4c3820647c7c6820ad6820d2ec5c464820b3fc4c3820e31c4c3820dafc4c3820c1ec4c38201e1c4c3820d39c4c3820a1ec3c2818ec1c0c4c3820f12c1c0c4c3820eb0c7c6820881820c36c4c3820a01c1c0c7c68201cb820d3bc4c382047ac1c0c7c68201f0820933c7c6820bf1821017c1c0c4c38209f2c4c382065dc7c68201838206e7c4c3820db9c7c6820631820c14c1c0c4c38203a8c1c0c7c68207b1821020c7c68205e2821031c1c0c1c0c4c3820d9ec4c3820456c1c0c4c3820e71c4c382022cc7c68201d1820f3ec4c38204dec1c0c1c0c4c3820c89c1c0c4c382096fc7c6820565820842c4c38209d9c4c3820963c7c682028382103ec7c682058c8205d7c4c3820da0c3c281acc4c3820f28c1c0c1c0c7c682071a820ed2c2c151c4c3820720c1c0c1c0c7c68206aa82092ac7c682089c820ed4c4c3820a0cc7c68208cd820a51c4c3820cb1c7c68204cb820a55c4c3820203c4c382085ec4c38208a6c2c128c4c3820f03c1c0c7c6820181820c09c4c3820834c7c6820d7a820decc4c3820d89c7c6820137820620c4c3820584c5c42f820f6bc7c6820c59820cbac4c3820a9ac4c3820dcfc1c0c7c6820e5c820ef1c7c68203cb820cbcc7c6820946820e29c4c3820f32c4c3820821c4c38202c3c7c6820783820de6c1c0c4c38207cfc4c3820325c4c3820deac4c3820288c1c0c4c3820adac4c3821000c1c0c1c0c1c0c4c382047cc7c6820f0782103cc7c68208c1820ddac7c682035f8205eac4c3821080c4c3820cc9c4c382036ec1c0c4c3820ffbc1c0c4c3820194c7c6820f4a82107dc7c68204c1820fdec1c0c7c6820c85820fb0c1c0c7c6820c40820fecc1c0c1c0c4c382095dc4c3820dcac1c0c1c0c4c3820ee2c7c6820d12820f38c6c581d38204acc7c68206c6820d87c7c68205a3820be7c4c382024dc4c3820475c4c3820f81c7c68207b7820cc6c4c3820b86c4c3820f27c4c38208f8c7c68204ae8207f0c4c3820617c4c38201c0c4c382055dc4c3820c4bc4c38203f6c1c0c4c38207a1c1c0c4c3821063c4c3820e88c4c382031cc4c38203dcc4c3820368c4c3820647c2c14dc4c3820c8dc4c3820ce8c1c0c4c3820a3ac1c0c7c682037f820b4ac1c0c4c3820d9dc1c0c4c38202a2c7c68201058210b2c7c6820299820b5bc3c281aac1c0c4c38209bfc4c3821099c4c3820219c4c3820f46c7c6820a08820ef0c4c3820ca3c4c3820f61c7c6820eed8210a2c7c6820683820df5c4c3821053c1c0c4c3820616c1c0c4c3820cd0c6c581ea820f66c3c281d1c4c3820c41c4c3820a58c2c118c7c682019682055bc1c0c4c38205a8c7c6820cc8820d3ec4c382086cc4c3820848c4c3820f45c4c3820fc0c4c38208a2c1c0c4c3820f0fc4c3820688c7c6820874820bd0c1c0c4c3820bf8c4c3820a64c7c68208d5821007c4c3820f81c6c581f7820d3cc4c3820e0ac1c0c1c0c4c3820850c4c382054fc7c682018b821032c4c382090cc4c3820f49c7c682063d8208b0c4c3820bf0c4c3820632c7c6820903820d9dc4c38208cec7c68206dc8209b6c7c68207ca820a6fc4c38202e2c4c3820c29c7c682051982087bc7c68206db820b84c4c38206ecc4c382096dc6c581da820412c4c38207eac7c6820d21820eaec1c0c7c682106f821092c1c0c1c0c4c38207e2c4c3820f06c7c68208bc820bc2c4c3820366c4c3820d6cc4c38210cac7c6820adb821033c1c0c1c0c7c6820733820e12c7c6820d08820f26c7c6820b30820c1bc4c3820fd4c4c3820556c4c3820167c4c3821099c7c682052e820f70c4c38209dcc4c3820e39c4c3820d33c1c0c1c0c4c3820d08c7c68201af820417c4c38205b1c4c3820ea0c4c38208a2c4c3820a9fc2c124c4c3820d64c4c38209bac4c3820b69c4c382029fc4c3820f7ec1c0c1c0c7c68205f1820a02c1c0c4c3821057c1c0c4c3820b05c4c3821096c7c6820fd0821052c4c382099ac3c281c5c1c0c4c3820c9fc7c68205c2820b45c7c68208bd820dd7c4c3820c59c1c0c4c382050cc7c68207c8820de8c1c0c7c6820cc0820cf4c1c0c4c3820cf3c4c3820c4fc7c682045b820d16c1c0c4c38210aec7c6820eea821049c4c3820923c7c68208b282091bc4c38208d4c1c0c7c6820ed9820f71c5c43b820679c4c3820563c4c3820872c1c0c1c0c4c3820624c7c68206f3820fcfc4c3820c3ac4c382110fc4c382018cc4c382089bc4c3821040c7c68208ba820a54c4c3821083c4c382077ec7c682036a82104bc4c38205cdc1c0c5c46f820d38c4c3820bb3c4c38204e7c7c68205e7820b0dc4c3820930c4c38204d1c4c3820f7bc4c3820e63c4c3820ee6c4c382098cc4c3820bc2c7c6820a71820d5fc7c682056d820e73c7c682056682062cc4c38209abc4c3820f76c4c3820b48c1c0c7c68208e082106cc7c682045a820ea4c7c6820245820290c1c0c1c0c7c68204ce820cabc1c0c7c682036e8210a4c4c38210d3c4c3820d5bc7c6820e648210d9c1c0c4c38201f7c4c382077dc7c68207d5821137c7c68210788210e9c7c68201d8820df9c4c3820bb2c1c0c1c0c7c6820f05821079c4c3820f96c1c0c1c0c4c38208e8c4c3820ea4c4c3820878c4c3820d48c4c38206bbc7c6820783820e8bc7c6820672821068c7c68210bf821107c4c382043dc7c6820f56821051c4c3820b07c7c6820d7b820feac7c6820a47821186c4c3820f50c4c3820b6bc7c68202b0820dc4c4c3820859c4c382074ec4c3820b58c7c6820571820952c6c581ec82031ac4c3820976c4c382047bc7c682058e821087c4c3820e0cc4c382057fc4c3821058c7c6820cac821036c4c3820b8fc4c382026ec7c68202bd820eddc1c0c4c38203a3c1c0c4c3820f53c4c3820e4bc7c6820e57820ebcc4c3820c1fc7c68210ff821148c4c3820fa6c4c382016dc7c6820662821114c7c6820829820f79c7c68203c38209d7c4c3820f76c4c3820aa7c1c0c1c0c4c3820da3c7c6820480820b3fc7c6820bbf820f1ec7c6820ae4820b40c2c14cc7c6820f25821055c1c0c1c0c7c6820bc5821199c7c6820f5d8210fec1c0c4c3820184c7c682088f8208d6c7c68202d7820f6ec4c38205d9c4c3820a21c4c38202c5c7c6820a49821100c7c6820982821193c7c6820f97821021c4c3820368c4c3820e78c7c682031882114ec4c3820eabc4c3820be2c4c3820435c7c68203c682059cc7c68203bb820ef4c4c3821023c4c382069cc4c382111ec4c3820fffc7c6820601820a49c4c3820bafc1c0c1c0c3c281fec7c68208d582108cc7c6820f7a820fdfc4c382042ac4c3821122c1c0c4c38202f4c4c3820354c1c0c3c28196c4c382015ec4c3820dcbc4c3820944c1c0c4c3820c82c4c3820668c4c3820dbdc7c6820114820596c1c0c4c3820fc6c7c68205d48209f4c1c0c7c6820ab7820b66c7c682101982103bc4c3820169c1c0c4c3820c8ac6c581a9820d28c4c38203eec4c38209f1c4c38211ddc4c3820b1bc4c3820610c4c38209d7c7c6820c50820dd9c3c2819ac4c3820a83c1c0c4c3820e71c1c0c4c3820772c1c0c4c3820776c4c3820682c7c68205e7820e58c7c6820768820f0dc4c3820fb9c4c3820fbac4c38204cfc7c6820cc7820db5c7c6820a0d820bf6c7c682049c820fe7c7c6820dc3821016c4c3820f44c4c3820d80c4c3820bb2c4c38211b2c4c3821202c7c68203ef820a68c7c68204858208d2c4c3820fa8c4c38205ebc7c6820a4d820cfac7c68202548208e3c7c6820816820cefc4c38208bfc4c3820da6c4c38205c1c1c0c4c38210b5c1c0c7c6820d2f8210a0c7c68208cb8211f1c1c0c7c6820c37821091c4c3820daac1c0c4c3821126c2c156c1c0c4c3820918c7c6820d79821160c4c382056fc1c0c4c3820a3ac7c68209a68210afc4c3820b1cc7c6820e66820f99c4c3821140c4c3820ea6c4c3820ffec1c0c7c68209278211f2c4c38210ddc4c38211d2c4c3820ed3c4c38208ffc4c38202afc7c6820711820ea2c4c382040ac7c68202f68204fcc1c0c4c382047dc4c3820fb3c1c0c4c382065bc7c68207a9820b39c1c0c4c3820646c4c38207d1c4c38210fdc4c3821234c1c0c7c68202208204e9c4c3821090c7c6820eb78211ddc4c3820644c4c382116ec1c0c7c682032e820cd8c7c68204148211d9c4c3820d9ac7c6820ecf8211cac1c0c4c3820a62c7c6820e2e8210d6c4c38211a4c4c382072ac4c382070fc4c382071ec4c3820313c7c6820b3b820e6cc1c0c7c68207b182112fc7c6820f25820fd6c7c682095a820de1c7c68207c3820a6cc4c38203b4c4c3820281c3c28195c4c382101fc7c682097e8211b5c7c6820fac8210f8c4c3820e82c4c3820553c4c3820fc5c4c3820d29c1c0c4c3820a06c1c0c4c3820cb9c4c3820f2fc2c134c7c682085b820f12c7c68205ce820d1ec1c0c4c3821173c4c3820d7fc7c6820c50820db1c1c0c3c28184c4c38209b2c4c3820d73c4c3820c86c7c68203c08203e4c4c38210a7c7c682085c820f1dc4c382120ac7c6820b8f8211b7c4c3820ab0c4c3820a2bc7c6820c25820e11c7c68203788210b3c4c3820f9dc4c3820ff3c7c6820d0b820debc4c3820d10c4c38201dec1c0c4c3820fe5c1c0c1c0c4c3820ba7c4c3820e67c4c3820300c4c382125cc7c682016c820e69c4c3820c7cc1c0c7c682043782076bc4c38211efc4c3820dcec1c0c7c6820c1082117ac4c3820dccc7c6820ad2820de5c4c3820f0cc7c6820ae38211c4c7c6820f6c821147c7c68202d5820cf6c7c68201fe820509c4c3821157c7c682121682128ec4c3820adbc4c382128fc4c382110dc7c68206ca820beec4c38210ddc7c68210ca821221c4c3820f2ec4c3820e5cc4c3820f30c4c38201a4c7c6820d218210afc4c3820ed5c7c6820146820b65c4c3820769c4c38201f4c7c68201af8210adc7c68203ea82090bc1c0c1c0c4c382056cc1c0c7c6820233820e13c4c38210fac1c0c4c382077cc4c382078bc4c3820e48c1c0c7c68208d1820e84c1c0c7c6820829820e07c4c3820e1ac4c38209dec4c382107ac1c0c4c3820a46c7c68206a5821278c4c3820a28c4c3820ffbc4c3820f7fc4c3820afec7c6820fcb821204c4c3820745c4c382112bc4c382035bc4c38205b0c3c28182c4c382125ec7c682078d8211c8c1c0c1c0c4c3820966c4c3820fb1c7c6820ae6820c79c7c6820e818212b3c4c3820a0dc4c3820888c1c0c7c682021582047cc4c3820281c7c68202d9820bb4c7c682083c8209b9c7c6820358820643c4c3821241c4c3820629c4c38207afc7c682097182122bc4c3820db1c4c3820af9c4c382126fc4c3820c43c7c6820d5c821042c7c6820b46820e8ac5c4658205b6c4c3820d4bc7c6820bc38212bac4c3820d60c7c6820b818210fac1c0c7c6820280820837c7c682021482119ac4c382095dc7c68210f6821269c4c3820d95c4c3821246c1c0c4c3820d76c1c0c7c68208c8821211c4c3820e96c4c3820d46c4c38211bdc4c3820fdfc1c0c7c6820144820fc7c4c38203c2c1c0c4c3820ccfc4c382127bc4c38212c6c4c3820b3cc4c3820278c1c0c4c3820d7fc4c38210c0c1c0c4c38211dfc4c3820b67c4c38204ffc4c3820f56c4c3820e37c7c6820786820bf3c4c3821072c4c38211cfc7c6820fd9821285c7c6820b32820dd5c7c6820b25820cddc4c3821155c4c38209b5c7c68201b482043ac7c68208f6820b90c4c3820b5ec4c3820d20c4c3820be1c7c6820365820890c7c68208f7820f59c4c3820ef5c7c68202ee820df0c7c6820e36820ee7c4c3820ee8c4c38203e0c7c6820589820ff7c4c382016bc4c3820157c4c3820204c4c3820ac7c1c0c4c382027bc4c382097bc4c382014ec4c3820b73c4c38206c1c1c0c7c6820c3e820fa7c4c3820f27c7c6820319820ba5c4c3820e15c1c0c4c3820691c4c3820e2ec4c3820834c4c38206a8c4c3820a13c4c3820126c7c682070d82089fc1c0c1c0c4c38209e8c7c68203b98206abc2c104c4c3820f33c4c3820ceec4c3820fbfc4c3820ff2c7c68208dd820b42c7c6820226820830c7c682017d820eb6c4c38203d1c4c3820fb7c2c173c4c3820231c7c6820df6821088c7c68203018212bbc4c382027bc4c38209c6c4c3820a13c1c0c4c38205e1c4c3820e68c4c3821325c4c3820777c4c3820fbec4c3821234c1c0c4c3821088c4c3820a74c4c3820522c4c38212a8c4c38207b2c7c682097b82133dc4c3820750c4c3820a25c4c3820d57c7c682020d82043ec1c0c4c3820b18c4c3820bfec1c0c7c6820655820bc4c4c3820bcfc1c0c4c38201a8c1c0c4c38206d3c4c3820611c4c38206b2c4c3820911c4c3820b56c4c3820c01c7c6820f368212d2c1c0c7c6820aea821346c4c3821336c7c6820757821368c7c682049e820d52c4c38204b9c7c682032f820b17c4c38212aec4c3820a73c4c38209efc7c68207c0820b01c4c3820944c4c38204f0c4c3820248c4c3820199c4c38202acc4c38205b7c4c3820621c7c6820f7d8212eac7c6820897820be1c7c682015b820a38c7c6820798820e30c4c3820f74c7c682053b82060fc4c38202ecc4c38203b8c4c3820a3bc1c0c7c68209f98212d9c4c3820bdcc1c0c1c0c7c6820573820954c4c3820665c1c0c1c0c3c281eac4c3821230c1c0c4c3820e1cc7c6820822821044c4c382138fc4c3820b33c4c38209f7c4c3821344c4c38205efc2c17fc7c6820d148210bec4c38208f4c1c0c4c3820f0ec4c3820558c1c0c1c0c2c14bc1c0c7c68209fb820d42c4c382087ec4c382135ec5c43a8201c8c1c0c1c0c4c38205c5c1c0c1c0c4c3820459c4c3821326c7c68211348213adc2c168c7c68204238208c4c1c0c4c38209a4c4c382057bc1c0c4c38209f4c4c3820c28c3c2818ac7c6820244820251c7c68203d9820547c4c38213a1c1c0c4c3820d17c1c0c4c38209fcc7c6820826820eebc4c38206e8c4c3820b8cc4c3820558c1c0c7c6820d9782137dc4c38206c4c4c38213c1c1c0c1c0c1c0c4c3820880c4c382040ec5c41e8209c2c1c0c4c3820b88c4c38203f0c7c6820193820cdcc7c68208758212c0c7c682084b820943c7c6820d5182129ac4c382101ec1c0c4c3820d8ec1c0c4c3820c72c4c382107cc4c3820eb4c1c0c4c3820681c4c3820bb9c4c3821392c7c6820acb820ee0c4c3820993c7c68203e3821181c1c0c4c3820c07c1c0c4c3821047c7c6820900820e1ec4c382025ac4c38209c6c4c3820a1cc4c382063ac6c581f4820f35c5c4268201ddc1c0c7c68202f5820358c7c682065b820faec4c38213e5c4c3820360c4c38209fac7c682090f820c70c1c0c4c3820575c4c382046bc7c6820d35820e3bc4c38207f5c4c3820383c4c3820ad8c4c3820bf7c7c682102f8212fec1c0c4c38212a3c4c38201d1c7c6820d4f820e51c7c6820141820e07c4c38204f6c4c382079bc7c682084f820884c4c3820442c4c38204b0c4c3820aa9c6c581bd821403c4c3820e03c4c3820bcbc4c3820e01c4c382131cc1c0c4c3820bd9c7c6820e2c82134fc1c0c4c3820723c1c0c4c38208bec1c0c4c3820921c4c382137bc4c382099fc1c0c4c3820bbec4c38209e3c7c68210fb82138bc2c13ec4c3820c38c4c3820992c4c38207d0c4c382136dc4c3820c6dc4c3820463c7c68209e0821253c7c6820ad0820f47c4c3820843c7c6820877820b89c4c3820700c4c382127fc4c3820577c1c0c2c10ec1c0c4c3820a61c4c3820f2ac4c3821169c4c3820a8cc4c3821010c4c38204a5c4c38207bfc4c38204cdc4c38209e6c4c3820e34c4c3820e75c1c0c4c38201bfc4c38209ebc4c382120bc4c382040cc4c38213bfc7c68210968211a6c4c3820b12c1c0c4c382055bc1c0c7c6821084821190c4c3820162c4c3820630c7c682080a82087ac3c281a0c4c3820225c4c3820188c7c68201d5820bfec4c3821041c4c38203f5c4c3820b78c4c382092bc4c3820d03c4c3820cb6c4c38213c9c4c3820485c7c6820369821200c4c3820cd0c1c0c4c3821073c4c3820f4dc1c0c7c68208c7821338c4c382069ac7c6820fc8821351c7c68210978211cec7c6820bbc820ee2c3c281adc7c68202a3820687c7c682027282036bc1c0c7c68210c28211d7c4c3820cb9c4c38204e0c7c68203cb820f21c4c3820cc3c7c68202ed820b91c7c68203a9820a2ec1c0c7c6820324820c6cc4c3820d53c4c38211f3c4c38212a8c2c161c4c38211c0c7c682023e820f7cc1c0c1c0c7c682031a8207e3c7c682054b82082bc1c0c4c3820a9dc1c0c4c382121fc1c0c1c0c4c3820fdbc4c382141cc4c3820a72c1c0c4c3820be2c4c3820967c4c3820b24c4c382139cc4c382108dc4c3820545c7c68204a4820710c1c0c4c3821425c4c38211dcc4c3821278c1c0c7c682117c821294c4c382080fc7c68202fc8203b7c7c6820871821346c7c6820305820761c1c0c1c0c4c382098dc4c38206d1c1c0c3c281bbc4c38212f0c4c38213e1c4c38204d4c4c382068bc4c3820841c4c3820c39c5c4058210d7c6c581a18213bac4c3820331c4c3820713c4c38209dcc4c38213a9c7c682139082140ac1c0c4c3820a61c4c3821250c4c3820598c4c382035cc1c0c7c682095a821109c4c3820962c4c38212a9c4c38211e6c4c3820df7c7c6820a93821158c4c382139cc1c0c4c38213dbc4c3820660c1c0c4c3820703c7c6820653820960c7c6820f2082145cc4c3820aecc4c3820bb1c4c38205dcc4c38206c3c1c0c4c3820e0cc7c6820d5882110bc4c38208c3c4c38206b3c4c382138ac2c125c4c3821008c4c38203cac4c3820529c4c3820be4c4c3821353c7c68205b9821463c4c382130ac4c3820ac1c1c0c1c0c4c3820bfac7c6820fe682100dc7c6820579820a88c4c3820c66c1c0c7c68206ee821364c4c3820ab6c1c0c1c0c7c6820654821466c4c38201eec4c38210e1c4c3821359c4c38202c7c7c682073d8212c4c4c382021dc1c0c1c0c1c0c1c0c7c682053c8213b6c1c0c4c3820f85c7c682096482145cc7c6820e06820f78c1c0c7c682065482141ac7c68203df8213a5c1c0c4c3820af2c4c3821109c7c68206078208cac7c682081c820a41c3c281a8c4c38203a3c7c6820c718214d6c1c0c4c382133dc4c38209c8c4c38214f3c7c6820850820d34c4c382086fc7c682012c820c03c4c3820e26c4c3820ef6c7c6820f8f82123ac7c68204118209fac4c382141bc1c0c1c0c4c38213afc4c38207d0c4c38209d6c7c6820c0c820c5bc4c38214a7c7c6820797820c57c4c3820f70c4c38205ddc7c682106782115dc7c6820e86820f52c4c3820389c4c38202e1c7c682049a8214e5c7c682026b820f86c1c0c4c38205d6c7c682076e821290c4c3820f33c4c3820773c4c38214cac4c3820170c1c0c7c682011c82139ec7c682144582147fc4c3820d4cc4c3820aa2c4c3820c39c7c6821028821477c7c682024e8211ccc7c682041e8214c7c4c3820433c7c6820444821054c4c382119dc4c3820600c7c68207418211e2c1c0c1c0c7c682030d821442c4c3820740c4c3820ce0c4c3820789c1c0c4c38206f5c4c3821394c4c38214b9c4c38202a0c4c3820755c4c3820b16c7c682051e82126cc4c3820975c4c3820104c4c38213a7c4c3820525c1c0c1c0c4c38214b7c4c382018ec7c68204cd8209fdc1c0c4c382131dc7c68205318210d3c6c5818082124fc7c6820ed682103fc4c382138cc4c38201aec4c3821428c1c0c1c0c7c6820892820b2cc1c0c4c3821549c7c682072282145dc4c382138ac4c3821352c1c0c1c0c4c38206d1c1c0c7c682066f820eddc7c68208258212d3c4c3820c91c4c3821522c1c0c1c0c4c38213cec4c3820c4dc7c68207c9820addc7c682090f820b94c4c38208f6c7c68205e882107bc7c6820305820729c4c382034fc4c382147bc4c3820b50c1c0c1c0c1c0c7c6820a7e82130cc4c3820790c4c3821497c4c38203f7c4c3820d02c4c38205efc4c38212dac7c6820345820e9bc1c0c4c3820b6cc1c0c4c3820435c1c0c4c382097dc1c0c4c3821564c4c382136bc7c682076b820cb3c7c68207da8209acc4c38206cec7c6820725820c47c7c682012c820a85c4c3821483c7c68201f28208e8c4c3820569c3c281d4c7c682061b82155ec4c3820440c4c3820a8fc7c6820642820d0ec4c382144cc4c3820348c4c38204ccc4c3821194c7c6820463821547c7c68201e082062fc4c382015fc1c0c4c382131fc1c0c4c3820b52c4c3820a6ac4c3820353c4c3820c6fc7c6820486820bd4c7c682042d82097ac7c68205918210acc7c6820ebf820f2dc4c38213a2c4c38214d6c1c0c4c3820c60c4c382059ec4c3821326c4c382149fc4c38213ddc4c382119ac4c38209ddc4c3820df4c4c38214e9c7c6820493821104c4c3821350c4c3821089c4c382156dc1c0c7c6820772821387c7c6820608820c32c1c0c4c382102bc7c6820981820c42c4c38214f9c7c6820858821483c4c38208b6c7c68208d78213f3c4c3820d62c4c38201b8c4c3821505c4c3820ab1c4c38208c6c4c3821348c4c3820398c4c3821225c4c3820b82c7c6820cc98212fec4c3821145c4c38203b8c1c0c4c382048fc1c0c4c382148bc1c0c4c38214b6c1c0c1c0c7c6820e4f820f13c4c38204b7c7c6820271820c19c7c6820cde8214d0c4c3820289c4c3820417c7c6820d25821029c4c38203a2c4c382140ac1c0c4c38214d7c4c382108cc1c0c1c0c7c682120982143bc7c68209698213d0c4c3820c4fc4c3821323c4c3820739c4c38203abc7c6820585820db7c4c38210c8c4c382092fc7c68205a0820b47c2c114c4c3820174c4c3820efdc1c0c7c6820b09821024c4c38213adc4c382064bc7c68202938212c9c3c281d0c1c0c1c0c7c6820f5c82101cc1c0c4c3820beec7c6820444820b74c4c3820649c5c46d820688c7c682063082149ec7c6820c628213e4c4c3820d77c4c3820c46c1c0c4c3820c9ac4c3820a89c4c38210b7c1c0c1c0c7c68202ff8210abc7c68201e2820b41c4c3820119c1c0c4c38208cac7c6820a6d820e08c1c0c1c0c7c68214ba8215bec7c682054882156ec4c3820f47c7c68206a082158ec7c6820ae8820c95c4c3821445c7c6820b638214a3c4c38208aec4c3820ce6c1c0c7c6820ccc821292c1c0c3c28187c1c0c7c68209e58215cdc7c6820d7d821322c7c68201458207c0c4c38205e3c4c38201bbc1c0c7c68213ee82145bc4c3821546c4c3820420c1c0c1c0c7c6820f7982159dc4c382124cc1c0c4c38213e9c4c3820947c4c38215dac7c6820d9f820dd8c4c3820788c4c38202c3c1c0c4c3820735c4c3821538c4c382097fc4c3820b38c2c15dc4c38203a2c7c682099d82125fc4c3820274c4c38215d5c4c38202d1c4c3821281c7c6820bc0821274c4c3820416c1c0c1c0c4c3820ebac1c0c7c6821108821618c7c6820a74821558c7c68201b782092ec4c3820a58c7c6820576821353c4c382136ec7c68213d582140ec4c3820876c7c682122582137cc7c68210f98213a5c4c3820b4cc4c3820b0cc7c6820311820ca7c7c682059d820676c4c38208d1c7c68205a08214dac7c68201ed82137bc1c0c4c3820608c7c682079a820873c7c6820d30821214c1c0c7c682096c8215a0c7c6820c99820ca3c4c38209b7c4c3820a7bc4c382094ec7c682084c821196c1c0c4c3820c56c4c3820e0ec4c3820c5ec7c6820ed78212cdc4c38205fbc4c3821136c7c682042f820f08c1c0c4c3821561c7c6820505820adec4c38205c8c7c682010b820422c7c6821362821427c7c6820bad820e9ec4c3821399c4c38215eec4c3820523c4c3820166c4c3820743c4c3820f67c4c3820c2ac7c6820e46821467c7c6820ab98212a5c7c68201a782158cc7c6820314821060c7c682085c8210f5c4c3820165c4c382136bc4c382142cc1c0c4c3821666c4c3820b4dc4c3820d1dc4c38214e4c4c382151ac4c382141cc7c682076a8213a6c4c38214bdc4c38208e7c1c0c4c3821444c7c682043f82060ac7c6821071821119c5c4228213aec4c382117fc1c0c4c38211eac4c382154ec7c6820cc7820d55c7c6820b518214a9c4c38201a7c4c382147bc4c3821264c4c382157bc4c38215a8c4c3821548c7c68210ea82152ec7c6820bf482134ac7c68209d4820d4fc4c3820da8c1c0c7c68213568215c0c7c6820c53820db4c1c0c7c6820f01820f39c1c0c7c68205ab820bd6c7c6821014821045c7c6820aa5821047c7c68206f282085dc1c0c4c3820355c4c382063bc7c682055a8207e9c7c68207f98211d7c7c6820b27821564c7c682072e821191c4c3820579c4c3820a30c4c3820bdec4c3821562c3c28191c4c3821622c1c0c7c682093d820c7bc4c3821529c4c3820909c4c38214e3c1c0c4c38213e3c7c6820559821440c4c3820492c7c68204f5821531c4c3820e2dc4c3820899c7c68215b082168cc7c682078f821026c1c0c1c0c1c0c6c58186820672c7c6820bd78214c1c1c0c4c38210c0c1c0c1c0c4c3821433c4c3820700c4c3820cb4c4c382156fc4c3820c27c1c0c1c0c4c3821391c7c68213fd82148ec7c682015f8208f0c1c0c4c382100fc4c382120ec7c682145e821470c4c3820373c7c682018582110cc4c3820b54c4c38206cdc7c68213938215f0c7c682061f821370c1c0c4c3820f73c1c0c7c682140b8215b1c4c382017ec4c38203e5c4c3821610c4c3820e98c4c3820af4c4c3820b66c1c0c4c3821500c4c3821629c4c38208a4c4c3820a03c1c0c4c3820852c4c3820337c7c68201ca820f37c7c682092b821512c4c3820791c7c68207188213a1c1c0c7c6820224821638c4c3820d1fc4c3821239c7c6820daf820db7c1c0c1c0c1c0c4c3821084c4c3820308c7c6820af082149bc4c38216a2c4c38206b0c4c3820637c7c682022e8211a8c4c3820849c4c3820813c7c682126e82132ec4c382105bc4c38214fac4c3820943c7c68204d78215ecc1c0c4c3820a30c1c0c4c38216e7c7c68201848214d0c7c68206ab821651c4c38206c1c1c0c7c6820b7f820e04c4c38216d6c7c6820ba1821303c1c0c4c3821658c1c0c4c3820a48c4c3820761c4c38213f2c7c682041f820c9dc4c3820a53c7c68206a8820926c4c38213cfc4c382021cc7c68209b0820a8cc4c3821545c7c6820a5d820e95c7c68213fc821482c1c0c4c3820bbac1c0c7c682032282084fc4c38214b4c7c682028f820501c6c581f58207d9c7c68206e6821089c1c0c7c6820466821175c7c6820e90820f98c4c3821155c4c3820de2c4c3820a95c4c3820535c4c38214c6c7c68201358215c1c4c3820dc0c4c38214cac1c0c7c68213a4821663c4c38205b4c4c3820d5dc7c68205be82170bc4c38212aac1c0c1c0c1c0c7c68208d3820b4bc4c3820d24c4c382167ac1c0c7c68213f88216b7c4c3820257c4c38207afc1c0c4c3821613c1c0c1c0c4c382042ec7c6820cbc82169bc7c6820626821124c4c3821575c4c3820194c4c3820206c6c581a78206fbc2c12fc7c6820ef28213bbc3c281e1c7c68204f98214ecc4c3821647c4c3820961c7c6820209821180c6c5818282123cc4c3820cc8c7c68214ee821644c7c682021e821690c1c0c4c3820532c4c3821652c4c38214ebc7c682070b820af6c4c3821727c4c3820e27c4c3821175c7c682056382134fc4c38206b7c7c68201b2820216c4c382025bc7c68205ce820d36c4c38209cfc1c0c4c38216d1c1c0c4c382013bc4c382073ac7c6820ac58214b3c4c38206afc1c0c4c3820742c7c6820a2f821643c1c0c1c0c4c3821339c4c3820addc4c38203d2c4c38209f6c5c446820779c7c6820f8d82137ec2c10cc4c3820947c4c382048ec7c68204eb820f64c7c68203f5821598c7c6820bd58212e0c7c682090e821604c4c3820aacc1c0c4c38209e1c7c6820f7a8211dac4c3820613c7c682094982148ac4c3820e4dc4c382096fc1c0c4c382039cc1c0c4c382069fc7c6820306821318c4c38205ebc5c441821257c1c0c7c6820fa5821146c4c3820864c4c382161cc4c3820ccac4c3820c80c4c3820ec6c7c6820938821332c4c3821631c4c3820167c4c3820d17c7c682072a820c33c1c0c4c38205bec4c3821352c4c3820685c1c0c7c68213858214d8c4c3821110c7c6820493820519c1c0c4c38211ebc4c38214c3c7c6821499821702c4c38213bcc4c3820de9c7c682014d821153c4c3821437c4c38210dfc5c408820441c4c3821374c4c3820605c1c0c4c3820f4ec1c0c4c3820e21c4c382031cc1c0c4c3820b97c7c6820f9f821794c4c382151bc4c3820a57c6c581d88201c2c7c6820a3782159ac4c3821698c7c6821492821766c4c3820873c4c382150cc7c6820bf8821521c4c3820b2cc4c382137fc4c3820657c7c6820a5082101dc7c6820396820b32c4c382177ec4c3821661c4c3820342c7c6820ecd821577c4c3820cecc4c3820c64c7c6820806820fe2c4c38214ebc4c3820828c1c0c7c68213fe8216b0c1c0c4c3820fc9c4c382178ec4c3821595c4c382158fc4c3820a3bc4c3821082c7c68204f3821516c4c382094dc4c38209c9c1c0c4c38213d8c4c3821796c4c3820e7cc4c3821736c7c68202fb820fdbc4c3820fa8c4c38215e4c4c3820758c4c3820751c4c382135dc4c38210bbc7c6820328820a19c7c68206598215fdc4c382163fc4c3820545c4c3820c3ec4c3821222c3c28198c7c682016b820c20c7c682021c8216eec1c0c4c38203e8c4c3820364c7c68208c9821098c7c682024a82175cc4c3820d57c7c68214ac821709c4c38212e5c4c38216ffc4c382088bc7c68207318217c3c4c38215c0c4c3821417c4c382031ec4c3820e70c4c3820a1bc4c3821453c7c6820147820c0ac4c382050fc4c382164dc7c68204a7821316c7c68208e2821798c4c382153cc4c3820801c4c3821528c1c0c7c682105c82160fc7c68216e982179ac7c68209578217e8c4c3820ab2c4c38207c7c4c3820895c1c0c4c38212bcc7c6820a6682139bc4c3820fe0c4c38216f1c4c38213ffc7c6820c1c8215b2c1c0c1c0c7c6820ca9820d54c1c0c4c382170cc4c3820fb6c4c3820a52c4c3820bcec1c0c7c682078a8210b4c4c3821471c2c10fc1c0c4c38216e2c4c3820855c7c68211218216afc4c3821671c7c6820828821722c4c3820a44c4c3821378c1c0c1c0c1c0c1c0c4c3821186c4c382074dc7c68205b8821006c7c6821310821542c4c3820cdbc7c6820a4a820c1cc4c38203f6c7c6820212820996c7c6820316821578c4c38208c2c1c0c4c38213fac4c3821598c4c382178fc7c6820bc1821486c4c3820a2ec4c3820a37c1c0c1c0c4c38214d5c4c3820eb3c4c38206e5c4c3820432c7c6820a998213b1c1c0c1c0c7c68211ba821300c1c0c7c682046f8207cbc3c281b5c4c382140bc4c38203acc7c68207c28211fec4c38217a3c4c38202f7c2c15ac4c38213a8c4c38210d6c4c3820851c4c382118fc4c3820bd1c4c382116cc7c68210948211a9c4c3821244c1c0c1c0c4c38217d5c4c382182fc4c3820186c4c38208e4c4c38217f3c1c0c4c38215c7c4c3820eb5c7c6820f10821801c7c682024782088cc4c3820a97c7c682070782127cc4c3820b04c7c682093f821354c7c6820a258217dbc4c3820e11c7c6820759821420c4c38208f3c1c0c7c682074a820d27c4c38201ffc7c682139d821833c4c38201cdc4c3820d00c1c0c4c38216bfc4c382118ac4c38215f2c7c6820a14821777c4c3820383c4c3821701c7c6820aee820c40c1c0c4c3821622c4c38209c7c7c68201b7821832c4c38203a5c4c3821544c1c0c7c6820234821729c4c382164cc4c382165ac7c682077382159cc7c6821085821491c4c3821062c4c38207cec4c38213b7c4c38217ebc5c429821759c4c3821706c7c68207288213f8c7c68205ed82145fc4c38213b2c4c3820f84c7c682023b8211eac5c41c8201fdc4c3821681c4c382095cc4c38215fcc7c682013e821712c4c382012fc4c38217d0c4c3821361c4c3820887c4c3820af4c7c6820e178211f0c1c0c4c382183ec7c682098982183bc7c68202d8821380c4c3820ab3c4c3820362c7c682051f821885c7c682078c8208fbc7c682121a8217cdc7c6820a6582134bc7c6820b5e821273c7c68206b8821281c7c6820dfe820e50c4c38217acc4c3821674c4c382176ec7c6820fcd821665c7c68203f082144dc1c0c4c3821891c1c0c7c68216648217f0c4c382182ec4c38203edc4c3820b0ec1c0c1c0c4c38216b2c4c382146dc7c6820230821386c7c68213638216d5c4c3821562c7c682098d821892c1c0c7c6820ad6820c7ac7c6820f248211e1c7c682019a820fefc7c6820e28821123c4c38204e5c4c38206c7c4c3820934c4c382153dc7c6820b2a8217c1c4c38217bec4c3820a90c2c11ec7c68216ad8217d7c4c3820282c4c3821637c7c682150b8217e0c4c3820aa7c1c0c1c0c7c6820b80821854c7c68206b3821408c1c0c4c38204bbc7c68213c982182ac4c3820320c4c3820763c4c3821589c4c3820c3ac7c6820a18821849c4c3820415c7c682010e8217f9c4c38205d3c7c6820d22821608c4c38210e2c4c382023bc1c0c4c3821636c4c3820381c4c38214ccc4c382102ac4c38204c2c1c0c4c38208bec1c0c7c6820c5282182dc7c6820c428214e2c4c38204f5c4c382180ac7c6820b19821264c4c38204a6c4c382082ec4c3820f34c7c68209458218e5c7c68205518218b2c7c68208878213cbc7c682046d821807c4c38204bcc4c3820c65c4c38207e9c4c3821301c4c38210a6c7c682148c821878c4c38204f1c4c382142fc7c6820ed08210b1c4c3820151c4c3820424c7c682081b820c3bc4c38216a0c7c682152b821650c4c38216dcc1c0c7c682167d8216c9c7c6820792821575c4c382095cc4c3821413c1c0c1c0c4c38218bec4c3820655c7c68203ba820c62c7c68205ff821708c4c38216b6c4c38216c7c7c6820f2f8211dcc7c6820ba28215d4c4c3820c6cc4c3820343c4c3821563c4c3821634c7c6820649820bc6c4c3820b7dc7c68204558215bfc3c281fdc4c3820c41c7c68203048210bac4c3820785c7c682085b820ec4c5c447820c67c4c3821406c4c3820ad8c4c3820bd2c4c3820b5fc4c382152ac4c3820d8bc1c0c4c38202ffc4c3821595c4c38211d9c4c3821297c5c402820e94c7c6820c1a82169fc1c0c7c68218518218c2c7c682028a8209adc4c3820ba3c7c68203cd821366c4c38217afc7c68204af820c0fc7c6820d9b82112ec7c6820bec8215b0c4c3821809c4c382107bc4c382063fc4c382167ec4c382151fc7c6820a24820f0fc4c38217cac7c682012d821899c4c3820da9c4c38216acc4c38217e4c7c6820cd7820f35c4c38204f7c7c6820c9b820ef3c4c3820c3cc4c3820eecc4c382087cc4c3821357c4c3820762c7c682025a8214fcc4c382166cc4c38203e9c1c0c7c68209058218a9c4c3820c66c4c38218bec4c3821065c4c3820c31c7c682011482100ec4c3820756c4c3820915c1c0c4c382062bc4c3821603c4c38203dfc7c6821861821897c1c0c7c6820dbc8216bac7c68204ba820ec9c3c281cec4c382137ac4c3820bb6c1c0c4c3820e19c4c3821736c7c68210258211aec4c3821223c1c0c4c3820298c4c382188cc2c153c7c6821662821836c7c6820e44821673c4c3820724c4c3821806c6c581be8215cfc4c38216aac4c3820863c4c3820f61c4c3821111c4c3820f19c7c682037c820d4dc4c382154fc4c3821452c7c682036c8216edc4c38217a1c7c6820bfb821923c1c0c4c38204c9c4c382151ac7c6820529820c32c7c6820d7b821586c4c382121cc7c6820866821971c1c0c4c3821530c4c3820e7dc4c3821863c4c3820201c3c28192c4c38210a1c7c68206f18211ccc4c38217a0c4c38201b3c4c3821168c4c3821319c4c3821864c7c6820770821658c4c3820749c4c38210b0c7c6820a078217fac4c382053bc7c68203918214c8c4c38216c2c7c68213b48215bac4c3821989c1c0c4c3820ce7c7c68202bc820c5ac4c38209ffc7c6820d6b820f3fc7c682094b821450c7c68206698212adc7c68213ef8217a2c4c3821105c4c3821345c4c3821557c4c38211e3c4c382135dc4c3821904c7c682077a820b31c4c3820dabc7c68213bd821546c1c0c4c3820677c4c3821740c1c0c4c382192dc4c3820b4ec7c68202f0820e5ec4c382165dc7c68202de8217b9c4c3820a1cc4c3821371c4c3820b91c4c3820f98c7c68212be821635c7c682021e82196ac7c682083a8215e5c4c38216c2c7c68213a78213cdc7c68216b98218dcc4c3821008c7c682021782185dc4c38205b3c1c0c4c382146cc7c68215db82191cc4c38213bdc7c6821265821824c7c682142c821614c7c68208ac82143cc4c382188fc7c682091d82192cc4c3820699c7c68208fc82198ac4c3820997c4c3820ca7c4c3820b9bc4c382159ec7c68202ce82083ec4c382185bc7c682056b820c55c4c38202edc7c682071182197dc1c0c4c38210c9c1c0c7c68216c9821713c1c0c4c3821775c7c6820bff821597c7c68215968218e5c2c15cc4c3820860c4c3820cd9c7c6820c0c821950c4c38205e0c4c3821340c4c3820fd6c7c6820c7782134ec7c6820a92821158c4c3820c97c4c382148dc4c38218abc4c3820cadc4c38213d3c7c6820951820f07c4c3821233c1c0c7c6820ae7820d85c7c68206f4820f6bc4c38216c3c7c68208dc820d29c1c0c4c382025cc3c281f8c7c68215478218f4c7c68214188218d5c4c382181ac1c0c4c382197cc7c68202fa8218fbc4c38218a4c7c6820cf58214afc7c68206d4820a16c7c6820b7e820e91c7c682161682174cc4c3820dd2c7c68215da8217b5c4c38203bec4c382075ac4c382079dc7c68215f58217f2c4c3821625c7c68218ac821908c7c682031f8219c3c7c68215738217c2c4c3820d8bc4c3820476c7c68201008204bec1c0c4c38213b4c7c68213ab82144ec7c68213448216cdc4c3820482c4c3821434c4c3820164c7c68214e6821832c7c682103d8215ffc7c6820c2b8216f2c7c682032f82139ac4c38218fac4c3820541c4c3820269c4c3821879c7c6820b6f8215adc7c6820b38820c70c7c68202eb8207fac2c16bc4c382143ac7c68216548217dec4c38219d7c7c68203c4820fb4c7c682050c82057ec7c6820e6282193dc4c382174dc7c682027282170ec7c68213ae8216b8c4c38218e2c1c0c5c46282024dc1c0c4c3821691c7c68201b2820399c4c382153fc7c682048382147dc4c3821549c4c3821307c4c3820670c7c682143f82199dc7c68217ae8219e2c7c682174a821755c4c3821385c7c682085a821416c7c682012a820cd1c1c0c7c68213f98218f6c4c3821161c4c38202f7c7c6820d9882153ec4c382142ec4c3820877c7c68204c6821196c7c68203fa821330c4c38216a1c7c6820507821894c7c682163b8217cbc4c38211e3c4c38215bac7c682035e821898c7c68204a98219f1c1c0c7c682088c8217bdc4c38202bec7c68212fb82193dc4c3820522c4c38205e6c4c382148bc4c38206cfc7c682031e821722c7c6820b8c821880c7c68209ca820d24c4c3820ccbc7c6820efc8211a5c4c382134dc7c68210808212cbc4c3820145c1c0c4c38215c2c7c68205d38214e0c4c3821185c7c68209d9821a1ec4c3820fd7c7c68202db821833c4c3821480c7c68207d7821481c4c3820adcc7c682117e821930c5c468821381c4c3821821c4c38214cfc4c38214f1c4c3821173c4c3820b6dc7c6820ac8821341c7c6820b928212edc7c6820c1e820ce1c1c0c4c3820b11c7c682134c8214d9c7c6821936821a14c4c3820c45c4c382139bc4c38211fac7c682019582041cc7c6820d0382164fc4c3821299c4c382169ec7c682049d820808c4c3820f00c7c68204698214efc4c38212a6c4c38215f5c7c6820c6e8219fbc7c6820b0082190ac7c6820e49820e4ac4c38208c7c4c3820ea7c4c3821982c7c682173b821933c7c682089d821484c4c38217cfc4c3821929c7c682050e821394c7c6820c83821a1cc7c6820c1d82193fc4c38215a6c7c682087e821887c7c6820983821776c1c0c7c682168b821958c7c682025f821459c4c3821963c4c382104bc7c6821208821432c4c3820d0fc4c382124ec4c3820258c4c38207f9c7c68208f2821251c7c68201a5820aa6c4c38216b1c7c6820242820d1fc4c3820dfac7c6820372820732c1c0c4c3820921c1c0c4c38218fcc7c68203b38209f3c7c6820d4782177bc4c3821830c1c0c4c38212a0c4c3820a73c4c38203c2c4c3820e87c7c682117d82153ec4c38216e0c7c6820bf982163dc4c3820569c7c682183a821a52c5c413821024c7c682139d82191ec4c3821a5dc4c3821621c4c3821611c7c68216a98216e4c4c38201f1c4c382198cc7c68219d78219e4c7c6820e8e821294c7c68219a5821a60c3c281e6c2c15fc4c3820b4fc4c3821700c4c3821a16c4c3821133c7c68202bc821868c4c3821064c7c68206c582070ac4c3820b7bc4c3820d56c7c682097c820ebdc4c382155dc7c682176d8217d6c4c3820a16c1c0c7c6820e5b8215d7c4c3820407c7c682063e820fc7c1c0c7c68206fb8207abc4c3820132c7c682067d8216fbc4c3820898c4c3821a57c4c3821525c4c38218e7c7c6820bd882160bc4c38216dac4c3821a7bc4c38206ddc4c382113ec7c682124282126ac4c3821624c4c3820359c1c0c1c0c4c3821665c4c38213a8c4c38213cdc4c38204eac4c38219b2c4c382186fc4c3821a57c4c3820e10c7c68205da821918c7c68212458215dec7c68212ee82174dc4c38209a3c4c3821458c7c682103d821ac3c4c38202eac7c682116e821455c4c3821368c4c3821633c7c6820c19821895c4c382064ac4c3821350c4c38219f7c4c38219d1c3c281b6c7c682144f8218e6c7c682116b8212dbc4c3820206c4c38216f5c4c38219eec7c68201508216c4c7c6820f228215c6c7c68202ae820533c5c4148219b9c4c3820deec7c68210cc8215f7c4c38219ffc4c382101bc5c44f8205aec7c682164c821a88c7c682059b820811c4c382100bc7c6820da2821ae9c4c3820312c7c6820c5282158cc4c38211f6c1c0c4c3821685c4c382107ec4c3821a9cc7c68202d08206bbc4c3820eafc4c3821a61c4c38212d2c7c6820a98820ed0c1c0c4c3820b6dc7c68216c5821805c7c6820a7f82142dc4c3821411c4c3821973c7c6821116821a50c4c38212f2c1c0c4c38215a4c4c3821343c7c682107a82171ec2c110c4c3820dcec7c6820ce2821452c7c6820ba88215afc4c3820f83c1c0c7c68208c582101ac7c682044e82064ec4c38209fbc4c38204afc4c38204d9c7c6820862820d8ac4c382118bc7c68205ba821393c4c382047dc4c382083dc7c6820da48212d0c7c6820be3820c85c7c6820406820880c4c38214e2c7c68201c9821115c7c68210cb821217c7c6820c938210f0c7c6820893820d0ec7c682107782129ec7c682102e821a47c1c0c4c3820734c4c3820c20c4c3821a19c4c3820adfc4c38210bbc4c3820243c1c0c4c3820698c7c682030a821759c7c6821953821adfc7c68216c6821b30c7c6820b238211fbc4c3821688c7c682093c821a1ac1c0c7c68217b8821895c4c3820efac7c6820c8a8218b3c5c477820a0bc4c3820318c4c3820601c1c0c2c140c7c682181f82194ec4c38203cec7c68202e9820cfcc7c6820173820a0ec7c68209a5820cbdc7c6820c88820ed6c5c458820572c4c38212d4c4c38217fcc7c68201d7820c7ec4c3820839c7c682042d821845c7c68218f182197ec4c38215a2c4c3821444c7c682093f820d99c4c38215f8c4c38211d6c4c3820accc4c38211cdc7c6820b1b820ef9c7c6820838821710c7c68201e0821811c7c682109e8212e7c7c68205c5820985c7c68214e58216f7c7c68206db821457c7c68214778214adc7c68214f98219e8c5c4528218e8c4c3821844c4c3821606c4c382161cc7c6820480820698c7c6820d4c8218c8c4c38210f7c1c0c4c3821686c7c68202fb8216bbc7c682051682186cc7c6820fe3821a1dc7c6820f3e8219f2c7c68210218214ddc1c0c7c6821498821941c7c6820a5c820cc6c7c6821a02821b2dc4c382115ec1c0c4c3821541c1c0c4c38217a6c7c68201b9820d3ac7c68202ab8219c6c7c68209e28210d0c7c682168b821a35c4c3821007c7c682111c82183dc4c38217c4c7c682194c821a7ec7c6820acc821794c4c3821773c7c6820bf382161dc4c3820bcbc4c38204f2c1c0c4c382177dc1c0c4c382189ec4c3820224c4c3821250c7c682061c820e75c1c0c7c682044f82159ac7c6820eb9821b72c4c3821261c7c68213b08219dfc1c0c7c68208e4821a43c7c68210428215f9c4c3820d39c6c581dd820cffc4c3820a8bc1c0c7c68201bc820b8ec1c0c4c3820666c7c6820a91820b9dc4c3821a4cc1c0c7c68201a082041dc7c682078f820a55c4c38210b9c7c6821304821539c7c6820a1f8216ccc4c38217a2c1c0c1c0c1c0c7c682105b8215c2c7c68201c582143ec4c38204ddc7c682016a82172cc4c38205aec7c6820ab58211abc2c148c4c3821a36c4c3820d48c7c682112a82187bc7c6820fc9821ab3c7c682064882143cc4c38212cbc7c6820e2d821666c1c0c4c3821116c7c682013c820160c4c3821863c4c3821357c7c6820678821295c7c6821a80821af9c7c68207c48217d8c7c682024f82048bc7c68211888214aec1c0c4c3820c6ec4c382102cc4c38217ecc4c3821a61c4c382064cc7c68208e7820aa8c7c68211c1821b18c4c3820fbcc4c3820cf8c4c38218edc3c281dbc4c3820e38c7c682081182113cc7c68212198218a0c4c3820fedc7c682104182171bc7c68209f082107ec7c68205ee8210ebc4c382045ec4c38216e9c5c455820a99c7c68205c6820b44c3c281b5c4c3821470c7c6821339821670c7c6820710820dc7c7c6820a7a8211fdc4c38202adc1c0c7c682143d8215d3c7c68209a9820d2fc4c3820b72c4c382145dc7c68201138210c7c4c38211aac7c6820a7c8215dcc7c6820f9a821594c1c0c7c68203b0820e6bc4c3821b29c7c6820e92821803c5c42c8219d4c4c382110cc4c38208d8c4c3821ae8c7c682030c820f82c4c38212b2c4c38202bfc4c3820af3c4c3820cbec4c3821b85c7c682090d820a07c1c0c7c68210a782117cc7c6820d5d821248c1c0c4c38216fac1c0c4c38217e6c4c3821865c4c3820fb5c4c38209c3c7c68217bf82184ec1c0c4c3820d4dc7c682044382170ec4c3821720c4c3820abcc4c38215f4c7c6820d7082156bc4c3821a6ac7c682113582179ec4c3820651c4c382014cc7c6820c61821bcac1c0c4c3820936c7c682132b82195bc7c6820e9d821ba1c7c68204a582191cc7c6820c698216d8c7c6820faf8210c3c7c6820b10820ee1c7c682182f8218aac4c3820dffc4c3820b36c4c38212a1c4c38218fdc3c281d8c4c3821bd1c4c3820b93c4c382011bc7c682079b821474c4c3820284c1c0c7c68215b5821b3fc4c3821499c4c3821a7cc4c38218aec4c38206f0c2c109c4c38214cdc4c382052dc7c6820778821402c4c3821b78c7c682112d821449c7c6821a1b821ba7c4c3820c58c4c382110bc1c0c4c38214f3c4c38206e9c4c38217d1c7c68210e08218ffc4c3821976c6c581a7821644c4c382075dc7c682080c821ae7c7c68217e38217f4c7c68203c9821581c4c382145ac7c6820fae821b0ac7c6820495821ae5c7c682045f820e2fc7c6820d0282102dc7c6821939821b4dc4c38208ecc7c6820ecb820fb9c4c3821617c4c3821232c7c6820cd682133cc4c3821753c4c3821220c4c38216cdc4c38219f6c4c3820335c4c382147ac4c382199ec4c382066ec7c68215ab82187dc7c6820752820816c7c6820133821c28c7c682154a821b61c7c6821835821bc0c4c38202f2c4c382178dc4c3820500c1c0c4c3820c5cc7c682055f82113bc7c682061b820b11c4c382180dc4c3820979c4c3820b29c7c6820ba4821502c7c6820996821336c4c382131ec7c682066682153bc4c38203c9c7c68209be8216b4c7c682062e821271c4c3821585c4c3820a31c7c682069982076cc4c3820d75c7c6820fab82104ac4c38215d1c7c682164b821657c7c6820692821ab8c4c3820c91c7c682045f821314c7c6820262821135c4c3820c5ec4c3821aaec7c6820b7d821814c7c6820a328211b7c4c3821756c4c3820732c4c38202fcc7c6820bc882163ec7c68210db821baec7c68205f682118dc7c6820ab98210edc4c3820f42c4c3821574c4c3820fdcc4c3820a46c7c682098c820ba9c4c3821987c4c3821258c4c3820526c4c382052fc4c3820800c6c581f28218c4c1c0c1c0c7c6820a4882199ac4c3821ab5c4c38206a0c7c6820979821925c7c682081a820ec7c4c38212aec4c3821427c7c68205ad8211f4c7c68214d2821ac6c4c3821990c4c38215d8c4c3821981c7c68215e1821ae5c4c3821bc1c4c3821c2dc7c68204f0820dd5c4c3820f4ac7c6821715821be2c4c3821657c4c3821062c1c0c7c682011a820e0ac4c3821748c4c3820586c7c68202a2820916c4c3821c0bc4c3821317c4c3820323c4c3821abcc4c3821c34c4c3820837c7c68206b4820c81c7c6820ea082171dc7c68201c1821461c4c38212d8c7c6820222820c96c1c0c7c68206f7820e53c4c3820d65c4c3820d92c4c38216f4c4c3821b93c4c3821376c7c6820f15821786c7c6820233820a34c7c6820970821966c7c682023f821772c7c6820c618218a2c7c68215f1821a4fc7c6820b2f821aa8c7c682128c821b4ec4c38203dac1c0c4c38213dac7c6820e6f8219dec4c38201fac4c3820fffc4c3821310c4c3821952c7c6820fec82166ec4c38202b0c4c38219bcc4c38214efc7c6820a79821583c4c382173cc4c3821875c7c68205748218a8c7c682010e8216aac7c6820658820e19c7c6821489821ca1c4c38211d3c7c682183c8219f4c7c6820a7682180fc4c3821251c7c68202da821594c5c4768205a4c1c0c4c3820dc3c4c38207d6c7c682121a821c12c7c682091f820e2ac4c3820604c7c68204d6821667c1c0c4c38215e8c7c6820903821bfdc7c68212de8217bec1c0c7c6820cc1821bb4c7c682114f821448c7c68202ad8212b5c4c3821735c4c38217d1c7c6821574821659c7c6820c51821b6ec4c3821c85c4c38211d2c7c6820c72821c1dc4c3821220c7c68201a28214e1c4c382197ec4c3820b5fc7c68202dd821676c7c6820e478216f6c4c3820b51c7c6820b4c820d7cc1c0c7c68210f18215e2c4c3821cd4c1c0c7c68209cb8215a1c4c3821b77c4c3821b3ac4c3820cc5c7c68209ba820e23c4c3821bf4c7c6820c63821829c7c6821790821826c7c6820189820831c4c3820dfdc4c38219dcc4c382066cc7c68210a9821821c7c6820e8f821bfcc1c0c7c68214ac8216e8c5c4188216c8c4c3821716c4c3820929c4c382105ec4c3821b9ec4c3821b3ec7c6820d73821bb0c7c6820be8821c03c7c6821891821a1dc4c38219f5c7c6820c78821587c4c3820ba0c7c6820c688213b0c7c68208b5820c82c4c3820ab7c7c682142a821951c7c682033b82181dc7c6820c768214f8c1c0c7c6820b0782185fc7c6820baa820d68c4c3821975c4c3820e9dc7c6820b3d821944c7c6820900820c31c4c3820a33c4c38213dbc7c6820640821cc7c1c0c7c682172f821788c7c6820643821b85c7c682174f821a9dc4c3821906c4c3820127c7c6820e83821adcc7c6821a71821cc3c7c68203b6821739c7c682078a821142c4c38209a1c7c6820d59821c28c7c682014082092ac1c0c7c682168e821af1c4c38218e9c1c0c6c581cb821a0bc7c6820528821c63c7c6820894821c6fc7c6820b43821105c4c3821293c4c3820853c4c3821438c4c3821b27c7c6820ee0821b48c7c68206ff820ddec7c6820f628210c9c7c682010d820582c4c3821cfec7c68204ee820ec8c1c0c3c281c5c7c682121382153ac4c38212e0c7c68201fc821bcdc4c3820b60c1c0c4c38214b1c4c3820df1c7c682113f821320c7c68201bf820c23c7c68204ef820cf9c7c6821bee821c09c7c68219c5821a5ac7c682120c821999c4c3821187c7c6820f1f8211aac7c6820b5a821996c7c6820dbc8210f3c7c6820fee82125dc4c3821b76c2c142c7c682088b820b49c7c68219bb8219f8c7c68205f6820901c7c682030e82178dc7c68211c7821a11c7c6820e02821cadc1c0c4c3821a79c4c3820d81c7c6820caf821c75c7c68201fa8203ecc1c0c4c38216fbc7c6820c60821c62c4c382074dc1c0c7c6821208821cf4c4c38207a5c7c68216fc8219c7c4c382062bc1c0c7c682152d821ccbc4c3821d36c7c682104d821496c4c3820675c4c3821a50c4c38212b0c4c382140cc1c0c4c38218cec4c3820e8ec4c3820ddcc7c68217ea821b37c7c6820701820d1ac7c682062d8206b2c4c38215b9c4c3820bb7c7c6820ad7821662c4c38203dcc7c6820b35820d8cc7c68203248213fec4c3820f3dc4c3821843c7c6820820820ea1c4c38217adc4c3821d78c4c382189bc4c3821ad7c4c3821aaac7c6820d40821127c4c3821749c7c68214f882174ac4c3820fc1c7c6820f87821b1ec7c68209a28219efc1c0c4c382155fc4c382123dc7c6820d528212cac4c38216efc7c68207a38210c8c4c3820c9ec5c403820f05c7c682104c821b24c4c3821c09c7c68207ac82179ac4c382130ec6c5818d820d6bc7c6820bec8219cbc4c38208a3c4c3820f94c7c6820484820e0bc4c3820d4ec7c6820a5f820e35c4c3821404c7c6820550821570c4c3821d3dc4c3821582c7c682130d821369c4c3820b4bc7c6820f178219fac7c6820eb4821262c4c38214c4c4c3821057c4c382123bc7c68209578218b7c7c6820207820dc1c4c382151fc4c382178ac4c38208c2c4c3820907c4c38216bdc4c38202bec7c6820154821273c7c6820c748215e4c6c581d3821070c1c0c4c38212b6c7c6820f43821189c4c3820b06c4c3821bbbc7c6821533821c37c4c3820c76c4c3821227c4c3821d20c4c3821390c7c6820a6e821b86c4c38212a4c4c382024ac7c68219a0821af4c4c3821ad7c7c6820171821d95c4c38215ebc7c6820fe1821689c7c68210b38212c8c7c68219f3821c8dc7c6820ffa821779c7c68210bc8210c7c4c3821347c7c6820f5182110ec4c3820a64c4c382122ac4c38204c1c4c3821377c4c3821d29c7c6820a7b820c63c7c6820d3b821182c4c3821524c7c6820e12821485c7c6820374820e88c4c3821c26c7c68205aa820f75c4c38210c1c4c3821a9ac7c682053a820e23c7c6821771821bf8c7c68209e6821b9cc4c3821782c4c3821d2cc7c682018582036dc4c3821aecc7c6820f1b8214a5c4c3820f78c7c682195a821ddcc7c6820ee8821426c4c382124dc7c6820b28821c33c1c0c6c581df821cabc4c382063cc7c68209568216bec7c6820b60821bf9c4c3820535c7c6820a1a82124dc7c682079f821831c7c6820cd182122cc4c3820c2cc4c3821928c4c3820d11c7c682034d821b75c7c6820dfe8210e7c7c6820a0982178bc4c3821b7ec4c3821329c7c682085282132dc4c38218dac1c0c7c6820af082193fc4c3821893c4c382198bc4c382050ac7c6820de2820f65c4c3820b37c4c382166ec4c38212bbc7c68216d3821a3dc4c3821461c7c6821af6821b3cc7c6821bf8821c4dc4c382165bc7c6821004821b95c7c682161382165ac4c3820e42c4c38217e1c7c682030b821bfcc4c38217d4c4c3820a26c7c6820fe9821d53c7c6821c48821c55c4c38214b3c4c38219b0c4c3821c1fc5c43d8211e5c7c68214e6821c84c1c0c7c6821590821deec7c6820c908215f6c2c16cc7c68215ec821970c4c3821602c1c0c7c6821909821dc2c7c68219b3821b3dc7c682069e821703c7c6821922821ad0c4c38210d7c7c6820a658219eac7c682028c8216cfc7c682122f821d5dc7c6820c75821501c7c6821815821938c4c38204f4c4c38203efc7c68210e6821340c7c6820da582192ac7c6820e538219f2c7c6820fb2821128c7c6820c6b821950c7c68210a58214dfc7c682048782108bc7c682165e821b55c4c38212b4c7c682184f821e16c4c3821bb3c7c682100b821ddec4c3821d12c2c119c7c682104f821b16c7c682022b8214b4c4c3820434c7c6820a968213c6c7c6820e9f8215dbc7c6820dbb821a98c4c3821101c7c68219a88219c5c7c6820708821a0cc7c682010382076fc4c38201ddc7c68209138214a0c7c682044e8211f8c7c6820ba5820ef7c4c3821730c7c6821867821dfac7c682162e821d32c7c682131482187cc1c0c7c68209e5821cd1c1c0c1c0c5c449821c7cc7c6820f69821babc4c3821311c4c3821487c7c6820717820d2dc4c382198bc4c3820478c7c68215ac821c92c4c3821db5c7c6820268820d43c7c6820db68211a9c7c682101d821252c7c6820fbd821c3dc7c68204c2821bb5c4c3821655c4c3821ba0c7c6820e298212b2c4c382131ac7c6820a63820d1bc4c38218fbc4c3821519c1c0c4c382027ac7c6820a15820d2dc7c6820726820b10c7c68204d1821c8ac4c382197cc7c6821307821d5fc1c0c7c68219f6821bdac4c3821a24c7c6820e15821d6ec7c6820c73821502c7c68203d3821903c4c3821e15c4c38216f7c4c38217e9c7c6820a69821d05c4c3821847c4c3821ae2c7c6820b858218e4c7c68212fd821c0cc4c382157fc7c682092c820aa2c4c38215a7c7c6820667821093c4c382125ec4c3820567c4c3821c6bc7c6821633821d11c7c6821684821d27c7c68213cf82164ac4c3821259c4c38210ecc7c68207de8217d6c4c382153ac4c382104ac4c3820aaac4c382191ec7c68202b5821da6c4c38219f7c4c3820d82c4c3820d88c7c6820455820ad9c4c3820c74c4c38210f5c7c6820c5b82188ec4c3821afbc4c3820c01c7c6820a2d821ba6c4c382178ec7c6820546821509c4c38213c3c7c6820fde821b57c4c3821433c7c68215e5821abfc7c6820321821d12c4c3820750c7c68207c98209d2c7c6820bf082171bc7c68207ac821c5fc7c68207958211edc7c6821b43821e0ec4c3821382c4c3821b5cc7c6820e5a82162ac4c3821de1c7c68217fa821915c1c0c7c682197d821dc0c7c68204038216dbc7c6820d26821ea0c7c68218ec821b70c4c38218e8c4c3821d2bc4c3821c69c7c682169a821e8dc7c6821180821d5bc7c6821179821b00c4c3821715c4c38213a2c7c68201a68201a9c7c682065c820719c4c3821843c4c3821907c1c0c4c3821e1dc7c682021b821de2c1c0c4c3821e33c4c3820f2cc7c6820b8a821816c4c3821a3ac7c6820c75821421c4c382121bc7c6821a34821a83c4c3821d90c4c38210c4c7c682091882188bc7c68201f782122ec4c3820c6bc4c38213e7c7c68207f38213e6c7c6820b70820c6dc4c3820512c7c68201aa820d6fc7c68213f9821eb1c7c6820e5f82158ac4c3821d5ac4c3820c7fc7c682162482177ec4c382199bc7c682067a821861c4c382196fc4c3820d16c4c3820e4ac4c3820d37c6c581db8216f9c4c38203b4c4c382184ec4c3820cd3c4c382103fc7c6820405821813c4c3820171c4c38218d5c4c3821912c7c68218458219fdc4c3820a72c1c0c4c3820d2cc7c6820279820f0bc4c3821c40c7c682069b820b79c7c6820d808210d9c7c682084982191ac7c68213738214c0c7c68216218217edc7c682156f821c58c4c3821149c7c6820cd2821288c4c3820bb5c4c382166fc4c38217b5c4c3821c40c4c3820950c7c68215848217fbc4c3821920c4c3821086c7c6820fac821b58c7c68204528209d0c4c3820a42c4c38215d4c4c3821214c1c0c7c68218df821ba2c7c6820ddf821a21c4c382181ec4c3820627c7c682088e821d34c4c382136fc7c68205408214d1c4c3820d87c4c3821e65c4c38218e3c4c382081bc7c6821535821bd0c4c3820d7dc7c682093a820bcdc4c3821bd6c7c682038a821be5c7c6821949821a1bc4c3820d65c7c6821b2e821b8ec7c6821b0f821f0dc7c6820211820c54c4c382109ac7c68208de8211bec4c3820ee9c7c68209cc821e45c4c38214c3c7c6821921821b63c5c449821219c4c3821ef2c7c68201068203c8c7c68209a4821abac4c3821c19c1c0c4c38213dec7c682140e821c8bc7c6820498820844c7c68209ee8210b8c4c38219ccc4c3821acec4c38201a0c4c3821513c4c3820f9cc4c3821c5ec7c6821b34821e22c7c68212ff821a9bc7c6820bac821028c6c581a48212e8c4c38207a9c4c3821e71c4c3821553c4c3821db2c7c6820ad382149ac7c6820f9382112ec4c3821d65c4c3821ea3c4c3821ea5c4c3821aacc4c3821776c7c6820b3e821b8dc7c6820abf82167dc4c3821c45c4c382121dc7c682132f821b97c3c281c7c7c682090e8216efc4c3821747c4c38215c3c7c6820c6f821770c7c6821959821ef7c7c68214b782166ac4c3821288c3c28192c4c38212b8c4c3820a98c7c682074e821249c4c3820a12c7c68201e7821923c4c382184dc4c3820682c7c68213da821cdec4c3820f36c7c68207e6821054c4c3821f25c4c3820e55c4c3821c82c7c682130a821c29c7c6820bca8215d3c4c3821d02c7c68209a7821cc7c7c6820af582175cc4c38211afc7c6820f6382172dc7c68201fb8214f4c4c3821dcdc7c682169b821e1bc4c38214bcc7c6821419821741c4c3821678c1c0c4c3821bbfc7c6821030821d5ac7c6820366821b83c4c3820602c7c6820496820abcc4c3821365c7c682022b821504c7c6820247821ee0c1c0c4c38213f7c4c382172ec4c3820a86c4c38206d5c4c38218f8c7c6820465820635c4c3820f18c7c68214d3821cb7c7c68205b5821f66c4c38219d6c7c68218ab821c5cc7c6821183821b92c1c0c7c68202b48218f9c4c3821a01c7c682029d820e02c5c427821cc0c4c3821f47c7c68204bb821774c7c68205ad821f2fc1c0c4c3820423c7c6820930820dfdc4c3821b3bc4c38208ddc4c38214f2c4c38213c5c4c3821112c7c6821184821cacc4c38205d9c4c38211adc4c3821c2ec4c38212fac7c68201d2820a9cc7c6820695821e2fc4c38209c4c5c41b821e12c7c6820684821679c7c6821c7f821cc9c7c68201c7821362c4c3821165c7c68209c782109cc7c6820b22820ff8c4c3821da4c4c38218d8c7c6820cc08214b2c4c3821983c4c3820df5c4c38212e2c7c68212cd821370c4c3821d31c4c38207ebc7c6821239821a85c4c3820146c4c3821876c7c682059782123fc7c6821571821ccac4c38207dbc7c6821199821889c4c38210a8c4c3820e84c7c6820e2a82163cc4c382146cc4c3820388c1c0c7c6820721821e5cc7c6821478821a26c4c3821cecc4c382114bc7c6820bab821aebc4c3821243c7c682132f821a87c7c6820a0582157ec7c6821397821619c7c6821429821e21c4c3821f79c4c3820f32c7c6820e8c821d44c7c6820266820950c7c68204e88213dec4c3820c23c4c3821a0ac7c6820f5d821002c7c682020d821a70c7c68214c2821b0ec7c6820ccb820f86c7c682151e8217bac7c682116f8212c7c4c38219d0c7c68208bf821095c4c382126fc1c0c4c38219b7c7c6821906821a07c4c38206a1c4c3821623c4c3820117c7c6820217820588c4c382133cc4c3821e23c4c38206bdc7c6820665820c5dc7c682082d821ec5c7c68217a782185dc4c3820b81c4c3820b37c4c3821b53c4c3821a0cc7c68206ed821d8fc4c3821fbcc7c6820402821fb3c7c6820737820f7bc4c3821d01c4c3820884c7c682125b821e46c4c38215a8c4c38217cdc7c6820ec4821c9cc4c3821926c7c6820dc9821b13c7c682142b821e03c7c6820fd8821342c4c3821f39c4c3820767c4c3821c62c2c175c7c6820403820dccc4c3820871c7c6820ca082123fc4c3820d83c7c682197a8219cac7c68204f1820c6ac4c3821a4dc7c682064a821f3bc4c3821942c7c6820207821171c7c682056f820cb8c7c6821a5b821cf7c4c3821b7dc7c68216ea821adac7c6820db382106ec7c6820df8821b23c1c0c7c6820b70821a49c4c3820c65c7c6821aff821f70c4c3821961c7c6820d3d821d2fc7c6820c5f8214e3c7c6820310820a10c7c6821d08821d1ec4c382166cc4c3820bb1c7c6820ff9821738c7c682120a82176cc4c38214e9c7c6821c51821d59c4c3821fd6c7c682080d821d01c7c68212fa821dc5c7c6821734821f79c4c382154ac4c3821defc7c68212bf82146bc7c6821576821dc0c4c3821087c7c6820568821579c7c6820b9f82190bc7c68204a4821c07c4c3821766c7c682014a82109dc4c382120fc7c6820bff821a89c7c6820c71821cd1c7c6820de7821934c4c38214bec7c6821aef821f08c4c38211a2c4c3821eedc7c6821086821bcdc4c3821f39c7c6820f088219fcc7c682133e821d8dc1c0c7c6821ced821ec0c4c3820c2cc4c38219c0c4c3821522c7c6820562821c0dc4c3820de0c7c6821988821d8dc6c5819f8209d5c7c6820cb58211bfc4c382184cc4c3821ba4c5c43e82091ec7c68212e9821a92c4c3820f9ec7c682181b821e8ec4c3821615c4c3821ad2c4c3820191c7c6820d8f820ed4c7c6820815820a84c7c6821b8b821e5bc4c3820d76c4c3820768c7c682122e821fa4c4c3821846c4c3820b88c4c3821a25c7c68212e9821a82c6c581ab8204fdc4c38202b1c7c6821267821f3fc4c382108bc4c3821acfc4c38209bcc4c3821027c4c3821120c7c6820f3f821f83c7c68213ed821a08c4c382154dc7c6821a27821a53c1c0c4c3821c53c7c68219e1822022c7c6820867821526c4c3821293c4c3820c5ac4c3821df0c7c6821011821f02c4c38218d0c4c3821420c7c6820e2b821e45c7c6821d99821f9cc4c3821d9ec4c38211e8c4c3821424c4c3821f72c7c682083c820da5c7c6820c57821c6dc4c3822031c4c382039bc7c682011d8202cdc7c6821834821feac7c6820976821f68c4c38217d2c7c6820494821c59c4c3820b9fc4c3820199c4c382100ec4c3821aa0c7c6820bb0821a40c4c382131fc4c3821bcac4c382198ec7c68201a1820a33c4c3820c14c4c3822053c7c6820d9682182bc4c38204a7c4c3821412c4c3821d4ac6c581a682135fc4c3821018c4c3820b78c4c3821ef2c7c68205a58213aac7c682067f821773c7c68210a5821222c7c6821978821b6bc4c3821191c3c281edc4c3821c7ac4c3821207c6c581fc8201b6c1c0c3c281b7c7c682024c820833c7c6820dac8211bbc7c68214b182163cc7c6820f1582179dc7c682032b820b5ac7c68219d4821fe5c7c6821618822041c7c6820584821ef4c7c6820e818212d0c4c38210b9c4c3820cf3c7c6820fc1821d5ec7c6821753821780c7c68212c1821869c7c682186d821959c7c682073c821207c7c6821bd3821ff5c7c68204fb821ee4c7c68201a18211b1c7c682044a821d1ec7c682050482128fc7c6821abb821fa6c7c68205c4822077c4c3820d49c7c682170f821df7c7c682037d820d85c7c6820448820e89c4c3820541c7c68212df821b41c7c68210da821eebc7c682039b820fd2c7c68208b0821e75c4c3820b14c4c3821e0cc7c68213d6821d15c7c6821726821dd9c7c68202008205eac7c6820b0a8215dec7c6820413821d49c7c6820228821d7ac7c68212cf8214f4c6c581e7821dfec7c6820c88820eaac7c68206df8217e5c4c3820febc7c6821862821ca0c6c581d082189ec4c3820ff0c7c6820325821aebc7c6820c7c82165fc7c6821e34822027c5c445821257c7c68215ee821bdcc4c38211b3c4c38212fcc7c682032d820aeec4c3821284c7c682017b821231c4c3821cf3c1c0c4c38202cac7c68204138214a2c4c382095bc7c682106a8218d8c4c38208ccc4c3821d4ec4c3821bbcc7c6821237821270c7c682035a821fe7c7c682010982186dc4c3821d1bc7c68201be821985c4c3822081c4c38201cdc7c682044b8212c5c7c6820375821163c7c6821496821c01c7c6820efe820f9cc7c682126d821aaec4c3820241c7c6820cf1821837c7c6820cea82127dc7c68211cb821f45c7c682021a821ee6c7c68211178219f4c7c6820977820c97c7c6820350821d0cc4c3821752c7c68204b8821423c7c682031b820dbec7c6820ce3821a8ec1c0c4c38215d9c7c68211d182185fc7c6820d3e821210c7c6820333820951c7c6820ca6821311c4c3821b0ac7c6820885821c0ac7c68202368220b1c7c6821ae0821ce7c7c68202d48219fcc7c68212f4821cb2c7c68211d0821ed7c7c6820213821398c7c6820d4982203bc7c68206f28206f5c7c6821ed98220c9c7c6820e5182164ec4c3821cb1c4c36781a6c7c6821072821388c7c68211db822024c7c682011d8220afc7c682012d820d32c7c68220308220f0c7c682138d821ba1c7c6820e958220afc4c382013ac7c68212af821edec4c3821dbfc7c6820e76820ebbc7c6820a2c821d35c7c68218c5821fabc7c682049e821c27c4c3821b52c4c3822023c7c68211bf821240c5c45b821e9bc7c6820329820bfbc7c68204b8822026c4c382070cc7c68212188220a9c4c3821c67c7c6820f04820fbdc5c4818481b6c7c682043682103cc7c68202f9821992c7c68201ba821c4bc4c3820abdc7c68208098209abc6c581b1821913c7c68204008206a7c7c6820e01821947c7c6820107821babc7c68203b082137dc4c38211afc7c68204d88218eec1c0c5c44d820f3ac7c6820832820e80c7c6820ad1821de5c7c68211f782131bc4c382123ec7c68209cb8220ecc7c6820c12820e82c7c68203b2821ba9c7c6820e85821d30c7c6820452821081c7c6820570821f7ac7c68215c982200cc7c682023e820794c4c3820c55c4c38212b9c7c68216e3821b28c7c68201828215e9c7c682190d821b13c4c382119bc4c3821ac5c6c581ec8204d0c7c682031d820eefc4c3820d6ec7c68211d4822070c7c6820b19821a7dc4c3820a0bc7c6820bfd82102cc7c6820eca821d3ac7c68205fa8219c4c7c68211d8821ddfc7c6820ae1821280c7c682038b8216c8c4c3821de0c7c68206c9821c9dc5c466821cb8c7c6821f72822119c7c6820a8a820effc4c382172cc7c6820d1b821f69c6c581ac8216cec7c68208cf822022c7c682165e821c1cc7c68203aa8219c3c7c68207ad820f63c4c38218cac4c3820673c7c682039582123cc7c682040f8218a8c7c68211f782211dc6c581c4820b21c7c6820a7d820f52c7c682033682180cc5c41f822144c7c682102a8215c9c4c3821a06c4c3821389c7c682011582160dc7c6820aeb821205c7c682013682103ac4c3820ec3c7c682138e8220f1c7c6821e7282205fc4c3820c86c4c3820ca1c4c38207d1c4c3821800c7c68220f2822124c7c68203bd820707c4c3821dffc7c68217df821ad1c7c6820af88212cec7c68215808219a3c4c3821f5ec4c382107fc7c68203fc820f8cc7c6821888821db6c7c6820f348216fcc4c3821115c7c68201a98220c3c7c682022d821c9fc7c6820f09822013c7c6821fb58220d3c7c6820ae4821813c4c3821df4c4c3821a80c7c68203e7821e8ac7c682101982195dc7c6820b988212c9c4c3822099c4c3820f03c7c68205068220c8c7c6820c08821802c7c6820c45821cf0c7c6821cc1821d51c7c6820dcd821e18c4c3820bbcc7c682035282206bc7c68206f68220dac4c3821291c7c6821a94821dffc1c0c7c68203fd8215b3c7c6820feb8213b9c7c6820f89821facc7c6820425820d41c7c682133f821bb6c7c6821baa821fbec7c6821c9a821c9fc7c68215ce821cefc7c6820f64821aafc4c3821018c7c68210b5821c30c7c682024e820b5cc7c68202f2821db7c7c68213c2821e66c1c0c7c68204db821e26c7c6821192821b44c7c682022e8213ecc7c682128282199bc4c38211f5c4c38219d5c7c6820f40821da8c7c6821c88821f73c7c6820359821d2ac6c581e9820d56c7c68206468207cfc4c3820e3ec4c3822190c4c3821ff2c4c3820b77c7c6820edb821c2ac4c3821fbbc7c682054d821b94c7c68211a1821e1fc4c3822173c7c68203ee821167c7c68208b1821d94c7c682041b820781c7c6821640821efac3c281a4c4c3820e9cc7c6820f8e821ccec7c682104582111ac7c68203a682156cc7c68214dc821f14c4c382206fc7c6821c00821fc4c4c38211c0c7c6820db0821aa7c7c68205f482100cc7c682108f821412c4c3821cc2c7c68203c5821ff0c5c406821b9dc7c6821d6d821d9ac7c6820ac58210e4c4c3821e02c7c6821201821cfbc4c3820fd0c7c6820c90821ca9c7c682032a8214bcc4c3821be9c7c68212ab821c4ac7c682038d82041bc7c682129e8212b1c7c682020c822110c7c68210f4821172c7c682062e82213dc7c6820e50821094c7c6821d7f821f87c2c148c7c682105e821ab4c7c68213058216bac7c6820288820e54c7c68202598220a4c7c6820e1c8220ccc7c6820137820f9dc7c682176b8219d0c7c68203d6821e0fc7c6820e748220c7c4c3821dd8c4c38210fcc7c6820bdb820f8fc7c6820895821dadc7c68213d182201cc7c6821308821676c7c682012f8216e5c7c682116c821eb8c4c3821188c7c6821e55822084c7c68204b6820a44c7c6821b46822091c7c68207048218ccc7c682019f821c9ac7c6821d3e82212fc7c68201fe820bbdc7c68201e18220b2c4c3821495c4c3821c03c7c68208e2821551c7c6821268821eb2c7c682014b820ce4c7c6821cda821edbc4c3820a1dc7c6821554822164c7c6820edc821b77c6c581b4820c4bc7c682066a821ca7c7c682119f8220dec7c6820622821e04c4c3821f98c4c3822011c7c68203728215fbc7c682143b8219b2c7c68211e6822113c7c6821c6b821e78c7c6820786821781c6c581888216ecc4c38203f9c4c3821d40c7c68209b2820e10c4c38202d3c7c68203d082200dc6c581e0821d82c7c68203a4821e61c7c68221568221ddc7c68202d9820300c4c3821240c7c6820b4882214ec7c6820a1582205dc4c38211ebc4c3821d66c7c6821c77821d97c7c6820396821a90c7c6820891821462c7c682094282115bc7c682155982215ec7c682094a82106bc7c68203868221e8c4c3820bf5c7c682171f8220a9c7c68212ac8217a8c7c6821c44821ea7c4c3820851c7c6820130821334c7c68207cc821df4c1c0c6c581a5820823c7c6820893822122c6c581ed821a66c7c68212ce8212e4c7c682180b82209cc4c3821fb4c7c6820ff9821f88c7c6820304822002c4c382135ac4c3821c4dc4c3821ee0c7c682038082171cc7c6821a65821e57c7c6820b98821767c4c3822206c7c68201ef8212eac7c6820aae820ad5c7c6820839821ba5c7c68211e7821b1bc7c68206068221eac4c382090cc7c68203dd82117bc4c3821f92c7c6821fad8221aec4c3821c56c7c68203cf820a1fc7c6821166821395c4c382220dc4c38221dfc7c68211518221b9c7c68202c88211f4c7c682077c820872c4c3820fe0c7c68212f9822094c7c6820edf82172ec4c3821d26c7c6821768821825c7c68202ae820cd4c7c6820d6a821272c4c38219dec7c682176e822040c4c3820248c4c3822166c7c6821cb08221dcc4c3820f88c4c38210c4c4c38213f6c4c3820277c4c3821e62c7c68208c382210ac7c6821c17821cd8c7c6820d4e821d09c7c68212bc822205c7c682175e822161c7c68221048221fdc7c6821540821e4dc4c38212a4c4c382096dc7c6820511821ce0c7c6821043821912c7c68203ea820593c1c0c4c38218dec7c6821256821ca8c7c68203a0820f26c7c6821d078221d6c7c68207c2821c41c6c581d7821159c7c68202f682168fc7c68218e6822140c7c68204308220f7c4c3820249c7c68203cf822025c7c68212b482221fc7c6820103821a63c7c6820b2d821dd8c7c68203e2821eb9c4c3821f31c7c6820e80821f7bc7c68210cd8221b4c4c3821c15c7c6820de78211e9c7c682033b8221f6c7c682039d820824c7c6820437822131c4c3821ba8c4c3821034c7c682013d820a2bc4c3820c1bc7c6820e7782124cc7c6821c69821eabc6c581ee821b1ec7c6820f588213f0c4c38217d3c7c6820a5e8214cec7c6821889821ee3c7c6821677821eb6c7c6820234821215c4c38216c1c7c6820afc821bf2c7c6820163821382c4c3821a38c4c38212d9c7c6821b8c822076c7c68207a3821386c7c682075a8220cbc7c6820aa1821ab1c7c6821038821570c4c38202f4c7c68202d482171cc7c6820379821b00c7c6821adb821ae0c7c6821246821b03c7c6820202821f04c7c6820ceb8218c6c7c6820b938210abc4c3820b79c7c6820cc48220a1c7c6821787821e19c7c68212ad8214c2c7c6821c71822184c7c682084a82105ac4c3821ea3c7c68219be821c17c7c6820fe8821469c7c682016a821cccc4c3820982c5c47e82176fc4c38212d7c4c3821cd4c7c6820a5c821165c7c68205b98211b6c7c682146b821a4ec7c6821c618221dfc4c38202d5c7c6820fbe8220bac4c3820612c7c6821ce58221a7c4c3820b62c7c682020a821ee1c7c68206a38212a5c7c68220f6822201c7c682153f821d50c7c6820c8e8214c7c7c6820d148212ccc7c6821cff822213c7c6821e36822130c7c68204398209c0c4c3821e4bc7c6821a76822177c4c3821e9cc4c3820ec6c7c68216f1822195c7c682027d821ba8c7c6821d24821f59c7c6820531821806c7c6820b6c820e25c1c0c4c3821e75c4c3821873c4c3821be5c7c68201e5821f10c7c682044a820ef0c4c38202dfc4c3821048c7c6820a29822005c4c38211e2c7c6820da1821e5fc5c444821048c4c3821e37c7c6821d798221a9c7c682034b822294c7c6821b0282207bc7c6821056821d1bc4c3821cd8c7c6821985821b1dc7c68212f4821784c7c68211de8216d2c7c68203db82220fc7c68201cc8214ffc7c68208f5821d5fc7c6820af7820cdac4c382201ac7c682129b82221fc7c6821fae822169c5c405821e29c1c0c7c68206b5821d6ec7c68214908221bec7c682065c820ae9c7c6821154821256c7c6820b7c8220c3c7c682010c820eeec7c68212d182136cc7c6820d3f821b53c4c382206ac7c6821c058221f9c7c6820d09821265c7c682042f821e4bc7c6820427821b64c4c3820a69c4c3820fbbc7c6821068821e39c4c382211ac7c6820229821335c4c3821216c7c68201ad820a4ec4c3821e72c7c6821235821f5cc7c6820a23821e51c4c3820a21c4c38222bcc7c682112d822277c7c6821d0c821e28c7c6820b03820d01c7c68202a78219c8c7c68210d28221a0c7c682019d821ff1c7c68207f7820a75c7c6820bdf820e74c4c38202c1c7c6820432821e0cc7c682200982229dc4c3821ea0c7c6820f55821c66c7c68212d68220d6c4c3821d71c7c6821395822270c4c3821d3bc7c6820f48821014c7c6820174821383c7c6820df1821655c7c6821568821981c7c6820f92820ff1c4c3821f1bc7c682040882069dc4c3822136c7c6821c00821cbbc7c682028e8202e4c4c382123bc7c6820674821cf4c7c6821f388222c7c4c3820e3fc7c6820ecd821009c7c68204df8204f4c7c68211e182207ac7c6821d2b8221b3c4c3822200c7c682015c821479c4c3821cdcc7c6820f8b8215c4c7c6821a3182213ac7c6821d0b8220ccc7c6820e21821743c7c6821afa822004c7c6821291821866c4c38203d7c7c6821fbb822306c7c6820189820e37c7c68203338220cec7c6821e6c82226fc4c38220b9c7c6820ccc821435c7c68221818221dec5c43882230ec4c38206c8c7c6820446820e41c7c6821138822274c7c6820dcb8222afc7c6820c7a8212c6c7c6820428821d19c4c38204cec7c6820306821d70c7c68201388221f1c7c6821039821270c7c682022282141fc7c6820575821ec3c7c6821162822125c4c38211a1c7c6820101821af3c7c682038e8219f5c7c6821e4e822287c7c6820c348218aec7c6820254821e78c7c6820c24820eefc7c68202aa821f24c4c3820988c4c3822279c7c6820748822123c4c3821df5c4c38212e4c4c38220f8c7c68211e482127ec7c6821fd78220a5c7c6820349821febc7c682089d8221c3c7c6820990821b86c4c38214b0c7c68204678206ffc7c6820acf821b8cc7c682193182230dc7c6820a4c821c5dc7c68204fa820e17c7c68202b7820882c7c6820a2482109fc4c3820b1ec7c682017c82130bc7c68212628216dcc7c6821f5a822070c7c682010d82127cc7c68211df822151c4c3821ec4c7c6821511821ea6c7c6820ea3821b0cc7c6820dc6820eeec7c6820159821999c4c38215a1c7c682215c822276c4c3821cf1c6c581fb820e45c7c6820482820faac7c6821ceb821f98c6c5819382220cc7c6820c26822170c7c6820c178222acc7c6820cfe822152c7c682105c822039c7c682045e8215a9c7c6820c8c821f4ec7c68222958222d3c7c6821d68822308c4c3820ebec7c682161d821840c4c38213e5c4c3822017c7c6820397820a0fc7c68213f4821f27c4c3822151c7c682027f822109c7c6820705822335c4c3820ea3c7c68202d1821bd3c7c68207a2821dd9c5c425820e0fc4c38220fac4c3822136c7c682199e8221f7c7c68205bf821fd2c7c682033a821c80c4c3820160c4c382078dc5c4238204cbc4c3822237c7c6820fca82171dc4c38208bac7c6821491821820c7c68212cf821af1c7c6821d698221f6c7c68213ea822332c7c6820efa821035c7c6820b1f821189c7c6821130821d7bc5c4328210f6c7c682041882173ec7c6820790820e4fc7c6821fe2822310c7c68205008221bcc4c3821e57c4c38221f1c7c68201ea8212b6c7c6820bd38221a7c4c38222f3c6c58196821255c4c38215c6c4c3821266c4c3821d31c4c3822311c7c6821194821bbdc7c682015e8222e3c5c445821dfcc4c38221f0c4c38211a0c7c6820404821227c7c6820f2282219ac7c6821bb0822180c7c682105082154bc7c6821748821bf3c7c6820d7882115dc7c6820d04821c0fc7c68218108219eac7c6820baf821719c7c68212038215a6c7c6820891821126c7c6822185822261c7c682111b821286c7c6820a03822134c4c3821728c7c6820fcc821d59c5c404821703c7c68219b5822202c4c3821d97c6c581ca8218b6c7c6820197821ed5c7c6821232821a25c7c68201b5821d03c7c68201d38213f6c7c6821097821a5cc7c68209488213f1c7c6820ed7821f2cc7c6820163822315c7c68206dc821bfdc6c581ba821169c7c6821430821ee2c7c68214e7821cadc7c6820bfd8221adc4c3821f29c6c581ef82012ac7c68208cc820caac7c68204908214e7c7c6820237821139c4c3820912c4c3822241c7c6820323822233c4c3822259c7c682040b821000c7c6820a00820fd2c7c682124b821afac7c682100d821cdfc4c3821eb4c7c68201c0821c6cc7c6820e6d821ca6c7c6821283821accc7c682202782237bc7c68219c2822005c7c6821c60821cd5c5c479821cd6c7c68220e78222a0c6c581da822308c7c68205ee820d32c7c68204fb820f8cc4c3820cb3c7c6821511822247c7c68212c28222f2c7c682044d82232fc7c6820124820ddfc7c682033c8212e2c7c682089a820c00c7c6821c7e8220d2c6c581bf820ed2c4c38212a9c7c68211638222aec7c6821d28822082c7c6821a678222cdc7c68206f7820e65c7c6820e1b821a6ec7c6821956821f0fc7c68207cb820f7cc7c6820428820c8bc4c38221e5c4c3821460c5c47e8207fdc4c38213dcc4c3821468c7c6820f4b822359c4c382175dc1c0c7c6821ba682215ac7c68205f5820a3fc7c68201b48221b8c4c3822052c6c581cd821ab6c7c6821dd382202bc7c6820752822362c4c3820f23c4c3822128c7c6820678820d89c6c581f182191bc7c6821519821f6bc4c38208d6c7c68203f8821b2cc4c3821708c7c6820868820d3fc4c382069ec7c6820cb0821031c4c3820dacc6c581d9821e49c7c6821af3821d71c7c68202658203e2c7c6821942821954c4c3820ffac7c6821a158223bfc7c6820a4e82211ac4c3821fc0c7c68202878206e0c4c38216dec7c682233b8223dbc7c6820afb820dd3c7c682033e821beac7c6821d63822334c4c3821244c7c6821bed82225cc7c682219b8222ccc7c68201de820673c4c3821de9c4c38217fbc7c6820ee5820f4dc7c6821049821ba5c7c68202c78206bcc4c38212a7c7c6820fb7821700c4c3822246c7c6821cbc821ebbc7c68202c6821fc9c4c38217f1c4c3821d0bc4c382119ec7c682129882227ec4c3821ebac4c3821e2cc7c68204fe8210c5c4c382113dc7c6820d15821d53c7c6822126822289c7c6820e568211c9c7c68220fe822296c4c3821dedc6c581f5821131c7c6820d30822098c7c68219e3821ca5c7c68210bd822258c4c382193bc4c38223cdc7c68204c6821f26c7c682148f821bb3c7c68201ba8220f4c7c6820344821d9dc7c6820e86821527c7c68217bc821f0fc4c382181bc4c38215d7c4c3820183c7c6821bba82222ac7c68209e9821f91c7c6820d44821ff4c7c68216d2822173c7c682159f8221fdc7c6820356821da3c7c68209e7821a77c6c581eb821139c7c68208eb8213d6c7c68204e682222fc7c682106f821e4cc7c682157f822095c4c382109bc7c6820d00821ecac7c6820cdf822226c7c682148a822390c1c0c7c68215b98217c7c4c38208f1c7c68215c8821cbcc7c6821bfb8221acc7c682093382209ec7c6820a17821177c7c68209738211a7c4c3821153c4c3821c57c7c68205f9821f76c7c6820203821955c4c3822410c7c68222d18222eec4c382179bc7c68216df821e2ac7c6820684821f38c7c68202848222b6c7c682014d821eb2c7c6822099822257c7c68219a482216ac7c6820cd7821b69c5c413822243c7c68203af821edbc7c68210e2821cb9c7c68202d28212c2c7c6820b698211d3c7c6820d4382177fc4c3820d88c1c0c4c3821404c7c6822212822303c5c4358220ebc7c6821b79822416c7c682100582173fc7c682067c821dcec7c682110f82220ac7c682201e8223d6c7c68217a6822159c4c3821253c4c3821c20c7c682186b82203fc7c6821a30821d3fc7c6821206821b64c4c3821b7ac4c3822349c4c3821edec7c682018a820b3ac4c38220bfc7c682025d8223b6c5c4158212afc7c68210b6822413c7c6820e52822113c6c581fa820a82c4c382170ac7c6820402822294c7c68210d1821994c7c6821f0c8222aac7c6820dee822256c7c6820d05821526c4c3821857c4c382238dc7c68214c98222b6c5c471821e5dc4c3820af7c7c6821001822087c7c6820bef821cbec4c3822413c4c3821ed6c7c682101e822035c7c68203bd8217ffc7c6820d72820f4ec7c682210b8221edc7c6820799820841c7c68216fd822207c7c68213a0821c8cc7c6820ced821ca8c7c6820327822402c7c682084d821948c4c38205d0c7c68215eb8219aac4c3822378c7c6820e5d822024c7c6820859821a15c7c6822161822371c7c682149a821911c7c6820db2822398c4c3820bf4c7c6821091822389c7c68204108212b3c7c6820f9b821e46c4c3821f96c7c68211ec8223ebc7c68202b3820c7ec4c382122dc7c68213678223ffc4c382126cc7c68210758223c8c7c68212e68214a6c7c6820ee4821f12c7c6821bf2821f3ac7c68204d982227bc7c6820238820b0fc7c68209958223f6c7c68207ec821f3dc7c6820d05821213c7c6821e14822304c4c382225dc7c6820c43821aeac7c682048b821a95c7c6820aad820fd5c7c6820e24821296c7c6821224821ae8c7c6820d1d821fd9c7c68202ef822123c5c42d820f3ac7c68204dd82104ec5c43f821bb1c4c3822382c7c6820a43821859c4c3821e7dc7c6821093821c1ec7c68211718222c8c7c6821d3b822283c4c3821e82c7c6820262820eafc7c68211be821855c4c3821689c4c38214eac7c68216e7822478c7c682191d821b96c7c6821b8b821dfec4c3820b0ac4c382210dc7c68203f18223bdc7c6820a94821215c7c68203738222e3c7c6820e7a821dc9c7c6821a2c822210c7c6820363822271c7c6821c30821f01c7c682122c82231ac7c6820aa38223bbc7c6820a81821632c4c382101ac4c38223c0c7c6821969821c76c5c47882155ac7c68202428211d0c7c68206e18222a6c6c5818f820b1ac4c3821762c7c682114b822232c7c6821ad682235ac7c68201b1822199c7c6821f478221e0c7c682128382184bc4c3821fa0c4c3820e60c7c6821c668222d8c5c4578216d7c7c6821b0e82249ac7c68217c482209ac4c3822196c7c6820f29821f37c4c3821a7fc7c68203268211e7c7c682025d821a48c7c682044c82235ec7c6820cf5821331c4c3820221c7c68203178217d5c7c6821ace8221b4c4c3821c06c7c682103a8216dbc7c6820393820919c7c6820b3b821b17c4c3820f85c4c38208c9c7c68207e48215fcc4c3822100c4c3821507c4c3821e37c7c6820346821f85c7c682103982112ac4c3820fddc7c6821fe082224ec4c3820311c7c6820b9e8222e2c7c6821add821bf5c4c3821cd2c7c6820c37821e56c7c682034b821c16c7c6820a8a821b88c7c68221c382230bc7c68201ce820e87c7c68210ee8223fdc7c68201a3820ce0c7c68202d382112fc7c6821c238222a5c4c382085ec7c682161f8222f6c7c6821827821b60c7c6822266822464c7c6820989820acac4c3820e1fc7c68211b2821c72c6c58198820806c7c6820a7e82128bc6c581a2820fa9c7c68207f68209c5c7c6821db3821e6cc7c682026d8208a7c7c6821933821f5fc4c3821f49c4c3821d03c7c6821ef3822487c7c6820fc08215e6c7c68212b782207dc7c68213be821c18c7c68220dc822435c7c6820afd821a8bc6c581a3820148c7c6822034822203c7c6820445820d7cc7c68215fe821e1cc7c68216c7821f4cc7c68209178212d1c4c3822300c7c6821120822376c7c6821f898224a4c7c6820e79821c9cc7c6821baa821d9ec3c281b8c4c3822266c7c68201b9820501c7c68209d8820afec7c682119e82193ec7c68211748214a2c4c3822298c7c682181982195cc7c6820964821e85c7c68213848221b1c7c682099e822427c4c382177ac6c581c9821980c4c3821d6cc7c68202cc82252ac7c682222a822233c7c682134982216ec4c3821921c1c0c7c6821abd82242cc7c68207f8820b15c7c6820113821b46c4c3820ecac7c682236d8223a5c7c68204b4820b1ac7c682114a821321c7c6820395821b83c7c682136c822263c7c68210c38213b9c7c6820ec08222d9c4c3820ff5c7c6821ef7822006c7c68219268219b0c7c6820d81820e58c7c6821913821f59c7c6820aab820eccc7c6820e1d821cacc7c68224198224f8c7c6820ee182252fc4c3820afbc7c68202188202f8c7c6820f14821ec6c4c38223eec7c682053d822211c7c6821de0821eb3c7c682207d8224efc7c68201778211edc7c68214f08216dfc7c6820622821ac5c7c6821bde822443c4c3822451c7c6820447821c9dc7c6822078822319c7c68212ef821cffc7c68211a082143ec7c682091c821d16c7c682033c821ab6c7c682015c8207ffc7c682117f822384c7c68203298220dec5c45982227bc7c68202e9820c24c7c6820ef6821dd7c7c6820f608224d8c7c6820265821fe4c7c6820fb6821005c7c68207db822444c7c68221d5822327c7c6820d908212b5c7c6821a2c8222b0c7c682127f822521c4c382230dc4c3820a77c7c68201f6821e31c7c6821d668222c7c7c6821e568222f4c7c682027e8211acc7c68220d9822567c7c6820edc822249c4c382238fc7c6821687821848c4c38211d8c7c682106b8223a2c7c6821100822516c7c68222ff8223e6c5c4398216e6c7c6820d9b821071c7c682048c821accc7c6820d94821460c7c68213df822443c4c38218cbc7c6821015821c83c4c382133bc7c68212f6821313c7c682074782255dc4c3820528c5c47a8208a7c4c3821ca5c7c6821bc48224fdc7c68209a0821e26c7c6821cae8222efc7c682110682211cc7c6821fdd822047c7c68207f482114dc6c581ae82244bc4c3821b2bc7c6820a9e8215f3c4c3822479c7c6820276820ccfc7c68201d882140fc7c68212098212d8c7c68224a6822556c4c38222f5c4c382191dc7c682202d822550c7c682013a8224abc7c68206428217f6c7c6820dbf822117c7c68208ea82242ac7c6821ab1822489c4c382254bc7c682188e821910c7c68201ec821dccc7c6820116821befc4c3821fb4c4c38220b0c5c4708223d2c5c412822422c7c682030b8218c8c7c68207f282229fc4c3821b0bc7c682033982062cc7c6820f5382207ec7c6821e32822367c4c38214b2c7c682013f820baec7c6820e85820fb5c4c3821fa8c7c68221058224f3c7c682067e8216cac4c3822090c7c6821be4821f10c4c382175fc7c68208648210dec7c68214ae821c63c4c382216fc7c6820210822480c7c6820280820e6bc4c382216cc7c68207948210efc7c6821cdf822293c7c6820bc88222e7c7c6820c448222a8c7c68204538211b5c7c68222208224aec7c682114c82253cc7c68218bc821ffac7c68207e5821d4dc4c3822054c4c3820f90c7c68202ba822120c7c6820cd2821ec1c7c6821b4c822459c7c682012b8224f0c7c682108a8224e0c7c68219c0822180c7c68201df821bbcc4c38207bbc7c6820251821279c7c682115282205ac4c3820d7ec7c6821398821c65c7c68223dc822591c7c682088d821e01c7c68202a5822578c7c68216c1821c4ec7c68205b1821d56c7c6820d9a822544c4c3821e3cc7c68220288225d3c7c6822217822324c7c68201b6821d90c7c6821d4e822230c7c6821701821eb5c7c6821218822368c7c682042c8220c1c7c682100f821de7c7c682151582248fc7c68201f0821bb6c7c682051a8209e3c7c682028b822545c7c682065182195ec7c6820aaf822433c4c382219dc7c6821d0082228dc7c68208a8820fa1c7c68206a482251dc7c68209b1822242c7c68216de82214dc7c68201dc8211cac7c6821a69822293c7c682232f822593c7c6820e1482225ac7c6821af08222adc4c3821b31c4c382167ac7c68211b48220b8c7c6820f1e820f68c7c6821c71821df2c7c6821b1d8222d0c7c6822248822338c4c3821cd6c4c3821dbdc7c6820f318225b6c7c6821e0682219fc7c68207b0821182c7c68204c88207adc7c6820144821650c4c38224e5c4c3822574c7c6820394821103c4c38224c9c7c6821f7782253bc7c6821eb88223dfc7c68202a88221f3c7c68207eb821fe8c7c6820ede820f5ec7c68209a3821d75c4c38209b5c5c460821569c7c682138d821a4ac7c68203d2821b4bc4c3822525c7c68217e7822331c7c682145982214cc7c682126b821b98c7c6821591822536c7c682201d8221d0c7c682120b821f58c7c6821e9782202ac4c3821a74c7c68220c6822561c7c6821241822112c4c38210a3c7c68216278224b9c7c68201f3821233c4c382215bc7c6821d5d821f69c7c682081e8222dbc7c6822436822517c4c3822457c7c682205b822343c7c68201c6821f6ec4c3820dc8c7c68219a7821a2fc7c6820307822415c7c68201d7821c18c7c6821d6482227ec7c6820ff4821beac4c382118fc7c68218558223f7c7c6820b9c821fa2c7c682172d821f60c4c3820e56c7c68211bc82223ac7c68203cc821fe2c7c6820433821f76c7c682156982231bc7c68209358211a6c7c68202998225c3c4c3822148c7c68218da822146c7c6820f8d82221dc4c3821b4ac6c581a3821de5c7c6821e99821fa1c7c6820d67821b56c7c6821e42821e85c7c6820287821d96c7c6821f7a8223f2c7c68202b28211c2c7c6821974821a20c7c68204c782111fc7c6820141821c93c7c6820149821b7ec7c6821f9d8222d2c7c6820d69821012c7c68220898222ccc7c68212458225afc7c6820809821bbac7c682061e821178c7c6821bb7821c5ac7c6820153820dd0c7c6821630822627c4c382231bc7c68217578223cec4c38223bbc7c6821e308221eec6c581e08212dec7c6820fd1822524c7c6820101821ef4c4c38215a3c7c682100a822344c4c3820f51c7c6821718821d5cc4c38225cac7c6822261822363c7c68212d78221bfc4c38222fdc7c682158f822148c7c6820cd482261dc7c6821c45821d38c7c6820fc482115cc7c6820338820f41c7c6820c53821aeec7c6821448821d42c5c433821fa4c7c6821aad82212fc7c682026a820b41c7c68222528222dec7c682238782249ec4c3820ae2c7c682171f822292c7c68212dd8224e2c7c68220ea822421c4c3821f34c7c68207cd82212cc4c38209b8c7c6820f5a821e92c4c3821ed3c4c382109cc7c682104e821c8fc7c68201958222d4c7c68201788221b7c5c403820cb7c7c6821c338224ddc4c3820c9ac7c6821a58821ab7c7c68206a982079ec7c68208b7822481c7c68205f5821f9bc7c6820cf7822028c7c6820da38218a1c7c68204a882248ac7c68204318214cbc7c6820557822495c7c6822231822577c7c6820310821ccac4c382247bc6c581e3820a9ec4c382213ac7c6821af2822430c7c682042a82115fc7c6820d93820e1dc7c6820e9682257cc7c6821c59821df2c6c58181821d3dc7c68202b4821e86c7c6821497821ccec7c6821d218221fec7c68201da822174c7c682088a82264ac7c6820ee6821bdfc7c6820bac8220dfc7c6821b2282252bc7c6820c8f8220d8c7c682151382224cc7c6821156822605c7c6820c2182111dc7c682216e8224c3c7c6821758822549c7c6820766821469c7c682029d820586c7c6821be0821e06c7c6820eab820ebac7c68223dc822617c4c3822431c7c6820ea6822142c7c68211a8821f4fc7c6820fe4822291c7c6820ba1821daac7c682210f82242bc4c3820ce5c7c682108d821befc7c68210328221dcc4c382250bc7c68204e68225bcc7c6821379821dddc7c6821fa3821fc9c7c68217a8821fefc7c6821cf6821f2ac7c68202ab822390c7c6821d57822342c7c6821711822406c5c4418222cac7c6820d1c82163ec7c682208f82221cc7c682040782243fc7c68211188220fdc7c6820cf182215dc7c6820935822455c7c6820854822189c7c6821cfc822315c7c68220598223abc5c44e8218d4c7c68203d3821f1ec7c6820c3f8216bec7c6820bb8821aa9c7c68203e1820bb4c7c6821cb58223c2c7c6820e08822135c7c6820d84822176c7c682235b82264fc4c3822235c7c68222f9822336c7c68212388225f1c7c6821d6c8224f8c7c6820230821a8ac7c682206a8222b9c7c682216a8225c5c6c581d1821b5fc7c6821174821586c7c6821a72821e5bc6c581fb822009c7c68201e5822314c7c68207a18223d9c4c3820418c7c682144f821dd6c7c6821141822558c7c6822414822604c7c68224f68225abc7c68221d88225f3c4c3820b53c7c68218238219cdc7c6820e06820f0cc4c38225c3c7c6821669821e84c7c6820e098210eac7c6820f4f821065c7c6820dbe82205bc7c68215f382217cc7c6821345822430c7c6820cf88212e3c7c6821c10821cdbc7c68214058225dec7c682036a8221c8c4c3821cb9c4c382088dc7c68218dd821cf7c5c430820eb0c7c68203038225ecc7c6820da0821724c7c6820853821d88c7c68220dd8225fec7c6820340821f9fc7c6821515822145c7c6821bb8822079c7c68207be8225b4c7c682037b821a03c7c682037f8225b9c7c68223f08226a1c7c6822115822234c7c6821289821fa5c7c68207fc8225edc7c68220408226cbc7c68212be821b78c7c6821bc7821df6c4c3820d54c7c68217b2822205c7c68210aa822270c7c68203f3821f97c7c6820130822282c7c682036d8206dfc7c682208882246dc7c6821b09822668c7c6820173820952c4c38220eec7c682203e82236bc4c382257bc7c682094f822288c7c68203c682137cc7c682084682239cc4c3820c18c7c68218b58218efc7c6821efc82223bc7c682162c8221b9c7c6820f43821e67c7c682181882254ec7c6820599821bf1c7c682248c822603c7c682112c821728c5c417822068c7c6820f168224aac7c68222a28223afc7c682146a821f93c4c3821e96c4c38226b1c7c68209bd8221efc7c6820738822633c7c68222cf822615c4c38206c9c4c382220bc7c6820283821117c4c3821d9dc7c6822257822540c7c68203c38208d4c7c68222e182240ec4c3820604c4c3821ea8c4c3822476c7c68211f9821f28c7c682253382257ec7c6821279822098c5c431820ffdc7c6820f4082266ec4c382228cc7c6820295822361c7c6820534820ad2c7c6820b7a8220dcc7c6821f5b8225b8c4c3820e66c7c68201cb822675c7c68204058224f1c4c3822086c7c6820671820d09c7c6820ac98215afc7c68202c8820befc7c6821900821ff7c4c3820617c7c6820196821ea2c5c41d822493c4c3820f65c7c6820d6c821f50c4c3820631c4c3822280c7c68211cb82207cc4c3821508c7c6821b4c82203cc7c6822655822656c7c682127582255ec7c682118482124ac7c6821485822662c7c6821719821c08c4c3822240c4c38218d2c7c6821426821607c7c6820eb1821969c7c68210508225b7c4c38222c4c7c6820cce82194ac7c68207ec82129dc7c68210ce8220ecc7c68201828225dcc7c6821c99822153c4c38218b3c7c682109b8213e0c7c68220e4822701c7c682168f821ff4c7c68209398218f9c4c382183dc4c3821a11c7c682043482046ec7c6821284821fbec7c6820e3e821b98c7c6821b21822692c7c682116a8224a3c4c382242bc7c682185a821b59c7c68202a1821125c7c6821cf1821d13c7c682105f821d96c4c3821debc7c682013d82204ac4c38224abc7c68222c58226b9c4c382216cc7c682180d821a3cc4c3822156c7c6821cf28225ccc7c682052b822360c7c6820d63821ef5c4c3822540c7c6820847821b67c7c6821a5e821ff5c7c6820e7e822760c7c682240b82242ec4c3821fd4c7c6821cf282237dc7c68213ca82246ec4c382184ac7c6821d49821d73c4c3820cf0c7c6820f828221d2c7c682063d8221a3c7c68211cd8222d6c4c3822491c4c382241dc7c682118d8226ebc7c68210a382257ac7c682203182240ac7c6821541822771c7c6821b2782246bc7c682160082258dc7c6820f728210f2c7c68212d4821fcfc7c68203fd82215cc7c6820cad8220f9c7c682111d82200ac7c6822311822576c4c382181cc4c38226a8c4c3821c3fc7c6820dc2821277c7c6820f99822623c7c68201ac821872c7c6820514821ff1c4c3820ab8c7c68217ef821fedc7c6820503821d69c7c68205bd8225f9c7c6821dbe82250cc7c6822127822425c7c68202e38216a5c4c3821480c7c6821d508225f9c4c3822528c7c682224282261ec7c682084a820f6ac7c6821b0d82249dc7c68214868215bbc7c682085682234ec7c68204ef820db3c7c6821f04822695c7c68210e8821c56c7c682115b821cb7c4c38226a4c7c68204e382263ec7c6820581820d07c7c68224838225b8c4c38204bdc7c6820582821481c7c682122682223fc4c38225edc7c682046c8204e1c7c68220978224a2c4c38218ecc4c3821fb7c7c68203cd821960c7c682055e8226d3c1c0c7c682052e822526c7c6820555821d86c7c682050b8225cdc7c68207ee82155fc4c38217d4c7c682052b821b6fc7c682153b821c79c7c6821e36822551c7c68207b38224a7c7c68204c5821a30c7c682018d820562c4c38224ecc4c3820a96c7c6821c7d8222fdc7c6821fe5822366c7c6820c5f822790c7c6820ab5822770c7c6821cd0821eefc7c682234b822627c7c682233d82263ac7c6821bbe822704c7c6820fb2821017c7c6821da08223e9c7c6820e05821b1bc7c682054a82136ac7c6820511822611c6c581de821e6fc7c68207ba8217fec7c682269a8226bac4c38209a2c7c6820e0382206fc7c682236a82250ac4c3821eaac7c6820fa4821a5cc7c6820539822221c7c682032c8218e2c7c682129c821b35c7c6821a7d822688c7c68212a3821b66c7c68204e2821b60c7c682140c82167cc7c68204eb8212f1c7c68213fb821742c7c68217548225d9c7c682071a82123ac7c68209f0821458c7c6820d25820f30c7c68206b082152fc7c6821055821758c7c68204bf822335c7c682232b82245cc7c682048a822400c7c6820e4c822165c7c68204d8821f11c4c3821b43c7c6821f51822411c7c68217dc821d46c7c68216a0821b7cc7c6821e0a822268c7c682183c8221b5c7c6821a70821bd4c7c68205238225cac7c6820f13821dcec7c6820d0f821bcec4c38213b8c7c6821593821ebfc7c68204c3820f92c7c68215b5821795c7c68220ad82274cc7c6821422821e97c7c6821e808227e4c7c68209c1820a87c7c682245c822608c7c68201f4820f2bc7c6820a2a82272ec7c682050a820ecec7c682048c8226b0c7c682053a820e09c7c68204748206cbc7c682049182259dc7c68210468222f1c7c68218bb821f48c7c682047a820991c7c682055f82243ac7c682055d822673c7c682066482249bc7c68205c0820dcac7c68218bc821c94c7c6821ec4821ef6c7c6820f45821db8c7c68205748227edc4c3821eb1c7c68204aa820eecc7c6820499821efcc7c68205df82255bc7c68213e28222b5c7c6821d2a82206cc7c6820d7e821f30c7c682056a8227a1c7c68205208221a2c4c38218b8c6c581d282149dc7c6820b42820bdac7c682047f8218bdc4c3821ffec1c0c7c6820b94821324c4c38213b6c7c682269c8227b8c7c682056d82277fc7c682058d821f60c7c68204b1821aedc7c682206d8226c7c5c40b820c78c7c682075d821915c7c6820a2f82281cc7c682074482267bc7c6821e2782269ec7c6820b35822122c7c6821447821577c7c682059582230ac7c6821a33822375c7c6820808820abec7c68204d3821be8c7c68205928224fac7c6820131821896c7c6821bd18222f0c7c6822752822768c7c6820f42822410c7c68205d082127dc7c68223398223e8c7c68204dc821567c7c68220cf82228ec7c682049c82255ac7c68220a082243ec7c682125a822187c7c6821c70821ffcc7c6821438822720c7c6820291820623c7c68204818220f5c7c68211f0821c53c7c6821d9b82270fc4c3820110c7c68204a3822824c7c68205c2821e2ec7c682153482222bc7c68204f8821f31c7c682199f821e9ac7c6822075822798c7c6820489821d06c7c68211b8821bbfc7c68205fe8214cfc7c6820585820ccac7c6820228822579c7c682067d821b8ec7c68221ea822838c7c6820f1a822054c4c382068fc7c6820488821f6ec4c382202fc7c68223fd822689c7c6822497822750c7c6820fa2822760c7c6820466821172c1c0c7c6820f77821883c4c382080ac7c682054c8213bbc7c68204cf8206cbc7c6821ee58223cdc7c68219b4822490c7c6821dd78226edc7c6820c35821a4cc7c68205488212bdc7c6822352822495c4c38217f4c7c6820486822533c7c6820a11822198c7c6820e1f8213a3c7c68225c48227a4c7c6821c3c82281ac4c3821532c7c68211148226afc7c68216c0821716c4c3820ac6c7c68225238225c0c7c68204f2821dc6c7c6821360821bd8c7c68205d2820e76c7c6821e98821f6ac7c682045a8222ecc7c68205b0822253c7c6820597821f9cc4c3821cedc1c0c7c6820b20821729c7c6820589821305c7c68205fb821c86c7c6821106821acac7c682049f8224e8c7c6820d688226bbc7c682045c820e22c7c6821bfa822814c4c38224edc4c3820c81c7c68215ea822012c5c4208204bdc7c68208af820e35c7c68204978225f7c7c68205138226f5c7c68204a0821fdcc7c68215b7821a29c7c6820878822678c6c581a8821f8ac4c3820469c4c38219b1c7c6820db0821694c7c682057c8226a1c7c682056b821841c7c6820537821d73c7c682055082273cc7c6820fce8214e0c7c68205608223dec7c6822726822826c7c68206d9821eaac7c6821761821a75c4c38215cac7c6820ea78221f3c7c68202b88213aac7c68223d48225c9c7c68209b7821a97c7c6820540821617c7c68217ee8218b1c7c68221aa822347c7c68205de8224bdc7c682160a82170cc7c6820df9821fb9c7c6820c2a821c68c7c68206bf822807c7c68221478226fbc7c6820ebf8225a8c7c682240482253dc7c682051582268dc7c6821bde821f6dc7c68217fe821b8ac7c6820f4c8212d6c7c6820efb8226e6c7c68212bd8224a0c4c3820791c4c38208dbc7c68204b3821b35c7c682057a821c83c7c6820c10821066c6c581bb821a00c4c3821fcac7c68220fa822227c4c3821bfec7c6820570822574c7c6821061822580c7c68204fd820e67c7c68213c7821af6c7c68209df820c02c7c68204ac821ff0c4c3821b10c7c68208f3821afec4c3821372c7c6820494821237c7c682052a822749c7c6820f0d822284c7c6820593820f37c7c68204a6821802c6c581b28213edc7c68204ba822085c7c682046f820fdac7c68205108225a2c7c682047082164bc7c682051a82260cc7c68204a1820b2dc4c38219b6c5c41a821690c7c6820abe821637c7c6821108821e7ac4c3821ffbc7c6821db38225d1c7c6820bfc82223bc4c38214b9c7c68209c1821b1cc7c68219f9821e7cc7c682064f821d8ac7c68208ed821e43c7c68209af821dfdc4c3821356c4c3821fbdc7c68201db821944c7c682068d822108c7c6820e3f820f6cc4c3821ff9c7c6820123820906c4c3822789c4c38214b5c4c3821aa8c7c68216118218a5c7c682089e821609c7c682029a82080fc7c6821588821a37c7c682094b821ae1c7c6820b8d822186c7c68213e882192fc7c68205ff8223cbc7c6820c69821aabc4c382279cc7c6820d6082235ec7c6821b87821f6fc4c3820111c4c3820316c7c682018e82092ec4c3821917c7c6821381821745c7c6820241822528c4c3821506c4c38215d2c4c38228e8c7c6820f94821f8dc7c68207d8820a0ac7c6821ab2821fc8c4c3821e70c7c68216b38226ffc3c281a0c4c3821b33c7c68207d48219c1c7c6821789821dfbc7c6821879821f20c4c38228d4c7c68205f8821b0fc7c6821faf8228f0c7c68203308227a6c4c38206b7c7c682079a8215e7c7c6820a22820d31c4c3821df8c7c68210ef8214a7c7c68208fa821638c1c0c7c6821882821989c4c3821400c4c38217d9c7c6821972822901c7c68207fe820a5ac7c682010a82187fc7c682139f8228c3c4c3822438c7c682029a8228f4c4c382153cc4c3821b5bc7c682029c821892c4c38204bec7c68218148218f0c7c68217c18228eac7c68201588208d7c7c682061a821b17c7c68220a2822737c7c68215a5821ad2c4c38228d8c5c474821debc4c3821e13c7c682132c821f65c7c68201e38227cec6c581998228e1c4c38215fac6c581cf821910c7c68201318218ebc7c68209108228c1c1c0c7c682020482037ec7c6821928821f18c7c6821e52822839c7c6822403822877c7c68202de822074c7c6820861821e7bc7c68204d78216c0c7c6821717821ea9c7c6820bf78216e2c7c6820adc821fc2c7c68203c7820bdac7c68203d5821f42c4c38213c1c7c68207ee8228afc7c68214c582173ac7c6821781821eb3c4c3821e91c7c68215998218e0c7c6820479821aa3c4c38216a5c7c68205cb821b47c7c6820a59821e69c4c3821744c7c6821894821b36c7c6821366821886c7c68218d78219fec7c682023d821d80c7c6821afe8223f3c7c6821a96821c49c7c6821518821d1dc7c68224018226cfc7c68219bd821a62c7c68213c6821bffc7c68216518228c8c4c38228ebc7c68205a38222b7c4c382169dc7c6821a418228f7c4c3822356c7c6820125822796c7c682162882166dc4c3822917c7c6820a56821775c4c38228f6c7c68215d0821f7ec7c682054b822879c7c68204d4821db0c7c6820a22821940c4c382281dc4c3820c77c7c6820ac882197bc7c6821bc58221efc4c38202bbc7c6820c0b821e4ac7c68209aa821464c7c68207f1821bb5c7c682019e821f36c4c3821b33c7c68205c3822855c7c6820a67821589c7c682035e821c46c4c3821e79c7c682089f821793c4c3821ab9c7c68213d48227a7c5c408820bd6c4c3821f75c7c682021f82281dc7c68228e3822946c4c3821e43c7c68217728218bac7c68205e882191fc7c68205168217e2c7c6820421821d61c7c682099882161ec7c6821556821760c7c6820302820495c7c6821a5d821be6c7c6821ee982291ac7c682170d8219e6c7c6820bc98219adc6c581978227c6c7c6820351821ad0c7c68215aa8217b3c7c6820f1c82188ac7c682095f8219acc7c68217aa822949c4c382036bc7c68215cc821887c4c3821b07c7c68217b8821a32c4c3821c0ec4c38213b7c4c3821765c7c6821bd28222a6c7c68207b58228d5c7c68225398226c7c7c68205a1820b56c7c6820627821a91c7c68208988222c9c7c682199c8219e5c7c68218fe821cfdc4c3822979c4c3821a99c7c6821bcb821c24c7c6821543821a3ec7c68219c9821f20c7c6821ac4821bcbc7c682274182288ec7c68205d78228d1c7c68215bd822973c7c682019e8205a7c7c68201db821908c4c38218a5c7c6821ac1821c55c4c3822954c7c68202c0821544c7c68206038208e6c7c68224c88228d1c7c68215ad821620c4c3821691c7c6820ade821ffbc7c6820609821ac4c7c68224a982277cc7c6821d22821f7cc7c68218f7821fc8c7c6821ad382259ac7c6821caf82284ac7c68226f6822854c4c3821bacc7c6820342821a0dc7c6820bcc821cb6c7c68202cb820abfc7c68204c98220acc7c682037782186bc7c682022a821732c7c6821a4e822388c7c6821446822072c7c68216e8821b75c7c682016f821ea4c7c6822880822896c7c68218f2822875c7c682273082292bc4c3821799c7c6822730822930c4c382058bc7c6820d31821f95c4c3821518c4c3821634c7c682176a821a4bc4c38227e7c7c68224c782261cc4c38228e3c7c68208d882145fc7c6820546821446c7c682174b821991c4c382147dc7c6820212820899c7c6820530820835c7c682069f821507c7c6821537821a71c7c6820acd82176dc4c3821b2dc4c382186ac7c68208b9820926c6c58183821ee8c7c68219a2821d81c4c3821903c7c6820438821979c7c682157d8227dac7c682038282296fc7c68217cf821fb8c7c6820c6a821f40c4c3821733c7c6820746821707c7c6821dec821e90c7c682157d8220cdc7c682086d8228d0c7c682198d8229a6c4c3821c21c7c68218e1822010c7c682160382293ac7c6821671821785c7c6820246820351c5c454821e8fc7c68202b9822761c7c6820f00822058c7c68204728218d0c7c6821e63821fd0c7c6820294821a36c7c68215298217ccc7c682061982263bc4c382284fc7c68201d98217b9c7c6820a43821379c7c6821328821fc7c5c4168229d6c4c382079dc7c68215b4821fffc7c68215bf821cc6c7c68201c4821858c7c6820dae821f41c7c6820460822906c7c682174e8225a4c7c6820b2e821961c7c68214678229a1c7c6821407822566c4c3822007c5c40e82292ac7c68218c0821e4ac7c6820be6821e5dc7c68204e7821e54c7c68217c38229dac7c68211278224dac4c3821e53c7c682084382181ec7c6820e78821f21c7c68218aa821f1ac4c382188fc4c3821628c4c3821636c7c68216b5821f86c4c38216e0c7c68202db820934c7c68201ae820aeac7c682099f821a9ec4c38213e4c7c6821605821dafc7c6820471821f1dc7c6820c4a821ab5c7c6821505821a6cc4c382142fc7c682150f82247cc4c3821a93c7c682179f822935c4c3821c58c5c40b822061c7c6820c68822904c7c682078982161ec4c3820162c7c6820ba6821c86c7c68202c282078cc2c111c4c3820967c4c3821d95c7c682173d822981c4c38221abc7c6821536821fa7c7c68216cb8229d8c4c3821b5ec4c382090dc7c682014f820578c7c682043b82134ec7c6821a8c821aa4c6c5819c820a83c7c68205e9820cd3c7c6821474821efdc7c68201eb82189fc7c6820c7d822811c4c38201a8c7c682158d821b73c4c38219d2c4c382292ec4c3822918c7c6820c938225e0c4c382184dc7c68201d5820cbfc4c3821ebec4c38228d8c4c3822a0cc4c3821675c7c6821443821f80c7c6820728822986c7c6820132821588c7c68202858229edc7c6820246820343c4c3821793c4c3821983c4c3821363c4c3821c4fc7c6821454821896c7c68203c882133ac4c38206c4c7c68227cb82284fc7c68217a4822964c7c6820987821ba2c4c38213b3c7c68215e3821eddc7c68213f2821488c7c68202d8820b7bc4c3820a35c4c3821d87c7c6821d10821fb1c7c682176582204cc7c68214b88218c7c7c6820c4c821325c7c68205648207fec4c3821e89c4c3821991c7c68205f482171ac7c68205ec821ec5c4c38228f5c4c3821ae1c4c3821885c7c6820616820d9ec7c682034d8216edc7c68216c3822a2bc4c3821874c7c6821439821c64c4c3822a39c4c3821b2fc7c682039f821d61c7c682027382162fc4c382294cc7c682115982267ec7c682170b82198dc4c3821333c7c6821cfa821db4c4c3820936c4c3820170c6c5818982068ec7c6821e348225e8c1c0c7c6820c3c822919c4c3821359c7c6820243822984c7c682060a8221cfc7c68204768219cfc7c6821b63821fa9c7c6821656821a9fc6c581b2821e17c7c6820c16821aa4c7c6822a59822a62c7c682060c8214d4c7c6821d1f822951c7c68213b5822a3ac4c3820ce7c7c6821f7e8229a7c7c682093c821e0dc7c68201cf82295fc7c682145b82154ec7c6820258822a17c4c38214f5c7c6820696822769c4c38218d1c4c3822a56c4c3822461c7c6820999821a6bc7c682150e822a6ec4c382067fc4c3821a96c7c68215ae8217b4c5c44b820498c7c682167c821764c7c682137182157cc7c6821760822a42c4c3821f00c4c382197ac7c68205d6822395c7c6820240821b31c7c6821df78229b4c5c4738229adc7c68202ac821d1ac7c682083d82249ac7c682023f8229cac7c68205d8822062c7c68208bb821d0fc7c6821c48822944c7c682182c821fa6c7c6821626822902c7c6821a3b822974c7c6820ac682167ec7c6821cfd8228a4c4c3820424c4c38226b8c7c682016f821c97c4c3820a97c4c38206c7c4c3821769c4c3821c85c7c682019c8219bac7c6821348821b80c7c68220cd8229dec7c6821d62822532c7c682200e8228f3c7c68216ee822994c7c68209208229d7c7c6821f338228dcc4c3821583c7c682138c821ccdc7c6820cbb822703c7c68213808219f9c4c3820b73c7c68218a3821c50c7c6821d27822a4cc7c682030a8218eac4c382144bc4c3822a25c7c682027482175ac7c682091c822336c7c6821d348224b1c7c68207df82157ac5c452820bf9c7c682048f820623c7c68209ef821839c7c68206ef820a04c4c3821fecc7c6821f71822aa0c4c3820680c7c6821c478228f9c7c68214de82281ec4c38229b6c7c682098e822982c7c68216478229c5c7c682058a8223d5c4c382037ec7c68216a28228a3c4c3822a18c7c68206108207d2c7c6820633820dffc7c6820ab18228fdc7c682146f821f00c7c682023d822a3cc7c6820390820941c7c68205388215a7c4c3821ae4c7c6820255821532c7c682169d822a17c4c3821a41c7c6821977821a05c7c68217a5822494c4c38206fac7c6821b30821f64c7c6820291821dc7c7c6821e03821f67c7c68206be8219abc4c38218a3c7c68210d48224fec7c6821e948228d7c7c68229918229d4c4c3821afcc7c6821d48821fb8c7c68209a1821bfec6c581f9821fbac7c6820b6182146dc7c6821409822a45c7c6820709822064c4c3820690c7c68205de8225fbc7c68218d7822a95c7c6820384821a02c7c682036c82141ac7c68215608228e1c7c682055c821b37c7c68209ff821b62c7c6820a6782299dc4c38214a8c4c38219a6c7c6821f5d8228cec7c6820bba8214fbc4c38211f8c7c6821cc38229e6c7c68217da821d39c4c3821f0dc7c682091e8216d5c5c446820451c4c3821a39c4c38229cdc4c3821ec7c4c38217edc4c38228e4c7c68217c5821c49c4c3820a5bc7c6821710821efbc4c382161ac7c682010f82281cc7c682189f822023c7c682082f8215b1c4c38229c8c7c68201c8821ca2c7c68215ef821ce4c7c6821365821391c4c382083bc4c382132ac7c68201e3821bc1c7c6820263822a81c4c3821a42c7c6821db982290bc7c6821881821ed2c7c68205b4821c77c4c3822a46c5c436822ad4c7c682065d821013c4c38213b8c7c68205a6822907c7c6822975822ae4c4c3821572c7c682296d822a08c7c68213e1821dacc7c6820fa98223ccc7c682011c822ae2c7c68226f08229f9c7c6821c5b822abcc7c6821723822058c7c6820235821a01c7c6821927821e41c7c6820f41822236c7c682056e821f36c7c6821f158229b5c7c68219fb821bd6c7c68216e18228cac4c38229c9c7c682023a822b12c7c68202c4822045c7c6820b0e822a0ec7c6821745822a7cc7c68203a7821d2ec7c6821f848222ebc4c3820273c7c68220148228c5c7c6820fab822224c7c682022382292bc7c6821c8482291fc7c6820578821b10c7c6820a89821f32c7c6820264820390c4c3821e9fc7c68228dd8229f6c7c6821fc7822a33c4c38213ccc7c68201ac822a10c7c6821bff821df8c4c382132bc7c6821d14822515c7c68203578229fdc7c6820c58821142c7c68213d4822b03c4c3820512c7c682194c822909c7c68218db822020c4c3821763c7c68206fa822a98c4c38216d1c7c682116182286fc7c682063482189ac4c3821cf8c4c38218cdc7c682163b822932c7c68218d68218dbc7c6821d9882202ec4c3821bd7c7c68202fe821ed8c7c682043e821696c7c6821431821f52c7c682082c822b26c7c6820954821a97c4c3821ed2c7c682173c822334c7c68215ac821ff7c7c682286d8229e0c7c682177c822946c7c6820376821e83c7c68205e2821fc4c7c68207f8820fddc4c3821b45c4c38228d3c7c6820e94822b31c7c68214a3821f67c7c682064b8229bdc7c6821b42821cc9c7c6822a23822b51c7c6821a7582290cc7c682123d821f06c7c682017a821874c7c68214f582214ac7c682219d822889c7c6821901822961c7c682016e82177cc7c6820644821ef6c7c6820ca08224e5c7c68201b38228a8c7c6821c54821f66c4c3821e09c7c682141b822a64c7c68228f2822ab4c7c68229fa822a46c7c6821a26821bf0c7c68228d5822b12c7c6820b89821a88c4c3821953c7c682150a822736c7c68216f38219afc7c68216a782196bc7c68215438227aac7c682165b822931c7c6821cf6821f29c7c6822a0f822a70c6c5818582139ac4c38227a6c7c6820b5d8217b0c7c682059a82206bc7c6820508821a59c4c3821e9dc7c682197f822860c4c3822862c7c682192b821a3cc7c682081f822b50c7c6820a3d821a27c7c6820600820ac2c7c6820c2282296ac7c682221e822739c7c682144a8228e9c7c682073f8214e1c7c6821bec822accc7c68201e6821e1ac4c38206d9c7c6822924822a0dc7c6820c5d821df0c7c6821984822a7ec6c581b9821807c7c68201ed821972c4c3821b26c4c38202ebc7c6820638821c90c7c68215ae82298bc7c68202e3821a6dc4c3822addc7c68205fd820ac0c7c6821979822968c5c44a8219bbc7c6820409822b10c7c682074f821901c4c38215cdc7c6821e3e822b36c7c6821c3b822ac7c7c68202a0820840c7c6821cea822a58c7c682060f821d88c7c6821704821ecbc4c3822a84c7c6820ad9822825c7c68208d9822a95c4c38229bcc7c6821e998224cac7c68229a9822a27c4c3821612c4c3821641c4c3821c91c7c6821bd9822af6c4c3822a49c7c6821bb98229e8c7c6820650822853c7c6821b87822a42c7c68208a9822a02c7c68205e482289dc7c6821ecf822a06c4c382188ac7c6820360822b01c7c6820c4a822b29c7c68228c58228cfc6c581e48228fdc7c68228e9822b60c7c6821a098229a2c7c6820645820c13c7c6820aa882141ec4c3821f75c7c68206218207b2c7c6821660822ab2c7c6821a10821b45c7c6820670821e9ec4c3820bd8c4c3822aaac4c38215d2c7c68202c482261fc7c6820da68212ddc7c682188d822b55c7c68208e5821d55c7c68209108228e6c7c68206d4820818c7c6820454822a6ec7c6821ce8822b54c7c6820f588215e2c7c6821eb7822b00c7c68215ed821b07c7c68219e2821f22c5c46a822a8ec7c68214d28217e4c7c682169c822906c7c6820bde82261fc7c6820680822ba1c7c6821d458228ffc7c6822432822595c7c68213ac822923c7c68202e0821ce8c4c382082dc7c6820521821a55c6c581a1821c50c4c38229e3c7c68214f68215fac7c6821277821823c7c68204098216a3c4c3820652c7c682288f822a63c7c682208a8226e4c7c6820d0c82226ec7c68229ac822a1ac4c382064ec7c68229e58229f9c7c6821c0b822a9fc7c68211c88219d3c7c6821cc8822a15c7c682049a822b1fc7c6820416821643c7c682086f8229fbc7c6821416822b1ec7c68229ad8229e4c7c68211ff8224b3c7c6820c0b8217e1c7c682192e821d29c7c682082b8214a4c7c6820d8d822187c7c6820dd482285ec4c3822a76c7c6821db4822ae9c7c6820a60822409c7c682033f82143fc7c6821c87822af7c7c6820387821e8dc4c382299ac5c46e822790c7c6820a79822ab1c7c68205f18228b8c7c6821625821f62c6c581ff822b9fc7c68207a7821831c7c6821a2a822ba8c7c6822a74822b46c7c682010b821860c7c6821c04822914c7c68215e3822a1cc7c6821a49821efbc4c3822bd6c7c68216d682194bc7c6821ae6822b0bc7c68215f8822731c7c6820286821a40c7c6820ddb820e6ac7c68225f5822ad9c7c6821e70822956c7c68214938219b8c7c6820660821c87c7c68214db821626c7c68201ff82081cc7c6821e2382297ac7c682062f821516c7c6821b6c8229fbc7c6822a1b822bc7c7c6820a41822a12c7c682299a8229b0c7c6820bcf8228a4c7c682018d821f09c7c6822920822a3cc7c682175a822b58c7c6821503822ae5c4c3821431c4c3821924c7c68205878228dbc5c45d8208dac7c6821327821769c4c3822ab7c4c3821f7fc7c68202a9821d87c7c6822a7b822b8ec7c6821edc821fdac7c682295c822a47c4c3821b3bc7c6821c25822a91c7c68208b8821e11c7c68229338229efc4c38219e7c7c682021d8219bac7c6821333822a18c4c3821dd0c7c68217c8822c25c7c682018782138bc4c3821584c4c38217c6c7c6822b07822c1fc7c68209d3821436c7c682142d822619c7c6821abe821f4ec7c6820e3c82285bc7c6821a34822b84c7c6820e20821b02c7c68221f58226d8c7c6820e0082262dc7c6820b958215ffc7c68223d58226e6c7c68206f18220d3c7c6820708822364c7c6820c0e822567c7c682126b8223edc7c682067c8210d4c7c6820ed1822548c7c68206f682135fc7c68206d7821a18c7c68223c582281fc7c6821c95822302c7c68206a28224f9c7c6821bbb82217bc7c682071b820b9ac7c682244c822767c7c682044982146fc7c68224488225fcc7c6820e9c822800c7c6821eeb8222b7c7c6822191822496c7c68206598222b8c7c682208d8224ecc7c68214fa822722c7c68210f782234fc7c682069282266dc7c682182b8221bfc7c68207478226cdc7c6821cfe82250ac7c6821e1882210cc7c6820aba8226c9c7c6820422822b64c7c6820443822bc0c7c68206d38218b4c7c6820689822120c7c6820629821a18c7c68206cc822154c7c6820f9a8225acc7c68206838208a3c7c682172b822501c7c6820a268218f5c7c68203f4822ac0c7c68219e1821cbdc7c6821cf0821f89c7c6820a3682209ac7c682189b8225e5c7c6820c998210e6c7c682179f821f1cc7c68220bb8220e8c7c68206fd821387c7c6821d4f822582c7c682067782269bc7c682201d82245fc7c6820625822338c7c68219c6821fd1c7c68206e1822590c7c68215fb821d19c7c6820ce282204fc7c6822601822899c7c68206b182280cc7c68219468222c1c7c6821812821e32c7c6820fa1822453c7c682072b82256fc7c68213bc82198ec7c682229e8228a5c7c68204f6822a2fc7c6820c9282231cc7c68220b78221bbc7c6821098822c62c7c6820eb18225c6c7c6821c0f822317c7c6820e3282271cc7c682073a821402c7c68203fe821631c7c68211018226bcc7c6820f84821107c4c38215bdc7c68207b68222f8c7c6820454821c34c7c68203b9820904c7c68206aa820feac7c6821144821c5dc7c6821f3582257dc7c682074c822440c7c68206d8822c78c7c6820636821f16c7c6820eb2822bbac7c68211a382271fc7c68206ea8227f6c7c682145a822402c7c68213ce8216f8c7c68206818220d5c7c6820648820f95c7c6820a6c822733c7c682205c8229e6c7c68206a6821a89c7c6820725822039c7c6820ea58220e9c7c682254e8226f9c7c68221d18223d0c7c682136a822b8fc7c68219928226f2c7c6820d79821fcbc7c682081282235ac7c6821687822b28c7c682065f8215a9c7c6820737820cafc7c68206d282246ac7c68203ba821b9fc7c682179182276fc7c682041982200ec7c68203eb8229d0c7c682162d822b72c7c682081482254fc7c68203e9821f6cc7c68206dd82223ec7c682064d8222b9c7c6821830821afcc7c6821f83822c99c7c68206ca821183c7c6821daa822101c7c68208198226bfc7c6820714821c0ac7c6820a57822667c7c68206ba8226b0c7c6821b51822b26c7c68227138227f9c7c6820d44820dfbc7c68215928228fac7c682237c822715c7c68206bc821095c7c68207168226fdc7c6821f06822471c7c682207a8223f8c7c6820741822636c7c682112182245dc7c6820662821d4cc7c68206128223b9c7c682076d821d76c7c682150a821b1fc7c68215ea8225c0c7c68206d6821856c7c6820c07821ec9c7c682196b822960c7c6821762821805c7c6822405822441c7c68209de82189cc7c6821c1b8221d4c7c6820b5782158dc7c6822520822c7dc7c68225a0822772c7c6822854822857c7c682077b822853c7c6821212822887cac982210082262a8227b6c7c682114a822306c7c6821bad822323c7c6820763821937c7c682088e821514c7c6820ccd821414c7c6820cc48211fcc7c68206f8822cdcc7c68206e5822699c7c68207658222c8c7c6820762822b5cc7c6820d38821b3ac7c682075e822c59c7c6820c4e821eb9c7c682077d82208bc7c68206eb8215f1c7c682074b822c94c7c6820bd9822286c7c6821bf7822375c7c682217f8222b5c7c6822175822549c7c6820b6e821d39c7c6820701820927c7c6820855821197c7c68212548225cfc7c6821b2c822b5fc7c6820fd98227e8c7c68208ff820d2ac7c68207788212aac7c68221ec822bdfc7c6820787820f7dc7c682070e821030c7c6820793821febc7c68222e58228b6c7c6820706821c06c7c682211f822144c7c6820e8d821995c7c6820f0e822bbdc7c68211bb82241cc7c6820f7e822c4ec7c68207298225a0c7c68207208221dbc7c6820799822357c7c68207cd8221e7c7c6820e0082135bc7c6820cfb822734c7c68219948227d7c7c68207b9822967c7c6820a40821d2dc7c682154b82265ec7c682073082236fc7c68207b3821787c7c682237f8227f3c7c68207b4821e2ec7c6821c1582298dc7c68207ae8220ffc7c6820ddd820e99c7c68207b0822514c7c682213e822668c7c682105d821a8fc7c68223558226c4c7c6822170822725c7c6820d788223d3c7c682072e82104cc7c682110a822738c7c6820dbf8220abc7c68207c8822216c7c68211a58222edc7c68207bc821997c7c6820aba8226ddc7c682151c8220d0c7c682268e822c5ac7c68221d9822671c7c6820dd0822ac2c7c68207c6820f02c7c68208ec821aaac7c682073e821b3ec7c68207ca82151dc7c6822833822c60c7c682075f8208bcc7c682222f822267c7c682174e8224d3c7c6820f4b82260bc7c68224cc822c48c7c6820fcd822687c7c68207f2822c8dc7c68207e482262bc7c6822214822773c7c6820769821003c7c682184f82282ec7c682075c822c96c7c68220d4822cf2c7c6821da5822035c7c682266b822a28c7c68207c48224c7c7c6822780822cc7c7c6820ddc821582c7c68210ba822588c7c682078482265fc7c68208d0820fafc7c6820d3d822512c7c6820cfd8221a1c7c6820b39820f48c7c682175f82248ac7c68221b08222f1c7c6821a548226edc7c68223be822d29c7c6820dc08224a5c7c6821c818227bec7c68220ed822397c7c68207d9820c9bc7c6821566821b04c7c6820e98822c9cc7c682093d822a60c7c682098f821eb6c7c6820fa28223d7c7c68203d982096bc7c6820961821f78c7c6821ba082268bc7c682296c822a6bc7c6821bce821f43c7c6821bf782266dc7c6820803821b90c7c6821ead822ab4c7c6820e3a82255ac7c68216eb821d75c7c6820df3821d28c7c6821fa28224fdc7c6822369822534c7c6820ecb822934c7c682080e820ceec7c682294e8229d7c7c6820b13822b53c7c68208258210b8c7c6820c98820fa7c7c6822223822c35c7c6822816822cabc7c682118e8215dfc7c68218c1822a6cc7c6821bc6821f1ec7c6822977822a58c7c682227a822673c7c6820817822869c7c6820b8e8219eec7c6821e54822a39c7c6820ff6822634c7c6820caa821804c7c68209dd821c73c7c6821259822239c7c68225d6822c81c7c68224858226fac7c6821a9f822c1bc7c68229b2822b19c7c682082a820beac7c6820420821c5fc7c6820c648217c8c7c6820715822a73c7c682280f822acbc7c6820331822c64c7c6821735821962c7c68214b8821e6ec7c6820819821bb7c7c6821e60821f62c7c6820818821e3ec7c6821a4b821d7ec7c6821156821d65c7c6821f2a822983c7c682049d822073c7c68218418227e6c7c6820821820a32c7c68205bc822c12c7c6821b9b821bc6c7c682097e822481c7c68224178224e3c7c6820e188222b1c7c68225c8822c50c7c68207cc822d31c7c68212dc821873c7c6822611822667c7c6820573821e79c7c68215e08228c9c7c682055c822851c7c68207c3822106c7c682200f82297fc7c682084682279dc7c6820cbe821074c7c68208368223a4c7c682083f822739c7c6820fcb8222d7c7c68220a882263dc7c68207be821962c7c682222b822500c7c68221fb82279ac7c682127a822d2cc7c68207c1820b0dc7c68224d58225d6c7c68207e5822c5ec7c682084c8217f5c7c6821fbf822706c7c6821f9a8223dac7c682086282244dc7c6820de5821914c7c6820d0682273fc7c6822747822c4fc4c382270ec7c682274a8229f5c7c68222078227cac7c68219bc822519c7c682051b820577c7c6820bb9822b7bc7c68208868223cac7c68207dd822817c7c682056e821c21c7c682266e82287fc7c6820a6b8214eec7c682058882068ec7c68204f9822c10c7c68208758209a8c7c682278a822bf3c7c68224298226e9c7c6822a67822acdc7c6822822822c7fc7c682050f82163fc7c6820b8d8215d0c7c6821819821f6cc7c6821ac98229bac7c682076c821fe6c7c682040c822b00c7c682071f822b4fc7c68214d9822881c7c6820b498216d0c7c68218dc8219e9c7c682299c8229bec7c68204d6821134c7c6821cd7822c65c7c68204b0821b80c7c68207f7822650c7c682039a822b7ec7c682292a822be4c7c682169482227cc7c68207e6821d77c7c6820b998222bac7c6820464822971c7c6821f0b821fb6c7c68205278228f5c7c682052c821f7cc7c6821c36822cdfc7c68203c1821e89c7c682091d820c67c7c6821a14822daec7c6821a16822b4cc7c68207a68214aac7c6820628822a32c7c68208688223b3c7c6820460822b6ac7c68219e5822abac7c6821ce182290ac7c68206ce82291cc4c3822b9bc7c6821e94822b2dc7c68209c28229efc7c68205d1822a40c7c68220938227fcc7c68213c88228efc7c6821ad4822db5c7c68206ef821d8cc7c6820580821a83c7c6821a23822bd7c7c68209b38228eec7c68213cc821721c7c682053c822b55c7c6820561821ff8c7c6821e00822b43c7c68218a2822b88c7c6821c31822daec7c68203e88217a1c7c6820fd7821a2bc7c6820508822461c7c6822ca9822d5ac7c682169c822a59c7c68205648219a8c7c682081d820ddac7c6821673822a57c7c682245a8226e0c7c68229d2822a07c7c68222f6822cdbc7c68208fa8217efc7c6821a77822729c7c6820890822d2ac7c6821f46822341c7c68227a0822a05c7c68202fe822950c7c6820382821ca4c7c68204b582293cc7c6822507822d04c7c6820815820917c7c68204f3821706c7c68215d6821e74c7c68227228227d0c7c6821af8822cf9c7c682072c8219dac7c68218c9822aacc7c68202ea822af1c7c68204388218b2c7c682090a822bbcc7c6822987822a80c7c68214728220e0c7c6822b39822b71c7c6821898821afbc7c6820822820fcec7c68215308216abc7c6822848822ab3c7c6820587822a1cc7c68216468228b7c7c6820691822c06c7c68215ca8216f2c7c682046d821adfc7c6821c11822926c7c682086e82242ec7c6821e9d822ab8c7c6821bd8822a14c7c6820567821b2ec7c68216998217b7c7c6820371822c15c7c68205d1822b2cc7c682067b820751c7c682181a822a3ac7c682294d822c2ec7c682046b82196ac7c68204ad821d48c7c6820827821bb4c7c6821dc1822be7c7c6821d16822629c7c68202fd820813c7c682096a8227b5c7c6821e6f822abfc7c682093b822da7c7c6821750822a43c7c68204cc8218f6c7c682051c822c2cc7c682087b8223e7c7c6820b2f82190fc7c682043b8228eac7c6820f968222bec7c6821a22822c89c7c6821e8b8220bdc7c6821465822c4cc7c682057e821abac7c6821f2d822d71c7c682037082160ac7c6820cff820de4c7c682055a821835c7c682086b82124fc7c6821d7e822a33c7c6822a1e822b07c7c6822988822e0ec7c68213ff822ac4c7c682290e822936c7c68214bb822b6dc7c68216458219afc7c68204578214b6c7c682067a821fd0c7c68205b58229c1c7c682045d822dafc7c682149f822dcbc7c6822b44822b95c7c682172582298ec7c6820fbf822b48c7c6820e5f82106ec7c6820db482210ac7c6820881821b05c7c682044282160ec7c6821bb9821f8ec7c6820492822e31c7c6821e07822a7dc7c68218eb821bc0c7c682091f821b2ac7c68203228206e8c7c6820827822abec7c6820389822919c7c6821b508229a5c7c6821a44822b5ac7c682034f822be1c7c6821c89822d16c7c682143a822aa7c7c6821aa5822adfc7c6821e8c822af1c7c68202ec82198fc7c682038582177dc7c6820551822bb3c7c6821587822b29c7c6820521822863c7c68213a6821b1ac7c68213bf822990c7c6821b89822b2bc7c682149e8218dfc7c68206da822adac4c3820538c7c68206af8206c3c7c6820a39822301c7c6821578822ab6c7c68208b18210cbc7c68221c782225ec7c6820576821b84c7c6822aa2822dd9c7c682252f822858c7c682220b8227e6c7c682094e822835c7c68208b8821d9cc7c68211c382207ec7c6821b8a821d67c7c6820831822abec7c682059e821dcfc7c68217118221cbc7c68208b5821125c7c68213c5822d6bc7c682059d821714c7c68205c6822b16c7c6821601822044c7c682060b821d80c7c68208b3822becc7c6820628822d62c7c6822ba0822ba6c7c6822896822a92c7c682298f8229c2c7c6820c30822b33c7c6820598822963c7c68229f3822be2c7c6822581822729c7c68214bf821d43c7c68217ba822964c7c6821aff822876c7c6820764822ac9c7c6821779821ddac7c68205b7822519c7c6822650822cb1c7c6820c278228fbc7c68216f0822e7ac7c6821b2282274dc7c6821b5d8229e0c7c6821efe822c1ac7c68215f0822a11c7c6820635822814c7c6822c24822d81c7c6820f5b821b4fc7c68205d5822bb9c7c6820fa38226e1c7c68205d5822e16c7c6821d74822acac7c68205f3821a81c7c6822b69822e04c7c68205a1821ac2c7c68206098207efc7c682061d82139fc7c68205a6821c5cc7c682116d822794c7c68205f2822bcec7c682062a8229b8c7c68205da82060cc7c68227e7822ea1c7c68214d482295dc7c68205af821eb7c7c68216978229a2c7c6821400821efdc7c68209858218b9c7c6820f3d821131c7c68219ab822e65c7c6820611822ad0c7c6820941821b61c7c6821ca382264ac7c68229b1822e26c7c6820801822d79c7c6821922822c8fc7c6820e3882226fc7c68218a7821acfc7c6821a53822e84c7c682115082218cc7c6821bac822ea9c7c682079c8214c5c7c682292e822e8fc7c6820661821f0ec7c682157b821fccc7c68208c8822281c7c6821bd4822ad3c7c6820861822048c7c68218c28229a4c7c682239a822e70c7c68222ad822486c7c682066d8206f9c7c6821cf9822e2cc7c68209a582182ac7c6820845821e8ac7c68214fd821b19c7c68219ef821c2ec7c68206de82070dc7c682083e821af0c7c6820eae82226cc7c6821e738226cac7c682297a822ea2c7c682164082244ac7c6821536821fc5c7c6821ea182239cc7c6821965822493c7c68214f7821ebcc7c682063f82294bc7c6821d0e821f32c7c6820a0f8225b5c7c6822c33822ea9c7c68209f6822bc1c7c6822cb2822e9ac7c68214ec8228e6c7c682287a822c43c7c682065382082fc7c68208ea822d32c7c6821560821653c7c6820b21820ffec7c68208cf8221cbc7c682181782185bc7c6821dfc82271ac7c6822bbc822e40c7c682156a821f25c7c6821902822e62c7c6820583822ad7c7c68228c3822accc7c6821f61822914c7c6820e7f82219ac7c6821fdf822e1bc7c682131e822cfec7c6822b45822bddc7c6820657822e4ac7c682237f8225b0c7c68222fb8227c1c7c68227af822c0ec7c682069782297bc7c6821d35822d30c7c6822842822981c7c6822db5822e1fc7c68216bc822e50c7c682077982135cc7c68208de822036c7c6822b87822bf2c7c68206d5822a93c7c6822ac7822deac7c68206fe820c03c7c6820b4a82256cc7c6821870822d75c7c6821550822a3fc7c6820901822676c7c68218528221bac7c6821a17822687c7c6821a8d822280c7c6822196822346c7c6822eb7822ebfc7c68229b4822a99c7c68206ed821fb2c7c682114682270bc7c68206e28229c6c7c68224e982269fc7c6820dfc822d64c7c6822680822821c7c6822e63822e6ec7c6820958822e15c7c6822350822c79c7c682071d822e1bc7c68208a0822630c7c68206ee822c1cc7c68216308221aac7c6821ec38221a5c7c682088a82208ec7c6821a4282293fc7c6821c78822da9c7c6820a18822a19c7c6820702822bf5c7c682089c82221bc7c682090b821267c7c6821e9c822ce0c7c68222d282243ac7c6822271822a86c7c6821ac0822b5ac7c6821355822bf4c7c6820916822d9fc7c68208a18223aac7c68212a28225cec7c682242f822534c7c6821e3a822acfc7c682233a822414c7c6820b71820d4ac7c6820ec58220b4c7c6820703822d8bc7c68210f0821166c7c682291f822ab5c7c6820924822723c7c6822aef822ec5c7c682219c8226d1c7c68223518227dbc7c6820914820ef5c7c68229e3822adec7c6820913820eb8c7c682105d8218e4c7c68208ac822202c7c68208af8225dfc7c682209d8225b2c7c682072c822de1c7c6821b54822983c7c6820727821f3cc7c68223e0822602c7c682141782160ec7c682114e822203c7c6822478822aafc7c68221eb822418c7c68225e48228aec7c6822141822e4fc7c682071f821851c7c6821822821b9fc7c682074f821838c7c6821c35821f05c7c6820ded820ffcc7c682115a821f87c7c6820b0c821abbc7c6822988822e1cc7c6820740822bddc7c6821303822711c7c6822a5e822b08c7c682205582283ec7c68228e58229ecc7c6820931821301c7c6821ee7822c1ac7c6822860822dafc7c68208aa822802c7c6820749822a9ec7c68208b482277ac7c68207318214bec7c6820734821c31c7c6821629821964c7c6820f11820fd3c7c6821661822e7dc7c68227b7822bf8c7c68208b2821b44c7c6820746822e4ec7c6821bd582284ac7c682073f82247cc7c6822ad7822e87c7c682073b821abfc7c68216a3821ae4c7c68208ab821b06c7c6821909821e40c7c6820753821e95c7c6820a3c822bb5c7c68224ea8228e2c7c6821447822d26c7c6820db8822527c7c6821a0d822bccc7c682278f822afdc7c6822354822560c7c6820e5a822ca6c7c682093e820958c7c68220be822ed6c7c682176a822948c7c6820782821af4c7c68208e182228bc7c682076a821fa7c7c682077a822bb0c7c682077782290dc7c6822a54822bfac7c68223ef822da4c7c6820aff82152cc7c68213128215b3c7c68220028224e2c7c68217b2821da6c7c6820948822015c7c682095e8226c6c7c682225982244fc7c6820949821d89c7c68218d4821b39c7c6822b84822d8ec7c682249c822995c7c682091a82147fc7c68207ab8228a9c7c6820cfc8212cac7c682155c8221dbc7c6821ac7822eb5c7c6822815822e0bc7c68208dc822d99c7c68211b9822865c7c68213f7822a14c7c68224b2822f66c7c68208d0821cbfc7c6820a27822a8bc7c6820f8b822c5fc7c6821746822426c7c6822008822e27c7c6822a4f822bd8c7c68216f8822beac7c6821dea822401c7c6821ef1822357c7c6821fce8228fcc7c68208e0822158c7c68209668228acc7c68221c4822c5ec7c682208f822784c7c6820971820f0bc7c68207b8822ab3c7c6821129822424c7c6821c88822ed1c7c68207a6821f09c7c6820db9822f7ec7c6820977821866c7c68207a7821b65c7c6821fd98222bdc7c6822be3822f63c7c6821dee8227d3c7c6820975820a9cc7c6821409822e46c7c6820912821205c7c68226b28226cec7c68207bf82083ac7c682119582287dc7c682125b822f3fc7c68208f28221cac7c6822504822632c7c6821f90822333c7c68222ab822dfec7c6820e7b822e15c7c68220a3822d83c7c6821ec88229c6c7c68208f882156cc7c6821919822e4ac7c68208f5820b8bc7c682148d822d73c7c682194a822097c7c682098f822d9dc7c6821eed822314c7c682136d8224dcc7c6820986822cc8c7c68207d4822bdec7c6820cd98222c2c7c68207ea822a4dc7c68224e7822f02c7c682098b8217e6c7c68207f38216e1c7c682232982247ac7c6821dae822c39c7c6820923820b1dc7c68207d3821721c7c682128a8224c9c7c6820a8e822d01c7c6822de3822eadc7c68226ee822db0c7c6820bbf8224ffc7c68207f5822b14c7c682092d822b63c7c68209a682261cc7c6820ee3822596c7c6822382822ec1c7c682243d822ceec7c68209a0822c57c7c6820ae7822fa0c7c682165d822f32c7c68209ae82147ec7c68209ad82244bc7c6820afd821cd3c7c68225e3822cedc7c682292d822bdcc7c6820f4f8211e0c7c6821853821860c7c68209b48220c6c7c68207fc8229d5c7c6820a3e8226f6c7c6820937821ed0c7c68209b9822d24c7c68207df822f5dc7c68207de8216a8c7c6820b8a821a6bc7c682111f8225a9c7c6821698822deec7c6820f198221afc7c6820974821c96c7c68209398225aac7c6821c2f822e9bc7c6820942821692c7c68209e48224bcc7c68207f1822ea0c7c682093a8220c4c7c6821479821871c7c6822b94822d8cc7c6821774822a78c7c68209be8210aac7c68219dd822182c7c6821f3b822f3ec7c68218fd8224a0c7c68209d1822cc2c7c6820ac3820b7fc7c6822ea8822f1dc7c68209d5821206c7c682219082244ec7c682095582208ec7c68207fb822063c7c6822a48822bb3c7c682144a822bc6c7c6821d1d822c10c7c6821c8a822c83c7c6822118822460c7c682149c822ebec7c682094f821318c7c68207fa82152fc7c6822a09822e24c7c682081f822e2fc7c68213a982244dc7c6821dc38228dac7c6821375822373c7c6820e4e821ccfc7c68209e88225a7c7c68209e782248ec7c6820ba28217c0c7c682096e8218f8c7c6821693821e4cc7c682097882263cc7c68225528225dec7c68220bc8225dcc7c6820b3c8211f5c7c68225db8226fcc7c6822b06822b4ac7c68209e0822762c7c6820972822458c7c682082e822c30c7c6822e59822ed7c7c6820974821ffdc7c6820f288223b4c7c6821da8822996c7c6822639822fa1c7c682129982252cc7c682187e822b02c7c682115f8212e8c7c682243f822d36c7c68209f5822546c7c6821ae3822fa2c7c68209948225e1c7c6821324822bd6c7c682257f822852c7c6820830822be2c7c68220d8822396c7c6821be78222d6c7c682121c82134bc7c6822bef822d6fc7c68229f7822c9bc7c6820d18821de1c7c6821a08822a73c7c68209ee8227a4c7c6821306821cb2c7c68218d9821dbac7c6822463822d38c7c68209838222dfc7c6821c74822e47c7c6820c4e821e64c7c6820f71822ed1c7c68227fd822d87c7c6821cd28224d1c7c68223fe822759c7c6820997822f36c7c682083582297dc7c682096a822e13c7c68216dd822dcfc7c68208408213b3c7c68217f1822f76c7c6820a20822702c7c682085a8217f2c7c6821113821c1ac7c6821a09822ae8c7c682116482298cc7c682085f822b67c7c6820865822e34c7c682098a822925c7c68221a6822850c7c6822480822fb8c7c6820a10822f28c7c68221f0822fecc7c6821adc8221ffc7c682101f8224a8c7c68209b1822452c7c6820866822a16c7c68208588208e5c7c682084b822adcc7c6821ba3821db5c7c6820a23822698c7c682087a822be4c7c68209bc82242dc7c6821f94822d80c7c682086d822a98c7c6820a2a82279dc7c6820ec2821d47c7c6821dc2822c0dc7c6820a3e82210cc7c6822418822c93c7c68222628222f5c7c6820bdf8222c3c7c68214fb822a2ec7c682087d821341c7c682193c821a3dc7c6822610822b77c7c6820f8a82234dc7c682105382261ac7c6821495822d4dc7c6820b90822c88c7c68209ac820aafc7c6820a31822562c7c6820a39822591c7c68213158225a3c7c682087f821aa2c7c6821a468224eac7c6820894822f3cc7c6821a9a8227f5c7c68227a382288fc7c6821c7b82240fc7c6820a42822ce4c7c6821413822b24c7c6820a9082119cc7c68209cc821c8ec7c68220aa822cd2c7c68209f8821351c7c68216398229d9c7c68209d28223e5c7c6820892822244c7c6820b6f821fe6c7c68220e5822c72c7c6820a458213f5c7c68209cd823043c7c682281082295ec7c6821b25822eb4c7c6822405822e02c7c68217eb822dc0c7c6820bf2821554c7c6820ede821bc9c7c68218bf821da9c7c682155a821b39c7c68217b6821f82c7c6822467822f99c7c6820a4b8223a3c7c68209e282223cc7c68224a2822ccfc7c6821858821eadc4c3821fd8c7c68215d1821c99c7c6821c438221d2c7c68228d48228d9c7c682125f822238c7c6820bdb823084c7c682262d822a1fc7c68210708222a1c7c6821d7b82237ec7c68221bd8221cdc7c68222c0822c9dc7c6820b338221e7c7c68218bd822770c7c6820c11822a66c7c6820f598224cfc7c6820b26821261c7c682192d8221ccc7c6820a2982121ec7c6820e04822f84c7c68215f4822e35c7c682208d82258ec7c682160c822bb8c7c682176f8220b3c7c6821b68822ecac7c6821b24822132c7c6822897822f93c7c6820bd582256ac7c682125482282bc7c6821dbc822837c7c6821110821ffac7c6821130821ceec7c68225fc822e75c7c6821e048221fac7c6821ac8822a54c7c682117082238ac7c6820c0882216fc7c6820b40820bc3c7c6821ef38220f3c7c6821d3a822609c7c6820e5d820f5fc7c682269a823045c7c68212c5822043c7c6820f1782262ec7c6821a8a821e3dc7c68212e582134ac7c6822d22822d46c7c68212ed822d3dc7c682179c822779c7c6822624822659c7c68211b68211dbc7c682102d8227f4c7c6820f20822812c7c682120282171ac7c6821a3f822095c7c6822b25822d20c7c6822832822c63c7c68220008223c9c7c6820fb0821b58c7c6821f2f822d28c7c682118e821cd9c7c6822535822cb4c7c6821a86822cb9c7c6821b40822cf8c7c68226c28228b7c7c68220f8822cd6c7c68222188222e6c7c68215d8821820c7c6822004822c82c7c6820d5b821296c7c6820bf5821a68c7c68225738226c5c7c6822686822fe2c7c6820ae3822291c7c6820b95822182c7c6820d3a8226f1c7c6821272822541c7c6820f4482302ec7c6821be18223eec7c68210d5822737c7c682195e822137c7c6820b6a821c75c7c6821252822c69c7c6820d04822f2dc7c68212f8823094c7c68220b7822da2c7c6821217822f2cc7c6820bb5822143c7c6820c05822d33c7c6820cf9822c3fc7c68220d782273fc7c6820ad4822d13c7c6821313821369c7c68215258220f9c7c6822d1d822d8dc7c6821a688222ffc7c6820dcf8211c1c7c682121f8219bfc7c6822691822ee9c7c6820b43821b7dc7c6820eaa821bbdc7c682108e822d66c7c6821bc4822f71c7c6821e50822572c7c6820a75821a3fc7c68220aa822757c7c682130b823012c7c682118a822384c7c682261382269ec7c68223a6822745c7c68224708225b4c7c6820acf821968c7c6821f9b822fcac7c6821289822e76c7c68223a182267cc7c6821952822267c7c68211b48220e6c7c6821052821b8fc7c6822c53822fb4c7c6821af582235fc7c682130c822f09c7c6821a248224acc7c6820b9e820dc1c7c6821136821274c7c68219808224b4c7c682215e8225fac7c68211c382224ac7c68210098230b8c7c682111c8224bec7c6820e7f822642c7c6820d7082287cc7c6820cea8213f1c7c6820af5820e7bc7c68210e38222c6c7c68212a1822cd2c7c6822eda8230b2c7c6820e1482304ac7c68221f8822fe9c7c6821f5b82283bc7c68219fa8225b9c7c68226168226d4c7c6820e49822fdec7c6822e68823021c7c68219d88227adc7c6821803822d7cc7c6820f5482253ec7c68210b78221a1c7c68215208223c7c7c68210b2822086c7c6820dfb8228c7c7c6820ae2822395c7c68220a7822fa7c7c68221418221ccc7c68211b0822362c7c6820f0982270cc7c682275e823117c7c6820d28822f40c7c6820b7a822ca1c7c68212f28230d6c7c68228c1822e5ec7c6820ec58228a6c7c6820b99820ed8c7c682113a82254ac7c6820ba9821947c7c6821c6a8223adc7c6820dae822d6bc7c6820a7182135bc7c6822d06822d1ec7c6820ca982240ec7c6821fbc822f90c7c6820b23822d89c7c6820aab822cbcc7c6822032822c73c7c6820dd4822ba3c7c6820f2d821be0c7c68212358221c9c7c682275a822f39c7c6822bae822c43c7c6820dd18223fbc7c6821a958230bec7c68210b68222bbc7c6820b72822094c7c682246f8224b5c7c6822b3f822b82c7c6820a94821b4ac7c6821f9d822874c7c68208bb822b05c7c68214ab82310cc7c68211c6822f30c7c6820f7f821dd2c7c68210b4822890c7c6820e97822dd4c7c68211a78226d4c7c682238c8225f0c7c68211d4821f07c7c6821d338227fdc7c682126a822470c7c6821004822507c7c6820e4c822321c7c68212f88224fec7c6821c168221e2c7c68221098228bbc7c6820e5e82274fc7c6821be4822d03c7c6821154821bf6c7c68220ca82234dc7c6820bfc82113bc7c68222858225adc7c6820ff7821cdac7c6822834822a61c7c6820ddb8215c8c7c6821b05823116c7c6821cba8221b2c7c682233d823110c7c6820da48220a0c7c6821cd5822333c7c6820e45822fe5c7c6820ff4822da0c7c682120e823030c7c6820c51820fd3c7c68225e2822664c7c6820e89821888c7c682227d823013c7c6820f57821063c7c6820ed98230dbc7c68212368230aac7c6820d01820f14c7c6820dbd82216bc7c68211ba822cc3c7c6820d968210dec7c68221788223e6c7c6821eec823080c7c6821d2f822707c7c6821eda82244ac7c6821c32822fbdc7c6820a2c823020c7c6821224822581c7c6821ef08225f4c7c68212da822679c7c68226b482306cc7c682191182306dc7c68223018225aec7c6820d7a822846c7c6821cbd822fa9c7c6821b54822e2ec7c682106d822381c7c68224bc8224d1c7c68220fd822282c7c6821dcc822188c7c68217ca82292dc7c6820bd082128cc7c68225bf823095c7c6820d5f8225d0c7c6820f8a821beec7c68217d08219aac7c68210c582162bc7c68215f682272dc7c68222b8822feac7c682115e8230cac7c6820d458229b7c7c6820de4822351c7c682143482226ec7c6820cc2821286c7c6820dc482226cc7c68226d9823137c7c6820d8d82108ec7c6821d98822997c7c6820be88224e0c7c6821263822f75c7c6820b04820f67c7c6821e4f822ba3c7c6820c8e821d17c7c68212df82311bc7c682128e8230bac7c6820dfc822195c7c682122f823157c7c6820ad08230c9c7c6821e2a822843c7c6821b32821e42c7c68223cf822723c7c68223648227d4c7c6820bca8226a2c7c6821a2b821c94c7c6820c8f822cc8c7c6821a6582277dc7c6821319822779c7c682261b8226f7c7c6820dab822c42c7c6822766822f24c7c6820ee58221b2c7c6820e57822d55c7c6821957822cbec7c6822c6e822ca3c7c6821302822618c7c68210e8822442c7c68217f7821a51c7c6820ebd821e58c7c68224bf82285dc7c68211c4821306c7c682224d822765c7c68212ec821ad1c7c6820c21821029c7c682166882226bc7c6820a1d8221e3c7c68225b7822cf6c7c6820ec7822124c7c68213218221f4c7c68211f1822fb9c7c68218378221b1c7c6821f54821feec7c68211a282264cc7c6821f0a82275fc7c682113a82142ac7c682279b822e9ec7c6821176822781c7c68210df822f04c7c68226a9822f08c7c682119d821905c7c6821ae9821ecec7c682129f822104c7c68225828230fbc7c6820a4d8223d1c7c6820e978219edc7c6821ed48225a2c7c68211ae821430c7c68224fb822e09c7c6820f3882275bc7c6822821822c54c7c68212c08226abc7c68214ea821dc8c7c682131d8230a2c7c68224f0822bb7c7c6820d9382106dc7c68211c5822695c7c6820e99822c70c7c68209fe821aeac7c682276e82278ec7c6821176821fd5c7c68211818223f4c7c6820a9f822cf7c7c68225fd823191c7c682302f82308bc7c6820b7482105ac7c68211fe8212efc7c68215c582297bc7c6820c9c82310fc7c68212e18220e1c7c6820c94823133c7c6821c1b8223b4c7c682245a822546c7c682207c823050c7c6820e6182272fc7c6820ccd8219ebc7c6820fad822c48c7c6822755822d3ac7c6820a82822756c7c68212d5822260c7c6820dd68230ebc7c6820f88821de6c7c682313982318bc7c68211d5821d93c7c682127a823090c7c6820b978210fdc7c682274382310bc7c68221c082272bc7c68210588225acc7c682129582247dc7c68212b18223d2c7c6820fb382238ec7c6821d08822b80c7c6820fb882225ec7c6822116822ce7c7c68212a282253fc7c682264c82318ac7c68212988214c8c7c68220d9822f6ac7c6820a8d821cdec7c6821d7d822693c7c68223c18231b6c7c6820d2a822139c7c6821414821ef9c7c6820e308213d9c7c68211f382250cc7c682130e822171c7c682110e822d7dc7c6821a76821df5c7c6820f01821300c7c6821c3282314ec7c682226b8222e0c7c6820a6d8231cec7c68212f9822065c7c6822393823067c7c68211ef82301fc7c68217fc8228adc7c68224a7823111c7c68225068225d7c7c6821bdb8220fec7c6820f4982304cc7c6820ff5822552c7c6821025821236c7c6820deb822c91c7c6820df7822ef2c7c6822dc6822f96c7c6821d238226bbc7c682257782272fc7c68224398228dfc7c68219e082232bc7c6820dec822b5cc7c682212b8230fcc7c6820f5e822caec7c6821da7822370c7c68223a282279fc7c6820ff18231c2c7c6821fe7822658c7c6820ab0820db2c7c6822f61822fa4c7c682233e822703c7c6820ea5821795c7c682114382226ac7c6820dad821473c7c68211b88230c9c7c682106a82279ac7c6820c188231a1c7c682114d8222bac7c682272a82307fc7c68221c48230dfc7c6820c3f8230a1c7c6822d1c8231d4c7c6821475821cd9c7c6820f2b82201bc7c6820d90822acec7c68218dd8225f6c7c682180b82274ec7c6820ae58230e6c7c6820d26822709c7c6821287821e48c7c6822fc78230fac7c68212698230a9c7c6820db6822197c7c68221128227edc7c68211ee822eb9c7c6820e908231b1c7c6820b87820c04c7c6820d15822681c7c682115a822f77c7c6821856822c6ac7c6820a0e821798c7c6820ab4822873c7c68223a78228b5c7c6820c878222fcc7c68210138226f3c7c6821bcc823231c7c6822c40822f81c7c682204982315dc7c682211482315ec7c6820f6d822faac7c68221c78223bac7c68210eb82181dc7c68212858226acc7c6820fb18211bdc7c6820fd5821026c7c682131a822452c7c68226e48227eec7c6820bf28220e1c7c6820a5182232dc7c6821f9a82267ec7c68219a9821c36c7c6820e0d822c54c7c6820c0e8220e2c7c682246982270ec7c682221882259ec7c682121d821920c7c6822df88230cfc7c68211fd8223dac7c6820e2c8226efc7c682212e8221b3c7c6820bce8221fec7c6822433822b37c7c682223c822cc4c7c6820f8082239ec7c6820f0a82276ac7c68212a7821399c7c6820c33822229c7c682221b822878c7c6821287823219c7c68223ae823257c7c682105f82289ac7c6821fd6822199c7c68210cf821ad8c7c6822c5d822f8dc7c6820d58822572c7c68226d78227d9c7c68221ec822c44c7c6822ce1822ce7c7c6820aa58222f7c7c68211fb823203c7c68221f7823153c7c6820de8823159c7c68226d3822f1bc7c6821d9f822f21c7c68211228221bcc7c6821b0882302cc7c68210208231b3c7c68210b182303ec7c6820d3c823211c7c68210238231e3c7c6821a9b82263ac7c6820f89821077c7c6820fba822726c7c6821078823144c7c6820be6822a1fc7c6820e13822407c7c6821419822a28c7c682120d822640c7c6820f6682317ec7c682229782312cc7c682283d822d2ec7c6821102822435c7c6821acb8224f7c7c6820ef4822d39c7c68215ce82216dc7c6821649823200c7c6820eb78224f7c7c682100c821797c7c68214fe82258dc7c682283c823150c7c6820fc6822dc6c7c682224582265bc7c68210dc821dcbc7c682248b8231d8c7c6821684822a4dc7c68223bc823292c7c6820ee9822fc7c7c6820d9f82109ec7c6822243822ca7c7c68225258230d3c7c682110a8225a6c7c6821aa18224e1c7c6821451822457c7c6821705821a73c7c68222a78223d9c7c6820fc58228c6c7c682206282237ac7c6822d66823297c7c6820f8e82319cc7c6820fd48231acc7c68219a1822cf3c7c682125d82255dc7c68210cd8225d8c7c68228698231dcc7c6820a08820d50c7c6820b84821148c7c68216c5822e47c7c6820b53822763c7c6820f2e822042c7c68227d882300bc7c68219f08227a5c7c6820f1b821275c7c68221fc8228a7c7c6821f5e822698c7c6820ec1821f74c7c6822934822d41c7c68218b5821ffcc7c68221598230b5c7c6821223821473c7c68211d1822d0ac7c6821b498220c9c7c682180e822f15c7c6820eda8223afc7c6820b558226e9c7c68228c0822b4dc7c6820cb8822e8dc7c6821090822d37c7c682128282149cc7c6820cdf82246ec7c6821104822cbcc7c6820aca8222a4c7c6820ce8822799c7c68217dd8223c9c7c682158b822861c7c6820f3c8221f8c7c6820cd8821bc2c7c682293f822a16c7c6820d9c8224ebc7c68224cb822671c7c682205f82234ec7c6820bae8222e9c7c6820c35822f88c7c682104f8231ccc7c6820e6f8221dac7c6820ca2821bdcc7c6821a69822198c7c68211fa8228b0c7c6821eff82320bc7c6820f2a82273ac7c6820b36820faac7c6822ce9823190c7c68213eb82238ec7c6820a288226a3c7c6821da2822738c7c68230178230ddc7c6820b348220b3c7c682274c8229f5c7c68221b582279ec7c6820b088221a0c7c68211e082243bc4c3821076c7c6820ff3822cd3c7c68212c8822fe6c7c6820cf28225c9c7c6820fee8218d3c7c682278c82321bc7c68224d7822f95c7c682214b822867c7c6821e3f823106c7c6820f5c822139c7c682250d822df0c7c6820d1e821827c7c682284e822cd5c7c6822c8d82323cc7c6820f54822744c7c68222e482260bc7c68215dd822d9fc7c6820ff8822179c7c68210a1821eecc7c6821d58821ed7c7c6821f7d8232d5c7c682131b8228a0c7c68221da822c3dc7c68208c4822e42c7c6822841822d70c7c682278b82286bc7c682131c823002c7c6821308822843c7c682100182222cc7c6822472823152c7c68211118231cbc7c68209e9822327c7c68214d3823081c7c6822446823162c7c682275f822d90c7c6822052822ffac7c68221ca8230c0c7c6822801823164c7c6821141821dc8c7c6821027822084c7c6821d40823128c7c682122982219cc7c682246a822f48c7c6820d37822d9ec7c68223ce8232a3c7c6822774823173c7c68210c28232efc7c68229a8822f1ec7c6820f1f8232d2c7c6820e43822943c7c6822498822ec7c7c6820b6b822d5ec7c6821228822742c7c6821260822827c7c6820ceb8221ebc7c68212a6821c13c7c68211c7822d37c7c6820bcd8226b9c7c682305f82307ac7c68225188226f4c7c6820fbc823120c7c6820aa182289ec7c682127b82262bc7c682245182304dc7c6820ff0823203c7c6820e48822f9bc7c6822643822d53c7c6822b938232cdc7c6820dde82122dc7c6821783823137c7c682166f8227e3c7c68211f6822219c7c682223a82313ac7c6822146822d18c7c6820f6e822f2ac7c6820d948212fbc7c6820f1d8222f7c7c6822d05822d44c7c6821247821415c7c6821a5f821e48c7c68224098225bbc7c6821f688224cec7c682186e82192ac7c6820eff821677c7c6820aa0821129c7c6821ea1822652c7c682122b821b99c7c6821e5f8226f7c7c682242f82297ec7c6820b0b821396c7c6820de1821eb5c7c682231d822c66c7c682119f823337c7c6820ee38220c2c7c68222df8225e9c7c68214e8822801c7c682247782277bc7c6820df68224b4c7c682124782288ec7c68220f68230aac7c6820f6f823089c7c6820cdc82259cc7c68212018223cac7c6820f18822134c7c6820e628214a6c7c6820b46822d11c7c68230e8823212c7c6821035822462c7c68210ac822565c7c6822cb7823099c7c68210fc821d46c7c682224b822579c7c68225698230b9c7c6821c8182330ac7c6820f10822222c7c6820e838226e3c7c682128a822302c7c682119c822bd3c7c6822da08230fdc7c68226f28230cac7c6820c2d820fa0c7c6820d5a8220efc7c68211ab822445c7c6821b818231d6c7c68225f8822912c7c6821d3e8220c0c7c6820bf6822bf0c7c68224c8822cfbc7c6820e6a82326cc7c6820b02822f9bc7c6820ce3821857c7c682191b8231f5c7c68211b08216fdc7c682259b8230acc7c68210d8822ebec7c6820b9b82209cc7c6820f958218a1c7c6820d8c821d6bc7c6820e52822249c7c6820fe7822cf2c7c68226d1822ff0c7c6820c09821514c7c6821b9c8227a2c7c6820e77822473c7c6820a8d823114c7c6821d41822372c7c6820de38210e7c7c6822757822ceac7c68210b0823152c7c6820c348232b2c7c6820d428230e6c7c68215b8822092c7c682274082324dc7c682118b8230f6c7c68230a7823282c7c6820dc582334cc7c6820def82258fc7c6821043822df2c7c682285f823287c7c6820aa4822563c7c68212ab8220bec7c682209b82309dc7c68225108227b3c7c6820a4c82239bc7c6820b20820b2bc7c6821caa823309c7c682164e823150c7c682254d822d93c7c68210f3821dfac7c6821167822ebbc7c68212f7822d4ac7c682305b8232adc7c6821337822c5cc7c68218cf8228b4c7c6822503822b0cc7c6821fe18228d2c7c6821132822c71c7c6821552821f2cc7c6821317821d47c7c68211858227b0c7c6821b2b823109c7c6820c12821a20c7c68210ae822d69c7c682115782321cc7c68229f2823204c7c6821f548230b3c7c682218b822d53c7c682110d8230d0c7c6820f398226d6c7c68227568231aac7c6820c4d82177ac7c6822326822d5bc7c6821b1282214ec7c6820a66822f4bc7c68210cc8226e8c7c6820a27821c7dc7c6822545822864c7c6820f55823299c7c6821258821e3dc7c682127182270cc7c682108f822795c7c6820ee48217a9c7c6820ca48230e2c7c682308782313ac7c68210108212e3c7c6822f0e823334c7c6820bc5823197c7c6821dd2823029c7c68224fb8232b8c7c6820d5e82223fc7c682129a823366c7c6820ec28225c8c7c6820bc7821f55c7c6821bb282320fc7c682123e822163c7c6820f6f82147ec7c6820fe5822121c7c682280e822cecc7c6821e02822f3ac7c68228a2822c6fc7c6820e6d82274ac7c6822586822799c7c6821113823149c7c68226bc8230a6c7c682218a823295c7c6821967822318c7c68221838232e2c7c6822bf182312dc7c6822508822efcc7c68232be8232e2c7c6822e1982328dc7c68210d182151cc7c6822377823183c7c6821229823166c7c6820ac4821b4ec7c6821ca7822747c7c6821a4a8220a6c7c6820f468227c4c7c682178c82318fc7c6820c3d8221f4c7c6820fdc822fa1c7c6822f2e823134c7c682256d822fcbc7c6820c79823063c7c6822cd9823398c7c68215c3822fd2c7c6820e4b821b23c7c68221d7823346c7c6822d3b8230d5c7c682212a822614c7c68228858230c2c7c68218c3822350c7c682231982258fc7c6822d21822dacc7c68216e38220b1c7c682204b82205dc7c6821524823228c7c6820bad823307c7c6821c7f82329bc7c682167f821f2ec7c68232c48233b8c7c68218e382322ec7c682318e8231a5c7c6820ce6822791c7c6820f3182327dc7c6820d108231ffc7c6821ec2822cebc7c68210e5822be0c7c682217a82331ac7c6822fa8823151c7c682129082256fc7c6820ad3821160c7c682234c8232c2c7c68211198233e4c7c68218b1822a5ec7c6820c15822138c7c682272d822fd4c7c6822aee823281c7c6821074822f7ac7c682149d822e61c7c68210928226c6c7c6821cdd8233d0c7c6820def821103c7c6821aef8232d9c7c6821fb38224e4c7c68228618230c3c7c6821ce382307ac7c6820f168224f5c7c68213168223c6c7c6820dd8822374c7c68224df823390c7c6820ef9823349c7c68222d98233fac7c6820a6f822e49c7c6820e3d822c90c7c682282f823362c7c6822ecd823370c7c6821de38230c4c7c6820e65822ce5c7c682267a822d4fc7c68217f5821ee2c7c6821b748230fbc7c682251b822564c7c68212b0822473c7c6822db082332ac7c68220db82313bc7c6821a64822c49c7c682218182327bc7c6822606823396c7c6820efc822f5fc7c6820ba8822509c7c68222c38231c3c7c68217178229b9c7c6821683822426c7c6821263822cf3c7c6820fc8821e4ec7c6820ec3822500c7c68220788227d1c7c68212ec822325c7c6820eac822d9cc7c682260d822ee6c7c682203382339ac7c6820b6882212bc7c6821150822846c7c68226698230ddc7c682196e82328fc7c6820aeb82303bc7c68225e882324ac7c6820d9882247dc7c6821daf822a2ec7c6820cde82313ec7c682306a8231a0c7c6821079823286c7c68218248221c9c7c682112b82251ac7c6820a0c823317c7c6821b4b821dd4c7c6822e95822ec7c7c6820d198223f0c7c682148782224ac7c6821f07821fadc7c6820d22821ee6c7c6821059822383c7c6822412822b78c7c6820f2182326dc7c68212d3822b28c7c682107382218ec7c6820a47822102c7c682263c822c0ac7c6820c96822200c7c68218178225b2c7c68222dd822d6dc7c68226698232a8c7c6820fef821b49c7c6820b28822735c7c6820dc28223bfc7c6821bc9822c3bc7c68230c78233e7c7c6821dc982334cc7c68230a5823252c7c6821e7e822db1c7c6821771822208c7c68212f3822225c7c68232c08232eec7c68222ae822f44c7c682265b822bdbc7c68212438222e1c7c6822e5182316cc7c6820de0822fcdc7c6820dba821228c7c68210dc82334bc7c682122682282ac7c68216d4822d33c7c6822e75823356c7c682129f8213b2c7c6821f58822d0bc7c6820a8e8227d5c7c6820b1e821b67c7c6822015822240c7c68208ae8209c9c7c68223408233f0c7c68210bf82302ac7c6820e8c8233d3c7c682210d822c5fc7c68213d7823290c7c6821968822d38c7c6820ba7822aa6c7c6821893822c52c7c68211ee822003c7c68222ed82324ec7c6821f8c8225d2c7c682101282185ec7c6821022823466c7c6820abb8210e3c7c6821f4482343dc7c6821aa78230a1c7c6820a81823148c7c6820ec9821407c7c682213f822776c7c6821cc582332cc7c68220eb823215c7c6821df98223f1c7c6820d0d8233bdc7c6821b148220f7c7c68210618230cec7c6822d06822ec3c7c682104d82322dc7c6820c3882335fc7c6822372823192c7c6820ebb822d45c7c6820b62822697c7c6821e19821e3cc7c6821d1c821e73c7c6820cac82173ec7c6823261823347c7c6820a50822788c7c6820a80821f49c7c68211e9822529c7c682260f8226d0c7c6821c7e82346dc7c6821c1d821cc4c7c6821a13822111c7c682218c822447c7c682254c8231c4c7c6821f4c82231ec7c6820e688222bfc7c6820eb28233a5c7c6820fb48221d8c4c38217d2c7c68212c3822d9bc7c6822b09822debc7c6820ebc821bb8c7c682130f8223d1c7c6822437822fbbc7c68210c6822c51c7c6820bbd822ae0c7c6821bcf8226d8c7c6820e7a821ddbc7c6822645823395c7c6822f148231fec7c6820c29822727c7c6820fe1822d2dc7c682235582248fc7c6821e018228c4c7c6820e398225f7c7c6821fa182302cc7c68229eb822b59c7c6821118821e6bc7c68213e9821a1cc7c6822f9e823135c7c6820fc38210f8c7c68212dc8213fdc7c68226dc822892c7c6821ca3821dbbc7c6820ae6822807c7c6822121822283c7c68231c0823322c7c68219b582320dc7c6821f3782289bc7c6821523821b56c7c682121e8230afc7c68222ca8224adc7c682151d82289ec7c6821899823040c7c6820be0822f65c7c6820aef82229fc7c6822127822192c7c682224b8231ecc7c6820cdd823104c7c6821ded8232b7c7c682107582310bc7c682116a82338bc7c68210a28233efc7c6821b14823111c7c6820e0f8216ebc7c68211c28225a8c7c68227858233a9c7c6821905822686c7c682129c82248dc7c682344a823475c7c6822b528231bbc7c6821551822dfdc7c6820ae8821904c7c6820f728232edc7c68220ff82340cc7c6821a638228f6c7c6820de68219d3c7c68212fd821cf5c7c68213eb822713c7c6820a12821231c7c6822563823310c7c682244f823379c7c68219f08222a7c7c6821a04822085c7c6822800823446c7c6820ad5822f92c7c68222ea82282cc7c68211028224b0c7c6820f73823205c7c6820bd38233e1c7c682101c822c41c7c6822631822e7bc7c68231bc823450c7c68228098230b4c7c682241d8225dbc7c682130982329ac7c6820ef8821248c7c6821a9482268fc7c68212ee82320ec7c6820aa3823459c7c68211e4822889c7c6820bf1821dd6c7c68224dd8231a4c7c6820d6f82196ec7c6820e058221adc7c6820a5f82262ec7c6822d78823481c7c68211d6822042c7c6820e9a82336bc7c68226a0822d98c7c6820d1c82117ec7c68232ac8232e0c7c6820bb6823386c7c6820a8b820e43c7c682144182239dc7c6821cc0823332c7c6821ff38224fcc7c6820dfa822953c7c6821221821f55c7c682337c8234d8c7c682274d82305dc7c6822065822d6cc7c6820a5e822602c7c682129782313fc7c6821e77822be5c7c6820e20821d00c7c6822bb78234a7c7c682113c8230d5c7c68209f9821187c7c68210c6821565c7c6820c9f82311bc7c682103882347cc7c6820fcf8234abc7c68219db82297ec7c6820c2e821489c7c682107c82211cc7c68230ff823191c7c6820c17823245c7c6821d41822662c7c68221288233dbc7c6822264822509c7c682102b822b09c7c68230fc823264c7c6820e9182218ac7c6820b028232e8c7c682268c822cbbc7c6820fad821eacc7c6821c51821eeec7c68221c6822353c7c6820cec823305c7c682175282269bc7c68217b68234d8c7c68227e582308fc7c682168d8216cec7c6820d40822102c7c682108a8212e6c7c68224348227d6c7c6820b5982204ec7c6821034822834c7c6820d978221e9c7c6820c9882299bc7c6820b9d821addc7c6821059822585c7c6821138821dabc7c6822d70822d85c7c68210bd82332dc7c6820fda82226ac7c6821bf6822f7dc7c6820d82821d44c7c6821015821f16c7c6821123821a84c7c68212048220d1c7c68211938234b8c7c6820b30823364c7c6822fae823339c7c68211518231c4c7c6822c7b822d5cc7c6820e59823093c7c6820be5822358c7c6822069822612c7c6820f0682143dc7c682213c822a4bc7c6820d278232e1c7c68211f98233f4c7c6821e8282207fc7c68210e98230e3c7c68210ee8224f4c7c68219378220eec7c6820b658231e2c7c6820e3b8231a7c7c6822ace823360c7c6821bf182315dc7c68223d882269dc7c6822d6082339fc7c682216682305ec7c6820edb82213dc7c6822872823123c7c6820cba8224c6c7c68209f3822429c7c68210ed822485c7c68211958212ccc7c6821a4f821f3ac7c68220c182225fc7c682191682237ecac98207c5820d50822da3c7c6820c258231f4c7c682117882300ac7c6820cda823219c7c6821164822011c7c6820fcc8215e8c7c682310a82344ec7c6820e8b822513c7c6821002823464c7c6820c1f822aeec7c6820b2282307cc7c682124e8230f5c7c682201882310ec7c6820be78230e0c7c6821003821255c7c6820b7c8230f9c7c682225d822a8dc7c6821e39823187c7c6820cb4822541c7c6822fee8232ecc7c6821081823052c7c682256b822cfcc7c6820f04821767c7c6820c4982231dc7c68217b78231cec7c6822d358234eec7c68229ee8234c9c7c68209af822bd1c7c682113f8234ebc7c6820e8f8234a4c7c68224a5822d68c7c68213f58232ebc7c682091a822f4cc7c682233c823457c7c68211c582214dc7c6822172822e88c7c68231898234e8c7c6821d9c8220a7c7c6820d368234b6c7c682289d823120c7c68214688217e9c7c6821afd8231a5c7c6820ce9821210c7c682236f82342ac7c68212f582356ec7c6821730823447c7c68211b982310ac7c6820c158226cbc7c682118c821d8ec7c6821e598223aec7c6821f6b823061c7c6822396822f51c7c6820f9b822cd4c7c682196782350bc7c682109a8230b6c7c6822265823367c7c6820bdc8233f5c7c68221dd823384c7c682227f822638c7c6820ff6821914c7c682240c823524c7c682296f822c8cc7c6822717823365c7c68223b8822464c7c6820fe2821b95c7c6820dba821192c7c6822051822d2fc7c682240882284cc7c6822290823412c7c6820bdd82334fc7c6820d33822887c7c682208b8234bfc7c68221e482221ac7c6821c4e82200dc7c6820b77822c08c7c6821be3822ce6c7c68221ce82357ac7c68229d8822bdac7c6820f69822e69c7c6821f9e82223dc7c68230158231b1c7c6820f688224d9c7c6820fb8822068c7c682204a823391c7c6820d1882321ec7c68227d78232dec7c682117082358cc7c6821051821d82c7c68220438227eac7c68228b882320ac7c682112482245fc7c68223fa823408c4c3822856c7c6822c49823446c7c6821b9d822a50c7c68225598230fdc7c68220c48231afc7c68209ed823151c7c68219a9822cecc7c682217a8231f6c7c6820a7882314bc7c6821c2c8233a3c7c68225d58232afc7c68212c482338fc7c68217ff822883c7c68221018230d8c7c68212038224fcc7c6820af18222b3c7c6820eb9822f7ac7c6820d4182116fc7c6820f4c8230bdc7c68211ec822651c7c68210cf822313c7c6820bd782225ac7c6821085821b47c7c68211978230afc7c6821eaf823207c7c68211a48224d4c7c682109d82353cc7c682189d82267dc7c6822556823027c7c6820df88227b6c7c6821016821fb9c7c6820fd1823324c7c6821973822454c7c6822d1c8231dbc7c6820bc68226bac7c6821e768227cdc7c68224a1823165c7c6821df982262cc7c6820fa4822487c7c682257a823164c7c68211e88233ddc7c6822c938232aac7c682266f8235bbc7c6820e1e822ff2c7c6821a8d822f61c7c68210d8823263c7c68226828226cec7c6822d2c8231c8c7c6821c98823231c7c68228058233e9c7c6820fd882148fc7c6821667822968c7c6820be3821db1c7c682195f823217c7c6820ab38218afc7c6821064823104c7c6820eea8215e9c7c68227ff823181c7c68227f7823342c7c682125c823591c7c68233ac823574c7c6820fca8230dac7c68219bf8230edc7c682214c822e56c7c68234bc823573c7c6822c848233ffc7c6822f188235b1c7c6820f3b823337c7c6821f1182304dc7c68223208233b6c7c68224e382304cc7c682128d8234ecc7c68217ab822d1dc7c6821ca9823336c7c6821ecc822751c7c68223ac8227f3c7c6820ac382126dc7c6820af9822827c7c68220ad8227c4c7c682112c821e86c7c6820e25822fdfc7c6822c40823532c7c6820c95822da4c7c6821033822efcc7c68230f982356cc7c68230dc823193c7c68211cf822393c7c6820fa6822657c7c68211778231dfc7c68208a9822e17c7c68224de8226b7c7c682165c8233f1c7c6820d9c8222dbc7c6821931822850c7c6821133823469c7c6822013822126c7c6820e3c8210adc7c6821930823450c7c682121b823563c7c682288682335cc7c6820dd3822cfdc7c682106682107fc7c682349d8234eac7c6820e8a82319bc7c6820ce1823242c7c6821d70822c92c7c68228c4823569c7c68211b18232d4c7c68210d082306fc7c682264782323dc7c6820ba382316bc7c6820f6082312dc7c682240f8233b4c7c6822774823407c7c68211a382337ec7c6820fa58231d1c7c6820f93822df4c7c6820b4d822fc8c7c682241e823474c7c6820c1d82304bc7c68225dd823240c7c6821c96822dc7c7c6821693821e51c7c6822d5182353ec7c6820f91823065c7c6822511823135c7c6821a9c8223b9c7c68211328230eec7c6821b7b822744c7c68233778233abc7c6822d39823576c7c682127e821654c7c6821044822666c7c68211fc823533c7c6822fed82317fc7c682246f822c4ac7c6822d1982338ac7c68230ec823220c7c68216cf822a9dc7c6820c548225cec7c682120c82270bc7c682359c8235b3c7c68210d28230cfc7c6821a8582229bc7c6822640823497c7c6820bc0823214c7c682241a82349ac7c68212768230d9c7c68213128230aec7c6820b1c822943c7c68222f9823030c7c6820f6a822450c7c682263b823535c7c682317c8235bcc7c68220b58235f7c7c6821a478230bfc7c682125a822d5dc7c68223078226bdc7c6820e418234adc7c6822e66823445c7c6822cc1823333c7c6820ecf8233fac7c6820a9b823525c7c68210a882332bc7c68230e282312ac7c68218ca82352bc7c682280c8230c8c7c682256d823588c7c68210e1821c2cc7c6820fe4822162c7c682137882305ec7c6820fbb823423c7c6822450822f92c7c6822117823188c7c68211ff822fd6c7c6821c42822efbc7c6821ec68232d3c7c68210408234c9c7c682230982269dc7c682264b823596c7c682107d822f0fc7c6821147823341c7c682318d8235f0c7c6821f8f82361bc7c68212e7822d91c7c68223b382323fc7c68223a98233fcc7c6821e658230a3c7c68224c5823319c7c6820e8d823416c7c6821be3822160c7c682116d822542c7c6820a59822828c7c6820b1482315ac7c682130f82256ac7c68230e3823469c7c682199082224cc7c6820dc5823286c7c68214e8822440c7c6821ed1822afcc7c6822c678234bac7c68230f582343cc7c682167b8222e0c7c6822147822803c7c6820e288228b5c7c68221e18226b3c7c6820d71823185c7c68211e58219e0c7c682100682228bc7c68227c98235eec7c6820e34821d37c7c6821ad8822103c7c6821dad822cb4c7c6821069822578c7c6821f4b8227d2c7c6821778822131c7c6822454822505c7c682324c823548c7c68210f18223ebc7c6820c0a822a67c7c68226f4823436c7c68219b7823650c7c6822710822cefc7c6820b63822976c7c6820cb28231e2c7c68233508235c2c7c68215a3821c0cc7c6820efe821eabc7c682355482356dc7c68231ab82332fc7c6820e92821f81c7c68211ac8235a6c7c68210468233b0c7c68230b7823492c7c68215cc822fe4c7c6820d84822155c7c6821bc3822ef1c7c68212f6822e8ac7c6821ab2821e9ac7c6822cbb8235cec7c6821573823066c7c682362982365bc7c682186a822950c7c6821585822db6c7c6820acd821358c7c6822a72822b81c7c68233af82358ec7c6820eb68231dec7c6822c468232c7c7c6821d60822b57c7c68213e28234f7c7c6820d35820e59c7c682141d822a7fc7c6821268821939c7c6821067823021c7c68227b48234a2c7c6822f6582359dc7c682111a823417c7c6820e7e821c65c7c68234678235bac7c6820cae82245bc7c6821e6d822dbbc7c6820b4e822f6bc7c68222168223ccc7c68226498231a6c7c6822168823607c7c682266a822689c7c68228448234ecc7c68210db823369c7c6822f788235ccc7c6821c788226d0c7c68228b4822e04c7c6822933822bd4c7c68228718236a9c7c68225ec823043c7c68212ac82359ec7c6821334822d9ec7c6822aa8822e77c7c682141182276dc7c6821179821cb8c7c6820e938231ffc7c6822c6182348bc7c682292c822f8bc7c68220ce823615c7c68225ba8226dac7c6821d92821e81c7c6821aad823596c7c6820cb58229eec7c6822087822cfec7c682206c822460c7c6822df5822e96c7c6821e87822ddec7c68218fa8229ddc7c6821ea2822fecc7c68224d08233c7c7c682337d8235ecc7c68233b1823634c7c6820d1a821d6ac7c68217688234fec7c682126682253ac7c68230e5823661c7c6820ced821f3dc7c6821211822d40c7c6821dcb822829c7c6820e69823395c7c6821a7f822570c7c682192e822893c7c6820f24821c1ec7c6820efb82222cc7c682162f8235d8c7c6820c4c822e65c7c6822a74822ffbc7c6820ea1821ddac7c682224e8235c4c7c682124b821feec7c6822afa822ff3c7c682127682348ac7c68218ba82295cc7c6821da5822352c7c6821343822a41c7c68223e382253ac7c6821641822a12c7c682220c822d1bc7c68212e1822e01c7c6820ba0822997c7c6822b6182362cc7c68233ef8236c7c7c68210a6823210c7c6821601823696c7c6821cab82362ac7c6821436822c20c7c6822f7b822fdbc7c6822840822cffc7c6822be6822c0cc7c6821b59823211c7c6820e47822c60c7c6820fed822d0ac7c6821465822787c7c68208db822de8c7c6821d20822496c7c682128d822f71c7c6821852822d15c7c68225ee822b06c7c6822e41822ef1c7c6820ece821456c7c682113e823194c7c68221ff823143c7c68224af822e3cc7c682209b82247fc7c6821337822ab9c7c68219658236bfc7c682122a822c6fc7c68208fb820c16c7c6821aab822b9ac7c68218908227b8c7c68224b18232b9c7c68214f0822344c7c68227be822e83c7c6820d74822d18c7c6820f62823431c7c6821732822e5bc7c68226e2822c5dc7c6822990822da6c7c6823124823570c7c6820df0822626c7c6822d54823393c7c6821b018220b4c7c68210fe822318c7c682190e822937c7c6821749822d0cc7c682251382263fc7c68212a0822d5ec7c682291e822a19c7c68217188235b9c7c6822927822b23c7c68219b9822377c7c68222d5822628c7c682118c8234c0c7c6822f4d823597c7c6822a2c823062c7c6821fb0822fc4c7c6821956823220c7c68228a0822b13c7c6820d64822a77c7c6821d91823091c7c6821292822637c7c6822a78822e07c7c6820ca88232a5c7c68211bc822449c7c682114082277ec7c68210a082362ec7c682111b821200c7c6821e5e822ff9c7c682218b822cf1c7c682186c8219fec7c682288d822dbcc7c6821dbc8227b2c7c6821872822a88c7c6820fe9822f98c7c6820e2b82250fc7c6821beb82363bc7c6822e3e823448c7c6821859822f6cc7c682161b822bacc7c6820dc9821ab7c7c68211f2822cdcc7c68211d5823145c7c68223b88230f8c7c6820d62821978c7c68225378236fac7c6822a51822aa5c7c682103e821c13c7c6821327822bcdc7c6821143821e64c7c6822251822d0fc7c6821037823587c7c6822796822d4bc7c68229aa822ef9c7c68217de82276cc7c68208b9821816c7c6822a0a822b8ec7c6821750822e46c7c6820ed38233bec7c68212f0823547c7c68213df8227a9c7c682102e823193c7c682133f823417c7c68209c8821c79c7c682111e8233aec7c6822fb68232fcc7c6820ea882217dc7c682345582368ac7c6822bfb822cbfc7c6821c9e8229afc7c68219468223b0c7c6822f15823525c7c682202c823373c7c682114f8235a8c7c6821853822b64c7c682311582369fc7c6820a0a8236d9c7c6821604821761c7c682198f822b56c7c682111282268bc7c682141e8217fdc7c68216ae822ae2c7c6821656822dfbc7c6820c48823058c7c6821727822da8c7c6820ecc823544c7c68209c3820bc4c7c6821510822045c7c68216458228f7c7c6822bcf82373cc7c6821740822bc0c7c682109f82253cc7c6822a5b823718c7c68218b8822d52c7c6821616822215c7c682141082202ac7c6822ab9822e25c7c68219be82257cc7c6822823823213c7c682144b822a7fc7c6820eb3821d18c7c6821e478229c0c7c6820cdb822f0bc7c68214e4821d2ec7c68218ed821dc1c7c6822342822cddc7c68213548230b1c7c6820d4b8222bcc7c6821763822decc7c68208ed822a0dc7c6821943821945c7c6821ac7822944c7c682181f822aa9c7c6821e38822ccbc7c682317982332bc7c6822903822cacc7c6820c7b821edfc7c68232e4823506c7c682236e82300ec7c6822855823217c7c6821509821548c7c6821466822b6fc7c682147682339cc7c6820c228213abc7c68213ef821b71c7c6820ba6822c02c7c68227e48227f2c7c682120d821e68c7c68217cc8228dac7c68230578236b1c7c682198c822baac7c68218088228e0c7c682161b821ff9c7c6822aad822edec7c6821ffe822aa8c7c68216238217bfc7c682180f822a56c7c6821ed8822ffdc7c6820bed822ff6c7c68223488226a6c7c682105682347dc7c6822f3982354ac7c6821e148231eac7c6822c17823032c7c682168082192bc7c6820f978227d8c7c6823034823716c7c6822806822f63c7c68217db822d74c7c6820bfa821590c7c6821d04822a13c7c6822daa823427c7c6821517822f69c7c6821389822dc2c7c682220e822844c7c6821dd182294ac7c68219e6821d7cc7c68229058237a9c7c68214bf822e08c7c6820d2c822c52c7c68218708237a1c7c6820b54821424c7c68219c1822fdac7c6820c06822e7fc7c6822915822a0bc7c68229e1822c23c7c6822e12822e8bc7c6821764822b8cc7c682095f821528c7c682230f8228edc7c68218ea822f23c7c682257d822ad6c7c68227b9822ffec7c68213af8228efc7c68218cf822f59c7c68215ed821fd8c7c6822a34822f23c7c6821751822bf7c7c682192782345bc7c6822d52823004c7c6821fd3822d6fc7c6821a93822a38c7c68219dc823056c7c682124a823470c7c6821c93823371c7c68236df82378ac7c682158e822dfcc7c6821c7082336cc7c68215b68235f3c7c6820d59822641c7c6821882821e20c7c6820ffd823398c7c68229f08236d4c7c6822498823248c7c6821e44821e7fc7c6822bed822ecbc7c68229cf822d6ac7c6821c68822922c7c6820e9e822758c7c68216bf822b3cc7c682150f8215a2c7c68227a7822a2ac7c68214b5821ec7c7c6820e3382340ec7c6820bd18214d7c7c6822e79823760c7c6822ece8237d6c7c6822941822b5ec7c6821edf8229c2c7c6822945822e0fc7c682126e823481c7c6822ac3823003c7c6822204823732c7c6820db58224aac7c6821f1d822b22c7c6820d53822809c7c6822935822b89c7c68212eb822428c7c6821a7e822c31c7c6822b0b8237dfc7c6822b67823561c7c682162d82182dc7c6821401822a35c7c6820905820c73c4c3823794c7c6821083822fefc7c6822f0c823702c7c68224ee822c41c7c6822b45822ee0c7c68210fb822866c7c6821a05821e25c7c6820d2382170ac7c68221af822efbc7c68229d0822a55c7c68210ff823060c7c6820cf48230bbc7c6820ef2821b6ec7c682151b8237d6c7c68212f182172bc7c6821bdd8227fcc7c68215aa822afcc7c6822a3e822c97c7c68210ce82229bc7c6821acb822d1fc7c68209b3823035c7c68216f3821c22c7c68213d2822870c7c6821bc3822a22c7c682196d821b25c7c68211b3821652c7c6820c1a822ef3c7c6820dc782379ec7c6821714822a7cc7c68213758234fdc7c68215c18216aec7c6822a528236f8c7c68228bf822ac3c7c682293d822a13c7c6821a8b822d7ec7c68213e88219e8c7c68223f78235c0c7c682097a821a73c7c6822e1c823764c7c6821442821a12c7c6820ffc823413c7c6822ab2822e41c7c68214db822868c7c682324e8235c1c7c68215ab8216b9c7c6821ade822f0bc7c6822185822d12c7c6821464821d11c7c6821615822c27c7c68214aa823817c7c682180a8228ccc7c6822ef9822ffdc7c68229c4823776c7c6821936822dbec7c682199c8219dac7c68219ad821c0dc7c682193a822f0cc7c6820c3b8219cbc7c682224f8226aac7c6821baf821c1fc7c6821421821df3c7c68229cb822abdc7c6820b318228a8c7c68211de822cb8c7c6822902823814c7c6822792822e1dc7c682199d82203fc7c6820ef382343ac7c6821e38823743c7c682157a822e2ac7c68210be8226c1c7c6820d34822efac7c6822976822ef6c7c6822aa7822c4cc7c682139e822a68c7c6821849822954c7c68214de823796c7c68218a6823694c7c6822d7782381ec7c682133b822ac4c7c6820981822c31c7c6820f1c822dd1c7c6822ca6823256c7c6820bc1820bedc7c6821713822bffc7c68213c88214d8c7c682273d822cc9c7c682369b8237aec7c68216968219a0c7c6822f43822f7fc7c68223c68227c0c7c6822d11823542c7c6821bd0821f56c7c68213d2821ad9c7c68229108237b5c7c68216fa822aa4c7c682186482384bc7c6821609822fe7c7c682194d821e5ac7c6820a5a822ec5c7c6821b11822e5fc7c6822c7c822fafc7c68218e9821ae3c7c682166b822e80c7c6822a5c822b5dc7c6820b17820c46c7c6821493822ee5c7c68217ee822a36c7c6822bfa822c20c7c6820c28822c28c7c68210e5823357c7c6820bcc821e53c7c6821ef8822dbac7c6822af5822b89c7c6821b2f8229e4c7c6821b848227b5c7c6821330821a3ac7c68216fe8229ccc7c682140d822bafc7c6822def822e00c7c6822c7e822dc0c7c68232b9823647c7c6821f2d823747c7c68212f3822c55c7c682197b822815c7c6822ac5822db1c7c68216a6822febc7c682119b823456c7c68216cb822b0fc7c68213098224dac7c6821963822ea5c7c6822b2d8237abc7c68215e78237b2c7c682182c8236ecc7c6820f9f82249cc7c68211ce823186c7c6822900823790c7c682174b823865c7c6821fc68227cbc7c6821ee982369bc7c68228e78229c0c7c68214a1822b0dc7c682375c8237c3c7c6820f91821c7bc7c6822adb822e90c7c68217318229a4c7c6820a56821a5ac7c6820bb7821d26c7c6820a38822038c7c6820ac7822e96c7c6820987821709c7c6823235823434c7c68213298216adc7c68228e88237d1c7c6822921823593c7c682150e8229e1c7c682385a823880c7c6822bf9823069c7c6821e938236e7c7c68228e0822f8ec7c682156a822e22c7c6822300822831c7c6822a05822b2cc7c68217d78229ffc7c68216d08228f9c7c6821b408225bec7c6822b6f822db8c7c6821c428232f4c7c6820c7d822fb7c7c682180882384dc7c682290d822a15c7c68213dd822dc1c7c6822f4e823071c7c6822e8582375bc7c6822b6b823891c7c682166b8229dec7c68217f8822e3ec7c6821a7a822952c7c68219b1823843c7c6821dd58232a4c7c6821145821f23c7c68222e28223eac7c6820e46823854c7c6820c30822e84c7c68216da821b51c7c6822993822b75c7c68228728232e7c7c682129b823254c7c68218ad821c92c7c682185082383fc7c6822019823687c7c68209da823712c7c6822f16823355c7c68210a98225a5c7c6821940823878c7c682117b82186ec7c6822c21823832c7c68218818218afc7c68213d8822b94c7c682179c822025c7c6822279822d27c7c6821361821439c7c68213c78237f0c7c6821958822008c7c6822c2d822c86c7c68213cb822a63c7c682151e8229c5c7c6821476822589c7c68219e9822d61c7c6820b2e821e25c7c6823092823721c7c6822c21823035c7c6820e408234b9c7c682137a821998c7c682313682331cc7c682287e822b5bc7c682180482318fc7c682199a821a06c7c6821b5e822bd0c7c682373c8237a3c7c6822aae822e98c7c68218ff8227a5c7c68211c982385cc7c68229fe822df9c7c6821a3e822ea3c7c6821454821a2dc7c6820bb0823820c7c68221b8822705c7c6822bd98237d0c7c68217778229f4c7c682133a822fcfc7c682114482326ac7c6822f2b8238d4c7c6821639821b11c7c68228c2822c2ec7c682138f82178fc7c68217ea823764c7c6822ad8822bb9c7c68227c8822a68c7c682299e822c8fc7c68229a5822e85c7c6821a628238b9c7c682136f823747c7c6821572821a28c7c6821a0e822e82c7c6820b75822bcac7c6821796822a8cc7c6822aff8237a0c7c6820a76821b36c7c6820b52821a35c7c6822b83823818c7c682101b823834c7c68216d982380ac7c68221748234f5c7c68213f38238d9c7c6822b01822e6ec7c6820e7982380ec7c682132d822a6fc7c682326d823602c7c68231a882331ec7c6822fd1823884c7c6821734821b71c7c6821a288237c4c7c68215f2822e11c7c6822b0e8237c1c7c6821755822e17c7c68233828233b7c7c68218868229bcc7c6820ef182289cc7c682151282301ac7c6820e0e82373dc7c68216a6822010c7c682135c822c2cc7c6822e93822fb6c7c6821ae2822f52c7c6821f1f822cf5c7c6820efd82344ac7c6820b00822b2ac7c6821a128229cbc7c682187e821935c7c682195b822f33c7c6822a70823047c7c6821a7a822ddac7c6820b1682169fc7c6822adc8238e6c7c6820ee7823438c7c6821418822f1fc7c68217ce822b30c7c682192c823886c7c6822ad4823703c7c6821918822f42c7c6822e3f823866c7c6821977821993c7c6820cc1823300c7c682141d823906c7c682303a82372fc7c6822a20823907c7c68214f2822a01c7c6821747822c34c7c682329182379bc7c682293d822b4fc7c6820ba4822de4c7c6821b2082376ac7c6822aae8236c5c7c682148c82362cc7c68218a78238d6c7c6820906822de2c7c6820ded822d5bc7c6820960821809c7c6821a7b821d81c7c6820f2c823655c7c6822a00822e38c7c682194d8238f2c7c6821d078232d4c7c68217c6822ff5c7c6821664821744c7c6822c75823872c7c68213d38237a0c7c6822761822fbec7c6820d868214abc7c6821ad98226b8c7c6821828822915c7c6820b57822c1cc7c68216b58229cdc7c6820b47822b2fc7c6821392823899c7c68219d28228ddc7c682134d821954c7c6821a1f822e53c7c68210e482356ac7c68229cc82378cc7c68218b0823121c7c68213228235b0c7c682093b821c5bc7c682199f823837c7c68229ae822b4ec7c68235e282371ac7c68232fa82350fc7c6821488821c0ec7c682255382327ac7c68231958236e9c7c68219a6822e9bc7c68210118235d0c7c6821a2e823627c7c6822d5d82309ac7c68212b982366ec7c682132e823783c7c682250b8236bec7c682169a822db9c7c682153182159bc7c682211982265ac7c68212ba8227f8c7c6822a01822dbcc7c6822848822907c7c6822379823658c7c682215a822c7ac7c6820b5d822c15c7c6821ebd8237d8c7c682302a8231d9c7c6821a2f82388cc7c68221e88232b6c7c68219b38227e1c7c6822d4c823161c7c6820e2782312cc7c6821be88227bbc7c6821fc182319cc7c6821d8e82203ac7c6821a6a822b92c7c682211b822ce1c7c6820d2b821907c7c6820c0f8218acc7c6822773822e48c7c6822a9b822bfec7c68222e9822aa6c7c68214af8221fbc7c6820e36823319c7c6820f80821fe4c7c68227ab822cc0c7c68216ab823593c7c68223168227d4c7c6821ff8822989c7c68218f3823963c7c6821679822833c7c68213028234b2c7c6821168823238c7c6821ce2822b47c7c68211498217a3c7c6821dec8229d3c7c6822345822625c7c6822871822f8dc7c6823176823729c7c68216928230d6c7c6822a9a822d7bc7c6820e16822fa8c7c6821de88221a8c7c682291d822937c7c6821338821723c7c68214c0822a76c7c6821498821fc5c7c6820d6382217ec7c6820ab6822beec7c6822cc5823639c7c682153d823871c7c6822780822e0cc7c682314d823419c7c6820ed1823342c7c682205782305fc7c6820d6e822178c7c6820f1a823093c7c682301c8235d5c7c6822e498236c3c7c68214418233bbc7c6821799822962c7c68222698234cfc7c6823476823479c7c682253782271dc7c68230f782344fc7c68225ee822a82c7c68228888234fac7c6821ebc822c32c7c6821916822688c7c6820cef8227efc7c68212c38233a4c7c6820fe38234eac7c682194f821d8cc7c6820d5c822802c7c6820e4d8225bac7c68231a382342ac7c68215d5822dd6c7c68211c6822204c7c68233e28233e8c7c6821f02823393c7c68222cb82317dc7c6820f5a8222a5c7c68219d882356fc7c6821b90822fcac7c6822557823167c7c68227b782298bc7c682273a8238fac7c6822612823144c7c68236b98237cac7c6823453823729c7c68216dd8228a3c7c682191982378ac7c6821036823225c7c6821596822b19c7c68226db823235c7c6821acd821ea5c7c68211988234b0c4c3821e8cc7c682170d8236b7c7c6821069822692c7c68217c9823081c7c682150c8229f8c7c6821152822763c7c682149b822e6ac7c68223e482398dc7c682207382387cc7c6820f50823958c7c6821a23821acdc7c682345a823568c7c682175b822a6cc7c68223ec822cc2c7c6821e0882329ec7c6821230821d2cc7c6822135823170c7c682298f822de4c7c6821b3d822e31c7c6820fc2823006c7c6822c2d822c34c7c682183f821ec9c7c682291c822fe4c7c6823515823639c7c68236ee823741c7c68224e6822665c7c6822dc98237d1c7c6821b6d823079c7c682103b8226a6c7c6821d7c822a5fc7c6821376823735c7c68233ee823726c7c68228ec823976c7c68227d682397fc7c6821238822323c7c6822e378231e1c7c68210da823666c7c68226ec823626c7c6821c2f822a81c7c6821a81822ef8c7c6821aa28228fbc7c6821f1f823237c7c68216d382391cc7c6822719823600c7c68214da822e4bc7c6820d928224c5c7c68210d5822cf0c7c68210a4823730c7c68210ec82189cc7c682212d8224b8c7c68214f7822862c7c68212b8821be2c7c6821f2482395ac7c682192f8236f3c7c6821198822fb0c7c68212ff822554c7c682100a821553c7c68212d58224aec7c68217708238eac7c6822787823883c7c68214a9822fe7c7c6822d9b82321fc7c6821fc3822228c7c6821453821bafc7c6822ea3822f79c7c68212c1821f99c7c68222878229e9c7c68232098233dec7c68216f0822bcec7c68216b18237c2c7c68225eb823439c7c6821494822bd4c7c68218388238d3c7c6821660822c2fc7c68216ff822a49c7c68234048236fbc7c6820e9f822765c7c682200182353bc7c682293b823922c7c6821932822b8bc7c6821581823868c7c6820e72821a43c7c68215bb8239f4c7c6822c30823804c7c6821533821941c7c6822db9823a02c7c6820e7c823780c7c68217a7822e0fc7c6822817822f34c7c6821842823779c7c6820df482353cc7c6821242823293c7c68227f882341fc7c6821cb3822e52c7c6821a878227dec7c682286c822ec0c7c6821868823853c7c6820df2822fe6c7c682144c822e57c7c6822a4a822e2dc7c68213158235e7c7c6820e7d82284cc7c6822aa4822da8c7c6820da2822ecac7c68211288224cfc7c6820e5b822f3ac7c68210c1823828c7c68229ce822b41c7c682117d822707c7c6820adf8238a7c7c682183f821e15c7c6821ba9823748c7c6821212821c72c7c68238a08238e7c7c6820e42822644c7c6821db082377dc7c68226908231e8c7c6820b82822ebac7c68211ad82268dc7c68234d98235bec7c68209b0822ffbc7c68228d6822ddac7c6822a3e8237c1c7c682154c8229fdc7c68235d6823779c7c68212bf823488c7c6820f878239d2c7c6820da9823280c7c6821b18823178c7c68210bc8231aec7c682237c82380ec7c6821dca82300ac7c6822fc9823606c7c68228478237b7c7c6821472822d78c7c682284b8228bcc7c6820ea9823622c7c6821fc08222fcc7c6820e6c8230bdc7c682327982378dc7c6820ead823625c7c68234fc823643c7c6821884822c37c7c6822368823778c7c6821fdc823124c7c682296a8238f4c7c6822af8823960c7c6822fad823971c7c68216bd822610c7c6820cfe82333ec7c6821a8c822a4ac7c6821d838236ffc7c68212608221e4c7c6822998822c0fc7c6822ae1822c13c7c68225f282398cc7c68214948237b5c7c682238f822c96c7c6821534822bc2c7c6821964822ea5c7c68216a4823071c7c6821a488225cbc7c6821f5f8224d6c7c6822ff482386ac7c68211da82257bc7c6821670823a27c7c682155b8219ecc7c6821280821bc7c7c6820aec8229f1c7c68229668237e8c7c68209998237eac7c6820b5082307bc7c68227f1822926c7c6820a4a82294bc7c6821ac0822929c7c682155b82164ac7c68217078239c0c7c68232e68235bfc7c68209eb821a3bc7c6822cad822ea4c7c68233f2823a5ac7c68219d9821c4cc7c6822f4a823932c7c6821d54822a97c7c6822be882383ec7c68215568239c3c7c6821bc8822f2fc7c6821ad4822b15c7c6820a87822bffc7c6821aa6822f62c7c6822a9c822db8c7c6823078823a12c7c6822e7d8237c7c7c6820a6a8215b2c7c6821a6c8236e1c7c682094d82390cc7c682173a8237afc7c68215d6823909c7c6821592822f57c7c68213d0822a5dc7c6822d4b8237b8c7c6822bc98234c7c7c68214a1822f8ac7c68235568238fcc7c6821a45823098c7c68215048216fec7c6822982822d74c7c6822903823a5dc7c6821e0b821fbac7c6822071822bf2c7c68231f1823298c7c6821c38822962c7c68235cb8237fac7c6822b888237b1c7c6822ba4823070c7c68213728238e1c7c6821877821fedc7c6822d48823760c7c6822ae9822dbdc7c682294f822ec4c7c6822b8f8235a5c7c6820af6821fd3c7c68217aa823911c7c6822c0682382bc7c6821e40823827c7c682293c822ddcc7c6821403822b77c7c6821538822e0ac7c6822fe1823696c7c682163d8226b6c7c68214438237b4c7c68215fe8238c7c7c6821a1a82338dc7c6821ab98238c5c7c6823789823a42c7c68219a2821e07c7c6821e63822e34c7c6822d7b8236edc7c6821d1a823991c7c6822305822c91c7c682152a822e7cc7c6821422823a1ac7c6820ab8822b97c7c6821c4c8228dbc7c68213ee822cbdc7c6820b12822b11c7c68213e6822aa2c7c6822de6823767c7c68220388239cdc7c68220b68239e5c7c6821373821789c7c6822f07823a9bc7c68209d3822bb5c7c6822b3d8237d0c7c6821702822b60c7c682214b8221c1c7c68213fc822ec4c7c68220e38227e5c7c68229d1822e0dc7c6822e54823016c7c682204d823aa0c7c68231778236a1c7c6822938822a91c7c68214408229bfc7c6822dee823795c7c6820b27822b9bc7c6820ae0822e91c7c6821545823038c7c68209db8237e8c7c6821935822fabc7c68209e1822972c7c6822b6c82374dc7c6821d0d82306ec7c6822911823920c7c6820fe8822ec6c7c6821429823160c7c68213ac821cf9c7c682165c822894c7c6822928823070c7c6821751823856c7c68215cb821902c7c6821aa5822f6bc7c68219d6822eabc7c6822db78232c8c7c68210e0823279c7c6821ccd823792c7c6821614822bbfc7c6821680823418c7c6821844823a7cc7c6822af7822ee0c7c6821d83821feac7c6821506823a22c7c6822fd382300cc7c6821c6f823952c7c6822ed5823a4bc7c682116b823283c7c6820b3e821f86c7c68219a18232a9c7c6822bca822f56c7c682389f823acec7c68217ae822ef5c7c682159b822be1c7c68234ac8237e2c7c68233998234dac7c682329d823a73c7c6821ba4821c91c7c68213c08239dbc7c6822a9382393cc7c68213ba8216b0c7c68220fb823841c7c6822b98822e54c7c6822aa1822b4bc7c6821c11821c24c7c682373e823856c7c682144e821a91c7c6820f75822d3ec7c6821742823401c7c68214ba8217adc7c68219718219e7c7c6821a0f821d74c7c6821e108227a8c7c6822c22822e0ec4c38229fac7c682190f8229abc7c68208f48239a3c7c68228be822d77c7c682152c822ad8c7c68229fc823957c7c68237b6823846c7c682132c823914c7c68214f6822c16c7c6821022823345c7c682175b823786c7c68208f08239c1c7c6820a048218fcc7c68234c5823771c7c6821610822e5cc7c6822f35823abdc7c6820a53823932c7c6821b38822b91c7c68209c4821d8ac7c6821558822bbbc7c6822e03823acac7c68217f882392ac7c682098e821c02c7c68209cf822922c7c68231848236eec7c68214fc822e8ec7c682196c823a82c7c6822ba9823a5ec7c6822aa3823823c7c6822f7082304bc7c6822fbc82386dc7c68217378238bcc7c682292c822f83c7c68238fd823ad9c7c6821790822f2bc7c6821705822e80c7c6820909821fcec7c68228e7823ac5c7c682160c822cb2c7c682169e82297cc7c68229bb822b10c7c682198682397ec7c68219ce822c18c7c682170f821811c7c6821737822ba5c7c6821fe9823a85c7c68237b9823ac7c7c6821b38822bc7c7c68229e2822b08c7c6821d0d822ef0c7c6822ea7823ae3c7c6822dcb823b0bc7c6822e97822ea6c7c6821c04822965c7c6822bf7822dd7c7c6823801823840c7c6822b4a823b1cc7c682137f8229a3c7c682370c823abac7c68217e3822dc4c7c68213b5823aa4c7c6820aac823a88c7c6821374822b9ac7c682190e822eafc7c6822ec2823462c7c6821675822c01c7c6822df1823ae5c7c6820a92823811c7c682117a821557c7c68214a88238a6c7c68214c6823a70c7c6822de78237aec7c68215bc822a80c7c6821ecf823765c7c682147a823b0ac7c682134c8214d5c7c682115c8227c9c7c682168282278ac7c6822d61822de0c7c6822d7382392fc7c6822e58822ecfc7c6822b50822f83c7c6821e0e822fdbc7c6822b18822dadc7c68214bd823940c7c6820a85823affc7c68210f2823654c7c68216858238dac7c6820ff2821bc8c7c6822ef8823b1ac7c6822af3822f49c7c6820aa6820b44c7c68218cd82394bc7c6822a64822acac7c68215cb821ce6c7c6821a6f82291ac7c6822a0f8237edc7c6821ff6822fd5c7c6821672823a24c7c682174c8217bdc7c6820fc3822155c7c6822b3a822dbdc7c68209f7821917c7c6822b998237ccc7c68219ca822d67c7c6821a0e822a5fc7c6821ad382375ac7c68216058229f1c7c68213c0823753c7c6822987822b68c7c682297482387bc4c3821328c7c68216b2821d4bc7c6822016822b49c7c68213e3821f71c7c6822ebc8239ffc7c682196c821ec0c7c6821731823adfc7c6822b7f823b0cc7c6821e7d822d59c7c682141082154cc7c682146e823a87c7c682161a822ed4c7c68230048233f2c7c68218a982299ec7c6822548822677c7c68223898223a0c7c6822697823119c7c682289f822addc7c6822e5d823a72c7c6821a0a823a80c7c6823857823939c7c68237bc8239b7c7c6821fca822b34c7c682154f8215cfc7c6822db38238adc7c6821943822e67c7c6820bb8821401c7c6822a028237dac7c682154d823b26c7c682092082377ac7c6820e1b822ed6c7c6822f3b82387fc7c6822b71823b56c7c682157c823a90c7c6820b61822f80c7c68229e7823b4ec7c6822afd82394ec7c682161282377ac7c68209fd822b2ac7c6821550822e1ac7c68210f482292fc7c6821ac8823b75c7c6822c5b82388ac7c68228a9823b85c7c68216b8823051c7c682146e822f27c7c6821b26821f17c7c68216a18239b5c7c682275c822ad3c7c68237b482389bc7c6822999822febc7c6821dcf8236c4c7c68214cc821500c7c6821e1c823a61c7c6820a14822978c7c68228cd822ae3c7c6821e8f8239bbc7c6820b76822b95c7c682390482390fc7c6821733822a6dc7c682173d822d6ac7c6822e78823b24c7c6822ba5823010c7c6822e8c823796c7c682391a823af2c7c6822e32822f79c7c6822e5f822e81c7c6820af2821986c7c6822060822adec7c6820a3c823ae2c7c6822e33822f3bc7c6820e2f82309cc7c6821561822db7c7c682216b8227f6c7c6823952823a54c7c682295b823873c7c6821b6a823a66c7c6820b75823915c7c68218288237bfc7c6820ac1822515c7c68208e9823b79c7c68218fe823b4fc7c68218ad823b93c7c6822e7a823803c7c68213a482142bc7c6822acb822bcbc7c6822eb1823841c7c682293882374dc7c68223c38232a7c7c6822c8c823aa9c7c6823888823b3ac7c6822ddd8236c6c7c6822ac88238c8c7c68236978236eac7c6822b3b822dd7c7c682162e823bb7c7c6820b2a822e11c7c6821620823a22c7c682114c823710c7c682189a82308dc7c682287e82370ac7c6821d22822a35c7c682156d823bafc7c682287b823528c7c682161f821cbec7c68222fa823ac2c7c6822a3282384bc7c6821642821ea4c7c682340b8234fbc7c6820f8382208cc7c68213fa823a86c7c682149282397bc7c6821539822a84c7c682166d821b82c7c6821d8f822ac1c7c68219ac823b38c7c68227e98232d0c7c6820dd9822e01c7c6821672822fd5c7c6821edc8229b6c7c6821432821f21c7c6821785822875c7c6821e95823933c7c682273e822fc9c7c6822ef282357cc7c682234a823353c7c6821137823308c7c682102f8233aac7c68217a0822ab1c7c6821f3e823b16c7c68212b78219ebc7c682340f8236a9c7c6821037823769c7c6821a4d822743c7c682182e823694c7c68211908230eac7c6823208823595c7c682120f823727c7c6820ef782338cc7c68217c082295dc7c68217ac822e5ac7c68220f0823927c7c6821f8b82346fc7c6822e3d823b8cc7c68216a8823987c7c6821a17821d72c7c6821a5482384ac7c68217ce823ad4c7c682371e823938c7c6822aec823a54c7c6820f778223bec7c682234c822629c7c68222cb82280dc7c682130d821d0ac7c6820cc3821720c7c6821364822eefc7c68218bb822a09c7c6821ebf823125c7c6822046822898c7c68222eb822f82c7c68236d7823babc7c68216bc823a9dc7c68224b78232cec7c6820cb282106cc7c6821d0f822403c7c682181082302dc7c6821738822836c7c6821a78823695c7c68226d582392dc7c6822be9823ae9c7c68231c682329cc7c6821686823b5ac7c68213318226fdc7c6822aa382380dc7c6822d62823690c7c6821e44823a77c7c68214fe821c9bc7c682150d822221c7c68215e1822fe8c7c68214ed822dc5c7c68227938234f4c7c682195c822c01c7c6822822823358c7c6822ae7823b07c7c6822423823620c7c682168a821f53c7c68218c7822c26c7c68235b5823c14c7c682298a822c1dc7c6822aeb823b08c7c68235738239b1c7c6821739823a46c7c6823742823a6bc7c68218f7823979c7c68229f3822af2c7c6823adc823b77c7c6821b5b823a96c7c6823b4c823b98c7c68228d3822b4bc7c68214f1823bcac7c68223f3823863c7c682132082232ec7c68219cf822a21c7c68238db823a1dc7c6822ad1823086c7c68212db82348ec7c6821a7c823860c7c6821e7b823519c7c6821682821f52c7c6822037822f2dc7c68222cd8226bdc7c682170482214ac7c68216d7822f82c7c682156882238bc7c6821fb6823818c7c6821a78822a26c7c6821801823a2dc7c682135e822f46c7c68216c6822a9fc7c68217f3822016c7c6820eda823325c7c6822940823ba2c7c6821663822a9cc7c6823572823584c7c6821df1822ba7c7c6821b06822388c7c6823a11823a74c7c68213a0823126c7c682354b823b68c7c6821725823b75c7c682159e823824c7c6820e9b8232f1c7c6822a9b822f41c7c6822255822927c7c68214848234b1c7c68215a5822e16c7c6822d948234a3c7c68210828228f1c7c6822b8a823583c7c682160f823625c7c6821d6f821ee8c7c682203e8239ecc7c6822f6f823372c7c6822acd823bc5c7c682279c8228bac7c6821508822343c7c682213082348bc7c682140d82369ec7c6822a07822ad1c7c6821db7822c85c7c6820e558232a2c7c6821428823767c7c68216498223d7c7c6821eef821f61c7c68213e7823434c7c682148e82180ec7c6821674822eadc7c682129d821527c7c682327e823284c7c68221c68226e5c7c68225d48235fcc7c6820d91823a05c7c6821e248220a3c7c6822bd2823500c7c6820fe68232e3c7c68223c5823691c7c68234e982371cc7c6821d068222fec7c682159d823140c7c68217d3821da1c7c6822b0e823820c7c6822c6b8236b2c7c6822d2f823572c7c68214498216b6c7c6821e92823c18c7c68215998215a4c7c68212f58213bec7c6821e6282337ac7c6822163822694c7c6821ac2822b32c7c68219a5822bfdc7c6823abf823b73c7c68217d8823316c7c6821e2c822278c7c68236b78238e5c7c68217b182260ac7c682193a822fbac7c6821425822936c7c6822646822cfcc7c6821b15823b8dc7c68227c38228acc7c6822225823616c7c68213b182383dc7c6822670822f86c7c68230e0823a78c7c682288c8237abc7c682296d823803c7c682145e823b7bc7c6822b3682397ac7c68216ca823426c7c682173b822d6dc7c6821695823323c7c6821ac982396ac7c682141f823493c7c68216bb822f37c7c6821f65823669c7c6821699823c65c7c6821648823676c7c682361a823684c7c68214a5822cdfc7c6822a4e8236fdc7c682181c822397c7c6823ae0823b76c7c6823a71823b54c7c6822dd28237c0c7c682172a821a1fc7c68216068239e0c7c6822425823c4cc7c6821d7782307cc7c68217b4823821c7c6821e118223a1c7c68229d5823b3cc7c68217c5823047c7c6821b5a822b76c7c682190c821dc3c7c68238fe8239ebc7c682134782396dc7c6822eb0823582c7c6821571823422c7c6821a2d822c1bc7c68217f0823b6bc7c682188d823baec7c682152b82241cc7c6821ca1822223c7c6822e1e823ac6c7c6822ed0823c73c7c682296b823b73c7c6823b37823b9fc7c682389a823b63c7c6821f0e8228cec7c68219ae823bcbc7c68217cb822b90c7c682343e82393ec7c682138e8222e6c7c68218a682394fc7c6821fdb82276dc7c68217bc823992c7c68230018236d1c7c682155c8216d4c7c6821a22823b5ec7c6821800821c44c7c682143582308cc7c68223b1823994c7c682390a823aedc7c6821475822824c7c6822f4b823020c7c6821a59822b42c7c682191f823706c7c68223cf82361cc7c6822dec82375ec7c6821995822505c7c68219fd821ecbc7c682318082361bc7c6821993823b87c7c6821e30822ce2c7c6821b6d8232abc7c682132a8227cec7c68216488236dac7c68213238239d6c7c6823899823af5c7c682167f823a3cc7c68215f98233f3c7c6821517822dc4c7c6822a47822dedc7c682250282349bc7c68228ec823aedc7c6821a21823831c7c6821c39823353c7c68217268225ffc7c6823c0e823c39c7c6822aab823c9fc7c68215dc82341cc7c682152e8233bac7c682281b8239b4c7c6822bc4822fd9c7c682213882323dc7c6822e89822eb2c7c6822eb8823b9fc7c6822fcc8233c0c7c68214ce82345ec7c682250f8235f0c7c68217dd8230f1c7c68231b7823461c7c6822d67822d72c7c6821642822f52c7c68239b0823c7ec7c68224d682325cc7c6822aea8239adc7c682185c823993c7c6821342823961c7c68216e48227aec7c68214908232b8c7c6821f5a822fe9c7c68238c38239c3c7c6822e3f823791c7c68238bc823c7fc7c68213fb8234a8c7c6821791821b19c7c68220bb8232b4c7c6821360823b7ac7c6821b72822543c7c6822158822620c7c6822f3d823bbfc7c68234cd823a3ac7c68213ec823bf9c7c682188c822358c7c68238cf823ba0c7c6821678822f2ac7c68216af8236cac7c682179e822f64c7c6821b41823c2fc7c68229858229b3c7c6823a85823b05c7c68230268232abc7c682163a8236adc7c68215fd822e8ac7c68224ad823589c7c6822984822d63c7c6822b3c823b42c7c6821c3a8236dcc4c382351dc7c6821724823b3dc7c68222398232a8c7c682339e8233d2c7c682286d8239e1c7c6823c91823c9ac7c6821c208231fbc7c6822d28823a2dc7c6822a31823a7ec7c68229db822f7bc7c682168a82360ec7c682163282315bc7c6821a9d822ae6c7c68217b18237e1c7c6821c3e823233c7c68217928226ccc7c6822663822c3cc7c68214158225b3c7c68233e382364ec7c6822b6e8233c3c7c68224398236a5c7c68218788236e6c7c68219828237a2c7c6822dfc823aecc7c682154082259bc7c6822f73823456c7c68213ea822586c7c682191a822e98c7c6823249823326c7c6821b48823c71c7c68213968224cdc7c68217c2823758c7c6821e98822957c7c68217c9823c70c7c68218e7822ed4c7c68226c38235cfc7c68224568225c1c7c6821b29823a35c7c68219c982294ec7c6821c3a8232aac7c682171e823a21c7c682317e82342dc7c6821e17823930c7c68216f4823bdcc7c68216b4823315c7c6821576823c05c7c682306b823989c7c6821681821f4dc7c682210e822d30c7c68215d98223b5c7c6821697822d72c7c6823695823ca5c7c6823821823a9ac7c6821743823005c7c682303d823b19c7c68213778237dbc7c68230ea823b49c7c682180c8234e5c7c68217b0823b24c7c68234b482379ac7c68234e4823807c7c682164f821ef5c7c6822cb6823307c7c68217bb82267ac7c6822e7482331ac7c68213838237fcc7c68216a9821dfbc7c68215e0822ae3c7c6821482821a39c7c68216d882194ec7c68231ec8239cbc7c682309182327fc7c68221c58227d5c7c6821ab882228ac7c6823166823485c7c68239fe823af0c7c6822a40823996c7c6821da78238b6c7c682162c823542c7c68215b7822ccbc7c6821e3a8237e4c7c682179b823aeac7c68213848231acc7c68214cb823383c7c6822607823311c7c68214d1821d8bc7c682175e823679c7c68235dd82369cc7c6822958823792c7c68216d98217ecc7c6821bdb8239aac7c68216b7822b99c7c68215c7823503c7c682168e82366ac7c6821a58822f98c7c68216f9822e57c7c68227ac823d4ec7c68216cc8221a5c7c682178c82364fc7c6822367822782c7c682226d823a0bc7c682205082235fc7c6821a00823046c7c68217dc821c5ac7c6822963823b7ac7c682137e8235f8c7c68213d98239a7c7c6821f51823201c7c682156582370ec7c6823299823a4dc7c6823513823586c7c68226d2822d55c7c6823926823d16c7c6822be8823b8ac7c6821818823728c7c68214c982314dc7c6821d9f822cedc7c6822f94823b45c7c682179782322bc7c682166a822ebfc7c68213e08234cac7c68234a0823d52c7c682255e823a08c7c6821a6f822cbdc7c682288482323ac7c6822dff823b15c7c682198a823cc4c7c6822c008239b9c7c6820a6e823c9cc7c6821780823a2fc7c6823949823d72c7c6822920823aa2c7c6822d578233d5c7c6822603823a10c7c682146a823c69c7c6822b7f822b81c7c6821f63822f5cc7c682158a823559c7c6822482823519c7c68215b8823c5cc7c682152d822ff9c7c682177b82395fc7c6822ac5822e53c7c68222738233b6c7c6821e618232cfc7c6821c67822188c7c68217e8823890c7c6822d2482333fc7c68233c4823560c7c68217da823c14c7c682231a823b35c7c682177882306ac7c68238c9823ba5c7c6822b6c822e9cc7c682336d823d93c7c682212982264ec7c68215b4823c7cc7c6821555821f93c7c682258b823d20c7c682174f8238d3c7c68214df8227fbc7c682211e8231e7c7c68214058234a3c7c68220518233cac7c6821da9822e45c7c68215c582393cc7c68217e2822e5cc7c68227ca8239a2c7c682301c8238b7c7c68216a7822f25c7c68213a38227bbc7c6821beb82306dc7c6822536822facc7c682165f821aa1c7c682179d8223a9c7c6822941822f9dc7c6821437823564c7c68223928223f2c7c68223f982346cc7c68222ea823003c7c6821c528221d1c7c682160782245ec7c68213d58236e1c7c68230de823736c7c6823a88823ab6c7c682380c8238e2c7c68225bc823686c7c682334f8234f6c7c6821e8382285ac4c382241fc7c68220b8823d64c7c68231ba823d01c7c6821388822fbcc7c6822a55823d67c7c68227da823ac0c7c6822c4f823985c7c68213498230ccc7c6821408823c40c7c6822d7f8234c7c7c68213c282252dc7c6821f15822bf5c7c682219782287ac7c6821c43823435c7c6823871823c46c7c6821335823dc1c7c6821e84822092c7c682145082157ec7c682133e8224e6c7c6822928822b66c7c6821dbd8229fec7c682158b821f74c7c6822f0d823dbfc4c3823265c7c6821cf382351cc7c6822277823c56c7c6822e598239f8c7c68216e68238a1c7c6823338823d84c7c6822d1a823000c7c68229e282392cc7c682160d822cf1c7c68215528232c3c7c6822001823d1dc7c68217df822f7dc7c6822ed9823a9dc7c68220808231dec7c682239f82266cc7c682135a821a64c7c6822ed7823b71c7c6821842823dc3c7c6821462822f37c7c68228b98231c2c7c68219f8823b2dc7c6822b23822e36c7c6821e7a8238b5c7c6821d52823186c7c68226ac8234c3c7c68215f78220c8c7c682155d82320bc7c68217a9823c11c7c6821b82823bcec7c682162782398dc7c68218f2823bbac7c682289f822908c7c68213f0822304c7c6823c99823cafc7c682229a822700c7c682265c8232dac7c6821f7382224fc7c6823a03823c26c7c6822571822f04c7c682178a823940c7c682150d8230b0c7c6822615823a81c7c682241a822849c7c68225648233a9c7c682219e82335fc7c68217b3823b44c7c682160b823056c7c6823a29823b4fc7c6822fcd8231e9c7c68213dc822522c7c6822520823970c7c682281a823460c7c68227ba823a48c7c682168c822e77c7c6822ee5823adac7c682195f8232dcc7c6821d02823c01c7c68214c4822ca3c7c682172a822b8dc7c6821355823d33c7c6822f878235dbc7c682159c823889c7c68213588236b1c7c6822948822eaec7c6821e298232a1c7c6821688822859c7c68213d7823d55c7c6822725823128c7c6823c1b823c74c7c68215678238a9c7c6821efe822a36c7c6822ee68234c1c7c6823338823cd3c7c6821659821d67c7c6821754823438c7c68214718234dfc7c6821423822021c7c6823073823cdec7c682167b823c05c7c6823c4b823c89c7c6821c3b823c9bc7c68230c5823d6bc7c6821c4182368bc7c6823223823744c7c682205e8235a2c7c6823032823b4dc7c6822a30823a59c7c68214c18228bdc7c68233a28234c6c7c68216358231e8c7c6821f14822ccec7c6821782822bb4c7c6821520823a16c7c6821756823301c7c6822d10823306c7c68216b38225adc7c6823733823ab2c7c682326782355cc7c682159f823371c7c68226de823cbec7c682218d823079c7c682286e822980c7c68213ca82354dc7c682376882387bc7c6821d1c823737c7c6823de0823de2c7c68213c48234e5c7c68224eb823421c7c68227eb8239f3c7c6822ca9823b9bc7c6821d3c823c61c7c6821a4582290bc7c6821457823937c7c6821e3f823139c7c68232188238a2c7c682310e823154c7c6821563823babc7c68223ad822634c7c6821580823d27c7c6821451822674c7c682273e82331cc7c682136e8238ecc7c6823451823a2bc7c68215df821867c7c6823a7b823b03c7c68216ea821b08c7c68222a3822e19c7c68215238224e7c7c6821a67821f94c7c6822214823550c7c6821668823c7bc7c6822de0823bbac7c682246282370fc7c6821fc382222dc7c6821c0882311fc7c68214cd82331ec7c682159182238cc7c6821e1e8236f9c7c682333a823c5fc7c6823b69823e37c7c68213d18231c7c7c682168d82185ec7c68235318235aec7c682173f821741c7c6822399823248c7c6822a50823232c7c6821249823757c7c6821be18233cac7c682201e822179c7c6821521821a92c7c68236888236bfc7c6821822823842c7c682247e82357bc7c6822f60823310c7c68215dd822f9ec7c6823d40823d7fc7c6823c77823d2dc7c6821b168237fcc7c6821f4a822888c7c682330c823d65c7c682300f823c12c7c682305c823674c7c6821503822acfc7c6822edd823105c7c6821d588221c0c7c68215e6823284c7c6821332823b74c7c68217ab8231fec7c68232a08235d4c7c6822969823b94c7c68214a48237f2c7c68238e3823a93c7c6822209823e6ac7c68217e7823705c7c682290a823a4fc7c68217f6822cfac7c6821e3b823a25c7c6823c6c823e54c7c6821f57823736c7c6822171823175c7c68227f0823929c7c6822a1d822f4fc7c6822b7a823a43c7c682233f823250c7c68212eb823a37c7c6822471822ccdc7c682299d823aa1c7c6822eeb823d67c7c6822ddd823b9cc7c682302882383cc7c682187a822732c7c6821f7d823668c7c6821f4d821fafc7c6822e03823afec7c68214dc821d99c7c6821da1822eeac7c68217af823911c7c6822af0823bb6c7c6822b73823449c7c6821faa82353bc7c6823786823b06c7c68227bf823096c7c6823a7c823af8c7c682156b8218cbc7c6822d448233cdc7c6821a37822eb7c7c682381a823827c7c68223c482298dc7c6821456822ff1c7c68214fd823eb1c7c682362d823b88c7c68216f5822f16c7c68239b58239f3c7c6822067823e84c7c6822c138238a0c7c6821559821a51c7c6821a60823793c7c6821c4f822ef4c7c68222378238c6c7c68214b0822c3fc7c6821478822a89c7c68226fe82351bc7c682251a8225f0c7c682159382324bc7c68221cd823812c7c682297c823e00c7c682296e822a97c7c6822fc18238b3c7c68235088235fdc7c6821b50822b76c7c682244282352ec7c68216c4823d9fc7c6821938823589c7c6822488823d98c7c6822f9a823caec7c68229c7823bd9c7c6821b0b822c3dc7c68214ed823e31c7c682136782365fc7c682291b823d69c7c68225b1822f88c7c68218e1823e8bc7c6822e3882390ec7c6822ccc82329dc7c68233ac823520c7c68230e782374ec7c6821de48234ffc7c68220e482332ec7c68227fe8235e0c7c6823b09823e66c7c68218348237acc7c6821455823d6cc7c68219dd823bfac7c6821555823c08c7c682144d8237eec7c682113d8233e3c7c682150b822b68c7c68227168236b6c7c6822c6d8234c4c7c68217a4822a0bc7c6822788823840c7c6821998823776c7c68216f68235cac7c68229d4822c32c7c6821c7a8226d9c7c6822fc082321ac7c68217d9823d25c7c6822391823327c7c6821597823874c7c68225ea822ccac7c68237f9823aebc7c68226c5822884c7c682162b822385c7c6821406823496c7c68216a482391bc7c68217e0823787c7c68235118236c2c7c68218718233b0c7c6822089822d76c7c68236aa8237a5c7c682128b8236cbc7c68224b9823468c7c682377c823a8cc7c682201b822f05c7c6822e5a822f57c4c3822810c7c68217f9822940c7c6822d818238ecc7c6821eb08230f3c7c6821eba82341bc7c6823a04823ddcc7c682196d822ddfc7c68236cc823b6fc7c68214a082332fc7c6821757821f2ec7c6822b86823e51c7c6821786822446c7c682160082334dc7c68215c4823e60c7c6822f448235b7c7c68235cd823e80c7c68213f48219c7c7c68238a8823e7fc7c6821f56823c67c7c6821d368233d8c7c68236e7823b15c7c682297d822e79c7c6821ce6823bb4c7c6822a21822cd1c7c682240d822641c7c68217a5821f46c7c68214dd8227b4c7c682153582225fc7c6821712823b95c7c68237de82388bc7c68215a082284ec7c682236c822794c7c68217e5822d23c7c68217f78230bcc7c682240d823788c7c6821463822503c7c6822742823c92c7c6821579823bdfc7c682275b823a3bc7c6822165823de3c7c68217bb8233dbc7c6821f288232d7c7c6822b18822f5ac7c6823912823bb5c7c682194882307dc7c6821815823e91c7c6822b358235d5c7c682316e8233dec7c6823775823a55c7c68217fd823aecc7c68218c98219e4c7c682178b821f8fc7c682140f82371fc7c68216838225a9c7c6821ac1822e67c7c6823c8f823d08c7c6823b0d823e07c7c6821501822877c7c682262c822d27c7c6821566823cc0c7c6821a90822088c7c6822ec9823b23c7c68215b6823451c7c682370482376fc7c68231a9823657c7c68225d8823cf2c7c68227668231b5c7c68216ec82339dc7c68225bd8238a2c7c68239e7823a38c7c6821510822f53c7c6821a52823cc9c7c682176c823ec6c7c68227b0822f73c7c6821646823d49c7c68214ad821eeec7c6823915823a5ec7c68218b08218f4c7c6821542822472c7c6821cee823d0fc7c68222a9823348c7c6821669821acac7c6823934823d96c7c68216e5822504c7c6822a7e822b1bc7c6821a9e823b0bc7c6823ad8823ec8c7c682215f822b40c7c6821ab3821b7fc7c6822c56823575c7c68237a68237b9c7c6822b6a822dabc7c6821784822380c7c682074c823dddc7c68231b68234bbc7c6823d8b823f00c7c68231f8823e5dc7c6821a6d822ee8c7c6822d84823329c7c6821783823d46c7c682184c823129c7c6821653823c1dc7c68233e0823c41c7c6823bfc823c28c7c682290f822af2c7c6821c9b823378c7c6821e2b823351c7c6821ce082398bc7c682162a8226ffc7c6822caa822cfbc7c6822f2e823414c7c6821e2d823775c7c68218ee823812c7c68219d5823117c7c68215ef822096c7c6822474822823c7c682251b823364c7c68238ce823a7fc7c68214bb823ee0c7c68219ce823743c7c6823d53823daac7c682203a823009c7c68238a4823c15c7c68217c782398bc7c6822a96822ec0c7c682155e822717c7c6821a5f823dbbc7c6823433823cb7c7c6821a56823623c7c6821dc4822ff7c7c682311c823329c7c6823c68823f81c7c68223e1823984c7c68218258233f6c7c68218b78238dac7c6822a85823cc7c7c6822c988234cac7c682172f8234dcc7c68235858237e0c7c6823230823f12c7c6823a58823b57c7c6821850823bd0c7c6821f35822ff5c7c682175d82264dc7c68218c08238bec7c6821a1e823363c7c6823206823b88c7c682274e822751c7c6822e23823491c7c6823343823c7dc7c6821a0b822786c7c682311d823e67c7c6822900823c21c7c6823aa1823da7c7c682337e823e2ec7c6821f0a823717c7c682200a82229cc7c68220e3822522c7c6821a19823742c7c6823813823a5fc7c68218e082388ec7c68222508226e7c7c682142e8239bdc7c68231128231b8c7c68225fa8235a1c7c6821957823aaec7c68208ad823befc7c6821abc821d86c7c6821876823650c7c6822d948231abc7c6821d5e82328ac7c6822735823700c7c6822cf582346bc7c682201f822029c7c682374b823bd8c7c682237b8237f5c7c68232bf823e04c7c68220f5822c57c7c68239df823cb9c7c6821eae823e97c7c6821ac68228cbc7c6821f978233c1c7c6823c2a823eb8c7c682147c823167c7c6822bc1823b34c7c6822380823a50c7c6821602822436c7c682183e823038c7c6821304823917c7c682116282156ec7c68219558232b7c7c6821b12823259c7c6821a44823fb2c7c6821b7f822e56c7c6821e6d823908c7c6821b89821e6ac7c68227dd823cbcc7c68219c8822f74c7c68214ff8232ddc7c6822e29823ee0c7c6822de982399ac7c6821934822cc3c7c682177f8233a7c7c682163a823241c7c6821aa6822afec7c682279582373fc7c68239dd823a36c7c68234e9823d1ec7c68225028236d2c7c6821a33822831c7c682192582230bc7c68228128234a1c7c68219498237e9c7c682229c8235dac7c6821aac8239efc7c6822b7d823ce3c7c68222288230d2c7c682190c823c0bc7c6820ebe8238edc7c6821c3f823ec1c7c6821a998228fcc7c6821a31823540c7c68237ec8238b4c7c6821ed48224bac7c6821a98823f44c7c6821846822f2fc7c6821ade82264dc7c682188b823c10c7c6822189822e6dc7c68230d4823c63c7c68219a7823a9fc7c6823881823cf5c7c682183a821af7c7c68232e5823a00c7c682244782361fc7c682195d821b66c7c6822952823935c7c6823ef0823f56c7c6821847823f88c7c682288582354fc7c6820d75821ed9c7c682315f8235f1c7c6821c14822e82c7c68235298237c9c7c6822676823cc5c7c6823017823e6fc7c6822c12823b89c7c6822bcc8237f4c7c6821d9b8232ebc7c682274b822874c7c6821a5e822385c7c6822cc1823245c7c6821a86823a0ac7c682285782307ec7c682372c8237ccc7c68219878232e1c7c6822c09822d7ec7c6821a5b823e24c7c68223e1823867c7c682190a823b1bc7c682195a823f43c7c68219d182219fc7c682193c8220fcc7c6821d4b822ac6c7c682189d823482c7c682370c823987c7c682208c823e39c7c68218f1823662c7c6823e5d823f0bc7c682228f82241bc7c6822376823fb8c7c682192482390dc7c6823f2c823f33c7c6821976822551c7c68218d6822fb7c7c6821f8b823262c7c68227bf823455c7c6821a048226aec7c6822cda8234b5c7c6821988823f40c7c68219cc823a06c7c68222cf823065c7c68230e78230efc7c68219848239eec7c6823e1b823eadc7c6821c2b823e96c7c6822f31823f6ec7c6823832823f74c7c6822af982369ec7c6822cab823761c7c68224bd823dd5c7c682283d823df1c7c682328a823e77c7c6822d12823125c7c68221c5823f92c7c6821ac38234e0c7c68225808227ccc7c6821fae823f73c7c6821aaf8225dac7c68218c3823e3cc7c68239f7823f1cc7c68232d98233ccc7c682262182282dc7c68218f5823c7cc7c68218a48233e7c7c68220c5823969c7c6823472823611c7c6821a848236f7c7c6822956823b08c7c6821840823213c7c68226de823f50c7c682353682364cc7c6822a24823f1cc7c6821abd823a0dc7c6821865823dbac7c68234158235f5c7c6821829823eccc7c6822bd1822c07c7c6823539823631c7c6821a3882350dc7c6822f07823906c7c68236af823e94c7c6821ab0822942c7c6821974821a13c7c68228298238cbc7c68221bb822672c7c6821a29823d89c7c6822d468235dfc7c68218cc8224a1c7c6821ad6821b42c7c6822d4d823df3c7c68218698230edc7c6821b01821d7ac7c6823141823e62c7c68234db823d26c7c682187b8220cbc7c6822a2982384ec7c6821edd823862c7c682185c821e77c7c6822882823b7fc7c682400f824042c7c6822ae5823872c7c682396a823f31c7c682319582373ac7c6821c108224ffc7c68227f7823edec7c6823c3e823c81c7c682189082307dc7c68212c7823ff0c7c6821b6282377bc7c682384f8238e1c7c6821a2e82287dc7c6823b21823cc9c7c68219ed823eefc7c682253282388ac7c68221c18239a0c7c682184b822670c7c6823c19823dc9c7c6821ab0822a26c7c68210f9822b51c7c68222758222c0c7c6822f8a823e9ec7c6823c5e823dfac7c6822746823430c7c6822dc3823fb1c7c6822e06822f6cc7c6823e6c823ffcc7c6822c2b82405cc7c6823c07823cd0c7c6822fbf822fd7c7c6821a03823f93c7c68231bf82335dc7c68219a4823e78c7c6821ece82203dc7c6821839823c8ac7c68239a1823cbec7c68218d282279ec7c682372c82382fc7c6821a79823c17c7c6823ab1823b06c7c682268482269fc7c68224b68234a1c7c6822d88823edac7c6821862823c6bc7c6821a5582406fc7c682311682321ac7c6821abe822db4c7c682206d822499c7c6821975823ca1c7c68219db8236dbc7c68219b68229c8c7c68231ed8239e8c7c682237d8231a7c7c682190b823f01c7c6823b60823c9bc7c6821fdd8230b9c7c682331b823f34c7c6823b5f823bc4c7c6822c24823892c7c6823d11823ecbc7c6822d64823408c7c6821ad58236fec7c68222c2823eb9c7c68219a3823c8fc7c6821a66823d80c7c68218b6822ca7c7c68218c1821f1ac7c68227ea823c31c7c6821a56821adac7c682183b823902c7c68218848239f1c7c6820f74823e91c7c68220b0823be2c7c6822eb6823a98c7c6820ec88231c7c7c6822194823411c7c6821f1382310dc7c6822cb3823ab4c7c6821a8e822491c7c68225a1823300c7c682196f823d5dc7c6823ec7823f76c7c682187f82385fc7c68220c5823340c7c682378f823fc2c7c68224c2823320c7c6821c6e82283fc7c6821929823f71c7c6823671823844c7c6822f50823becc7c6821ee3823875c7c6822e72823636c7c68229c382380cc7c682210b8233ebc7c68230198234e1c7c6821a32822b8cc7c6823f2b824004c7c6821a0f823f04c7c6823555823dbbc7c6820d67822775c7c6821997822386c7c6821ce9823db6c7c6821854823ef0c7c6821ebd823960c7c68230f082349cc7c6823101823cb9c7c6823d45823ee9c7c6821a10822dfac7c6823271823a69c7c6822e44822f3dc7c68235a2824097c7c682194b8237ffc7c6821836823143c7c6821a7282329fc7c682326a8232e3c7c6822324822c45c7c6821c9082312bc7c6822f50823ebbc7c68234258237e5c7c6821e7482377ec7c6822972823d24c7c68225df823d43c7c6822b1d8238c1c7c6823ce7823dabc7c6821d188239a3c7c68226468238b1c7c6823531823d57c7c68232d18240b3c7c68239ac824060c7c6821ab4821da3c7c6821ad5821b57c7c68223e78223f8c7c6821ae6822de3c7c682187c8240b5c7c68234cc823598c7c6821e12823f07c7c6821f6f822828c7c682328882344dc7c6821cb5822e21c7c68222fe822edcc7c6823e85823f47c7c6821c6e823480c7c6821966822cd8c7c6823d07823f95c7c6822b70823e10c7c6822a08822a53c7c6823f0f82406dc7c6821f858234a9c7c6822c368238f0c7c6823092823e53c7c6821060823efcc7c6823855823bfbc7c6821e6082385ac7c68218758239e9c7c6823bf2824016c7c6823bb0823c3bc7c682289a82395fc7c68218c68235d2c7c6823ffb824064c7c68219c4822325c7c68218f3822263c7c682185a8233dfc7c6821a8f8226b5c7c68238fb823ce2c7c682248d82360ec7c68220bd823e08c7c6822e6d823719c7c68219d9822f12c7c6822705823266c7c68219cd823d31c7c6821d25822f54c7c682184a8227dbc7c6821e6b82369cc4c38240f5c7c68237b0823ba6c7c68218d38232a4c7c68218b482333bc7c682186f822684c7c68218ce823f7ac7c68218b9822845c7c68223378235bdc7c6821a748225ddc7c6823a5f823afdc7c6821f19823b83c7c6821ea6823f72c7c6821897823882c7c6822f74823581c7c6822ac1823f29c7c682195182272ac7c6821880823059c7c6821a828232ccc7c68238b98238edc7c682369f8236dec7c682193e8239cec7c6821f8882327cc7c6822e60823076c7c68223b78237fbc7c68218de822d96c7c6821826824054c7c6822238824032c7c68227a28234c2c7c68233608239dec7c682380f823af7c7c68218838240f9c7c68218ef8235fac7c6822f85823e29c7c6821aa9823f60c7c6821a2a823b29c7c682213f822dc8c7c6822c9c822eacc7c682368c8238fec7c6821848823ab0c7c68233ff823fb9c7c6821bb182328bc7c68219bd822f59c7c6823cff823d9ec7c68237ed823ba3c7c68225cf82274fc7c6820e708236c5c7c6822f62823d30c7c6821ec2823de6c7c68221678240f5c7c6821bed8236c9c7c68226968240f2c7c68225d2822644c7c68219ae822a5dc7c6821900823b14c7c6822754822d2ac7c68219b48236abc7c6822ff38238c9c7c6821fd182395ec7c68235dc823bf1c7c682250d823e05c7c68216ac822c07c7c6823255823fbec7c68221078230bcc7c68218c4823778c7c682193b822d09c7c6821b998230c6c7c68221e6823473c7c68219f1822813c7c682187d821cfac7c6821a07823402c7c6821c8b823809c7c682223e82336cc7c68224d78233fec7c6821d238235d7c7c6823c85823e74c7c68237d7824122c7c6821c528227c2c7c68237d38237ebc7c68231558240dac7c6821b92823458c7c68233688233dcc7c6821e50823e3ac7c68218d1823f75c7c6823734824109c7c682381b824151c7c68238f9823f85c7c6822d7a824085c7c68219608236f8c7c68234448239f5c7c68219ec823d76c7c682187a823efac7c6821b6b821c76c7c6821b5f822fb1c7c6823a30823be4c7c6821c01822675c7c68222a0822ceec7c6821b96821d64c7c6821cb4823da6c7c6823886823abcc7c68222dc823b47c7c6822deb823e54c7c6821a46823756c7c68230d8823fd0c7c68231f0823567c7c6822709823ef1c7c6822e13823798c7c6822a6b823dc4c7c68219c2823e5ac7c682197f823f5ac7c682190d8232d1c7c68232368237a5c7c6821df68226a5c7c6821e52823ea7c7c6822939823bb6c7c6822797823385c7c682310582374ac7c682319e823e58c7c6822cb7824168c7c6821d84823ac1c7c6823794823f4cc7c68218f08238cdc7c6822efe82309fc7c6823905823f3cc7c6823c80823cf7c7c6822d95823b09c7c6821fd4823fbdc7c6823a4e824110c7c6821b9b822891c7c6822e7f823b2ac7c6822475823da1c7c6821af58236f0c7c68219968223c1c7c6822272823d6ac7c6822970823bd0c7c682299c823815c7c6821da4823e2ac7c6821af2823751c7c6821bcf823693c7c682258a8240e0c7c6822b79823b67c7c68224d08225d5c7c68223b5823099c7c6823b97823fe7c7c68219e382239ac7c6821877822e3cc7c682314282412cc7c6821c4a823358c7c68218bf8237fec7c6821d858235edc7c682356d824010c7c6822c22823c37c7c6821b68823fccc7c6821f7f822e36c7c6821a6e823f0cc7c68218d9823afcc7c6823f1e823fbcc7c6821aed823fb4c7c6821dfd823d91c7c6821b32823445c7c6821c61821cccc7c6823678823848c7c6821b3f82408bc7c6821b79821f53c7c6821b6f8227fac7c6822620823e05c7c68218a0823f3dc7c6821bf382352dc7c6821afd82267bc7c682253b82346cc7c6822420822d7dc7c68228b2822a69c7c68219f3823b79c7c6822a8f82383bc7c6821b8882418ac7c6821bcc8236d0c7c68230ef8237d4c7c6822547823bd4c7c6821970823a62c7c6821b91822beac7c6821b9e821cefc7c6822683823bc2c7c682210e823738c7c6821932823058c7c6821dc6821fb1c7c682263d82335cc7c6821c5e822b42c7c6821ed6823854c7c68221e58227cfc7c6821746822758c7c6821bae8234d6c7c6823f9a824108c7c68228de823fdbc7c68219458227f2c7c6822841823468c7c6821aa0822a71c7c68232708235a4c7c6821bdf823e2cc7c6823cfa823d04c7c68224bb823fa4c7c6821aa38240afc7c6821608822d0ec7c6821f0b821f18c7c68221f2824048c7c68220dd8222f8c7c68219b8823e21c7c6822360822ccec7c682194f823fc1c7c6823b2d82405dc7c68230d0823453c7c68231f2823e1dc7c6821b5d822e55c7c682263e823edac7c6822c8282323ec7c6822f5b823da9c7c6823e6b824122c7c6821b098230bac7c6823b968241cfc7c6821de9823f6dc7c6821bb28237d9c7c682398e823d7cc7c682366f82418dc7c6822ebd823f9ac7c6821bf9821fe3c7c6821ae7824195c7c6821af7823c58c7c6821b0d823e59c7c6822172822754c7c68228f882401ac7c6821b1f823ba7c7c6822622823d1cc7c6823b3d824139c7c68229718229edc7c68227b1822f42c7c6823552823e63c7c6822c7c82417fc7c6823443823f20c7c6822a2a822e71c7c6823893824081c7c682288a823a34c7c6822f788236a3c7c6821af88232b3c7c6821b7a822fc0c7c682239d823f57c7c6821ba7823b82c7c6821b91823b65c7c6821b558226e5c7c68238d7823ed3c7c6821bd78238f5c7c6822d4e823825c7c6821b8182249bc7c6821dbe824019c7c6821b0c82201cc7c6822ec6823156c7c68220ae823fadc7c6822d82822e9fc7c68237108240d5c7c6822eb8823860c7c6821b6982254ac7c6823b49824091c7c6822eab824143c7c68223fc823c55c7c6822845823b36c7c6821e90823a6ac7c682201a8238ccc7c6821bfb823654c7c68222b4823fe8c7c682209f823e93c7c68225148238a3c7c6821bc582330ac7c68238dc82408dc7c682319d82331dc7c6821b5c824025c7c68240dd8241cdc7c68225be8225c7c7c68239e2823e23c7c6821b0382326ec7c68222b3823420c7c6822a3d823b97c7c6821812823feac7c6821788822531c7c6821bad823e69c7c682241e822538c7c682406a824167c7c6822ad58231d1c7c682206e8241fdc7c6821b20823cf4c7c68230618233fbc7c6822dc8823e0ec7c6823d81823de3c7c6821e5a82390fc7c68223228234c2c7c6821b7682311ac7c6823685823afac7c68226428239d3c7c6823039823c56c7c6821aee823f7cc7c6822f978239dbc7c68228b3823e2bc7c6822712823948c7c6821b4f823e83c7c6821b4d823e53c7c6822593822fa6c7c68220df8240b1c7c6821b52823845c7c6821b04823f03c7c6821b1c822eb1c7c6821b8d822b9fc7c6822f60823590c7c682328d823660c7c68232838236cec7c6821b2a823f56c7c68223dd8239dfc7c6821ba382357bc7c68227ba822959c7c6823064823a28c7c6821bc2823eedc7c6821bbe82400bc7c6821b8f823fdac7c68230d7823168c7c6823441823cd6c7c682206682423ac7c68234de823f91c7c6821bfa823a89c7c68229cf822a87c7c6823b93823c24c7c6822fb28231e6c7c68235b882360bc7c6823a568240d8c7c6823e5f823e99c7c6821b6c821becc7c6823a94824205c7c6822518822d50c7c6821b9382280bc7c68225e3823597c7c6821c07822c19c7c68232bb82336ec7c6822839823eb6c7c6822764823fa3c7c682207f823521c7c6822e86823ae9c7c6821b288238f1c7c6823027824216c7c682235b82361dc7c6822bac823a5bc7c6821aec82366ec7c6821fde82256cc7c6821c298237e7c7c6822adf823e1cc7c682164d82387dc7c6822d238241d9c7c68222068226dbc7c682309b823bf9c7c6821fb08239eec7c6821b7b823228c7c682209f822804c7c6821b73821be6c7c682352f823ebcc7c6821c058233f8c7c68222558228abc7c682361d82403ec7c682263582395cc7c6821c6a8225f6c7c6822cda823d35c7c6823ecf824228c7c682383c823c87c7c6821b15823db6c7c68224b0823333c7c68232d682358fc7c68220f2823708c7c6821b748226c8c7c6822346823d68c7c6821b1a821ce2c7c6822ae0823ff9c7c6822d6e823735c7c682225b82412fc7c6821d2d823c76c7c68226f0822ebac7c6821bda822bdcc7c6822e6082388ec7c682161982377ec7c6821c9e8238f3c7c6821d3782285bc7c6822dc2824143c7c6821b6a823bb2c7c6821b21823feac7c6822e88823895c7c6823715823d14c7c68233dd8236d5c7c6823d5682414cc7c682341a823f69c7c6822fc88240dfc7c682308582354ec7c6821bf082401cc7c6821d0a8232aec7c6822bcb8237aac7c6822cd08233d9c7c682396682396cc7c6821c3e822f17c7c6821b978240a0c7c6821bf582322bc7c682382682382ac7c6821b7c823e00c7c682399c823f68c7c68222d78236a4c7c6821b3c823b84c7c6821b65823e88c7c6822029823669c7c6821f45823130c7c68225a3823281c7c6821af98239cbc7c6823aeb8240a9c7c6822d7f823cccc7c6821e5c8241b3c7c68229ba8238c8c7c6821bd28241d9c7c682323082348fc7c6823953823a64c7c6823c38823d75c7c682236e8235b2c7c682260d823459c7c68228668239cdc7c68220a182415bc7c68238e6823ae7c7c68229b582373ec7c6821b94823620c7c68235a1823bccc7c6821be9823a53c7c68225838231ddc7c6821bd5822c14c7c6822cb8823d2bc7c6822b9d823176c7c682280b823e86c7c6821c74823073c7c6821c4b82422dc7c682354882387dc7c682313d82314ac7c68233f7823c88c7c6822e18823ae7c7c6821d9a823313c7c6821bd9823b36c7c6823806823b37c7c6821c60822224c7c6821ddf823e75c7c6823750823f65c7c6822149823c30c7c6821b9a823bb9c7c6821b70823b39c7c6821f2b823526c7c6822fbe823bd1c4c3821be7c7c68225f1823949c7c6821fc1822f54c7c6821b5a823e4fc7c6822e4e8239d5c7c6821c268224cec7c68239a6823a1ec7c6821e05823859c7c6821eaf822330c7c6822b258238c2c7c6821eac821f3fc7c68221508232c1c7c6821c3c82325ec7c6821c57823077c7c6823317824140c7c6820c8782347ac7c68242838242d0c7c682179282324cc7c682271b8242d2c7c6822ea1822eaac7c6822ae6823f53c4c3823558c7c68223918242d6c7c68234958239a7c7c682307f823d7bc7c6821c02823e8dc7c6821c6c822df6c7c68238b0823b02c7c6821c23823addc7c6821c39822f81c7c6822939823836c7c68230b182423dc4c38233b8c7c68240ce8242e1c7c6820a7782333cc7c6822fed8242e3c7c6822b7c82378bc7c6823805823ee5c7c6823a1f8242c3c7c6822c7d823215c7c682250e824181c7c682276c8242e9c7c6822b6e823f5cc7c68225948242ebc7c6821ee48234fac7c6823a23823da2c7c6822c1e824263c7c6822ec2823675c7c6823d4f823e71c7c68229ff824249c7c68221d982419fc7c6822592822ac2c7c68241978242f4c7c682176b823dbec7c68239368241abc7c68232428233c3c7c6823c348242f8c7c68227e1822f4ac4c3822050c7c682302b8242fbc7c6822c6d823d0ec7c682326b8242fdc7c6821c3d822289c7c6821e00822bc8c7c68237de8238f3c7c6823429823d30c7c68231ee8235f7c7c6823d36824303c7c6821bf4823b48c7c6823714823d63c7c6821c1c824120c7c68233a38240e6c7c68240bb82413ec7c6822321823bc3c7c6823dfa82430ac7c6823498823e36c7c6823bdf82430cc7c6822295822babc7c682361282430ec7c6823dcb824236c7c6822588823369c7c6823ba38241aac7c682224d8241d0c7c6822d48823a8ac7c6823773823934c7c6822ce8823a78c7c6822b04823d0cc7c6822f00823a64c7c68212f78242b0c7c6822ce0824319c7c6822003822cd4c7c6822aeb824135c7c6822048823ccac7c682047f822f1ac7c6822e3082431ec7c6821c2a823de8c7c6821c7c8227cfc7c68224c482264ec7c682416c8241f8c7c68223dd8228a6c7c682393b8241f3c7c6823aef823f1bc7c6821c64823566c7c6821e55823f26c7c6823a34823d75c7c6821c278235aac7c6822e9d82401fc7c6822e728234aac7c6822033823642c7c6822eec823effc7c6822c8e8239e2c7c682338d823883c7c6821c2d822cefc7c6821c1282249fc7c68234008241fbc7c6821fe3823896c7c682249282311ec7c68231aa823483c7c68227e08235e6c7c68227b1822893c7c6822992823ccec7c68221fc8231c1c7c68231d7823ea1c7c6822b59823d9bc7c68221b6823586c7c682325482420ac7c6821e4f823568c7c6821ea7821f8dc7c6823f988242a6c7c6822d02823563c7c6821f508234cdc7c6821d94821ea8c7c6822a10823b4ac7c682251e823205c7c6822f118237c5c7c6821eea822e28c7c6822b7d823bfec7c6821c25823ca5c7c68232d3823d8ec7c68222fb823d97c7c682364a8238c6c7c68234f1823a4cc7c682321b8232f3c7c6822f4f823c79c7c682342182373fc7c6821c6d8242f2c7c6822fe282408ec7c6823bf6824309c7c68236e0824354c7c6821c7382385fc7c682255f822babc7c6821c1a823d4bc7c68224f682333fc7c6821c828220a5c7c6823829824061c7c682259f822736c7c6823eea824107c7c6823bf3823ea3c7c6822049823668c7c6821c378233f7c4c3824230c7c6823246824361c7c68226e7823dc2c7c6821c2b82383ac7c68231c3823c03c7c6823eae824365c7c68234848242b7c7c682200b8223d8c7c682215b823fdfc7c6821c14821fdac4c382223dc7c682279382436bc7c682283a823e4dc7c6823b86823cb0c7c6821c19824066c7c6820ec182396bc7c682433e824370c4c3824358c7c68235aa824372c7c6821dbf82304fc7c6823e40823fc8c7c68222c6823698c7c6821e588234ebc7c68230fe824377c7c6821cae824265c7c68227bc8241b9c7c6821f1b8238acc7c6821c38822bd5c7c6822c8b823ca9c7c682260e823209c4c3820497c7c68240bf82437fc7c682207982428fc7c6823dd7824381c7c6822a378238aac7c6821c54823a6cc7c6821f90822ca0c7c6823dfd82437fc7c68236b9824386c7c6821c8c823cdfc7c6821e76822da3c7c6821f77824204c7c6822e1482401cc7c682213b8234e8c7c6822806823ad0c7c6821c35823908c7c68228c08230a8c4c3823414c7c68221d6824390c7c6823e57823f40c7c6821d63823d2ac7c682315882347bc7c6822ce4823343c7c6821c47823b33c7c68239d5823ed4c7c68215be824384c7c6821d8b823c4dc7c6823b0f824399c7c6821c22823e3dc7c68236bd8242c1c7c6822d1782439cc7c68212fc8236f6c7c68239a682439ec7c6822d3b8241d5c7c6821d798243a0c4c38235f9c7c6823c858243a2c7c682309e823e4bc7c6822c698243a4c7c68237188240cac7c6823352824395c7c682147c8235f2c7c6822c688243a8c7c6821c46822ea2c7c6822701823f6bc7c6823ff48243abc7c6823f70823ff7c7c6821fb5824071c7c68232fb8242a5c7c6821f30823667c7c6823652824276c7c6823fc78242a0c7c682255b8231d3c7c6821e3b8236f5c7c6822328822819c7c68232a082339bc7c68232b282429cc7c6822732822c84c7c6821f0182329ac7c6822041823f91c7c6821fcf8235d6c7c6821e4982316bc7c6821e8b8239e7c7c6821cba82326cc7c6822e6f8239edc7c6821e4d823588c7c68233d48239e3c7c682237382343cc7c68224a9823a17c7c6823df0824151c7c6823c6b824274c7c6821dd4822e0cc7c68231948240d4c7c68221e1823107c7c6821f27824328c7c68220838230f4c7c6822762822cf7c7c6821c958243bbc7c68222dc822cbec7c6821db68242f3c7c682309c8233dfc7c6821d728241b5c7c682379882410ec7c6823cfe823df1c7c682306b824346c7c6822d3f8233a8c7c682374482429ac7c682202f82434ac7c6822b38824050c7c6823a33823ed9c7c682276a823fc4c7c6823429823f71c7c6822b65822cadc7c6821d4f824365c7c682243c823a39c7c6821cc4823d23c7c68222da823fecc7c6821f998225efc7c68221a682405ac7c68239c4823c8ac7c682254d822e4fc7c6822f72823ff3c7c68231bb824255c7c6823114823624c7c6821dae823bf6c7c68229aa823b65c7c6822a20822ecfc7c6821caf823900c7c6822783822c29c7c68223a88234e7c7c6822d3c82429ec7c6821f4b8220aec7c6821d45823895c7c6821f968226adc7c6821d3882270dc7c6823d4b824003c7c682363082409fc7c6821d0e822ee1c7c6821d10823c9cc7c6822133823707c7c6821e20822074c7c68227f082438bc7c6823f8e823fe1c7c6821cd3823a59c7c68223bd822d98c7c6822c0b8243dac7c68223fb823c72c7c6821fa98239f6c7c6822162823536c7c6821fbf823406c7c6822622823243c7c68234cf823f32c7c6822d8b822ddec7c6823ac3823b18c7c682202682434bc7c6821d4c823e41c7c6823fe182439fc7c6821cd7823aaac7c6822061823ab7c7c68222ef822678c7c68226cd823df8c7c6822b178240d7c7c6822047823165c7c6821d9382368dc7c68234a58241dac7c6821fd7823ea4c7c682268582431bc7c6822044824156c7c6823c368241c7c7c6821f788233c2c7c68233a6823f3dc7c682200782385ec7c6821dba823bd2c7c68224ee822d34c7c682298a824138c7c68237d28241c0c7c68231c682329fc7c682200682271ac7c6821c97823791c7c6822718822c0ac7c6821eb0823cefc7c682318c823ac0c7c682205e822637c7c682231f8238a3c7c682202c823d1fc7c6823f3f823f5bc7c682215382395cc7c6823a948241f7c7c6822b62824254c7c6823e11824144c7c6823048823c0ec7c6821e0a823db8c7c6822adb824008c7c6822386823403c7c68226ee823fc4c7c682218f824182c7c6821e598235f4c7c68237ba8240f4c7c6823927824246c7c68237538242a7c7c6821dcd823de7c7c6821e5e822630c7c6822a8d8231b9c7c6821c898236d6c7c6821f70823909c7c68227ec82333dc7c6821ef08231f7c7c682323c823e45c7c6821df1822f31c7c6823383824423c7c6822e4d8238f7c7c6821ce58224b2c7c6823d44823dd6c7c68228bf824012c7c6821d9282315ac7c682385d823955c7c682324482416fc7c6822241822778c7c6823c91823ccbc7c6823119823c10c7c6823790824198c7c6821ff28238ebc7c6821e21823a47c7c682280e82335dc7c6823d9e824051c7c6822dcc822dd1c7c6821d54823b8dc7c6821f8482426fc7c6821eca823044c7c682233082289cc7c6821f238233d5c7c6821d6a823c2fc7c68228fe824156c7c6822b738237c6c7c682203c8235dac7c682233f823486c7c6821e938229f4c7c6822c74823636c7c6822037823fb6c7c6823721823e64c7c6822f8b822fc1c7c682333182366dc7c6821eae821f3cc7c6824065824203c7c682206782420dc7c6821e69823bbec7c6821d2582323bc7c6823c3f82424dc7c6821efa823ed7c7c68241b18242b8c7c6821ca0824316c7c6821e1d824170c7c682357d823ca0c7c68239fb823f18c7c6824091824297c7c682295a82372ec7c682206e823406c7c6821dc7822a89c7c6822056823f0bc7c682284b8236dcc7c6821adb822590c7c6821d628227aac7c6821f4482320fc7c6821d89822c70c7c6821f3e823b90c7c6823a84823aa3c7c682273c823c11c7c68236c382415ec7c6822724823663c7c6823bb8823f21c7c6821f6d822310c7c6823c308240dbc7c6822c808236cbc7c6823198823665c7c68234378243c4c7c6822d4982325dc7c6822069823376c7c6821d218233e6c7c682229982417dc7c6823c20823fc1c7c6821fbd823f30c7c68218c5822dfdc7c6822000822ca4c7c68232438235e6c7c6821e0d824153c7c6821e7c823d17c7c68241c282421dc7c6821e1e823f64c7c6822297823084c7c6823cbb824092c7c6821e878238a6c7c6821cdc822648c7c6821de4823aa8c7c6821d158232cdc7c6821fc2822a9ec7c6821d17821fdbc7c68236f1823978c7c6821ddc8243c2c7c6821dab823327c7c6822a7b823accc7c682296e823bf0c7c68222e7822cb5c7c6821dd1823b86c7c6821d24821f43c7c6823c418242c3c7c68225aa823edbc7c6821d528231b0c7c6821caa82410dc7c6821fab82242cc7c6822aad823b12c7c6821d7f823c66c7c6822253823388c7c68226df82444bc7c6822e6f823fdec7c6822f4382418ec7c6823ae6823b6ac7c6821de682345dc7c6822a03823a0fc7c6821fa3823bdec7c6821db28244a6c7c6821f57822c68c7c6822e94824419c7c682271982336dc7c682203682393fc7c6822211822d4cc7c6821e81823289c7c6821e318243c5c7c6821f33822ecec7c6823850823cf4c7c6823971824278c7c68231698233f9c7c6822f09823488c7c6822ad6823745c7c68232b5823f4fc7c6822618823c08c7c6821ca4821e6ec7c6821e9b8241ebc7c682205a82341ac7c6823600823bc8c7c6821f8182378dc7c6821cfc823ab0c7c6823c04823c0fc7c68232618242fbc7c6823bd782418bc7c68221a4823239c7c6823c63823ee3c7c682376d823b2cc7c6821db8823ff0c7c68237c2824134c7c6821d058232e9c7c6822a8a8237bdc7c68232ba823674c7c682265d8234e2c7c6823392823440c7c6822f21823055c7c68243448243b1c7c6821dc58234aec7c68222b282312ac7c6821cb1823517c7c6821f8c82355dc7c68241248241f2c7c6823c268241bfc7c682350c823efbc7c6821cc6823724c7c6822ebb823dcec7c682399e8241a6c7c682211d823dffc7c6822dbf823c60c7c6822020823ba8c7c6821e9682380bc7c68231d0823ed7c7c682379a823c29c7c6821e08823f7cc7c682288b82384fc7c6821eff823422c7c6823858823cbdc7c6822ebc823d38c7c6821e888240b0c7c68236da82394cc7c6822326824281c7c6821ed3824265c7c6822dca822e8fc7c6823036823269c7c6821d42823412c7c6823ab38242b5c7c68221118241cac7c6822468822649c7c6823d50823dcac7c68238cd8240bac7c6823fa6824430c7c6821f40823dd6c7c6823375823452c7c6823db4824200c7c68220fb823a8cc7c6821ff3823297c7c6821d098237f9c7c6822c76824070c7c68237018241a3c7c6822b9e823962c7c682405382428bc7c6821e33822fd7c7c682429182444cc7c6823c93824269c7c682204d823b30c7c6821f03823ec0c7c6822465823601c7c682400b82428cc7c6822c2b822dcec7c6821c8f822ce2c7c68219ff823f15c7c6823db282417ac7c6821e8e823ed0c7c6821cdb8227c7c7c682276f823d0fc7c6824183824410c7c6823517823954c7c6821cb3823bbdc7c68236d2823c6dc7c68224a88233f1c7c6821e2d822ef5c7c6823bdc8243aec7c6822f01823d12c7c6821ebb8231d6c7c68226f182312ec7c6822093822fffc7c68222c1822608c7c6822cc4823d47c7c68221c2823c43c7c6821ca282435bc7c6821ee58235a0c7c6821cea82406cc7c6822ea8824476c7c68222d58236dbc7c68222d08233d6c7c6822f19823bcbc7c68229dc823b7bc7c68235cd82418cc7c6821e35822cd7c7c6821e6882314cc7c6821ebe822dadc7c68233558242afc7c682202e8237b3c7c68223fc8231fdc7c6821f82823eddc7c682247982286bc7c68244bb8244c6c7c68237c3824300c7c682215782443cc7c6821fcc822dcfc7c6822c3a8235b4c7c6822c36823516c7c6822be78244bac7c6821f8082427ac7c682278d8242c2c7c68226c88230dec7c6821e358240d6c7c68236d18243d3c7c68239a5823e15c7c6823d94824074c7c6821ce1823f2ac7c6821cec8237f8c7c68220648228f0c7c68231fa82337fc7c68228c9823c50c7c68234ed823efbc7c6821e9f823f14c7c682251f822587c7c6821faa823ffac7c68228478244a1c7c6821eb4823b5fc7c6822b6d824020c7c6821cbb823f3ec7c6822c0c823082c7c6821fef824259cac9821b34823dcb823fe9c7c6821cf582233ec7c6822484824049c7c6823782823926c7c6821e7f822e8ec7c6822989824229c7c6823085823ee4c7c6822b21823afdc7c682217e82280ac7c6821f63822de8c7c6821dd382357ec7c682321c823d3cc7c6821de8823a31c7c68228198238a8c7c682219282333ac7c68231848234fdc7c6823dd8824363c7c6821fa58221b6c7c68233b58234f3c7c6823e44824063c7c682218d823623c7c6823154823a51c7c6823cf882425ec7c6821da0822d43c7c6821e1a823d8cc7c6821ef18241b7c7c6821d51823234c7c68233bb82448ec7c6821ce78230e9c7c682397c823ebac7c6823ebf82446fc7c6823fac8242bcc7c6821ceb82388fc7c6821d78822b49c7c6821df38238a4c7c6821f918220bac7c6821f5c824056c7c6822072823a5bc7c6822056823eb2c7c6823c3b824095c7c682272882405ec7c68220d482417bc7c682354782454fc7c682376e8242d5c7c6822e738241d6c7c6821fb2823121c7c682243e8229f8c7c68237aa823df7c7c6821e4182222ec7c6821ddb822427c7c682251c822584c7c6822be5823919c7c682379c823d60c7c68220e2823303c7c6821f26823db0c7c6822ad282371dc7c6821ef982399fc7c6821e10822c26c7c68234968239e6c7c68221e6823cadc7c682296982436fc7c6822993823f8dc7c6823d8282400cc7c68221f5824340c7c68231718231f3c7c6823938823dd3c7c682266c82309fc7c682343a8243cdc7c68237cd8239acc7c6822476824201c7c682204f823afbc7c682200c8243a9c7c68243ac82449ac7c6823b2282442bc7c6821d1482389dc7c6823f4e82404cc7c68231b98236b5c7c6821d688239bfc7c68232f28236d3c7c6821d438236b2c7c6821e7e823c53c7c6821fe182314cc7c6822019824258") +} diff --git a/core/pevm_processor.go b/core/pevm_processor.go new file mode 100644 index 0000000000..83c5ef65cb --- /dev/null +++ b/core/pevm_processor.go @@ -0,0 +1,415 @@ +package core + +import ( + "errors" + "fmt" + "runtime" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/metrics" + + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/consensus/misc" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +type PEVMProcessor struct { + StateProcessor + allTxReqs []*PEVMTxRequest + delayGasFee bool + commonTxs []*types.Transaction + receipts types.Receipts + debugConflictRedoNum uint64 + unorderedMerge bool +} + +func newPEVMProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *PEVMProcessor { + processor := &PEVMProcessor{ + StateProcessor: *NewStateProcessor(config, bc, engine), + unorderedMerge: bc.vmConfig.EnableParallelUnorderedMerge, + } + initParallelRunner(bc.vmConfig.ParallelTxNum) + log.Info("Parallel execution mode is enabled", "Parallel Num", ParallelNum(), + "CPUNum", runtime.GOMAXPROCS(0), "unorderedMerge", processor.unorderedMerge) + return processor +} + +type PEVMTxResult struct { + txReq *PEVMTxRequest + receipt *types.Receipt + slotDB *state.UncommittedDB + gpSlot *GasPool + evm *vm.EVM + result *ExecutionResult + originalNonce *uint64 + err error +} + +type PEVMTxRequest struct { + txIndex int + baseStateDB *state.StateDB + tx *types.Transaction + gasLimit uint64 + msg *Message + block *types.Block + vmConfig vm.Config + usedGas *uint64 + useDAG bool + value int // only for unit test +} + +// resetState clear slot state for each block. +func (p *PEVMProcessor) resetState(txNum int, statedb *state.StateDB) { + statedb.PrepareForParallel() + p.allTxReqs = make([]*PEVMTxRequest, txNum) +} + +// hasConflict conducts conflict check +func (p *PEVMProcessor) hasConflict(txResult *PEVMTxResult) error { + slotDB := txResult.slotDB + if txResult.err != nil { + log.Warn("execute result failed, HasConflict due to err", "err", txResult.err) + return txResult.err + } + // check whether the slot db reads during execution are correct. + if err := slotDB.ConflictsToMaindb(); err != nil { + atomic.AddUint64(&p.debugConflictRedoNum, 1) + log.Debug("executed result conflicts", "err", err) + return err + } + return nil +} + +// executeInSlot do tx execution with thread local slot. +func (p *PEVMProcessor) executeInSlot(maindb *state.StateDB, txReq *PEVMTxRequest) *PEVMTxResult { + slotDB := state.NewUncommittedDB(maindb) + blockContext := NewEVMBlockContext(txReq.block.Header(), p.bc, nil, p.config, slotDB) // can share blockContext within a block for efficiency + txContext := NewEVMTxContext(txReq.msg) + vmenv := vm.NewEVM(blockContext, txContext, slotDB, p.config, txReq.vmConfig) + + rules := p.config.Rules(txReq.block.Number(), blockContext.Random != nil, blockContext.Time) + slotDB.Prepare(rules, txReq.msg.From, vmenv.Context.Coinbase, txReq.msg.To, vm.ActivePrecompiles(rules), txReq.msg.AccessList) + + // gasLimit not accurate, but it is ok for block import. + // each slot would use its own gas pool, and will do gas limit check later + gpSlot := new(GasPool).AddGas(txReq.gasLimit) // block.GasLimit() + + on := txReq.tx.Nonce() + if txReq.msg.IsDepositTx && p.config.IsOptimismRegolith(vmenv.Context.Time) { + on = slotDB.GetNonce(txReq.msg.From) + } + + slotDB.SetTxContext(txReq.tx.Hash(), txReq.txIndex) + evm, result, err := pevmApplyTransactionStageExecution(txReq.msg, gpSlot, slotDB, vmenv, p.delayGasFee) + txResult := PEVMTxResult{ + txReq: txReq, + receipt: nil, + slotDB: slotDB, + err: err, + gpSlot: gpSlot, + evm: evm, + result: result, + originalNonce: &on, + } + return &txResult +} + +// to confirm one txResult, return true if the result is valid +// if it is in Stage 2 it is a likely result, not 100% sure +func (p *PEVMProcessor) toConfirmTxIndexResult(txResult *PEVMTxResult) error { + txReq := txResult.txReq + if !p.unorderedMerge { + // If we do not use a DAG, then we need to check for conflicts to ensure correct execution. + // When we perform an unordered merge, we cannot conduct conflict checks + // and can only choose to trust that the DAG is correct and that conflicts do not exist. + if err := p.hasConflict(txResult); err != nil { + log.Info(fmt.Sprintf("HasConflict!! block: %d, txIndex: %d\n", txResult.txReq.block.NumberU64(), txResult.txReq.txIndex)) + return err + } + } + + // goroutine unsafe operation will be handled from here for safety + gasConsumed := txReq.gasLimit - txResult.gpSlot.Gas() + if gasConsumed != txResult.result.UsedGas { + log.Error("gasConsumed != result.UsedGas mismatch", + "gasConsumed", gasConsumed, "result.UsedGas", txResult.result.UsedGas) + return fmt.Errorf("gasConsumed != result.UsedGas mismatch") + } + + // ok, time to do finalize, stage2 should not be parallel + txResult.receipt, txResult.err = pevmApplyTransactionStageFinalization(txResult.evm, txResult.result, + *txReq.msg, p.config, txResult.slotDB, txReq.block, + txReq.tx, txReq.usedGas, txResult.originalNonce) + return nil +} + +// wait until the next Tx is executed and its result is merged to the main stateDB +func (p *PEVMProcessor) confirmTxResult(statedb *state.StateDB, gp *GasPool, result *PEVMTxResult) error { + checkErr := p.toConfirmTxIndexResult(result) + // ok, the tx result is valid and can be merged + if checkErr != nil { + return checkErr + } + + if err := gp.SubGas(result.receipt.GasUsed); err != nil { + log.Error("gas limit reached", "block", result.txReq.block.Number(), + "txIndex", result.txReq.txIndex, "GasUsed", result.receipt.GasUsed, "gp.Gas", gp.Gas()) + return fmt.Errorf("gas limit reached") + } + + var root []byte + header := result.txReq.block.Header() + + isByzantium := p.config.IsByzantium(header.Number) + isEIP158 := p.config.IsEIP158(header.Number) + if err := result.slotDB.Merge(isByzantium || isEIP158); err != nil { + // something very wrong, should not happen + log.Error("merge slotDB failed", "err", err) + return err + } + result.slotDB.Finalise(isByzantium || isEIP158) + + delayGasFee := result.result.delayFees + // add delayed gas fee + if delayGasFee != nil { + if delayGasFee.TipFee != nil { + statedb.AddBalance(delayGasFee.Coinbase, delayGasFee.TipFee) + } + if delayGasFee.BaseFee != nil { + statedb.AddBalance(params.OptimismBaseFeeRecipient, delayGasFee.BaseFee) + } + if delayGasFee.L1Fee != nil { + statedb.AddBalance(params.OptimismL1FeeRecipient, delayGasFee.L1Fee) + } + } + + // Do IntermediateRoot after mergeSlotDB. + if !isByzantium { + root = statedb.IntermediateRoot(isEIP158).Bytes() + } + result.receipt.PostState = root + p.receipts[result.txReq.txIndex] = result.receipt + p.commonTxs[result.txReq.txIndex] = result.txReq.tx + return nil +} + +// Process implements BEP-130 Parallel Transaction Execution +func (p *PEVMProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { + var ( + usedGas = new(uint64) + header = block.Header() + gp = new(GasPool).AddGas(block.GasLimit()) + ) + + // Mutate the block and state according to any hard-fork specs + if p.config.DAOForkSupport && p.config.DAOForkBlock != nil && p.config.DAOForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyDAOHardFork(statedb) + } + if p.config.PreContractForkBlock != nil && p.config.PreContractForkBlock.Cmp(block.Number()) == 0 { + misc.ApplyPreContractHardFork(statedb) + } + + misc.EnsureCreate2Deployer(p.config, block.Time(), statedb) + + allTxs := block.Transactions() + p.resetState(len(allTxs), statedb) + + var ( + // with parallel mode, vmenv will be created inside of slot + blockContext = NewEVMBlockContext(block.Header(), p.bc, nil, p.config, statedb) + vmenv = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) + signer = types.MakeSigner(p.bc.chainConfig, block.Number(), block.Time()) + ) + + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) + } + statedb.MarkFullProcessed() + txDAG := cfg.TxDAG + + txNum := len(allTxs) + // Iterate over and process the individual transactions + p.commonTxs = make([]*types.Transaction, txNum) + p.receipts = make([]*types.Receipt, txNum) + p.debugConflictRedoNum = 0 + + for i, tx := range allTxs { + // parallel start, wrap an exec message, which will be dispatched to a slot + txReq := &PEVMTxRequest{ + txIndex: i, + baseStateDB: statedb, + tx: tx, + gasLimit: block.GasLimit(), // gp.Gas(). + block: block, + vmConfig: cfg, + usedGas: usedGas, + useDAG: txDAG != nil, + } + p.allTxReqs[i] = txReq + } + p.delayGasFee = true + if txDAG != nil && !txDAG.DelayGasFeeDistribution() { + p.delayGasFee = false + } + + // parallel execution + start := time.Now() + txLevels := NewTxLevels(p.allTxReqs, txDAG) + log.Debug("txLevels size", "txLevels size", len(txLevels)) + parallelTxLevelsSizeMeter.Update(int64(len(txLevels))) + buildLevelsDuration := time.Since(start) + var executeDurations, confirmDurations int64 = 0, 0 + err, txIndex := txLevels.Run(func(pr *PEVMTxRequest) (res *PEVMTxResult) { + defer func(t0 time.Time) { + atomic.AddInt64(&executeDurations, time.Since(t0).Nanoseconds()) + }(time.Now()) + + if err := buildMessage(pr, signer, header); err != nil { + return &PEVMTxResult{txReq: pr, err: err} + } + return p.executeInSlot(statedb, pr) + }, func(pr *PEVMTxResult) (err error) { + defer func(t0 time.Time) { + atomic.AddInt64(&confirmDurations, time.Since(t0).Nanoseconds()) + }(time.Now()) + log.Debug("pevm confirm", "txIndex", pr.txReq.txIndex) + return p.confirmTxResult(statedb, gp, pr) + }, p.unorderedMerge) + parallelRunDuration := time.Since(start) - buildLevelsDuration + if err != nil { + tx := allTxs[txIndex] + log.Error("ProcessParallel tx failed", "txIndex", txIndex, "txHash", tx.Hash().Hex(), "err", err) + return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", txIndex, tx.Hash().Hex(), err) + } + + // len(commonTxs) could be 0, such as: https://bscscan.com/block/14580486 + var redoRate int = 0 + if len(p.commonTxs) == 0 { + redoRate = 100 * (int(p.debugConflictRedoNum)) / 1 + } else { + redoRate = 100 * (int(p.debugConflictRedoNum)) / len(p.commonTxs) + } + pevmBuildLevelsTimer.Update(buildLevelsDuration) + pevmRunTimer.Update(parallelRunDuration) + log.Info("ProcessParallel tx all done", "block", header.Number, "usedGas", *usedGas, + "parallelNum", ParallelNum(), + "buildLevelsDuration", buildLevelsDuration, + "parallelRunDuration", parallelRunDuration, + "executeDurations", time.Duration(executeDurations), + "confirmDurations", time.Duration(confirmDurations), + "txNum", txNum, + "len(commonTxs)", len(p.commonTxs), + "conflictNum", p.debugConflictRedoNum, + "redoRate(%)", redoRate, + "txDAG", txDAG != nil) + if metrics.EnabledExpensive { + parallelTxNumMeter.Mark(int64(len(p.commonTxs))) + parallelConflictTxNumMeter.Mark(int64(p.debugConflictRedoNum)) + } + + // Fail if Shanghai not enabled and len(withdrawals) is non-zero. + withdrawals := block.Withdrawals() + if len(withdrawals) > 0 && !p.config.IsShanghai(block.Number(), block.Time()) { + return nil, nil, 0, errors.New("withdrawals before shanghai") + } + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + p.engine.Finalize(p.bc, header, statedb, p.commonTxs, block.Uncles(), withdrawals) + + var allLogs []*types.Log + var lindex = 0 + var cumulativeGasUsed uint64 + for _, receipt := range p.receipts { + // reset the log index + for _, oneLog := range receipt.Logs { + oneLog.Index = uint(lindex) + lindex++ + } + // re-calculate the cumulativeGasUsed + cumulativeGasUsed += receipt.GasUsed + receipt.CumulativeGasUsed = cumulativeGasUsed + allLogs = append(allLogs, receipt.Logs...) + } + return p.receipts, allLogs, *usedGas, nil +} + +func buildMessage(txReq *PEVMTxRequest, signer types.Signer, header *types.Header) error { + if txReq.msg != nil { + return nil + } + msg, err := TransactionToMessage(txReq.tx, signer, header.BaseFee) + if err != nil { + return err + //return fmt.Errorf("could not apply tx %d [%v]: %w", txReq.txIndex, txReq.tx.Hash().Hex(), err) + } + txReq.msg = msg + return nil +} + +func pevmApplyTransactionStageExecution(msg *Message, gp *GasPool, statedb *state.UncommittedDB, evm *vm.EVM, delayGasFee bool) (*vm.EVM, *ExecutionResult, error) { + // Create a new context to be used in the EVM environment. + txContext := NewEVMTxContext(msg) + evm.Reset(txContext, statedb) + + // Apply the transaction to the current state (included in the env). + var ( + result *ExecutionResult + err error + ) + if delayGasFee { + result, err = ApplyMessageDelayGasFee(evm, msg, gp) + } else { + result, err = ApplyMessage(evm, msg, gp) + } + + if err != nil { + return nil, nil, err + } + + return evm, result, err +} + +func pevmApplyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg Message, config *params.ChainConfig, statedb *state.UncommittedDB, block *types.Block, tx *types.Transaction, usedGas *uint64, nonce *uint64) (*types.Receipt, error) { + *usedGas += result.UsedGas + // Create a new receipt for the transaction, storing the intermediate root and gas used by the tx. + receipt := &types.Receipt{Type: tx.Type(), PostState: nil, CumulativeGasUsed: *usedGas} + if result.Failed() { + receipt.Status = types.ReceiptStatusFailed + } else { + receipt.Status = types.ReceiptStatusSuccessful + } + receipt.TxHash = tx.Hash() + receipt.GasUsed = result.UsedGas + + if msg.IsDepositTx && config.IsOptimismRegolith(evm.Context.Time) { + // The actual nonce for deposit transactions is only recorded from Regolith onwards and + // otherwise must be nil. + receipt.DepositNonce = nonce + // The DepositReceiptVersion for deposit transactions is only recorded from Canyon onwards + // and otherwise must be nil. + if config.IsOptimismCanyon(evm.Context.Time) { + receipt.DepositReceiptVersion = new(uint64) + *receipt.DepositReceiptVersion = types.CanyonDepositReceiptVersion + } + } + if tx.Type() == types.BlobTxType { + receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) + receipt.BlobGasPrice = evm.Context.BlobBaseFee + } + // If the transaction created a contract, store the creation address in the receipt. + if msg.To == nil { + receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, *nonce) + } + // Set the receipt logs and create the bloom filter. + receipt.Logs = statedb.PackLogs(block.NumberU64(), block.Hash()) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + receipt.BlockHash = block.Hash() + receipt.BlockNumber = block.Number() + receipt.TransactionIndex = uint(statedb.TxIndex()) + return receipt, nil +} diff --git a/core/pevm_processor_test.go b/core/pevm_processor_test.go new file mode 100644 index 0000000000..1804243ac8 --- /dev/null +++ b/core/pevm_processor_test.go @@ -0,0 +1,265 @@ +package core + +import ( + "context" + "crypto/ecdsa" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "math/big" + "os" + "runtime" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" +) + +type parallel chan func() + +func (p parallel) do(f func()) { + p <- f +} + +func (p parallel) close() { + close(p) +} + +func (p parallel) start(pnum int) { + for i := 0; i < pnum; i++ { + go func() { + for f := range p { + f() + } + }() + } +} + +type keypair struct { + key *ecdsa.PrivateKey + addr common.Address +} + +func randAddress() (common.Address, *ecdsa.PrivateKey) { + // Generate a new private key using rand.Reader + key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("Failed to generate private key: %v", err)) + } + return crypto.PubkeyToAddress(key.PublicKey), key +} + +func generateAddress(num int) []*keypair { + proc := parallel(make(chan func())) + proc.start(16) + address := make([]*keypair, num) + wait := sync.WaitGroup{} + wait.Add(num) + for i := 0; i < num; i++ { + index := i + proc.do(func() { + addr, key := randAddress() + address[index] = &keypair{key, addr} + wait.Done() + }) + } + wait.Wait() + proc.close() + return address +} + +func genesisAlloc(addresses []*keypair, funds *big.Int) GenesisAlloc { + alloc := GenesisAlloc{} + for _, addr := range addresses { + alloc[addr.addr] = GenesisAccount{Balance: funds} + } + return alloc +} + +func TestPevmInsertChain(t *testing.T) { + //from := 8000 + //to := 8001 + //endpoint := "https://opbnb-qanet-ec-5-seq-pevm-2.bk.nodereal.cc" + //gJson := "/Users/awen/Desktop/Nodereal/aweneagle_projects/chain-infra/qa/gitops/qa-us/opbnb-qanet-ec-5/contracts-info/genesis.json" + //genesis := loadGenesis(gJson) + //blocks := fetchBlocks(uint64(from), uint64(to), endpoint) + ////preapare parent header + //chain := buildBlockChain(genesis, false) + //chain.hc.headerCache.Add(blocks[0].Hash(), blocks[0].Header()) + //if err := InsertChain(chain, blocks[1:]); err != nil { + // t.Fatal(err) + //} +} + +func fetchBlocks(from, to uint64, endpoint string) []*types.Block { + client, err := ethclient.Dial(endpoint) + if err != nil { + panic(err) + } + defer client.Close() + + var blocks []*types.Block + for i := from; i <= to; i++ { + block, err := client.BlockByNumber(context.Background(), big.NewInt(int64(i))) + if err != nil { + panic(err) + } + blocks = append(blocks, block) + } + return blocks +} + +func loadGenesis(jsonfile string) *Genesis { + // Open the JSON file + file, err := os.Open(jsonfile) + if err != nil { + panic("failed to open json file, err=" + err.Error()) + } + defer file.Close() + + // Read the file content + bytes, err := io.ReadAll(file) + if err != nil { + panic(fmt.Sprintf("failed to read genesis file: %v", err)) + } + + // Unmarshal the JSON content into the Genesis struct + var genesis Genesis + if err := json.Unmarshal(bytes, &genesis); err != nil { + panic(fmt.Sprintf("failed to unmarshal genesis JSON: %v", err)) + } + return &genesis +} + +func buildBlockChain(genesis *Genesis, parallel bool) *BlockChain { + archiveDb := rawdb.NewMemoryDatabase() + // Import the chain as an archive node for the comparison baseline + archive, err := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), genesis, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: parallel}, nil, nil) + if err != nil { + panic(err) + } + return archive +} + +func InsertChain(bc *BlockChain, blocks []*types.Block) error { + _, err := bc.InsertChain(blocks) + return err +} + +func BenchmarkAkaka(b *testing.B) { + addrNum := 10000 + // Configure and generate a sample block chain + funds := big.NewInt(1000000000000000) + addresses := generateAddress(addrNum) + genesisAlloc := genesisAlloc(addresses, funds) + randomAddr := make(chan common.Address, addrNum) + for addr := range genesisAlloc { + randomAddr <- addr + } + var ( + gspec = &Genesis{ + Config: params.TestChainConfig, + Alloc: genesisAlloc, + BaseFee: big.NewInt(params.InitialBaseFee), + GasLimit: 500000000, + } + signer = types.LatestSigner(gspec.Config) + ) + + _, blocks, _ := GenerateChainWithGenesis(gspec, ethash.NewFaker(), 2, func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{0x00}) + txs := make([]*types.Transaction, len(addresses)) + for i, addr := range addresses { + // borrow an address + to := <-randomAddr + from := addr + tx, err := types.SignTx(types.NewTransaction(block.TxNonce(from.addr), to, big.NewInt(1000), params.TxGas, block.header.BaseFee, nil), signer, from.key) + if err != nil { + panic(err) + } + txs[i] = tx + randomAddr <- to + } + for i := 0; i < len(txs); i++ { + block.AddTx(txs[i]) + } + }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + archiveDb := rawdb.NewMemoryDatabase() + // Import the chain as an archive node for the comparison baseline + archive, _ := NewBlockChain(archiveDb, DefaultCacheConfigWithScheme(rawdb.PathScheme), gspec, nil, ethash.NewFaker(), vm.Config{EnableParallelExec: true}, nil, nil) + if n, err := archive.InsertChain(blocks); err != nil { + panic(fmt.Sprintf("failed to process block %d: %v", n, err)) + } + archive.Stop() + } +} + +var cacheLock = sync.RWMutex{} +var cached = make(map[common.Address]uint64) + +func getAddressSafe(addr common.Address) uint64 { + cacheLock.Lock() + defer cacheLock.Unlock() + return cached[addr] +} + +func getAddress(addr common.Address) uint64 { + return cached[addr] +} + +func BenchmarkSequencialRead(b *testing.B) { + // generate a set of addresses + // case 1: read the state sequentially + // case 2: read them in parallel + address := generateAddress(10000) + for i := 0; i < len(address); i++ { + cached[address[i].addr] = uint64(i) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // iterate all the address + for i := 0; i < len(address); i++ { + getAddress(address[i].addr) + } + } +} + +func BenchmarkParallelRead(b *testing.B) { + // generate a set of addresses + // case 1: read the state sequentially + // case 2: read them in parallel + address := generateAddress(10000) + for i := 0; i < len(address); i++ { + cached[address[i].addr] = uint64(i) + } + + proc := parallel(make(chan func())) + proc.start(runtime.NumCPU()) + wait := sync.WaitGroup{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + // iterator all the address in parallel + wait.Add(len(address)) + for i := 0; i < len(address); i++ { + index := i + proc.do(func() { + getAddressSafe(address[index].addr) + wait.Done() + }) + } + wait.Wait() + } + proc.close() +} diff --git a/core/state/access_list.go b/core/state/access_list.go index 4194691345..942829787a 100644 --- a/core/state/access_list.go +++ b/core/state/access_list.go @@ -134,3 +134,36 @@ func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { func (al *accessList) DeleteAddress(address common.Address) { delete(al.addresses, address) } + +// Copy creates an independent copy of an accessList. +func (dest *accessList) Append(src *accessList) *accessList { + for addr, sIdx := range src.addresses { + if i, present := dest.addresses[addr]; present { + // dest already has addr. + if sIdx >= 0 { + // has slot in list + if i == -1 { + dest.addresses[addr] = len(dest.slots) + slotmap := src.slots[sIdx] + dest.slots = append(dest.slots, slotmap) + } else { + slotmap := src.slots[sIdx] + for hash := range slotmap { + if _, ok := dest.slots[i][hash]; !ok { + dest.slots[i][hash] = struct{}{} + } + } + } + } + } else { + // dest doesn't have the address + dest.addresses[addr] = -1 + if sIdx >= 0 { + dest.addresses[addr] = len(dest.slots) + slotmap := src.slots[sIdx] + dest.slots = append(dest.slots, slotmap) + } + } + } + return dest +} diff --git a/core/state/dump.go b/core/state/dump.go index 55abb50f1c..1b0c0c0dae 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -160,7 +160,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] address = &addr account.Address = address } - obj := newObject(s, addr, &data) + obj := newObject(s, s.isParallel, addr, &data) if !conf.SkipCode { account.Code = obj.Code() } diff --git a/core/state/interface.go b/core/state/interface.go new file mode 100644 index 0000000000..3e808aa82e --- /dev/null +++ b/core/state/interface.go @@ -0,0 +1,81 @@ +// 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 state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/holiman/uint256" +) + +// StateDBer is copied from vm/interface.go +// It is used by StateObject & Journal right now, to abstract StateDB & ParallelStateDB +type StateDBer interface { + getBaseStateDB() *StateDB + getStateObject(common.Address) *stateObject // only accessible for journal + storeStateObj(common.Address, *stateObject) // only accessible for journal + + CreateAccount(common.Address) + + SubBalance(common.Address, *uint256.Int) + AddBalance(common.Address, *uint256.Int) + GetBalance(common.Address) *uint256.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + SelfDestruct(common.Address) + HasSelfDestructed(common.Address) bool + + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // is defined according to EIP161 (balance = nonce = code = 0). + Empty(common.Address) bool + + //PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) + + GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) +} diff --git a/core/state/journal.go b/core/state/journal.go index 6cdc1fc868..d436dbd5ac 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -25,7 +25,7 @@ import ( // reverted on demand. type journalEntry interface { // revert undoes the changes introduced by this journal entry. - revert(*StateDB) + revert(StateDBer) // dirtied returns the Ethereum address modified by this journal entry. dirtied() *common.Address @@ -56,10 +56,10 @@ func (j *journal) append(entry journalEntry) { // revert undoes a batch of journalled modifications along with any reverted // dirty handling too. -func (j *journal) revert(statedb *StateDB, snapshot int) { +func (j *journal) revert(dber StateDBer, snapshot int) { for i := len(j.entries) - 1; i >= snapshot; i-- { // Undo the changes made by the operation - j.entries[i].revert(statedb) + j.entries[i].revert(dber) // Drop any dirty tracking induced by the change if addr := j.entries[i].dirtied(); addr != nil { @@ -151,8 +151,18 @@ type ( } ) -func (ch createObjectChange) revert(s *StateDB) { - delete(s.stateObjects, *ch.account) +func (ch createObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() + if s.parallel.isSlotDB { + delete(s.parallel.dirtiedStateObjectsInSlot, *ch.account) + delete(s.parallel.addrStateChangesInSlot, *ch.account) + delete(s.parallel.nonceChangesInSlot, *ch.account) + delete(s.parallel.balanceChangesInSlot, *ch.account) + delete(s.parallel.codeChangesInSlot, *ch.account) + delete(s.parallel.kvChangesInSlot, *ch.account) + } else { + s.deleteStateObj(*ch.account) + } delete(s.stateObjectsDirty, *ch.account) } @@ -160,10 +170,27 @@ func (ch createObjectChange) dirtied() *common.Address { return ch.account } -func (ch resetObjectChange) revert(s *StateDB) { - s.setStateObject(ch.prev) +func (ch resetObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() + if s.parallel.isSlotDB { + // ch.prev must be from dirtiedStateObjectsInSlot, put it back + s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev + } else { + // ch.prev was got from main DB, put it back to main DB. + s.setStateObject(ch.prev) + } + if !ch.prevdestruct { - delete(s.stateObjectsDestruct, ch.prev.address) + s.stateObjectDestructLock.Lock() + s.removeStateObjectsDestruct(ch.prev.address) + s.stateObjectDestructLock.Unlock() + if s.isParallel && s.parallel.isSlotDB { + s.snapParallelLock.Lock() + if _, ok := s.snapDestructs[ch.prev.address]; ok { + delete(s.snapDestructs, ch.prev.address) + } + s.snapParallelLock.Unlock() + } } if ch.prevAccount != nil { s.accounts[ch.prev.addrHash] = ch.prevAccount @@ -183,8 +210,8 @@ func (ch resetObjectChange) dirtied() *common.Address { return ch.account } -func (ch selfDestructChange) revert(s *StateDB) { - obj := s.getStateObject(*ch.account) +func (ch selfDestructChange) revert(dber StateDBer) { + obj := dber.getStateObject(*ch.account) if obj != nil { obj.selfDestructed = ch.prev obj.setBalance(ch.prevbalance) @@ -197,46 +224,47 @@ func (ch selfDestructChange) dirtied() *common.Address { var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") -func (ch touchChange) revert(s *StateDB) { +func (ch touchChange) revert(dber StateDBer) { } func (ch touchChange) dirtied() *common.Address { return ch.account } -func (ch balanceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setBalance(ch.prev) +func (ch balanceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setBalance(ch.prev) } func (ch balanceChange) dirtied() *common.Address { return ch.account } -func (ch nonceChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setNonce(ch.prev) +func (ch nonceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setNonce(ch.prev) } func (ch nonceChange) dirtied() *common.Address { return ch.account } -func (ch codeChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) +func (ch codeChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } func (ch codeChange) dirtied() *common.Address { return ch.account } -func (ch storageChange) revert(s *StateDB) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) +func (ch storageChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } func (ch storageChange) dirtied() *common.Address { return ch.account } -func (ch transientStorageChange) revert(s *StateDB) { +func (ch transientStorageChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.setTransientState(*ch.account, ch.key, ch.prevalue) } @@ -244,7 +272,8 @@ func (ch transientStorageChange) dirtied() *common.Address { return nil } -func (ch refundChange) revert(s *StateDB) { +func (ch refundChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.refund = ch.prev } @@ -252,7 +281,8 @@ func (ch refundChange) dirtied() *common.Address { return nil } -func (ch addLogChange) revert(s *StateDB) { +func (ch addLogChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() logs := s.logs[ch.txhash] if len(logs) == 1 { delete(s.logs, ch.txhash) @@ -266,7 +296,8 @@ func (ch addLogChange) dirtied() *common.Address { return nil } -func (ch addPreimageChange) revert(s *StateDB) { +func (ch addPreimageChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() delete(s.preimages, ch.hash) } @@ -274,7 +305,7 @@ func (ch addPreimageChange) dirtied() *common.Address { return nil } -func (ch accessListAddAccountChange) revert(s *StateDB) { +func (ch accessListAddAccountChange) revert(dber StateDBer) { /* One important invariant here, is that whenever a (addr, slot) is added, if the addr is not already present, the add causes two journal entries: @@ -284,6 +315,7 @@ func (ch accessListAddAccountChange) revert(s *StateDB) { (addr) at this point, since no storage adds can remain when come upon a single (addr) change. */ + s := dber.getBaseStateDB() s.accessList.DeleteAddress(*ch.address) } @@ -291,7 +323,8 @@ func (ch accessListAddAccountChange) dirtied() *common.Address { return nil } -func (ch accessListAddSlotChange) revert(s *StateDB) { +func (ch accessListAddSlotChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.accessList.DeleteSlot(*ch.address, *ch.slot) } diff --git a/core/state/pevm_journal.go b/core/state/pevm_journal.go new file mode 100644 index 0000000000..b9df4e1eca --- /dev/null +++ b/core/state/pevm_journal.go @@ -0,0 +1,307 @@ +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/holiman/uint256" +) + +type jBalance struct { + created bool // whether the object is newly created in the uncommitted db + addr common.Address + prev *uint256.Int +} + +func newJBalance(obj *state, addr common.Address) *jBalance { + if obj == nil { + return &jBalance{ + created: true, + addr: addr, + prev: nil, + } + } else { + return &jBalance{ + created: false, + addr: addr, + prev: new(uint256.Int).Set(obj.balance), + } + } +} + +func (j *jBalance) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setBalance(j.addr, j.prev) +} + +type jNonce struct { + created bool + addr common.Address + nonce uint64 +} + +func newJNonce(obj *state, addr common.Address) *jNonce { + if obj == nil { + return &jNonce{ + created: true, + addr: addr, + nonce: 0, + } + } else { + return &jNonce{ + created: false, + addr: addr, + nonce: obj.nonce, + } + } +} + +func (j *jNonce) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setNonce(j.addr, j.nonce) +} + +type jStorage struct { + created bool + addr common.Address + keyCreated bool // whether the key is newly created in the object + key, val common.Hash +} + +func newJStorage(obj *state, addr common.Address, key common.Hash) *jStorage { + if obj == nil { + return &jStorage{ + created: true, + addr: addr, + keyCreated: false, + key: key, + val: common.Hash{}, + } + } + val, ok := obj.state[key] + if !ok { + return &jStorage{ + created: false, + addr: addr, + keyCreated: true, + key: key, + val: common.Hash{}, + } + } + return &jStorage{ + created: false, + addr: addr, + keyCreated: false, + key: key, + val: val, + } +} + +func (j *jStorage) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + if j.keyCreated { + delete(db.cache[j.addr].state, j.key) + } else { + db.cache.setState(j.addr, j.key, j.val) + } +} + +type jCode struct { + created bool + addr common.Address + code []byte +} + +func newJCode(obj *state, addr common.Address) *jCode { + if obj == nil { + return &jCode{ + created: true, + addr: addr, + code: nil, + } + } else { + return &jCode{ + created: false, + addr: addr, + code: append([]byte(nil), obj.code...), + } + } +} + +func (j *jCode) revert(db *UncommittedDB) { + if j.created { + delete(db.cache, j.addr) + return + } + db.cache.setCode(j.addr, j.code) +} + +type jCreateAccount struct { + replaced bool + addr common.Address + obj *state +} + +func newJCreateAccount(obj *state, addr common.Address) *jCreateAccount { + if obj == nil { + return &jCreateAccount{ + addr: addr, + obj: nil, + replaced: false, + } + } else { + return &jCreateAccount{ + addr: addr, + obj: obj.clone(), + replaced: true, + } + } +} + +func (j *jCreateAccount) revert(db *UncommittedDB) { + if !j.replaced { + delete(db.cache, j.addr) + } else { + db.cache[j.addr] = j.obj + } +} + +type jSelfDestruct struct { + addr common.Address + obj *state +} + +func newJSelfDestruct(obj *state) *jSelfDestruct { + if obj == nil { + //@TODO: should we handle this case? + return &jSelfDestruct{ + addr: common.Address{}, + obj: nil, + } + } + return &jSelfDestruct{ + addr: obj.addr, + obj: obj.clone(), + } +} + +func (j *jSelfDestruct) revert(db *UncommittedDB) { + db.cache[j.addr] = j.obj +} + +type jLogs struct { +} + +func (j *jLogs) revert(db *UncommittedDB) { + if len(db.logs) == 0 { + // it should never happen + return + } + db.logs = db.logs[:len(db.logs)-1] +} + +type jRefund struct { + prev uint64 +} + +func newJRefund(prev uint64) *jRefund { + return &jRefund{ + prev: prev, + } +} + +func (j *jRefund) revert(db *UncommittedDB) { + db.refund = j.prev +} + +type jPreimage struct { + created bool + hash common.Hash +} + +func newJPreimage(hash common.Hash) *jPreimage { + return &jPreimage{ + created: true, + hash: hash, + } +} + +func (j *jPreimage) revert(db *UncommittedDB) { + delete(db.preimages, j.hash) +} + +type jAccessList struct { + addr *common.Address +} + +func (j *jAccessList) revert(db *UncommittedDB) { + db.accessList.DeleteAddress(*j.addr) +} + +type jAccessListSlot struct { + addr *common.Address + slot *common.Hash +} + +func (j *jAccessListSlot) revert(db *UncommittedDB) { + db.accessList.DeleteSlot(*j.addr, *j.slot) +} + +type jLog struct { + i uint +} + +func newJLog(i uint) *jLog { + return &jLog{ + i: i, + } +} + +func (j *jLog) revert(db *UncommittedDB) { + db.logs = db.logs[:j.i] +} + +type jTransientStorage struct { + addr common.Address + key, val common.Hash +} + +func newJTransientStorage(addr common.Address, key, val common.Hash) *jTransientStorage { + return &jTransientStorage{ + addr: addr, + key: key, + val: val, + } +} + +func (j *jTransientStorage) revert(db *UncommittedDB) { + db.transientStorage.Set(j.addr, j.key, j.val) +} + +type ujournal []ustate + +func (j *ujournal) append(st ustate) { + *j = append(*j, st) +} + +func (j *ujournal) revertTo(db *UncommittedDB, snapshot int) { + for i := len(*j) - 1; i >= snapshot; i-- { + (*j)[i].revert(db) + } + *j = (*j)[:snapshot] +} + +func (j *ujournal) snapshot() int { + return len(*j) +} + +type ustate interface { + revert(db *UncommittedDB) +} diff --git a/core/state/pevm_journal_test.go b/core/state/pevm_journal_test.go new file mode 100644 index 0000000000..0b1ae01e18 --- /dev/null +++ b/core/state/pevm_journal_test.go @@ -0,0 +1,154 @@ +package state + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestPevmJournalOfState(t *testing.T) { + var snapid int + var ( + Address1 = common.Address{0x01} + Address2 = common.Address{0x02} + Address3 = common.Address{0x03} + Address4 = common.Address{0x04} + ) + prepare := Tx{ + {"SetNonce", Address1, 1}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value"}, + {"SetCode", Address4, []byte{0x04}}, + } + // set states that will be reverted + tx := Tx{ + {"Snapshot", &snapid}, + {"SetNonce", Address1, 2}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value3"}, + {"SetCode", Address4, []byte{0x040}}, + } + revertTx := Tx{ + {"Revert", &snapid}, + } + beforeRevert := Checks{ + {"nonce", Address1, 2}, + {"balance", Address2, big.NewInt(200)}, + {"state", Address3, "key", "value3"}, + {"code", Address4, []byte{0x040}}, + } + afterRevert := Checks{ + {"nonce", Address1, 1}, + {"balance", Address2, big.NewInt(100)}, + {"state", Address3, "key", "value"}, + {"code", Address4, []byte{0x04}}, + } + + statedb := newStateDB() + prepare.Call(statedb) + tx.Call(statedb) + if err := beforeRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]before revert: %v", err) + } + revertTx.Call(statedb) + if err := afterRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]after revert: %v", err) + } + + uncommitted := NewUncommittedDB(newStateDB()) + prepare.Call(uncommitted) + tx.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]before revert: %v", err) + } + revertTx.Call(uncommitted) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]after revert: %v", err) + } +} + +func TestPevmJournal(t *testing.T) { + var snapid int + var tx1 = common.Hash{0x012} + var txIndex = 20 + var ( + Address1 = common.Address{0x01} + Address2 = common.Address{0x02} + Address3 = common.Address{0x03} + Address4 = common.Address{0x04} + Address5 = common.Address{0x05} + Address6 = common.Address{0x06} + ) + tx := Tx{ + {"Snapshot", &snapid}, + {"SetNonce", Address1, 1}, + {"AddBalance", Address2, big.NewInt(100)}, + {"SetState", Address3, "key", "value"}, + {"SetCode", Address4, []byte{0x04}}, + {"Create", Address5}, + {"AddPreimage", common.Hash{0x3}, []byte{0x05}}, + {"SetTransientStorage", Address6, "hash", "tx"}, + {"AddRefund", 102}, + {"AddLog", &types.Log{TxHash: tx1, Data: []byte("hello"), TxIndex: uint(txIndex)}}, + {"AddLog", &types.Log{TxHash: tx1, Data: []byte("world"), TxIndex: uint(txIndex)}}, + {"AddAddress", Address1}, + {"AddSlots", Address2, common.Hash{0x22}}, + } + revertTx := Tx{ + {"Revert", &snapid}, + } + beforeRevert := Checks{ + {"nonce", Address1, 1}, + {"balance", Address2, big.NewInt(100)}, + {"state", Address3, "key", "value"}, + {"code", Address4, []byte{0x04}}, + {"exists", Address5, true}, + {"preimage", common.Hash{0x3}, []byte{0x05}}, + {"tstorage", Address6, "hash", "tx"}, + {"refund", 102}, + {"log", tx1, 0, []byte("hello"), txIndex, 0}, + {"log", tx1, 1, []byte("world"), txIndex, 1}, + {"loglen", 2}, + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address2, common.Hash{0x22}, true}, + } + afterRevert := Checks{ + {"exists", Address1, false}, + {"exists", Address2, false}, + {"exists", Address3, false}, + {"exists", Address4, false}, + {"exists", Address5, false}, + {"preimageExists", common.Hash{0x3}, false}, + {"tstorage", Address6, "hash", ""}, + {"refund", 0}, + {"loglen", 0}, + {"address", Address1, false}, + {"address", Address2, false}, + {"slot", Address2, common.Hash{0x22}, false}, + } + + statedb := newStateDB() + statedb.SetTxContext(tx1, txIndex) + tx.Call(statedb) + if err := beforeRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]before revert: %v", err) + } + revertTx.Call(statedb) + if err := afterRevert.Verify(statedb); err != nil { + t.Fatalf("[maindb]after revert: %v", err) + } + + uncommitted := NewUncommittedDB(newStateDB()) + uncommitted.SetTxContext(tx1, txIndex) + tx.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]before revert: %v", err) + } + revertTx.Call(uncommitted) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("[uncommitted]after revert: %v", err) + } +} diff --git a/core/state/pevm_statedb.go b/core/state/pevm_statedb.go new file mode 100644 index 0000000000..14f5224753 --- /dev/null +++ b/core/state/pevm_statedb.go @@ -0,0 +1,935 @@ +package state + +import ( + "bytes" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// UncommittedDB is a wrapper of StateDB, which records all the writes of the state. +// It is designed for parallel running of the EVM. +// All fields of UncommittedDB survive in the scope of a transaction. +// Here is the original score of each field in the maindb(StateDB): +// |------------------|---------|---------------------|------------------------| +// | field | score | need to be merged | need to be committed | +// |------------------|---------|---------------------|------------------------| +// | accessList | block: before Berlin hardfork ; transaction : after Berlin hardfork | no | no | +// | transientStorage | transaction | no | no | +// | objects | block | yes | yes | +// | refund | block | yes | yes | +// | preimages | block | yes | yes | +// |------------------|---------|-------|-------| +type UncommittedDB struct { + isBerlin bool + discarded bool + + // transaction context + txHash common.Hash + txIndex int + logs []*types.Log + + // accessList + accessList *accessList + + // in the maindb, transientStorage survives only in the scope of a transaction, and no need to + //@todo make transientStorage lazy loaded + transientStorage transientStorage + + // object reads and writes + reads reads + cache writes + + // in the maindb, refund survives in the scope of block; and will be reset to 0 whenever a transaction's modification is Finalized to trie . + prevRefund *uint64 + refund uint64 + + // in the maindb, preimages survives in the scope of block + // it might be too large that we need to lazy load it. + preimages map[common.Hash][]byte + preimagesReads map[common.Hash][]byte + + journal ujournal + + maindb *StateDB +} + +func NewUncommittedDB(maindb *StateDB) *UncommittedDB { + return &UncommittedDB{ + accessList: newAccessList(), + transientStorage: newTransientStorage(), + maindb: maindb, + reads: make(reads), + cache: make(writes), + } +} + +// @TODO drop? +func (pst *UncommittedDB) BeforeTxTransition() { +} + +// @TODO drop? +func (pst *UncommittedDB) FinaliseRWSet() error { + return nil +} + +// @TODO drop? +func (pst *UncommittedDB) TxIndex() int { + return pst.txIndex +} + +func (pst *UncommittedDB) SetTxContext(txHash common.Hash, txIndex int) { + pst.txHash = txHash + pst.txIndex = txIndex +} + +// =============================================== +// Constructor +func (pst *UncommittedDB) CreateAccount(addr common.Address) { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + obj := pst.getDeletedObject(addr, pst.maindb) + // keep the balance + pst.cache.create(addr) + if obj != nil { + pst.cache[addr].balance.Set(obj.balance) + } +} + +// Prepare handles the preparatory steps for executing a state transition with. +// This method must be invoked before state transition. +// +// Berlin fork: +// - Add sender to access list (2929) +// - Add destination to access list (2929) +// - Add precompiles to access list (2929) +// - Add the contents of the optional tx access list (2930) +// +// Potential EIPs: +// - Reset access list (Berlin) +// - Add coinbase to access list (EIP-3651) +// - Reset transient storage (EIP-1153) +func (pst *UncommittedDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { + // do nothing, because the work had been done in constructer NewUncommittedDB() + // 0. init accessList + // 1. init transientStorage + pst.isBerlin = rules.IsBerlin + if rules.IsBerlin { + // Clear out any leftover from previous executions + al := newAccessList() + pst.accessList = al + + al.AddAddress(sender) + if dst != nil { + al.AddAddress(*dst) + // If it's a create-tx, the destination will be added inside evm.create + } + for _, addr := range precompiles { + al.AddAddress(addr) + } + for _, el := range list { + al.AddAddress(el.Address) + for _, key := range el.StorageKeys { + al.AddSlot(el.Address, key) + } + } + if rules.IsShanghai { // EIP-3651: warm coinbase + al.AddAddress(coinbase) + } + } else { + pst.accessList = pst.maindb.accessList.Copy() + } + // Reset transient storage at the beginning of transaction execution + pst.transientStorage = newTransientStorage() +} + +// =============================================== +// Object Methods +// 1. journal +// 2. object + +func (pst *UncommittedDB) SubBalance(addr common.Address, amount *uint256.Int) { + pst.journal.append(newJBalance(pst.cache[addr], addr)) + obj := pst.getOrNewObject(addr) + newb := new(uint256.Int).Sub(obj.balance, amount) + pst.cache.setBalance(addr, newb) +} + +func (pst *UncommittedDB) AddBalance(addr common.Address, amount *uint256.Int) { + pst.journal.append(newJBalance(pst.cache[addr], addr)) + obj := pst.getOrNewObject(addr) + newb := new(uint256.Int).Add(obj.balance, amount) + pst.cache.setBalance(addr, newb) +} + +func (pst *UncommittedDB) GetBalance(addr common.Address) *uint256.Int { + if obj := pst.getObject(addr); obj != nil { + return new(uint256.Int).Set(obj.balance) + } + return uint256.NewInt(0) +} + +func (pst *UncommittedDB) GetNonce(addr common.Address) uint64 { + if obj := pst.getObject(addr); obj != nil { + return obj.nonce + } + return 0 +} + +func (pst *UncommittedDB) SetNonce(addr common.Address, nonce uint64) { + pst.journal.append(newJNonce(pst.cache[addr], addr)) + pst.getOrNewObject(addr) + pst.cache.setNonce(addr, nonce) +} + +func (pst *UncommittedDB) GetCodeHash(addr common.Address) common.Hash { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return common.Hash{} + } + return common.BytesToHash(obj.codeHash) +} + +func (pst *UncommittedDB) GetCode(addr common.Address) []byte { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return nil + } + return obj.code +} + +func (pst *UncommittedDB) GetCodeSize(addr common.Address) int { + obj := pst.getDeletedObjectWithCode(addr, pst.maindb) + if obj == nil || obj.deleted { + return 0 + } + return obj.codeSize +} + +func (pst *UncommittedDB) SetCode(addr common.Address, code []byte) { + pst.journal.append(newJCode(pst.cache[addr], addr)) + if obj := pst.getDeletedObjectWithCode(addr, pst.maindb); obj == nil || obj.deleted { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + pst.cache.create(addr) + } + pst.cache.setCode(addr, code) +} + +func (pst *UncommittedDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + // this is a uncommitted db, so just get it from the maindb + //@TODO GetCommittedState() need to be thread safe + return pst.maindb.GetCommittedState(addr, hash) +} + +func (pst *UncommittedDB) GetState(addr common.Address, hash common.Hash) common.Hash { + obj := pst.getDeletedObjectWithState(addr, pst.maindb, hash) + if obj == nil || obj.deleted { + return common.Hash{} + } + return obj.state[hash] +} + +func (pst *UncommittedDB) SetState(addr common.Address, key, value common.Hash) { + pst.journal.append(newJStorage(pst.cache[addr], addr, key)) + if obj := pst.getDeletedObjectWithState(addr, pst.maindb, key); obj == nil || obj.deleted { + pst.journal.append(newJCreateAccount(pst.cache[addr], addr)) + pst.cache.create(addr) + } + pst.cache.setState(addr, key, value) +} + +func (pst *UncommittedDB) SelfDestruct(addr common.Address) { + pst.journal.append(newJSelfDestruct(pst.cache[addr])) + if obj := pst.getObject(addr); obj == nil { + return + } + pst.cache.selfDestruct(addr) +} + +func (pst *UncommittedDB) HasSelfDestructed(addr common.Address) bool { + if obj := pst.getObject(addr); obj != nil { + return obj.selfDestruct + } + return false +} + +func (pst *UncommittedDB) Selfdestruct6780(addr common.Address) { + obj := pst.getObject(addr) + if obj == nil { + return + } + if obj.created { + pst.SelfDestruct(addr) + } +} + +func (pst *UncommittedDB) Exist(addr common.Address) bool { + return pst.getObject(addr) != nil +} + +func (pst *UncommittedDB) Empty(addr common.Address) bool { + obj := pst.getObject(addr) + return obj == nil || obj.empty() +} + +// =============================================== +// Refund Methods +// 1. journal +// 2. refunds + +func (pst *UncommittedDB) AddRefund(gas uint64) { + pst.journal.append(newJRefund(pst.refund)) + pst.recordRefundOnce() + pst.refund += gas +} + +func (pst *UncommittedDB) SubRefund(gas uint64) { + pst.journal.append(newJRefund(pst.refund)) + pst.recordRefundOnce() + if pst.refund < gas { + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, pst.refund)) + } + pst.refund -= gas +} + +func (pst *UncommittedDB) GetRefund() uint64 { + pst.recordRefundOnce() + return pst.refund +} + +func (pst *UncommittedDB) recordRefundOnce() { + if pst.prevRefund == nil { + refund := pst.maindb.GetRefund() + pst.prevRefund = &refund + pst.refund = refund + } +} + +// =============================================== +// transientStorage Methods (EIP-1153: https://eips.ethereum.org/EIPS/eip-1153) + +func (pst *UncommittedDB) GetTransientState(addr common.Address, key common.Hash) common.Hash { + return pst.transientStorage.Get(addr, key) +} +func (pst *UncommittedDB) SetTransientState(addr common.Address, key, value common.Hash) { + prev := pst.transientStorage.Get(addr, key) + if prev == value { + return // no need to record the same value + } + pst.journal.append(newJTransientStorage(addr, key, prev)) + pst.transientStorage.Set(addr, key, value) +} + +// =============================================== +// AccessList Methods (EIP-2930: https://eips.ethereum.org/EIPS/eip-2930) +func (pst *UncommittedDB) AddressInAccessList(addr common.Address) bool { + return pst.accessList.ContainsAddress(addr) +} +func (pst *UncommittedDB) SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) { + return pst.accessList.Contains(addr, slot) +} +func (pst *UncommittedDB) AddAddressToAccessList(addr common.Address) { + if pst.accessList.AddAddress(addr) { + pst.journal.append(&jAccessList{addr: &addr}) + } +} +func (pst *UncommittedDB) AddSlotToAccessList(addr common.Address, slot common.Hash) { + addrChanged, slotChanged := pst.accessList.AddSlot(addr, slot) + if addrChanged { + pst.journal.append(&jAccessList{addr: &addr}) + } + if slotChanged { + pst.journal.append(&jAccessListSlot{addr: &addr, slot: &slot}) + } +} + +// =============================================== +// Snapshot Methods +func (pst *UncommittedDB) RevertToSnapshot(id int) { + if id < 0 || id > len(pst.journal) { + panic(fmt.Sprintf("invalid snapshot index, out of range, snapshot:%d, len:%d", id, len(pst.journal))) + } + pst.journal.revertTo(pst, id) +} +func (pst *UncommittedDB) Snapshot() int { + return pst.journal.snapshot() +} + +// =============================================== +// Logs Methods +func (pst *UncommittedDB) AddLog(log *types.Log) { + pst.journal.append(&jLog{i: uint(len(pst.logs))}) + log.TxHash = pst.txHash + log.TxIndex = uint(pst.txIndex) + // we don't need to set Index now, because it will be recalculated when merging into maindb + log.Index = uint(len(pst.logs)) + pst.logs = append(pst.logs, log) +} + +// PackLogs returns the logs matching the specified transaction hash, and annotates +// them with the given blockNumber and blockHash. +func (s *UncommittedDB) PackLogs(blockNumber uint64, blockHash common.Hash) []*types.Log { + for _, l := range s.logs { + l.BlockNumber = blockNumber + l.BlockHash = blockHash + } + return s.logs +} + +// =============================================== +// Preimage Methods (EIP-1352: https://eips.ethereum.org/EIPS/eip-1352) +func (pst *UncommittedDB) AddPreimage(hash common.Hash, preimage []byte) { + pst.journal.append(newJPreimage(hash)) + pst.recordPreimageOnce(hash) + if _, ok := pst.preimages[hash]; !ok { + pi := make([]byte, len(preimage)) + copy(pi, preimage) + pst.preimages[hash] = pi + } +} + +func (pst *UncommittedDB) recordPreimageOnce(hash common.Hash) { + // first time to read the preimage + if pst.preimages == nil { + // load all preimages from maindb + pst.preimages = make(map[common.Hash][]byte) + pst.preimagesReads = make(map[common.Hash][]byte) + for h, pi := range pst.maindb.preimages { + pst.preimagesReads[h], pst.preimages[h] = pi, pi + } + } + if _, ok := pst.preimagesReads[hash]; ok { + return + } + // get and record the preimage state from the maindb + preimageFromMaindb, ok := pst.maindb.preimages[hash] + if ok { + pst.preimagesReads[hash] = preimageFromMaindb + } else { + // not exists in the maindb too + pst.preimagesReads[hash] = nil + } +} + +func (pst *UncommittedDB) Preimages() map[common.Hash][]byte { + return pst.preimages +} + +// check conflict +func (pst *UncommittedDB) conflictsToMaindb() error { + // 1. check preimages reads conflict (@TODO preimage should be lazy loaded) + // 2. check accesslist reads conflict (@TODO confirm: whether the accesslist should be checked or not) + // 3. check logs conflict (check the txIndex) + // 4. check object conflict + if err := pst.hasLogConflict(pst.maindb); err != nil { + return err + } + if err := pst.hasRefundConflict(pst.maindb); err != nil { + return err + } + if err := pst.hasPreimageConflict(pst.maindb); err != nil { + return err + } + return pst.reads.conflictsTo(pst.maindb) +} + +func (pst *UncommittedDB) hasLogConflict(maindb *StateDB) error { + if pst.txIndex == 0 { + // this is the first transaction in the block, + // so the logs should be empty + // and the maindb.txIndex should be -1 + if maindb.logSize != 0 { + return fmt.Errorf("conflict logs, logSize: %d, expected 0", maindb.logSize) + } + if len(maindb.logs) != 0 { + return fmt.Errorf("conflict logs, logsLen: %d, expected 0", len(maindb.logs)) + } + } else { + // this is not the first transaction in the block + if maindb.txIndex != int(pst.txIndex)-1 { + return fmt.Errorf("conflict txIndex, txIndex: %d, expected %d", maindb.txIndex, pst.txIndex-1) + } + } + return nil +} + +func (pst *UncommittedDB) hasPreimageConflict(maindb *StateDB) error { + for hash, preimage := range pst.preimagesReads { + mainstate := maindb.preimages[hash] + if !bytes.Equal(preimage, mainstate) { + return fmt.Errorf("conflict preimage, hash:%s, expected:%s, actual:%s", hash.String(), preimage, mainstate) + } + } + return nil +} + +func (pst *UncommittedDB) hasRefundConflict(maindb *StateDB) error { + // never read + if pst.prevRefund == nil { + return nil + } + if *pst.prevRefund != maindb.GetRefund() { + return fmt.Errorf("conflict refund, expected:%d, actual:%d", *pst.prevRefund, maindb.GetRefund()) + } + return nil +} + +func (pst *UncommittedDB) ConflictsToMaindb() error { + return pst.conflictsToMaindb() +} + +func (pst *UncommittedDB) Merge(deleteEmptyObjects bool) error { + if pst.discarded { + // all the writes of this db will be discarded, including: + // 1. accessList + // 2. preimages + // 3. obj states + // 4. refund + // so we don't need to merge anything. + return nil + } + + // 0. set the TxContext + pst.maindb.SetTxContext(pst.txHash, pst.txIndex) + // 1. merge preimages writes + for hash, preimage := range pst.preimages { + pst.maindb.preimages[hash] = preimage + } + // 2. merge accesslist writes + // we need to merge accessList before Berlin hardfork + if !pst.isBerlin { + for addr, slot := range pst.accessList.addresses { + pst.maindb.accessList.AddAddress(addr) + if slot >= 0 { + slots := pst.accessList.slots[slot] + for hash := range slots { + pst.maindb.accessList.AddSlot(addr, hash) + } + } + } + } else { + // after Berlin hardfork, all the accessList should be reset before a transaction was executed + pst.maindb.accessList = pst.accessList + } + // 3. merge logs writes + for _, st := range pst.cache { + st.merge(pst.maindb) + } + // 4. merge object states + for _, log := range pst.logs { + pst.maindb.AddLog(log) + } + // 5. merge refund + if pst.refund != 0 { + pst.maindb.AddRefund(pst.refund) + } + // clean empty objects if needed + for _, obj := range pst.cache { + if obj.selfDestruct || (deleteEmptyObjects && obj.empty()) { + obj.deleted = true + } + // we don't need to do obj.finalize() here, it will be done in the maindb.Finalize() + // just mark the object as deleted + obj.created = false + } + return nil +} + +func (pst *UncommittedDB) Finalise(deleteEmptyObjects bool) { + pst.maindb.Finalise(deleteEmptyObjects) +} + +// getDeletedObj returns the state object for the given address or nil if not found. +// it first gets from the maindb, if not found, then get from the maindb. +// it never modifies the maindb. the maindb is read-only. +// there are some cases to be handle: +// 1. it exists in the maindb's cache +// a) just return it +// 2. it exists in the maindb, but not in the cache: +// a) record a read state +// b) clone it, and put it into the cache +// c) return it +// 3. it doesn't exist in the maindb, and neighter in the slaveb: +// a) record a read state with create = true +// b) return nil +// +// it is notable that the getObj() will not be called parallelly, but the getStateObject() of maindb will be. +// so we need: +// 1. the uncommittedDB's cache is not thread safe +// 2. the maindb's cache is thread safe +func (pst *UncommittedDB) getDeletedObject(addr common.Address, maindb *StateDB) (o *state) { + if pst.cache[addr] != nil { + return pst.cache[addr] + } + // it reads the cache from the maindb + obj := maindb.getDeletedStateObject(addr) + defer func() { + pst.reads.recordOnce(addr, copyObj(obj)) + }() + if obj == nil { + // the object is not found, do a read record + return nil + } + // write it into the cache and return + pst.cache[addr] = copyObj(obj) + return pst.cache[addr] +} + +// getObject returns the state object for the given address or nil if not found. +// 1. it first gets from the cache. +// 2. if not found, then get from the maindb, +// 3. record its state in maindb for the first read, which is for further conflict check. +func (pst *UncommittedDB) getObject(addr common.Address) *state { + obj := pst.getDeletedObject(addr, pst.maindb) + if obj == nil || obj.deleted { + return nil + } + return obj +} + +// getOrNewObj returns the state object for the given address or create a new one if not found. +// 1. it first gets from the cache. +// 2. if not found, then get from the maindb: +// 2.1) if not found +// 2.1.1) create a new one in the cache, keep maindb unchanged +// 2.1.2) record a read state with create = true +// 2.1.3) record a write cache with create = true, balance = 0 +// 2.2) if found, record its state in maindb for the first read, which is for further conflict check. +// +// 3. return the state object +func (pst *UncommittedDB) getOrNewObject(addr common.Address) *state { + obj := pst.getObject(addr) + if obj != nil { + return obj + } + return pst.cache.create(addr) +} + +func (pst *UncommittedDB) getObjectWithCode(addr common.Address) *state { + obj := pst.getObject(addr) + if obj == nil { + return nil + } + if obj.codeIsLoaded() { + return obj + } + // try to load the code from maindb + deletedObject := pst.maindb.getStateObject(addr) + if deletedObject == nil { + return nil + } + code, codeHash := deletedObject.Code(), common.BytesToHash(deletedObject.CodeHash()) + obj.code = code + obj.codeHash = codeHash[:] + obj.codeSize = len(code) + return obj +} + +// getDeletedObjectWithCode return an object with code, and load the code from maindb if it is not loaded. +func (pst *UncommittedDB) getDeletedObjectWithCode(addr common.Address, maindb *StateDB) (o *state) { + o = pst.getDeletedObject(addr, maindb) + if o == nil { + pst.reads.recordCodeOnce(addr, common.Hash{}, nil) + return nil + } + if o.codeIsLoaded() { + return o + } + // load code from maindb + deletedObj := pst.maindb.getDeletedStateObject(addr) + if deletedObj == nil { + pst.reads.recordCodeOnce(addr, common.Hash{}, nil) + } else { + pst.reads.recordCodeOnce(addr, common.BytesToHash(deletedObj.CodeHash()), deletedObj.Code()) + } + // set code into the cache + code, codeHash := deletedObj.Code(), common.BytesToHash(deletedObj.CodeHash()) + o.code = code + o.codeHash = codeHash[:] + o.codeSize = len(code) + return o +} + +// getDeletedObjectWithState return an object with state, and load the state from maindb if it is not loaded. +func (pst *UncommittedDB) getDeletedObjectWithState(addr common.Address, maindb *StateDB, hash common.Hash) (o *state) { + o = pst.getDeletedObject(addr, maindb) + if o == nil { + pst.reads.recordKVOnce(addr, hash, common.Hash{}) + return nil + } + if _, ok := o.state[hash]; ok { + return o + } + // first, load code from maindb and record the previous state + // we can't use getStateObject() here , because the state of deletedObj will be used for conflict check. + deletedObj := pst.maindb.getDeletedStateObject(addr) + if deletedObj != nil { + // record the previous state for conflict check. + pst.reads.recordKVOnce(addr, hash, deletedObj.GetState(hash)) + } + + // now write the true state into cache + var value = common.Hash{} + if deletedObj != nil && !deletedObj.deleted { + value = deletedObj.GetState(hash) + } + o.state[hash] = value + return o +} + +type state struct { + // object states + modified int32 //records all the modified fields + addr common.Address + balance *uint256.Int + nonce uint64 + //@TODO code is lazy loaded, be careful to record its state + code []byte + codeHash []byte + codeSize int // when codeSize == -1, it means the code is unloaded + state map[common.Hash]common.Hash + selfDestruct bool + created bool + deleted bool +} + +func (c *state) clone() *state { + newstate := make(map[common.Hash]common.Hash, len(c.state)) + for k, v := range c.state { + newstate[k] = v + } + return &state{ + modified: c.modified, + addr: c.addr, + balance: new(uint256.Int).Set(c.balance), + nonce: c.nonce, + code: append([]byte(nil), c.code...), + codeHash: append([]byte(nil), c.codeHash...), + codeSize: c.codeSize, + state: newstate, + selfDestruct: c.selfDestruct, + created: c.created, + deleted: c.deleted, + } +} + +func (s *state) markAllModified() { + s.modified |= ModifyBalance + s.modified |= ModifyNonce + // @TODO confirm: whether the code should be reset or not? + // @TODO confirm: whether the state should be reset or not? + s.modified |= ModifyCode + s.modified |= ModifyState +} + +// check whether the state of current object is conflicted with the maindb +func (s state) conflicts(maindb *StateDB) error { + addr := s.addr + obj := maindb.getDeletedStateObject(addr) + // created == true means it doen't exist in the maindb + if s.created != (obj == nil) { + return errors.New("conflict: created") + } + // newly created object, no need to compare anything + if obj == nil { + return nil + } + // they are all deleted, no need to compare anything + //if obj.deleted && s.deleted { + // return nil + //} + if s.deleted != obj.deleted { + return fmt.Errorf("conlict: deleted, expected:%t, actual:%t", s.deleted, obj.deleted) + } + if s.selfDestruct != obj.selfDestructed { + return fmt.Errorf("conflict: selfDestruct, expected:%t, actual:%t", s.selfDestruct, obj.selfDestructed) + } + if s.nonce != obj.Nonce() { + return fmt.Errorf("conflict: nonce, expected:%d, actual:%d", s.nonce, obj.Nonce()) + } + if s.balance.Cmp(obj.Balance()) != 0 { + return fmt.Errorf("conflict: balance, expected:%d, actual:%d", s.balance.Uint64(), obj.data.Balance.Uint64()) + } + // code is lazy loaded + if s.codeIsLoaded() && !bytes.Equal(obj.Code(), s.code) { + return fmt.Errorf("conflict: code, expected len:%d, actual len:%d", len(s.code), len(obj.Code())) + } + // state is lazy loaded, and should be checked as less as possible , too. + for key, val := range s.state { + if obj.GetState(key).Cmp(val) != 0 { + return fmt.Errorf("conflict: state, key:%s, expected:%s, actual:%s", key.String(), val.String(), obj.GetState(key).String()) + } + } + return nil +} + +func (s state) merge(maindb *StateDB) { + // 1. merge the balance + // 2. merge the nonce + // 3. merge the code + // 4. merge the state + if s.modified&ModifySelfDestruct != 0 { + maindb.SelfDestruct(s.addr) + return + } + obj := maindb.getOrNewStateObject(s.addr) + if s.modified&ModifyBalance != 0 { + obj.SetBalance(s.balance) + } + if s.modified&ModifyNonce != 0 { + obj.SetNonce(s.nonce) + } + if s.modified&ModifyCode != 0 { + obj.SetCode(common.BytesToHash(s.codeHash), s.code) + } + if s.modified&ModifyState != 0 { + for key, val := range s.state { + obj.SetState(key, val) + } + //TODO: should we reset all kv pairs if the s.state == nil ? + } +} + +func (s *state) empty() bool { + return s.nonce == 0 && s.balance.Sign() == 0 && bytes.Equal(s.codeHash, types.EmptyCodeHash.Bytes()) +} + +func (s *state) codeIsLoaded() bool { + return s.codeSize != -1 +} + +func copyObj(obj *stateObject) *state { + if obj == nil { + return nil + } + // we don't copy the fields of `code` and `state` here, because they are lazy loaded. + // we don't copy the `created` eigher, because it true only when the object is nil, which means "it was created newly by the uncommited db". + // we need to copy the fields `deleted`, because it identifies whether the object is deleted or not. + return &state{ + addr: obj.Address(), + nonce: obj.Nonce(), + balance: new(uint256.Int).Set(obj.Balance()), + selfDestruct: obj.selfDestructed, + deleted: obj.deleted, // deleted is true when a "selfDestruct=true" object is finalized. more details can be found in the method Finalize() of StateDB + codeSize: -1, // mark the code "unloaded" + state: make(map[common.Hash]common.Hash), + } +} + +type reads map[common.Address]*state + +func (sts reads) recordOnce(addr common.Address, st *state) *state { + if _, ok := sts[addr]; !ok { + if st == nil { + // this is a newly created object + sts[addr] = &state{addr: addr, created: true, state: make(map[common.Hash]common.Hash), codeSize: -1} + } else { + sts[addr] = st + } + } + return st +} + +func (sts reads) recordKVOnce(addr common.Address, key, val common.Hash) { + obj := sts[addr] + if _, ok := obj.state[key]; !ok { + obj.state[key] = val + } +} + +func (sts reads) recordCodeOnce(addr common.Address, codeHash common.Hash, code []byte) { + st := sts[addr] + // we can't check whether the code is loaded or not, by checking codeHash == nil or code == nil (maybe the code is empty) + // so we need another fields to record the code previous state. + // and that is the `codeSize`, when codeSize == -1, it means the code is unloaded. + if !st.codeIsLoaded() { + st.codeHash = codeHash[:] + st.code = code + st.codeSize = len(code) + } +} + +func (sts reads) conflictsTo(maindb *StateDB) error { + for addr, st := range sts { + if err := st.conflicts(maindb); err != nil { + return fmt.Errorf("conflict at address %s: %s", addr.String(), err.Error()) + } + } + return nil +} + +// =============================================== +// Writes Methods +// it is used to record all the writes of the uncommitted db. + +type writes map[common.Address]*state + +const ( + ModifyNonce = 1 << iota + ModifyBalance + ModifyCode + ModifyState + ModifySelfDestruct + ModifySelfDestruct6780 +) + +func (wst writes) setBalance(addr common.Address, balance *uint256.Int) { + wst[addr].balance = balance + wst[addr].modified |= ModifyBalance +} + +func (wst writes) setNonce(addr common.Address, nonce uint64) { + wst[addr].nonce = nonce + wst[addr].modified |= ModifyNonce +} + +func (wst writes) setState(addr common.Address, key, val common.Hash) { + wst[addr].state[key] = val + wst[addr].modified |= ModifyState +} + +func (wst writes) setCode(addr common.Address, code []byte) { + codeHash := crypto.Keccak256Hash(code) + wst[addr].code = code + wst[addr].codeHash = codeHash[:] + wst[addr].codeSize = len(code) + wst[addr].modified |= ModifyCode +} + +func (wst writes) create(addr common.Address) *state { + wst[addr] = &state{ + addr: addr, + balance: uint256.NewInt(0), + nonce: 0, + //need to init code & codeHash + code: nil, + codeHash: types.EmptyCodeHash.Bytes(), + // mark the code "unloaded"? + //codeSize: -1, + //need to reset storage + state: make(map[common.Hash]common.Hash), + created: true, + } + wst[addr].markAllModified() + return wst[addr] +} + +func (wst writes) selfDestruct(addr common.Address) { + obj := wst[addr] + if obj == nil { + return + } + obj.modified |= ModifySelfDestruct + obj.selfDestruct = true + obj.balance = uint256.NewInt(0) +} + +func (wst writes) merge(maindb *StateDB) { + for _, st := range wst { + st.merge(maindb) + } +} diff --git a/core/state/pevm_statedb_test.go b/core/state/pevm_statedb_test.go new file mode 100644 index 0000000000..d4443c8581 --- /dev/null +++ b/core/state/pevm_statedb_test.go @@ -0,0 +1,2439 @@ +package state + +import ( + "bytes" + "crypto/ecdsa" + "crypto/rand" + "fmt" + "math/big" + "runtime" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/triedb" + "github.com/ethereum/go-ethereum/triedb/hashdb" + "github.com/ethereum/go-ethereum/triedb/pathdb" + "github.com/holiman/uint256" +) + +var Address1 = common.HexToAddress("0x1") +var Address2 = common.HexToAddress("0x2") + +func TestInvalidGasUsed(t *testing.T) { + txs := Txs{ + { // prepare for the account + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + {"SetCode", Address1, []byte("hello")}, + }, + { + {"SetNonce", Address1, 1}, // make the object not empty + {"SelfDestruct", Address1}, + }, + { + {"GetCodeHash", Address1, common.Hash{0x1}}, + {"GetNonce", Address1, 0}, + {"Create", Address1}, // re-create the object + {"SetNonce", Address1, 1}, // make the object not empty + }, + } + checks := []Checks{ + // after execute tx1, before finalize + { + {"state", Address1, "key1", "value1"}, + {"code", Address1, []byte("hello")}, + }, + //after finalize tx1, + { + {"state", Address1, "key1", ""}, + {"code", Address1, []byte{}}, + }, + // after execute tx1, before && after finalize + { + {"state", Address1, "key1", ""}, + {"code", Address1, []byte{}}, + }, + } + + verifyDBs := func(c Checks, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + // firtst prepare for the account, to ensure the selfDestruct happens + txs[0].Call(maindb) + txs[0].Call(shadow) + maindb.Finalise(true) + shadow.Finalise(true) + + uncommitted := newUncommittedDB(maindb) + txs[1].Call(uncommitted) + txs[1].Call(shadow) + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // now check the state after finalize + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now execute another tx + uncommitted = newUncommittedDB(maindb) + txs[2].Call(uncommitted) + txs[2].Call(shadow) + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestStateAfterDestructWithBalance(t *testing.T) { + // tx0: + // 1. create an account -> key1: nil + // 2. set state -> key1: value1 + // 3. self destruct -> key1: nil + // tx1: + // 1. create an account -> key1: nil + // 2. set state -> key1: value2 + // 3. self destruct -> key1: nil + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, // balance should not be carried over + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + {"SelfDestruct", Address1}, + }, + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value2"}, + }, + } + checks := []Checks{ + // after execute tx0, before finalize + { + {"state", Address1, "key1", "value1"}, + {"balance", Address1, big.NewInt(0)}, + }, + //after finalize tx0, + { + {"state", Address1, "key1", ""}, + {"balance", Address1, big.NewInt(0)}, + }, + // after execute tx1, before && after finalize + { + {"state", Address1, "key1", "value2"}, + {"balance", Address1, big.NewInt(0)}, + }, + } + + verifyDBs := func(c Checks, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + uncommitted := newUncommittedDB(maindb) + txs[0].Call(uncommitted) + txs[0].Call(shadow) + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // now check the state after finalize + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now execute another tx + uncommitted = newUncommittedDB(maindb) + txs[1].Call(uncommitted) + txs[1].Call(shadow) + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestStateAfterDestruct(t *testing.T) { + // tx0: + // 1. create an account -> key1: nil + // 2. set state -> key1: value1 + // 3. self destruct -> key1: nil + // tx1: + // 1. create an account -> key1: nil + // 2. set state -> key1: value2 + // 3. self destruct -> key1: nil + txs := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value1"}, + }, + { + {"SelfDestruct", Address1}, + }, + { + {"Create", Address1}, + {"SetNonce", Address1, 1}, // make the object not empty + {"SetState", Address1, "key1", "value2"}, + }, + { + {"SelfDestruct", Address1}, + }, + } + checks := []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key1", ""}, + {"state", Address1, "key1", "value2"}, + {"state", Address1, "key1", ""}, + } + + verifyDBs := func(c Check, maindb *StateDB, uncommited *UncommittedDB) error { + if c.Verify(maindb) != nil { + return fmt.Errorf("maindb: %s", c.Verify(maindb).Error()) + } + if c.Verify(uncommited) != nil { + return fmt.Errorf("uncommited: %s", c.Verify(uncommited).Error()) + } + return nil + } + + maindb := newStateDB() + shadow := newStateDB() + uncommitted := newUncommittedDB(maindb) + txs[0].Call(uncommitted) + txs[0].Call(shadow) + // key1: value1 + if err := verifyDBs(checks[0], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + txs[1].Call(uncommitted) + txs[1].Call(shadow) + beforeMerge := checks[0] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now finalize the data to maindb + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // now recreate the account + uncommitted = newUncommittedDB(maindb) + // check the state again, to make sure the newly create uncommited have the same state of the shadow db + if err := verifyDBs(checks[1], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + txs[2].Call(uncommitted) + txs[2].Call(shadow) + beforeMerge = checks[2] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[2], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted = newUncommittedDB(maindb) + txs[3].Call(uncommitted) + txs[3].Call(shadow) + // check the state again, to ensure the Finalize works + beforeMerge = checks[2] + if err := verifyDBs(beforeMerge, shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + uncommitted.Merge(true) + maindb.Finalise(true) + shadow.Finalise(true) + // check the state again, to ensure the Finalize works + if err := verifyDBs(checks[3], shadow, uncommitted); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +// TestUncommitedDBCreateAccount tests the creation of an account in an uncommited DB. +func TestPevmUncommitedDBCreateAccount(t *testing.T) { + // case 1. create an account without previous state + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // case 2. create an account with previous state + txs = Txs{ + // previous state + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(200)}, + {"SetNonce", Address1, 2}, + }, + // create a new account, should have a balance of 100, and nonce = 0 + { + {"Create", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 0}, + }, + } + if er := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); er != nil { + t.Fatalf("ut failed, err=%s", er.Error()) + } +} + +func TestPevmAddBalance(t *testing.T) { + // case 1. add balance to an account without previous state + txs := Txs{ + { + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check := CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmSubBalance(t *testing.T) { + txs := Txs{ + { + {"AddBalance", Address1, big.NewInt(120)}, + {"SubBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(20)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(20)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmSetCode(t *testing.T) { + // case 1. set code to an account without previous state + txs := Txs{ + { + {"SetCode", Address1, []byte("hello")}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("")}, + }, + Maindb: []Check{ + {"code", Address1, []byte("")}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello")}, + }, + Maindb: []Check{ + {"code", Address1, []byte("")}, + }, + }, + AfterMerge: []Check{ + {"code", Address1, []byte("hello")}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. + txs = Txs{ + { + {"Create", Address1}, + {"SetCode", Address1, []byte("hello")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"code", Address1, []byte("hello")}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSetState(t *testing.T) { + // 1. set state to an account without previous state, check getState, getCommittedState + txs := Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + {"SetNonce", Address1, 1}, // to avoid the deletion when finalise + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", ""}, + {"state", Address1, "key2", ""}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"cstate", Address1, "key1", "value1"}, + {"cstate", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // 1. set state to an account without previous state, check getState, getCommittedState + prepare := Txs{ + { + {"Create", Address1}, + {"SetState", Address1, "key0", "value0"}, + {"SetState", Address1, "key1", "valuea"}, + {"AddBalance", Address1, big.NewInt(100)}, // to avoid the deletion when finalise + }, + } + statedb := newStateDB() + prepare.Call(statedb) + sroot := statedb.IntermediateRoot(true) + + maindb := newStateDB() + prepare.Call(maindb) + proot := maindb.IntermediateRoot(true) + if sroot.Cmp(proot) != 0 { + t.Fatalf("ut failed, err=%s", "roots mismatch") + } + uncommitedDB := newUncommittedDB(maindb) + + txs = Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + {"cstate", Address1, "key1", "valuea"}, + {"cstate", Address1, "key0", "value0"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + {"cstate", Address1, "key1", "valuea"}, + {"cstate", Address1, "key0", "value0"}, + {"obj", Address1, "exists"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"obj", Address1, "exists"}, + {"state", Address1, "key1", "valuea"}, + {"state", Address1, "key2", ""}, + }, + }, + AfterMerge: []Check{ + {"state", Address1, "key0", "value0"}, + {"state", Address1, "key1", "value1"}, + {"state", Address1, "key2", "value2"}, + {"cstate", Address1, "key0", "value0"}, + {"cstate", Address1, "key1", "value1"}, + {"cstate", Address1, "key2", "value2"}, + {"obj", Address1, "exists"}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, statedb, uncommitedDB, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestructStateDB(t *testing.T) { + //1. create address1, set code, set balance, set nonce + //2. self destruct address1 + //3. check the state of address1 before finalize + //4. finalize the stateDB + //5. check the state of address1 after finalize + statedb := newStateDB() + uncommitedState := newUncommittedDB(newStateDB()) + prepare := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + prepare.Call(statedb) + prepare.Call(uncommitedState) + checks := Checks{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("unexpected prepared state, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState); err != nil { + t.Fatalf("unexpected prepared state, err=%s", err.Error()) + } + destruct := Txs{ + { + {"SelfDestruct", Address1}, + }, + } + destruct.Call(statedb) + destruct.Call(uncommitedState) + // check the state of address1 before finalise: they should keep the same except the balance + checks = Checks{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state before finalized, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState); err != nil { + t.Fatalf("[uncommitted] unexpected selfdestruct state before finalized, err=%s", err.Error()) + } + statedb.Finalise(true) + uncommitedState.Merge(true) + uncommitedState.maindb.Finalise(true) + checks = Checks{ + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + } + // check the state of address1 after finalise: they should be all nil + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state after finalized, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState.maindb); err != nil { + t.Fatalf("[uncommitted] unexpected selfdestruct state after finalized, err=%s", err.Error()) + } + statedb.IntermediateRoot(true) + uncommitedState.maindb.IntermediateRoot(true) + // check the state of address1 after finalise: they should be all nil + checks = Checks{ + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + } + if err := checks.Verify(statedb); err != nil { + t.Fatalf("[statedb] unexpected selfdestruct state after committed, err=%s", err.Error()) + } + if err := checks.Verify(uncommitedState.maindb); err != nil { + t.Fatalf("[unstate.maindb] unexpected selfdestruct state after committed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestruct(t *testing.T) { + // case 1. self destruct an account without previous state + txs := Txs{ + { + {"SelfDestruct", Address1}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. selfdestruct an account who exists in uncommited db but not maindb + txs = Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct", Address1}, + }, + } + check = CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"codeHash", Address1, common.Hash{}}, // an empty hash will be returned if the account does not exist + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"codeHash", Address1, common.Hash{}}, + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. destruct an account who exist in maindb but not in uncommited db + prepare := Txs{ + { + {"Create", Address1}, + {"SetNonce", Address1, 12}, + {"SetCode", Address1, []byte("hello world")}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + statedb := newStateDB() + maindb := newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + UncommittedDB := newUncommittedDB(maindb) + txs = Txs{ + { + {"SelfDestruct", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + Maindb: []Check{ + {"code", Address1, []byte("hello world")}, + {"balance", Address1, big.NewInt(100)}, + {"nonce", Address1, 12}, + {"obj", Address1, "exists"}, + }, + }, + AfterMerge: []Check{ + {"codeHash", Address1, common.Hash{}}, + {"code", Address1, []byte(nil)}, + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, statedb, UncommittedDB, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. create an account after self destruct + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct", Address1}, + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(50)}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(50)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmSelfDestruct6780AndRevert(t *testing.T) { + shadow := newStateDB() + uncommitted := newUncommittedDB(newStateDB()) + //prepare: create an account, set state, set balance + // A1{key1: val1, balance: 100, accesslist: 0x33} + prepare := Tx{ + {"Create", Address1}, + {"SetState", Address1, "key1", "val1"}, + {"AddBalance", Address1, big.NewInt(100)}, + {"AddSlots", Address1, common.Hash{0x33}}, + } + prepareCheck := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"slot", Address1, common.Hash{0x33}, true}, + } + check := func(verify Checks, shadow, uncommitted vm.StateDB) { + if err := verify.Verify(shadow); err != nil { + t.Fatalf("maindb verify failed, err=%s", err.Error()) + } + if err := verify.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted verify failed, err=%s", err.Error()) + } + } + prepare.Call(shadow) + prepare.Call(uncommitted) + check(prepareCheck, shadow, uncommitted) + + // do the following operations, and then selfdestruct, and then revert + // 1. add balance 200, + // 2. set state key1: val2 + // 3. add slot 0x34 + // 4. selfdestruct + // 5. revert + case1 := Tx{ + {"AddBalance", Address1, big.NewInt(200)}, + {"SetState", Address1, "key1", "val2"}, + {"SetState", Address1, "key2", "val0"}, + {"AddSlots", Address1, common.Hash{0x34}}, + } + case1SelfDestruct := Tx{ + {"SelfDestruct6780", Address1}, + } + beforeSelfDestruct := Checks{ + {"balance", Address1, big.NewInt(300)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + beforeRevert := Checks{ + {"balance", Address1, big.NewInt(0)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + afterRevert := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"state", Address1, "key2", ""}, + {"slot", Address1, common.Hash{0x34}, false}, + {"slot", Address1, common.Hash{0x33}, true}, + } + // run the case1 on shadow + snapshot := shadow.Snapshot() + case1.Call(shadow) + if err := beforeSelfDestruct.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(shadow) + if err := beforeRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + shadow.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + + // now on uncommitted + snapshot = uncommitted.Snapshot() + case1.Call(uncommitted) + if err := beforeSelfDestruct.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + uncommitted.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + + // now compare the mercle root of shadow and uncommitted + shadow.Finalise(true) + if err := uncommitted.Merge(true); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + uncommitted.Finalise(true) + if shadow.IntermediateRoot(true) != uncommitted.maindb.IntermediateRoot(true) { + t.Fatalf("ut failed, err: the mercle root of shadow and uncommitted are different") + } +} + +func TestPevmSelfDestructAndRevert(t *testing.T) { + shadow := newStateDB() + uncommitted := newUncommittedDB(newStateDB()) + //prepare: create an account, set state, set balance + // A1{key1: val1, balance: 100, accesslist: 0x33} + prepare := Tx{ + {"Create", Address1}, + {"SetState", Address1, "key1", "val1"}, + {"AddBalance", Address1, big.NewInt(100)}, + {"AddSlots", Address1, common.Hash{0x33}}, + } + prepareCheck := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"slot", Address1, common.Hash{0x33}, true}, + } + check := func(verify Checks, shadow, uncommitted vm.StateDB) { + if err := verify.Verify(shadow); err != nil { + t.Fatalf("maindb verify failed, err=%s", err.Error()) + } + if err := verify.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted verify failed, err=%s", err.Error()) + } + } + prepare.Call(shadow) + prepare.Call(uncommitted) + shadow.Finalise(true) + uncommitted.Finalise(true) + check(prepareCheck, shadow, uncommitted) + + // do the following operations, and then selfdestruct, and then revert + // 1. add balance 200, + // 2. set state key1: val2 + // 3. add slot 0x34 + // 4. selfdestruct + // 5. revert + case1 := Tx{ + {"AddBalance", Address1, big.NewInt(200)}, + {"SetState", Address1, "key1", "val2"}, + {"SetState", Address1, "key2", "val0"}, + {"AddSlots", Address1, common.Hash{0x34}}, + } + case1SelfDestruct := Tx{ + {"SelfDestruct", Address1}, + } + beforeSelfDestruct := Checks{ + {"balance", Address1, big.NewInt(300)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + beforeRevert := Checks{ + {"balance", Address1, big.NewInt(0)}, + {"state", Address1, "key1", "val2"}, + {"state", Address1, "key2", "val0"}, + {"slot", Address1, common.Hash{0x34}, true}, + {"slot", Address1, common.Hash{0x33}, true}, + } + afterRevert := Checks{ + {"balance", Address1, big.NewInt(100)}, + {"state", Address1, "key1", "val1"}, + {"state", Address1, "key2", ""}, + {"slot", Address1, common.Hash{0x34}, false}, + {"slot", Address1, common.Hash{0x33}, true}, + } + // run the case1 on shadow + snapshot := shadow.Snapshot() + case1.Call(shadow) + if err := beforeSelfDestruct.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(shadow) + if err := beforeRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + shadow.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(shadow); err != nil { + t.Fatalf("maindb ut failed, err:%s", err.Error()) + } + + // now on uncommitted + snapshot = uncommitted.Snapshot() + case1.Call(uncommitted) + if err := beforeSelfDestruct.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + case1SelfDestruct.Call(uncommitted) + if err := beforeRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + uncommitted.RevertToSnapshot(snapshot) + if err := afterRevert.Verify(uncommitted); err != nil { + t.Fatalf("uncommitted ut failed, err:%s", err.Error()) + } + + // now compare the mercle root of shadow and uncommitted + shadow.Finalise(true) + if err := uncommitted.Merge(true); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + uncommitted.Finalise(true) + if shadow.IntermediateRoot(true) != uncommitted.maindb.IntermediateRoot(true) { + t.Fatalf("ut failed, err: the mercle root of shadow and uncommitted are different") + } +} + +func TestPevmSelfDestruct6780(t *testing.T) { + // case 1. no previous state + txs := Txs{ + { + {"SelfDestruct6780", Address1}, + }, + } + check := CheckState{ + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"obj", Address1, "nil"}, + }, + Maindb: []Check{ + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 2. self destruct an account with previous state + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct6780", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 3. create an account after self destruct + txs = Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 10}, + {"SelfDestruct6780", Address1}, + {"Create", Address1}, + }, + } + check = CheckState{ + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(0)}, + {"nonce", Address1, 0}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + // case 4. self destruct 6780 should not work if the account is not empty + maindb := newStateDB() + statedb := newStateDB() + prepare := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + {"SetNonce", Address1, 2}, + }, + } + prepare.Call(maindb) + prepare.Call(statedb) + maindb.IntermediateRoot(true) + statedb.IntermediateRoot(true) + txs = Txs{ + { + {"AddBalance", Address1, big.NewInt(100)}, + {"SelfDestruct6780", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + Maindb: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + }, + AfterMerge: []Check{ + {"balance", Address1, big.NewInt(200)}, + {"nonce", Address1, 2}, + }, + } + if err := runCase(txs, statedb, newUncommittedDB(maindb), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmExistsAndEmpty(t *testing.T) { + // case 1. exists an account without previous state + txs := Txs{ + { + {"Create", Address1}, + {"AddBalance", Address1, big.NewInt(100)}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, true}, + {"empty", Address1, false}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"empty", Address1, false}, + {"exists", Address1, true}, + {"balance", Address1, big.NewInt(100)}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + txs = Txs{ + { + {"Create", Address1}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"exists", Address1, true}, //the account is created, but empty + {"empty", Address1, true}, + }, + Maindb: []Check{ + {"exists", Address1, false}, + {"empty", Address1, true}, + {"obj", Address1, "nil"}, + }, + }, + AfterMerge: []Check{ + {"empty", Address1, true}, //empty accounts will be deleted after finalise + {"exists", Address1, false}, + {"obj", Address1, "nil"}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmRefund(t *testing.T) { + // case 1. add refund from 0 + txs := Txs{ + { + {"AddRefund", 100}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"refund", 0}, + }, + Maindb: []Check{ + {"refund", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"refund", 100}, + }, + Maindb: []Check{ + {"refund", 0}, + }, + }, + AfterMerge: []Check{ + {"refund", 0}, //refund will be cleared after finalise + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // case 2. add refund to a new account + statedb := newStateDB() + unstateMain := newStateDB() + unstateMain.refund, statedb.refund = 100, 100 + txs = Txs{ + { + {"AddRefund", 100}, + {"SubRefund", 20}, + }, + } + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"refund", 100}, + }, + Maindb: []Check{ + {"refund", 100}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"refund", 180}, + }, + Maindb: []Check{ + {"refund", 100}, + }, + }, + AfterMerge: []Check{ + {"refund", 0}, + }, + } + if err := runCase(txs, statedb, newUncommittedDB(unstateMain), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmPreimages(t *testing.T) { + txs := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("hello")), []byte("world")}, + {"AddPreimage", common.BytesToHash([]byte("hello")), []byte("world2")}, // the second one should be ignored + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + Maindb: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("world")}, + }, + Maindb: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("")}, + }, + }, + AfterMerge: []Check{ + {"preimage", common.BytesToHash([]byte("hello")), []byte("world")}, + }, + } + if err := runCase(txs, newStateDB(), newUncommittedDB(newStateDB()), check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } +} + +func TestPevmLogs(t *testing.T) { + // diff logs of two stateDBs: + // 1. check thash + // 2. check index + // 3. check txIndex + // log.thash, log.index, log.txIndex = s.txHash, s.logSize, s.txIndex + // so we need to test it by running a true transaction, in which we call the log function + txs := Txs{ + { + {"AddLog", &types.Log{Data: []byte("hello")}}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + thash := common.BytesToHash([]byte("00000000000000000tx")) + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"loglen", 0}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 20, 0}, // i, Data, TxIndex, Index + {"log", thash, 1, []byte("world"), 20, 1}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + AfterMerge: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 20, 0}, + {"log", thash, 1, []byte("world"), 20, 1}, + }, + } + state := newStateDB() + maindb := newStateDB() + unstate := newUncommittedDB(maindb) + //set TxContext + unstate.txIndex, state.txIndex, maindb.txIndex = 20, 20, 19 + unstate.txHash, state.thash, maindb.thash = thash, thash, common.Hash{} + + if err := runCase(txs, state, unstate, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // test the maindb with txIndex=-1 + txs = Txs{ + { + {"AddLog", &types.Log{Data: []byte("hello")}}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + thash = common.BytesToHash([]byte("00000000000000000tx2")) + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"loglen", 0}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 0, 0}, // i, Data, TxIndex, Index + {"log", thash, 1, []byte("world"), 0, 1}, + }, + Maindb: []Check{ + {"loglen", 0}, + }, + }, + AfterMerge: []Check{ + {"loglen", 2}, + {"log", thash, 0, []byte("hello"), 0, 0}, + {"log", thash, 1, []byte("world"), 0, 1}, + }, + } + state = newStateDB() + state.SetTxContext(thash, 0) + unstate = newUncommittedDB(newStateDB()) + unstate.SetTxContext(thash, 0) + if err := runCase(txs, state, unstate, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +func TestPevmAccessList(t *testing.T) { + // before Berlin hardfork: + // accesslist will be available in the whole scope of block + // after Berlin hardfork: + // accesslist will be available only in the scope of the transaction, except those predefined. + + // before Berlin hardfork: + // case 1. + // prepare: add address 1, add slot 1 + // tx: add address 2, add slot 2 + // check: address 1, slot 1, address 2, slot 2 are all in the accesslist + coinBase := common.BytesToAddress([]byte("0xcoinbase")) + sender := common.BytesToAddress([]byte("0xsender")) + key1 := common.Hash{0x33} + key2 := common.Hash{0x34} + prepare := Txs{ + { + {"AddAddress", Address1}, + {"AddSlots", Address1, key1}, + }, + } + tx := Txs{ + { + {"AddAddress", Address2}, + {"AddSlots", Address2, key2}, + }, + } + check := CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"address", Address1, true}, + {"slot", Address1, key1, true}, + }, + Maindb: []Check{ + {"address", Address1, true}, + {"slot", Address1, key1, true}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, true}, + {"address", sender, false}, + {"address", coinBase, false}, + }, + Maindb: []Check{ + {"address", Address1, true}, + {"address", Address2, false}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, false}, + }, + }, + AfterMerge: []Check{ + {"address", Address1, true}, + {"address", Address2, true}, + {"slot", Address1, key1, true}, + {"slot", Address2, key2, true}, + {"address", sender, false}, + {"address", coinBase, false}, + }, + } + statedb := newStateDB() + maindb := newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + + statedb.Prepare(params.Rules{IsBerlin: false}, sender, coinBase, nil, nil, nil) + uncommited := newUncommittedDB(maindb) + uncommited.Prepare(params.Rules{IsBerlin: false}, sender, coinBase, nil, nil, nil) + if err := runCase(tx, statedb, uncommited, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + + // after Berlin hardfork: + // case 2: + // prepare: add address 1, add slot 1 + // tx: add address 2, add slot 2 + // before merge: + // address 2, slot 2 are in the accesslist; but not address 1, slot 1 + // predefined address n, slot n are in the accesslist + // after merge: + // the same as before merge + check = CheckState{ + BeforeRun: uncommitedState{ + Uncommited: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"slot", Address1, key1, false}, + }, + }, + BeforeMerge: uncommitedState{ + Uncommited: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"address", Address2, true}, + {"slot", Address1, key1, false}, + {"slot", Address2, key2, true}, + }, + }, + AfterMerge: []Check{ + {"address", sender, true}, + {"address", coinBase, true}, + {"address", Address1, false}, + {"address", Address2, true}, + {"slot", Address1, key1, false}, + {"slot", Address2, key2, true}, + }, + } + statedb, maindb = newStateDB(), newStateDB() + prepare.Call(statedb) + prepare.Call(maindb) + statedb.Prepare(params.Rules{IsBerlin: true, IsShanghai: true}, sender, coinBase, nil, nil, nil) + uncommited = newUncommittedDB(maindb) + uncommited.Prepare(params.Rules{IsBerlin: true, IsShanghai: true}, sender, coinBase, nil, nil, nil) + if err := runCase(tx, statedb, uncommited, check); err != nil { + t.Fatalf("ut failed, err=%s", err.Error()) + } + +} + +// ================== conflict test ================== +// case 1. conflict in objects: balance, nonce, code, state +// case 2. conflict in transient storage +// case 3. conflict in accesslist +// case 4. conflict in logs +func TestPevmConflictObject(t *testing.T) { + // case 1. conflict in balance + // case 2. conflict in nonce + // case 3. conflict in code + // case 4. conflict in state + + // case of balance + prepare := Txs{ + { + {"AddBalance", Address1, big.NewInt(10)}, + }, + } + add40 := Txs{ + { + {"AddBalance", Address1, big.NewInt(30)}, + }, + } + add50 := Txs{ + { + {"AddBalance", Address1, big.NewInt(50)}, + }, + } + check := []Check{ + {"balance", Address1, big.NewInt(40)}, + } + check2 := []Check{ + {"balance", Address1, big.NewInt(30)}, + } + if err := runConflictCase(nil, add40, add50, check2); err != nil { + t.Fatalf("ut failed, errr:%s", err.Error()) + } + if err := runConflictCase(prepare, add40, add50, check); err != nil { + t.Fatalf("ut failed, errr:%s", err.Error()) + } + + // case of nonce + prepare = Txs{ + { + {"SetNonce", Address1, 10}, + }, + } + txs := Txs{ + { + {"SetNonce", Address1, 21}, + }, + } + txs2 := Txs{ + { + {"SetNonce", Address1, 22}, + }, + } + check = []Check{ + {"nonce", Address1, 21}, + } + if err := runConflictCase(nil, txs, txs2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(prepare, txs, txs2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + + // case of code + prepare = Txs{ + { + {"SetCode", Address1, []byte("hello")}, + }, + } + op1 := Txs{ + { + {"SetCode", Address1, []byte("here we go")}, + }, + } + op2 := Txs{ + { + {"SetCode", Address1, []byte("here we go now")}, + }, + } + check = []Check{ + {"code", Address1, []byte("here we go")}, + } + if err := runConflictCase(nil, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(prepare, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + + // case of state + prepare = Txs{ + { + {"SetState", Address1, "key1", "value1"}, + {"SetState", Address1, "key2", "value2"}, + }, + } + op1 = Txs{ + { + {"SetState", Address1, "key2", "value2.22"}, + {"SetState", Address1, "key3", "value2.33"}, + }, + } + op2 = Txs{ + { + {"SetState", Address1, "key2", "value3.22"}, + {"SetState", Address1, "key1", "value3.11"}, + }, + } + check = []Check{ + {"state", Address1, "key2", "value3.22"}, + {"state", Address1, "key1", "value3.11"}, + {"state", Address1, "key3", ""}, + } + if err := runConflictCase(prepare, op2, op1, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + if err := runConflictCase(nil, op2, op1, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + +} + +func TestPevmConflictLogs(t *testing.T) { + thash1 := common.BytesToHash([]byte("tx1")) + thash2 := common.BytesToHash([]byte("tx2")) + thash3 := common.BytesToHash([]byte("tx3")) + prepare := Txs{ + { + {"SetTxContext", thash1, 0}, + {"AddLog", &types.Log{Data: []byte("hello")}}, + }, + } + op1 := Txs{ + { + {"SetTxContext", thash2, 1}, + {"AddLog", &types.Log{Data: []byte("world")}}, + }, + } + op2 := Txs{ + { + {"SetTxContext", thash3, 1}, + {"AddLog", &types.Log{Data: []byte("eth")}}, + }, + } + check := []Check{ + {"loglen", 2}, + {"log", thash1, 0, []byte("hello"), 0, 0}, + {"log", thash2, 0, []byte("world"), 1, 1}, + } + if err := runConflictCase(prepare, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } + +} + +func TestPevmConflictPreimage(t *testing.T) { + op1 := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("key1")), []byte("value1")}, + }, + } + op2 := Txs{ + { + {"AddPreimage", common.BytesToHash([]byte("key1")), []byte("value2")}, + }, + } + check := []Check{ + {"preimage", common.BytesToHash([]byte("key1")), []byte("value1")}, + } + if err := runConflictCase(nil, op1, op2, check); err != nil { + t.Fatalf("ut failed, err:%s", err.Error()) + } +} + +type uncommitedState struct { + Uncommited []Check + Maindb []Check +} + +type CheckState struct { + BeforeRun uncommitedState + BeforeMerge uncommitedState + AfterMerge []Check +} + +type Check []interface{} + +type Checks []Check + +func (c Checks) Verify(state vm.StateDB) error { + for _, check := range c { + if err := check.Verify(state); err != nil { + return err + } + } + return nil +} + +// +// {"address", Address1, true}, +// {"slot", Address1, "key1", true}, + +func (c Check) Verify(state vm.StateDB) error { + switch c[0].(string) { + + case "address": + addr := c[1].(common.Address) + exists := c[2].(bool) + var accesslist *accessList + if db, ok := state.(*StateDB); ok { + accesslist = db.accessList + } else if db, ok := state.(*UncommittedDB); ok { + accesslist = db.accessList + } else { + panic("unknown stateDB type") + } + if exists == accesslist.ContainsAddress(addr) { + return nil + } else { + return fmt.Errorf("address 'in list' mismatch, addr:%s, expected:%t, actual:%t", addr.String(), exists, accesslist.ContainsAddress(addr)) + } + + case "slot": + addr := c[1].(common.Address) + key := c[2].(common.Hash) + exists := c[3].(bool) + var accesslist *accessList + if db, ok := state.(*StateDB); ok { + accesslist = db.accessList + } else if db, ok := state.(*UncommittedDB); ok { + accesslist = db.accessList + } else { + panic("unknown stateDB type") + } + _, slotExists := accesslist.Contains(addr, key) + if exists == slotExists { + return nil + } else { + return fmt.Errorf("slot 'in list' mismatch, addr:%s, key:%s, expected:%t, actual:%t", addr, key.String(), exists, slotExists) + } + + case "loglen": + loglen := c[1].(int) + var logSize int + if db, ok := state.(*StateDB); ok { + logSize = int(db.logSize) + } else if db, ok := state.(*UncommittedDB); ok { + logSize = len(db.logs) + } else { + panic("unknown stateDB type") + } + if loglen == logSize { + return nil + } else { + return fmt.Errorf("loglen mismatch, expected:%d, actual:%d", loglen, logSize) + } + + //{"log", thash, 0, []byte("hello"), 20, 0}, + case "log": + thash := c[1].(common.Hash) + i := c[2].(int) + data := c[3].([]byte) + txIndex := c[4].(int) + Index := c[5].(int) + var logs []*types.Log + if db, ok := state.(*StateDB); ok { + logs, ok = db.logs[thash] + if !ok { + return fmt.Errorf("log thash not found, thash:%s", thash.String()) + } + } else if db, ok := state.(*UncommittedDB); ok { + if thash.Cmp(db.txHash) != 0 { + return fmt.Errorf("log thash not found, expected:%s, actual:%s", thash.String(), db.txHash.String()) + } + logs = db.logs + } else { + panic("unknown stateDB type") + } + if len(logs) <= i { + return fmt.Errorf("log index out of range, index:%d, len:%d", i, len(logs)) + } + log := logs[i] + if !bytes.Equal(log.Data, data) { + return fmt.Errorf("log mismatch, expected:%s, actual:%s", string(data), string(logs[i].Data)) + } + if log.TxIndex != uint(txIndex) { + return fmt.Errorf("log txIndex mismatch, expected:%d, actual:%d", txIndex, log.TxIndex) + } + if log.Index != uint(Index) { + return fmt.Errorf("log index mismatch, expected:%d, actual:%d", Index, log.Index) + } + return nil + + case "preimage": + hash := c[1].(common.Hash) + data := c[2].([]byte) + var preimages map[common.Hash][]byte + if db, ok := state.(*StateDB); ok { + preimages = db.preimages + } else if db, ok := state.(*UncommittedDB); ok { + preimages = db.preimages + } else { + panic("unknown stateDB type") + } + if bytes.Equal(preimages[hash], data) { + return nil + } else { + return fmt.Errorf("preimage mismatch, expected:%s, actual:%s", string(data), string(preimages[hash])) + } + + case "preimageExists": + hash := c[1].(common.Hash) + exists := c[2].(bool) + var preimages map[common.Hash][]byte + if db, ok := state.(*StateDB); ok { + preimages = db.preimages + } else if db, ok := state.(*UncommittedDB); ok { + preimages = db.preimages + } else { + panic("unknown stateDB type") + } + if _, ok := preimages[hash]; ok == exists { + return nil + } else { + return fmt.Errorf("preimageExists mismatch, expected:%t, actual:%t", exists, ok) + } + + case "tstorage": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetTransientState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("tstorage mismatch, key:%s, expected:%s, actual:%s", key.String(), val.String(), state.GetTransientState(addr, key).String()) + } + + case "transientExists": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + exists := c[3].(bool) + var ts transientStorage + if db, ok := state.(*StateDB); ok { + ts = db.transientStorage + } else if db, ok := state.(*UncommittedDB); ok { + ts = db.transientStorage + } else { + panic("unknown stateDB type") + } + found := false + if _, ok := ts[addr]; ok { + if _, ok := ts[addr][key]; ok { + found = true + } + } + if found == exists { + return nil + } else { + return fmt.Errorf("transientExists mismatch, expected:%t, actual:%t", exists, found) + } + + case "exists": + addr := c[1].(common.Address) + exists := c[2].(bool) + if state.Exist(addr) == exists { + return nil + } else { + return fmt.Errorf("exists mismatch,addr:%s, expected:%t, actual:%t", addr.String(), exists, state.Exist(addr)) + } + + case "empty": + addr := c[1].(common.Address) + empty := c[2].(bool) + if state.Empty(addr) == empty { + return nil + } else { + return fmt.Errorf("empty mismatch, expected:%t, actual:%t", empty, state.Empty(addr)) + } + + case "balance": + addr := c[1].(common.Address) + balance, _ := uint256.FromBig(c[2].(*big.Int)) + if state.GetBalance(addr).Cmp(balance) == 0 { + return nil + } else { + return fmt.Errorf("balance mismatch, expected:%d, actual:%d", balance.Uint64(), state.GetBalance(addr).Uint64()) + } + + case "nonce": + addr := c[1].(common.Address) + nonce := uint64(c[2].(int)) + if state.GetNonce(addr) == nonce { + return nil + } else { + return fmt.Errorf("nonce mismatch, expected:%d, actual:%d", nonce, state.GetNonce(addr)) + } + + case "code": + addr := c[1].(common.Address) + code := c[2].([]byte) + if bytes.Equal(state.GetCode(addr), code) { + return nil + } else { + return fmt.Errorf("code mismatch, expected:%s, actual:%s", string(code), string(state.GetCode(addr))) + } + + case "codeHash": + addr := c[1].(common.Address) + codeHash := c[2].(common.Hash) + if codeHash.Cmp(state.GetCodeHash(addr)) == 0 { + return nil + } else { + return fmt.Errorf("codeHash mismatch, expected:%s, actual:%s", codeHash.String(), state.GetCodeHash(addr).String()) + } + + case "state": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("state mismatch, key:%s, expected:%s, actual:%s", key.String(), val.String(), state.GetState(addr, key).String()) + } + + case "cstate": + addr := c[1].(common.Address) + key := common.BytesToHash([]byte(c[2].(string))) + val := common.BytesToHash([]byte(c[3].(string))) + if state.GetCommittedState(addr, key).Cmp(val) == 0 { + return nil + } else { + return fmt.Errorf("committed state mismatch, expected:%s, actual:%s", val.String(), state.GetCommittedState(addr, key).String()) + } + + case "obj": + addr := c[1].(common.Address) + exists := c[2].(string) + if exists == "nil" { + if state.Exist(addr) { + return fmt.Errorf("object was expected not exists, addr:%s", addr.String()) + } else { + return nil + } + } else { + if !state.Exist(addr) { + return fmt.Errorf("object was expected exists, addr:%s", addr.String()) + } else { + return nil + } + } + + case "refund": + refund := uint64(c[1].(int)) + if state.GetRefund() == refund { + return nil + } else { + return fmt.Errorf("refund mismatch, expected:%d, actual:%d", refund, state.GetRefund()) + } + + default: + panic(fmt.Sprintf("unknown check type: %s", c[0].(string))) + } +} + +type Op []interface{} + +type Tx []Op + +func (op Op) Call(db vm.StateDB) error { + switch op[0].(string) { + case "GetNonce": + addr := op[1].(common.Address) + db.GetNonce(addr) + return nil + case "GetCodeHash": + addr := op[1].(common.Address) + db.GetCodeHash(addr) + return nil + case "SetTxContext": + state := db + if db, ok := state.(*UncommittedDB); ok { + db.SetTxContext(op[1].(common.Hash), op[2].(int)) + } else if db, ok := state.(*StateDB); ok { + db.SetTxContext(op[1].(common.Hash), op[2].(int)) + } else { + panic("unknown stateDB type") + } + return nil + + case "AddSlots": + addr := op[1].(common.Address) + slot := op[2].(common.Hash) + db.AddSlotToAccessList(addr, slot) + return nil + + case "AddAddress": + addr := op[1].(common.Address) + db.AddAddressToAccessList(addr) + return nil + + case "AddLog": + log := op[1].(*types.Log) + db.AddLog(log) + return nil + + case "SetTransientStorage": + addr := op[1].(common.Address) + key := op[2].(string) + val := op[3].(string) + db.SetTransientState(addr, common.BytesToHash([]byte(key)), common.BytesToHash([]byte(val))) + return nil + + case "Create": + addr := op[1].(common.Address) + db.CreateAccount(addr) + return nil + + case "AddPreimage": + hash := op[1].(common.Hash) + data := op[2].([]byte) + db.AddPreimage(hash, data) + return nil + + case "AddBalance": + addr := op[1].(common.Address) + balance, _ := uint256.FromBig(op[2].(*big.Int)) + db.AddBalance(addr, balance) + return nil + case "SubBalance": + addr := op[1].(common.Address) + balance, _ := uint256.FromBig(op[2].(*big.Int)) + db.SubBalance(addr, balance) + return nil + + case "SetNonce": + addr := op[1].(common.Address) + nonce := uint64(op[2].(int)) + db.SetNonce(addr, nonce) + return nil + + case "SetCode": + addr := op[1].(common.Address) + code := op[2].([]byte) + db.SetCode(addr, code) + return nil + + case "AddRefund": + refund := uint64(op[1].(int)) + db.AddRefund(refund) + return nil + + case "SubRefund": + refund := uint64(op[1].(int)) + db.SubRefund(refund) + return nil + + case "SetState": + addr := op[1].(common.Address) + key := common.BytesToHash([]byte(op[2].(string))) + val := common.BytesToHash([]byte(op[3].(string))) + db.SetState(addr, key, val) + return nil + + case "SelfDestruct": + addr := op[1].(common.Address) + db.SelfDestruct(addr) + return nil + + case "SelfDestruct6780": + addr := op[1].(common.Address) + db.Selfdestruct6780(addr) + return nil + + case "Snapshot": + snapid := op[1].(*int) + *snapid = db.Snapshot() + return nil + + case "Revert": + snapid := op[1].(*int) + db.RevertToSnapshot(*snapid) + return nil + + default: + return fmt.Errorf("unknown op type: %s", op[0].(string)) + } +} + +// Call executes the transaction. +func (tx Tx) Call(db vm.StateDB) error { + for _, op := range tx { + if err := op.Call(db); err != nil { + return err + } + } + return nil +} + +type Txs []Tx + +func (txs Txs) Call(db vm.StateDB) error { + for _, tx := range txs { + if err := tx.Call(db); err != nil { + return err + } + } + return nil +} + +func triedbConfig(StateScheme string) *triedb.Config { + config := &triedb.Config{ + Preimages: true, + NoTries: false, + } + if StateScheme == rawdb.HashScheme { + config.HashDB = &hashdb.Config{ + CleanCacheSize: 1 * 1024 * 1024, + } + } + if StateScheme == rawdb.PathScheme { + config.PathDB = &pathdb.Config{ + //TrieNodeBufferType: c.PathNodeBuffer, + //StateHistory: c.StateHistory, + CleanCacheSize: 1 * 1024 * 1024, + DirtyCacheSize: 1 * 1024 * 1024, + //ProposeBlockInterval: c.ProposeBlockInterval, + //NotifyKeep: keepFunc, + //JournalFilePath: c.JournalFilePath, + //JournalFile: c.JournalFile, + } + } + return config +} + +func newStateDB() *StateDB { + memdb := rawdb.NewMemoryDatabase() + // Open trie database with provided config + triedb := triedb.NewDatabase(memdb, triedbConfig(rawdb.HashScheme)) + stateCache := NewDatabaseWithNodeDB(memdb, triedb) + st, err := New(common.Hash{}, stateCache, nil) + if err != nil { + panic(err) + } + return st +} + +func newUncommittedDB(db *StateDB) *UncommittedDB { + return NewUncommittedDB(db) +} + +func runTxsOnStateDB(txs Txs, db *StateDB, check CheckState) (common.Hash, error) { + // run the transactions + if err := txs.Call(db); err != nil { + return common.Hash{}, fmt.Errorf("state failed to run txs: %v", err) + } + // states before merge should be the same as the uncommitted db + for _, c := range check.BeforeMerge.Uncommited { + if err := c.Verify(db); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][statedbt db] failed to verify : %v", err) + } + } + return db.IntermediateRoot(true), nil +} + +func runTxOnUncommittedDB(txs Txs, db *UncommittedDB, check CheckState) (common.Hash, error) { + // run the transaction + if err := txs.Call(db); err != nil { + return common.Hash{}, fmt.Errorf("unconfirm db failed to run txs: %v", err) + } + for _, check := range check.BeforeMerge.Uncommited { + if err := check.Verify(db); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][uncommited db] failed to verify : %v", err) + } + } + for _, check := range check.BeforeMerge.Maindb { + if err := check.Verify(db.maindb); err != nil { + return common.Hash{}, fmt.Errorf("[before merge][maindb] failed to verify : %v", err) + } + } + if err := db.Merge(true); err != nil { + return common.Hash{}, fmt.Errorf("failed to merge: %v", err) + } + return db.maindb.IntermediateRoot(true), nil +} + +func runConflictCase(prepare, txs1, txs2 Txs, checks []Check) error { + maindb := newStateDB() + if prepare != nil { + maindb.CreateAccount(Address1) + for _, op := range prepare { + if err := op.Call(maindb); err != nil { + return fmt.Errorf("failed to call prepare txs, err:%s", err.Error()) + } + } + } + un1, un2 := newUncommittedDB(maindb), newUncommittedDB(maindb) + if err := txs1.Call(un1); err != nil { + return fmt.Errorf("failed to call txs1, err:%s", err.Error()) + } + if err := txs2.Call(un2); err != nil { + return fmt.Errorf("failed to call txs2, err:%s", err.Error()) + } + if err := un1.ConflictsToMaindb(); err != nil { + return fmt.Errorf("failed to check conflicts of un1, err:%s", err.Error()) + } + if err := un1.Merge(true); err != nil { + return fmt.Errorf("failed to merge un1, err:%s", err.Error()) + } + if err := un2.ConflictsToMaindb(); err == nil { + return fmt.Errorf("un2 merge is expected to be failed") + } + for _, c := range checks { + if err := c.Verify(maindb); err != nil { + return fmt.Errorf("failed to verify maindb, err:%s", err.Error()) + } + } + return nil +} + +func runCase(txs Txs, state *StateDB, unstate *UncommittedDB, check CheckState) error { + stRoot, errSt := runTxsOnStateDB(txs, state, check) + unRoot, errUn := runTxOnUncommittedDB(txs, unstate, check) + if errSt != nil { + return fmt.Errorf("failed to run txs on state db: %v", errSt) + } + if errUn != nil { + return fmt.Errorf("failed to run tx on uncommited db: %v", errUn) + } + for _, check := range check.AfterMerge { + if err := check.Verify(state); err != nil { + return fmt.Errorf("[after merge] failed to verify statedb: %v", err) + } + // an uncommitted is invalid after merge, so we verify its maindb instead + if err := check.Verify(unstate.maindb); err != nil { + return fmt.Errorf("[after merge] failed to verify uncommited db: %v", err) + } + } + if stRoot.Cmp(unRoot) != 0 { + return fmt.Errorf("state root mismatch: %s != %s", stRoot.String(), unRoot.String()) + } + return nil +} + +func Diff(a, b *StateDB) []common.Hash { + // compare the two stateDBs, and return the different objects + return nil +} + +type object struct { + data int +} + +type HashSyncMap struct { + cap int + maps []sync.Map +} + +func newHashSyncMap(cap int) *HashSyncMap { + hsm := &HashSyncMap{cap: cap, maps: make([]sync.Map, cap)} + return hsm +} + +func (hsm *HashSyncMap) Load(key interface{}) (value any, ok bool) { + addr := key.(common.Address) + slot := hash2int(addr) % hsm.cap + return hsm.maps[slot].Load(key) +} + +func (hsm *HashSyncMap) Store(key, val interface{}) { + addr := key.(common.Address) + slot := hash2int(addr) % hsm.cap + hsm.maps[slot].Store(key, val) +} + +type HashMutexMap struct { + cap int + maps []struct { + sync.Mutex + data map[common.Address]*object + } +} + +func newHashMutexMap(cap int) *HashMutexMap { + hmm := &HashMutexMap{cap: cap, maps: make([]struct { + sync.Mutex + data map[common.Address]*object + }, cap)} + for i := 0; i < cap; i++ { + hmm.maps[i].data = make(map[common.Address]*object) + } + return hmm +} + +func (hmm *HashMutexMap) Load(key interface{}) (value any, ok bool) { + addr := key.(common.Address) + slot := hash2int(addr) % hmm.cap + cache := &hmm.maps[slot] + cache.Mutex.Lock() + defer cache.Mutex.Unlock() + value, ok = cache.data[addr] + return value, ok +} + +func (hmm *HashMutexMap) Store(key, val interface{}) { + addr := key.(common.Address) + obj := val.(*object) + slot := hash2int(addr) % hmm.cap + cache := &hmm.maps[slot] + cache.Mutex.Lock() + defer cache.Mutex.Unlock() + cache.data[addr] = obj +} + +type istore interface { + Store(key, val interface{}) + Load(key interface{}) (value any, ok bool) +} + +type DB struct { + cache istore + persist map[common.Address]*object +} + +func newDB(cache istore, addresses []common.Address) *DB { + db := &DB{cache: cache, persist: make(map[common.Address]*object)} + i := 0 + for _, addr := range addresses { + db.persist[addr] = &object{data: i} + i++ + } + return db +} + +func (db *DB) Get(addr common.Address) *object { + if obj, ok := db.cache.Load(addr); ok { + return obj.(*object) + } + obj := db.persist[addr] + db.cache.Store(addr, obj) + return obj +} + +func hash2int(addr common.Address) int { + return int(addr[common.AddressLength/2]) +} + +func randAddresses(num int) []common.Address { + addresses := make([]common.Address, num) + for i := 0; i < len(addresses); i++ { + addr, _ := randAddress() + addresses[i] = addr + } + return addresses +} + +func randAddress() (common.Address, *ecdsa.PrivateKey) { + // Generate a new private key using rand.Reader + key, err := ecdsa.GenerateKey(crypto.S256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("Failed to generate private key: %v", err)) + } + return crypto.PubkeyToAddress(key.PublicKey), key +} + +func accessWithMultiProcesses(db *DB, parallelNum int, queryNum int, randAddress []common.Address) { + wait := sync.WaitGroup{} + wait.Add(parallelNum) + for p := 0; p < parallelNum; p++ { + runner <- func() { + for j := 0; j < queryNum; j++ { + addr := randAddress[j%10000] + obj := db.Get(addr) + if obj != nil { + obj.data++ + } + db.cache.Store(addr, obj) + } + wait.Done() + } + } + wait.Wait() +} + +var runner = make(chan func(), runtime.NumCPU()) + +func init() { + for i := 0; i < runtime.NumCPU(); i++ { + go func() { + for f := range runner { + f() + } + }() + } +} + +func BenchmarkSyncMap10000(b *testing.B) { + sm := sync.Map{} + runBench(&sm, b) +} + +func BenchmarkHashSyncMap10000(b *testing.B) { + sm := newHashSyncMap(128) + runBench(sm, b) +} + +func BenchmarkHashMutexMap10000(b *testing.B) { + sm := newHashMutexMap(128) + runBench(sm, b) +} + +func runBench(sm istore, b *testing.B) { + randAddress := randAddresses(10000) + db := newDB(sm, randAddress) + b.ResetTimer() + var duration int64 = 0 + var round int64 = 0 + for i := 0; i < b.N; i++ { + start := time.Now() + accessWithMultiProcesses(db, runtime.NumCPU(), 10000, randAddress) + atomic.AddInt64(&duration, int64(time.Since(start))) + atomic.AddInt64(&round, 1) + } + fmt.Printf("total cost:%s, rounds:%d, avg costs:%s\n", time.Duration(duration), round, time.Duration(duration/round)) +} diff --git a/core/state/snapshot/conversion.go b/core/state/snapshot/conversion.go index 8a0fd1989a..365660caa2 100644 --- a/core/state/snapshot/conversion.go +++ b/core/state/snapshot/conversion.go @@ -300,6 +300,7 @@ func generateTrieRoot(db ethdb.KeyValueWriter, scheme string, it Iterator, accou fullData []byte ) if leafCallback == nil { + fullData, err = types.FullAccountRLP(it.(AccountIterator).Account()) if err != nil { return stop(err) diff --git a/core/state/state_object.go b/core/state/state_object.go index 8696557845..a7a0aafb67 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -24,6 +24,7 @@ import ( "time" "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" + "golang.org/x/exp/slices" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -40,40 +41,136 @@ func (c Code) String() string { return string(c) //strings.Join(Disassemble(c), " ") } -type Storage map[common.Hash]common.Hash +type Storage interface { + String() string + GetValue(hash common.Hash) (common.Hash, bool) + StoreValue(hash common.Hash, value common.Hash) + Length() (length int) + Copy() Storage + Range(func(key, value interface{}) bool) +} + +type StorageMap map[common.Hash]common.Hash -func (s Storage) String() (str string) { +func (s StorageMap) String() (str string) { for key, value := range s { str += fmt.Sprintf("%X : %X\n", key, value) } return } -func (s Storage) Copy() Storage { - cpy := make(Storage, len(s)) +func (s StorageMap) Copy() Storage { + cpy := make(StorageMap, len(s)) for key, value := range s { cpy[key] = value } return cpy } +func (s StorageMap) GetValue(hash common.Hash) (common.Hash, bool) { + value, ok := s[hash] + return value, ok +} + +func (s StorageMap) StoreValue(hash common.Hash, value common.Hash) { + s[hash] = value +} + +func (s StorageMap) Length() int { + return len(s) +} + +func (s StorageMap) Range(f func(hash, value interface{}) bool) { + for k, v := range s { + result := f(k, v) + if !result { + return + } + } +} + +type StorageSyncMap struct { + sync.Map +} + +func (s *StorageSyncMap) String() (str string) { + s.Range(func(key, value interface{}) bool { + str += fmt.Sprintf("%X : %X\n", key, value) + return true + }) + + return +} + +func (s *StorageSyncMap) GetValue(hash common.Hash) (common.Hash, bool) { + value, ok := s.Load(hash) + if !ok { + return common.Hash{}, ok + } + + return value.(common.Hash), ok +} + +func (s *StorageSyncMap) StoreValue(hash common.Hash, value common.Hash) { + s.Store(hash, value) +} + +func (s *StorageSyncMap) Length() (length int) { + s.Range(func(key, value interface{}) bool { + length++ + return true + }) + return length +} + +func (s *StorageSyncMap) Copy() Storage { + cpy := StorageSyncMap{} + s.Range(func(key, value interface{}) bool { + cpy.Store(key, value) + return true + }) + return &cpy +} + +func newStorage(isParallel bool) Storage { + if isParallel { + return &StorageSyncMap{} + } + return make(StorageMap) +} + // stateObject represents an Ethereum account which is being modified. // // The usage pattern is as follows: // - First you need to obtain a state object. // - Account values as well as storages can be accessed and modified through the object. // - Finally, call commit to return the changes of storage trie and update account data. +// +// NOTICE: For Parallel, there is lightCopy and deepCopy used for cloning object between +// slot DB and global DB, and it is not guaranteed to be happened after finalise(), so any +// field added into the stateObject must be handled in lightCopy and deepCopy. type stateObject struct { - db *StateDB + db *StateDB // The baseDB for parallel. + dbItf StateDBer // The slotDB for parallel. address common.Address // address of ethereum account addrHash common.Hash // hash of ethereum address of the account origin *types.StateAccount // Account original data without any change applied, nil means it was not existent data types.StateAccount // Account data with all mutations applied in the scope of block + // dirty account state + dirtyBalance *uint256.Int + dirtyNonce *uint64 + dirtyCodeHash []byte + // Write caches. trie Trie // storage trie, which becomes non-nil on first access code Code // contract bytecode, which gets set when code is loaded + // isParallel indicates this state object is used in parallel mode, in which mode the + // storage would be sync.Map instead of map + isParallel bool + storageRecordsLock sync.RWMutex // for pending/dirty/origin storage read (lightCopy) and write (Intermediate/FixupOrigin) + originStorage Storage // Storage cache of original entries to dedup rewrites pendingStorage Storage // Storage entries that need to be flushed to disk, at the end of an entire block dirtyStorage Storage // Storage entries that have been modified in the current transaction execution, reset for every transaction @@ -96,11 +193,55 @@ type stateObject struct { // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.data.Nonce == 0 && s.data.Balance.IsZero() && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + // return s.data.Nongn() == 0 && bytes.ce == 0 && s.data.Balance.SiEqual(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + // return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, emptyCodeHash) + + // empty() has 3 use cases: + // 1.StateDB.Empty(), to empty check + // A: It is ok, we have handled it in Empty(), to make sure nonce, balance, codeHash are solid + // 2:AddBalance 0, empty check for touch event + // empty() will add a touch event. + // if we misjudge it, the touch event could be lost, which make address not deleted. // fixme + // 3.Finalise(), to do empty delete + // the address should be dirtied or touched + // if it nonce dirtied, it is ok, since nonce is monotonically increasing, won't be zero + // if balance is dirtied, balance could be zero, we should refer solid nonce & codeHash // fixme + // if codeHash is dirtied, it is ok, since code will not be updated. + // if suicide, it is ok + // if object is new created, it is ok + // if CreateAccount, recreate the address, it is ok. + + // Slot 0 tx 0: AddBalance(100) to addr_1, => addr_1: balance = 100, nonce = 0, code is empty + // Slot 1 tx 1: addr_1 Transfer 99.9979 with GasFee 0.0021, => addr_1: balance = 0, nonce = 1, code is empty + // notice: balance transfer cost 21,000 gas, with gasPrice = 100Gwei, GasFee will be 0.0021 + // Slot 0 tx 2: add balance 0 to addr_1(empty check for touch event), + // the object was lightCopied from tx 0, + + // in parallel mode, we should not check empty by raw nonce, balance, codeHash anymore, + // since it could be invalid. + // e.g., AddBalance() to an address, we will do lightCopy to get a new StateObject, we did balance fixup to + // make sure object's Balance is reliable. But we did not fixup nonce or code, we only do nonce or codehash + // fixup on need, that's when we want to update the nonce or codehash. + // So nonce, balance + // Before the block is processed, addr_1 account: nonce = 0, emptyCodeHash, balance = 100 + // Slot 0 tx 0: no access to addr_1 + // Slot 1 tx 1: sub balance 100, it is empty and deleted + // Slot 0 tx 2: GetNonce, lightCopy based on main DB(balance = 100) , not empty + + if !s.dbItf.GetBalance(s.address).IsZero() { // check balance first, since it is most likely not zero + + return false + } + if s.dbItf.GetNonce(s.address) != 0 { + return false + } + codeHash := s.dbItf.GetCodeHash(s.address) + return bytes.Equal(codeHash.Bytes(), types.EmptyCodeHash.Bytes()) // code is empty, the object is empty } // newObject creates a state object. -func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { +func newObject(dbItf StateDBer, isParallel bool, address common.Address, acct *types.StateAccount) *stateObject { + db := dbItf.getBaseStateDB() var ( origin = acct created = acct == nil // true if the account was not existent @@ -108,17 +249,29 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s if acct == nil { acct = types.NewEmptyStateAccount() } - return &stateObject{ + s := &stateObject{ db: db, + dbItf: dbItf, address: address, addrHash: crypto.Keccak256Hash(address[:]), origin: origin, - data: *acct, - originStorage: make(Storage), - pendingStorage: make(Storage), - dirtyStorage: make(Storage), + data: *acct.Copy(), + isParallel: isParallel, + originStorage: newStorage(isParallel), + pendingStorage: newStorage(isParallel), + dirtyStorage: newStorage(isParallel), created: created, } + + // dirty data when create a new account + + if created { + s.dirtyBalance = new(uint256.Int).Set(acct.Balance) + s.dirtyNonce = new(uint64) + *s.dirtyNonce = acct.Nonce + s.dirtyCodeHash = acct.CodeHash + } + return s } // EncodeRLP implements rlp.Encoder. @@ -165,32 +318,75 @@ func (s *stateObject) getTrie() (Trie, error) { // GetState retrieves a value from the account storage trie. func (s *stateObject) GetState(key common.Hash) common.Hash { // If we have a dirty value for this state entry, return it - value, dirty := s.dirtyStorage[key] + value, dirty := s.dirtyStorage.GetValue(key) if dirty { return value } // Otherwise return the entry's original value - return s.GetCommittedState(key) + result := s.GetCommittedState(key) + // Record first read for conflict verify + if s.db.isParallel && s.db.parallel.isSlotDB { + addr := s.address + if s.db.parallel.kvReadsInSlot[addr] == nil { + s.db.parallel.kvReadsInSlot[addr] = newStorage(false) + } + if _, ok := s.db.parallel.kvReadsInSlot[addr].GetValue(key); !ok { + s.db.parallel.kvReadsInSlot[addr].StoreValue(key, result) + } + } + return result } // GetCommittedState retrieves a value from the committed account storage trie. func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // If we have a pending write or clean cached, return that - if value, pending := s.pendingStorage[key]; pending { + // if value, pending := s.pendingStorage[key]; pending { + if value, pending := s.pendingStorage.GetValue(key); pending { return value } - if value, cached := s.originStorage[key]; cached { + if value, cached := s.originStorage.GetValue(key); cached { return value } + + if s.db.isParallel && s.db.parallel.isSlotDB { + // Need to confirm the object is not destructed in unconfirmed db and resurrected in this tx. + // otherwise there is an issue for cases like: + // B0: TX0 --> createAccount @addr1 -- merged into DB + // B1: Tx1 and Tx2 + // Tx1 account@addr1, setState(key0), setState(key1) selfDestruct -- unconfirmed + // Tx2 recreate account@addr2, setState(key0) -- executing + // TX2 GetState(addr2, key1) --- + // key1 is never set after recurrsect, and should not return state in trie as it destructed in unconfirmed + if s.db.parallel.useDAG != true { + obj, exist := s.dbItf.GetStateObjectFromUnconfirmedDB(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } + } + } + // also test whether the object is in mainDB and deleted. + pdb := s.db.parallel.baseStateDB + obj, exist := pdb.getStateObjectFromStateObjects(s.address) + if exist { + if obj.deleted || obj.selfDestructed { + return common.Hash{} + } + } + } // If the object was destructed in *this* block (and potentially resurrected), // the storage has been cleared out, and we should *not* consult the previous // database 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.stateObjectsDestruct[s.address]; destructed { + s.db.stateObjectDestructLock.RLock() + if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + s.db.stateObjectDestructLock.RUnlock() return common.Hash{} } + s.db.stateObjectDestructLock.RUnlock() + // If no live objects are available, attempt to use snapshots var ( enc []byte @@ -214,6 +410,8 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { // If the snapshot is unavailable or reading from it fails, load from the database. if s.db.snap == nil || err != nil { start := time.Now() + s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() tr, err := s.getTrie() if err != nil { s.db.setError(err) @@ -229,14 +427,21 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { } value.SetBytes(val) } - s.originStorage[key] = value + s.originStorage.StoreValue(key, value) return value } // SetState updates a value in account storage. func (s *stateObject) SetState(key, value common.Hash) { // If the new value is the same as old, don't set - prev := s.GetState(key) + // In parallel mode, it has to get from StateDB, in case: + // a.the Slot did not set the key before and try to set it to `val_1` + // b.Unconfirmed DB has set the key to `val_2` + // c.if we use StateObject.GetState, and the key load from the main DB is `val_1` + // this `SetState could be skipped` + // d.Finally, the key's value will be `val_2`, while it should be `val_1` + // such as: https://bscscan.com/txs?block=2491181 + prev := s.dbItf.GetState(s.address, key) if prev == value { return } @@ -246,28 +451,73 @@ func (s *stateObject) SetState(key, value common.Hash) { key: key, prevalue: prev, }) + + if s.db.isParallel && s.db.parallel.isSlotDB { + s.db.parallel.kvChangesInSlot[s.address][key] = struct{}{} + } s.setState(key, value) } func (s *stateObject) setState(key, value common.Hash) { - s.dirtyStorage[key] = value + s.dirtyStorage.StoreValue(key, value) } // finalise moves all dirty storage slots into the pending area to be hashed or // committed later. It is invoked at the end of every transaction. func (s *stateObject) finalise(prefetch bool) { - slotsToPrefetch := make([][]byte, 0, len(s.dirtyStorage)) - for key, value := range s.dirtyStorage { - s.pendingStorage[key] = value - if value != s.originStorage[key] { - slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(key[:])) // Copy needed for closure + s.storageRecordsLock.Lock() + defer s.storageRecordsLock.Unlock() + slotsToPrefetch := make([][]byte, 0, s.dirtyStorage.Length()) + s.dirtyStorage.Range(func(key, value interface{}) bool { + s.pendingStorage.StoreValue(key.(common.Hash), value.(common.Hash)) + originalValue, _ := s.originStorage.GetValue(key.(common.Hash)) + if value.(common.Hash) != originalValue { + originalKey := key.(common.Hash) + slotsToPrefetch = append(slotsToPrefetch, common.CopyBytes(originalKey[:])) // Copy needed for closure } + return true + }) + if s.dirtyNonce != nil { + s.data.Nonce = *s.dirtyNonce + s.dirtyNonce = nil + } + if s.dirtyBalance != nil { + s.data.Balance = s.dirtyBalance + s.dirtyBalance = nil + } + if s.dirtyCodeHash != nil { + s.data.CodeHash = s.dirtyCodeHash + s.dirtyCodeHash = nil } if s.db.prefetcher != nil && prefetch && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { + s.db.trieParallelLock.Lock() s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, slotsToPrefetch) + s.db.trieParallelLock.Unlock() } - if len(s.dirtyStorage) > 0 { - s.dirtyStorage = make(Storage) + if s.dirtyStorage.Length() > 0 { + s.dirtyStorage = newStorage(s.isParallel) + } +} + +func (s *stateObject) finaliseRWSet() { + s.dirtyStorage.Range(func(key, value interface{}) bool { + // three are some unclean dirtyStorage from previous reverted txs, it will skip finalise + // so add a new rule, if val has no change, then skip it + if value == s.GetCommittedState(key.(common.Hash)) { + return true + } + s.db.RecordWrite(types.StorageStateKey(s.address, key.(common.Hash)), value.(common.Hash)) + return true + }) + + if s.dirtyNonce != nil && *s.dirtyNonce != s.data.Nonce { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountNonce), *s.dirtyNonce) + } + if s.dirtyBalance != nil && s.dirtyBalance.Cmp(s.data.Balance) != 0 { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountBalance), new(uint256.Int).Set(s.dirtyBalance)) + } + if s.dirtyCodeHash != nil && !slices.Equal(s.dirtyCodeHash, s.data.CodeHash) { + s.db.RecordWrite(types.AccountStateKey(s.address, types.AccountCodeHash), s.dirtyCodeHash) } } @@ -278,16 +528,25 @@ func (s *stateObject) finalise(prefetch bool) { // this function will return the mutated storage trie, or nil if there is no // storage change at all. func (s *stateObject) updateTrie() (Trie, error) { + maindb := s.db + if s.db.isParallel && s.db.parallel.isSlotDB { + // we need to fixup the origin storage with the mainDB. otherwise the changes maybe problematic since the origin + // is wrong. + maindb = s.db.parallel.baseStateDB + // For dirty/pending/origin Storage access and update. + s.storageRecordsLock.Lock() + defer s.storageRecordsLock.Unlock() + } // Make sure all dirty slots are finalized into the pending storage area s.finalise(false) // Short circuit if nothing changed, don't bother with hashing anything - if len(s.pendingStorage) == 0 { + if s.pendingStorage.Length() == 0 { return s.trie, nil } // Track the amount of time wasted on updating the storage trie if metrics.EnabledExpensive { - defer func(start time.Time) { s.db.StorageUpdates += time.Since(start) }(time.Now()) + defer func(start time.Time) { maindb.StorageUpdates += time.Since(start) }(time.Now()) } // The snapshot storage map for the object var ( @@ -297,17 +556,21 @@ func (s *stateObject) updateTrie() (Trie, error) { ) tr, err := s.getTrie() if err != nil { - s.db.setError(err) + maindb.setError(err) return nil, err } + // Insert all the pending storage updates into the trie - usedStorage := make([][]byte, 0, len(s.pendingStorage)) + usedStorage := make([][]byte, 0, s.pendingStorage.Length()) dirtyStorage := make(map[common.Hash][]byte) - for key, value := range s.pendingStorage { + s.pendingStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) // Skip noop changes, persist actual changes - if value == s.originStorage[key] { - continue + originalValue, _ := s.originStorage.GetValue(key) + if value == originalValue { + return true } var v []byte if value != (common.Hash{}) { @@ -315,7 +578,8 @@ func (s *stateObject) updateTrie() (Trie, error) { v = common.TrimLeftZeroes(value[:]) } dirtyStorage[key] = v - } + return true + }) var wg sync.WaitGroup wg.Add(1) go func() { @@ -323,14 +587,14 @@ func (s *stateObject) updateTrie() (Trie, error) { for key, value := range dirtyStorage { if len(value) == 0 { if err := tr.DeleteStorage(s.address, key[:]); err != nil { - s.db.setError(err) + maindb.setError(err) } - s.db.StorageDeleted += 1 + maindb.StorageDeleted += 1 } else { if err := tr.UpdateStorage(s.address, key[:], value); err != nil { - s.db.setError(err) + maindb.setError(err) } - s.db.StorageUpdated += 1 + maindb.StorageUpdated += 1 } // Cache the items for preloading usedStorage = append(usedStorage, common.CopyBytes(key[:])) @@ -340,20 +604,21 @@ func (s *stateObject) updateTrie() (Trie, error) { wg.Add(1) go func() { defer wg.Done() - s.db.StorageMux.Lock() + maindb.StorageMux.Lock() + // The snapshot storage map for the object - storage = s.db.storages[s.addrHash] + storage = maindb.storages[s.addrHash] if storage == nil { storage = make(map[common.Hash][]byte, len(dirtyStorage)) - s.db.storages[s.addrHash] = storage + maindb.storages[s.addrHash] = storage } // Cache the original value of mutated storage slots - origin = s.db.storagesOrigin[s.address] + origin = maindb.storagesOrigin[s.address] if origin == nil { origin = make(map[common.Hash][]byte) - s.db.storagesOrigin[s.address] = origin + maindb.storagesOrigin[s.address] = origin } - s.db.StorageMux.Unlock() + maindb.StorageMux.Unlock() for key, value := range dirtyStorage { khash := crypto.HashData(hasher, key[:]) @@ -365,8 +630,8 @@ func (s *stateObject) updateTrie() (Trie, error) { storage[khash] = encoded // encoded will be nil if it's deleted // Track the original value of slot only if it's mutated first time - prev := s.originStorage[key] - s.originStorage[key] = common.BytesToHash(value) // fill back left zeroes by BytesToHash + prev, _ := s.originStorage.GetValue(key) + s.originStorage.StoreValue(key, common.BytesToHash(value)) // fill back left zeroes by BytesToHash if _, ok := origin[khash]; !ok { if prev == (common.Hash{}) { origin[khash] = nil // nil if it was not present previously @@ -380,17 +645,19 @@ func (s *stateObject) updateTrie() (Trie, error) { }() wg.Wait() - if s.db.prefetcher != nil { - s.db.prefetcher.used(s.addrHash, s.data.Root, usedStorage) + if maindb.prefetcher != nil { + maindb.prefetcher.used(s.addrHash, s.data.Root, usedStorage) } - s.pendingStorage = make(Storage) // reset pending map + + s.pendingStorage = newStorage(s.isParallel) // reset pending map return tr, nil } // updateRoot flushes all cached storage mutations to trie, recalculating the // new storage trie root. func (s *stateObject) updateRoot() { - // If node runs in no trie mode, set root to empty. + + // If node runs in no trie mode, set root to empty defer func() { if s.db.db.NoTries() { s.data.Root = types.EmptyRootHash @@ -399,6 +666,9 @@ func (s *stateObject) updateRoot() { // Flush cached storage mutations into trie, short circuit if any error // is occurred or there is not change in the trie. + s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() + tr, err := s.updateTrie() if err != nil || tr == nil { return @@ -431,7 +701,6 @@ func (s *stateObject) commit() (*trienode.NodeSet, error) { return nil, err } s.data.Root = root - // Update original account data after commit s.origin = s.data.Copy() return nodes, nil @@ -463,34 +732,68 @@ func (s *stateObject) SubBalance(amount *uint256.Int) { func (s *stateObject) SetBalance(amount *uint256.Int) { s.db.journal.append(balanceChange{ account: &s.address, - prev: new(uint256.Int).Set(s.data.Balance), + prev: new(uint256.Int).Set(s.Balance()), }) s.setBalance(amount) } func (s *stateObject) setBalance(amount *uint256.Int) { - s.data.Balance = amount + s.dirtyBalance = amount } +// ReturnGas Return the gas back to the origin. Used by the Virtual machine or Closures +func (s *stateObject) ReturnGas(gas *uint256.Int) {} + +// deepCopy happens only at global serial execution stage. +// E.g. prepareForParallel and merge (copy slotObj to mainDB) +// otherwise the origin/dirty/pending storages may cause incorrect issue. func (s *stateObject) deepCopy(db *StateDB) *stateObject { - obj := &stateObject{ - db: db, - address: s.address, - addrHash: s.addrHash, - origin: s.origin, - data: s.data, + object := &stateObject{ + db: db.getBaseStateDB(), + dbItf: db, + address: s.address, + addrHash: s.addrHash, + origin: s.origin, + data: *s.data.Copy(), + isParallel: s.isParallel, } if s.trie != nil { - obj.trie = db.db.CopyTrie(s.trie) - } - obj.code = s.code - obj.dirtyStorage = s.dirtyStorage.Copy() - obj.originStorage = s.originStorage.Copy() - obj.pendingStorage = s.pendingStorage.Copy() - obj.selfDestructed = s.selfDestructed - obj.dirtyCode = s.dirtyCode - obj.deleted = s.deleted - return obj + s.db.trieParallelLock.Lock() + object.trie = db.db.CopyTrie(s.trie) + s.db.trieParallelLock.Unlock() + } + + object.code = s.code + object.dirtyStorage = s.dirtyStorage.Copy() + object.originStorage = s.originStorage.Copy() + object.pendingStorage = s.pendingStorage.Copy() + object.selfDestructed = s.selfDestructed + object.dirtyCode = s.dirtyCode + object.deleted = s.deleted + object.dirtyBalance = s.dirtyBalance + object.dirtyNonce = s.dirtyNonce + object.dirtyCodeHash = s.dirtyCodeHash + return object +} + +func (s *stateObject) MergeSlotObject(db Database, dirtyObjs *stateObject, keys StateKeys) { + for key := range keys { + // In parallel mode, always GetState by StateDB, not by StateObject directly, + // since it the KV could exist in unconfirmed DB. + // But here, it should be ok, since the KV should be changed and valid in the SlotDB, + s.setState(key, dirtyObjs.GetState(key)) + } + + // The dirtyObject may have new state accessed from Snap and Trie, so merge the origins. + dirtyObjs.originStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) + // Skip noop changes, persist actual changes + if _, ok := s.originStorage.GetValue(key); !ok { + s.originStorage.StoreValue(key, value) + } + return true + }) } // @@ -536,7 +839,7 @@ func (s *stateObject) CodeSize() int { } func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { - prevcode := s.Code() + prevcode := s.dbItf.GetCode(s.address) s.db.journal.append(codeChange{ account: &s.address, prevhash: s.CodeHash(), @@ -547,35 +850,162 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) { func (s *stateObject) setCode(codeHash common.Hash, code []byte) { s.code = code - s.data.CodeHash = codeHash[:] + s.dirtyCodeHash = codeHash[:] s.dirtyCode = true compiler.GenOrLoadOptimizedCode(codeHash, s.code) } func (s *stateObject) SetNonce(nonce uint64) { + prevNonce := s.dbItf.GetNonce(s.address) s.db.journal.append(nonceChange{ account: &s.address, - prev: s.data.Nonce, + prev: prevNonce, }) s.setNonce(nonce) } func (s *stateObject) setNonce(nonce uint64) { - s.data.Nonce = nonce + s.dirtyNonce = &nonce } func (s *stateObject) CodeHash() []byte { + if len(s.dirtyCodeHash) > 0 { + return s.dirtyCodeHash + } return s.data.CodeHash } func (s *stateObject) Balance() *uint256.Int { + if s.dirtyBalance != nil { + return s.dirtyBalance + } return s.data.Balance } func (s *stateObject) Nonce() uint64 { + if s.dirtyNonce != nil { + return *s.dirtyNonce + } return s.data.Nonce } func (s *stateObject) Root() common.Hash { return s.data.Root } + +// GetStateNoUpdate retrieves a value from the account storage trie, but never update the stateDB cache +func (s *stateObject) GetStateNoUpdate(key common.Hash) common.Hash { + // If we have a dirty value for this state entry, return it + value, dirty := s.dirtyStorage.GetValue(key) + if dirty { + return value + } + // Otherwise return the entry's original value + result := s.GetCommittedStateNoUpdate(key) + return result +} + +// GetCommittedStateNoUpdate retrieves a value from the committed account storage trie, but never update the +// stateDB cache (object.originStorage) +func (s *stateObject) GetCommittedStateNoUpdate(key common.Hash) common.Hash { + // If we have a pending write or clean cached, return that + // if value, pending := s.pendingStorage[key]; pending { + if value, pending := s.pendingStorage.GetValue(key); pending { + return value + } + if value, cached := s.originStorage.GetValue(key); cached { + return value + } + + // If the object was destructed in *this* block (and potentially resurrected), + // the storage has been cleared out, and we should *not* consult the previous + // database 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.stateObjectsDestruct[s.address]; destructed { + s.db.stateObjectDestructLock.RLock() + if _, destructed := s.db.getStateObjectsDestruct(s.address); destructed { // fixme: use sync.Map, instead of RWMutex? + s.db.stateObjectDestructLock.RUnlock() + return common.Hash{} + } + s.db.stateObjectDestructLock.RUnlock() + + // If no live objects are available, attempt to use snapshots + var ( + enc []byte + err error + value common.Hash + ) + if s.db.snap != nil { + start := time.Now() + enc, err = s.db.snap.Storage(s.addrHash, crypto.Keccak256Hash(key.Bytes())) + if metrics.EnabledExpensive { + s.db.SnapshotStorageReads += time.Since(start) + } + if len(enc) > 0 { + _, content, _, err := rlp.Split(enc) + if err != nil { + s.db.setError(err) + } + value.SetBytes(content) + } + } + // If the snapshot is unavailable or reading from it fails, load from the database. + if s.db.snap == nil || err != nil { + start := time.Now() + s.db.trieParallelLock.Lock() + defer s.db.trieParallelLock.Unlock() + tr, err := s.getTrie() + if err != nil { + s.db.setError(err) + return common.Hash{} + } + val, err := tr.GetStorage(s.address, key.Bytes()) + if metrics.EnabledExpensive { + s.db.StorageReads += time.Since(start) + } + if err != nil { + s.db.setError(err) + return common.Hash{} + } + value.SetBytes(val) + } + return value +} + +// fixUpOriginAndResetPendingStorage is used for slot object only, the target is to fix up the origin storage of the +// object with the latest mainDB. And reset the pendingStorage as the execution recorded the changes in dirty and the +// dirties will be merged to pending at finalise. so the current pendingStorage contains obsoleted info mainly from +// lightCopy() +func (s *stateObject) fixUpOriginAndResetPendingStorage() { + if s.db.isParallel && s.db.parallel.isSlotDB { + mainDB := s.db.parallel.baseStateDB + origObj := mainDB.getStateObject(s.address) + s.storageRecordsLock.Lock() + if origObj != nil && origObj.originStorage.Length() != 0 { + // There can be racing issue with CopyForSlot/LightCopy + origObj.storageRecordsLock.RLock() + originStorage := origObj.originStorage.Copy() + origObj.storageRecordsLock.RUnlock() + // During the tx execution, the originStorage can be updated with GetCommittedState() + // But is never get updated for the already existed one as there is no finalise called in execution. + // so here get the latest object in MainDB, and update the object storage with + s.originStorage.Range(func(keyItf, valueItf interface{}) bool { + key := keyItf.(common.Hash) + value := valueItf.(common.Hash) + // Skip noop changes, persist actual changes + if _, ok := originStorage.GetValue(key); !ok { + originStorage.StoreValue(key, value) + } + return true + }) + s.originStorage = originStorage + } + // isParallel is unnecessary since the pendingStorage for slotObject will be used serially from now on. + if s.pendingStorage.Length() > 0 { + s.pendingStorage = newStorage(false) + } + s.storageRecordsLock.Unlock() + } +} diff --git a/core/state/state_test.go b/core/state/state_test.go index 9be610f962..063d4b5567 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -269,30 +269,46 @@ func compareStateObjects(so0, so1 *stateObject, t *testing.T) { t.Fatalf("Code mismatch: have %v, want %v", so0.code, so1.code) } - if len(so1.dirtyStorage) != len(so0.dirtyStorage) { - t.Errorf("Dirty storage size mismatch: have %d, want %d", len(so1.dirtyStorage), len(so0.dirtyStorage)) + if so1.dirtyStorage.Length() != so0.dirtyStorage.Length() { + t.Errorf("Dirty storage size mismatch: have %d, want %d", so1.dirtyStorage.Length(), so0.dirtyStorage.Length()) } - for k, v := range so1.dirtyStorage { - if so0.dirtyStorage[k] != v { - t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, so0.dirtyStorage[k], v) + so1.dirtyStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so0.dirtyStorage.GetValue(k); tmpV != v { + t.Errorf("Dirty storage key %x mismatch: have %v, want %v", k, tmpV.String(), v) } - } - for k, v := range so0.dirtyStorage { - if so1.dirtyStorage[k] != v { + return true + }) + + so0.dirtyStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so1.dirtyStorage.GetValue(k); tmpV != v { t.Errorf("Dirty storage key %x mismatch: have %v, want none.", k, v) } + return true + }) + + if so1.originStorage.Length() != so0.originStorage.Length() { + t.Errorf("Origin storage size mismatch: have %d, want %d", so1.originStorage.Length(), so0.originStorage.Length()) } - if len(so1.originStorage) != len(so0.originStorage) { - t.Errorf("Origin storage size mismatch: have %d, want %d", len(so1.originStorage), len(so0.originStorage)) - } - for k, v := range so1.originStorage { - if so0.originStorage[k] != v { - t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, so0.originStorage[k], v) + + so1.originStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so0.originStorage.GetValue(k); tmpV != v { + t.Errorf("Origin storage key %x mismatch: have %v, want %v", k, tmpV, v) } - } - for k, v := range so0.originStorage { - if so1.originStorage[k] != v { + return true + }) + + so0.originStorage.Range(func(key, value interface{}) bool { + k, v := key.(common.Hash), value.(common.Hash) + + if tmpV, _ := so1.originStorage.GetValue(k); tmpV != v { t.Errorf("Origin storage key %x mismatch: have %v, want none.", k, v) } - } + return true + }) } diff --git a/core/state/statedb.go b/core/state/statedb.go index f5464eb23c..efecb52e58 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ package state import ( + "errors" "fmt" "runtime" "sort" @@ -51,6 +52,119 @@ type revision struct { journalIndex int } +var emptyAddr = common.Address{} + +type StateKeys map[common.Hash]struct{} + +type StateObjectSyncMap struct { + sync.Map +} + +type DelayedGasFee struct { + BaseFee *uint256.Int + TipFee *uint256.Int + L1Fee *uint256.Int + Coinbase common.Address +} + +func (s *StateObjectSyncMap) LoadStateObject(addr common.Address) (*stateObject, bool) { + so, ok := s.Load(addr) + if !ok { + return nil, ok + } + return so.(*stateObject), ok +} + +func (s *StateObjectSyncMap) StoreStateObject(addr common.Address, stateObject *stateObject) { + s.Store(addr, stateObject) +} + +// loadStateObj is the entry for loading state object from stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) loadStateObj(addr common.Address) (*stateObject, bool) { + if s.isParallel { + if s.parallel.isSlotDB { + if ret, ok := s.parallel.locatStateObjects[addr]; ok { + return ret, ok + } else { + ret, ok := s.parallel.baseStateDB.loadStateObj(addr) + return ret, ok + } + } + ret, ok := s.parallel.stateObjects.LoadStateObject(addr) + return ret, ok + } + + obj, ok := s.stateObjects[addr] + return obj, ok +} + +// storeStateObj is the entry for storing state object to stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) storeStateObj(addr common.Address, stateObject *stateObject) { + if s.isParallel { + if s.parallel.isSlotDB { + s.parallel.locatStateObjects[addr] = stateObject + } else { + s.parallel.stateObjects.StoreStateObject(addr, stateObject) + } + } else { + s.stateObjects[addr] = stateObject + } +} + +// deleteStateObj is the entry for deleting state object to stateObjects in StateDB or stateObjects in parallel +func (s *StateDB) deleteStateObj(addr common.Address) { + if s.isParallel { + if s.parallel.isSlotDB { + delete(s.parallel.locatStateObjects, addr) + } + s.parallel.stateObjects.Delete(addr) + } else { + delete(s.stateObjects, addr) + } +} + +// ParallelState is for parallel mode only +type ParallelState struct { + isSlotDB bool // denotes StateDB is used in slot, we will try to remove it + SlotIndex int // for debug + // stateObjects holds the state objects in the base slot db + stateObjects *StateObjectSyncMap + locatStateObjects map[common.Address]*stateObject + + baseStateDB *StateDB // for parallel mode, there will be a base StateDB in dispatcher routine. + baseTxIndex int // slotDB is created base on this tx index. + dirtiedStateObjectsInSlot map[common.Address]*stateObject + unconfirmedDBs *sync.Map // do unconfirmed reference in same slot. + + // record the read detail for conflict check and + // the changed addr or key for object merge, the changed detail can be achieved from the dirty object + nonceChangesInSlot map[common.Address]struct{} + nonceReadsInSlot map[common.Address]uint64 + balanceChangesInSlot map[common.Address]struct{} // the address's balance has been changed + balanceReadsInSlot map[common.Address]*uint256.Int // the address's balance has been read and used. + // codeSize can be derived based on code, but codeHash can not be directly derived based on code + // - codeSize is 0 for address not exist or empty code + // - codeHash is `common.Hash{}` for address not exist, emptyCodeHash(`Keccak256Hash(nil)`) for empty code, + // so we use codeReadsInSlot & codeHashReadsInSlot to keep code and codeHash, codeSize is derived from code + codeReadsInSlot map[common.Address][]byte // empty if address not exist or no code in this address + codeHashReadsInSlot map[common.Address]common.Hash + codeChangesInSlot map[common.Address]struct{} + kvReadsInSlot map[common.Address]Storage + kvChangesInSlot map[common.Address]StateKeys // value will be kept in dirtiedStateObjectsInSlot + // Actions such as SetCode, Suicide will change address's state. + // Later call like Exist(), Empty(), HasSuicided() depend on the address's state. + addrStateReadsInSlot map[common.Address]bool // true: exist, false: not exist or deleted + addrStateChangesInSlot map[common.Address]bool // true: created, false: deleted + + addrSnapDestructsReadsInSlot map[common.Address]bool + createdObjectRecord map[common.Address]struct{} + // we may need to redo for some specific reasons, like we read the wrong state and need to panic in sequential mode in SubRefund + needsRedo bool + useDAG bool + conflictCheckStateObjectCache *sync.Map + conflictCheckKVReadCache *sync.Map +} + // StateDB structs within the ethereum protocol are used to store anything // within the merkle trie. StateDBs take care of caching and storing // nested states. It's the general query interface to retrieve: @@ -68,9 +182,15 @@ type StateDB struct { trie Trie noTrie bool hasher crypto.KeccakState + hasherLock sync.Mutex snaps *snapshot.Tree // Nil if snapshot is not available snap snapshot.Snapshot // Nil if snapshot is not available + snapParallelLock sync.RWMutex // for parallel mode, for main StateDB, slot will read snapshot, while processor will write. + trieParallelLock sync.Mutex // for parallel mode of trie, mostly for get states/objects from trie, lock required to handle trie tracer. + stateObjectDestructLock sync.RWMutex // for parallel mode, used in mainDB for mergeSlot and conflict check. + snapDestructs map[common.Address]struct{} + // originalRoot is the pre-state root, before any changes were made. // It will be updated when the Commit is called. originalRoot common.Hash @@ -90,10 +210,11 @@ type StateDB struct { // 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 - stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution - stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value + stateObjects map[common.Address]*stateObject + stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie + stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution + stateObjectsDestruct map[common.Address]*types.StateAccount // State objects destructed in the block along with its previous value + stateObjectsDestructDirty map[common.Address]*types.StateAccount // DB error. // State objects are used by the consensus core and VM which are @@ -113,6 +234,11 @@ type StateDB struct { logs map[common.Hash][]*types.Log logSize uint + // parallel EVM related + rwSet *types.RWSet + mvStates *types.MVStates + stat *types.ExeStat + // Preimages occurred seen by VM in the scope of block. preimages map[common.Hash][]byte @@ -143,41 +269,56 @@ type StateDB struct { TrieDBCommits time.Duration TrieCommits time.Duration CodeCommits time.Duration + TxDAGGenerate time.Duration AccountUpdated int StorageUpdated int AccountDeleted int StorageDeleted int + isParallel bool + parallel ParallelState // to keep all the parallel execution elements // Testing hooks onCommit func(states *triestate.Set) // Hook invoked when commit is performed } +func (s *StateDB) GetStateObjectFromUnconfirmedDB(addr common.Address) (*stateObject, bool) { + return nil, false +} + // New creates a new state from a given trie. func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { tr, err := db.OpenTrie(root) + if err != nil { return nil, err } sdb := &StateDB{ - db: db, - trie: tr, - originalRoot: root, - snaps: snaps, - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), - stateObjects: make(map[common.Address]*stateObject), - stateObjectsPending: make(map[common.Address]struct{}), - stateObjectsDirty: make(map[common.Address]struct{}), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount), - logs: make(map[common.Hash][]*types.Log), - preimages: make(map[common.Hash][]byte), - journal: newJournal(), - accessList: newAccessList(), - transientStorage: newTransientStorage(), - hasher: crypto.NewKeccakState(), + db: db, + trie: tr, + originalRoot: root, + snaps: snaps, + snapDestructs: make(map[common.Address]struct{}), + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject), + stateObjectsPending: make(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount), + stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount), + logs: make(map[common.Hash][]*types.Log), + preimages: make(map[common.Hash][]byte), + journal: newJournal(), + accessList: newAccessList(), + transientStorage: newTransientStorage(), + hasher: crypto.NewKeccakState(), + + parallel: ParallelState{ + SlotIndex: -1, + }, + txIndex: -1, } if sdb.snaps != nil { sdb.snap = sdb.snaps.Snapshot(root) @@ -193,6 +334,7 @@ func NewStateDBByTrie(tr Trie, db Database, snaps *snapshot.Tree) (*StateDB, err trie: tr, originalRoot: tr.Hash(), snaps: snaps, + snapDestructs: make(map[common.Address]struct{}), accounts: make(map[common.Hash][]byte), storages: make(map[common.Hash]map[common.Hash][]byte), accountsOrigin: make(map[common.Address][]byte), @@ -215,6 +357,18 @@ func NewStateDBByTrie(tr Trie, db Database, snaps *snapshot.Tree) (*StateDB, err return sdb, nil } +func (s *StateDB) IsParallel() bool { + return s.isParallel +} + +func (s *StateDB) getBaseStateDB() *StateDB { + return s +} + +func (s *StateDB) getStateObjectFromStateObjects(addr common.Address) (*stateObject, bool) { + return s.loadStateObj(addr) +} + // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. @@ -264,7 +418,6 @@ func (s *StateDB) Error() error { func (s *StateDB) AddLog(log *types.Log) { s.journal.append(addLogChange{txhash: s.thash}) - log.TxHash = s.thash log.TxIndex = uint(s.txIndex) log.Index = s.logSize @@ -336,7 +489,10 @@ func (s *StateDB) Empty(addr common.Address) bool { } // GetBalance retrieves the balance from the given address or 0 if object not found -func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { +func (s *StateDB) GetBalance(addr common.Address) (ret *uint256.Int) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), ret) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Balance() @@ -345,12 +501,14 @@ func (s *StateDB) GetBalance(addr common.Address) *uint256.Int { } // GetNonce retrieves the nonce from the given address or 0 if object not found -func (s *StateDB) GetNonce(addr common.Address) uint64 { +func (s *StateDB) GetNonce(addr common.Address) (ret uint64) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountNonce), ret) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Nonce() } - return 0 } @@ -369,7 +527,15 @@ func (s *StateDB) TxIndex() int { return s.txIndex } +// BaseTxIndex returns the tx index that slot db based. +func (s *StateDB) BaseTxIndex() int { + return s.parallel.baseTxIndex +} + func (s *StateDB) GetCode(addr common.Address) []byte { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.Code() @@ -378,6 +544,9 @@ func (s *StateDB) GetCode(addr common.Address) []byte { } func (s *StateDB) GetCodeSize(addr common.Address) int { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), s.GetCodeHash(addr)) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.CodeSize() @@ -385,7 +554,14 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { return 0 } -func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { +// GetCodeHash return: +// - common.Hash{}: the address does not exist +// - emptyCodeHash: the address exist, but code is empty +// - others: the address exist, and code is not empty +func (s *StateDB) GetCodeHash(addr common.Address) (ret common.Hash) { + defer func() { + s.RecordRead(types.AccountStateKey(addr, types.AccountCodeHash), ret.Bytes()) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return common.BytesToHash(stateObject.CodeHash()) @@ -394,7 +570,10 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { } // GetState retrieves a value from the given account's storage trie. -func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { +func (s *StateDB) GetState(addr common.Address, hash common.Hash) (ret common.Hash) { + defer func() { + s.RecordRead(types.StorageStateKey(addr, hash), ret) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.GetState(hash) @@ -403,7 +582,10 @@ func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { } // GetCommittedState retrieves a value from the given account's committed storage trie. -func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { +func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) (ret common.Hash) { + defer func() { + s.RecordRead(types.StorageStateKey(addr, hash), ret) + }() stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.GetCommittedState(hash) @@ -432,16 +614,22 @@ func (s *StateDB) HasSelfDestructed(addr common.Address) bool { func (s *StateDB) AddBalance(addr common.Address, amount *uint256.Int) { stateObject := s.getOrNewStateObject(addr) if stateObject != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance()) stateObject.AddBalance(amount) + return } + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) } // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *uint256.Int) { stateObject := s.getOrNewStateObject(addr) if stateObject != nil { + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), stateObject.Balance()) stateObject.SubBalance(amount) + return } + s.RecordRead(types.AccountStateKey(addr, types.AccountBalance), common.U2560) } func (s *StateDB) SetBalance(addr common.Address, amount *uint256.Int) { @@ -466,6 +654,7 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { + stateObject := s.getOrNewStateObject(addr) if stateObject != nil { stateObject.SetState(key, value) @@ -484,8 +673,8 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // // TODO(rjl493456442) this function should only be supported by 'unwritable' // state and all mutations made should all be discarded afterwards. - if _, ok := s.stateObjectsDestruct[addr]; !ok { - s.stateObjectsDestruct[addr] = nil + if _, ok := s.getStateObjectsDestruct(addr); !ok { + s.setStateObjectsDestruct(addr, nil) } stateObject := s.getOrNewStateObject(addr) for k, v := range storage { @@ -509,7 +698,7 @@ func (s *StateDB) SelfDestruct(addr common.Address) { prevbalance: new(uint256.Int).Set(stateObject.Balance()), }) stateObject.markSelfdestructed() - stateObject.data.Balance = new(uint256.Int) + stateObject.setBalance(new(uint256.Int)) } func (s *StateDB) Selfdestruct6780(addr common.Address) { @@ -517,7 +706,6 @@ func (s *StateDB) Selfdestruct6780(addr common.Address) { if stateObject == nil { return } - if stateObject.created { s.SelfDestruct(addr) } @@ -563,14 +751,18 @@ func (s *StateDB) updateStateObject(obj *stateObject) { } // Encode the account and update the account trie addr := obj.Address() + s.trieParallelLock.Lock() if err := s.trie.UpdateAccount(addr, &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err)) } if obj.dirtyCode { s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) } + s.trieParallelLock.Unlock() } + s.AccountMux.Lock() + defer s.AccountMux.Unlock() // Cache the data until 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 @@ -616,27 +808,88 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { return nil } -// getDeletedStateObject is similar to getStateObject, but instead of returning -// nil for a deleted state object, it returns the actual object with the deleted -// flag set. This is needed by the state journal to revert to the correct s- -// destructed object instead of wiping all knowledge about the state object. -func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { +// GetStateNoUpdate retrieves a value from the given account's storage trie, but do not update the db.stateObjects cache. +func (s *StateDB) GetStateNoUpdate(addr common.Address, hash common.Hash) (ret common.Hash) { + object := s.getStateObjectNoUpdate(addr) + if object != nil { + return object.GetStateNoUpdate(hash) + } + return common.Hash{} +} + +// getStateObjectNoUpdate is similar with getStateObject except that it does not +// update stateObjects records. +func (s *StateDB) getStateObjectNoUpdate(addr common.Address) *stateObject { + obj := s.getDeletedStateObjectNoUpdate(addr) + if obj != nil && !obj.deleted { + return obj + } + return nil +} + +func (s *StateDB) getDeletedStateObjectNoUpdate(addr common.Address) *stateObject { // Prefer live objects if any is available - if obj := s.stateObjects[addr]; obj != nil { + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { return obj } + + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } + obj := newObject(s, s.isParallel, addr, data) + return obj +} + +func (s *StateDB) GetStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { + return s.getStateObjectFromSnapshotOrTrie(addr) +} + +func (s *StateDB) SnapHasAccount(addr common.Address) (exist bool) { + if s.snap == nil { + return false + } + acc, _ := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) + return acc != nil +} + +func (s *StateDB) TriHasAccount(addr common.Address) (exist bool) { + if s.trie == nil { + return false + } + + acc, _ := s.trie.GetAccount(addr) + return acc != nil +} + +func (s *StateDB) GetTrie() Trie { + if s.trie == nil { + return nil + } + return s.trie +} + +func (s *StateDB) SetTrie(trie Trie) { + s.trie = trie +} + +func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *types.StateAccount, ok bool) { // If no live objects are available, attempt to use snapshots - var data *types.StateAccount if s.snap != nil { start := time.Now() - acc, err := s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())) + // the s.hasher is not thread-safe, let's use a mutex to protect it + s.hasherLock.Lock() + hash := crypto.HashData(s.hasher, addr.Bytes()) + s.hasherLock.Unlock() + acc, err := s.snap.Account(hash) if metrics.EnabledExpensive { s.SnapshotAccountReads += time.Since(start) } if err == nil { if acc == nil { - return nil + return nil, false } + data = &types.StateAccount{ Nonce: acc.Nonce, Balance: acc.Balance, @@ -651,30 +904,84 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } } } + // If snapshot unavailable or reading from it failed, load from the database if data == nil { + s.trieParallelLock.Lock() + defer s.trieParallelLock.Unlock() + var trie Trie + if s.isParallel { + // hold lock for parallel + if s.parallel.isSlotDB { + if s.parallel.baseStateDB == nil { + return nil, false + } else { + tr, err := s.parallel.baseStateDB.db.OpenTrie(s.originalRoot) + if err != nil { + log.Error("Can not openTrie for parallel SlotDB\n") + return nil, false + } + trie = tr + } + } else { + trie = s.trie + } + } else { + trie = s.trie + } + start := time.Now() var err error - data, err = s.trie.GetAccount(addr) + data, err = trie.GetAccount(addr) if metrics.EnabledExpensive { s.AccountReads += time.Since(start) } if err != nil { s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %w", addr.Bytes(), err)) - return nil + return nil, false } if data == nil { - return nil + return nil, false } } + + return data, true +} + +// getDeletedStateObject is similar to getStateObject, but instead of returning +// nil for a deleted state object, it returns the actual object with the deleted +// flag set. This is needed by the state journal to revert to the correct s- +// destructed object instead of wiping all knowledge about the state object. +func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { + s.RecordRead(types.AccountStateKey(addr, types.AccountSelf), struct{}{}) + + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } // Insert into the live set - obj := newObject(s, addr, data) + obj := newObject(s, s.isParallel, addr, data) s.setStateObject(obj) return obj } func (s *StateDB) setStateObject(object *stateObject) { - s.stateObjects[object.Address()] = object + if s.isParallel { + if s.parallel.isSlotDB { + s.parallel.locatStateObjects[object.address] = object + } else { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + s.parallel.stateObjects.Store(object.address, object) + } + } else { + s.stateObjects[object.Address()] = object + } } // getOrNewStateObject retrieves a state object or create a new state object if nil. @@ -688,9 +995,22 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { // createObject creates a new state object. If there is an existing account with // the given address, it is overwritten and returned as the second return value. -func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) { +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is existed in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *StateDB) createObject(addr common.Address) (newobj *stateObject, prev *stateObject) { prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! - newobj = newObject(s, addr, nil) + newobj = newObject(s, s.isParallel, addr, nil) if prev == nil { s.journal.append(createObjectChange{account: &addr}) } else { @@ -698,10 +1018,12 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // account and storage data should be cleared as well. Note, it must // be done here, otherwise the destruction event of "original account" // will be lost. - _, prevdestruct := s.stateObjectsDestruct[prev.address] + s.stateObjectDestructLock.Lock() + _, prevdestruct := s.getStateObjectsDestruct(prev.address) if !prevdestruct { - s.stateObjectsDestruct[prev.address] = prev.origin + s.setStateObjectsDestruct(prev.address, prev.origin) } + s.stateObjectDestructLock.Unlock() // There may be some cached account/storage data already since IntermediateRoot // will be called for each transaction before byzantium fork which will always // cache the latest account/storage data. @@ -716,11 +1038,17 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) prevAccountOrigin: prevAccount, prevStorageOrigin: s.storagesOrigin[prev.address], }) + s.AccountMux.Lock() delete(s.accounts, prev.addrHash) - delete(s.storages, prev.addrHash) delete(s.accountsOrigin, prev.address) + s.AccountMux.Unlock() + s.StorageMux.Lock() + delete(s.storages, prev.addrHash) delete(s.storagesOrigin, prev.address) + s.StorageMux.Unlock() } + + newobj.created = true s.setStateObject(newobj) if prev != nil && !prev.deleted { return newobj, prev @@ -739,34 +1067,57 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) // // Carrying over the balance ensures that Ether doesn't disappear. func (s *StateDB) CreateAccount(addr common.Address) { + // no matter it is got from dirty, unconfirmed or main DB + // if addr not exist, preBalance will be common.U2560, it is same as new(big.Int) which + // is the value newObject(), newObj, prev := s.createObject(addr) if prev != nil { - newObj.setBalance(prev.data.Balance) + newObj.setBalance(prev.Balance()) + } +} + +// CopyWithMvStates will copy state with MVStates +func (s *StateDB) CopyWithMvStates(doPrefetch bool) *StateDB { + state := s.copyInternal(doPrefetch) + if s.mvStates != nil { + state.mvStates = s.mvStates } + return state } // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { + return s.copyInternal(false) +} + +// CopyDoPrefetch It is mainly for state prefetcher to do trie prefetch right now. +func (s *StateDB) CopyDoPrefetch() *StateDB { + return s.copyInternal(true) +} + +func (s *StateDB) copyInternal(doPrefetch bool) *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ - db: s.db, - trie: s.db.CopyTrie(s.trie), - originalRoot: s.originalRoot, - accounts: make(map[common.Hash][]byte), - storages: make(map[common.Hash]map[common.Hash][]byte), - accountsOrigin: make(map[common.Address][]byte), - storagesOrigin: make(map[common.Address]map[common.Hash][]byte), - stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), - stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), - stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), - stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), - refund: s.refund, - logs: make(map[common.Hash][]*types.Log, len(s.logs)), - logSize: s.logSize, - preimages: make(map[common.Hash][]byte, len(s.preimages)), - journal: newJournal(), - hasher: crypto.NewKeccakState(), + db: s.db, + trie: s.db.CopyTrie(s.trie), + originalRoot: s.originalRoot, + snapDestructs: make(map[common.Address]struct{}), + accounts: make(map[common.Hash][]byte), + storages: make(map[common.Hash]map[common.Hash][]byte), + accountsOrigin: make(map[common.Address][]byte), + storagesOrigin: make(map[common.Address]map[common.Hash][]byte), + stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), + stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), + stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), + stateObjectsDestruct: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestruct)), + stateObjectsDestructDirty: make(map[common.Address]*types.StateAccount, len(s.stateObjectsDestructDirty)), + refund: s.refund, + logs: make(map[common.Hash][]*types.Log, len(s.logs)), + logSize: s.logSize, + preimages: make(map[common.Hash][]byte, len(s.preimages)), + journal: newJournal(), + hasher: crypto.NewKeccakState(), // In order for the block producer to be able to use and make additions // to the snapshot tree, we need to copy that as well. Otherwise, any @@ -774,6 +1125,8 @@ func (s *StateDB) Copy() *StateDB { // miner to operate trie-backed only. snaps: s.snaps, snap: s.snap, + + parallel: ParallelState{}, } // Copy the dirty states, logs, and preimages for addr := range s.journal.dirties { @@ -781,11 +1134,11 @@ func (s *StateDB) Copy() *StateDB { // and in the Finalise-method, there is a case where an object is in the journal but not // in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for // nil - if object, exist := s.stateObjects[addr]; exist { + if object, exist := s.getStateObjectFromStateObjects(addr); exist { // Even though the original object is dirty, we are not copying the journal, // so we need to make sure that any side-effect the journal would have caused // during a commit (or similar op) is already applied to the copy. - state.stateObjects[addr] = object.deepCopy(state) + state.storeStateObj(addr, object.deepCopy(state)) state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits @@ -796,14 +1149,16 @@ func (s *StateDB) Copy() *StateDB { // is empty. Thus, here we iterate over stateObjects, to enable copies // of copies. for addr := range s.stateObjectsPending { - if _, exist := state.stateObjects[addr]; !exist { - state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) + if _, exist := state.getStateObjectFromStateObjects(addr); !exist { + object, _ := s.getStateObjectFromStateObjects(addr) + state.storeStateObj(addr, object.deepCopy(state)) } state.stateObjectsPending[addr] = struct{}{} } for addr := range s.stateObjectsDirty { - if _, exist := state.stateObjects[addr]; !exist { - state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state) + if _, exist := state.getStateObjectFromStateObjects(addr); !exist { + object, _ := s.getStateObjectFromStateObjects(addr) + state.storeStateObj(addr, object.deepCopy(state)) } state.stateObjectsDirty[addr] = struct{}{} } @@ -811,12 +1166,20 @@ func (s *StateDB) Copy() *StateDB { for addr, value := range s.stateObjectsDestruct { state.stateObjectsDestruct[addr] = value } + for addr, value := range s.stateObjectsDestructDirty { + state.stateObjectsDestructDirty[addr] = value + } // Deep copy the state changes made in the scope of block // along with their original values. + s.AccountMux.Lock() state.accounts = copySet(s.accounts) - state.storages = copy2DSet(s.storages) state.accountsOrigin = copySet(state.accountsOrigin) + s.AccountMux.Unlock() + + s.StorageMux.Lock() + state.storages = copy2DSet(s.storages) state.storagesOrigin = copy2DSet(state.storagesOrigin) + s.StorageMux.Unlock() // Deep copy the logs occurred in the scope of block for hash, logs := range s.logs { @@ -846,6 +1209,7 @@ func (s *StateDB) Copy() *StateDB { if s.prefetcher != nil { state.prefetcher = s.prefetcher.copy() } + return state } @@ -883,8 +1247,27 @@ func (s *StateDB) GetRefund() uint64 { // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + + // finalise stateObjectsDestruct + // The finalise of stateDB is called at verify & commit phase, which is global, no need to acquire the lock. + for addr, acc := range s.stateObjectsDestructDirty { + s.stateObjectsDestruct[addr] = acc + } + s.stateObjectsDestructDirty = make(map[common.Address]*types.StateAccount) for addr := range s.journal.dirties { - obj, exist := s.stateObjects[addr] + var obj *stateObject + var exist bool + if s.parallel.isSlotDB { + obj = s.parallel.dirtiedStateObjectsInSlot[addr] + if obj != nil { + exist = true + } else { + log.Error("StateDB Finalise dirty addr not in dirtiedStateObjectsInSlot", + "addr", addr) + } + } else { + obj, exist = s.getStateObjectFromStateObjects(addr) + } if !exist { // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 // That tx goes out of gas, and although the notion of 'touched' does not exist there, the @@ -911,8 +1294,14 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { delete(s.accountsOrigin, obj.address) // Clear out any previously updated account data (may be recreated via a resurrect) delete(s.storagesOrigin, obj.address) // Clear out any previously updated storage data (may be recreated via a resurrect) } else { - obj.finalise(true) // Prefetch slots in the background + // 1.none parallel mode, we do obj.finalise(true) as normal + // 2.with parallel mode, we do obj.finalise(true) on dispatcher, not on slot routine + // obj.finalise(true) will clear its dirtyStorage, will make prefetch broken. + if !s.isParallel || !s.parallel.isSlotDB { + obj.finalise(true) // Prefetch slots in the background + } } + obj.created = false s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} @@ -932,6 +1321,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { // IntermediateRoot computes the current root hash of the state trie. // It is called in between transactions to get the root hash that // goes into transaction receipts. +// TODO: For parallel SlotDB, IntermediateRootForSlot is used, need to clean up this method. func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) @@ -963,19 +1353,37 @@ func (s *StateDB) AccountsIntermediateRoot() { // first, giving the account prefetches just a few more milliseconds of time // to pull useful data from disk. for addr := range s.stateObjectsPending { - if obj := s.stateObjects[addr]; !obj.deleted { - wg.Add(1) - tasks <- func() { - defer wg.Done() - obj.updateRoot() - - // Cache the data until 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. - s.AccountMux.Lock() - s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) - s.AccountMux.Unlock() + if s.parallel.isSlotDB { + if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; !obj.deleted { + wg.Add(1) + tasks <- func() { + defer wg.Done() + obj.updateRoot() + + // Cache the data until 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. + s.AccountMux.Lock() + s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + s.AccountMux.Unlock() + } + } + } else { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { + wg.Add(1) + tasks <- func() { + defer wg.Done() + obj.updateRoot() + + // Cache the data until 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. + s.AccountMux.Lock() + s.accounts[obj.addrHash] = types.SlimAccountRLP(obj.data) + s.AccountMux.Unlock() + } } } } @@ -991,18 +1399,22 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { // the remainder without, but pre-byzantium even the initial prefetcher is // useless, so no sleep lost. prefetcher := s.prefetcher + r := s.originalRoot if s.prefetcher != nil { defer func() { s.prefetcher.close() s.prefetcher = nil }() + if s.isParallel { + r = s.trie.Hash() + } } - // Now we're about to start to write changes to the trie. The trie is so far // _untouched_. We can check with the prefetcher, if it can give us a trie // which has the same root, but also has some content loaded into it. + // The parallel execution do the change incrementally, so can not check the prefetcher here if prefetcher != nil { - if trie := prefetcher.trie(common.Hash{}, s.originalRoot); trie != nil { + if trie := prefetcher.trie(common.Hash{}, r); trie != nil { s.trie = trie } } @@ -1015,8 +1427,17 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { } usedAddrs := make([][]byte, 0, len(s.stateObjectsPending)) + for addr := range s.stateObjectsPending { - if obj := s.stateObjects[addr]; obj.deleted { + if s.parallel.isSlotDB { + if obj := s.parallel.dirtiedStateObjectsInSlot[addr]; obj.deleted { + s.deleteStateObject(obj) + s.AccountDeleted += 1 + } else { + s.updateStateObject(obj) + s.AccountUpdated += 1 + } + } else if obj, _ := s.getStateObjectFromStateObjects(addr); obj.deleted { s.deleteStateObject(obj) s.AccountDeleted += 1 } else { @@ -1028,6 +1449,9 @@ func (s *StateDB) StateIntermediateRoot() common.Hash { if prefetcher != nil { prefetcher.used(common.Hash{}, s.originalRoot, usedAddrs) } + // parallel slotDB trie will be updated to mainDB since intermediateRoot happens after conflict check. + // so it should be save to clear pending here. + // otherwise there can be a case that the deleted object get ignored and processes as live object in verify phase. if len(s.stateObjectsPending) > 0 { s.stateObjectsPending = make(map[common.Address]struct{}) @@ -1221,6 +1645,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if s.db.TrieDB().Scheme() == rawdb.HashScheme { return incomplete, nil } + for addr, prev := range s.stateObjectsDestruct { // The original account was non-existing, and it's marked as destructed // in the scope of block. It can be case (a) or (b). @@ -1241,6 +1666,7 @@ func (s *StateDB) handleDestruction(nodes *trienode.MergedNodeSet) (map[common.A if prev.Root == types.EmptyRootHash { continue } + // Remove storage slots belong to the account. aborted, slots, set, err := s.deleteStorage(addr, addrHash, prev.Root) if err != nil { @@ -1363,7 +1789,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } for addr := range s.stateObjectsDirty { - if obj := s.stateObjects[addr]; !obj.deleted { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { tasks <- func() { // Write any storage changes in the state object to its storage trie if !s.noTrie { @@ -1447,7 +1873,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er } codeWriter := s.db.DiskDB().NewBatch() for addr := range s.stateObjectsDirty { - if obj := s.stateObjects[addr]; !obj.deleted { + if obj, _ := s.getStateObjectFromStateObjects(addr); !obj.deleted { // Write any contract code associated with the state object if obj.code != nil && obj.dirtyCode { rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code) @@ -1608,7 +2034,7 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre func (s *StateDB) convertAccountSet(set map[common.Address]*types.StateAccount) map[common.Hash]struct{} { ret := make(map[common.Hash]struct{}, len(set)) for addr := range set { - obj, exist := s.stateObjects[addr] + obj, exist := s.getStateObjectFromStateObjects(addr) if !exist { ret[crypto.Keccak256Hash(addr[:])] = struct{}{} } else { @@ -1632,6 +2058,208 @@ func (s *StateDB) GetSnap() snapshot.Snapshot { return s.snap } +func (s *StateDB) BeforeTxTransition() { + if s.isParallel && s.parallel.isSlotDB { + return + } + log.Debug("BeforeTxTransition", "mvStates", s.mvStates == nil, "rwSet", s.rwSet == nil) + if s.mvStates == nil { + return + } + s.rwSet = types.NewRWSet(types.StateVersion{ + TxIndex: s.txIndex, + }) +} + +func (s *StateDB) BeginTxStat(index int) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.mvStates == nil { + return + } + if metrics.EnabledExpensive { + s.stat = types.NewExeStat(index).Begin() + } +} + +func (s *StateDB) StopTxStat(usedGas uint64) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.mvStates == nil { + return + } + // record stat first + if metrics.EnabledExpensive && s.stat != nil { + s.stat.Done().WithGas(usedGas) + rwSet := s.mvStates.RWSet(s.txIndex) + if rwSet != nil { + s.stat.WithRead(len(rwSet.ReadSet())) + } + } +} + +func (s *StateDB) RecordRead(key types.RWKey, val interface{}) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.rwSet == nil { + return + } + s.rwSet.RecordRead(key, types.StateVersion{ + TxIndex: -1, + }, val) +} + +func (s *StateDB) RecordWrite(key types.RWKey, val interface{}) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.rwSet == nil { + return + } + s.rwSet.RecordWrite(key, val) +} + +func (s *StateDB) ResetMVStates(txCount int) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.mvStates != nil { + s.mvStates.Stop() + } + s.mvStates = types.NewMVStates(txCount).EnableAsyncDepGen() + s.rwSet = nil +} + +func (s *StateDB) FinaliseRWSet() error { + if s.isParallel && s.parallel.isSlotDB { + return nil + } + if s.rwSet == nil { + return nil + } + rwSet := s.rwSet + stat := s.stat + if metrics.EnabledExpensive { + defer func(start time.Time) { + s.TxDAGGenerate += time.Since(start) + }(time.Now()) + } + ver := types.StateVersion{ + TxIndex: s.txIndex, + } + if ver != rwSet.Version() { + return errors.New("you finalize a wrong ver of RWSet") + } + + // finalise stateObjectsDestruct + for addr := range s.stateObjectsDestructDirty { + s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) + } + for addr := range s.journal.dirties { + obj, exist := s.getStateObjectFromStateObjects(addr) + if !exist { + continue + } + if obj.selfDestructed || obj.empty() { + // We need to maintain account deletions explicitly (will remain + // set indefinitely). Note only the first occurred self-destruct + // event is tracked. + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + log.Debug("FinaliseRWSet find Destruct", "tx", s.txIndex, "addr", addr, "selfDestructed", obj.selfDestructed) + s.RecordWrite(types.AccountStateKey(addr, types.AccountSuicide), struct{}{}) + } + } else { + // finalise account & storages + obj.finaliseRWSet() + } + } + + // reset stateDB + s.rwSet = nil + if err := s.mvStates.FulfillRWSet(rwSet, stat); err != nil { + return err + } + // just Finalise rwSet in serial execution + return s.mvStates.Finalise(s.txIndex) +} + +func (s *StateDB) getStateObjectsDestruct(addr common.Address) (*types.StateAccount, bool) { + if !(s.isParallel && s.parallel.isSlotDB) { + if acc, ok := s.stateObjectsDestructDirty[addr]; ok { + return acc, ok + } + } + acc, ok := s.stateObjectsDestruct[addr] + return acc, ok +} + +func (s *StateDB) setStateObjectsDestruct(addr common.Address, acc *types.StateAccount) { + if !(s.isParallel && s.parallel.isSlotDB) { + s.stateObjectsDestructDirty[addr] = acc + return + } + s.stateObjectsDestruct[addr] = acc + return +} + +func (s *StateDB) removeStateObjectsDestruct(addr common.Address) { + if !(s.isParallel && s.parallel.isSlotDB) { + delete(s.stateObjectsDestructDirty, addr) + return + } + delete(s.stateObjectsDestruct, addr) +} + +func (s *StateDB) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (types.TxDAG, error) { + if s.isParallel && s.parallel.isSlotDB { + return nil, nil + } + if s.mvStates == nil { + return types.NewEmptyTxDAG(), nil + } + if metrics.EnabledExpensive { + defer func(start time.Time) { + s.TxDAGGenerate += time.Since(start) + }(time.Now()) + } + + return s.mvStates.ResolveTxDAG(txCnt, gasFeeReceivers) +} + +func (s *StateDB) ResolveStats() map[int]*types.ExeStat { + if s.isParallel && s.parallel.isSlotDB { + return nil + } + if s.mvStates == nil { + return nil + } + + return s.mvStates.Stats() +} + +func (s *StateDB) MVStates() *types.MVStates { + if s.isParallel && s.parallel.isSlotDB { + return nil + } + return s.mvStates +} + +func (s *StateDB) RecordSystemTxRWSet(index int) { + if s.isParallel && s.parallel.isSlotDB { + return + } + if s.mvStates == nil { + return + } + s.mvStates.FulfillRWSet(types.NewRWSet(types.StateVersion{ + TxIndex: index, + }).WithExcludedTxFlag(), types.NewExeStat(index).WithExcludedTxFlag()) + s.mvStates.Finalise(index) +} + // copySet returns a deep-copied set. func copySet[k comparable](set map[k][]byte) map[k][]byte { copied := make(map[k][]byte, len(set)) @@ -1652,3 +2280,17 @@ func copy2DSet[k comparable](set map[k]map[common.Hash][]byte) map[k]map[common. } return copied } + +// PrepareForParallel prepares for state db to be used in parallel execution mode. +func (s *StateDB) PrepareForParallel() { + s.isParallel = true + s.parallel.stateObjects = &StateObjectSyncMap{} + // copy objects in stateObjects into parallel if not exist. + // This is lock free as the PrepareForParallel() is invoked at serial phase. + for addr, objPtr := range s.stateObjects { + if _, exist := s.parallel.stateObjects.LoadStateObject(addr); !exist { + newObj := objPtr.deepCopy(s) + s.parallel.stateObjects.StoreStateObject(addr, newObj) + } + } +} diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index e71c984f12..b7eae225ef 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -43,6 +43,10 @@ import ( "github.com/holiman/uint256" ) +var ( + testAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") +) + // Tests that updating a state trie does not leak any database writes prior to // actually committing the state. func TestUpdateLeaks(t *testing.T) { @@ -180,6 +184,7 @@ func TestCopy(t *testing.T) { // modify all in memory for i := byte(0); i < 255; i++ { + origObj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) copyObj := copy.getOrNewStateObject(common.BytesToAddress([]byte{i})) ccopyObj := ccopy.getOrNewStateObject(common.BytesToAddress([]byte{i})) @@ -465,7 +470,7 @@ func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.H for it.Next() { key := common.BytesToHash(s.trie.GetKey(it.Key)) - if value, dirty := so.dirtyStorage[key]; dirty { + if value, dirty := so.dirtyStorage.GetValue(key); dirty { if !cb(key, value) { return nil } diff --git a/core/state/transient_storage.go b/core/state/transient_storage.go index 66e563efa7..ea2b5bfefe 100644 --- a/core/state/transient_storage.go +++ b/core/state/transient_storage.go @@ -21,7 +21,7 @@ import ( ) // transientStorage is a representation of EIP-1153 "Transient Storage". -type transientStorage map[common.Address]Storage +type transientStorage map[common.Address]StorageMap // newTransientStorage creates a new instance of a transientStorage. func newTransientStorage() transientStorage { @@ -31,7 +31,7 @@ func newTransientStorage() transientStorage { // Set sets the transient-storage `value` for `key` at the given `addr`. func (t transientStorage) Set(addr common.Address, key, value common.Hash) { if _, ok := t[addr]; !ok { - t[addr] = make(Storage) + t[addr] = make(StorageMap) } t[addr][key] = value } @@ -49,7 +49,8 @@ func (t transientStorage) Get(addr common.Address, key common.Hash) common.Hash func (t transientStorage) Copy() transientStorage { storage := make(transientStorage) for key, value := range t { - storage[key] = value.Copy() + m := value.Copy() + storage[key] = m.(StorageMap) } return storage } diff --git a/core/state_processor.go b/core/state_processor.go index c9df98536c..d5308af37e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -90,8 +90,12 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } statedb.MarkFullProcessed() + if p.bc.enableTxDAG && !p.bc.vmConfig.EnableParallelExec { + statedb.ResetMVStates(len(block.Transactions())) + } // Iterate over and process the individual transactions for i, tx := range block.Transactions() { + statedb.BeginTxStat(i) start := time.Now() msg, err := TransactionToMessage(tx, signer, header.BaseFee) if err != nil { @@ -103,11 +107,16 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } + // if systemTx or depositTx, tag it + if tx.IsSystemTx() || tx.IsDepositTx() { + statedb.RecordSystemTxRWSet(i) + } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) if metrics.EnabledExpensive { processTxTimer.UpdateSince(start) } + statedb.StopTxStat(receipt.GasUsed) } // Fail if Shanghai not enabled and len(withdrawals) is non-zero. withdrawals := block.Withdrawals() @@ -116,7 +125,6 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles(), withdrawals) - return receipts, allLogs, *usedGas, nil } diff --git a/core/state_transition.go b/core/state_transition.go index a23a26468e..5b87b84fdd 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -18,6 +18,8 @@ package core import ( "fmt" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" "math" "math/big" "time" @@ -41,6 +43,7 @@ type ExecutionResult struct { RefundedGas uint64 // Total gas refunded after execution Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) + delayFees *state.DelayedGasFee } // Unwrap returns the internal evm error which allows us for further @@ -197,6 +200,12 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err return NewStateTransition(evm, msg, gp).TransitionDb() } +func ApplyMessageDelayGasFee(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, error) { + transition := NewStateTransition(evm, msg, gp) + transition.delayGasFee = true + return transition.TransitionDb() +} + // StateTransition represents a state transition. // // == The State Transitioning Model @@ -226,6 +235,7 @@ type StateTransition struct { initialGas uint64 state vm.StateDB evm *vm.EVM + delayGasFee bool } // NewStateTransition initialises and returns a new state transition object. @@ -408,6 +418,10 @@ func (st *StateTransition) preCheck() error { // However if any consensus issue encountered, return the error directly with // nil evm execution result. func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { + // start record rw set in here + if !st.msg.IsSystemTx && !st.msg.IsDepositTx { + st.state.BeforeTxTransition() + } if mint := st.msg.Mint; mint != nil { mintU256, overflow := uint256.FromBig(mint) if overflow { @@ -432,6 +446,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.msg.IsSystemTx && !st.evm.ChainConfig().IsRegolith(st.evm.Context.Time) { gasUsed = 0 } + // just record error tx here + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise error deposit tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) + } result = &ExecutionResult{ UsedGas: gasUsed, Err: fmt.Errorf("failed deposit: %w", err), @@ -439,6 +457,12 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } err = nil } + if err != nil { + // just record error tx here + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise error tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) + } + } return result, err } @@ -519,6 +543,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } DebugInnerExecutionDuration += time.Since(start) + // stop record rw set in here, skip gas fee distribution + if ferr := st.state.FinaliseRWSet(); ferr != nil { + log.Error("finalise tx rwSet fail", "block", st.evm.Context.BlockNumber, "tx", st.evm.StateDB.TxIndex(), "err", ferr) + } + // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. if st.msg.IsDepositTx && !rules.IsOptimismRegolith { @@ -554,12 +583,19 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { ReturnData: ret, }, nil } + + var ( + tipFee *uint256.Int + baseFee *uint256.Int + l1Fee *uint256.Int + ) effectiveTip := msg.GasPrice if rules.IsLondon { effectiveTip = cmath.BigMin(msg.GasTipCap, new(big.Int).Sub(msg.GasFeeCap, st.evm.Context.BaseFee)) } effectiveTipU256, _ := uint256.FromBig(effectiveTip) + // delay gas fee calculation, provide from TxDAG if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 { // Skip fee payment when NoBaseFee is set and the fee fields // are 0. This avoids a negative effectiveTip being applied to @@ -567,7 +603,11 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { } else { fee := new(uint256.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTipU256) - st.state.AddBalance(st.evm.Context.Coinbase, fee) + if st.delayGasFee { + tipFee = fee + } else { + st.state.AddBalance(st.evm.Context.Coinbase, fee) + } } // Check that we are post bedrock to enable op-geth to be able to create pseudo pre-bedrock blocks (these are pre-bedrock, but don't follow l2 geth rules) @@ -578,23 +618,40 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { if overflow { return nil, fmt.Errorf("optimism gas cost overflows U256: %d", gasCost) } - st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + if st.delayGasFee { + baseFee = amtU256 + } else { + st.state.AddBalance(params.OptimismBaseFeeRecipient, amtU256) + } if st.msg.GasPrice.Cmp(big.NewInt(0)) == 0 && st.evm.ChainConfig().IsWright(st.evm.Context.Time) { - st.state.AddBalance(params.OptimismL1FeeRecipient, uint256.NewInt(0)) + if st.delayGasFee { + l1Fee = uint256.NewInt(0) + } else { + st.state.AddBalance(params.OptimismL1FeeRecipient, uint256.NewInt(0)) + } } else if l1Cost := st.evm.Context.L1CostFunc(st.msg.RollupCostData, st.evm.Context.Time); l1Cost != nil { amtU256, overflow = uint256.FromBig(l1Cost) if overflow { return nil, fmt.Errorf("optimism l1 cost overflows U256: %d", l1Cost) } - st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256) + if st.delayGasFee { + l1Fee = amtU256 + } else { + st.state.AddBalance(params.OptimismL1FeeRecipient, amtU256) + } } } - return &ExecutionResult{ UsedGas: st.gasUsed(), RefundedGas: gasRefund, Err: vmerr, ReturnData: ret, + delayFees: &state.DelayedGasFee{ + TipFee: tipFee, + BaseFee: baseFee, + L1Fee: l1Fee, + Coinbase: st.evm.Context.Coinbase, + }, }, nil } diff --git a/core/txdag_test.go b/core/txdag_test.go new file mode 100644 index 0000000000..c69431cc91 --- /dev/null +++ b/core/txdag_test.go @@ -0,0 +1,207 @@ +package core + +import ( + "bufio" + "encoding/csv" + "fmt" + "os" + "strings" + "testing" + + "github.com/ethereum/go-ethereum/core/types" +) + +func TestAnalyzeDAGFile(t *testing.T) { + fromBlock := uint64(8900000) + toBlock := uint64(9200000) + inputFilePath := "/Users/welkin/Downloads/parallel-txdag-output_compare_15000000.csv" + outputFilePath := "./dagAnalyzeResult-890w-920w.csv" + analyzeDAGFile(fromBlock, toBlock, inputFilePath, outputFilePath, t) +} + +func analyzeDAGFile(fromBlock uint64, toBlock uint64, inputFilePath string, outputFilePath string, t *testing.T) { + scanner, file, err := openFile(inputFilePath) + if err != nil { + t.Error("Failed to open dag file", "err", err) + return + } + defer func() { + err := file.Close() + if err != nil { + t.Error("Failed to close dag file", "err", err) + return + } + }() + var walkBlock uint64 + if fromBlock == 0 { + walkBlock = 0 + } else { + walkBlock = fromBlock - 1 + } + err = walkScannerToBlock(scanner, walkBlock) + if err != nil { + t.Error("Failed to walk dag file to block", "err", err, "targetBlock", walkBlock-1) + return + } + writer, outputFile, err := newCSVWriter(outputFilePath) + if err != nil { + t.Error("Failed to create output file", "err", err, "outputFilePath", outputFilePath) + return + } + defer func() { + err := outputFile.Close() + if err != nil { + t.Error("Failed to close output file", "err", err) + return + } + }() + err = scanAndOutput(scanner, writer, toBlock) + if err != nil { + t.Error("Failed to scan dag file to csv", "err", err, "toBlock", toBlock) + return + } +} + +type oneLine struct { + block uint64 + depList [][]uint64 + flags []uint8 + emptyDepRate float64 + totalTxCount int + txLevelLength int + txLevelDetail levelDetails +} + +func (l *oneLine) toCsvStringList() []string { + var result []string + result = append(result, fmt.Sprintf("%d", l.block)) + result = append(result, fmt.Sprintf("%v", l.depList)) + result = append(result, fmt.Sprintf("%v", l.flags)) + result = append(result, fmt.Sprintf("%.4f", l.emptyDepRate)) + result = append(result, fmt.Sprintf("%v", l.totalTxCount)) + result = append(result, fmt.Sprintf("%v", l.txLevelLength)) + result = append(result, fmt.Sprintf("%v", l.txLevelDetail)) + return result +} + +type levelDetails []*levelDetail + +func (l levelDetails) String() string { + sb := strings.Builder{} + for i, detail := range l { + sb.WriteString(detail.String()) + if i < len(l)-1 { + sb.WriteString(",") + } + } + return sb.String() +} + +type levelDetail struct { + idx int + size int +} + +func (l *levelDetail) String() string { + return fmt.Sprintf("%d:%d", l.idx, l.size) +} + +func scanAndOutput(scanner *bufio.Scanner, writer *csv.Writer, toBlock uint64) error { + defer writer.Flush() + err := writer.Write([]string{"block", "depList", "flags", "emptyDepRate", "totalTxCount", "txLevelLength", "txLevelDetail"}) + if err != nil { + return err + } + for scanner.Scan() { + line := scanner.Text() + number, dag, err := readTxDAGItemFromLine(line) + if err != nil { + return err + } + if number > toBlock { + return nil + } + csvOneLine := dagToCSVOneLine(number, dag) + csvStringList := csvOneLine.toCsvStringList() + err = writer.Write(csvStringList) + if err != nil { + return err + } + } + if err := scanner.Err(); err != nil { + return err + } + return nil +} + +func dagToCSVOneLine(number uint64, dag types.TxDAG) *oneLine { + depList := make([][]uint64, 0, dag.TxCount()) + flags := make([]uint8, 0, dag.TxCount()) + emptyDepCount := 0 + var reqs []*PEVMTxRequest + for i := 0; i < dag.TxCount(); i++ { + txIdxs := types.TxDependency(dag, i) + dep := dag.TxDep(i) + depList = append(depList, txIdxs) + if dep.Count() == 0 { + emptyDepCount++ + } + if dep.Flags != nil { + flags = append(flags, *dep.Flags) + } else { + flags = append(flags, 0) + } + reqs = append(reqs, &PEVMTxRequest{txIndex: i}) + } + + txLevels := NewTxLevels(reqs, dag) + var txLevelDetail []*levelDetail + for idx, oneLevel := range txLevels { + txLevelDetail = append(txLevelDetail, &levelDetail{idx: idx, size: len(oneLevel)}) + } + return &oneLine{ + block: number, + depList: depList, + flags: flags, + emptyDepRate: float64(emptyDepCount) / float64(dag.TxCount()), + totalTxCount: dag.TxCount(), + txLevelLength: len(txLevels), + txLevelDetail: txLevelDetail, + } +} + +func newCSVWriter(filePath string) (*csv.Writer, *os.File, error) { + file, err := os.Create(filePath) + if err != nil { + return nil, nil, err + } + writer := csv.NewWriter(file) + return writer, file, nil +} + +func walkScannerToBlock(scanner *bufio.Scanner, targetBlock uint64) error { + for scanner.Scan() { + line := scanner.Text() + blockNum, err := readTxDAGBlockFromLine(line) + if err != nil { + return err + } + if blockNum >= targetBlock { + return nil + } + } + if err := scanner.Err(); err != nil { + return err + } + return nil +} + +func openFile(filePath string) (*bufio.Scanner, *os.File, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, nil, err + } + scanner := bufio.NewScanner(file) + scanner.Buffer(make([]byte, 5*1024*1024), 5*1024*1024) + return scanner, file, nil +} diff --git a/core/types/dag.go b/core/types/dag.go new file mode 100644 index 0000000000..7dc0d89e9b --- /dev/null +++ b/core/types/dag.go @@ -0,0 +1,648 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + "strings" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rlp" + "golang.org/x/exp/slices" +) + +const TxDAGAbiJson = ` +[ + { + "type": "function", + "name": "setTxDAG", + "inputs": [ + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + } +] +` + +var TxDAGABI abi.ABI + +func init() { + var err error + // must be able to register the TxDAGABI + TxDAGABI, err = abi.JSON(strings.NewReader(TxDAGAbiJson)) + if err != nil { + panic(err) + } +} + +// TxDAGType Used to extend TxDAG and customize a new DAG structure +const ( + EmptyTxDAGType byte = iota + PlainTxDAGType +) + +var ( + // NonDependentRelFlag indicates that the txs described is non-dependent + // and is used to reduce storage when there are a large number of dependencies. + NonDependentRelFlag uint8 = 0x01 + // ExcludedTxFlag indicates that the tx is excluded from TxDAG, user should execute them in sequence. + // These excluded transactions should be consecutive in the head or tail. + ExcludedTxFlag uint8 = 0x02 + TxDepFlagMask = NonDependentRelFlag | ExcludedTxFlag +) + +type TxDAG interface { + // Type return TxDAG type + Type() byte + + // Inner return inner instance + Inner() interface{} + + // DelayGasFeeDistribution check if delay the distribution of GasFee + DelayGasFeeDistribution() bool + + // TxDep query TxDeps from TxDAG + TxDep(int) *TxDep + + // TxCount return tx count + TxCount() int + + // SetTxDep at the last one + SetTxDep(int, TxDep) error +} + +func DecodeTxDAGCalldata(data []byte) (TxDAG, error) { + // trim the method id before unpack + if len(data) < 4 { + return nil, fmt.Errorf("invalid txDAG calldata, len(data)=%d", len(data)) + } + calldata, err := TxDAGABI.Methods["setTxDAG"].Inputs.Unpack(data[4:]) + if err != nil { + return nil, fmt.Errorf("failed to call abi unpack, err: %v", err) + } + if len(calldata) <= 0 { + return nil, fmt.Errorf("invalid txDAG calldata, len(calldata)=%d", len(calldata)) + } + data, ok := calldata[0].([]byte) + if !ok { + return nil, fmt.Errorf("invalid txDAG calldata parameter") + } + return DecodeTxDAG(data) +} + +func EncodeTxDAGCalldata(dag TxDAG) ([]byte, error) { + data, err := EncodeTxDAG(dag) + if err != nil { + return nil, fmt.Errorf("failed to encode txDAG, err: %v", err) + } + data, err = TxDAGABI.Pack("setTxDAG", data) + if err != nil { + return nil, fmt.Errorf("failed to call abi pack, err: %v", err) + } + return data, nil +} + +func EncodeTxDAG(dag TxDAG) ([]byte, error) { + if dag == nil { + return nil, errors.New("input nil TxDAG") + } + var buf bytes.Buffer + buf.WriteByte(dag.Type()) + if err := rlp.Encode(&buf, dag.Inner()); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func DecodeTxDAG(enc []byte) (TxDAG, error) { + if len(enc) <= 1 { + return nil, errors.New("too short TxDAG bytes") + } + + switch enc[0] { + case EmptyTxDAGType: + return NewEmptyTxDAG(), nil + case PlainTxDAGType: + dag := new(PlainTxDAG) + if err := rlp.DecodeBytes(enc[1:], dag); err != nil { + return nil, err + } + return dag, nil + default: + return nil, errors.New("unsupported TxDAG bytes") + } +} + +func ValidateTxDAG(d TxDAG, txCnt int) error { + if d == nil { + return nil + } + + switch d.Type() { + case EmptyTxDAGType: + return nil + case PlainTxDAGType: + return ValidatePlainTxDAG(d, txCnt) + default: + return fmt.Errorf("unsupported TxDAG type: %v", d.Type()) + } +} + +func ValidatePlainTxDAG(d TxDAG, txCnt int) error { + if d.TxCount() != txCnt { + return fmt.Errorf("PlainTxDAG contains wrong txs count, expect: %v, actual: %v", txCnt, d.TxCount()) + } + for i := 0; i < txCnt; i++ { + dp := d.TxDep(i) + if dp == nil { + return fmt.Errorf("PlainTxDAG contains nil txdep, tx: %v", i) + } + if dp.Flags != nil && *dp.Flags & ^TxDepFlagMask > 0 { + return fmt.Errorf("PlainTxDAG contains unknown flags, flags: %v", *dp.Flags) + } + dep := TxDependency(d, i) + for j, tx := range dep { + if tx >= uint64(i) || tx >= uint64(txCnt) { + return fmt.Errorf("PlainTxDAG contains the exceed range dependency, tx: %v", i) + } + if j > 0 && dep[j] <= dep[j-1] { + return fmt.Errorf("PlainTxDAG contains unordered dependency, tx: %v", i) + } + } + } + return nil +} + +// GetTxDAG return TxDAG bytes from block if there is any, or return nil if not exist +// the txDAG is stored in the calldata of the last transaction of the block +func GetTxDAG(block *Block) (TxDAG, error) { + txs := block.Transactions() + if txs.Len() <= 0 { + return nil, fmt.Errorf("no txdag found") + } + // get data from the last tx + return DecodeTxDAGCalldata(txs[txs.Len()-1].Data()) +} + +func TxDependency(d TxDAG, i int) []uint64 { + if d == nil || i < 0 || i >= d.TxCount() { + return []uint64{} + } + dep := d.TxDep(i) + if dep.CheckFlag(ExcludedTxFlag) { + return []uint64{} + } + if dep.CheckFlag(NonDependentRelFlag) { + txs := make([]uint64, 0, d.TxCount()-dep.Count()) + for j := 0; j < i; j++ { + if !dep.Exist(j) && j != i { + txs = append(txs, uint64(j)) + } + } + return txs + } + return dep.TxIndexes +} + +// EmptyTxDAG indicate that execute txs in sequence +// It means no transactions or need timely distribute transaction fees +// it only keep partial serial execution when tx cannot delay the distribution or just execute txs in sequence +type EmptyTxDAG struct { +} + +func NewEmptyTxDAG() TxDAG { + return &EmptyTxDAG{} +} + +func (d *EmptyTxDAG) Type() byte { + return EmptyTxDAGType +} + +func (d *EmptyTxDAG) Inner() interface{} { + return d +} + +func (d *EmptyTxDAG) DelayGasFeeDistribution() bool { + return false +} + +func (d *EmptyTxDAG) TxDep(int) *TxDep { + dep := TxDep{ + TxIndexes: nil, + Flags: new(uint8), + } + dep.SetFlag(NonDependentRelFlag) + return &dep +} + +func (d *EmptyTxDAG) TxCount() int { + return 0 +} + +func (d *EmptyTxDAG) SetTxDep(int, TxDep) error { + return nil +} + +func (d *EmptyTxDAG) String() string { + return "EmptyTxDAG" +} + +// PlainTxDAG indicate how to use the dependency of txs, and delay the distribution of GasFee +type PlainTxDAG struct { + // Tx Dependency List, the list index is equal to TxIndex + TxDeps []TxDep +} + +func (d *PlainTxDAG) Type() byte { + return PlainTxDAGType +} + +func (d *PlainTxDAG) Inner() interface{} { + return d +} + +func (d *PlainTxDAG) DelayGasFeeDistribution() bool { + return true +} + +func (d *PlainTxDAG) TxDep(i int) *TxDep { + return &d.TxDeps[i] +} + +func (d *PlainTxDAG) TxCount() int { + return len(d.TxDeps) +} + +func (d *PlainTxDAG) SetTxDep(i int, dep TxDep) error { + if i < 0 || i > len(d.TxDeps) { + return fmt.Errorf("SetTxDep with wrong index: %d", i) + } + if i < len(d.TxDeps) { + d.TxDeps[i] = dep + return nil + } + d.TxDeps = append(d.TxDeps, dep) + return nil +} + +func NewPlainTxDAG(txLen int) *PlainTxDAG { + return &PlainTxDAG{ + TxDeps: make([]TxDep, txLen), + } +} + +func (d *PlainTxDAG) String() string { + builder := strings.Builder{} + for _, txDep := range d.TxDeps { + if txDep.Flags != nil { + builder.WriteString(fmt.Sprintf("%v|%v\n", txDep.TxIndexes, *txDep.Flags)) + continue + } + builder.WriteString(fmt.Sprintf("%v\n", txDep.TxIndexes)) + } + return builder.String() +} + +func (d *PlainTxDAG) Size() int { + enc, err := EncodeTxDAG(d) + if err != nil { + return 0 + } + return len(enc) +} + +// MergeTxDAGExecutionPaths will merge duplicate tx path for scheduling parallel. +// Any tx cannot exist in >= 2 paths. +func MergeTxDAGExecutionPaths(d TxDAG, from, to uint64) ([][]uint64, error) { + if from > to || to >= uint64(d.TxCount()) { + return nil, fmt.Errorf("input wrong from: %v, to: %v, txCnt:%v", from, to, d.TxCount()) + } + mergeMap := make(map[uint64][]uint64, d.TxCount()) + txMap := make(map[uint64]uint64, d.TxCount()) + for i := int(to); i >= int(from); i-- { + index, merge := uint64(i), uint64(i) + deps := TxDependency(d, i) + // drop the out range txs + deps = depExcludeTxRange(deps, from, to) + if oldIdx, exist := findTxPathIndex(deps, index, txMap); exist { + merge = oldIdx + } + for _, tx := range deps { + txMap[tx] = merge + } + txMap[index] = merge + } + + // result by index order + for f, t := range txMap { + if mergeMap[t] == nil { + mergeMap[t] = make([]uint64, 0) + } + if f < from || f > to { + continue + } + mergeMap[t] = append(mergeMap[t], f) + } + mergePaths := make([][]uint64, 0, len(mergeMap)) + for i := from; i <= to; i++ { + path, ok := mergeMap[i] + if !ok { + continue + } + slices.Sort(path) + mergePaths = append(mergePaths, path) + } + + return mergePaths, nil +} + +// depExcludeTxRange drop all from~to items, and deps is ordered. +func depExcludeTxRange(deps []uint64, from uint64, to uint64) []uint64 { + if len(deps) == 0 { + return deps + } + start, end := 0, len(deps)-1 + for start < len(deps) && deps[start] < from { + start++ + } + for end >= 0 && deps[end] > to { + end-- + } + if start > end { + return nil + } + return deps[start : end+1] +} + +func findTxPathIndex(path []uint64, cur uint64, txMap map[uint64]uint64) (uint64, bool) { + if old, ok := txMap[cur]; ok { + return old, true + } + + for _, index := range path { + if old, ok := txMap[index]; ok { + return old, true + } + } + + return 0, false +} + +// travelTxDAGExecutionPaths will print all tx execution path +func travelTxDAGExecutionPaths(d TxDAG) [][]uint64 { + exePaths := make([][]uint64, 0) + // travel tx deps with BFS + for i := uint64(0); i < uint64(d.TxCount()); i++ { + exePaths = append(exePaths, travelTxDAGTargetPath(d, i)) + } + return exePaths +} + +// TxDep store the current tx dependency relation with other txs +type TxDep struct { + TxIndexes []uint64 + // Flags may has multi flag meaning, ref NonDependentRelFlag, ExcludedTxFlag. + Flags *uint8 `rlp:"optional"` +} + +func NewTxDep(indexes []uint64, flags ...uint8) TxDep { + dep := TxDep{ + TxIndexes: indexes, + } + if len(flags) == 0 { + return dep + } + dep.Flags = new(uint8) + for _, flag := range flags { + dep.SetFlag(flag) + } + return dep +} + +func (d *TxDep) AppendDep(i int) { + d.TxIndexes = append(d.TxIndexes, uint64(i)) +} + +func (d *TxDep) Exist(i int) bool { + for _, index := range d.TxIndexes { + if index == uint64(i) { + return true + } + } + + return false +} + +func (d *TxDep) Count() int { + return len(d.TxIndexes) +} + +func (d *TxDep) Last() int { + if d.Count() == 0 { + return -1 + } + return int(d.TxIndexes[len(d.TxIndexes)-1]) +} + +func (d *TxDep) CheckFlag(flag uint8) bool { + var flags uint8 + if d.Flags != nil { + flags = *d.Flags + } + return flags&flag == flag +} + +func (d *TxDep) SetFlag(flag uint8) { + if d.Flags == nil { + d.Flags = new(uint8) + } + *d.Flags |= flag +} + +func (d *TxDep) ClearFlag(flag uint8) { + if d.Flags == nil { + return + } + *d.Flags &= ^flag +} + +var ( + longestTimeTimer = metrics.NewRegisteredTimer("dag/longesttime", nil) + longestGasTimer = metrics.NewRegisteredTimer("dag/longestgas", nil) + serialTimeTimer = metrics.NewRegisteredTimer("dag/serialtime", nil) + totalTxMeter = metrics.NewRegisteredMeter("dag/txcnt", nil) + totalNoDepMeter = metrics.NewRegisteredMeter("dag/nodepcnt", nil) + total2DepMeter = metrics.NewRegisteredMeter("dag/2depcnt", nil) + total4DepMeter = metrics.NewRegisteredMeter("dag/4depcnt", nil) + total8DepMeter = metrics.NewRegisteredMeter("dag/8depcnt", nil) + total16DepMeter = metrics.NewRegisteredMeter("dag/16depcnt", nil) + total32DepMeter = metrics.NewRegisteredMeter("dag/32depcnt", nil) +) + +func EvaluateTxDAGPerformance(dag TxDAG, stats map[int]*ExeStat) { + if len(stats) != dag.TxCount() || dag.TxCount() == 0 { + return + } + paths := travelTxDAGExecutionPaths(dag) + // Attention: this is based on best schedule, it will reduce a lot by executing previous txs in parallel + // It assumes that there is no parallel thread limit + txCount := dag.TxCount() + var ( + maxGasIndex int + maxGas uint64 + maxTimeIndex int + maxTime time.Duration + txTimes = make([]time.Duration, txCount) + txGases = make([]uint64, txCount) + txReads = make([]int, txCount) + noDepCnt int + ) + + totalTxMeter.Mark(int64(txCount)) + for i, path := range paths { + if stats[i].excludedTx { + continue + } + if len(path) <= 1 { + noDepCnt++ + totalNoDepMeter.Mark(1) + } + if len(path) <= 3 { + total2DepMeter.Mark(1) + } + if len(path) <= 5 { + total4DepMeter.Mark(1) + } + if len(path) <= 9 { + total8DepMeter.Mark(1) + } + if len(path) <= 17 { + total16DepMeter.Mark(1) + } + if len(path) <= 33 { + total32DepMeter.Mark(1) + } + + // find the biggest cost time from dependency txs + for j := 0; j < len(path)-1; j++ { + prev := path[j] + if txTimes[prev] > txTimes[i] { + txTimes[i] = txTimes[prev] + } + if txGases[prev] > txGases[i] { + txGases[i] = txGases[prev] + } + if txReads[prev] > txReads[i] { + txReads[i] = txReads[prev] + } + } + txTimes[i] += stats[i].costTime + txGases[i] += stats[i].usedGas + txReads[i] += stats[i].readCount + + // try to find max gas + if txGases[i] > maxGas { + maxGas = txGases[i] + maxGasIndex = i + } + if txTimes[i] > maxTime { + maxTime = txTimes[i] + maxTimeIndex = i + } + } + + longestTimeTimer.Update(txTimes[maxTimeIndex]) + longestGasTimer.Update(txTimes[maxGasIndex]) + // serial path + var ( + sTime time.Duration + sGas uint64 + sRead int + sPath []int + ) + for i, stat := range stats { + if stat.excludedTx { + continue + } + sPath = append(sPath, i) + sTime += stat.costTime + sGas += stat.usedGas + sRead += stat.readCount + } + serialTimeTimer.Update(sTime) +} + +// travelTxDAGTargetPath will print target execution path +func travelTxDAGTargetPath(d TxDAG, from uint64) []uint64 { + var ( + queue []uint64 + path []uint64 + ) + + queue = append(queue, from) + path = append(path, from) + for len(queue) > 0 { + var next []uint64 + for _, i := range queue { + for _, dep := range TxDependency(d, int(i)) { + if !slices.Contains(path, dep) { + path = append(path, dep) + next = append(next, dep) + } + } + } + queue = next + } + slices.Sort(path) + return path +} + +// ExeStat records tx execution info +type ExeStat struct { + txIndex int + usedGas uint64 + readCount int + startTime time.Time + costTime time.Duration + + // some flags + excludedTx bool +} + +func NewExeStat(txIndex int) *ExeStat { + return &ExeStat{ + txIndex: txIndex, + } +} + +func (s *ExeStat) Begin() *ExeStat { + s.startTime = time.Now() + return s +} + +func (s *ExeStat) Done() *ExeStat { + s.costTime = time.Since(s.startTime) + return s +} + +func (s *ExeStat) WithExcludedTxFlag() *ExeStat { + s.excludedTx = true + return s +} + +func (s *ExeStat) WithGas(gas uint64) *ExeStat { + s.usedGas = gas + return s +} + +func (s *ExeStat) WithRead(rc int) *ExeStat { + s.readCount = rc + return s +} diff --git a/core/types/dag_test.go b/core/types/dag_test.go new file mode 100644 index 0000000000..f59ca51cc1 --- /dev/null +++ b/core/types/dag_test.go @@ -0,0 +1,443 @@ +package types + +import ( + "encoding/hex" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/golang/snappy" + + "github.com/cometbft/cometbft/libs/rand" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + mockAddr = common.HexToAddress("0x482bA86399ab6Dcbe54071f8d22258688B4509b1") + mockHash = common.HexToHash("0xdc13f8d7bdb8ec4de02cd4a50a1aa2ab73ec8814e0cdb550341623be3dd8ab7a") +) + +func TestEncodeTxDAGCalldata(t *testing.T) { + tg := mockSimpleDAG() + originTg := tg + data, err := EncodeTxDAGCalldata(tg) + assert.Equal(t, nil, err) + tg, err = DecodeTxDAGCalldata(data) + assert.Equal(t, nil, err) + assert.Equal(t, true, tg.TxCount() > 0) + assert.Equal(t, originTg, tg) + + _, err = DecodeTxDAGCalldata(nil) + assert.NotEqual(t, nil, err) +} + +func TestDecodeCalldata(t *testing.T) { + calldata := "0x5517ed8c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001b201f901aef901abc2c002c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c152c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c1c0c2c0010000000000000000000000000000" + decode, err := hexutil.Decode(calldata) + if err != nil { + return + } + dagCalldata, err := DecodeTxDAGCalldata(decode) + if err != nil { + t.Errorf("Error decoding calldata: %s", err) + return + } + //for i := 0; i < dagCalldata.TxCount(); i++ { + // dep := dagCalldata.TxDep(i) + // log.Printf("idx:%d,dep:%v", i, dep.TxIndexes) + //} + assert.Equal(t, true, dagCalldata.TxDep(186).Exist(82)) + assert.Equal(t, 0, dagCalldata.TxDep(187).Count()) +} + +func TestTxDAG_SetTxDep(t *testing.T) { + dag := mockSimpleDAG() + require.NoError(t, dag.SetTxDep(9, NewTxDep(nil, NonDependentRelFlag))) + require.NoError(t, dag.SetTxDep(10, NewTxDep(nil, NonDependentRelFlag))) + require.Error(t, dag.SetTxDep(12, NewTxDep(nil, NonDependentRelFlag))) + dag = NewEmptyTxDAG() + require.NoError(t, dag.SetTxDep(0, NewTxDep(nil, NonDependentRelFlag))) + require.NoError(t, dag.SetTxDep(11, NewTxDep(nil, NonDependentRelFlag))) +} + +func TestTxDAG(t *testing.T) { + dag := mockSimpleDAG() + t.Log(dag) + dag = mockSystemTxDAG() + t.Log(dag) +} + +func TestEvaluateTxDAG(t *testing.T) { + dag := mockSystemTxDAG() + stats := make(map[int]*ExeStat, dag.TxCount()) + for i := 0; i < dag.TxCount(); i++ { + stats[i] = NewExeStat(i).WithGas(uint64(i)).WithRead(i) + stats[i].costTime = time.Duration(i) + txDep := dag.TxDep(i) + if txDep.CheckFlag(NonDependentRelFlag) { + stats[i].WithExcludedTxFlag() + } + } + EvaluateTxDAGPerformance(dag, stats) +} + +func TestMergeTxDAGExecutionPaths_Simple(t *testing.T) { + tests := []struct { + d TxDAG + from uint64 + to uint64 + expect [][]uint64 + }{ + { + d: mockSimpleDAG(), + from: 0, + to: 9, + expect: [][]uint64{ + {0, 3, 4}, + {1, 2, 5, 6, 7}, + {8, 9}, + }, + }, + { + d: mockSimpleDAG(), + from: 1, + to: 1, + expect: [][]uint64{ + {1}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 0, + to: 9, + expect: [][]uint64{ + {5, 6}, + {0, 1, 2, 3, 4, 7, 8, 9}, + }, + }, + { + d: mockSystemTxDAGWithLargeDeps(), + from: 0, + to: 11, + expect: [][]uint64{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {10}, + {11}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 5, + to: 8, + expect: [][]uint64{ + {5, 6}, + {7}, + {8}, + }, + }, + { + d: mockSimpleDAGWithLargeDeps(), + from: 5, + to: 9, + expect: [][]uint64{ + {5, 6}, + {7}, + {8, 9}, + }, + }, + } + for i, item := range tests { + paths, err := MergeTxDAGExecutionPaths(item.d, item.from, item.to) + require.NoError(t, err) + require.Equal(t, item.expect, paths, i) + } +} + +func TestMergeTxDAGExecutionPaths_Random(t *testing.T) { + dag := mockRandomDAG(10000) + paths, _ := MergeTxDAGExecutionPaths(dag, 0, uint64(dag.TxCount()-1)) + txMap := make(map[uint64]uint64, dag.TxCount()) + for _, path := range paths { + for _, index := range path { + old, ok := txMap[index] + require.False(t, ok, index, path, old) + txMap[index] = path[0] + } + } + require.Equal(t, dag.TxCount(), len(txMap)) +} + +func TestTxDAG_Compression(t *testing.T) { + dag := mockRandomDAG(10000) + enc, err := EncodeTxDAG(dag) + require.NoError(t, err) + encoded := snappy.Encode(nil, enc) + t.Log("enc", len(enc), "compressed", len(encoded), "ratio", 1-(float64(len(encoded))/float64(len(enc)))) +} + +func BenchmarkMergeTxDAGExecutionPaths(b *testing.B) { + dag := mockRandomDAG(100000) + for i := 0; i < b.N; i++ { + MergeTxDAGExecutionPaths(dag, 0, uint64(dag.TxCount()-1)) + } +} + +func BenchmarkTxDAG_Encode(b *testing.B) { + dag := mockRandomDAG(10000) + for i := 0; i < b.N; i++ { + EncodeTxDAG(dag) + } +} + +func BenchmarkTxDAG_Decode(b *testing.B) { + dag := mockRandomDAG(10000) + enc, _ := EncodeTxDAG(dag) + for i := 0; i < b.N; i++ { + DecodeTxDAG(enc) + } +} + +func mockSimpleDAG() TxDAG { + dag := NewPlainTxDAG(10) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{6} + dag.TxDeps[8].TxIndexes = []uint64{} + dag.TxDeps[9].TxIndexes = []uint64{8} + return dag +} + +func mockSimpleDAGWithLargeDeps() TxDAG { + dag := NewPlainTxDAG(10) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{2, 4} + dag.TxDeps[8].TxIndexes = []uint64{} + //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 3, 4, 8} + dag.TxDeps[9] = NewTxDep([]uint64{2, 5, 6, 7}, NonDependentRelFlag) + return dag +} + +func mockRandomDAG(txLen int) TxDAG { + dag := NewPlainTxDAG(txLen) + for i := 0; i < txLen; i++ { + deps := make([]uint64, 0) + if i == 0 || rand.Bool() { + dag.TxDeps[i].TxIndexes = deps + continue + } + depCnt := rand.Int()%i + 1 + for j := 0; j < depCnt; j++ { + var dep uint64 + if j > 0 && deps[j-1]+1 == uint64(i) { + break + } + if j > 0 { + dep = uint64(rand.Int())%(uint64(i)-deps[j-1]-1) + deps[j-1] + 1 + } else { + dep = uint64(rand.Int() % i) + } + deps = append(deps, dep) + } + dag.TxDeps[i].TxIndexes = deps + } + return dag +} + +func mockSystemTxDAG() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{6} + dag.TxDeps[8].TxIndexes = []uint64{} + dag.TxDeps[9].TxIndexes = []uint64{8} + dag.TxDeps[10] = NewTxDep([]uint64{}, ExcludedTxFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, ExcludedTxFlag) + return dag +} + +func mockSystemTxDAG2() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0] = NewTxDep([]uint64{}) + dag.TxDeps[1] = NewTxDep([]uint64{}) + dag.TxDeps[2] = NewTxDep([]uint64{}) + dag.TxDeps[3] = NewTxDep([]uint64{0}) + dag.TxDeps[4] = NewTxDep([]uint64{0}) + dag.TxDeps[5] = NewTxDep([]uint64{1, 2}) + dag.TxDeps[6] = NewTxDep([]uint64{5}) + dag.TxDeps[7] = NewTxDep([]uint64{6}) + dag.TxDeps[8] = NewTxDep([]uint64{}) + dag.TxDeps[9] = NewTxDep([]uint64{8}) + dag.TxDeps[10] = NewTxDep([]uint64{}, NonDependentRelFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, NonDependentRelFlag) + return dag +} + +func mockSystemTxDAGWithLargeDeps() TxDAG { + dag := NewPlainTxDAG(12) + dag.TxDeps[0].TxIndexes = []uint64{} + dag.TxDeps[1].TxIndexes = []uint64{} + dag.TxDeps[2].TxIndexes = []uint64{} + dag.TxDeps[3].TxIndexes = []uint64{0} + dag.TxDeps[4].TxIndexes = []uint64{0} + dag.TxDeps[5].TxIndexes = []uint64{1, 2} + dag.TxDeps[6].TxIndexes = []uint64{5} + dag.TxDeps[7].TxIndexes = []uint64{3} + dag.TxDeps[8].TxIndexes = []uint64{} + //dag.TxDeps[9].TxIndexes = []uint64{0, 1, 2, 6, 7, 8} + dag.TxDeps[9] = NewTxDep([]uint64{3, 4, 5, 10, 11}, NonDependentRelFlag) + dag.TxDeps[10] = NewTxDep([]uint64{}, ExcludedTxFlag) + dag.TxDeps[11] = NewTxDep([]uint64{}, ExcludedTxFlag) + return dag +} + +func TestTxDAG_Encode_Decode(t *testing.T) { + tests := []struct { + expect TxDAG + }{ + { + expect: TxDAG(&EmptyTxDAG{}), + }, + { + expect: mockSimpleDAG(), + }, + { + expect: mockRandomDAG(100), + }, + { + expect: mockSystemTxDAG(), + }, + { + expect: mockSystemTxDAG2(), + }, + { + expect: mockSystemTxDAGWithLargeDeps(), + }, + } + for i, item := range tests { + enc, err := EncodeTxDAG(item.expect) + t.Log(hex.EncodeToString(enc)) + require.NoError(t, err, i) + actual, err := DecodeTxDAG(enc) + require.NoError(t, err, i) + require.Equal(t, item.expect, actual, i) + if i%2 == 0 { + enc[0] = 2 + _, err = DecodeTxDAG(enc) + require.Error(t, err) + } + } +} + +func TestDecodeTxDAG(t *testing.T) { + tests := []struct { + enc string + err bool + }{ + {"00c0", false}, + {"01dddcc1c0c1c0c1c0c2c180c2c180c3c20102c3c20205c2c106c1c0c2c108", false}, + {"01e3e2c1c0c1c0c1c0c2c180c2c180c3c20102c3c20205c2c106c1c0c2c108c2c001c2c001", false}, + {"0132e212", true}, + {"01dfdec280c0c280c0c380c101c380c102c380c103c380c104c380c105c380c106", true}, + {"01cdccc280c0c280c0c280c0c280c0", true}, + } + for i, item := range tests { + enc, err := hex.DecodeString(item.enc) + require.NoError(t, err, i) + txDAG, err := DecodeTxDAG(enc) + if item.err { + require.Error(t, err, i) + continue + } + require.NoError(t, err, i) + t.Log(txDAG) + } +} + +func TestTxDep_Flags(t *testing.T) { + dep := NewTxDep(nil) + dep.ClearFlag(NonDependentRelFlag) + dep.SetFlag(NonDependentRelFlag) + dep.SetFlag(ExcludedTxFlag) + compared := NewTxDep(nil, NonDependentRelFlag, ExcludedTxFlag) + require.Equal(t, dep, compared) + require.Equal(t, NonDependentRelFlag|ExcludedTxFlag, *dep.Flags) + dep.ClearFlag(ExcludedTxFlag) + require.Equal(t, NonDependentRelFlag, *dep.Flags) + require.True(t, dep.CheckFlag(NonDependentRelFlag)) + require.False(t, dep.CheckFlag(ExcludedTxFlag)) +} + +func TestDepExcludeTxRange(t *testing.T) { + tests := []struct { + src []uint64 + from uint64 + to uint64 + expect []uint64 + }{ + { + src: nil, + from: 0, + to: 4, + expect: nil, + }, + { + src: []uint64{}, + from: 0, + to: 4, + expect: []uint64{}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 4, + to: 4, + expect: []uint64{4}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 1, + to: 3, + expect: []uint64{1, 2, 3}, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 5, + to: 6, + expect: nil, + }, + { + src: []uint64{2, 3, 4}, + from: 0, + to: 1, + expect: nil, + }, + { + src: []uint64{0, 1, 2, 3, 4}, + from: 0, + to: 4, + expect: []uint64{0, 1, 2, 3, 4}, + }, + } + + for i, item := range tests { + require.Equal(t, item.expect, depExcludeTxRange(item.src, item.from, item.to), i) + } +} diff --git a/core/types/mvstates.go b/core/types/mvstates.go new file mode 100644 index 0000000000..4637b71d2f --- /dev/null +++ b/core/types/mvstates.go @@ -0,0 +1,661 @@ +package types + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/holiman/uint256" + "golang.org/x/exp/slices" +) + +const ( + AccountStatePrefix = 'a' + StorageStatePrefix = 's' +) + +type RWKey [1 + common.AddressLength + common.HashLength]byte + +type AccountState byte + +const ( + AccountSelf AccountState = iota + AccountNonce + AccountBalance + AccountCodeHash + AccountSuicide +) + +const ( + asyncDepGenChanSize = 10000 +) + +func AccountStateKey(account common.Address, state AccountState) RWKey { + var key RWKey + key[0] = AccountStatePrefix + copy(key[1:], account.Bytes()) + key[1+common.AddressLength] = byte(state) + return key +} + +func StorageStateKey(account common.Address, state common.Hash) RWKey { + var key RWKey + key[0] = StorageStatePrefix + copy(key[1:], account.Bytes()) + copy(key[1+common.AddressLength:], state.Bytes()) + return key +} + +func (key *RWKey) IsAccountState() (bool, AccountState) { + return AccountStatePrefix == key[0], AccountState(key[1+common.AddressLength]) +} + +func (key *RWKey) IsAccountSelf() bool { + ok, s := key.IsAccountState() + if !ok { + return false + } + return s == AccountSelf +} + +func (key *RWKey) IsAccountSuicide() bool { + ok, s := key.IsAccountState() + if !ok { + return false + } + return s == AccountSuicide +} + +func (key *RWKey) ToAccountSelf() RWKey { + return AccountStateKey(key.Addr(), AccountSelf) +} + +func (key *RWKey) IsStorageState() bool { + return StorageStatePrefix == key[0] +} + +func (key *RWKey) String() string { + return hex.EncodeToString(key[:]) +} + +func (key *RWKey) Addr() common.Address { + return common.BytesToAddress(key[1 : 1+common.AddressLength]) +} + +// StateVersion record specific TxIndex & TxIncarnation +// if TxIndex equals to -1, it means the state read from DB. +type StateVersion struct { + TxIndex int + // Tx incarnation used for multi ver state + TxIncarnation int +} + +// RWSet record all read & write set in txs +// Attention: this is not a concurrent safety structure +type RWSet struct { + ver StateVersion + readSet map[RWKey]*RWItem + writeSet map[RWKey]*RWItem + + // some flags + rwRecordDone bool + excludedTx bool +} + +func NewRWSet(ver StateVersion) *RWSet { + return &RWSet{ + ver: ver, + readSet: make(map[RWKey]*RWItem, 64), + writeSet: make(map[RWKey]*RWItem, 32), + } +} + +func (s *RWSet) RecordRead(key RWKey, ver StateVersion, val interface{}) { + // only record the first read version + if _, exist := s.readSet[key]; exist { + return + } + s.readSet[key] = &RWItem{ + Ver: ver, + Val: val, + } +} + +func (s *RWSet) RecordWrite(key RWKey, val interface{}) { + wr, exist := s.writeSet[key] + if !exist { + s.writeSet[key] = &RWItem{ + Ver: s.ver, + Val: val, + } + return + } + wr.Val = val +} + +func (s *RWSet) Version() StateVersion { + return s.ver +} + +func (s *RWSet) ReadSet() map[RWKey]*RWItem { + return s.readSet +} + +func (s *RWSet) WriteSet() map[RWKey]*RWItem { + return s.writeSet +} + +func (s *RWSet) WithExcludedTxFlag() *RWSet { + s.excludedTx = true + return s +} + +func (s *RWSet) String() string { + builder := strings.Builder{} + builder.WriteString(fmt.Sprintf("tx: %v, inc: %v\nreadSet: [", s.ver.TxIndex, s.ver.TxIncarnation)) + i := 0 + for key, _ := range s.readSet { + if i > 0 { + builder.WriteString(fmt.Sprintf(", %v", key.String())) + continue + } + builder.WriteString(fmt.Sprintf("%v", key.String())) + i++ + } + builder.WriteString("]\nwriteSet: [") + i = 0 + for key, _ := range s.writeSet { + if i > 0 { + builder.WriteString(fmt.Sprintf(", %v", key.String())) + continue + } + builder.WriteString(fmt.Sprintf("%v", key.String())) + i++ + } + builder.WriteString("]\n") + return builder.String() +} + +// isEqualRWVal compare state +func isEqualRWVal(key RWKey, src interface{}, compared interface{}) bool { + if ok, state := key.IsAccountState(); ok { + switch state { + case AccountBalance: + if src != nil && compared != nil { + return equalUint256(src.(*uint256.Int), compared.(*uint256.Int)) + } + return src == compared + case AccountNonce: + return src.(uint64) == compared.(uint64) + case AccountCodeHash: + if src != nil && compared != nil { + return slices.Equal(src.([]byte), compared.([]byte)) + } + return src == compared + } + return false + } + + if src != nil && compared != nil { + return src.(common.Hash) == compared.(common.Hash) + } + return src == compared +} + +func equalUint256(s, c *uint256.Int) bool { + if s != nil && c != nil { + return s.Eq(c) + } + + return s == c +} + +type RWItem struct { + Ver StateVersion + Val interface{} +} + +func NewRWItem(ver StateVersion, val interface{}) *RWItem { + return &RWItem{ + Ver: ver, + Val: val, + } +} + +func (w *RWItem) TxIndex() int { + return w.Ver.TxIndex +} + +func (w *RWItem) TxIncarnation() int { + return w.Ver.TxIncarnation +} + +type PendingWrites struct { + list []*RWItem +} + +func NewPendingWrites() *PendingWrites { + return &PendingWrites{ + list: make([]*RWItem, 0, 8), + } +} + +func (w *PendingWrites) Append(pw *RWItem) { + if i, found := w.SearchTxIndex(pw.TxIndex()); found { + w.list[i] = pw + return + } + + w.list = append(w.list, pw) + for i := len(w.list) - 1; i > 0; i-- { + if w.list[i].TxIndex() > w.list[i-1].TxIndex() { + break + } + w.list[i-1], w.list[i] = w.list[i], w.list[i-1] + } +} + +func (w *PendingWrites) SearchTxIndex(txIndex int) (int, bool) { + n := len(w.list) + i, j := 0, n + for i < j { + h := int(uint(i+j) >> 1) + // i ≤ h < j + if w.list[h].TxIndex() < txIndex { + i = h + 1 + } else { + j = h + } + } + return i, i < n && w.list[i].TxIndex() == txIndex +} + +func (w *PendingWrites) FindLastWrite(txIndex int) *RWItem { + var i, _ = w.SearchTxIndex(txIndex) + for j := i - 1; j >= 0; j-- { + if w.list[j].TxIndex() < txIndex { + return w.list[j] + } + } + + return nil +} + +func (w *PendingWrites) FindPrevWrites(txIndex int) []*RWItem { + var i, _ = w.SearchTxIndex(txIndex) + for j := i - 1; j >= 0; j-- { + if w.list[j].TxIndex() < txIndex { + return w.list[:j+1] + } + } + + return nil +} + +type MVStates struct { + rwSets map[int]*RWSet + pendingWriteSet map[RWKey]*PendingWrites + nextFinaliseIndex int + + // dependency map cache for generating TxDAG + // depMapCache[i].exist(j) means j->i, and i > j + depMapCache map[int]TxDepMap + depsCache map[int][]uint64 + + // async dep analysis + depsGenChan chan int + stopChan chan struct{} + asyncRunning bool + + // execution stat infos + stats map[int]*ExeStat + lock sync.RWMutex +} + +func NewMVStates(txCount int) *MVStates { + return &MVStates{ + rwSets: make(map[int]*RWSet, txCount), + pendingWriteSet: make(map[RWKey]*PendingWrites, txCount*8), + depMapCache: make(map[int]TxDepMap, txCount), + depsCache: make(map[int][]uint64, txCount), + stats: make(map[int]*ExeStat, txCount), + } +} + +func (s *MVStates) EnableAsyncDepGen() *MVStates { + s.lock.Lock() + defer s.lock.Unlock() + s.depsGenChan = make(chan int, asyncDepGenChanSize) + s.stopChan = make(chan struct{}) + s.asyncRunning = true + go s.asyncDepGenLoop() + return s +} + +func (s *MVStates) Stop() error { + s.lock.Lock() + defer s.lock.Unlock() + s.stopAsyncDepGen() + return nil +} + +func (s *MVStates) stopAsyncDepGen() { + if !s.asyncRunning { + return + } + s.asyncRunning = false + if s.stopChan != nil { + close(s.stopChan) + } +} + +func (s *MVStates) asyncDepGenLoop() { + timeout := time.After(3 * time.Second) + for { + select { + case tx := <-s.depsGenChan: + s.lock.Lock() + s.resolveDepsCacheByWrites(tx, s.rwSets[tx]) + s.lock.Unlock() + case <-s.stopChan: + return + case <-timeout: + log.Warn("asyncDepGenLoop exit by timeout") + return + } + } +} + +func (s *MVStates) RWSets() map[int]*RWSet { + s.lock.RLock() + defer s.lock.RUnlock() + return s.rwSets +} + +func (s *MVStates) Stats() map[int]*ExeStat { + s.lock.RLock() + defer s.lock.RUnlock() + return s.stats +} + +func (s *MVStates) RWSet(index int) *RWSet { + s.lock.RLock() + defer s.lock.RUnlock() + if index >= len(s.rwSets) { + return nil + } + return s.rwSets[index] +} + +// ReadState read state from MVStates +func (s *MVStates) ReadState(txIndex int, key RWKey) *RWItem { + s.lock.RLock() + defer s.lock.RUnlock() + + wset, ok := s.pendingWriteSet[key] + if !ok { + return nil + } + return wset.FindLastWrite(txIndex) +} + +// FulfillRWSet it can execute as async, and rwSet & stat must guarantee read-only +// try to generate TxDAG, when fulfill RWSet +func (s *MVStates) FulfillRWSet(rwSet *RWSet, stat *ExeStat) error { + log.Debug("FulfillRWSet", "total", len(s.rwSets), "cur", rwSet.ver.TxIndex, "reads", len(rwSet.readSet), "writes", len(rwSet.writeSet)) + s.lock.Lock() + defer s.lock.Unlock() + index := rwSet.ver.TxIndex + if index < s.nextFinaliseIndex { + return errors.New("fulfill a finalized RWSet") + } + if stat != nil { + if stat.txIndex != index { + return errors.New("wrong execution stat") + } + s.stats[index] = stat + } + + //if metrics.EnabledExpensive { + // for k := range rwSet.writeSet { + // // this action is only for testing, it runs when enable expensive metrics. + // checkRWSetInconsistent(index, k, rwSet.readSet, rwSet.writeSet) + // } + //} + s.rwSets[index] = rwSet + return nil +} + +// Finalise it will put target write set into pending writes. +func (s *MVStates) Finalise(index int) error { + log.Debug("Finalise", "total", len(s.rwSets), "index", index) + s.lock.Lock() + + rwSet := s.rwSets[index] + if rwSet == nil { + s.lock.Unlock() + return fmt.Errorf("finalise a non-exist RWSet, index: %d", index) + } + + if index != s.nextFinaliseIndex { + s.lock.Unlock() + return fmt.Errorf("finalise in wrong order, next: %d, input: %d", s.nextFinaliseIndex, index) + } + + // append to pending write set + for k, v := range rwSet.writeSet { + if _, exist := s.pendingWriteSet[k]; !exist { + s.pendingWriteSet[k] = NewPendingWrites() + } + s.pendingWriteSet[k].Append(v) + } + s.nextFinaliseIndex++ + s.lock.Unlock() + // async resolve dependency, but non-block action + if s.asyncRunning && s.depsGenChan != nil { + s.depsGenChan <- index + } + return nil +} + +func (s *MVStates) resolveDepsCacheByWrites(index int, rwSet *RWSet) { + // analysis dep, if the previous transaction is not executed/validated, re-analysis is required + s.depMapCache[index] = NewTxDeps(8) + if rwSet.excludedTx { + return + } + seen := make(map[int]struct{}, 8) + // check tx dependency, only check key, skip version + if len(s.pendingWriteSet) > len(rwSet.readSet) { + for key := range rwSet.readSet { + // check self destruct + if key.IsAccountSelf() { + key = AccountStateKey(key.Addr(), AccountSuicide) + } + writes := s.pendingWriteSet[key] + if writes == nil { + continue + } + items := writes.FindPrevWrites(index) + for _, item := range items { + seen[item.TxIndex()] = struct{}{} + } + } + } else { + for k, w := range s.pendingWriteSet { + // check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios. + if k.IsAccountSuicide() { + k = k.ToAccountSelf() + } + if _, ok := rwSet.readSet[k]; !ok { + continue + } + items := w.FindPrevWrites(index) + for _, item := range items { + seen[item.TxIndex()] = struct{}{} + } + } + } + for prev := 0; prev < index; prev++ { + if _, ok := seen[prev]; !ok { + continue + } + s.depMapCache[index].add(prev) + // clear redundancy deps compared with prev + for dep := range s.depMapCache[index] { + if s.depMapCache[prev].exist(dep) { + s.depMapCache[index].remove(dep) + } + } + } + s.depsCache[index] = s.depMapCache[index].toArray() +} + +func (s *MVStates) resolveDepsCache(index int, rwSet *RWSet) { + // analysis dep, if the previous transaction is not executed/validated, re-analysis is required + s.depMapCache[index] = NewTxDeps(0) + if rwSet.excludedTx { + return + } + for prev := 0; prev < index; prev++ { + // if there are some parallel execution or system txs, it will fulfill in advance + // it's ok, and try re-generate later + prevSet, ok := s.rwSets[prev] + if !ok { + continue + } + // if prev tx is tagged ExcludedTxFlag, just skip the check + if prevSet.excludedTx { + continue + } + // check if there has written op before i + if checkDependency(prevSet.writeSet, rwSet.readSet) { + s.depMapCache[index].add(prev) + // clear redundancy deps compared with prev + for dep := range s.depMapCache[index] { + if s.depMapCache[prev].exist(dep) { + s.depMapCache[index].remove(dep) + } + } + } + } + s.depsCache[index] = s.depMapCache[index].toArray() +} + +func checkRWSetInconsistent(index int, k RWKey, readSet map[RWKey]*RWItem, writeSet map[RWKey]*RWItem) bool { + var ( + readOk bool + writeOk bool + r *RWItem + ) + + if k.IsAccountSuicide() { + _, readOk = readSet[k.ToAccountSelf()] + } else { + _, readOk = readSet[k] + } + + r, writeOk = writeSet[k] + if readOk != writeOk { + // check if it's correct? read nil, write non-nil + log.Warn("checkRWSetInconsistent find inconsistent", "tx", index, "k", k.String(), "read", readOk, "write", writeOk, "val", r.Val) + return true + } + + return false +} + +// ResolveTxDAG generate TxDAG from RWSets +func (s *MVStates) ResolveTxDAG(txCnt int, gasFeeReceivers []common.Address) (TxDAG, error) { + s.lock.Lock() + defer s.lock.Unlock() + if len(s.rwSets) != txCnt { + return nil, fmt.Errorf("wrong rwSet count, expect: %v, actual: %v", txCnt, len(s.rwSets)) + } + if txCnt != s.nextFinaliseIndex { + return nil, fmt.Errorf("resolve in wrong order, next: %d, input: %d", s.nextFinaliseIndex, txCnt) + } + s.stopAsyncDepGen() + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := 0; i < txCnt; i++ { + // check if there are RW with gas fee receiver for gas delay calculation + for _, addr := range gasFeeReceivers { + if _, ok := s.rwSets[i].readSet[AccountStateKey(addr, AccountSelf)]; ok { + return NewEmptyTxDAG(), nil + } + } + txDAG.TxDeps[i].TxIndexes = []uint64{} + if s.rwSets[i].excludedTx { + txDAG.TxDeps[i].SetFlag(ExcludedTxFlag) + continue + } + if s.depMapCache[i] == nil { + s.resolveDepsCacheByWrites(i, s.rwSets[i]) + } + deps := s.depsCache[i] + if len(deps) <= (txCnt-1)/2 { + txDAG.TxDeps[i].TxIndexes = deps + continue + } + // if tx deps larger than half of txs, then convert with NonDependentRelFlag + txDAG.TxDeps[i].SetFlag(NonDependentRelFlag) + for j := uint64(0); j < uint64(txCnt); j++ { + if !slices.Contains(deps, j) && j != uint64(i) { + txDAG.TxDeps[i].TxIndexes = append(txDAG.TxDeps[i].TxIndexes, j) + } + } + } + + return txDAG, nil +} + +func checkDependency(writeSet map[RWKey]*RWItem, readSet map[RWKey]*RWItem) bool { + // check tx dependency, only check key, skip version + for k, _ := range writeSet { + // check suicide, add read address flag, it only for check suicide quickly, and cannot for other scenarios. + if k.IsAccountSuicide() { + if _, ok := readSet[k.ToAccountSelf()]; ok { + return true + } + continue + } + if _, ok := readSet[k]; ok { + return true + } + } + + return false +} + +type TxDepMap map[int]struct{} + +func NewTxDeps(cap int) TxDepMap { + return make(map[int]struct{}, cap) +} + +func (m TxDepMap) add(index int) { + m[index] = struct{}{} +} + +func (m TxDepMap) exist(index int) bool { + _, ok := m[index] + return ok +} + +func (m TxDepMap) toArray() []uint64 { + ret := make([]uint64, 0, len(m)) + for index := range m { + ret = append(ret, uint64(index)) + } + slices.Sort(ret) + return ret +} + +func (m TxDepMap) remove(index int) { + delete(m, index) +} diff --git a/core/types/mvstates_test.go b/core/types/mvstates_test.go new file mode 100644 index 0000000000..7a0e16db8c --- /dev/null +++ b/core/types/mvstates_test.go @@ -0,0 +1,459 @@ +package types + +import ( + "bytes" + "compress/gzip" + "fmt" + "testing" + "time" + + "github.com/cometbft/cometbft/libs/rand" + "github.com/golang/snappy" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" +) + +const mockRWSetSize = 5000 + +func TestMVStates_BasicUsage(t *testing.T) { + ms := NewMVStates(0) + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(0, []interface{}{"0x00", 0}, []interface{}{"0x00", 0}), nil)) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.NoError(t, ms.Finalise(0)) + require.Error(t, ms.Finalise(0)) + require.Error(t, ms.FulfillRWSet(mockRWSetWithVal(0, nil, nil), nil)) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 0}, 0), ms.ReadState(1, str2key("0x00"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(1, []interface{}{"0x01", 1}, []interface{}{"0x01", 1}), nil)) + require.Nil(t, ms.ReadState(1, str2key("0x01"))) + require.NoError(t, ms.Finalise(1)) + require.Nil(t, ms.ReadState(0, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(2, []interface{}{"0x02", 2, "0x01", 1}, []interface{}{"0x01", 2, "0x02", 2}), nil)) + require.NoError(t, ms.Finalise(2)) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadState(3, str2key("0x01"))) + + require.NoError(t, ms.FulfillRWSet(mockRWSetWithVal(3, []interface{}{"0x03", 3, "0x00", 0, "0x01", 2}, []interface{}{"0x00", 3, "0x01", 3, "0x03", 3}), nil)) + require.Nil(t, ms.ReadState(3, str2key("0x03"))) + require.NoError(t, ms.Finalise(3)) + require.Nil(t, ms.ReadState(0, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 1}, 1), ms.ReadState(2, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 2}, 2), ms.ReadState(3, str2key("0x01"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadState(4, str2key("0x01"))) + require.Nil(t, ms.ReadState(0, str2key("0x00"))) + require.Equal(t, NewRWItem(StateVersion{TxIndex: 3}, 3), ms.ReadState(5, str2key("0x00"))) +} + +func TestMVStates_SimpleResolveTxDAG(t *testing.T) { + ms := NewMVStates(10) + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + }) + + dag, err := ms.ResolveTxDAG(10, nil) + require.NoError(t, err) + require.Equal(t, mockSimpleDAG(), dag) + t.Log(dag) +} + +func TestMVStates_AsyncDepGen_SimpleResolveTxDAG(t *testing.T) { + ms := NewMVStates(10).EnableAsyncDepGen() + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + }) + time.Sleep(10 * time.Millisecond) + + dag, err := ms.ResolveTxDAG(10, nil) + require.NoError(t, err) + time.Sleep(100 * time.Millisecond) + require.NoError(t, ms.Stop()) + require.Equal(t, mockSimpleDAG(), dag) + t.Log(dag) +} + +func TestMVStates_ResolveTxDAG_Async(t *testing.T) { + txCnt := 10000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt).EnableAsyncDepGen() + for i := 0; i < txCnt; i++ { + require.NoError(t, ms1.FulfillRWSet(rwSets[i], nil)) + require.NoError(t, ms1.Finalise(i)) + } + time.Sleep(100 * time.Millisecond) + _, err := ms1.ResolveTxDAG(txCnt, nil) + require.NoError(t, err) +} + +func TestMVStates_ResolveTxDAG_Compare(t *testing.T) { + txCnt := 3000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt) + ms2 := NewMVStates(txCnt) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms2.rwSets[i] = rwSet + require.NoError(t, ms2.Finalise(i)) + } + + d1 := resolveTxDAGInMVStates(ms1) + d2 := resolveTxDAGByWritesInMVStates(ms2) + require.Equal(t, d1.(*PlainTxDAG).String(), d2.(*PlainTxDAG).String()) +} + +func TestMVStates_TxDAG_Compression(t *testing.T) { + txCnt := 10000 + rwSets := mockRandomRWSet(txCnt) + ms1 := NewMVStates(txCnt) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms1.Finalise(i) + } + dag := resolveTxDAGByWritesInMVStates(ms1) + enc, err := EncodeTxDAG(dag) + require.NoError(t, err) + + // snappy compression + start := time.Now() + encoded := snappy.Encode(nil, enc) + t.Log("snappy", "enc", len(enc), "compressed", len(encoded), + "ratio", 1-(float64(len(encoded))/float64(len(enc))), + "time", float64(time.Since(start).Microseconds())/1000) + + // gzip compression + start = time.Now() + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + _, err = zw.Write(enc) + require.NoError(t, err) + err = zw.Close() + require.NoError(t, err) + encoded = buf.Bytes() + t.Log("gzip", "enc", len(enc), "compressed", len(encoded), + "ratio", 1-(float64(len(encoded))/float64(len(enc))), + "time", float64(time.Since(start).Microseconds())/1000) +} + +func BenchmarkResolveTxDAGInMVStates(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + } + for i := 0; i < b.N; i++ { + resolveTxDAGInMVStates(ms1) + } +} + +func BenchmarkResolveTxDAGByWritesInMVStates(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i, rwSet := range rwSets { + ms1.rwSets[i] = rwSet + ms1.Finalise(i) + } + for i := 0; i < b.N; i++ { + resolveTxDAGByWritesInMVStates(ms1) + } +} + +func BenchmarkMVStates_Finalise(b *testing.B) { + rwSets := mockRandomRWSet(mockRWSetSize) + ms1 := NewMVStates(mockRWSetSize) + for i := 0; i < b.N; i++ { + for k, rwSet := range rwSets { + ms1.rwSets[k] = rwSet + ms1.Finalise(k) + } + } +} + +func resolveTxDAGInMVStates(s *MVStates) TxDAG { + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := 0; i < len(s.rwSets); i++ { + s.resolveDepsCache(i, s.rwSets[i]) + txDAG.TxDeps[i].TxIndexes = s.depsCache[i] + } + return txDAG +} + +func resolveTxDAGByWritesInMVStates(s *MVStates) TxDAG { + txDAG := NewPlainTxDAG(len(s.rwSets)) + for i := 0; i < len(s.rwSets); i++ { + s.resolveDepsCacheByWrites(i, s.rwSets[i]) + txDAG.TxDeps[i].TxIndexes = s.depsCache[i] + } + return txDAG +} + +func TestMVStates_SystemTxResolveTxDAG(t *testing.T) { + ms := NewMVStates(12) + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x06", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x08", "0x09"}, []string{"0x09"}), + mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag(), + mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag(), + }) + + dag, err := ms.ResolveTxDAG(12, nil) + require.NoError(t, err) + require.Equal(t, mockSystemTxDAG(), dag) + t.Log(dag) +} + +func TestMVStates_SystemTxWithLargeDepsResolveTxDAG(t *testing.T) { + ms := NewMVStates(12) + finaliseRWSets(t, ms, []*RWSet{ + mockRWSet(0, []string{"0x00"}, []string{"0x00"}), + mockRWSet(1, []string{"0x01"}, []string{"0x01"}), + mockRWSet(2, []string{"0x02"}, []string{"0x02"}), + mockRWSet(3, []string{"0x00", "0x03"}, []string{"0x03"}), + mockRWSet(4, []string{"0x00", "0x04"}, []string{"0x04"}), + mockRWSet(5, []string{"0x01", "0x02", "0x05"}, []string{"0x05"}), + mockRWSet(6, []string{"0x02", "0x05", "0x06"}, []string{"0x06"}), + mockRWSet(7, []string{"0x00", "0x03", "0x07"}, []string{"0x07"}), + mockRWSet(8, []string{"0x08"}, []string{"0x08"}), + mockRWSet(9, []string{"0x00", "0x01", "0x02", "0x06", "0x07", "0x08", "0x09"}, []string{"0x09"}), + mockRWSet(10, []string{"0x10"}, []string{"0x10"}).WithExcludedTxFlag(), + mockRWSet(11, []string{"0x11"}, []string{"0x11"}).WithExcludedTxFlag(), + }) + dag, err := ms.ResolveTxDAG(12, nil) + require.NoError(t, err) + require.Equal(t, mockSystemTxDAGWithLargeDeps(), dag) + t.Log(dag) +} + +func TestIsEqualRWVal(t *testing.T) { + tests := []struct { + key RWKey + src interface{} + compared interface{} + isEqual bool + }{ + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(0), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountNonce), + src: uint64(0), + compared: uint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: new(uint256.Int).SetUint64(1), + compared: new(uint256.Int).SetUint64(1), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: nil, + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: new(uint256.Int).SetUint64(1), + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountBalance), + src: (*uint256.Int)(nil), + compared: (*uint256.Int)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: []byte{1}, + compared: []byte{1}, + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: nil, + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: []byte{1}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountCodeHash), + src: ([]byte)(nil), + compared: ([]byte)(nil), + isEqual: true, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: struct{}{}, + compared: struct{}{}, + isEqual: false, + }, + { + key: AccountStateKey(mockAddr, AccountSuicide), + src: nil, + compared: struct{}{}, + isEqual: false, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: mockHash, + compared: mockHash, + isEqual: true, + }, + { + key: StorageStateKey(mockAddr, mockHash), + src: nil, + compared: mockHash, + isEqual: false, + }, + } + + for i, item := range tests { + require.Equal(t, item.isEqual, isEqualRWVal(item.key, item.src, item.compared), i) + } +} + +func mockRWSet(index int, read []string, write []string) *RWSet { + ver := StateVersion{ + TxIndex: index, + } + set := NewRWSet(ver) + for _, k := range read { + set.readSet[str2key(k)] = &RWItem{ + Ver: ver, + Val: struct{}{}, + } + } + for _, k := range write { + set.writeSet[str2key(k)] = &RWItem{ + Ver: ver, + Val: struct{}{}, + } + } + + return set +} + +func mockRWSetWithVal(index int, read []interface{}, write []interface{}) *RWSet { + ver := StateVersion{ + TxIndex: index, + } + set := NewRWSet(ver) + + if len(read)%2 != 0 { + panic("wrong read size") + } + if len(write)%2 != 0 { + panic("wrong write size") + } + + for i := 0; i < len(read); { + set.readSet[str2key(read[i].(string))] = &RWItem{ + Ver: StateVersion{ + TxIndex: index - 1, + }, + Val: read[i+1], + } + i += 2 + } + for i := 0; i < len(write); { + set.writeSet[str2key(write[i].(string))] = &RWItem{ + Ver: ver, + Val: write[i+1], + } + i += 2 + } + + return set +} + +func mockRandomRWSet(count int) []*RWSet { + var ret []*RWSet + for i := 0; i < count; i++ { + read := []string{fmt.Sprintf("0x%d", i)} + write := []string{fmt.Sprintf("0x%d", i)} + if i != 0 && rand.Bool() { + depCnt := rand.Int()%i + 1 + last := 0 + for j := 0; j < depCnt; j++ { + num, ok := randInRange(last, i) + if !ok { + break + } + read = append(read, fmt.Sprintf("0x%d", num)) + last = num + } + } + // random read + for j := 0; j < 20; j++ { + read = append(read, fmt.Sprintf("rr-%d-%d", j, rand.Int())) + } + for j := 0; j < 5; j++ { + read = append(read, fmt.Sprintf("rw-%d-%d", j, rand.Int())) + } + // random write + s := mockRWSet(i, read, write) + ret = append(ret, s) + } + return ret +} + +func finaliseRWSets(t *testing.T, mv *MVStates, rwSets []*RWSet) { + for i, rwSet := range rwSets { + require.NoError(t, mv.FulfillRWSet(rwSet, nil)) + require.NoError(t, mv.Finalise(i)) + } +} + +func randInRange(i, j int) (int, bool) { + if i >= j { + return 0, false + } + return rand.Int()%(j-i) + i, true +} + +func str2key(k string) RWKey { + key := RWKey{} + if len(k) > len(key) { + k = k[:len(key)] + } + copy(key[:], k) + return key +} diff --git a/core/types/receipt.go b/core/types/receipt.go index 67c1addb3d..8cb2bbdad8 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -18,6 +18,7 @@ package types import ( "bytes" + "encoding/json" "errors" "fmt" "io" @@ -605,3 +606,11 @@ func u32ptrTou64ptr(a *uint32) *uint64 { b := uint64(*a) return &b } + +// Debug PrettyPrint +func (r Receipt) PrettyPrint() (string, error) { + b, err := r.MarshalJSON() + var prettyJSON bytes.Buffer + json.Indent(&prettyJSON, b, "", "\t") + return prettyJSON.String(), err +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 43ab27308b..82d0c19b8b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -17,10 +17,11 @@ package vm import ( - "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" "math/big" "sync/atomic" + "github.com/ethereum/go-ethereum/core/opcodeCompiler/compiler" + "github.com/holiman/uint256" "github.com/ethereum/go-ethereum/common" diff --git a/core/vm/interface.go b/core/vm/interface.go index 25bfa06720..537e300b97 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -79,6 +79,11 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) + TxIndex() int + + // parallel DAG related + BeforeTxTransition() + FinaliseRWSet() error } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 80acdcc013..e204c36b0a 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -19,6 +19,7 @@ package vm import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -29,12 +30,16 @@ type PrecompileOverrides func(params.Rules, PrecompiledContract, common.Address) // Config are the configuration options for the Interpreter type Config struct { - Tracer EVMLogger // Opcode logger - NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) - EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages - ExtraEips []int // Additional EIPS that are to be enabled - OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism - EnableOpcodeOptimizations bool // Enable opcode optimization + Tracer EVMLogger // Opcode logger + NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) + EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages + ExtraEips []int // Additional EIPS that are to be enabled + EnableParallelExec bool // Whether to execute transaction in parallel mode when do full sync + ParallelTxNum int // Number of slot for transaction execution + OptimismPrecompileOverrides PrecompileOverrides // Precompile overrides for Optimism + EnableOpcodeOptimizations bool // Enable opcode optimization + TxDAG types.TxDAG + EnableParallelUnorderedMerge bool // Whether to enable unordered merge in parallel mode } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/backend.go b/eth/backend.go index b690938e87..3b7e77c39c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -21,12 +21,13 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "math/big" "runtime" "sync" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -222,8 +223,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { journalFilePath := fmt.Sprintf("%s/%s", stack.ResolvePath(ChainData), JournalFileName) var ( vmConfig = vm.Config{ - EnablePreimageRecording: config.EnablePreimageRecording, - EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, + EnablePreimageRecording: config.EnablePreimageRecording, + EnableParallelExec: config.ParallelTxMode, + EnableParallelUnorderedMerge: config.ParallelTxUnorderedMerge, + ParallelTxNum: config.ParallelTxNum, + EnableOpcodeOptimizations: config.EnableOpcodeOptimizing, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, @@ -270,6 +274,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + if config.EnableParallelTxDAG { + if config.ParallelTxMode { + eth.blockchain.SetupTxDAGGeneration(config.ParallelTxDAGFile, config.ParallelTxMode) + } + } if chainConfig := eth.blockchain.Config(); chainConfig.Optimism != nil { // config.Genesis.Config.ChainID cannot be used because it's based on CLI flags only, thus default to mainnet L1 config.NetworkId = chainConfig.ChainID.Uint64() // optimism defaults eth network ID to chain ID eth.networkID = config.NetworkId diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 383641ffc3..181a3ac383 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -19,9 +19,10 @@ package ethconfig import ( "errors" - "github.com/ethereum/go-ethereum/core/txpool/bundlepool" "time" + "github.com/ethereum/go-ethereum/core/txpool/bundlepool" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/beacon" @@ -218,7 +219,13 @@ type Config struct { RollupDisableTxPoolAdmission bool RollupHaltOnIncompatibleProtocolVersion string - EnableOpcodeOptimizing bool + ParallelTxMode bool // Whether to execute transaction in parallel mode when do full sync + ParallelTxNum int // Number of slot for transaction execution + EnableOpcodeOptimizing bool + EnableParallelTxDAG bool + ParallelTxDAGFile string + ParallelTxUnorderedMerge bool // Whether to enable unordered merge in parallel mode + } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/go.mod b/go.mod index 73768e2685..2cc116668f 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/olekukonko/tablewriter v0.0.5 github.com/panjf2000/ants/v2 v2.4.5 github.com/peterh/liner v1.2.0 + github.com/prometheus/client_golang v1.14.0 github.com/protolambda/bls12-381-util v0.0.0-20220416220906-d8552aa452c7 github.com/prysmaticlabs/prysm/v4 v4.2.0 github.com/rs/cors v1.8.3 @@ -148,7 +149,6 @@ require ( github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.14.0 // indirect github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.9.0 // indirect diff --git a/miner/miner.go b/miner/miner.go index b65b226238..08c60c186e 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -19,14 +19,16 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" - "github.com/ethereum/go-ethereum/consensus/misc/eip1559" - "github.com/ethereum/go-ethereum/consensus/misc/eip4844" "math/big" "sync" "time" + "github.com/ethereum/go-ethereum/consensus/misc/eip1559" + "github.com/ethereum/go-ethereum/consensus/misc/eip4844" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" @@ -62,7 +64,8 @@ var ( snapshotAccountReadTimer = metrics.NewRegisteredTimer("miner/snapshot/account/reads", nil) snapshotStorageReadTimer = metrics.NewRegisteredTimer("miner/snapshot/storage/reads", nil) - waitPayloadTimer = metrics.NewRegisteredTimer("miner/wait/payload", nil) + waitPayloadTimer = metrics.NewRegisteredTimer("miner/wait/payload", nil) + txDAGGenerateTimer = metrics.NewRegisteredTimer("miner/txdag/gen", nil) isBuildBlockInterruptCounter = metrics.NewRegisteredCounter("miner/build/interrupt", nil) ) @@ -107,6 +110,8 @@ type Config struct { EffectiveGasCeil uint64 // if non-zero, a gas ceiling to apply independent of the header's gaslimit value Mev MevConfig // Mev configuration + + ParallelTxDAGSenderPriv *ecdsa.PrivateKey // The private key for the parallel tx DAG sender } // DefaultConfig contains default settings for miner. diff --git a/miner/worker.go b/miner/worker.go index c3ff52d5c0..e21509caba 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -18,6 +18,7 @@ package miner import ( "context" + "crypto/ecdsa" "errors" "fmt" "math/big" @@ -37,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth/tracers" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/log" @@ -100,6 +102,10 @@ var ( txErrReplayMeter = metrics.NewRegisteredMeter("miner/tx/replay", nil) ) +var ( + DefaultTxDAGAddress = common.HexToAddress("0xda90000000000000000000000000000000000000") +) + // environment is the worker's current environment and holds all // information of the sealing block generation. type environment struct { @@ -1026,6 +1032,60 @@ func (w *worker) commitTransactions(env *environment, plainTxs, blobTxs *transac return nil } +// generateDAGTx generates a DAG transaction for the block +func (w *worker) generateDAGTx(env *environment) error { + // get txDAG data from the stateDB + txDAG, err := env.state.ResolveTxDAG(env.tcount, []common.Address{env.coinbase, params.OptimismBaseFeeRecipient, params.OptimismL1FeeRecipient}) + if txDAG == nil || err != nil { + return err + } + // txIndex is the index of this txDAG transaction + txDAG.SetTxDep(env.tcount, types.TxDep{Flags: &types.NonDependentRelFlag}) + + if env.signer == nil { + return fmt.Errorf("current signer is nil") + } + + //privateKey, err := crypto.HexToECDSA(privateKeyHex) + sender := w.config.ParallelTxDAGSenderPriv + receiver := DefaultTxDAGAddress + if sender == nil { + return fmt.Errorf("missing sender private key") + } + + publicKey := sender.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("error casting public key to ECDSA") + } + fromAddress := crypto.PubkeyToAddress(*publicKeyECDSA) + + // get nonce from the + nonce := env.state.GetNonce(fromAddress) + + data, err := types.EncodeTxDAGCalldata(txDAG) + if err != nil { + return fmt.Errorf("failed to encode txDAG, err: %v", err) + } + + // Create the transaction + tx := types.NewTransaction(nonce, receiver, big.NewInt(0), 21100, big.NewInt(0), data) + + // Sign the transaction with the private key + signedTx, err := types.SignTx(tx, env.signer, sender) + if err != nil { + return fmt.Errorf("failed to sign transaction, err: %v", err) + } + + _, err = w.commitTransaction(env, signedTx) + if err != nil { + log.Warn("failed to commit DAG tx", "err", err) + return err + } + env.tcount++ + return nil +} + // generateParams wraps various of settings for generating sealing task. type generateParams struct { timestamp uint64 // The timestamp for sealing task @@ -1180,6 +1240,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err w.mu.RUnlock() start := time.Now() + // Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees filter := txpool.PendingFilter{ MinTip: tip, @@ -1264,6 +1325,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { misc.EnsureCreate2Deployer(w.chainConfig, work.header.Time, work.state) start := time.Now() + if w.chain.TxDAGEnabledWhenMine() { + work.state.ResetMVStates(0) + } for _, tx := range genParams.txs { from, _ := types.Sender(work.signer, tx) work.state.SetTxContext(tx.Hash(), work.tcount) @@ -1271,6 +1335,9 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { if err != nil { return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)} } + if tx.IsSystemTx() || tx.IsDepositTx() { + work.state.RecordSystemTxRWSet(work.tcount) + } work.tcount++ } commitDepositTxsTimer.UpdateSince(start) @@ -1324,6 +1391,12 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { if intr := genParams.interrupt; intr != nil && genParams.isUpdate && intr.Load() != commitInterruptNone { return &newPayloadResult{err: errInterruptedUpdate} } + //append the tx DAG transaction to the block + if w.chain.TxDAGEnabledWhenMine() { + if err := w.generateDAGTx(work); err != nil { + log.Warn("failed to generate DAG tx", "err", err) + } + } start = time.Now() block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, nil, work.receipts, genParams.withdrawals) @@ -1345,6 +1418,7 @@ func (w *worker) generateWork(genParams *generateParams) *newPayloadResult { storageUpdateTimer.Update(work.state.StorageUpdates) // Storage updates are complete(in FinalizeAndAssemble) accountHashTimer.Update(work.state.AccountHashes) // Account hashes are complete(in FinalizeAndAssemble) storageHashTimer.Update(work.state.StorageHashes) // Storage hashes are complete(in FinalizeAndAssemble) + txDAGGenerateTimer.Update(work.state.TxDAGGenerate) innerExecutionTimer.Update(core.DebugInnerExecutionDuration) diff --git a/tests/block_test.go b/tests/block_test.go index fb355085fd..ae0290862a 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -17,14 +17,61 @@ package tests import ( + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "math/rand" + "os" + "path/filepath" "runtime" + "sync/atomic" "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" ) +func TestBlockchainWithTxDAG(t *testing.T) { + //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) + bt := new(testMatcher) + // General state tests are 'exported' as blockchain tests, but we can run them natively. + // For speedier CI-runs, the line below can be uncommented, so those are skipped. + // For now, in hardfork-times (Berlin), we run the tests both as StateTests and + // as blockchain tests, since the latter also covers things like receipt root + bt.skipLoad(`^GeneralStateTests/`) + + // Skip random failures due to selfish mining test + bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) + + // Slow tests + bt.slow(`.*bcExploitTest/DelegateCallSpam.json`) + bt.slow(`.*bcExploitTest/ShanghaiLove.json`) + bt.slow(`.*bcExploitTest/SuicideIssue.json`) + bt.slow(`.*/bcForkStressTest/`) + bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`) + bt.slow(`.*/bcWalletTest/`) + + // Very slow test + bt.skipLoad(`.*/stTimeConsuming/.*`) + // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, + // using 4.6 TGas + bt.skipLoad(`.*randomStatetest94.json.*`) + + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + t.Skip("test (randomly) skipped on 32-bit windows") + } + execBlockTestWithTxDAG(t, bt, test) + }) + //bt := new(testMatcher) + //path := filepath.Join(blockTestDir, "ValidBlocks", "bcEIP1559", "intrinsic.json") + //_, name := filepath.Split(path) + //t.Run(name, func(t *testing.T) { + // bt.runTestFile(t, path, name, func(t *testing.T, name string, test *BlockTest) { + // if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { + // t.Skip("test (randomly) skipped on 32-bit windows") + // } + // execBlockTestWithTxDAG(t, bt, test) + // }) + //}) +} func TestBlockchain(t *testing.T) { bt := new(testMatcher) // General state tests are 'exported' as blockchain tests, but we can run them natively. @@ -73,21 +120,41 @@ func TestExecutionSpecBlocktests(t *testing.T) { }) } +var txDAGFileCounter atomic.Uint64 + +func execBlockTestWithTxDAG(t *testing.T, bt *testMatcher, test *BlockTest) { + txDAGFile := filepath.Join(os.TempDir(), fmt.Sprintf("test_txdag_%v.csv", txDAGFileCounter.Add(1))) + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, false)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + // run again with dagFile + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, txDAGFile, true)); err != nil { + t.Errorf("test in path mode with snapshotter failed: %v", err) + return + } + + // clean + os.Remove(txDAGFile) +} + func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { - if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil, "", true)); err != nil { t.Errorf("test in hash mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil, "", true)); err != nil { t.Errorf("test in hash mode with snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil, "", true)); err != nil { t.Errorf("test in path mode without snapshotter failed: %v", err) return } - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil, "", true)); err != nil { t.Errorf("test in path mode with snapshotter failed: %v", err) return } + } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 5f77a1c326..643f719c35 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -109,7 +109,7 @@ type btHeaderMarshaling struct { ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain)) (result error) { +func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, postCheck func(error, *core.BlockChain), dagFile string, enableParallel bool) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -151,13 +151,17 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, tracer vm.EVMLogger, po cache.SnapshotWait = true } chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ - Tracer: tracer, + EnableParallelExec: enableParallel, + ParallelTxNum: 4, + Tracer: tracer, }, nil, nil) if err != nil { return err } defer chain.Stop() - + if len(dagFile) > 0 { + chain.SetupTxDAGGeneration(dagFile, enableParallel) + } validBlocks, err := t.insertBlocks(chain) if err != nil { return err