From 3f8e9f5c02dd2d455f3bff25272f565b93ba0495 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 2 Mar 2026 23:13:16 -0500 Subject: [PATCH] all: implement eip 7928 block access lists --- beacon/engine/gen_ed.go | 79 +-- beacon/engine/types.go | 95 +-- build/checksums.txt | 5 + build/ci.go | 17 + cmd/evm/blockrunner.go | 2 +- cmd/geth/config.go | 16 +- cmd/geth/main.go | 1 + cmd/utils/flags.go | 25 + consensus/beacon/consensus.go | 34 +- consensus/clique/clique.go | 9 +- consensus/consensus.go | 5 +- consensus/ethash/consensus.go | 9 +- consensus/misc/eip4844/eip4844.go | 2 + core/block_validator.go | 32 +- core/blockchain.go | 244 +++++++- core/blockchain_stats.go | 81 ++- core/blockchain_test.go | 2 +- core/chain_makers.go | 8 +- core/gen_genesis.go | 70 ++- core/genesis.go | 41 +- core/parallel_state_processor.go | 397 +++++++++++++ core/rawdb/accessors_chain.go | 46 +- core/rawdb/schema.go | 6 + core/state/bal_state_transition.go | 514 ++++++++++++++++ core/state/database.go | 38 +- core/state/iterator.go | 2 +- core/state/journal.go | 2 +- core/state/reader.go | 151 ++--- core/state/reader_eip_7928.go | 334 +++++++++++ core/state/reader_eip_7928_test.go | 201 +++++++ core/state/reader_stater.go | 82 +++ core/state/state_object.go | 109 +++- core/state/statedb.go | 211 +++++-- core/state/statedb_hooked.go | 66 ++- core/state_processor.go | 109 ++-- core/state_transition.go | 23 +- core/stateless.go | 4 + core/tracing/hooks.go | 222 ++++--- core/tracing/journal.go | 30 +- core/tracing/journal_test.go | 9 +- core/types.go | 13 +- core/types/bal/bal.go | 586 +++++++++++++++---- core/types/bal/bal.rlp.hex | 1 + core/types/bal/bal_encoding.go | 422 +++++++++---- core/types/bal/bal_encoding_json.go | 107 ++++ core/types/bal/bal_encoding_rlp_generated.go | 395 ++++++------- core/types/bal/bal_test.go | 71 ++- core/types/bal_blocks_test.go | 32 + core/types/block.go | 55 +- core/types/gen_header_json.go | 96 +-- core/types/gen_header_rlp.go | 24 +- core/vm/evm.go | 32 +- core/vm/gas_table.go | 115 +++- core/vm/instructions.go | 3 - core/vm/interface.go | 3 +- core/vm/jump_table_export.go | 2 + core/vm/operations_acl.go | 185 +++--- eth/api_backend.go | 20 + eth/backend.go | 1 + eth/catalyst/api.go | 15 +- eth/ethconfig/config.go | 2 + eth/ethconfig/gen_config.go | 6 + eth/tracers/api.go | 2 +- internal/ethapi/api.go | 15 + internal/ethapi/backend.go | 1 + internal/ethapi/simulate.go | 6 +- internal/web3ext/web3ext.go | 17 +- miner/worker.go | 129 +++- params/config.go | 15 +- rlp/decode.go | 2 + tests/block_test.go | 94 ++- tests/block_test_util.go | 133 +++-- tests/gen_btheader.go | 6 + tests/init.go | 64 ++ tests/init_test.go | 23 +- trie/bintrie/trie.go | 8 + trie/secure_trie.go | 46 ++ trie/tracer.go | 19 +- trie/transitiontrie/transition.go | 8 + trie/trie.go | 66 +++ trie/trie_test.go | 114 ++-- triedb/database.go | 1 - 82 files changed, 4958 insertions(+), 1330 deletions(-) create mode 100644 core/parallel_state_processor.go create mode 100644 core/state/bal_state_transition.go create mode 100644 core/state/reader_eip_7928.go create mode 100644 core/state/reader_eip_7928_test.go create mode 100644 core/state/reader_stater.go create mode 100644 core/types/bal/bal.rlp.hex create mode 100644 core/types/bal/bal_encoding_json.go create mode 100644 core/types/bal_blocks_test.go diff --git a/beacon/engine/gen_ed.go b/beacon/engine/gen_ed.go index b460368b84..c882fe6bb0 100644 --- a/beacon/engine/gen_ed.go +++ b/beacon/engine/gen_ed.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" ) var _ = (*executableDataMarshaling)(nil) @@ -17,24 +18,25 @@ var _ = (*executableDataMarshaling)(nil) // MarshalJSON marshals as JSON. func (e ExecutableData) MarshalJSON() ([]byte, error) { type ExecutableData struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - SlotNumber *hexutil.Uint64 `json:"slotNumber"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + SlotNumber *hexutil.Uint64 `json:"slotNumber"` } var enc ExecutableData enc.ParentHash = e.ParentHash @@ -59,6 +61,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { enc.Withdrawals = e.Withdrawals enc.BlobGasUsed = (*hexutil.Uint64)(e.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(e.ExcessBlobGas) + enc.BlockAccessList = e.BlockAccessList enc.SlotNumber = (*hexutil.Uint64)(e.SlotNumber) return json.Marshal(&enc) } @@ -66,24 +69,25 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (e *ExecutableData) UnmarshalJSON(input []byte) error { type ExecutableData struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` - Random *common.Hash `json:"prevRandao" gencodec:"required"` - Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` - BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` - BlockHash *common.Hash `json:"blockHash" gencodec:"required"` - Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` - SlotNumber *hexutil.Uint64 `json:"slotNumber"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient *common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot *common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot *common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom *hexutil.Bytes `json:"logsBloom" gencodec:"required"` + Random *common.Hash `json:"prevRandao" gencodec:"required"` + Number *hexutil.Uint64 `json:"blockNumber" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Timestamp *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + ExtraData *hexutil.Bytes `json:"extraData" gencodec:"required"` + BaseFeePerGas *hexutil.Big `json:"baseFeePerGas" gencodec:"required"` + BlockHash *common.Hash `json:"blockHash" gencodec:"required"` + Transactions []hexutil.Bytes `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` + BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + SlotNumber *hexutil.Uint64 `json:"slotNumber"` } var dec ExecutableData if err := json.Unmarshal(input, &dec); err != nil { @@ -157,6 +161,9 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error { if dec.ExcessBlobGas != nil { e.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) } + if dec.BlockAccessList != nil { + e.BlockAccessList = dec.BlockAccessList + } if dec.SlotNumber != nil { e.SlotNumber = (*uint64)(dec.SlotNumber) } diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 5c94e67de1..2124ac9dd5 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -18,6 +18,7 @@ package engine import ( "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "slices" @@ -82,24 +83,25 @@ type payloadAttributesMarshaling struct { // ExecutableData is the data necessary to execute an EL payload. type ExecutableData struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` - StateRoot common.Hash `json:"stateRoot" gencodec:"required"` - ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` - LogsBloom []byte `json:"logsBloom" gencodec:"required"` - Random common.Hash `json:"prevRandao" gencodec:"required"` - Number uint64 `json:"blockNumber" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed" gencodec:"required"` - Timestamp uint64 `json:"timestamp" gencodec:"required"` - ExtraData []byte `json:"extraData" gencodec:"required"` - BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` - BlockHash common.Hash `json:"blockHash" gencodec:"required"` - Transactions [][]byte `json:"transactions" gencodec:"required"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` - BlobGasUsed *uint64 `json:"blobGasUsed"` - ExcessBlobGas *uint64 `json:"excessBlobGas"` - SlotNumber *uint64 `json:"slotNumber"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + FeeRecipient common.Address `json:"feeRecipient" gencodec:"required"` + StateRoot common.Hash `json:"stateRoot" gencodec:"required"` + ReceiptsRoot common.Hash `json:"receiptsRoot" gencodec:"required"` + LogsBloom []byte `json:"logsBloom" gencodec:"required"` + Random common.Hash `json:"prevRandao" gencodec:"required"` + Number uint64 `json:"blockNumber" gencodec:"required"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + GasUsed uint64 `json:"gasUsed" gencodec:"required"` + Timestamp uint64 `json:"timestamp" gencodec:"required"` + ExtraData []byte `json:"extraData" gencodec:"required"` + BaseFeePerGas *big.Int `json:"baseFeePerGas" gencodec:"required"` + BlockHash common.Hash `json:"blockHash" gencodec:"required"` + Transactions [][]byte `json:"transactions" gencodec:"required"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + BlobGasUsed *uint64 `json:"blobGasUsed"` + ExcessBlobGas *uint64 `json:"excessBlobGas"` + BlockAccessList *bal.BlockAccessList `json:"blockAccessList"` + SlotNumber *uint64 `json:"slotNumber"` } // JSON type overrides for executableData. @@ -303,6 +305,8 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H requestsHash = &h } + body := types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals} + header := &types.Header{ ParentHash: data.ParentHash, UncleHash: types.EmptyUncleHash, @@ -326,33 +330,41 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H RequestsHash: requestsHash, SlotNumber: data.SlotNumber, } - return types.NewBlockWithHeader(header). - WithBody(types.Body{Transactions: txs, Uncles: nil, Withdrawals: data.Withdrawals}), - nil + + if data.BlockAccessList != nil { + balHash := data.BlockAccessList.Hash() + header.BlockAccessListHash = &balHash + block := types.NewBlockWithHeader(header).WithBody(body).WithAccessList(data.BlockAccessList) + return block, nil + } + + return types.NewBlockWithHeader(header).WithBody(body), nil + } // BlockToExecutableData constructs the ExecutableData structure by filling the // fields from the given block. It assumes the given block is post-merge block. func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.BlobTxSidecar, requests [][]byte) *ExecutionPayloadEnvelope { data := &ExecutableData{ - BlockHash: block.Hash(), - ParentHash: block.ParentHash(), - FeeRecipient: block.Coinbase(), - StateRoot: block.Root(), - Number: block.NumberU64(), - GasLimit: block.GasLimit(), - GasUsed: block.GasUsed(), - BaseFeePerGas: block.BaseFee(), - Timestamp: block.Time(), - ReceiptsRoot: block.ReceiptHash(), - LogsBloom: block.Bloom().Bytes(), - Transactions: encodeTransactions(block.Transactions()), - Random: block.MixDigest(), - ExtraData: block.Extra(), - Withdrawals: block.Withdrawals(), - BlobGasUsed: block.BlobGasUsed(), - ExcessBlobGas: block.ExcessBlobGas(), - SlotNumber: block.SlotNumber(), + BlockHash: block.Hash(), + ParentHash: block.ParentHash(), + FeeRecipient: block.Coinbase(), + StateRoot: block.Root(), + Number: block.NumberU64(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFeePerGas: block.BaseFee(), + Timestamp: block.Time(), + ReceiptsRoot: block.ReceiptHash(), + LogsBloom: block.Bloom().Bytes(), + Transactions: encodeTransactions(block.Transactions()), + Random: block.MixDigest(), + ExtraData: block.Extra(), + Withdrawals: block.Withdrawals(), + BlobGasUsed: block.BlobGasUsed(), + ExcessBlobGas: block.ExcessBlobGas(), + BlockAccessList: block.AccessList(), + SlotNumber: block.SlotNumber(), } // Add blobs. @@ -391,8 +403,9 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types. // ExecutionPayloadBody is used in the response to GetPayloadBodiesByHash and GetPayloadBodiesByRange type ExecutionPayloadBody struct { - TransactionData []hexutil.Bytes `json:"transactions"` - Withdrawals []*types.Withdrawal `json:"withdrawals"` + TransactionData []hexutil.Bytes `json:"transactions"` + Withdrawals []*types.Withdrawal `json:"withdrawals"` + AccessList *bal.BlockAccessList `json:"blockAccessList"` } // Client identifiers to support ClientVersionV1. diff --git a/build/checksums.txt b/build/checksums.txt index ba80a3e201..9c6cb4e2a2 100644 --- a/build/checksums.txt +++ b/build/checksums.txt @@ -5,6 +5,11 @@ # https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0 a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz +# version:spec-tests-bal v5.0.0 +# https://github.com/ethereum/execution-spec-tests/releases +# https://github.com/ethereum/execution-spec-tests/releases/download/bal%40v5.1.0 +c8a7406e6337c1dfd2540f0477afb8abe965c5ed2a63382d7a483eb818f79939 fixtures_bal.tar.gz + # version:golang 1.25.7 # https://go.dev/dl/ 178f2832820274b43e177d32f06a3ebb0129e427dd20a5e4c88df2c1763cf10a go1.25.7.src.tar.gz diff --git a/build/ci.go b/build/ci.go index 4d0a1d7e35..2568bce5da 100644 --- a/build/ci.go +++ b/build/ci.go @@ -172,6 +172,9 @@ var ( // This is where the tests should be unpacked. executionSpecTestsDir = "tests/spec-tests" + + // This is where the bal-specific release of the tests should be unpacked. + executionSpecTestsBALDir = "tests/spec-tests-bal" ) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) @@ -380,6 +383,7 @@ func doTest(cmdline []string) { // Get test fixtures. if !*short { downloadSpecTestFixtures(csdb, *cachedir) + downloadBALSpecTestFixtures(csdb, *cachedir) } // Configure the toolchain. @@ -445,6 +449,19 @@ func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string return filepath.Join(cachedir, base) } +func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string { + ext := ".tar.gz" + base := "fixtures_bal" + archivePath := filepath.Join(cachedir, base+ext) + if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil { + log.Fatal(err) + } + if err := build.ExtractArchive(archivePath, executionSpecTestsBALDir); err != nil { + log.Fatal(err) + } + return filepath.Join(cachedir, base) +} + // doCheckGenerate ensures that re-generating generated files does not cause // any mutations in the source file tree. func doCheckGenerate() { diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index c6fac5396e..c674926825 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -117,7 +117,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) { test := tests[name] result := &testResult{Name: name, Pass: true} var finalRoot *common.Hash - if err := test.Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), tracer, func(res error, chain *core.BlockChain) { + if err := test.Run(false, rawdb.PathScheme, ctx.Bool(WitnessCrossCheckFlag.Name), false, tracer, func(res error, chain *core.BlockChain) { if ctx.Bool(DumpFlag.Name) { if s, _ := chain.State(); s != nil { result.State = dump(s) diff --git a/cmd/geth/config.go b/cmd/geth/config.go index 720d1ef9fc..7e24842225 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -240,7 +240,21 @@ func makeFullNode(ctx *cli.Context) *node.Node { cfg.Eth.OverrideVerkle = &v } - // Start metrics export if enabled. + if ctx.IsSet(utils.BlockAccessListExecutionModeFlag.Name) { + val := ctx.String(utils.BlockAccessListExecutionModeFlag.Name) + switch val { + case utils.BalExecutionModeFull: + cfg.Eth.BALExecutionMode = 0 + case utils.BalExecutionModeNoBatchIO: + cfg.Eth.BALExecutionMode = 1 + case utils.BalExecutionModeSequential: + cfg.Eth.BALExecutionMode = 2 + default: + utils.Fatalf("invalid option for --bal.executionmode: %s. acceptable values are full|nobatchio|sequential", val) + } + } + + // Start metrics export if enabled utils.SetupMetrics(&cfg.Metrics) // Setup OpenTelemetry reporting if enabled. diff --git a/cmd/geth/main.go b/cmd/geth/main.go index ca75775be2..aaf318c9c2 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -273,6 +273,7 @@ func init() { consoleFlags, debug.Flags, metricsFlags, + []cli.Flag{utils.BlockAccessListExecutionModeFlag}, ) flags.AutoEnvVars(app.Flags, "GETH") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index e114eb2cd4..203dfce9d2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1108,6 +1108,31 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server. Name: "era.format", Usage: "Archive format: 'era1' or 'erae'", } + + // Block Access List flags + ExperimentalBALFlag = &cli.BoolFlag{ + Name: "experimental.bal", + Usage: "Enable generation of EIP-7928 block access lists when importing post-Cancun blocks which lack them. When this flag is specified, importing blocks containing access lists triggers validation of their correctness and execution based off them. The header block access list field is not set with blocks created when this flag is specified, nor is it validated when importing blocks that contain access lists. This is used for development purposes only. Do not enable it otherwise.", + Category: flags.MiscCategory, + } + // block access list flags + + BlockAccessListExecutionModeFlag = &cli.StringFlag{ + Name: "bal.executionmode", + Usage: ` +block access list execution type. possible inputs are: +- sequential: no performance acceleration +- full: parallel transaction execution, state root calculation, async warming of access list reads +- nobatchio: same as 'full', but without async warming of access list reads`, + Value: BalExecutionModeFull, + Category: flags.MiscCategory, + } +) + +const ( + BalExecutionModeFull = "full" + BalExecutionModeNoBatchIO = "nobatchio" + BalExecutionModeSequential = "sequential" ) var ( diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 45a480c50e..233f0f7ead 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -19,6 +19,7 @@ package beacon import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "github.com/ethereum/go-ethereum/common" @@ -280,6 +281,12 @@ func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, pa if !amsterdam && header.SlotNumber != nil { return fmt.Errorf("invalid slotNumber: have %d, expected nil", *header.SlotNumber) } + if !amsterdam && header.BlockAccessListHash != nil { + return fmt.Errorf("invalid block access list hash: have %x, expected nil", header.BlockAccessListHash) + } + if amsterdam && header.BlockAccessListHash == nil { + return fmt.Errorf("header is missing block access list hash") + } return nil } @@ -334,26 +341,29 @@ func (beacon *Beacon) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine and processes withdrawals on top. -func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) bal.StateMutations { if !beacon.IsPoSHeader(header) { - beacon.ethone.Finalize(chain, header, state, body) - return + return beacon.ethone.Finalize(chain, header, state, body) } // Withdrawals processing. for _, w := range body.Withdrawals { + // always read the target account regardless of withdrawal amt to include it in the BAL + state.GetBalance(w.Address) + // Convert amount from gwei to wei. amount := new(uint256.Int).SetUint64(w.Amount) amount = amount.Mul(amount, uint256.NewInt(params.GWei)) state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal) } + return state.Finalise(true) // No block reward which is issued by consensus layer instead. } // FinalizeAndAssemble implements consensus.Engine, setting the final state and // assembling the block. -func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalizeAccessList func(postMut bal.StateMutations) *bal.BlockAccessList) (*types.Block, error) { if !beacon.IsPoSHeader(header) { - return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts) + return beacon.ethone.FinalizeAndAssemble(chain, header, state, body, receipts, nil) } shanghai := chain.Config().IsShanghai(header.Number, header.Time) if shanghai { @@ -366,14 +376,24 @@ func (beacon *Beacon) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea return nil, errors.New("withdrawals set before Shanghai activation") } } + // Finalize and assemble the block. - beacon.Finalize(chain, header, state, body) + postMut := beacon.Finalize(chain, header, state, body) // Assign the final state root to header. header.Root = state.IntermediateRoot(true) // Assemble the final block. - return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil + if onFinalizeAccessList != nil { + al := onFinalizeAccessList(postMut) + alHash := al.Hash() + + header.BlockAccessListHash = &alHash + block := types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)).WithAccessList(al) + return block, nil + } else { + return types.NewBlock(header, body, receipts, trie.NewStackTrie(nil)), nil + } } // Seal generates a new sealing request for the given input block and pushes diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 28095011c1..9906ffd0d0 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -21,6 +21,7 @@ import ( "bytes" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "io" "math/big" "math/rand" @@ -575,13 +576,14 @@ func (c *Clique) Prepare(chain consensus.ChainHeaderReader, header *types.Header // Finalize implements consensus.Engine. There is no post-transaction // consensus rules in clique, do nothing here. -func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) (mut bal.StateMutations) { // No block rewards in PoA, so the state remains as is + return } // FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set, // nor block rewards given, and returns the final block. -func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalizeAccessList func(withdrawalMut bal.StateMutations) *bal.BlockAccessList) (*types.Block, error) { if len(body.Withdrawals) > 0 { return nil, errors.New("clique does not support withdrawals") } @@ -591,6 +593,9 @@ func (c *Clique) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header * // Assign the final state root to header. header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + if onFinalizeAccessList != nil { + panic("access list embedding not enabled for clique consensus") + } // Assemble and return the final block for sealing. return types.NewBlock(header, &types.Body{Transactions: body.Transactions}, receipts, trie.NewStackTrie(nil)), nil } diff --git a/consensus/consensus.go b/consensus/consensus.go index a68351f7ff..9133d4ebfb 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -18,6 +18,7 @@ package consensus import ( + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "github.com/ethereum/go-ethereum/common" @@ -85,14 +86,14 @@ type Engine interface { // // Note: The state database might be updated to reflect any consensus rules // that happen at finalization (e.g. block rewards). - Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) + Finalize(chain ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) bal.StateMutations // FinalizeAndAssemble runs any post-transaction state modifications (e.g. block // rewards or process withdrawals) and assembles the final block. // // Note: The block header and state database might be updated to reflect any // consensus rules that happen at finalization (e.g. block rewards). - FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) + FinalizeAndAssemble(chain ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalizeAccessList func(mutations bal.StateMutations) *bal.BlockAccessList) (*types.Block, error) // Seal generates a new sealing request for the given input block and pushes // the result into the given channel. diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 61371e0636..f8b433fa63 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -19,6 +19,7 @@ package ethash import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "time" @@ -506,14 +507,15 @@ func (ethash *Ethash) Prepare(chain consensus.ChainHeaderReader, header *types.H } // Finalize implements consensus.Engine, accumulating the block and uncle rewards. -func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) { +func (ethash *Ethash) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state vm.StateDB, body *types.Body) (mut bal.StateMutations) { // Accumulate any block and uncle rewards accumulateRewards(chain.Config(), state, header, body.Uncles) + return } // FinalizeAndAssemble implements consensus.Engine, accumulating the block and // uncle rewards, setting the final state and assembling the block. -func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt) (*types.Block, error) { +func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, body *types.Body, receipts []*types.Receipt, onFinalizeAccessList func(withdrawalMut bal.StateMutations) *bal.BlockAccessList) (*types.Block, error) { if len(body.Withdrawals) > 0 { return nil, errors.New("ethash does not support withdrawals") } @@ -523,6 +525,9 @@ func (ethash *Ethash) FinalizeAndAssemble(chain consensus.ChainHeaderReader, hea // Assign the final state root to header. header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + if onFinalizeAccessList != nil { + panic("access list embedding not supported for ethash consenus") + } // Header seems complete, assemble into a block and return return types.NewBlock(header, &types.Body{Transactions: body.Transactions, Uncles: body.Uncles}, receipts, trie.NewStackTrie(nil)), nil } diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 2ebf4f7155..3f7c369fe3 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -69,6 +69,8 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) bc = s.BPO4 case cfg.IsBPO3(london, time) && s.BPO3 != nil: bc = s.BPO3 + case cfg.IsAmsterdam(london, time) && s.Amsterdam != nil: + bc = s.Amsterdam case cfg.IsBPO2(london, time) && s.BPO2 != nil: bc = s.BPO2 case cfg.IsBPO1(london, time) && s.BPO1 != nil: diff --git a/core/block_validator.go b/core/block_validator.go index 008444fbbc..3a5379d1a9 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -19,7 +19,6 @@ package core import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -111,6 +110,30 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } } + // block access lists must be present after the Amsterdam hard fork + if v.config.IsAmsterdam(block.Number(), block.Time()) { + if block.Header().BlockAccessListHash == nil { + // TODO: verify that this check isn't also done elsewhere + return fmt.Errorf("block access list hash not set in header") + } + if block.AccessList() != nil { + if *block.Header().BlockAccessListHash != block.AccessList().Hash() { + return fmt.Errorf("access list hash mismatch. local: %x. remote: %x\n", block.AccessList().Hash(), *block.Header().BlockAccessListHash) + } else if err := block.AccessList().Validate(len(block.Transactions())); err != nil { + return fmt.Errorf("invalid block access list: %v", err) + } + } else { + //panic("TODO: implement local access list construction path if importing a block without an access list") + } + } else { + // if experimental.bal is not enabled, block headers cannot have access list hash and bodies cannot have access lists. + if block.AccessList() != nil { + return fmt.Errorf("access list not allowed in block body if not in amsterdam or experimental.bal is set") + } else if block.Header().BlockAccessListHash != nil { + return fmt.Errorf("access list hash in block header not allowed when experimental.bal is set") + } + } + // Ancestor block must be known. if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { @@ -123,7 +146,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // ValidateState validates the various changes that happen after a state transition, // such as amount of used gas, the receipt roots and the state root itself. -func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error { +func (v *BlockValidator) ValidateState(block *types.Block, stateTransition state.BlockStateTransition, res *ProcessResult, stateless bool) error { if res == nil { return errors.New("nil ProcessResult value") } @@ -160,10 +183,11 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD } else if res.Requests != nil { return errors.New("block has requests before prague fork") } + // Validate the state root against the received state root and throw // an error if they don't match. - if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { - return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) + if root := stateTransition.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, stateTransition.Error()) } return nil } diff --git a/core/blockchain.go b/core/blockchain.go index d41f301243..3f5b047c6d 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -21,6 +21,7 @@ import ( "context" "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "io" "math/big" "runtime" @@ -103,6 +104,21 @@ var ( blockExecutionTimer = metrics.NewRegisteredResettingTimer("chain/execution", nil) blockWriteTimer = metrics.NewRegisteredResettingTimer("chain/write", nil) + // BALspecific timers + blockPreprocessingTimer = metrics.NewRegisteredResettingTimer("chain/preprocess", nil) + txExecutionTimer = metrics.NewRegisteredResettingTimer("chain/txexecution", nil) + + stateTrieHashTimer = metrics.NewRegisteredResettingTimer("chain/statetriehash", nil) + accountTriesUpdateTimer = metrics.NewRegisteredResettingTimer("chain/accounttriesupdate", nil) + stateTriePrefetchTimer = metrics.NewRegisteredResettingTimer("chain/statetrieprefetch", nil) + stateTrieUpdateTimer = metrics.NewRegisteredResettingTimer("chain/statetrieupdate", nil) + originStorageLoadTimer = metrics.NewRegisteredResettingTimer("chain/originstorageload", nil) + + stateRootComputeTimer = metrics.NewRegisteredResettingTimer("chain/staterootcompute", nil) + stateCommitTimer = metrics.NewRegisteredResettingTimer("chain/statetriecommit", nil) + + blockPostprocessingTimer = metrics.NewRegisteredResettingTimer("chain/postprocess", nil) + blockReorgMeter = metrics.NewRegisteredMeter("chain/reorg/executes", nil) blockReorgAddMeter = metrics.NewRegisteredMeter("chain/reorg/add", nil) blockReorgDropMeter = metrics.NewRegisteredMeter("chain/reorg/drop", nil) @@ -163,6 +179,12 @@ const ( BlockChainVersion uint64 = 9 ) +const ( + BALExecutionModeFull = 0 + BALExecutionModeNoBatchIO = iota + BALExecutionModeSequential = iota +) + // BlockChainConfig contains the configuration of the BlockChain object. type BlockChainConfig struct { // Trie database related options @@ -219,6 +241,8 @@ type BlockChainConfig struct { // detailed statistics will be logged. Negative value means disabled (default), // zero logs all blocks, positive value filters blocks by execution time. SlowBlockThreshold time.Duration + + BALExecutionMode int } // DefaultConfig returns the default config. @@ -357,12 +381,13 @@ type BlockChain struct { stopping atomic.Bool // false if chain is running, true when stopped procInterrupt atomic.Bool // interrupt signaler for block processing - engine consensus.Engine - validator Validator // Block and state validator interface - prefetcher Prefetcher - processor Processor // Block transaction processor interface - logger *tracing.Hooks - stateSizer *state.SizeTracker // State size tracking + engine consensus.Engine + validator Validator // Block and state validator interface + prefetcher Prefetcher + processor Processor // Block transaction processor interface + parallelProcessor ParallelStateProcessor + logger *tracing.Hooks + stateSizer *state.SizeTracker // State size tracking lastForkReadyAlert time.Time // Last time there was a fork readiness print out slowBlockThreshold time.Duration // Block execution time threshold beyond which detailed statistics will be logged @@ -424,6 +449,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, bc.validator = NewBlockValidator(chainConfig, bc) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) bc.processor = NewStateProcessor(bc.hc) + bc.parallelProcessor = NewParallelStateProcessor(bc.hc, &cfg.VmConfig) genesisHeader := bc.GetHeaderByNumber(0) if genesisHeader == nil { @@ -571,6 +597,113 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, } return bc, nil } +func (bc *BlockChain) processBlockWithAccessList(parentRoot common.Hash, block *types.Block, setHead bool) (procRes *blockProcessingResult, blockEndErr error) { + var ( + startTime = time.Now() + procTime time.Duration + ) + + useAsyncReads := bc.cfg.BALExecutionMode != BALExecutionModeNoBatchIO + al := block.AccessList() // TODO: make the return of this method not be a pointer + accessListReader := bal.NewAccessListReader(*al) + prefetchReader, err := bc.statedb.ReaderEIP7928(parentRoot, accessListReader.StorageKeys(useAsyncReads), runtime.NumCPU()) + if err != nil { + return nil, err + } + + stateTransition, err := state.NewBALStateTransition(block, prefetchReader, bc.statedb, parentRoot) + if err != nil { + return nil, err + } + statedb, err := state.NewWithReader(parentRoot, bc.statedb, prefetchReader) + + if bc.logger != nil && bc.logger.OnBlockStart != nil { + bc.logger.OnBlockStart(tracing.BlockEvent{ + Block: block, + Finalized: bc.CurrentFinalBlock(), + Safe: bc.CurrentSafeBlock(), + }) + } + if bc.logger != nil && bc.logger.OnBlockEnd != nil { + defer func() { + bc.logger.OnBlockEnd(blockEndErr) + }() + } + + res, err := bc.parallelProcessor.Process(block, stateTransition, statedb, bc.cfg.VmConfig) + if err != nil { + return nil, err + } + + if err := bc.validator.ValidateState(block, stateTransition, res.ProcessResult, false); err != nil { + return nil, err + } + + procTime = time.Since(startTime) + writeStart := time.Now() + // Write the block to the chain and get the status. + var ( + //wstart = time.Now() + status WriteStatus + ) + if !setHead { + // Don't set the head, only insert the block + err = bc.writeBlockWithState(block, res.ProcessResult.Receipts, stateTransition) + } else { + status, err = bc.writeBlockAndSetHead(block, res.ProcessResult.Receipts, res.ProcessResult.Logs, stateTransition, false) + } + if err != nil { + return nil, err + } + writeTime := time.Since(writeStart) + var stats ExecuteStats + + /* + // TODO: implement the gathering of this data + stats.AccountReads = statedb.AccountReads // Account reads are complete(in processing) + stats.StorageReads = statedb.StorageReads // Storage reads are complete(in processing) + stats.AccountUpdates = statedb.AccountUpdates // Account updates are complete(in validation) + stats.StorageUpdates = statedb.StorageUpdates // Storage updates are complete(in validation) + stats.AccountHashes = statedb.AccountHashes // Account hashes are complete(in validation) + stats.CodeReads = statedb.CodeReads + + stats.AccountLoaded = statedb.AccountLoaded + stats.AccountUpdated = statedb.AccountUpdated + stats.AccountDeleted = statedb.AccountDeleted + stats.StorageLoaded = statedb.StorageLoaded + stats.StorageUpdated = int(statedb.StorageUpdated.Load()) + stats.StorageDeleted = int(statedb.StorageDeleted.Load()) + stats.CodeLoaded = statedb.CodeLoaded + stats.CodeLoadBytes = statedb.CodeLoadBytes + + stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing + stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation + */ + + // Update the metrics touched during block commit + stats.AccountCommits = stateTransition.Metrics().AccountCommits + stats.StorageCommits = stateTransition.Metrics().StorageCommits + stats.SnapshotCommit = stateTransition.Metrics().SnapshotCommits + stats.TrieDBCommit = stateTransition.Metrics().TrieDBCommits + + // stats.StateReadCacheStats = whichReader.GetStats() + // ^ TODO fix this + + elapsed := time.Since(startTime) + 1 // prevent zero division + stats.TotalTime = elapsed + stats.MgasPerSecond = float64(res.ProcessResult.GasUsed) * 1000 / float64(elapsed) + stats.BlockWrite = writeTime + + stats.balTransitionStats = res.StateTransitionMetrics + + return &blockProcessingResult{ + usedGas: res.ProcessResult.GasUsed, + procTime: procTime, + status: status, + witness: nil, + stats: &stats, + }, nil +} func (bc *BlockChain) setupSnapshot() { // Short circuit if the chain is established with path scheme, as the @@ -1641,7 +1774,7 @@ func (bc *BlockChain) writeKnownBlock(block *types.Block) error { // writeBlockWithState writes block, metadata and corresponding state data to the // database. -func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, statedb *state.StateDB) error { +func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types.Receipt, transition state.BlockStateTransition) error { if !bc.HasHeader(block.ParentHash(), block.NumberU64()-1) { return consensus.ErrUnknownAncestor } @@ -1655,7 +1788,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. ) rawdb.WriteBlock(batch, block) rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) - rawdb.WritePreimages(batch, statedb.Preimages()) + rawdb.WritePreimages(batch, transition.Preimages()) if err := batch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } @@ -1670,7 +1803,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. hasStateSizer = bc.stateSizer != nil ) if hasStateHook || hasStateSizer { - r, update, err := statedb.CommitWithUpdate(block.NumberU64(), isEIP158, isCancun) + r, update, err := transition.CommitWithUpdate(block.NumberU64(), isEIP158, isCancun) if err != nil { return err } @@ -1686,7 +1819,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. } root = r } else { - root, err = statedb.Commit(block.NumberU64(), isEIP158, isCancun) + root, err = transition.Commit(block.NumberU64(), isEIP158, isCancun) if err != nil { return err } @@ -1753,7 +1886,7 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. // writeBlockAndSetHead is the internal implementation of WriteBlockAndSetHead. // This function expects the chain mutex to be held. -func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state *state.StateDB, emitHeadEvent bool) (status WriteStatus, err error) { +func (bc *BlockChain) writeBlockAndSetHead(block *types.Block, receipts []*types.Receipt, logs []*types.Log, state state.BlockStateTransition, emitHeadEvent bool) (status WriteStatus, err error) { if err := bc.writeBlockWithState(block, receipts, state); err != nil { return NonStatTy, err } @@ -1990,11 +2123,16 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHe } // The traced section of block import. start := time.Now() + blockHasAccessList := block.AccessList() != nil res, err := bc.ProcessBlock(ctx, parent.Root, block, setHead, makeWitness && len(chain) == 1) if err != nil { return nil, it.index, err } - res.stats.reportMetrics() + if blockHasAccessList && bc.cfg.BALExecutionMode != BALExecutionModeSequential { + res.stats.reportBALMetrics() + } else { + res.stats.reportMetrics() + } // Log slow block only if a single block is inserted (usually after the // initial sync) to not overwhelm the users. @@ -2076,6 +2214,16 @@ func (bpr *blockProcessingResult) Stats() *ExecuteStats { // ProcessBlock executes and validates the given block. If there was no error // it writes the block and associated state to database. func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, block *types.Block, setHead bool, makeWitness bool) (result *blockProcessingResult, blockEndErr error) { + isAmsterdam := bc.chainConfig.IsAmsterdam(block.Number(), block.Time()) + // TODO: need to check that the block is also postcancun if it contained an access list? + // this should be checked during decoding (?) + blockHasAccessList := block.AccessList() != nil + + // optimized execution path for blocks which contain BALs + if blockHasAccessList && bc.cfg.BALExecutionMode != BALExecutionModeSequential { + return bc.processBlockWithAccessList(parentRoot, block, setHead) + } + var ( err error startTime = time.Now() @@ -2085,9 +2233,22 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, defer interrupt.Store(true) // terminate the prefetch at the end if bc.cfg.NoPrefetch { - statedb, err = state.New(parentRoot, bc.statedb) - if err != nil { - return nil, err + if isAmsterdam { + reader, err := bc.statedb.Reader(parentRoot) + if err != nil { + return nil, err + } + + readerTracker := state.NewReaderWithTracker(reader) + statedb, err = state.NewWithReader(parentRoot, bc.statedb, readerTracker) + if err != nil { + return nil, err + } + } else { + statedb, err = state.New(parentRoot, bc.statedb) + if err != nil { + return nil, err + } } } else { // If prefetching is enabled, run that against the current state to pre-cache @@ -2095,7 +2256,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, // // Note: the main processor and prefetcher share the same reader with a local // cache for mitigating the overhead of state access. - prefetch, process, err := bc.statedb.ReadersWithCacheStats(parentRoot) + prefetch, process, err := bc.statedb.ReadersWithCache(parentRoot) if err != nil { return nil, err } @@ -2103,6 +2264,9 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, if err != nil { return nil, err } + if isAmsterdam { + process = state.NewReaderWithTracker(process) + } statedb, err = state.NewWithReader(parentRoot, bc.statedb, process) if err != nil { return nil, err @@ -2110,15 +2274,22 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, // Upload the statistics of reader at the end defer func() { if result != nil { - result.stats.StatePrefetchCacheStats = prefetch.GetStats() - result.stats.StateReadCacheStats = process.GetStats() + if stater, ok := prefetch.(state.ReaderStater); ok { + result.stats.StatePrefetchCacheStats = stater.GetStats() + } + if stater, ok := process.(state.ReaderStater); ok { + result.stats.StateReadCacheStats = stater.GetStats() + } } }() go func(start time.Time, throwaway *state.StateDB, block *types.Block) { // Disable tracing for prefetcher executions. vmCfg := bc.cfg.VmConfig vmCfg.Tracer = nil - bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt) + if block.AccessList() == nil { + // only use the state prefetcher for non-BAL blocks. + bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt) + } blockPrefetchExecuteTimer.Update(time.Since(start)) if interrupt.Load() { @@ -2147,6 +2318,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, witnessStats = stateless.NewWitnessStats() } } + statedb.StartPrefetcher("chain", witness, witnessStats) defer statedb.StopPrefetcher() } @@ -2164,16 +2336,19 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, }() } + var res *ProcessResult + var ptime, vtime time.Duration + // Process block using the parent state as reference point pstart := time.Now() pctx, _, spanEnd := telemetry.StartSpan(ctx, "bc.processor.Process") - res, err := bc.processor.Process(pctx, block, statedb, bc.cfg.VmConfig) + res, err = bc.processor.Process(pctx, block, statedb, bc.cfg.VmConfig) spanEnd(&err) if err != nil { bc.reportBadBlock(block, res, err) return nil, err } - ptime := time.Since(pstart) + ptime = time.Since(pstart) vstart := time.Now() _, _, spanEnd = telemetry.StartSpan(ctx, "bc.validator.ValidateState") @@ -2183,7 +2358,28 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, bc.reportBadBlock(block, res, err) return nil, err } - vtime := time.Since(vstart) + vtime = time.Since(vstart) + + if isAmsterdam { + computedAccessList := res.AccessList.ToEncodingObj() + computedAccessListHash := computedAccessList.Hash() + + if *block.Header().BlockAccessListHash != computedAccessListHash { + //fmt.Printf("remote:\n%s\nlocal:\n%s\n", block.Body().AccessList.JSONString(), computedAccessList.JSONString()) + err := fmt.Errorf("block header access list hash mismatch with computed (header=%x computed=%x)", *block.Header().BlockAccessListHash, computedAccessListHash) + bc.reportBadBlock(block, res, err) + return nil, err + } + if block.AccessList() == nil { + // attach the computed access list to the block so it gets persisted + // when the block is written to disk + block = block.WithAccessList(computedAccessList) + } else if block.AccessList().Hash() != computedAccessListHash { + err := fmt.Errorf("block access list hash mismatch (remote=%x computed=%x)", block.AccessList().Hash(), computedAccessListHash) + bc.reportBadBlock(block, res, err) + return nil, err + } + } // If witnesses was generated and stateless self-validation requested, do // that now. Self validation should *never* run in production, it's more of @@ -2775,6 +2971,10 @@ func (bc *BlockChain) reportBadBlock(block *types.Block, res *ProcessResult, err log.Error(summarizeBadBlock(block, receipts, bc.Config(), err)) } +func (bc *BlockChain) reportBALBlock(block *types.Block, res *ProcessResult, err error) { + +} + // logForkReadiness will write a log when a future fork is scheduled, but not // active. This is useful so operators know their client is ready for the fork. func (bc *BlockChain) logForkReadiness(block *types.Block) { diff --git a/core/blockchain_stats.go b/core/blockchain_stats.go index adc66266c4..837b859250 100644 --- a/core/blockchain_stats.go +++ b/core/blockchain_stats.go @@ -61,6 +61,9 @@ type ExecuteStats struct { // Cache hit rates StateReadCacheStats state.ReaderStats StatePrefetchCacheStats state.ReaderStats + + // Stats specific to BAL state update + balTransitionStats *state.BALStateTransitionMetrics } // reportMetrics uploads execution statistics to the metrics system. @@ -94,15 +97,15 @@ func (s *ExecuteStats) reportMetrics() { chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer // Cache hit rates - accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheHit) - accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.AccountCacheMiss) - storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheHit) - storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StorageCacheMiss) + accountCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheHit) + accountCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.AccountCacheMiss) + storageCacheHitPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheHit) + storageCacheMissPrefetchMeter.Mark(s.StatePrefetchCacheStats.StateStats.StorageCacheMiss) - accountCacheHitMeter.Mark(s.StateReadCacheStats.AccountCacheHit) - accountCacheMissMeter.Mark(s.StateReadCacheStats.AccountCacheMiss) - storageCacheHitMeter.Mark(s.StateReadCacheStats.StorageCacheHit) - storageCacheMissMeter.Mark(s.StateReadCacheStats.StorageCacheMiss) + accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit) + accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss) + storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit) + storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss) } // slowBlockLog represents the JSON structure for slow block logging. @@ -238,14 +241,14 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat }, Cache: slowBlockCache{ Account: slowBlockCacheEntry{ - Hits: s.StateReadCacheStats.AccountCacheHit, - Misses: s.StateReadCacheStats.AccountCacheMiss, - HitRate: calculateHitRate(s.StateReadCacheStats.AccountCacheHit, s.StateReadCacheStats.AccountCacheMiss), + Hits: s.StateReadCacheStats.StateStats.AccountCacheHit, + Misses: s.StateReadCacheStats.StateStats.AccountCacheMiss, + HitRate: calculateHitRate(s.StateReadCacheStats.StateStats.AccountCacheHit, s.StateReadCacheStats.StateStats.AccountCacheMiss), }, Storage: slowBlockCacheEntry{ - Hits: s.StateReadCacheStats.StorageCacheHit, - Misses: s.StateReadCacheStats.StorageCacheMiss, - HitRate: calculateHitRate(s.StateReadCacheStats.StorageCacheHit, s.StateReadCacheStats.StorageCacheMiss), + Hits: s.StateReadCacheStats.StateStats.StorageCacheHit, + Misses: s.StateReadCacheStats.StateStats.StorageCacheMiss, + HitRate: calculateHitRate(s.StateReadCacheStats.StateStats.StorageCacheHit, s.StateReadCacheStats.StateStats.StorageCacheMiss), }, Code: slowBlockCodeCacheEntry{ Hits: s.StateReadCacheStats.CodeStats.CacheHit, @@ -263,3 +266,53 @@ func (s *ExecuteStats) logSlow(block *types.Block, slowBlockThreshold time.Durat } log.Warn(string(jsonBytes)) } + +func (s *ExecuteStats) reportBALMetrics() { + /* + if s.AccountLoaded != 0 { + accountReadTimer.Update(s.AccountReads) + accountReadSingleTimer.Update(s.AccountReads / time.Duration(s.AccountLoaded)) + } + if s.StorageLoaded != 0 { + storageReadTimer.Update(s.StorageReads) + storageReadSingleTimer.Update(s.StorageReads / time.Duration(s.StorageLoaded)) + } + if s.CodeLoaded != 0 { + codeReadTimer.Update(s.CodeReads) + codeReadSingleTimer.Update(s.CodeReads / time.Duration(s.CodeLoaded)) + codeReadBytesTimer.Update(time.Duration(s.CodeLoadBytes)) + } + // TODO: implement these ^ + */ + //accountUpdateTimer.Update(s.AccountUpdates) // Account updates are complete(in validation) + //storageUpdateTimer.Update(s.StorageUpdates) // Storage updates are complete(in validation) + //accountHashTimer.Update(s.AccountHashes) // Account hashes are complete(in validation) + + accountCommitTimer.Update(s.AccountCommits) // Account commits are complete, we can mark them + storageCommitTimer.Update(s.StorageCommits) // Storage commits are complete, we can mark them + + stateTriePrefetchTimer.Update(s.balTransitionStats.StatePrefetch) + accountTriesUpdateTimer.Update(s.balTransitionStats.AccountUpdate) + stateTrieUpdateTimer.Update(s.balTransitionStats.StateUpdate) + stateTrieHashTimer.Update(s.balTransitionStats.StateHash) + stateRootComputeTimer.Update(s.balTransitionStats.AccountUpdate + s.balTransitionStats.StateUpdate + s.balTransitionStats.StateHash) + + //blockExecutionTimer.Update(s.Execution) // The time spent on EVM processing + // ^basically impossible to get this metric with parallel execution + + //blockValidationTimer.Update(s.Validation) // The time spent on block validation + //blockCrossValidationTimer.Update(s.CrossValidation) // The time spent on stateless cross validation + + snapshotCommitTimer.Update(s.SnapshotCommit) // Snapshot commits are complete, we can mark them + triedbCommitTimer.Update(s.TrieDBCommit) // Trie database commits are complete, we can mark them + blockWriteTimer.Update(s.BlockWrite) // The time spent on block write + blockInsertTimer.Update(s.TotalTime) // The total time spent on block execution + chainMgaspsMeter.Update(time.Duration(s.MgasPerSecond)) // TODO(rjl493456442) generalize the ResettingTimer + + // Cache hit rates + + accountCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheHit) + accountCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.AccountCacheMiss) + storageCacheHitMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheHit) + storageCacheMissMeter.Mark(s.StateReadCacheStats.StateStats.StorageCacheMiss) +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 13ce690518..2f0f3d2fa3 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -166,7 +166,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { blockchain.reportBadBlock(block, res, err) return err } - err = blockchain.validator.ValidateState(block, statedb, res, false) + err = blockchain.validator.ValidateState(block, statedb, res, true, false) if err != nil { blockchain.reportBadBlock(block, res, err) return err diff --git a/core/chain_makers.go b/core/chain_makers.go index 5264336aaa..2407b00fce 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -117,7 +117,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) ) b.statedb.SetTxContext(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) + _, receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) if err != nil { panic(err) } @@ -329,11 +329,11 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{}) // EIP-7002 - if err := ProcessWithdrawalQueue(&requests, evm); err != nil { + if _, err := ProcessWithdrawalQueue(&requests, evm); err != nil { panic(fmt.Sprintf("could not process withdrawal requests: %v", err)) } // EIP-7251 - if err := ProcessConsolidationQueue(&requests, evm); err != nil { + if _, err := ProcessConsolidationQueue(&requests, evm); err != nil { panic(fmt.Sprintf("could not process consolidation requests: %v", err)) } } @@ -411,7 +411,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse } body := types.Body{Transactions: b.txs, Uncles: b.uncles, Withdrawals: b.withdrawals} - block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts) + block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, &body, b.receipts, nil) if err != nil { panic(err) } diff --git a/core/gen_genesis.go b/core/gen_genesis.go index b94825b185..cc17dad6cb 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -19,22 +19,23 @@ var _ = (*genesisSpecMarshaling)(nil) // MarshalJSON marshals as JSON. func (g Genesis) MarshalJSON() ([]byte, error) { type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce math.HexOrDecimal64 `json:"nonce"` - Timestamp math.HexOrDecimal64 `json:"timestamp"` - ExtraData hexutil.Bytes `json:"extraData"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` - Mixhash common.Hash `json:"mixHash"` - Coinbase common.Address `json:"coinbase"` - Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` - Number math.HexOrDecimal64 `json:"number"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - ParentHash common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` - SlotNumber *uint64 `json:"slotNumber"` + Config *params.ChainConfig `json:"config"` + Nonce math.HexOrDecimal64 `json:"nonce"` + Timestamp math.HexOrDecimal64 `json:"timestamp"` + ExtraData hexutil.Bytes `json:"extraData"` + GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` + Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` + Number math.HexOrDecimal64 `json:"number"` + GasUsed math.HexOrDecimal64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"` + SlotNumber *uint64 `json:"slotNumber"` } var enc Genesis enc.Config = g.Config @@ -57,6 +58,7 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.BaseFee = (*math.HexOrDecimal256)(g.BaseFee) enc.ExcessBlobGas = (*math.HexOrDecimal64)(g.ExcessBlobGas) enc.BlobGasUsed = (*math.HexOrDecimal64)(g.BlobGasUsed) + enc.BlockAccessListHash = g.BlockAccessListHash enc.SlotNumber = g.SlotNumber return json.Marshal(&enc) } @@ -64,22 +66,23 @@ func (g Genesis) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (g *Genesis) UnmarshalJSON(input []byte) error { type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce *math.HexOrDecimal64 `json:"nonce"` - Timestamp *math.HexOrDecimal64 `json:"timestamp"` - ExtraData *hexutil.Bytes `json:"extraData"` - GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` - Mixhash *common.Hash `json:"mixHash"` - Coinbase *common.Address `json:"coinbase"` - Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"number"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - ParentHash *common.Hash `json:"parentHash"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` - SlotNumber *uint64 `json:"slotNumber"` + Config *params.ChainConfig `json:"config"` + Nonce *math.HexOrDecimal64 `json:"nonce"` + Timestamp *math.HexOrDecimal64 `json:"timestamp"` + ExtraData *hexutil.Bytes `json:"extraData"` + GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` + Difficulty *math.HexOrDecimal256 `json:"difficulty" gencodec:"required"` + Mixhash *common.Hash `json:"mixHash"` + Coinbase *common.Address `json:"coinbase"` + Alloc map[common.UnprefixedAddress]types.Account `json:"alloc" gencodec:"required"` + Number *math.HexOrDecimal64 `json:"number"` + GasUsed *math.HexOrDecimal64 `json:"gasUsed"` + ParentHash *common.Hash `json:"parentHash"` + BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas"` + ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas"` + BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed"` + BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"` + SlotNumber *uint64 `json:"slotNumber"` } var dec Genesis if err := json.Unmarshal(input, &dec); err != nil { @@ -136,6 +139,9 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { if dec.BlobGasUsed != nil { g.BlobGasUsed = (*uint64)(dec.BlobGasUsed) } + if dec.BlockAccessListHash != nil { + g.BlockAccessListHash = dec.BlockAccessListHash + } if dec.SlotNumber != nil { g.SlotNumber = dec.SlotNumber } diff --git a/core/genesis.go b/core/genesis.go index 6edc6e6779..25b54cdfbd 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -67,13 +67,14 @@ type Genesis struct { // These fields are used for consensus tests. Please don't use them // in actual genesis blocks. - Number uint64 `json:"number"` - GasUsed uint64 `json:"gasUsed"` - ParentHash common.Hash `json:"parentHash"` - BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559 - ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844 - BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844 - SlotNumber *uint64 `json:"slotNumber"` // EIP-7843 + Number uint64 `json:"number"` + GasUsed uint64 `json:"gasUsed"` + ParentHash common.Hash `json:"parentHash"` + BaseFee *big.Int `json:"baseFeePerGas"` // EIP-1559 + ExcessBlobGas *uint64 `json:"excessBlobGas"` // EIP-4844 + BlobGasUsed *uint64 `json:"blobGasUsed"` // EIP-4844 + BlockAccessListHash *common.Hash `json:"blockAccessListHash,omitempty"` // EIP-7928 + SlotNumber *uint64 `json:"slotNumber"` // EIP-7843 } // copy copies the genesis. @@ -123,6 +124,7 @@ func ReadGenesis(db ethdb.Database) (*Genesis, error) { genesis.BaseFee = genesisHeader.BaseFee genesis.ExcessBlobGas = genesisHeader.ExcessBlobGas genesis.BlobGasUsed = genesisHeader.BlobGasUsed + genesis.BlockAccessListHash = genesisHeader.BlockAccessListHash genesis.SlotNumber = genesisHeader.SlotNumber return &genesis, nil @@ -487,18 +489,19 @@ func (g *Genesis) ToBlock() *types.Block { // toBlockWithRoot constructs the genesis block with the given genesis state root. func (g *Genesis) toBlockWithRoot(root common.Hash) *types.Block { head := &types.Header{ - Number: new(big.Int).SetUint64(g.Number), - Nonce: types.EncodeNonce(g.Nonce), - Time: g.Timestamp, - ParentHash: g.ParentHash, - Extra: g.ExtraData, - GasLimit: g.GasLimit, - GasUsed: g.GasUsed, - BaseFee: g.BaseFee, - Difficulty: g.Difficulty, - MixDigest: g.Mixhash, - Coinbase: g.Coinbase, - Root: root, + Number: new(big.Int).SetUint64(g.Number), + Nonce: types.EncodeNonce(g.Nonce), + Time: g.Timestamp, + ParentHash: g.ParentHash, + Extra: g.ExtraData, + GasLimit: g.GasLimit, + GasUsed: g.GasUsed, + BaseFee: g.BaseFee, + Difficulty: g.Difficulty, + MixDigest: g.Mixhash, + Coinbase: g.Coinbase, + BlockAccessListHash: g.BlockAccessListHash, + Root: root, } if g.GasLimit == 0 { head.GasLimit = params.GenesisGasLimit diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go new file mode 100644 index 0000000000..7431cb8923 --- /dev/null +++ b/core/parallel_state_processor.go @@ -0,0 +1,397 @@ +package core + +import ( + "cmp" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" + "github.com/ethereum/go-ethereum/core/vm" + "golang.org/x/sync/errgroup" + "runtime" + "slices" + "time" +) + +// ProcessResultWithMetrics wraps ProcessResult with some metrics that are +// emitted when executing blocks containing access lists. +type ProcessResultWithMetrics struct { + ProcessResult *ProcessResult + PreProcessTime time.Duration + StateTransitionMetrics *state.BALStateTransitionMetrics + // the time it took to execute all txs in the block + ExecTime time.Duration + PostProcessTime time.Duration +} + +// ParallelStateProcessor is used to execute and verify blocks containing +// access lists. +type ParallelStateProcessor struct { + *StateProcessor + vmCfg *vm.Config +} + +// NewParallelStateProcessor returns a new ParallelStateProcessor instance. +func NewParallelStateProcessor(chain *HeaderChain, vmConfig *vm.Config) ParallelStateProcessor { + res := NewStateProcessor(chain) + return ParallelStateProcessor{ + res, + vmConfig, + } +} + +func validateStateAccesses(lastIdx int, accessList bal.AccessListReader, localAccesses bal.StateAccesses) bool { + // 1. strip out any state in the localAccesses that was modified + muts := accessList.Mutations(lastIdx + 1) + for acct, mut := range *muts { + if _, exist := localAccesses[acct]; !exist { + continue + } + // delete any storage slots that were mutated from the read set + if len(localAccesses[acct]) > 0 { + for key, _ := range mut.StorageWrites { + if _, ok := localAccesses[acct][key]; ok { + delete(localAccesses[acct], key) + } + } + } + + if len(localAccesses[acct]) == 0 { + delete(localAccesses, acct) + } + } + if !accessList.Accesses().Eq(localAccesses) { + return false + } + return true +} + +// called by resultHandler when all transactions have successfully executed. +// performs post-tx state transition (system contracts and withdrawals) +// and calculates the ProcessResult, returning it to be sent on resCh +// by resultHandler +func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, tExecStart time.Time, accesses bal.StateAccesses, statedb *state.StateDB, prefetchReader state.Reader, results []txExecResult) *ProcessResultWithMetrics { + tExec := time.Since(tExecStart) + var requests [][]byte + tPostprocessStart := time.Now() + header := block.Header() + + context := NewEVMBlockContext(header, p.chain, nil) + lastBALIdx := len(block.Transactions()) + 1 + postTxState := statedb.WithReader(state.NewReaderWithTracker(state.NewReaderWithBlockLevelAccessList(prefetchReader, *block.AccessList(), lastBALIdx))) + + cfg := vm.Config{ + NoBaseFee: p.vmCfg.NoBaseFee, + EnablePreimageRecording: p.vmCfg.EnablePreimageRecording, + ExtraEips: slices.Clone(p.vmCfg.ExtraEips), + StatelessSelfValidation: p.vmCfg.StatelessSelfValidation, + EnableWitnessStats: p.vmCfg.EnableWitnessStats, + } + evm := vm.NewEVM(context, postTxState, p.chainConfig(), cfg) + + // 1. order the receipts by tx index + // 2. correctly calculate the cumulative gas used per receipt, returning bad block error if it goes over the allowed + slices.SortFunc(results, func(a, b txExecResult) int { + return cmp.Compare(a.receipt.TransactionIndex, b.receipt.TransactionIndex) + }) + + var ( + // total gas used not applying refunds + blockGas = uint64(0) + // total gas used applying refunds + execGas = uint64(0) + ) + + var allLogs []*types.Log + var allReceipts []*types.Receipt + for _, result := range results { + blockGas += result.blockGas + execGas += result.execGas + result.receipt.CumulativeGasUsed = blockGas + if blockGas > header.GasLimit { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")}, + } + } + allLogs = append(allLogs, result.receipt.Logs...) + allReceipts = append(allReceipts, result.receipt) + } + // Block gas limit is enforced against usedGas (pre-refund after Amsterdam, post-refund before). + if blockGas > header.GasLimit { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: fmt.Errorf("gas limit exceeded")}, + } + } + + var postMut bal.StateMutations + // Read requests if Prague is enabled. + if p.chainConfig().IsPrague(block.Number(), block.Time()) { + requests = [][]byte{} + var err error + // EIP-6110 + if err = ParseDepositLogs(&requests, allLogs, p.chainConfig()); err != nil { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: err}, + } + } + + // EIP-7002 + postMut, err = ProcessWithdrawalQueue(&requests, evm) + if err != nil { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: err}, + } + } + + // EIP-7251 + consolidationMut, err := ProcessConsolidationQueue(&requests, evm) + if err != nil { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: err}, + } + } + postMut.Merge(consolidationMut) + } + + // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) + postMut.Merge(p.chain.Engine().Finalize(p.chain, header, postTxState, block.Body())) + postTxAccesses := postTxState.Reader().(state.StateReaderTracker).GetStateAccessList() + + accessList := bal.NewAccessListReader(*block.AccessList()) + if !postMut.Eq(*accessList.MutationsAt(lastBALIdx)) { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: fmt.Errorf("mismatch between local/remote access list mutations for final idx")}, + } + } + + accesses.Merge(postTxAccesses) + if !validateStateAccesses(lastBALIdx, accessList, accesses) { + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{Error: fmt.Errorf("mismatch between local/remote access list for state accesses")}, + } + } + + tPostprocess := time.Since(tPostprocessStart) + + return &ProcessResultWithMetrics{ + ProcessResult: &ProcessResult{ + Receipts: allReceipts, + Requests: requests, + Logs: allLogs, + GasUsed: execGas, + }, + PostProcessTime: tPostprocess, + ExecTime: tExec, + } +} + +type txExecResult struct { + idx int // transaction index + receipt *types.Receipt + err error // non-EVM error which would render the block invalid + blockGas uint64 + execGas uint64 + + stateReads bal.StateAccesses +} + +// resultHandler polls until all transactions have finished executing and the +// state root calculation is complete. The result is emitted on resCh. +func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxReads bal.StateAccesses, statedb *state.StateDB, prefetchReader state.Reader, tExecStart time.Time, txResCh <-chan txExecResult, stateRootCalcResCh <-chan stateRootCalculationResult, resCh chan *ProcessResultWithMetrics) { + // 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd + // 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL) + var results []txExecResult + gp := NewGasPool(block.GasLimit()) + var execErr error + var numTxComplete int + + accesses := preTxReads + + if len(block.Transactions()) > 0 { + loop: + for { + select { + case res := <-txResCh: + if execErr == nil { + // short-circuit if invalid block was detected + if res.err != nil { + execErr = res.err + continue + } + + if err := gp.SubGas(res.receipt.CumulativeGasUsed); err != nil { + execErr = err + } else { + results = append(results, res) + accesses.Merge(res.stateReads) + } + } + numTxComplete++ + if numTxComplete == len(block.Transactions()) { + break loop + } + } + } + + if execErr != nil { + resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: execErr}} + return + } + } + + execResults := p.prepareExecResult(block, tExecStart, accesses, statedb, prefetchReader, results) + rootCalcRes := <-stateRootCalcResCh + + if execResults.ProcessResult.Error != nil { + resCh <- execResults + } else if rootCalcRes.err != nil { + resCh <- &ProcessResultWithMetrics{ProcessResult: &ProcessResult{Error: rootCalcRes.err}} + } else { + execResults.StateTransitionMetrics = rootCalcRes.metrics + resCh <- execResults + } +} + +type stateRootCalculationResult struct { + err error + metrics *state.BALStateTransitionMetrics + root common.Hash +} + +// calcAndVerifyRoot performs the post-state root hash calculation, verifying +// it against what is reported by the block and returning a result on resCh. +func (p *ParallelStateProcessor) calcAndVerifyRoot(block *types.Block, stateTransition *state.BALStateTransition, resCh chan stateRootCalculationResult) { + root := stateTransition.IntermediateRoot(false) + + res := stateRootCalculationResult{ + metrics: stateTransition.Metrics(), + } + + if root != block.Root() { + res.err = fmt.Errorf("state root mismatch. local: %x. remote: %x", root, block.Root()) + } + resCh <- res +} + +// execTx executes single transaction returning a result which includes state accessed/modified +func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transaction, balIdx int, db *state.StateDB, signer types.Signer) *txExecResult { + header := block.Header() + context := NewEVMBlockContext(header, p.chain, nil) + + cfg := vm.Config{ + NoBaseFee: p.vmCfg.NoBaseFee, + EnablePreimageRecording: p.vmCfg.EnablePreimageRecording, + ExtraEips: slices.Clone(p.vmCfg.ExtraEips), + StatelessSelfValidation: p.vmCfg.StatelessSelfValidation, + EnableWitnessStats: p.vmCfg.EnableWitnessStats, + } + evm := vm.NewEVM(context, db, p.chainConfig(), cfg) + + msg, err := TransactionToMessage(tx, signer, header.BaseFee) + if err != nil { + err = fmt.Errorf("could not apply tx %d [%v]: %w", balIdx, tx.Hash().Hex(), err) + return &txExecResult{err: err} + } + gp := NewGasPool(block.GasLimit()) + db.SetTxContext(tx.Hash(), balIdx-1) + mut, receipt, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, evm) + if err != nil { + err := fmt.Errorf("could not apply tx %d [%v]: %w", balIdx, tx.Hash().Hex(), err) + return &txExecResult{err: err} + } + + accessList := bal.NewAccessListReader(*block.AccessList()) + if !accessList.MutationsAt(balIdx).Eq(mut) { + err := fmt.Errorf("mismatch between local/remote computed state mutations at bal idx %d. got:\n%s\nexpected:\n%s\n", balIdx, mut.String(), accessList.MutationsAt(balIdx).String()) + return &txExecResult{err: err} + } + + return &txExecResult{ + idx: balIdx, + receipt: receipt, + execGas: receipt.GasUsed, + blockGas: gp.CumulativeUsed(), + stateReads: db.Reader().(state.StateReaderTracker).GetStateAccessList(), + } +} + +func (p *ParallelStateProcessor) processBlockPreTx(block *types.Block, statedb *state.StateDB, prefetchReader state.Reader, cfg vm.Config) (bal.StateAccesses, error) { + var ( + header = block.Header() + ) + + alReader := state.NewReaderWithBlockLevelAccessList(prefetchReader, *block.AccessList(), 0) + readerWithTracker := state.NewReaderWithTracker(alReader) + sdb := statedb.WithReader(readerWithTracker) + accessList := bal.NewAccessListReader(*block.AccessList()) + + context := NewEVMBlockContext(header, p.chain, nil) + evm := vm.NewEVM(context, sdb, p.chainConfig(), cfg) + + var mutations bal.StateMutations + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + mutations = ProcessBeaconBlockRoot(*beaconRoot, evm) + } + + pbhMutations := ProcessParentBlockHash(block.ParentHash(), evm) + mutations.Merge(pbhMutations) + reads := readerWithTracker.(state.StateReaderTracker).GetStateAccessList() + if !accessList.MutationsAt(0).Eq(mutations) { + return nil, fmt.Errorf("mismatch between local/remote access list mutations at idx 0") + } + return reads, nil +} + +// Process performs EVM execution and state root computation for a block which is known +// to contain an access list. +func (p *ParallelStateProcessor) Process(block *types.Block, stateTransition *state.BALStateTransition, statedb *state.StateDB, cfg vm.Config) (*ProcessResultWithMetrics, error) { + var ( + header = block.Header() + resCh = make(chan *ProcessResultWithMetrics) + signer = types.MakeSigner(p.chainConfig(), header.Number, header.Time) + rootCalcResultCh = make(chan stateRootCalculationResult) + txResCh = make(chan txExecResult) + + pStart = time.Now() + tExecStart time.Time + tPreprocess time.Duration // time to create a set of prestates for parallel transaction execution + balReader = statedb.Reader() + ) + + startingState := statedb.Copy() + preReads, err := p.processBlockPreTx(block, statedb, balReader, cfg) + if err != nil { + return nil, err + } + + // compute the reads/mutations at the last bal index + tPreprocess = time.Since(pStart) + + // execute transactions and state root calculation in parallel + tExecStart = time.Now() + go p.resultHandler(block, preReads, statedb, balReader, tExecStart, txResCh, rootCalcResultCh, resCh) + var workers errgroup.Group + workers.SetLimit(runtime.NumCPU()) + for i, t := range block.Transactions() { + tx := t + idx := i + sdb := startingState.Copy() + workers.Go(func() error { + startingStateWithReadTracker := sdb.WithReader(state.NewReaderWithTracker(state.NewReaderWithBlockLevelAccessList(balReader, *block.AccessList(), idx+1))) + res := p.execTx(block, tx, idx+1, startingStateWithReadTracker, signer) + txResCh <- *res + return nil + }) + } + + go p.calcAndVerifyRoot(block, stateTransition, rootCalcResultCh) + + res := <-resCh + if res.ProcessResult.Error != nil { + return nil, res.ProcessResult.Error + } + // TODO: remove preprocess metric ? + res.PreProcessTime = tPreprocess + return res, nil +} diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 6ae64fb2fd..18e960f892 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -20,6 +20,7 @@ import ( "bytes" "encoding/binary" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "slices" @@ -421,6 +422,17 @@ func WriteBodyRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp } } +func WriteAccessListRLP(db ethdb.KeyValueWriter, hash common.Hash, number uint64, rlp rlp.RawValue) { + if err := db.Put(accessListKey(number, hash), rlp); err != nil { + log.Crit("failed to store block access list", "err", err) + } +} + +func ReadAccessListRLP(db ethdb.Reader, hash common.Hash, number uint64) rlp.RawValue { + data, _ := db.Get(accessListKey(number, hash)) + return data +} + // HasBody verifies the existence of a block body corresponding to the hash. func HasBody(db ethdb.Reader, hash common.Hash, number uint64) bool { if isCanon(db, number, hash) { @@ -455,6 +467,26 @@ func WriteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64, body *t WriteBodyRLP(db, hash, number, data) } +func ReadAccessList(db ethdb.Reader, hash common.Hash, number uint64) *bal.BlockAccessList { + var al bal.BlockAccessList + data := ReadAccessListRLP(db, hash, number) + if data != nil { + err := rlp.DecodeBytes(data, &al) + if err != nil { + log.Crit("failed to RLP decode access list", "err", err) + } + } + return &al +} + +func WriteAccessList(db ethdb.KeyValueWriter, hash common.Hash, number uint64, al *bal.BlockAccessList) { + data, err := rlp.EncodeToBytes(al) + if err != nil { + log.Crit("failed to RLP encode block access list", "err", err) + } + WriteAccessListRLP(db, hash, number, data) +} + // DeleteBody removes all block body data associated with a hash. func DeleteBody(db ethdb.KeyValueWriter, hash common.Hash, number uint64) { if err := db.Delete(blockBodyKey(number, hash)); err != nil { @@ -659,13 +691,25 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { if body == nil { return nil } - return types.NewBlockWithHeader(header).WithBody(*body) + + block := types.NewBlockWithHeader(header).WithBody(*body) + + if header.BlockAccessListHash != nil { + accessList := ReadAccessList(db, hash, number) + if accessList != nil { + block = block.WithAccessList(accessList) + } + } + return block } // WriteBlock serializes a block into the database, header and body separately. func WriteBlock(db ethdb.KeyValueWriter, block *types.Block) { WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) WriteHeader(db, block.Header()) + if block.AccessList() != nil { + WriteAccessList(db, block.Hash(), block.NumberU64(), block.AccessList()) + } } // WriteAncientBlocks writes entire block data into ancient store and returns the total written size. diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index d9140c5fd6..f20cf06b8e 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -111,6 +111,7 @@ var ( headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian) blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body + accessListPrefix = []byte("z") blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata @@ -209,6 +210,11 @@ func blockBodyKey(number uint64, hash common.Hash) []byte { return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) } +// accessListKey = accessListPrefix + num (uint64 big endian) + hash +func accessListKey(number uint64, hash common.Hash) []byte { + return append(append(accessListPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + // blockReceiptsKey = blockReceiptsPrefix + num (uint64 big endian) + hash func blockReceiptsKey(number uint64, hash common.Hash) []byte { return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) diff --git a/core/state/bal_state_transition.go b/core/state/bal_state_transition.go new file mode 100644 index 0000000000..1208577cc9 --- /dev/null +++ b/core/state/bal_state_transition.go @@ -0,0 +1,514 @@ +package state + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie/trienode" + "github.com/holiman/uint256" + "golang.org/x/sync/errgroup" + "maps" + "sync" + "sync/atomic" + "time" +) + +// BALStateTransition is responsible for performing the state root update +// and commit for EIP 7928 access-list-containing blocks. An instance of +// this object is only used for a single block. +type BALStateTransition struct { + accessList bal.AccessListReader + db Database + reader Reader + stateTrie Trie + parentRoot common.Hash + + // the computed state root of the block + rootHash common.Hash + // the state modifications performed by the block + diffs bal.StateMutations + + // a map of common.Address -> *types.StateAccount containing the block + // prestate of all accounts that will be modified + prestates sync.Map + + postStates map[common.Address]*types.StateAccount + // a map of common.Address -> Trie containing the account tries for all + // accounts with mutated storage + tries sync.Map //map[common.Address]Trie + deletions map[common.Address]struct{} + + accountDeleted int64 + accountUpdated int64 + storageDeleted atomic.Int64 + storageUpdated atomic.Int64 + + stateUpdate *stateUpdate + + metrics BALStateTransitionMetrics + maxBALIdx int + + err error +} + +func (s *BALStateTransition) Metrics() *BALStateTransitionMetrics { + return &s.metrics +} + +type BALStateTransitionMetrics struct { + // trie hashing metrics + AccountUpdate time.Duration + StatePrefetch time.Duration + StateUpdate time.Duration + StateHash time.Duration + OriginStorageLoadTime time.Duration + + // commit metrics + AccountCommits time.Duration + StorageCommits time.Duration + SnapshotCommits time.Duration + TrieDBCommits time.Duration + TotalCommitTime time.Duration +} + +func NewBALStateTransition(block *types.Block, prefetchReader Reader, db Database, parentRoot common.Hash) (*BALStateTransition, error) { + stateTrie, err := db.OpenTrie(parentRoot) + if err != nil { + return nil, err + } + + return &BALStateTransition{ + accessList: bal.NewAccessListReader(*block.AccessList()), + db: db, + reader: prefetchReader, + stateTrie: stateTrie, + parentRoot: parentRoot, + rootHash: common.Hash{}, + diffs: make(bal.StateMutations), + prestates: sync.Map{}, + postStates: make(map[common.Address]*types.StateAccount), + tries: sync.Map{}, + deletions: make(map[common.Address]struct{}), + stateUpdate: nil, + maxBALIdx: len(block.Transactions()) + 1, + }, nil +} + +func (s *BALStateTransition) Error() error { + return s.err +} + +func (s *BALStateTransition) setError(err error) { + if s.err != nil { + s.err = err + } +} + +// TODO: refresh my knowledge of the storage-clearing EIP and ensure that my assumptions around +// an empty account which contains storage are valid here. +// +// isAccountDeleted checks whether the state account was deleted in this block. Post selfdestruct-removal, +// deletions can only occur if an account which has a balance becomes the target of a CREATE2 initcode +// which calls SENDALL, clearing the account and marking it for deletion. +func isAccountDeleted(prestate *types.StateAccount, mutations bal.AccountMutations) bool { + // TODO: figure out how to simplify this method + if mutations.Code != nil && len(mutations.Code) != 0 { + return false + } + if mutations.Nonce != nil && *mutations.Nonce != 0 { + return false + } + if mutations.StorageWrites != nil && len(mutations.StorageWrites) > 0 { + return false + } + if mutations.Balance != nil { + if mutations.Balance.IsZero() { + if prestate.Nonce != 0 || prestate.Balance.IsZero() || common.BytesToHash(prestate.CodeHash) != types.EmptyCodeHash { + return false + } + // consider an empty account with storage to be deleted, so we don't check root here + return true + } + } + return false +} + +// updateAccount applies the block state mutations to a given account returning +// the updated state account and new code (if the account code changed) +func (s *BALStateTransition) updateAccount(addr common.Address) (*types.StateAccount, []byte) { + a, _ := s.prestates.Load(addr) + acct := a.(*types.StateAccount) + + acct, diff := acct.Copy(), s.diffs[addr] + code := diff.Code + + if diff.Nonce != nil { + acct.Nonce = *diff.Nonce + } + if diff.Balance != nil { + acct.Balance = new(uint256.Int).Set(diff.Balance) + } + if tr, ok := s.tries.Load(addr); ok { + acct.Root = tr.(Trie).Hash() + } + return acct, code +} + +func (s *BALStateTransition) commitAccount(addr common.Address) (*accountUpdate, *trienode.NodeSet, error) { + var ( + encode = func(val common.Hash) []byte { + if val == (common.Hash{}) { + return nil + } + blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:])) + return blob + } + ) + op := &accountUpdate{ + address: addr, + data: types.SlimAccountRLP(*s.postStates[addr]), // TODO: cache the updated state acocunt somewhere + } + if prestate, exist := s.prestates.Load(addr); exist { + prestate := prestate.(*types.StateAccount) + op.origin = types.SlimAccountRLP(*prestate) + } + + if s.diffs[addr].Code != nil { + code := contractCode{ + hash: crypto.Keccak256Hash(s.diffs[addr].Code), + blob: s.diffs[addr].Code, + } + if op.origin == nil { + code.originHash = types.EmptyCodeHash + } else { + code.originHash = crypto.Keccak256Hash(op.origin) + } + op.code = &code + } + + if len(s.diffs[addr].StorageWrites) == 0 { + return op, nil, nil + } + + op.storages = make(map[common.Hash][]byte) + op.storagesOriginByHash = make(map[common.Hash][]byte) + op.storagesOriginByKey = make(map[common.Hash][]byte) + + for key, value := range s.diffs[addr].StorageWrites { + hash := crypto.Keccak256Hash(key[:]) + op.storages[hash] = encode(value) + storage, err := s.reader.Storage(addr, key) + if err != nil { + return nil, nil, err + } + origin := encode(storage) + op.storagesOriginByHash[hash] = origin + op.storagesOriginByKey[key] = origin + } + tr, _ := s.tries.Load(addr) + root, nodes := tr.(Trie).Commit(false) + s.postStates[addr].Root = root + return op, nodes, nil +} + +// CommitWithUpdate flushes mutated trie nodes and state accounts to disk. +func (s *BALStateTransition) CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) { + // 1) create a stateUpdate object + // Commit objects to the trie, measuring the elapsed time + var ( + commitStart = time.Now() + accountTrieNodesUpdated int + accountTrieNodesDeleted int + storageTrieNodesUpdated int + storageTrieNodesDeleted int + + lock sync.Mutex // protect two maps below + nodes = trienode.NewMergedNodeSet() // aggregated trie nodes + updates = make(map[common.Hash]*accountUpdate, len(s.diffs)) // aggregated account updates + + // merge aggregates the dirty trie nodes into the global set. + // + // Given that some accounts may be destroyed and then recreated within + // the same block, it's possible that a node set with the same owner + // may already exist. In such cases, these two sets are combined, with + // the later one overwriting the previous one if any nodes are modified + // or deleted in both sets. + // + // merge run concurrently across all the state objects and account trie. + merge = func(set *trienode.NodeSet) error { + if set == nil { + return nil + } + lock.Lock() + defer lock.Unlock() + + updates, deletes := set.Size() + if set.Owner == (common.Hash{}) { + accountTrieNodesUpdated += updates + accountTrieNodesDeleted += deletes + } else { + storageTrieNodesUpdated += updates + storageTrieNodesDeleted += deletes + } + return nodes.Merge(set) + } + ) + + destructedPrestates := make(map[common.Address]*types.StateAccount) + s.prestates.Range(func(key, value any) bool { + addr := key.(common.Address) + acct := value.(*types.StateAccount) + destructedPrestates[addr] = acct + return true + }) + + deletes, delNodes, err := handleDestruction(s.db, s.stateTrie, noStorageWiping, maps.Keys(s.deletions), destructedPrestates) + if err != nil { + return common.Hash{}, nil, err + } + for _, set := range delNodes { + if err := merge(set); err != nil { + return common.Hash{}, nil, err + } + } + + // Handle all state updates afterwards, concurrently to one another to shave + // off some milliseconds from the commit operation. Also accumulate the code + // writes to run in parallel with the computations. + var ( + start = time.Now() + root common.Hash + workers errgroup.Group + ) + // Schedule the account trie first since that will be the biggest, so give + // it the most time to crunch. + // + // TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain + // heads, which seems excessive given that it doesn't do hashing, it just + // shuffles some data. For comparison, the *hashing* at chain head is 2-3ms. + // We need to investigate what's happening as it seems something's wonky. + // Obviously it's not an end of the world issue, just something the original + // code didn't anticipate for. + workers.Go(func() error { + // Write the account trie changes, measuring the amount of wasted time + newroot, set := s.stateTrie.Commit(true) + root = newroot + + if err := merge(set); err != nil { + return err + } + s.metrics.AccountCommits = time.Since(start) + return nil + }) + + // Schedule each of the storage tries that need to be updated, so they can + // run concurrently to one another. + // + // TODO(karalabe): Experimentally, the account commit takes approximately the + // same time as all the storage commits combined, so we could maybe only have + // 2 threads in total. But that kind of depends on the account commit being + // more expensive than it should be, so let's fix that and revisit this todo. + for addr, _ := range s.diffs { + if _, isDeleted := s.deletions[addr]; isDeleted { + continue + } + + address := addr + // Run the storage updates concurrently to one another + workers.Go(func() error { + // Write any storage changes in the state object to its storage trie + update, set, err := s.commitAccount(address) + if err != nil { + return err + } + + if err := merge(set); err != nil { + return err + } + lock.Lock() + updates[crypto.Keccak256Hash(address[:])] = update + s.metrics.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime + lock.Unlock() + return nil + }) + } + // Wait for everything to finish and update the metrics + if err := workers.Wait(); err != nil { + return common.Hash{}, nil, err + } + + accountUpdatedMeter.Mark(s.accountUpdated) + storageUpdatedMeter.Mark(s.storageUpdated.Load()) + accountDeletedMeter.Mark(s.accountDeleted) + storageDeletedMeter.Mark(s.storageDeleted.Load()) + accountTrieUpdatedMeter.Mark(int64(accountTrieNodesUpdated)) + accountTrieDeletedMeter.Mark(int64(accountTrieNodesDeleted)) + storageTriesUpdatedMeter.Mark(int64(storageTrieNodesUpdated)) + storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) + + ret := newStateUpdate(noStorageWiping, s.parentRoot, root, block, deletes, updates, nodes) + + snapshotCommits, trieDBCommits, err := flushStateUpdate(s.db, block, ret) + if err != nil { + return common.Hash{}, nil, err + } + + s.metrics.SnapshotCommits, s.metrics.TrieDBCommits = snapshotCommits, trieDBCommits + s.metrics.TotalCommitTime = time.Since(commitStart) + return root, ret, nil +} +func (s *BALStateTransition) Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) { + root, _, err := s.CommitWithUpdate(block, deleteEmptyObjects, noStorageWiping) + return root, err +} + +// IntermediateRoot applies block state mutations and computes the updated state +// trie root. +func (s *BALStateTransition) IntermediateRoot(_ bool) common.Hash { + if s.rootHash != (common.Hash{}) { + return s.rootHash + } + + // State root calculation proceeds as follows: + + // 1 (b): load the origin storage values for all slots which were modified during the block (this is needed for computing the stateUpdate) + // 1 (c): update each mutated account, producing the post-block state object by applying the state mutations to the prestate (retrieved in 1a). + // 1 (d): prefetch the intermediate trie nodes of the mutated state set from the account trie. + // + // 2: compute the post-state root of the account trie + // + // Steps 1/2 are performed sequentially, with steps 1a-d performed in parallel + + start := time.Now() + + var wg sync.WaitGroup + + s.diffs = *s.accessList.Mutations(s.maxBALIdx + 1) + + for addr, d := range s.diffs { + wg.Add(1) + address := addr + diff := d + go func() { + defer wg.Done() + + // 1 (c): update each mutated account, producing the post-block state object by applying the state mutations to the prestate (retrieved in 1a). + acct, err := s.reader.Account(address) + if err != nil { + s.setError(err) + return + } + + if acct == nil { + acct = types.NewEmptyStateAccount() + } + s.prestates.Store(address, acct) + + if len(diff.StorageWrites) > 0 { + tr, err := s.db.OpenStorageTrie(s.parentRoot, address, acct.Root, s.stateTrie) + if err != nil { + s.setError(err) + return + } + s.tries.Store(address, tr) + + var ( + updateKeys, updateValues [][]byte + deleteKeys [][]byte + ) + for key, val := range diff.StorageWrites { + if val != (common.Hash{}) { + updateKeys = append(updateKeys, key[:]) + updateValues = append(updateValues, common.TrimLeftZeroes(val[:])) + + s.storageUpdated.Add(1) + } else { + deleteKeys = append(deleteKeys, key[:]) + + s.storageDeleted.Add(1) + } + } + if err := tr.UpdateStorageBatch(address, updateKeys, updateValues); err != nil { + s.setError(err) + return + } + + for _, key := range deleteKeys { + if err := tr.DeleteStorage(address, key); err != nil { + s.setError(err) + return + } + } + + hashStart := time.Now() + tr.Hash() + s.metrics.StateHash = time.Since(hashStart) + } + }() + } + + wg.Add(1) + // 1 (d): prefetch the intermediate trie nodes of the mutated state set from the account trie. + go func() { + defer wg.Done() + prefetchStart := time.Now() + var prefetchAddrs []common.Address + for addr, _ := range s.diffs { + prefetchAddrs = append(prefetchAddrs, addr) + } + if err := s.stateTrie.PrefetchAccount(prefetchAddrs); err != nil { + s.setError(err) + return + } + s.metrics.StatePrefetch = time.Since(prefetchStart) + }() + + wg.Wait() + s.metrics.AccountUpdate = time.Since(start) + + // 2: compute the post-state root of the account trie + stateUpdateStart := time.Now() + for mutatedAddr, _ := range s.diffs { + p, _ := s.prestates.Load(mutatedAddr) + prestate := p.(*types.StateAccount) + + isDeleted := isAccountDeleted(prestate, s.diffs[mutatedAddr]) + if isDeleted { + if err := s.stateTrie.DeleteAccount(mutatedAddr); err != nil { + s.setError(err) + return common.Hash{} + } + s.deletions[mutatedAddr] = struct{}{} + } else { + acct, code := s.updateAccount(mutatedAddr) + + if code != nil { + codeHash := crypto.Keccak256Hash(code) + acct.CodeHash = codeHash.Bytes() + if err := s.stateTrie.UpdateContractCode(mutatedAddr, codeHash, code); err != nil { + s.setError(err) + return common.Hash{} + } + } + if err := s.stateTrie.UpdateAccount(mutatedAddr, acct, len(code)); err != nil { + s.setError(err) + return common.Hash{} + } + s.postStates[mutatedAddr] = acct + } + } + + s.metrics.StateUpdate = time.Since(stateUpdateStart) + + stateTrieHashStart := time.Now() + s.rootHash = s.stateTrie.Hash() + s.metrics.StateHash = time.Since(stateTrieHashStart) + return s.rootHash +} + +func (s *BALStateTransition) Preimages() map[common.Hash][]byte { + // TODO: implement this + return make(map[common.Hash][]byte) +} diff --git a/core/state/database.go b/core/state/database.go index 4a5547d075..d04bef4889 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -76,7 +76,7 @@ type Trie interface { // be returned. GetAccount(address common.Address) (*types.StateAccount, error) - // PrefetchAccount attempts to resolve specific accounts from the database + // PrefetchAccount attempts to schedule specific accounts from the database // to accelerate subsequent trie operations. PrefetchAccount([]common.Address) error @@ -85,7 +85,7 @@ type Trie interface { // a trie.MissingNodeError is returned. GetStorage(addr common.Address, key []byte) ([]byte, error) - // PrefetchStorage attempts to resolve specific storage slots from the database + // PrefetchStorage attempts to schedule specific storage slots from the database // to accelerate subsequent trie operations. PrefetchStorage(addr common.Address, keys [][]byte) error @@ -94,12 +94,18 @@ type Trie interface { // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error + // UpdateAccountBatch attempts to update a list accounts in the batch manner. + UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error + // UpdateStorage associates key with value in the trie. If value has length zero, // any existing value is deleted from the trie. The value bytes must not be modified // by the caller while they are stored in the trie. If a node was not found in the // database, a trie.MissingNodeError is returned. UpdateStorage(addr common.Address, key, value []byte) error + // UpdateStorageBatch attempts to update a list storages in the batch manner. + UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error + // DeleteAccount abstracts an account deletion from the trie. DeleteAccount(address common.Address) error @@ -221,21 +227,35 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) { return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), sr), nil } -// ReadersWithCacheStats creates a pair of state readers that share the same -// underlying state reader and internal state cache, while maintaining separate -// statistics respectively. -func (db *CachingDB) ReadersWithCacheStats(stateRoot common.Hash) (ReaderWithStats, ReaderWithStats, error) { +// ReadersWithCache creates a pair of state readers that share the same +// underlying state reader and internal state cache, while maintaining +// separate statistics respectively. +func (db *CachingDB) ReadersWithCache(stateRoot common.Hash) (Reader, Reader, error) { r, err := db.StateReader(stateRoot) if err != nil { return nil, nil, err } sr := newStateReaderWithCache(r) - - ra := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) - rb := newReaderWithStats(sr, newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache)) + ra := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr)) + rb := newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), newStateReaderWithStats(sr)) return ra, rb, nil } +// ReaderEIP7928 creates a state reader with the manner of Block-level accessList. +func (db *CachingDB) ReaderEIP7928(stateRoot common.Hash, accessList map[common.Address][]common.Hash, threads int) (Reader, error) { + base, err := db.StateReader(stateRoot) + if err != nil { + return nil, err + } + // Construct the state reader with native cache and associated statistics + r := newStateReaderWithStats(newStateReaderWithCache(base)) + + // Construct the state reader with background prefetching + pr := newPrefetchStateReader(r, accessList, threads) + + return newReader(newCachingCodeReader(db.disk, db.codeCache, db.codeSizeCache), pr), nil +} + // OpenTrie opens the main account trie at a specific root hash. func (db *CachingDB) OpenTrie(root common.Hash) (Trie, error) { if db.triedb.IsVerkle() { diff --git a/core/state/iterator.go b/core/state/iterator.go index 0abae091d9..0a4d864f0e 100644 --- a/core/state/iterator.go +++ b/core/state/iterator.go @@ -29,7 +29,7 @@ import ( // nodeIterator is an iterator to traverse the entire state trie post-order, // including all of the contract code and contract state tries. Preimage is -// required in order to resolve the contract address. +// required in order to schedule the contract address. type nodeIterator struct { state *StateDB // State being iterated tr Trie // Primary account trie for traversal diff --git a/core/state/journal.go b/core/state/journal.go index a79bd7331a..978ac9352e 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -382,7 +382,7 @@ func (ch nonceChange) copy() journalEntry { } func (ch codeChange) revert(s *StateDB) { - s.getStateObject(ch.account).setCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode) + s.getStateObject(ch.account).SetCode(crypto.Keccak256Hash(ch.prevCode), ch.prevCode) } func (ch codeChange) dirtied() (common.Address, bool) { diff --git a/core/state/reader.go b/core/state/reader.go index 35b732173b..0835d9a0ef 100644 --- a/core/state/reader.go +++ b/core/state/reader.go @@ -18,7 +18,6 @@ package state import ( "errors" - "fmt" "sync" "sync/atomic" @@ -38,6 +37,8 @@ import ( ) // ContractCodeReader defines the interface for accessing contract code. +// +// ContractCodeReader is supposed to be thread-safe. type ContractCodeReader interface { // Has returns the flag indicating whether the contract code with // specified address and hash exists or not. @@ -58,35 +59,10 @@ type ContractCodeReader interface { CodeSize(addr common.Address, codeHash common.Hash) (int, error) } -// ContractCodeReaderStats aggregates statistics for the contract code reader. -type ContractCodeReaderStats struct { - CacheHit int64 // Number of cache hits - CacheMiss int64 // Number of cache misses - CacheHitBytes int64 // Total bytes served from cache - CacheMissBytes int64 // Total bytes read on cache misses -} - -// HitRate returns the cache hit rate. -func (s ContractCodeReaderStats) HitRate() float64 { - if s.CacheHit == 0 { - return 0 - } - return float64(s.CacheHit) / float64(s.CacheHit+s.CacheMiss) -} - -// ContractCodeReaderWithStats extends ContractCodeReader by adding GetStats to -// expose statistics of code reader. -type ContractCodeReaderWithStats interface { - ContractCodeReader - - GetStats() ContractCodeReaderStats -} - // StateReader defines the interface for accessing accounts and storage slots // associated with a specific state. // -// StateReader is assumed to be thread-safe and implementation must take care -// of the concurrency issue by themselves. +// StateReader is supposed to be thread-safe. type StateReader interface { // Account retrieves the account associated with a particular address. // @@ -114,40 +90,6 @@ type Reader interface { StateReader } -// ReaderStats wraps the statistics of reader. -type ReaderStats struct { - AccountCacheHit int64 - AccountCacheMiss int64 - StorageCacheHit int64 - StorageCacheMiss int64 - CodeStats ContractCodeReaderStats -} - -// String implements fmt.Stringer, returning string format statistics. -func (s ReaderStats) String() string { - var ( - accountCacheHitRate float64 - storageCacheHitRate float64 - ) - if s.AccountCacheHit > 0 { - accountCacheHitRate = float64(s.AccountCacheHit) / float64(s.AccountCacheHit+s.AccountCacheMiss) * 100 - } - if s.StorageCacheHit > 0 { - storageCacheHitRate = float64(s.StorageCacheHit) / float64(s.StorageCacheHit+s.StorageCacheMiss) * 100 - } - msg := fmt.Sprintf("Reader statistics\n") - msg += fmt.Sprintf("account: hit: %d, miss: %d, rate: %.2f\n", s.AccountCacheHit, s.AccountCacheMiss, accountCacheHitRate) - msg += fmt.Sprintf("storage: hit: %d, miss: %d, rate: %.2f\n", s.StorageCacheHit, s.StorageCacheMiss, storageCacheHitRate) - msg += fmt.Sprintf("code: hit: %d(%v), miss: %d(%v), rate: %.2f\n", s.CodeStats.CacheHit, common.StorageSize(s.CodeStats.CacheHitBytes), s.CodeStats.CacheMiss, common.StorageSize(s.CodeStats.CacheMissBytes), s.CodeStats.HitRate()) - return msg -} - -// ReaderWithStats wraps the additional method to retrieve the reader statistics from. -type ReaderWithStats interface { - Reader - GetStats() ReaderStats -} - // cachingCodeReader implements ContractCodeReader, accessing contract code either in // local key-value store or the shared code cache. // @@ -210,15 +152,16 @@ func (r *cachingCodeReader) CodeSize(addr common.Address, codeHash common.Hash) return len(code), nil } -// Has returns the flag indicating whether the contract code with -// specified address and hash exists or not. +// Has implements ContractCodeReader, returning the flag indicating whether +// the contract code with specified address and hash exists or not. func (r *cachingCodeReader) Has(addr common.Address, codeHash common.Hash) bool { code, _ := r.Code(addr, codeHash) return len(code) > 0 } -// GetStats returns the statistics of the code reader. -func (r *cachingCodeReader) GetStats() ContractCodeReaderStats { +// GetCodeStats implements ContractCodeReaderStater, returning the statistics +// of the code reader. +func (r *cachingCodeReader) GetCodeStats() ContractCodeReaderStats { return ContractCodeReaderStats{ CacheHit: r.hit.Load(), CacheMiss: r.miss.Load(), @@ -495,20 +438,6 @@ func (r *multiStateReader) Storage(addr common.Address, slot common.Hash) (commo return common.Hash{}, errors.Join(errs...) } -// reader is the wrapper of ContractCodeReader and StateReader interface. -type reader struct { - ContractCodeReader - StateReader -} - -// newReader constructs a reader with the supplied code reader and state reader. -func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader { - return &reader{ - ContractCodeReader: codeReader, - StateReader: stateReader, - } -} - // stateReaderWithCache is a wrapper around StateReader that maintains additional // state caches to support concurrent state access. type stateReaderWithCache struct { @@ -619,9 +548,10 @@ func (r *stateReaderWithCache) Storage(addr common.Address, slot common.Hash) (c return value, err } -type readerWithStats struct { +// stateReaderWithStats is a wrapper over the stateReaderWithCache, tracking +// the cache hit statistics of the reader. +type stateReaderWithStats struct { *stateReaderWithCache - ContractCodeReaderWithStats accountCacheHit atomic.Int64 accountCacheMiss atomic.Int64 @@ -629,11 +559,10 @@ type readerWithStats struct { storageCacheMiss atomic.Int64 } -// newReaderWithStats constructs the reader with additional statistics tracked. -func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats) *readerWithStats { - return &readerWithStats{ - stateReaderWithCache: sr, - ContractCodeReaderWithStats: cr, +// newReaderWithStats constructs the state reader with additional statistics tracked. +func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats { + return &stateReaderWithStats{ + stateReaderWithCache: sr, } } @@ -641,7 +570,7 @@ func newReaderWithStats(sr *stateReaderWithCache, cr ContractCodeReaderWithStats // The returned account might be nil if it's not existent. // // An error will be returned if the state is corrupted in the underlying reader. -func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, error) { +func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) { account, incache, err := r.stateReaderWithCache.account(addr) if err != nil { return nil, err @@ -659,7 +588,7 @@ func (r *readerWithStats) Account(addr common.Address) (*types.StateAccount, err // existent. // // An error will be returned if the state is corrupted in the underlying reader. -func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { +func (r *stateReaderWithStats) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { value, incache, err := r.stateReaderWithCache.storage(addr, slot) if err != nil { return common.Hash{}, err @@ -672,13 +601,51 @@ func (r *readerWithStats) Storage(addr common.Address, slot common.Hash) (common return value, nil } -// GetStats implements ReaderWithStats, returning the statistics of state reader. -func (r *readerWithStats) GetStats() ReaderStats { - return ReaderStats{ +// GetStateStats implements StateReaderStater, returning the statistics of the +// state reader. +func (r *stateReaderWithStats) GetStateStats() StateReaderStats { + return StateReaderStats{ AccountCacheHit: r.accountCacheHit.Load(), AccountCacheMiss: r.accountCacheMiss.Load(), StorageCacheHit: r.storageCacheHit.Load(), StorageCacheMiss: r.storageCacheMiss.Load(), - CodeStats: r.ContractCodeReaderWithStats.GetStats(), + } +} + +// reader aggregates a code reader and a state reader into a single object. +type reader struct { + ContractCodeReader + StateReader +} + +// newReader constructs a reader with the supplied code reader and state reader. +func newReader(codeReader ContractCodeReader, stateReader StateReader) *reader { + return &reader{ + ContractCodeReader: codeReader, + StateReader: stateReader, + } +} + +// GetCodeStats returns the statistics of code access. +func (r *reader) GetCodeStats() ContractCodeReaderStats { + if stater, ok := r.ContractCodeReader.(ContractCodeReaderStater); ok { + return stater.GetCodeStats() + } + return ContractCodeReaderStats{} +} + +// GetStateStats returns the statistics of state access. +func (r *reader) GetStateStats() StateReaderStats { + if stater, ok := r.StateReader.(StateReaderStater); ok { + return stater.GetStateStats() + } + return StateReaderStats{} +} + +// GetStats returns the aggregated statistics for both state and code access. +func (r *reader) GetStats() ReaderStats { + return ReaderStats{ + CodeStats: r.GetCodeStats(), + StateStats: r.GetStateStats(), } } diff --git a/core/state/reader_eip_7928.go b/core/state/reader_eip_7928.go new file mode 100644 index 0000000000..eb66f10992 --- /dev/null +++ b/core/state/reader_eip_7928.go @@ -0,0 +1,334 @@ +// Copyright 2026 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 + +// The EIP27928 reader utilizes a hierarchical architecture to optimize state +// access during block execution: +// +// - Base layer: The reader is initialized with the pre-transition state root, +// providing the access of the state. +// +// - Prefetching Layer: This base reader is wrapped by newPrefetchStateReader. +// Using an Access List hint, it asynchronously fetches required state data +// in the background, minimizing I/O blocking during transaction processing. +// +// - Execution Layer: To support parallel transaction execution within the EIP +// 7928 context, readers are wrapped in ReaderWithBlockLevelAccessList. +// This layer provides a "unified view" by merging the pre-transition state +// with mutated states from preceding transactions in the block. +// +// - Tracking Layer: Finally, the readerTracker wraps the execution reader to +// capture all state accesses made during a specific transaction. These individual +// access are subsequently merged to construct a comprehensive access list +// for the entire block. +// +// The architecture can be illustrated by the diagram below: + +// [ Block Level Access List ] <────────────────┐ +// ▲ │ (Merge) +// │ │ +// ┌───────┴───────┐ ┌───────┴───────┐ +// │ readerTracker │ │ readerTracker │ (Access Tracking) +// └───────┬───────┘ └───────┬───────┘ +// │ │ +// ┌──────────────┴──────────────┐ ┌──────────────┴──────────────┐ +// │ ReaderWithBlockLevelAL │ │ ReaderWithBlockLevelAL │ (Unified View) +// │ (Pre-state + Mutations) │ │ (Pre-state + Mutations) │ +// └──────────────┬──────────────┘ └──────────────┬──────────────┘ +// │ │ +// └────────────────┬─────────────────┘ +// │ +// ┌──────────────┴──────────────┐ +// │ newPrefetchStateReader │ (Async I/O) +// │ (Access List Hint driven) │ +// └──────────────┬──────────────┘ +// │ +// ┌──────────────┴──────────────┐ +// │ Base Reader │ (State Root) +// │ (State & Contract Code) │ +// └─────────────────────────────┘ + +import ( + "github.com/ethereum/go-ethereum/crypto" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" +) + +type fetchTask struct { + addr common.Address + slots []common.Hash +} + +func (t *fetchTask) weight() int { return 1 + len(t.slots) } + +type prefetchStateReader struct { + StateReader + tasks []*fetchTask + nThreads int + done chan struct{} + term chan struct{} + closeOnce sync.Once +} + +func newPrefetchStateReader(reader StateReader, accessList bal.StorageKeys, nThreads int) *prefetchStateReader { + tasks := make([]*fetchTask, 0, len(accessList)) + for addr, slots := range accessList { + tasks = append(tasks, &fetchTask{ + addr: addr, + slots: slots, + }) + } + return newPrefetchStateReaderInternal(reader, tasks, nThreads) +} + +func newPrefetchStateReaderInternal(reader StateReader, tasks []*fetchTask, nThreads int) *prefetchStateReader { + r := &prefetchStateReader{ + StateReader: reader, + tasks: tasks, + nThreads: nThreads, + done: make(chan struct{}), + term: make(chan struct{}), + } + go r.prefetch() + return r +} + +func (r *prefetchStateReader) Close() { + r.closeOnce.Do(func() { + close(r.term) + <-r.done + }) +} + +func (r *prefetchStateReader) Wait() error { + select { + case <-r.term: + return nil + case <-r.done: + return nil + } +} + +func (r *prefetchStateReader) prefetch() { + defer close(r.done) + + if len(r.tasks) == 0 { + return + } + var total int + for _, t := range r.tasks { + total += t.weight() + } + var ( + wg sync.WaitGroup + unit = (total + r.nThreads - 1) / r.nThreads // round-up the per worker unit + ) + for i := 0; i < r.nThreads; i++ { + start := i * unit + if start >= total { + break + } + limit := (i + 1) * unit + if i == r.nThreads-1 { + limit = total + } + // Schedule the worker for prefetching, the items on the range [start, limit) + // is exclusively assigned for this worker. + wg.Add(1) + go func(workerID, startW, endW int) { + r.process(startW, endW) + wg.Done() + }(i, start, limit) + } + wg.Wait() +} + +func (r *prefetchStateReader) process(start, limit int) { + var total = 0 + for _, t := range r.tasks { + tw := t.weight() + if total+tw > start { + s := 0 + if start > total { + s = start - total + } + l := tw + if limit < total+tw { + l = limit - total + } + for j := s; j < l; j++ { + select { + case <-r.term: + return + default: + if j == 0 { + r.StateReader.Account(t.addr) + } else { + r.StateReader.Storage(t.addr, t.slots[j-1]) + } + } + } + } + total += tw + if total >= limit { + return + } + } +} + +// ReaderWithBlockLevelAccessList provides state access that reflects the +// pre-transition state combined with the mutations made by transactions +// prior to TxIndex. +type ReaderWithBlockLevelAccessList struct { + Reader + AccessList bal.AccessListReader + TxIndex int +} + +func NewReaderWithBlockLevelAccessList(base Reader, accessList bal.BlockAccessList, txIndex int) *ReaderWithBlockLevelAccessList { + return &ReaderWithBlockLevelAccessList{ + Reader: base, + AccessList: bal.NewAccessListReader(accessList), + TxIndex: txIndex, + } +} + +// Account implements Reader, returning the account with the specific address. +func (r *ReaderWithBlockLevelAccessList) Account(addr common.Address) (acct *types.StateAccount, err error) { + acct, err = r.Reader.Account(addr) + if err != nil { + return nil, err + } + + mut := r.AccessList.AccountMutations(addr, r.TxIndex) + if mut == nil { + return + } + + if acct == nil { + acct = types.NewEmptyStateAccount() + } else { + // the account returned by the underlying reader is a reference + // copy it to avoid mutating the reader's instance + acct = acct.Copy() + } + + if mut.Balance != nil { + acct.Balance = mut.Balance + } + if mut.Code != nil { + codeHash := crypto.Keccak256Hash(mut.Code) + acct.CodeHash = codeHash[:] + } + if mut.Nonce != nil { + acct.Nonce = *mut.Nonce + } + return +} + +// Storage implements Reader, returning the storage slot with the specific +// address and slot key. +func (r *ReaderWithBlockLevelAccessList) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + + val := r.AccessList.Storage(addr, slot, r.TxIndex) + if val != nil { + return *val, nil + } + return r.Reader.Storage(addr, slot) +} + +// Has implements Reader, returning the flag indicating whether the contract +// code with specified address and hash exists or not. +func (r *ReaderWithBlockLevelAccessList) Has(addr common.Address, codeHash common.Hash) bool { + mut := r.AccessList.AccountMutations(addr, r.TxIndex) + if mut != nil && mut.Code != nil { + return crypto.Keccak256Hash(mut.Code) == codeHash + } + return r.Reader.Has(addr, codeHash) +} + +// Code implements Reader, returning the contract code with specified address +// and hash. +func (r *ReaderWithBlockLevelAccessList) Code(addr common.Address, codeHash common.Hash) ([]byte, error) { + mut := r.AccessList.AccountMutations(addr, r.TxIndex) + if mut != nil && mut.Code != nil && crypto.Keccak256Hash(mut.Code) == codeHash { + // TODO: need to copy here? + return mut.Code, nil + } + return r.Reader.Code(addr, codeHash) +} + +// CodeSize implements Reader, returning the contract code size with specified +// address and hash. +func (r *ReaderWithBlockLevelAccessList) CodeSize(addr common.Address, codeHash common.Hash) (int, error) { + mut := r.AccessList.AccountMutations(addr, r.TxIndex) + if mut != nil && mut.Code != nil && crypto.Keccak256Hash(mut.Code) == codeHash { + return len(mut.Code), nil + } + return r.Reader.CodeSize(addr, codeHash) +} + +// StateReaderTracker defines the capability to retrieve the access footprint +// recorded during state reading operations. +type StateReaderTracker interface { + GetStateAccessList() bal.StateAccesses +} + +func NewReaderWithTracker(r Reader) Reader { + return newReaderTracker(r) +} + +type readerTracker struct { + Reader + access bal.StateAccesses +} + +func newReaderTracker(reader Reader) *readerTracker { + return &readerTracker{ + Reader: reader, + access: make(bal.StateAccesses), + } +} + +// Account implements StateReader, tracking the accessed address locally. +func (r *readerTracker) Account(addr common.Address) (*types.StateAccount, error) { + _, exists := r.access[addr] + if !exists { + r.access[addr] = make(bal.StorageAccessList) + } + return r.Reader.Account(addr) +} + +// Storage implements StateReader, tracking the accessed slot identifier locally. +func (r *readerTracker) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + list, exists := r.access[addr] + if !exists { + list = make(bal.StorageAccessList) + r.access[addr] = list + } + list[slot] = struct{}{} + + return r.Reader.Storage(addr, slot) +} + +// GetStateAccessList implements StateReaderTracker, returning the access footprint. +func (r *readerTracker) GetStateAccessList() bal.StateAccesses { + return r.access +} diff --git a/core/state/reader_eip_7928_test.go b/core/state/reader_eip_7928_test.go new file mode 100644 index 0000000000..d9d20f6d7b --- /dev/null +++ b/core/state/reader_eip_7928_test.go @@ -0,0 +1,201 @@ +// Copyright 2026 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 ( + "fmt" + "maps" + "math/rand" + "sync" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/internal/testrand" +) + +type countingStateReader struct { + accounts map[common.Address]int + storages map[common.Address]map[common.Hash]int + lock sync.Mutex +} + +func newRefStateReader() *countingStateReader { + return &countingStateReader{ + accounts: make(map[common.Address]int), + storages: make(map[common.Address]map[common.Hash]int), + } +} + +func (r *countingStateReader) validate(total int) error { + var sum int + for addr, n := range r.accounts { + if n != 1 { + return fmt.Errorf("duplicated account access: %x-%d", addr, n) + } + sum += 1 + + slots, exists := r.storages[addr] + if !exists { + continue + } + for key, n := range slots { + if n != 1 { + return fmt.Errorf("duplicated storage access: %x-%x-%d", addr, key, n) + } + sum += 1 + } + } + for addr := range r.storages { + _, exists := r.accounts[addr] + if !exists { + return fmt.Errorf("dangling storage access: %x", addr) + } + } + if sum != total { + return fmt.Errorf("unexpected number of access, want: %d, got: %d", total, sum) + } + return nil +} + +func (r *countingStateReader) Account(addr common.Address) (*types.StateAccount, error) { + r.lock.Lock() + defer r.lock.Unlock() + + r.accounts[addr] += 1 + return nil, nil +} +func (r *countingStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + r.lock.Lock() + defer r.lock.Unlock() + + slots, exists := r.storages[addr] + if !exists { + slots = make(map[common.Hash]int) + r.storages[addr] = slots + } + slots[slot] += 1 + return common.Hash{}, nil +} + +func makeFetchTasks(n int) ([]*fetchTask, int) { + var ( + total int + tasks []*fetchTask + ) + for i := 0; i < n; i++ { + var slots []common.Hash + if rand.Intn(3) != 0 { + for j := 0; j < rand.Intn(100); j++ { + slots = append(slots, testrand.Hash()) + } + } + tasks = append(tasks, &fetchTask{ + addr: testrand.Address(), + slots: slots, + }) + total += len(slots) + 1 + } + return tasks, total +} + +func TestPrefetchReader(t *testing.T) { + type suite struct { + tasks []*fetchTask + threads int + total int + } + var suites []suite + for i := 0; i < 100; i++ { + tasks, total := makeFetchTasks(100) + suites = append(suites, suite{ + tasks: tasks, + threads: rand.Intn(30) + 1, + total: total, + }) + } + // num(tasks) < num(threads) + tasks, total := makeFetchTasks(1) + suites = append(suites, suite{ + tasks: tasks, + threads: 100, + total: total, + }) + for _, s := range suites { + r := newRefStateReader() + pr := newPrefetchStateReaderInternal(r, s.tasks, s.threads) + pr.Wait() + if err := r.validate(s.total); err != nil { + t.Fatal(err) + } + } +} + +func makeFakeSlots(n int) map[common.Hash]struct{} { + slots := make(map[common.Hash]struct{}) + for i := 0; i < n; i++ { + slots[testrand.Hash()] = struct{}{} + } + return slots +} + +type noopStateReader struct{} + +func (r *noopStateReader) Account(addr common.Address) (*types.StateAccount, error) { return nil, nil } +func (r *noopStateReader) Storage(addr common.Address, slot common.Hash) (common.Hash, error) { + return common.Hash{}, nil +} + +type noopCodeReader struct{} + +func (r *noopCodeReader) Has(addr common.Address, codeHash common.Hash) bool { return false } + +func (r *noopCodeReader) Code(addr common.Address, codeHash common.Hash) ([]byte, error) { + return nil, nil +} + +func (r *noopCodeReader) CodeSize(addr common.Address, codeHash common.Hash) (int, error) { + return 0, nil +} + +func TestReaderWithTracker(t *testing.T) { + var r Reader = newReaderTracker(newReader(&noopCodeReader{}, &noopStateReader{})) + + accesses := map[common.Address]map[common.Hash]struct{}{ + testrand.Address(): makeFakeSlots(10), + testrand.Address(): makeFakeSlots(0), + } + for addr, slots := range accesses { + r.Account(addr) + for slot := range slots { + r.Storage(addr, slot) + } + } + got := r.(StateReaderTracker).GetStateAccessList() + if len(got) != len(accesses) { + t.Fatalf("Unexpected access list, want: %d, got: %d", len(accesses), len(got)) + } + for addr, slots := range got { + entry, ok := accesses[addr] + if !ok { + t.Fatal("Unexpected access list") + } + if !maps.Equal(slots, entry) { + t.Fatal("Unexpected slots") + } + } +} diff --git a/core/state/reader_stater.go b/core/state/reader_stater.go new file mode 100644 index 0000000000..5294275953 --- /dev/null +++ b/core/state/reader_stater.go @@ -0,0 +1,82 @@ +// Copyright 2026 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 + +// ContractCodeReaderStats aggregates statistics for the contract code reader. +type ContractCodeReaderStats struct { + CacheHit int64 // Number of cache hits + CacheMiss int64 // Number of cache misses + CacheHitBytes int64 // Total bytes served from cache + CacheMissBytes int64 // Total bytes read on cache misses +} + +// HitRate returns the cache hit rate in percentage. +func (s ContractCodeReaderStats) HitRate() float64 { + total := s.CacheHit + s.CacheMiss + if total == 0 { + return 0 + } + return float64(s.CacheHit) / float64(total) * 100 +} + +// ContractCodeReaderStater wraps the method to retrieve the statistics of +// contract code reader. +type ContractCodeReaderStater interface { + GetCodeStats() ContractCodeReaderStats +} + +// StateReaderStats aggregates statistics for the state reader. +type StateReaderStats struct { + AccountCacheHit int64 // Number of account cache hits + AccountCacheMiss int64 // Number of account cache misses + StorageCacheHit int64 // Number of storage cache hits + StorageCacheMiss int64 // Number of storage cache misses +} + +// AccountCacheHitRate returns the cache hit rate of account requests in percentage. +func (s StateReaderStats) AccountCacheHitRate() float64 { + total := s.AccountCacheHit + s.AccountCacheMiss + if total == 0 { + return 0 + } + return float64(s.AccountCacheHit) / float64(total) * 100 +} + +// StorageCacheHitRate returns the cache hit rate of storage requests in percentage. +func (s StateReaderStats) StorageCacheHitRate() float64 { + total := s.StorageCacheHit + s.StorageCacheMiss + if total == 0 { + return 0 + } + return float64(s.StorageCacheHit) / float64(total) * 100 +} + +// StateReaderStater wraps the method to retrieve the statistics of state reader. +type StateReaderStater interface { + GetStateStats() StateReaderStats +} + +// ReaderStats wraps the statistics of reader. +type ReaderStats struct { + CodeStats ContractCodeReaderStats + StateStats StateReaderStats +} + +// ReaderStater defines the capability to retrieve aggregated statistics. +type ReaderStater interface { + GetStats() ReaderStats +} diff --git a/core/state/state_object.go b/core/state/state_object.go index f7109bddee..e9baf4737c 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -19,6 +19,7 @@ package state import ( "bytes" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "maps" "slices" "time" @@ -54,6 +55,9 @@ type stateObject struct { 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 + txPreBalance *uint256.Int // the account balance after the last call to finalise + txPreNonce uint64 // the account nonce after the last call to finalise + // Write caches. trie Trie // storage trie, which becomes non-nil on first access code []byte // contract bytecode, which gets set when code is loaded @@ -76,6 +80,9 @@ type stateObject struct { // Cache flags. dirtyCode bool // true if the code was updated + nonFinalizedCode bool // true if the code has been changed in the current transaction + txPrestateCode []byte // set to the value of the code at the beginning of the transaction if it changed in the current transaction + // Flag whether the account was marked as self-destructed. The self-destructed // account is still accessible in the scope of same transaction. selfDestructed bool @@ -85,6 +92,8 @@ type stateObject struct { // the contract is just created within the current transaction, or when the // object was previously existent and is being deployed as a contract within // the current transaction. + // + // the flag is set upon beginning of contract initcode execution, not when the code is actually deployed to the address. newContract bool } @@ -104,6 +113,8 @@ func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *s address: address, origin: origin, data: *acct, + txPreBalance: acct.Balance.Clone(), + txPreNonce: acct.Nonce, originStorage: make(Storage), dirtyStorage: make(Storage), pendingStorage: make(Storage), @@ -185,6 +196,7 @@ func (s *stateObject) GetCommittedState(key common.Hash) common.Hash { if value, pending := s.pendingStorage[key]; pending { return value } + if value, cached := s.originStorage[key]; cached { return value } @@ -240,6 +252,7 @@ func (s *stateObject) SetState(key, value common.Hash) common.Hash { if prev == value { return prev } + // New value is different, update and journal the change s.db.journal.storageChange(s.address, key, prev, origin) s.setState(key, value, origin) @@ -259,22 +272,64 @@ func (s *stateObject) setState(key common.Hash, value common.Hash, origin common // 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() { +func (s *stateObject) finalise() (mut *bal.AccountMutations) { + mut = &bal.AccountMutations{} + if s.Balance().Cmp(s.txPreBalance) != 0 { + mut.Balance = s.Balance() + } + if s.Nonce() != s.txPreNonce { + mut.Nonce = new(uint64) + *mut.Nonce = s.Nonce() + } + // include account code changes: created contracts and 7702 delegation authority code changes + if s.nonFinalizedCode { + if s.code == nil { + // code cleared (7702). code must be non-nil in the post to signal that it's part of the diff vs being unchanged. + mut.Code = []byte{} + } else { + mut.Code = s.code + } + } + + mut.StorageWrites = make(map[common.Hash]common.Hash) + slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage)) for key, value := range s.dirtyStorage { if origin, exist := s.uncommittedStorage[key]; exist && origin == value { + // non-parallel-execution: // The slot is reverted to its original value, delete the entry // to avoid thrashing the data structures. + // + // parallel-exec-with-BAL: + // each statedb instance only executes a single transaction so the previous value + // of the slot won't be in uncommittedStorage + txPrestateVal := s.GetCommittedState(key) + if txPrestateVal != value { + mut.StorageWrites[key] = value + } delete(s.uncommittedStorage, key) } else if exist { + // non-parallel-execution: // The slot is modified to another value and the slot has been - // tracked for commit, do nothing here. + // tracked for commit in uncommittedStorage. + // + // parallel-exec-with-BAL: + // each statedb instance only executes a single transaction so the previous value + // of the slot won't be in uncommittedStorage + mut.StorageWrites[key] = value } else { // The slot is different from its original value and hasn't been // tracked for commit yet. - s.uncommittedStorage[key] = s.GetCommittedState(key) + // Whether executing parallel with BAL or not, the value of the slot before the execution + // of the current transaction is in originStorage + origin := s.GetCommittedState(key) + if value != origin { + mut.StorageWrites[key] = value + } + s.uncommittedStorage[key] = origin slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure } + // Aggregate the dirty storage slots into the pending area. It might // be possible that the value of tracked slot here is same with the // one in originStorage (e.g. the slot was modified in tx_a and then @@ -283,6 +338,7 @@ func (s *stateObject) finalise() { // byzantium fork) and entry is necessary to modify the value back. s.pendingStorage[key] = value } + if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { if err := s.db.prefetcher.prefetch(s.addrHash(), s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil { log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err) @@ -295,6 +351,18 @@ func (s *stateObject) finalise() { // of the newly-created object as it's no longer eligible for self-destruct // by EIP-6780. For non-newly-created objects, it's a no-op. s.newContract = false + + s.nonFinalizedCode = false + s.txPrestateCode = nil + + // TODO: I had a bug here where i would set both of these to the value of s.data.* and there were no test failures. need to figure out why. + s.txPreBalance = s.Balance().Clone() + s.txPreNonce = s.Nonce() + + if mut.Nonce == nil && mut.Code == nil && mut.Balance == nil && len(mut.StorageWrites) == 0 { + return nil + } + return mut } // updateTrie is responsible for persisting cached storage changes into the @@ -314,6 +382,7 @@ func (s *stateObject) updateTrie() (Trie, error) { return s.trie, nil } } + // Retrieve a pretecher populated trie, or fall back to the database. This will // block until all prefetch tasks are done, which are needed for witnesses even // for unmodified state objects. @@ -345,8 +414,10 @@ func (s *stateObject) updateTrie() (Trie, error) { // into a shortnode. This requires `B` to be resolved from disk. // Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved. var ( - deletions []common.Hash - used = make([]common.Hash, 0, len(s.uncommittedStorage)) + deletions []common.Hash + used = make([]common.Hash, 0, len(s.uncommittedStorage)) + updateKeys [][]byte + updateValues [][]byte ) for key, origin := range s.uncommittedStorage { // Skip noop changes, persist actual changes @@ -360,10 +431,8 @@ func (s *stateObject) updateTrie() (Trie, error) { continue } if (value != common.Hash{}) { - if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil { - s.db.setError(err) - return nil, err - } + updateKeys = append(updateKeys, key[:]) + updateValues = append(updateValues, common.TrimLeftZeroes(value[:])) s.db.StorageUpdated.Add(1) } else { deletions = append(deletions, key) @@ -371,6 +440,12 @@ func (s *stateObject) updateTrie() (Trie, error) { // Cache the items for preloading used = append(used, key) // Copy needed for closure } + if len(updateKeys) > 0 { + if err := tr.UpdateStorageBatch(common.Address{}, updateKeys, updateValues); err != nil { + s.db.setError(err) + return nil, err + } + } for _, key := range deletions { if err := tr.DeleteStorage(s.address, key[:]); err != nil { s.db.setError(err) @@ -522,6 +597,8 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject { dirtyCode: s.dirtyCode, selfDestructed: s.selfDestructed, newContract: s.newContract, + txPreBalance: s.txPreBalance.Clone(), + txPreNonce: s.txPreNonce, } switch s.trie.(type) { @@ -604,13 +681,25 @@ func (s *stateObject) SetCode(codeHash common.Hash, code []byte) (prev []byte) { prev = slices.Clone(s.code) s.db.journal.setCode(s.address, prev) s.setCode(codeHash, code) + if s.txPrestateCode == nil { + if prev == nil { + prev = []byte{} + } + s.txPrestateCode = prev + } + if !bytes.Equal(code, s.txPrestateCode) { + s.dirtyCode = true + s.nonFinalizedCode = true + } else { + s.nonFinalizedCode = false + } + return prev } func (s *stateObject) setCode(codeHash common.Hash, code []byte) { s.code = code s.data.CodeHash = codeHash[:] - s.dirtyCode = true } func (s *stateObject) SetNonce(nonce uint64) { diff --git a/core/state/statedb.go b/core/state/statedb.go index 9c88f3a236..45ce1af6fc 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -20,6 +20,8 @@ package state import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" + "iter" "maps" "slices" "sort" @@ -65,6 +67,14 @@ func (m *mutation) isDelete() bool { return m.typ == deletion } +type BlockStateTransition interface { + CommitWithUpdate(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, *stateUpdate, error) + Commit(block uint64, deleteEmptyObjects bool, noStorageWiping bool) (common.Hash, error) + IntermediateRoot(deleteEmpty bool) common.Hash + Error() error + Preimages() map[common.Hash][]byte +} + // 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: @@ -118,6 +128,13 @@ type StateDB struct { // The tx context and all occurred logs in the scope of transaction. thash common.Hash txIndex int + + // block access list modifications will be recorded with this index. + // 0 - state access before transaction execution + // 1 -> len(block txs) - state access of each transaction + // len(block txs) + 1 - state access after transaction execution. + balIndex int + logs map[common.Hash][]*types.Log logSize uint @@ -200,6 +217,13 @@ func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, erro return sdb, nil } +// WithReader returns a copy of the statedb instance with the specified reader. +func (s *StateDB) WithReader(reader Reader) *StateDB { + cpy := s.Copy() + cpy.reader = reader + return cpy +} + // 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. @@ -315,6 +339,11 @@ func (s *StateDB) Exist(addr common.Address) bool { return s.getStateObject(addr) != nil } +func (s *StateDB) ExistBeforeCurTx(addr common.Address) bool { + obj := s.getStateObject(addr) + return obj != nil && !obj.newContract +} + // Empty returns whether the state object is either non-existent // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr common.Address) bool { @@ -570,6 +599,25 @@ func (s *StateDB) updateStateObject(obj *stateObject) { s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) } } +func (s *StateDB) updateStateObjects(objs []*stateObject) { + var addrs []common.Address + var accts []*types.StateAccount + + for _, obj := range objs { + addrs = append(addrs, obj.Address()) + accts = append(accts, &obj.data) + } + + if err := s.trie.UpdateAccountBatch(addrs, accts, nil); err != nil { + s.setError(fmt.Errorf("updateStateObjects error: %v", err)) + } + + for _, obj := range objs { + if obj.dirtyCode { + s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code) + } + } +} // deleteStateObject removes the given object from the state trie. func (s *StateDB) deleteStateObject(addr common.Address) { @@ -589,6 +637,7 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { if _, ok := s.stateObjectsDestruct[addr]; ok { return nil } + s.AccountLoaded++ start := time.Now() @@ -625,6 +674,7 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject { if obj == nil { obj = s.createObject(addr) } + return obj } @@ -683,6 +733,7 @@ func (s *StateDB) Copy() *StateDB { refund: s.refund, thash: s.thash, txIndex: s.txIndex, + balIndex: s.txIndex, logs: make(map[common.Hash][]*types.Log, len(s.logs)), logSize: s.logSize, preimages: maps.Clone(s.preimages), @@ -770,8 +821,10 @@ func (s *StateDB) GetRemovedAccountsWithBalance() (list []RemovedAccountWithBala // Finalise finalises the state by removing the destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. -func (s *StateDB) Finalise(deleteEmptyObjects bool) { +func (s *StateDB) Finalise(deleteEmptyObjects bool) (mutations bal.StateMutations) { addressesToPrefetch := make([]common.Address, 0, len(s.journal.dirties)) + mutations = make(bal.StateMutations) + for addr := range s.journal.dirties { obj, exist := s.stateObjects[addr] if !exist { @@ -792,8 +845,19 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj } + + // a pre-existing account can only be removed from the state under the following circumstance: + // it had a balance and was the target of a create2 which selfdestructed in the initcode + if !obj.txPreBalance.IsZero() { + mutations[addr] = bal.AccountMutations{ + Balance: uint256.NewInt(0), + } + } } else { - obj.finalise() + mut := obj.finalise() + if mut != nil { + mutations[addr] = *mut + } s.markUpdate(addr) } // At this point, also ship the address off to the precacher. The precacher @@ -808,6 +872,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } // Invalidate journal because reverting across transactions is not allowed. s.clearJournalAndRefund() + return mutations } // IntermediateRoot computes the current root hash of the state trie. @@ -855,12 +920,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // later time. workers.SetLimit(1) } + var updatedAddrs []common.Address + for addr, op := range s.mutations { if op.applied || op.isDelete() { continue } - obj := s.stateObjects[addr] // closure for the task runner below + updatedAddrs = append(updatedAddrs, addr) + } + + for _, addr := range updatedAddrs { workers.Go(func() error { + obj := s.stateObjects[addr] // closure for the task runner below if s.db.TrieDB().IsVerkle() { obj.updateTrie() } else { @@ -955,6 +1026,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { var ( usedAddrs []common.Address deletedAddrs []common.Address + updatedObjs []*stateObject ) for addr, op := range s.mutations { if op.applied { @@ -966,7 +1038,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { deletedAddrs = append(deletedAddrs, addr) } else { obj := s.stateObjects[addr] - s.updateStateObject(obj) + updatedObjs = append(updatedObjs, obj) s.AccountUpdated += 1 // Count code writes post-Finalise so reverted CREATEs are excluded. @@ -977,6 +1049,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } usedAddrs = append(usedAddrs, addr) // Copy needed for closure } + if len(updatedObjs) > 0 { + s.updateStateObjects(updatedObjs) + } for _, deletedAddr := range deletedAddrs { s.deleteStateObject(deletedAddr) s.AccountDeleted += 1 @@ -988,9 +1063,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { } // Track the amount of time wasted on hashing the account trie defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) - hash := s.trie.Hash() - // If witness building is enabled, gather the account trie witness if s.witness != nil { witness := s.trie.Witness() @@ -999,6 +1072,7 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.witnessStats.Add(witness, common.Hash{}) } } + return hash } @@ -1008,6 +1082,14 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { func (s *StateDB) SetTxContext(thash common.Hash, ti int) { s.thash = thash s.txIndex = ti + s.balIndex = ti + 1 +} + +// SetAccessListIndex sets the current index that state mutations will +// be reported as in the BAL. It is only relevant if this StateDB instance +// is being used in the BAL construction path. +func (s *StateDB) SetAccessListIndex(idx int) { + s.balIndex = idx } func (s *StateDB) clearJournalAndRefund() { @@ -1019,8 +1101,8 @@ func (s *StateDB) clearJournalAndRefund() { // of a specific account. It leverages the associated state snapshot for fast // storage iteration and constructs trie node deletion markers by creating // stack trie with iterated slots. -func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { - iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{}) +func fastDeleteStorage(originalRoot common.Hash, snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { + iter, err := snaps.StorageIterator(originalRoot, addrHash, common.Hash{}) if err != nil { return nil, nil, nil, err } @@ -1059,8 +1141,8 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, // slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage," // employed when the associated state snapshot is not available. It iterates the // storage slots along with all internal trie nodes via trie directly. -func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { - tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie) +func slowDeleteStorage(db Database, trie Trie, originalRoot common.Hash, addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { + tr, err := db.OpenStorageTrie(originalRoot, addr, root, trie) if err != nil { return nil, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err) } @@ -1095,7 +1177,7 @@ func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, r // The function will make an attempt to utilize an efficient strategy if the // associated state snapshot is reachable; otherwise, it will resort to a less // efficient approach. -func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { +func deleteStorage(db Database, trie Trie, addr common.Address, addrHash common.Hash, root, originalRoot common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { var ( err error nodes *trienode.NodeSet // the set for trie node mutations (value is nil) @@ -1105,12 +1187,12 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root // The fast approach can be failed if the snapshot is not fully // generated, or it's internally corrupted. Fallback to the slow // one just in case. - snaps := s.db.Snapshot() + snaps := db.Snapshot() if snaps != nil { - storages, storageOrigins, nodes, err = s.fastDeleteStorage(snaps, addrHash, root) + storages, storageOrigins, nodes, err = fastDeleteStorage(originalRoot, snaps, addrHash, root) } if snaps == nil || err != nil { - storages, storageOrigins, nodes, err = s.slowDeleteStorage(addr, addrHash, root) + storages, storageOrigins, nodes, err = slowDeleteStorage(db, trie, originalRoot, addr, addrHash, root) } if err != nil { return nil, nil, nil, err @@ -1136,39 +1218,38 @@ func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root // with their values be tracked as original value. // In case (d), **original** account along with its storages should be deleted, // with their values be tracked as original value. -func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) { +func handleDestruction(db Database, trie Trie, noStorageWiping bool, destructions iter.Seq[common.Address], prestates map[common.Address]*types.StateAccount) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) { var ( nodes []*trienode.NodeSet deletes = make(map[common.Hash]*accountDelete) ) - for addr, prevObj := range s.stateObjectsDestruct { - prev := prevObj.origin - + for addr := range destructions { + prestate := prestates[addr] // The account was non-existent, and it's marked as destructed in the scope // of block. It can be either case (a) or (b) and will be interpreted as // null->null state transition. // - for (a), skip it without doing anything // - for (b), the resurrected account with nil as original will be handled afterwards - if prev == nil { + if prestate == nil { continue } // The account was existent, it can be either case (c) or (d). addrHash := crypto.Keccak256Hash(addr.Bytes()) op := &accountDelete{ address: addr, - origin: types.SlimAccountRLP(*prev), + origin: types.SlimAccountRLP(*prestate), } deletes[addrHash] = op // Short circuit if the origin storage was empty. - if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() { + if prestate.Root == types.EmptyRootHash || db.TrieDB().IsVerkle() { continue } if noStorageWiping { return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr) } // Remove storage slots belonging to the account. - storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root) + storages, storagesOrigin, set, err := deleteStorage(db, trie, addr, addrHash, prestate.Root, prestate.Root) if err != nil { return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err) } @@ -1193,6 +1274,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum if s.dbErr != nil { return nil, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr) } + // Finalize any pending changes and merge everything into the tries s.IntermediateRoot(deleteEmptyObjects) @@ -1242,7 +1324,12 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum // the same block, account deletions must be processed first. This ensures // that the storage trie nodes deleted during destruction and recreated // during subsequent resurrection can be combined correctly. - deletes, delNodes, err := s.handleDestruction(noStorageWiping) + var stateAccountsDestruct, destructAccountsOrigins = make(map[common.Address]*types.StateAccount), make(map[common.Address]*types.StateAccount) + for addr, obj := range s.stateObjectsDestruct { + stateAccountsDestruct[addr] = &obj.data + destructAccountsOrigins[addr] = obj.origin + } + deletes, delNodes, err := handleDestruction(s.db, s.trie, noStorageWiping, maps.Keys(stateAccountsDestruct), destructAccountsOrigins) if err != nil { return nil, err } @@ -1343,6 +1430,44 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil } +func flushStateUpdate(d Database, block uint64, update *stateUpdate) (snapshotCommits, trieDBCommits time.Duration, err error) { + if db := d.TrieDB().Disk(); db != nil && len(update.codes) > 0 { + batch := db.NewBatch() + for _, code := range update.codes { + rawdb.WriteCode(batch, code.hash, code.blob) + } + if err := batch.Write(); err != nil { + return 0, 0, err + } + } + if !update.empty() { + // If snapshotting is enabled, update the snapshot tree with this new version + if snap := d.Snapshot(); snap != nil && snap.Snapshot(update.originRoot) != nil { + start := time.Now() + if err := snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil { + log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err) + } + // Keep 128 diff layers in the memory, persistent layer is 129th. + // - head layer is paired with HEAD state + // - head-1 layer is paired with HEAD-1 state + // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state + if err := snap.Cap(update.root, TriesInMemory); err != nil { + log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) + } + snapshotCommits += time.Since(start) + } + // If trie database is enabled, commit the state update as a new layer + if db := d.TrieDB(); db != nil { + start := time.Now() + if err := db.Update(update.root, update.originRoot, block, update.nodes, update.stateSet()); err != nil { + return 0, 0, err + } + trieDBCommits += time.Since(start) + } + } + return snapshotCommits, trieDBCommits, nil +} + // commitAndFlush is a wrapper of commit which also commits the state mutations // to the configured data stores. func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorageWiping bool, deriveCodeFields bool) (*stateUpdate, error) { @@ -1350,46 +1475,18 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag if err != nil { return nil, err } + // TODO: move this check inside flushStateUpdate? if deriveCodeFields { if err := ret.deriveCodeFields(s.reader); err != nil { return nil, err } } - // Commit dirty contract code if any exists - if db := s.db.TrieDB().Disk(); db != nil && len(ret.codes) > 0 { - batch := db.NewBatch() - for _, code := range ret.codes { - rawdb.WriteCode(batch, code.hash, code.blob) - } - if err := batch.Write(); err != nil { - return nil, err - } - } - if !ret.empty() { - // If snapshotting is enabled, update the snapshot tree with this new version - if snap := s.db.Snapshot(); snap != nil && snap.Snapshot(ret.originRoot) != nil { - start := time.Now() - if err := snap.Update(ret.root, ret.originRoot, ret.accounts, ret.storages); err != nil { - log.Warn("Failed to update snapshot tree", "from", ret.originRoot, "to", ret.root, "err", err) - } - // Keep 128 diff layers in the memory, persistent layer is 129th. - // - head layer is paired with HEAD state - // - head-1 layer is paired with HEAD-1 state - // - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state - if err := snap.Cap(ret.root, TriesInMemory); err != nil { - log.Warn("Failed to cap snapshot tree", "root", ret.root, "layers", TriesInMemory, "err", err) - } - s.SnapshotCommits += time.Since(start) - } - // If trie database is enabled, commit the state update as a new layer - if db := s.db.TrieDB(); db != nil { - start := time.Now() - if err := db.Update(ret.root, ret.originRoot, block, ret.nodes, ret.stateSet()); err != nil { - return nil, err - } - s.TrieDBCommits += time.Since(start) - } + snapshotCommits, trieDBCommits, err := flushStateUpdate(s.db, block, ret) + if err != nil { + return nil, err } + s.SnapshotCommits = snapshotCommits + s.TrieDBCommits = trieDBCommits s.reader, _ = s.db.Reader(s.originalRoot) return ret, err } diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 48794a3f41..c7e68e18da 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -18,6 +18,7 @@ package state import ( "bytes" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "sort" @@ -59,22 +60,37 @@ func (s *hookedStateDB) IsNewContract(addr common.Address) bool { } func (s *hookedStateDB) GetBalance(addr common.Address) *uint256.Int { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetBalance(addr) } func (s *hookedStateDB) GetNonce(addr common.Address) uint64 { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetNonce(addr) } func (s *hookedStateDB) GetCodeHash(addr common.Address) common.Hash { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetCodeHash(addr) } func (s *hookedStateDB) GetCode(addr common.Address) []byte { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetCode(addr) } func (s *hookedStateDB) GetCodeSize(addr common.Address) int { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetCodeSize(addr) } @@ -91,14 +107,23 @@ func (s *hookedStateDB) GetRefund() uint64 { } func (s *hookedStateDB) GetStateAndCommittedState(addr common.Address, hash common.Hash) (common.Hash, common.Hash) { + if s.hooks.OnStorageRead != nil { + s.hooks.OnStorageRead(addr, hash) + } return s.inner.GetStateAndCommittedState(addr, hash) } func (s *hookedStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + if s.hooks.OnStorageRead != nil { + s.hooks.OnStorageRead(addr, hash) + } return s.inner.GetState(addr, hash) } func (s *hookedStateDB) GetStorageRoot(addr common.Address) common.Hash { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.GetStorageRoot(addr) } @@ -111,14 +136,23 @@ func (s *hookedStateDB) SetTransientState(addr common.Address, key, value common } func (s *hookedStateDB) HasSelfDestructed(addr common.Address) bool { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.HasSelfDestructed(addr) } func (s *hookedStateDB) Exist(addr common.Address) bool { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.Exist(addr) } func (s *hookedStateDB) Empty(addr common.Address) bool { + if s.hooks.OnAccountRead != nil { + s.hooks.OnAccountRead(addr) + } return s.inner.Empty(addr) } @@ -221,6 +255,10 @@ func (s *hookedStateDB) SelfDestruct(address common.Address) { s.inner.SelfDestruct(address) } +func (s *hookedStateDB) ExistBeforeCurTx(addr common.Address) bool { + return s.inner.ExistBeforeCurTx(addr) +} + func (s *hookedStateDB) AddLog(log *types.Log) { // The inner will modify the log (add fields), so invoke that first s.inner.AddLog(log) @@ -229,11 +267,10 @@ func (s *hookedStateDB) AddLog(log *types.Log) { } } -func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { +func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) bal.StateMutations { if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil { // Short circuit if no relevant hooks are set. - s.inner.Finalise(deleteEmptyObjects) - return + return s.inner.Finalise(deleteEmptyObjects) } // Collect all self-destructed addresses first, then sort them to ensure @@ -272,16 +309,23 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) { // If an initcode invokes selfdestruct, do not emit a code change. prevCodeHash := s.inner.GetCodeHash(addr) - if prevCodeHash == types.EmptyCodeHash { - continue + if prevCodeHash != types.EmptyCodeHash { + // Otherwise, trace the change. + if s.hooks.OnCodeChangeV2 != nil { + s.hooks.OnCodeChangeV2(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct) + } else if s.hooks.OnCodeChange != nil { + s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil) + } } - // Otherwise, trace the change. - if s.hooks.OnCodeChangeV2 != nil { - s.hooks.OnCodeChangeV2(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil, tracing.CodeChangeSelfDestruct) - } else if s.hooks.OnCodeChange != nil { - s.hooks.OnCodeChange(addr, prevCodeHash, s.inner.GetCode(addr), types.EmptyCodeHash, nil) + + if s.hooks.OnSelfDestructChange != nil { + s.hooks.OnSelfDestructChange(addr) } } - s.inner.Finalise(deleteEmptyObjects) + return s.inner.Finalise(deleteEmptyObjects) +} + +func (s *hookedStateDB) TxIndex() int { + return s.inner.TxIndex() } diff --git a/core/state_processor.go b/core/state_processor.go index 86377fa88b..23abc7eb9f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -19,6 +19,7 @@ package core import ( "context" "fmt" + "github.com/ethereum/go-ethereum/core/types/bal" "math/big" "sort" @@ -62,13 +63,15 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig { // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { var ( - config = p.chainConfig() - receipts types.Receipts - header = block.Header() - blockHash = block.Hash() - blockNumber = block.Number() - allLogs []*types.Log - gp = NewGasPool(block.GasLimit()) + config = p.chainConfig() + receipts types.Receipts + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = NewGasPool(block.GasLimit()) + computedAccessList = make(bal.ConstructionBlockAccessList) + isAmsterdam = p.chainConfig().IsAmsterdam(block.Number(), block.Time()) ) var tracingStateDB = vm.StateDB(statedb) if hooks := cfg.Tracer; hooks != nil { @@ -89,10 +92,16 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated evm := vm.NewEVM(context, tracingStateDB, config, cfg) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - ProcessBeaconBlockRoot(*beaconRoot, evm) + mutations := ProcessBeaconBlockRoot(*beaconRoot, evm) + if isAmsterdam { + computedAccessList.AccumulateMutations(mutations, 0) + } } if config.IsPrague(block.Number(), block.Time()) || config.IsVerkle(block.Number(), block.Time()) { - ProcessParentBlockHash(block.ParentHash(), evm) + mutations := ProcessParentBlockHash(block.ParentHash(), evm) + if isAmsterdam { + computedAccessList.AccumulateMutations(mutations, 0) + } } // Iterate over and process the individual transactions @@ -106,8 +115,11 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated telemetry.StringAttribute("tx.hash", tx.Hash().Hex()), telemetry.Int64Attribute("tx.index", int64(i)), ) - - receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm) + var ( + receipt *types.Receipt + mutations bal.StateMutations + ) + mutations, receipt, err = ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, evm) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } @@ -115,52 +127,66 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated allLogs = append(allLogs, receipt.Logs...) spanEnd(&err) + + if isAmsterdam { + computedAccessList.AccumulateMutations(mutations, uint16(i)+1) + } } - requests, err := postExecution(ctx, config, block, allLogs, evm) + postMut, requests, err := postExecution(ctx, config, block, allLogs, evm) if err != nil { return nil, err } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) + eip4985WithdrawalMuts := p.chain.Engine().Finalize(p.chain, header, tracingStateDB, block.Body()) + postMut.Merge(eip4985WithdrawalMuts) + if isAmsterdam { + computedAccessList.AccumulateMutations(postMut, uint16(len(block.Transactions()))+1) + accesses := statedb.Reader().(state.StateReaderTracker).GetStateAccessList() + computedAccessList.AccumulateReads(accesses) + } return &ProcessResult{ - Receipts: receipts, - Requests: requests, - Logs: allLogs, - GasUsed: gp.Used(), + Receipts: receipts, + Requests: requests, + Logs: allLogs, + GasUsed: gp.Used(), + AccessList: computedAccessList, }, nil } // postExecution processes the post-execution system calls if Prague is enabled. -func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) { +func postExecution(ctx context.Context, config *params.ChainConfig, block *types.Block, allLogs []*types.Log, evm *vm.EVM) (mut bal.StateMutations, requests [][]byte, err error) { _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution") defer spanEnd(&err) + mut = make(bal.StateMutations) // Read requests if Prague is enabled. if config.IsPrague(block.Number(), block.Time()) { requests = [][]byte{} // EIP-6110 if err := ParseDepositLogs(&requests, allLogs, config); err != nil { - return requests, fmt.Errorf("failed to parse deposit logs: %w", err) + return mut, requests, fmt.Errorf("failed to parse deposit logs: %w", err) } // EIP-7002 - if err := ProcessWithdrawalQueue(&requests, evm); err != nil { - return requests, fmt.Errorf("failed to process withdrawal queue: %w", err) + if mut, err = ProcessWithdrawalQueue(&requests, evm); err != nil { + return mut, requests, fmt.Errorf("failed to process withdrawal queue: %w", err) } // EIP-7251 - if err := ProcessConsolidationQueue(&requests, evm); err != nil { - return requests, fmt.Errorf("failed to process consolidation queue: %w", err) + consolidationMut, err := ProcessConsolidationQueue(&requests, evm) + if err != nil { + return mut, requests, fmt.Errorf("failed to process consolidation queue: %w", err) } + mut.Merge(consolidationMut) } - return requests, nil + return mut, requests, nil } // ApplyTransactionWithEVM attempts to apply a transaction to the given state database // and uses the input parameters for its environment similar to ApplyTransaction. However, // this method takes an already created EVM instance as input. -func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (receipt *types.Receipt, err error) { +func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, evm *vm.EVM) (mutations bal.StateMutations, receipt *types.Receipt, err error) { if hooks := evm.Config.Tracer; hooks != nil { if hooks.OnTxStart != nil { hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) @@ -172,7 +198,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { - return nil, err + return nil, nil, err } if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) { // Emit burn logs where accounts with non-empty balances have been deleted @@ -189,7 +215,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, // Update the state with pending changes. var root []byte if evm.ChainConfig().IsByzantium(blockNumber) { - evm.StateDB.Finalise(true) + mutations = evm.StateDB.Finalise(true) } else { root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes() } @@ -198,7 +224,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, if statedb.Database().TrieDB().IsVerkle() { statedb.AccessEvents().Merge(evm.AccessEvents) } - return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil + return mutations, MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, gp.CumulativeUsed(), root), nil } // MakeReceipt generates the receipt object for a transaction given its execution result. @@ -243,10 +269,10 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b // and uses the input parameters for its environment. It returns the receipt // for the transaction and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (*types.Receipt, error) { +func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction) (bal.StateMutations, *types.Receipt, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee) if err != nil { - return nil, err + return nil, nil, err } // Create a new context to be used in the EVM environment return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm) @@ -254,7 +280,7 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header * // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root // contract. This method is exported to be used in tests. -func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { +func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) bal.StateMutations { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -273,12 +299,12 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) - evm.StateDB.Finalise(true) + return evm.StateDB.Finalise(true) } // ProcessParentBlockHash stores the parent block hash in the history storage contract // as per EIP-2935/7709. -func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { +func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) bal.StateMutations { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -303,22 +329,23 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { if evm.StateDB.AccessEvents() != nil { evm.StateDB.AccessEvents().Merge(evm.AccessEvents) } - evm.StateDB.Finalise(true) + return evm.StateDB.Finalise(true) } // ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract. // It returns the opaque request data returned by the contract. -func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error { +func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) (bal.StateMutations, error) { return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress) } // ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract. // It returns the opaque request data returned by the contract. -func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error { +func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) (bal.StateMutations, error) { return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress) } -func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error { +// TODO: does the requests contract produce mutations? I think it just parses the logs into requests but idk +func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) (bal.StateMutations, error) { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -336,19 +363,19 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte evm.SetTxContext(NewEVMTxContext(msg)) evm.StateDB.AddAddressToAccessList(addr) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, 30_000_000, common.U2560) - evm.StateDB.Finalise(true) + mut := evm.StateDB.Finalise(true) if err != nil { - return fmt.Errorf("system call failed to execute: %v", err) + return nil, fmt.Errorf("system call failed to execute: %v", err) } if len(ret) == 0 { - return nil // skip empty output + return mut, nil // skip empty output } // Append prefixed requestsData to the requests list. requestsData := make([]byte, len(ret)+1) requestsData[0] = requestType copy(requestsData[1:], ret) *requests = append(*requests, requestsData) - return nil + return mut, nil } var depositTopic = common.HexToHash("0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5") diff --git a/core/state_transition.go b/core/state_transition.go index 76a5147363..2bf3d977c5 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -19,9 +19,6 @@ package core import ( "bytes" "fmt" - "math" - "math/big" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" @@ -29,6 +26,8 @@ import ( "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" + "math" + "math/big" ) // ExecutionResult includes all output after executing given evm @@ -574,6 +573,10 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } else { fee := new(uint256.Int).SetUint64(st.gasUsed()) fee.Mul(fee, effectiveTipU256) + + // always read the coinbase account to include it in the BAL (TODO check this is actually part of the spec) + st.state.GetBalance(st.evm.Context.Coinbase) + st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee) // add the coinbase to the witness iff the fee is greater than 0 @@ -582,7 +585,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } } return &ExecutionResult{ - UsedGas: st.gasUsed(), + UsedGas: peakGasUsed, MaxUsedGas: peakGasUsed, Err: vmerr, ReturnData: ret, @@ -633,16 +636,22 @@ func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) } + prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority)) + // Update nonce and account code. st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) if auth.Address == (common.Address{}) { // Delegation to zero address means clear. - st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) + if isDelegated { + st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) + } return nil } - // Otherwise install delegation to auth.Address. - st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) + // install delegation to auth.Address if the delegation changed + if !isDelegated || auth.Address != prevDelegation { + st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) + } return nil } diff --git a/core/stateless.go b/core/stateless.go index 88d8ed8138..14cd4d4467 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -67,6 +67,10 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig processor := NewStateProcessor(chain) validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block + if config.IsAmsterdam(block.Number(), block.Time()) { + db = db.WithReader(state.NewReaderWithTracker(db.Reader())) + } + // Run the stateless blocks processing and self-validate certain fields res, err := processor.Process(ctx, block, db, vmconfig) if err != nil { diff --git a/core/tracing/hooks.go b/core/tracing/hooks.go index 6d0131ce70..59e2886d52 100644 --- a/core/tracing/hooks.go +++ b/core/tracing/hooks.go @@ -178,7 +178,6 @@ type ( CloseHook = func() // BlockStartHook is called before executing `block`. - // `td` is the total difficulty prior to `block`. BlockStartHook = func(event BlockEvent) // BlockEndHook is called after executing a block. @@ -192,24 +191,25 @@ type ( // GenesisBlockHook is called when the genesis block is being processed. GenesisBlockHook = func(genesis *types.Block, alloc types.GenesisAlloc) - // OnSystemCallStartHook is called when a system call is about to be executed. Today, - // this hook is invoked when the EIP-4788 system call is about to be executed to set the - // beacon block root. + // OnSystemCallStartHook is called when a system call is about to be executed. + // Today, this hook is invoked when the EIP-4788 system call is about to be + // executed to set the beacon block root. // - // After this hook, the EVM call tracing will happened as usual so you will receive a `OnEnter/OnExit` - // as well as state hooks between this hook and the `OnSystemCallEndHook`. + // After this hook, the EVM call tracing will happened as usual so you will + // receive a `OnEnter/OnExit` as well as state hooks between this hook and + // the `OnSystemCallEndHook`. // - // Note that system call happens outside normal transaction execution, so the `OnTxStart/OnTxEnd` hooks - // will not be invoked. + // Note that system call happens outside normal transaction execution, so + // the `OnTxStart/OnTxEnd` hooks will not be invoked. OnSystemCallStartHook = func() - // OnSystemCallStartHookV2 is called when a system call is about to be executed. Refer - // to `OnSystemCallStartHook` for more information. + // OnSystemCallStartHookV2 is called when a system call is about to be executed. + // Refer to `OnSystemCallStartHook` for more information. OnSystemCallStartHookV2 = func(vm *VMContext) - // OnSystemCallEndHook is called when a system call has finished executing. Today, - // this hook is invoked when the EIP-4788 system call is about to be executed to set the - // beacon block root. + // OnSystemCallEndHook is called when a system call has finished executing. + // Today, this hook is invoked when the EIP-4788 system call is about to be + // executed to set the beacon block root. OnSystemCallEndHook = func() // StateUpdateHook is called after state is committed for a block. @@ -239,9 +239,17 @@ type ( // StorageChangeHook is called when the storage of an account changes. StorageChangeHook = func(addr common.Address, slot common.Hash, prev, new common.Hash) + SelfDestructHook = func(address common.Address) + // LogHook is called when a log is emitted. LogHook = func(log *types.Log) + // AccountReadHook is called when the account is accessed. + AccountReadHook = func(addr common.Address) + + // StorageReadHook is called when the storage slot is accessed. + StorageReadHook = func(addr common.Address, slot common.Hash) + // BlockHashReadHook is called when EVM reads the blockhash of a block. BlockHashReadHook = func(blockNumber uint64, hash common.Hash) ) @@ -255,6 +263,7 @@ type Hooks struct { OnOpcode OpcodeHook OnFault FaultHook OnGasChange GasChangeHook + // Chain events OnBlockchainInit BlockchainInitHook OnClose CloseHook @@ -266,14 +275,23 @@ type Hooks struct { OnSystemCallStartV2 OnSystemCallStartHookV2 OnSystemCallEnd OnSystemCallEndHook OnStateUpdate StateUpdateHook - // State events - OnBalanceChange BalanceChangeHook - OnNonceChange NonceChangeHook - OnNonceChangeV2 NonceChangeHookV2 - OnCodeChange CodeChangeHook - OnCodeChangeV2 CodeChangeHookV2 - OnStorageChange StorageChangeHook - OnLog LogHook + + OnBlockFinalization func() // called after post-tx system contracts and consensus finalization are invoked + + // State mutation events + OnBalanceChange BalanceChangeHook + OnNonceChange NonceChangeHook + OnNonceChangeV2 NonceChangeHookV2 + OnCodeChange CodeChangeHook + OnCodeChangeV2 CodeChangeHookV2 + OnStorageChange StorageChangeHook + OnLog LogHook + OnSelfDestructChange SelfDestructHook + + // State access events + OnAccountRead AccountReadHook + OnStorageRead StorageReadHook + // Block hash read OnBlockHashRead BlockHashReadHook } @@ -290,57 +308,74 @@ const ( // Issuance // BalanceIncreaseRewardMineUncle is a reward for mining an uncle block. BalanceIncreaseRewardMineUncle BalanceChangeReason = 1 + // BalanceIncreaseRewardMineBlock is a reward for mining a block. BalanceIncreaseRewardMineBlock BalanceChangeReason = 2 + // BalanceIncreaseWithdrawal is ether withdrawn from the beacon chain. BalanceIncreaseWithdrawal BalanceChangeReason = 3 + // BalanceIncreaseGenesisBalance is ether allocated at the genesis block. BalanceIncreaseGenesisBalance BalanceChangeReason = 4 // Transaction fees - // BalanceIncreaseRewardTransactionFee is the transaction tip increasing block builder's balance. + // BalanceIncreaseRewardTransactionFee is the transaction tip increasing + // block builder's balance. BalanceIncreaseRewardTransactionFee BalanceChangeReason = 5 + // BalanceDecreaseGasBuy is spent to purchase gas for execution a transaction. // Part of this gas will be burnt as per EIP-1559 rules. BalanceDecreaseGasBuy BalanceChangeReason = 6 + // BalanceIncreaseGasReturn is ether returned for unused gas at the end of execution. BalanceIncreaseGasReturn BalanceChangeReason = 7 // DAO fork // BalanceIncreaseDaoContract is ether sent to the DAO refund contract. BalanceIncreaseDaoContract BalanceChangeReason = 8 - // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved to the refund contract. + + // BalanceDecreaseDaoAccount is ether taken from a DAO account to be moved + // to the refund contract. BalanceDecreaseDaoAccount BalanceChangeReason = 9 // BalanceChangeTransfer is ether transferred via a call. // it is a decrease for the sender and an increase for the recipient. BalanceChangeTransfer BalanceChangeReason = 10 + // BalanceChangeTouchAccount is a transfer of zero value. It is only there to // touch-create an account. BalanceChangeTouchAccount BalanceChangeReason = 11 - // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a selfdestructing account. + // BalanceIncreaseSelfdestruct is added to the recipient as indicated by a + // selfdestructing account. BalanceIncreaseSelfdestruct BalanceChangeReason = 12 + // BalanceDecreaseSelfdestruct is deducted from a contract due to self-destruct. BalanceDecreaseSelfdestruct BalanceChangeReason = 13 + // BalanceDecreaseSelfdestructBurn is ether that is sent to an already self-destructed // account within the same tx (captured at end of tx). // Note it doesn't account for a self-destruct which appoints itself as recipient. BalanceDecreaseSelfdestructBurn BalanceChangeReason = 14 - // BalanceChangeRevert is emitted when the balance is reverted back to a previous value due to call failure. - // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). + // BalanceChangeRevert is emitted when the balance is reverted back to a + // previous value due to call failure. + // + // It is only emitted when the tracer has opted in to use the journaling + // wrapper (WrapWithJournal). BalanceChangeRevert BalanceChangeReason = 15 ) // GasChangeReason is used to indicate the reason for a gas change, useful // for tracing and reporting. // -// There is essentially two types of gas changes, those that can be emitted once per transaction -// and those that can be emitted on a call basis, so possibly multiple times per transaction. +// There is essentially two types of gas changes, those that can be emitted +// once per transaction and those that can be emitted on a call basis, so possibly +// multiple times per transaction. // -// They can be recognized easily by their name, those that start with `GasChangeTx` are emitted -// once per transaction, while those that start with `GasChangeCall` are emitted on a call basis. +// They can be recognized easily by their name, those that start with `GasChangeTx` +// are emitted once per transaction, while those that start with `GasChangeCall` +// are emitted on a call basis. type GasChangeReason byte //go:generate go run golang.org/x/tools/cmd/stringer -type=GasChangeReason -trimprefix=GasChange -output gen_gas_change_reason_stringer.go @@ -348,61 +383,99 @@ type GasChangeReason byte const ( GasChangeUnspecified GasChangeReason = 0 - // GasChangeTxInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per transaction. + // GasChangeTxInitialBalance is the initial balance for the call which will + // be equal to the gasLimit of the call. There is only one such gas change + // per transaction. GasChangeTxInitialBalance GasChangeReason = 1 - // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the intrinsic cost of the transaction, there is - // always exactly one of those per transaction. + + // GasChangeTxIntrinsicGas is the amount of gas that will be charged for the + // intrinsic cost of the transaction, there is always exactly one of those + // per transaction. GasChangeTxIntrinsicGas GasChangeReason = 2 - // GasChangeTxRefunds is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) - // this generates an increase in gas. There is at most one of such gas change per transaction. + + // GasChangeTxRefunds is the sum of all refunds which happened during the tx + // execution (e.g. storage slot being cleared). this generates an increase in + // gas. There is at most one of such gas change per transaction. GasChangeTxRefunds GasChangeReason = 3 - // GasChangeTxLeftOverReturned is the amount of gas left over at the end of transaction's execution that will be returned - // to the account. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas - // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. - // There is at most one of such gas change per transaction. + // GasChangeTxLeftOverReturned is the amount of gas left over at the end of + // transaction's execution that will be returned to the chain. This change + // will always be a negative change as we "drain" left over gas towards 0. + // If there was no gas left at the end of execution, no such even will be + // emitted. The returned gas's value in Wei is returned to caller. There is + // at most one of such gas change per transaction. GasChangeTxLeftOverReturned GasChangeReason = 4 - // GasChangeCallInitialBalance is the initial balance for the call which will be equal to the gasLimit of the call. There is only - // one such gas change per call. + // GasChangeCallInitialBalance is the initial balance for the call which + // will be equal to the gasLimit of the call. There is only one such gas + // change per call. GasChangeCallInitialBalance GasChangeReason = 5 - // GasChangeCallLeftOverReturned is the amount of gas left over that will be returned to the caller, this change will always - // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even - // will be emitted. + + // GasChangeCallLeftOverReturned is the amount of gas left over that will + // be returned to the caller, this change will always be a negative change + // as we "drain" left over gas towards 0. If there was no gas left at the + // end of execution, no such even will be emitted. GasChangeCallLeftOverReturned GasChangeReason = 6 - // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded to the call after the child call execution it - // executed completed. This value is always positive as we are giving gas back to the you, the left over gas of the child. - // If there was no gas left to be refunded, no such even will be emitted. + + // GasChangeCallLeftOverRefunded is the amount of gas that will be refunded + // to the call after the child call execution it executed completed. This + // value is always positive as we are giving gas back to the you, the left over + // gas of the child. If there was no gas left to be refunded, no such event + // will be emitted. GasChangeCallLeftOverRefunded GasChangeReason = 7 - // GasChangeCallContractCreation is the amount of gas that will be burned for a CREATE. + + // GasChangeCallContractCreation is the amount of gas that will be burned + // for a CREATE. GasChangeCallContractCreation GasChangeReason = 8 - // GasChangeCallContractCreation2 is the amount of gas that will be burned for a CREATE2. + + // GasChangeCallContractCreation2 is the amount of gas that will be burned + // for a CREATE2. GasChangeCallContractCreation2 GasChangeReason = 9 - // GasChangeCallCodeStorage is the amount of gas that will be charged for code storage. + + // GasChangeCallCodeStorage is the amount of gas that will be charged for + // code storage. GasChangeCallCodeStorage GasChangeReason = 10 - // GasChangeCallOpCode is the amount of gas that will be charged for an opcode executed by the EVM, exact opcode that was - // performed can be check by `OnOpcode` handling. + + // GasChangeCallOpCode is the amount of gas that will be charged for an opcode + // executed by the EVM, exact opcode that was performed can be check by + // `OnOpcode` handling. GasChangeCallOpCode GasChangeReason = 11 - // GasChangeCallPrecompiledContract is the amount of gas that will be charged for a precompiled contract execution. + + // GasChangeCallPrecompiledContract is the amount of gas that will be charged + // for a precompiled contract execution. GasChangeCallPrecompiledContract GasChangeReason = 12 - // GasChangeCallStorageColdAccess is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules. + + // GasChangeCallStorageColdAccess is the amount of gas that will be charged + // for a cold storage access as controlled by EIP2929 rules. GasChangeCallStorageColdAccess GasChangeReason = 13 - // GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert. + + // GasChangeCallFailedExecution is the burning of the remaining gas when the + // execution failed without a revert. GasChangeCallFailedExecution GasChangeReason = 14 - // GasChangeWitnessContractInit flags the event of adding to the witness during the contract creation initialization step. + + // GasChangeWitnessContractInit flags the event of adding to the witness + // during the contract creation initialization step. GasChangeWitnessContractInit GasChangeReason = 15 - // GasChangeWitnessContractCreation flags the event of adding to the witness during the contract creation finalization step. + + // GasChangeWitnessContractCreation flags the event of adding to the witness + // during the contract creation finalization step. GasChangeWitnessContractCreation GasChangeReason = 16 - // GasChangeWitnessCodeChunk flags the event of adding one or more contract code chunks to the witness. + + // GasChangeWitnessCodeChunk flags the event of adding one or more contract + // code chunks to the witness. GasChangeWitnessCodeChunk GasChangeReason = 17 - // GasChangeWitnessContractCollisionCheck flags the event of adding to the witness when checking for contract address collision. + + // GasChangeWitnessContractCollisionCheck flags the event of adding to the + // witness when checking for contract address collision. GasChangeWitnessContractCollisionCheck GasChangeReason = 18 - // GasChangeTxDataFloor is the amount of extra gas the transaction has to pay to reach the minimum gas requirement for the - // transaction data. This change will always be a negative change. + + // GasChangeTxDataFloor is the amount of extra gas the transaction has to + // pay to reach the minimum gas requirement for the transaction data. + // This change will always be a negative change. GasChangeTxDataFloor GasChangeReason = 19 - // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as - // it will be "manually" tracked by a direct emit of the gas change event. + // GasChangeIgnored is a special value that can be used to indicate that + // the gas change should be ignored as it will be "manually" tracked by + // a direct emit of the gas change event. GasChangeIgnored GasChangeReason = 0xFF ) @@ -426,11 +499,12 @@ const ( // NonceChangeNewContract is the nonce change of a newly created contract. NonceChangeNewContract NonceChangeReason = 4 - // NonceChangeTransaction is the nonce change due to a EIP-7702 authorization. + // NonceChangeAuthorization is the nonce change due to a EIP-7702 authorization. NonceChangeAuthorization NonceChangeReason = 5 - // NonceChangeRevert is emitted when the nonce is reverted back to a previous value due to call failure. - // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). + // NonceChangeRevert is emitted when the nonce is reverted back to a previous + // value due to call failure. It is only emitted when the tracer has opted in + // to use the journaling wrapper (WrapWithJournal). NonceChangeRevert NonceChangeReason = 6 // NonceChangeSelfdestruct is emitted when the nonce is reset to zero due to a self-destruct @@ -445,22 +519,26 @@ type CodeChangeReason byte const ( CodeChangeUnspecified CodeChangeReason = 0 - // CodeChangeContractCreation is when a new contract is deployed via CREATE/CREATE2 operations. + // CodeChangeContractCreation is when a new contract is deployed via + // CREATE/CREATE2 operations. CodeChangeContractCreation CodeChangeReason = 1 - // CodeChangeGenesis is when contract code is set during blockchain genesis or initial setup. + // CodeChangeGenesis is when contract code is set during blockchain genesis + // or initial setup. CodeChangeGenesis CodeChangeReason = 2 // CodeChangeAuthorization is when code is set via EIP-7702 Set Code Authorization. CodeChangeAuthorization CodeChangeReason = 3 - // CodeChangeAuthorizationClear is when EIP-7702 delegation is cleared by setting to zero address. + // CodeChangeAuthorizationClear is when EIP-7702 delegation is cleared by + // setting to zero address. CodeChangeAuthorizationClear CodeChangeReason = 4 // CodeChangeSelfDestruct is when contract code is cleared due to self-destruct. CodeChangeSelfDestruct CodeChangeReason = 5 - // CodeChangeRevert is emitted when the code is reverted back to a previous value due to call failure. - // It is only emitted when the tracer has opted in to use the journaling wrapper (WrapWithJournal). + // CodeChangeRevert is emitted when the code is reverted back to a previous + // value due to call failure. It is only emitted when the tracer has opted + // in to use the journaling wrapper (WrapWithJournal). CodeChangeRevert CodeChangeReason = 6 ) diff --git a/core/tracing/journal.go b/core/tracing/journal.go index 62a70d6c27..87d1d2ad7d 100644 --- a/core/tracing/journal.go +++ b/core/tracing/journal.go @@ -42,7 +42,9 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { return nil, errors.New("wrapping nil tracer") } // No state change to journal, return the wrapped hooks as is - if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && hooks.OnCodeChange == nil && hooks.OnCodeChangeV2 == nil && hooks.OnStorageChange == nil { + if hooks.OnBalanceChange == nil && hooks.OnNonceChange == nil && hooks.OnNonceChangeV2 == nil && + hooks.OnCodeChange == nil && hooks.OnCodeChangeV2 == nil && hooks.OnStorageChange == nil { + // TODO(sina) hooks.OnLog should also be handled here return hooks, nil } if hooks.OnNonceChange != nil && hooks.OnNonceChangeV2 != nil { @@ -56,11 +58,14 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { wrapped := *hooks // Create journal - j := &journal{hooks: hooks} + j := &journal{ + hooks: hooks, + } // Scope hooks need to be re-implemented. wrapped.OnTxEnd = j.OnTxEnd wrapped.OnEnter = j.OnEnter wrapped.OnExit = j.OnExit + // Wrap state change hooks. if hooks.OnBalanceChange != nil { wrapped.OnBalanceChange = j.OnBalanceChange @@ -69,6 +74,7 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { // Regardless of which hook version is used in the tracer, // the journal will want to capture the nonce change reason. wrapped.OnNonceChangeV2 = j.OnNonceChangeV2 + // A precaution to ensure EVM doesn't call both hooks. wrapped.OnNonceChange = nil } @@ -81,7 +87,6 @@ func WrapWithJournal(hooks *Hooks) (*Hooks, error) { if hooks.OnStorageChange != nil { wrapped.OnStorageChange = j.OnStorageChange } - return &wrapped, nil } @@ -148,7 +153,11 @@ func (j *journal) OnExit(depth int, output []byte, gasUsed uint64, err error, re } func (j *journal) OnBalanceChange(addr common.Address, prev, new *big.Int, reason BalanceChangeReason) { - j.entries = append(j.entries, balanceChange{addr: addr, prev: prev, new: new}) + j.entries = append(j.entries, balanceChange{ + addr: addr, + prev: prev, + new: new, + }) if j.hooks.OnBalanceChange != nil { j.hooks.OnBalanceChange(addr, prev, new, reason) } @@ -158,7 +167,11 @@ func (j *journal) OnNonceChangeV2(addr common.Address, prev, new uint64, reason // When a contract is created, the nonce of the creator is incremented. // This change is not reverted when the creation fails. if reason != NonceChangeContractCreator { - j.entries = append(j.entries, nonceChange{addr: addr, prev: prev, new: new}) + j.entries = append(j.entries, nonceChange{ + addr: addr, + prev: prev, + new: new, + }) } if j.hooks.OnNonceChangeV2 != nil { j.hooks.OnNonceChangeV2(addr, prev, new, reason) @@ -194,7 +207,12 @@ func (j *journal) OnCodeChangeV2(addr common.Address, prevCodeHash common.Hash, } func (j *journal) OnStorageChange(addr common.Address, slot common.Hash, prev, new common.Hash) { - j.entries = append(j.entries, storageChange{addr: addr, slot: slot, prev: prev, new: new}) + j.entries = append(j.entries, storageChange{ + addr: addr, + slot: slot, + prev: prev, + new: new, + }) if j.hooks.OnStorageChange != nil { j.hooks.OnStorageChange(addr, slot, prev, new) } diff --git a/core/tracing/journal_test.go b/core/tracing/journal_test.go index e00447f5f3..9f9fa27565 100644 --- a/core/tracing/journal_test.go +++ b/core/tracing/journal_test.go @@ -63,7 +63,7 @@ func (t *testTracer) OnCodeChangeV2(addr common.Address, prevCodeHash common.Has } func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev common.Hash, new common.Hash) { - t.t.Logf("OnStorageCodeChange(%v, %v, %v -> %v)", addr, slot, prev, new) + t.t.Logf("OnStorageChange(%v, %v, %v -> %v)", addr, slot, prev, new) if t.storage == nil { t.storage = make(map[common.Hash]common.Hash) } @@ -76,7 +76,12 @@ func (t *testTracer) OnStorageChange(addr common.Address, slot common.Hash, prev func TestJournalIntegration(t *testing.T) { tr := &testTracer{t: t} - wr, err := WrapWithJournal(&Hooks{OnBalanceChange: tr.OnBalanceChange, OnNonceChange: tr.OnNonceChange, OnCodeChange: tr.OnCodeChange, OnStorageChange: tr.OnStorageChange}) + wr, err := WrapWithJournal(&Hooks{ + OnBalanceChange: tr.OnBalanceChange, + OnNonceChange: tr.OnNonceChange, + OnCodeChange: tr.OnCodeChange, + OnStorageChange: tr.OnStorageChange, + }) if err != nil { t.Fatalf("failed to wrap test tracer: %v", err) } diff --git a/core/types.go b/core/types.go index 87bbfcff58..f881c9ed8f 100644 --- a/core/types.go +++ b/core/types.go @@ -18,6 +18,7 @@ package core import ( "context" + "github.com/ethereum/go-ethereum/core/types/bal" "sync/atomic" "github.com/ethereum/go-ethereum/core/state" @@ -33,7 +34,7 @@ type Validator interface { ValidateBody(block *types.Block) error // ValidateState validates the given statedb and optionally the process result. - ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error + ValidateState(block *types.Block, state state.BlockStateTransition, res *ProcessResult, stateless bool) error } // Prefetcher is an interface for pre-caching transaction signatures and state. @@ -54,8 +55,10 @@ type Processor interface { // ProcessResult contains the values computed by Process. type ProcessResult struct { - Receipts types.Receipts - Requests [][]byte - Logs []*types.Log - GasUsed uint64 + AccessList bal.ConstructionBlockAccessList + Receipts types.Receipts + Requests [][]byte + Logs []*types.Log + GasUsed uint64 + Error error } diff --git a/core/types/bal/bal.go b/core/types/bal/bal.go index 86dc8e5426..c1a44b8934 100644 --- a/core/types/bal/bal.go +++ b/core/types/bal/bal.go @@ -18,156 +18,516 @@ package bal import ( "bytes" - "maps" - + "encoding/json" "github.com/ethereum/go-ethereum/common" "github.com/holiman/uint256" + "maps" ) -// ConstructionAccountAccess contains post-block account state for mutations as well as +// ConstructionAccountAccesses contains post-block account state for mutations as well as // all storage keys that were read during execution. It is used when building block // access list during execution. -type ConstructionAccountAccess struct { +type ConstructionAccountAccesses struct { // StorageWrites is the post-state values of an account's storage slots // that were modified in a block, keyed by the slot key and the tx index // where the modification occurred. - StorageWrites map[common.Hash]map[uint16]common.Hash `json:"storageWrites,omitempty"` + StorageWrites map[common.Hash]map[uint16]common.Hash // StorageReads is the set of slot keys that were accessed during block // execution. // - // Storage slots which are both read and written (with changed values) + // storage slots which are both read and written (with changed values) // appear only in StorageWrites. - StorageReads map[common.Hash]struct{} `json:"storageReads,omitempty"` + StorageReads map[common.Hash]struct{} // BalanceChanges contains the post-transaction balances of an account, // keyed by transaction indices where it was changed. - BalanceChanges map[uint16]*uint256.Int `json:"balanceChanges,omitempty"` + BalanceChanges map[uint16]*uint256.Int // NonceChanges contains the post-state nonce values of an account keyed // by tx index. - NonceChanges map[uint16]uint64 `json:"nonceChanges,omitempty"` + NonceChanges map[uint16]uint64 - // CodeChange contains the post-state contract code of an account keyed - // by tx index. - CodeChange map[uint16][]byte `json:"codeChange,omitempty"` + CodeChanges map[uint16][]byte } -// NewConstructionAccountAccess initializes the account access object. -func NewConstructionAccountAccess() *ConstructionAccountAccess { - return &ConstructionAccountAccess{ +func (c *ConstructionAccountAccesses) Copy() (res ConstructionAccountAccesses) { + if c.StorageWrites != nil { + res.StorageWrites = make(map[common.Hash]map[uint16]common.Hash) + for slot, writes := range c.StorageWrites { + res.StorageWrites[slot] = maps.Clone(writes) + } + } + if c.StorageReads != nil { + res.StorageReads = maps.Clone(c.StorageReads) + } + if c.BalanceChanges != nil { + res.BalanceChanges = maps.Clone(c.BalanceChanges) + } + if c.NonceChanges != nil { + res.NonceChanges = maps.Clone(c.NonceChanges) + } + if c.CodeChanges != nil { + res.CodeChanges = maps.Clone(c.CodeChanges) + } + return res +} + +type StateMutations map[common.Address]AccountMutations + +func (s StateMutations) String() string { + b, _ := json.MarshalIndent(s, "", " ") + return string(b) +} + +// Merge merges the state changes present in next into the caller. After, +// the state of the caller is the aggregate diff through next. +func (s StateMutations) Merge(next StateMutations) { + for account, diff := range next { + if mut, ok := s[account]; ok { + if diff.Balance != nil { + mut.Balance = diff.Balance + } + if diff.Code != nil { + mut.Code = diff.Code + } + if diff.Nonce != nil { + mut.Nonce = diff.Nonce + } + if len(diff.StorageWrites) > 0 { + if mut.StorageWrites == nil { + mut.StorageWrites = maps.Clone(diff.StorageWrites) + } else { + for key, val := range diff.StorageWrites { + mut.StorageWrites[key] = val + } + } + } + } else { + s[account] = *diff.Copy() + } + } +} + +func (s StateMutations) Eq(other StateMutations) bool { + if len(s) != len(other) { + return false + } + + for addr, mut := range s { + otherMut, ok := other[addr] + if !ok { + return false + } + + if !mut.Eq(&otherMut) { + return false + } + } + + return true +} + +type ConstructionBlockAccessList map[common.Address]*ConstructionAccountAccesses + +func (c ConstructionBlockAccessList) Copy() ConstructionBlockAccessList { + res := make(ConstructionBlockAccessList) + for addr, accountAccess := range c { + aaCopy := accountAccess.Copy() + res[addr] = &aaCopy + } + return res +} + +func (c ConstructionBlockAccessList) AccumulateMutations(muts StateMutations, idx uint16) { + for addr, mut := range muts { + if _, exist := c[addr]; !exist { + c[addr] = newConstructionAccountAccesses() + } + if mut.Nonce != nil { + if c[addr].NonceChanges == nil { + c[addr].NonceChanges = make(map[uint16]uint64) + } + c[addr].NonceChanges[idx] = *mut.Nonce + } + if mut.Balance != nil { + if c[addr].BalanceChanges == nil { + c[addr].BalanceChanges = make(map[uint16]*uint256.Int) + } + c[addr].BalanceChanges[idx] = mut.Balance.Clone() + } + if mut.Code != nil { + if c[addr].CodeChanges == nil { + c[addr].CodeChanges = make(map[uint16][]byte) + } + c[addr].CodeChanges[idx] = bytes.Clone(mut.Code) + } + if len(mut.StorageWrites) > 0 { + for key, val := range mut.StorageWrites { + if c[addr].StorageWrites[key] == nil { + c[addr].StorageWrites[key] = make(map[uint16]common.Hash) + } + c[addr].StorageWrites[key][idx] = val + } + } + } +} + +func (c ConstructionBlockAccessList) AccumulateReads(reads StateAccesses) { + for addr, addrReads := range reads { + if _, ok := c[addr]; !ok { + c[addr] = newConstructionAccountAccesses() + } + for storageKey, _ := range addrReads { + if c[addr].StorageWrites != nil { + if _, ok := c[addr].StorageWrites[storageKey]; ok { + continue + } + } + if c[addr].StorageReads == nil { + c[addr].StorageReads = make(map[common.Hash]struct{}) + } + c[addr].StorageReads[storageKey] = struct{}{} + } + } +} + +func newConstructionAccountAccesses() *ConstructionAccountAccesses { + return &ConstructionAccountAccesses{ StorageWrites: make(map[common.Hash]map[uint16]common.Hash), StorageReads: make(map[common.Hash]struct{}), BalanceChanges: make(map[uint16]*uint256.Int), NonceChanges: make(map[uint16]uint64), - CodeChange: make(map[uint16][]byte), + CodeChanges: make(map[uint16][]byte), } } -// ConstructionBlockAccessList contains post-block modified state and some state accessed -// in execution (account addresses and storage keys). -type ConstructionBlockAccessList struct { - Accounts map[common.Address]*ConstructionAccountAccess +// StateDiff contains state mutations occuring over one or more access list +// index. +type StateDiff struct { + Mutations map[common.Address]*AccountMutations `json:"Mutations,omitempty"` } -// NewConstructionBlockAccessList instantiates an empty access list. -func NewConstructionBlockAccessList() ConstructionBlockAccessList { - return ConstructionBlockAccessList{ - Accounts: make(map[common.Address]*ConstructionAccountAccess), - } -} +// StateAccesses contains a set of accounts/storage that were accessed during the +// execution of one or more access list indices. +type StateAccesses map[common.Address]StorageAccessList +type StorageAccessList map[common.Hash]struct{} -// AccountRead records the address of an account that has been read during execution. -func (b *ConstructionBlockAccessList) AccountRead(addr common.Address) { - if _, ok := b.Accounts[addr]; !ok { - b.Accounts[addr] = NewConstructionAccountAccess() - } -} - -// StorageRead records a storage key read during execution. -func (b *ConstructionBlockAccessList) StorageRead(address common.Address, key common.Hash) { - if _, ok := b.Accounts[address]; !ok { - b.Accounts[address] = NewConstructionAccountAccess() - } - if _, ok := b.Accounts[address].StorageWrites[key]; ok { - return - } - b.Accounts[address].StorageReads[key] = struct{}{} -} - -// StorageWrite records the post-transaction value of a mutated storage slot. -// The storage slot is removed from the list of read slots. -func (b *ConstructionBlockAccessList) StorageWrite(txIdx uint16, address common.Address, key, value common.Hash) { - if _, ok := b.Accounts[address]; !ok { - b.Accounts[address] = NewConstructionAccountAccess() - } - if _, ok := b.Accounts[address].StorageWrites[key]; !ok { - b.Accounts[address].StorageWrites[key] = make(map[uint16]common.Hash) - } - b.Accounts[address].StorageWrites[key][txIdx] = value - - delete(b.Accounts[address].StorageReads, key) -} - -// CodeChange records the code of a newly-created contract. -func (b *ConstructionBlockAccessList) CodeChange(address common.Address, txIndex uint16, code []byte) { - if _, ok := b.Accounts[address]; !ok { - b.Accounts[address] = NewConstructionAccountAccess() - } - // TODO(rjl493456442) is it essential to deep-copy the code? - b.Accounts[address].CodeChange[txIndex] = bytes.Clone(code) -} - -// NonceChange records tx post-state nonce of any contract-like accounts whose -// nonce was incremented. -func (b *ConstructionBlockAccessList) NonceChange(address common.Address, txIdx uint16, postNonce uint64) { - if _, ok := b.Accounts[address]; !ok { - b.Accounts[address] = NewConstructionAccountAccess() - } - b.Accounts[address].NonceChanges[txIdx] = postNonce -} - -// BalanceChange records the post-transaction balance of an account whose -// balance changed. -func (b *ConstructionBlockAccessList) BalanceChange(txIdx uint16, address common.Address, balance *uint256.Int) { - if _, ok := b.Accounts[address]; !ok { - b.Accounts[address] = NewConstructionAccountAccess() - } - b.Accounts[address].BalanceChanges[txIdx] = balance.Clone() -} - -// PrettyPrint returns a human-readable representation of the access list -func (b *ConstructionBlockAccessList) PrettyPrint() string { - enc := b.toEncodingObj() - return enc.PrettyPrint() -} - -// Copy returns a deep copy of the access list. -func (b *ConstructionBlockAccessList) Copy() *ConstructionBlockAccessList { - res := NewConstructionBlockAccessList() - for addr, aa := range b.Accounts { - var aaCopy ConstructionAccountAccess - - slotWrites := make(map[common.Hash]map[uint16]common.Hash, len(aa.StorageWrites)) - for key, m := range aa.StorageWrites { - slotWrites[key] = maps.Clone(m) +// Merge combines adds the accesses from other into s. +func (s StateAccesses) Merge(other StateAccesses) { + for addr, accesses := range other { + if _, ok := s[addr]; !ok { + s[addr] = make(map[common.Hash]struct{}) } - aaCopy.StorageWrites = slotWrites - aaCopy.StorageReads = maps.Clone(aa.StorageReads) - - balances := make(map[uint16]*uint256.Int, len(aa.BalanceChanges)) - for index, balance := range aa.BalanceChanges { - balances[index] = balance.Clone() + for slot := range accesses { + s[addr][slot] = struct{}{} } - aaCopy.BalanceChanges = balances - aaCopy.NonceChanges = maps.Clone(aa.NonceChanges) + } +} - codes := make(map[uint16][]byte, len(aa.CodeChange)) - for index, code := range aa.CodeChange { - codes[index] = bytes.Clone(code) +func (s StateAccesses) Eq(other StateAccesses) bool { + if len(s) != len(other) { + return false + } + for addr, accesses := range s { + if _, ok := other[addr]; !ok { + return false + } + if !maps.Equal(accesses, other[addr]) { + return false + } + } + return true +} + +type StorageMutations map[common.Hash]common.Hash + +// AccountMutations contains mutations that were made to an account across +// one or more access list indices. +type AccountMutations struct { + Balance *uint256.Int `json:"Balance,omitempty"` + Nonce *uint64 `json:"Nonce,omitempty"` + Code ContractCode `json:"Code,omitempty"` + StorageWrites StorageMutations `json:"StorageWrites,omitempty"` +} + +// String returns a human-readable JSON representation of the account mutations. +func (a *AccountMutations) String() string { + var res bytes.Buffer + enc := json.NewEncoder(&res) + enc.SetIndent("", " ") + enc.Encode(a) + return res.String() +} + +// Copy returns a deep-copy of the instance. +func (a *AccountMutations) Copy() *AccountMutations { + res := &AccountMutations{ + nil, + nil, + nil, + nil, + } + if a.Nonce != nil { + res.Nonce = new(uint64) + *res.Nonce = *a.Nonce + } + if a.Code != nil { + res.Code = bytes.Clone(a.Code) + } + if a.Balance != nil { + res.Balance = new(uint256.Int).Set(a.Balance) + } + if a.StorageWrites != nil { + res.StorageWrites = maps.Clone(a.StorageWrites) + } + return res +} + +// String returns the state diff as a formatted JSON string. +func (s *StateDiff) String() string { + var res bytes.Buffer + enc := json.NewEncoder(&res) + enc.SetIndent("", " ") + enc.Encode(s) + return res.String() +} + +// Copy returns a deep copy of the StateDiff +func (s *StateDiff) Copy() *StateDiff { + res := &StateDiff{make(map[common.Address]*AccountMutations)} + for addr, accountDiff := range s.Mutations { + cpy := accountDiff.Copy() + res.Mutations[addr] = cpy + } + return res +} + +// AccessListReader exposes utilities to read state mutations and accesses from an access list +// TODO: expose this an an interface? +type AccessListReader map[common.Address]*AccountAccess + +func NewAccessListReader(bal BlockAccessList) (reader AccessListReader) { + reader = make(AccessListReader) + for _, accountAccess := range bal { + reader[accountAccess.Address] = &accountAccess + } + return +} + +func (a AccessListReader) Accesses() (accesses StateAccesses) { + accesses = make(StateAccesses) + for addr, acctAccess := range a { + if len(acctAccess.StorageReads) > 0 { + accesses[addr] = make(StorageAccessList) + for _, key := range acctAccess.StorageReads { + accesses[addr][key.ToHash()] = struct{}{} + } + } else if len(acctAccess.CodeChanges) == 0 && len(acctAccess.StorageChanges) == 0 && len(acctAccess.BalanceChanges) == 0 && len(acctAccess.NonceChanges) == 0 { + accesses[addr] = make(StorageAccessList) + } + } + return +} + +// TODO: these methods should return the mutations accrued before the execution of the given index + +// TODO: strip the storage mutations from the returned result +// the returned object should be able to be modified +func (a AccessListReader) accountMutationsAt(addr common.Address, idx int) (res *AccountMutations) { + acct, exist := a[addr] + if !exist { + return nil + } + + res = &AccountMutations{} + // TODO: remove the reverse iteration here to clean the code up + + for i := len(acct.BalanceChanges) - 1; i >= 0; i-- { + if acct.BalanceChanges[i].TxIdx == uint16(idx) { + res.Balance = acct.BalanceChanges[i].Balance + } + if acct.BalanceChanges[i].TxIdx < uint16(idx) { + break + } + } + + for i := len(acct.CodeChanges) - 1; i >= 0; i-- { + if acct.CodeChanges[i].TxIndex == uint16(idx) { + res.Code = bytes.Clone(acct.CodeChanges[i].Code) + break + } + if acct.CodeChanges[i].TxIndex < uint16(idx) { + break + } + } + + for i := len(acct.NonceChanges) - 1; i >= 0; i-- { + if acct.NonceChanges[i].TxIdx == uint16(idx) { + res.Nonce = new(uint64) + *res.Nonce = acct.NonceChanges[i].Nonce + break + } + if acct.NonceChanges[i].TxIdx < uint16(idx) { + break + } + } + + for i := len(acct.StorageChanges) - 1; i >= 0; i-- { + if res.StorageWrites == nil { + res.StorageWrites = make(map[common.Hash]common.Hash) + } + slotWrites := acct.StorageChanges[i] + + for j := len(slotWrites.Accesses) - 1; j >= 0; j-- { + if slotWrites.Accesses[j].TxIdx == uint16(idx) { + res.StorageWrites[slotWrites.Slot.ToHash()] = slotWrites.Accesses[j].ValueAfter.ToHash() + break + } + if slotWrites.Accesses[j].TxIdx < uint16(idx) { + break + } + } + if len(res.StorageWrites) == 0 { + res.StorageWrites = nil + } + } + + if res.Code == nil && res.Nonce == nil && len(res.StorageWrites) == 0 && res.Balance == nil { + return nil + } + return res +} + +func (a AccessListReader) AccountMutations(addr common.Address, idx int) (res *AccountMutations) { + diff, exist := a[addr] + if !exist { + return nil + } + + res = &AccountMutations{} + + for i := 0; i < len(diff.BalanceChanges) && diff.BalanceChanges[i].TxIdx < uint16(idx); i++ { + res.Balance = diff.BalanceChanges[i].Balance.Clone() + } + + for i := 0; i < len(diff.CodeChanges) && diff.CodeChanges[i].TxIndex < uint16(idx); i++ { + res.Code = bytes.Clone(diff.CodeChanges[i].Code) + } + + for i := 0; i < len(diff.NonceChanges) && diff.NonceChanges[i].TxIdx < uint16(idx); i++ { + res.Nonce = new(uint64) + *res.Nonce = diff.NonceChanges[i].Nonce + } + + if len(diff.StorageChanges) > 0 { + res.StorageWrites = make(map[common.Hash]common.Hash) + for _, slotWrites := range diff.StorageChanges { + for i := 0; i < len(slotWrites.Accesses) && slotWrites.Accesses[i].TxIdx < uint16(idx); i++ { + res.StorageWrites[slotWrites.Slot.ToHash()] = slotWrites.Accesses[i].ValueAfter.ToHash() + } + } + } + + if res.Code == nil && res.Nonce == nil && len(res.StorageWrites) == 0 && res.Balance == nil { + return nil + } + return res +} + +// Mutations returns the aggregate state mutations from [0, idx) +func (a AccessListReader) Mutations(idx int) *StateMutations { + res := make(StateMutations) + for addr := range a { + if mut := a.AccountMutations(addr, idx); mut != nil { + res[addr] = *mut } - aaCopy.CodeChange = codes - res.Accounts[addr] = &aaCopy } return &res } + +// MutationsAt returns the state mutations from an index +func (a AccessListReader) MutationsAt(idx int) *StateMutations { + res := make(StateMutations) + for addr := range a { + if mut := a.accountMutationsAt(addr, idx); mut != nil { + res[addr] = *mut + } + } + return &res +} + +type StorageKeys map[common.Address][]common.Hash + +// StorageKeys returns the set of accounts and storage keys mutated in the access list. +// If reads is set, the un-mutated accounts/keys are included in the result. +func (a AccessListReader) StorageKeys(reads bool) (keys StorageKeys) { + keys = make(StorageKeys) + for addr, acct := range a { + for _, storageChange := range acct.StorageChanges { + keys[addr] = append(keys[addr], storageChange.Slot.ToHash()) + } + if !(reads && len(acct.StorageReads) > 0) { + continue + } + for _, storageRead := range acct.StorageReads { + keys[addr] = append(keys[addr], storageRead.ToHash()) + } + } + return +} + +// Storage returns the value of a storage key at the start of executing an index. +// If the slot has no mutations in the access list, it returns nil. +func (a AccessListReader) Storage(addr common.Address, key common.Hash, idx int) (val *common.Hash) { + storageMuts := a.AccountMutations(addr, idx) + if storageMuts != nil { + res, ok := storageMuts.StorageWrites[key] + if ok { + return &res + } + } + return nil +} + +// Copy returns a deep copy of the access list +func (e BlockAccessList) Copy() (res BlockAccessList) { + for _, accountAccess := range e { + res = append(res, accountAccess.Copy()) + } + return +} + +// Eq returns whether the calling instance is equal to the provided one. +func (a *AccountMutations) Eq(other *AccountMutations) bool { + if a.Balance != nil || other.Balance != nil { + if a.Balance == nil || other.Balance == nil { + return false + } + + if !a.Balance.Eq(other.Balance) { + return false + } + } + + if (len(a.Code) != 0 || len(other.Code) != 0) && !bytes.Equal(a.Code, other.Code) { + return false + } + + if a.Nonce != nil || other.Nonce != nil { + if a.Nonce == nil || other.Nonce == nil { + return false + } + + if *a.Nonce != *other.Nonce { + return false + } + } + + if a.StorageWrites != nil || other.StorageWrites != nil { + if !maps.Equal(a.StorageWrites, other.StorageWrites) { + return false + } + } + return true +} diff --git a/core/types/bal/bal.rlp.hex b/core/types/bal/bal.rlp.hex new file mode 100644 index 0000000000..bc855f850d --- /dev/null +++ b/core/types/bal/bal.rlp.hex @@ -0,0 +1 @@ +fa0232eefa0232eada940000000000000000000000000000000000000001c0c0c0c0c0da940000000000000000000000000000000000000004c0c0c0c0c0f905ea940000000000000068f116a894984e2db1123eb395f9039bf845a02879eb18191584a3a1ebce36a8d948c75a6f8c9fda6da11e4a3fab1bd3ee1a7de3e203a00000000000000000000000000000010000000000000000000000000000010001f845a032d4f5dfd2de87dfd375caa9ece73a1836f808f322ae8165556b19ee9b6ee5bbe3e203a00000000000000000000000000000010000000000000000000000000000010001f845a0385bd69fd0a4bb474385b68e83f65ac3c4026b842acb9c32971f9e751b8ceca5e3e266a00000000000000000000000000000010000000000000000000000000000010001f845a051509531c62a021e8d735685092e25c29be839568e0bf54fe50af22d757d0fefe3e211a00000000000000000000000000000010000000000000000000000000000010001f845a0551298134701db706297bcbe9e61fa706d2ae3d72d7fa6f464cd605f7ff46b91e3e203a00000000000000000000000000000010000000000000000000000000000010001f845a06fd69f14432de6ca0fd7a0afe266c1d2f15066ac477e6c8e26d4d4e36dd555a0e3e203a00000000000000000000000000000010000000000000000000000000000010001f845a07becbfd691d229a8755150d84095d2057ea205d4824d58e890ecfd58b8ee81b1e3e203a00000000000000000000000000000010000000000000000000000000000010001f845a08af99cf03ac6af02fc168a4de01e4786c0f7ac8b3e4810e6a7e5d660129cec24e3e266a00000000000000000000000000000010000000000000000000000000000010001f845a0a761ee45024e359298929f02210f136ec8ea7ecc213d124d688df248e2083f92e3e203a00000000000000000000000000000010000000000000000000000000000010001f845a0d2359819549a4b8909616d11e73c544b513069e66dc67f9056089a917a5d8fbce3e203a00000000000000000000000000000010000000000000000000000000000010001f845a0d91096803defa942270be20b3387c1774041f198954ba5fd503984d4ac0e7a61e3e206a00000000000000000000000000000010000000000000000000000000000010001f845a0e35da6dba6b14447af96e3b74b05ce421142c82a91c8e13afefb555171486bf6e3e203a00000000000000000000000000000010000000000000000000000000000010001f845a0e8ad641b3ed88082e4cd76c62a2dad5ba22000b276acb6233329c9667a2c904ee3e203a00000000000000000000000000000010000000000000000000000000000010001f90231a004a3c4a0d9047623b8d7dcc3b677ed2c4d54685e57fe6cd104d802b4d4f7b7efa00d4a57882193ce57c7646ab152072e4c024e23f8bafb6689586cfffe5f974315a01709d7b25e94969bfaa391d9a630b815521992b8ffc51672a8fc80eacf0b55b5a0240b730d61c4bf13447525287d6827368bae79bdce1dc6db0d2b538df3d1cd37a0284cc95d48029f1784f0afc558ff3176af587a76478dd098c1c84124633e8582a062b6c76342a7681bbacde838bb2ebeb623de0cc0cb5b41e28543aca6b5176d59a06484b2e917f126ed3e49ecb98ccdfee0552978a4277c1f20aec531f2799d2adca079d7e14944620d9cd62c91d1b6eb1729494b6a264d6d4cfc3fde7657bd4a8ab0a07f37666151e0742dc4e1604442f707cc4e14a477e70516d04137158689a8fdf1a0998b7070b8aa97b737b95edaa42eeb988a498c0add57e71695bdc356e551b34ca0a67eb2745aace31d3a09ac0256d2d87a3104e1f8faee70aa77de29f3b036901fa0b43f6cc3aa7f4991e0cd46cc36dbfcf6df468811ff45fcf0243641caba09af25a0b9ef8724ad29084e233d76ffe54969b7f07e23e0a674aa3946f744cd10d35769a0e93f8b2d15d9079158e44d4119bec6d7f8b9c7fde7ae4dd9d8aca39386b0d812a0ed9adb6c793843819de3c018021715ffea8880c9d7ab503edbee94c165d36fa2a0ee835806d06236377e76760aac360a386a959b400c300a8f4b5119ac88d977b2a0fbd0261837df9af0ad3ec0cba83022d1bbc05d612551f8bb3b281a1ba1ba9ab5c0c0c0da940000000000001ff3684f28c67538d4d072c22734c0c0c0c0c0f6940000000000008359421a71a3d1f8c72921c8504dc0c0d5d482013a900000000000000000097f46dfb4f6e101c7c682013a8216a7c0f89f94000000000000aaeb6d7670e522a718067333cd4ec0f884a027731369f0182227d5ee3f30301231ccf0c3322575059918eb616a9bf26e980fa05344153e8ca27a513c3abedf923021a87531bbc83613ac09db5321eb1f931ae4a0a5e1f1ea720828095d7ab73f1c7e9411da82aee9bc5912a6db678c1278872869a0e9f6ea489f01977683b83865ec5011394dbbdb035734e086a69bb5804da3523cc0c0c0f9012494000000000004444c5dc75cb358380d2e3de08a90c0f90108a02251a267dd413cd2f816e4e39dccc617915aec866e0d2bcf079ee27af055df5fa045f1c2ca1bc0fcd8c740d5a8a3c1aea36b125fbf83148ce1efb1cfff6b88b362a045f1c2ca1bc0fcd8c740d5a8a3c1aea36b125fbf83148ce1efb1cfff6b88b364a045f1c2ca1bc0fcd8c740d5a8a3c1aea36b125fbf83148ce1efb1cfff6b88b365a0cec23763c1f03e9adaba802adb9cbe72eb9a7c221efa9496ee324863442cc600a0f001563bc94a5938a90a6eb9e7c9db7201706585fc453f514a4c32a74ac8fea9a0f001563bc94a5938a90a6eb9e7c9db7201706585fc453f514a4c32a74ac8feaba0f001563bc94a5938a90a6eb9e7c9db7201706585fc453f514a4c32a74ac8feacc0c0c0f8ca9400000000000c2e074ec69a0dfb2997ba6c7d2e1ef88ef845a01b7bdf7aaaf4a7bf4a4756f270ee99d9e9b6223012c1ac9bc423435181210951e3e239a0000000000000000000000000b8d0633c0b02e2fcb369e06715d073c26c3c82d5f845a01b7bdf7aaaf4a7bf4a4756f270ee99d9e9b6223012c1ac9bc423435181210952e3e239a0000000000000000000000000231b0ee14048e9dccd1d247744d114a4eb5e8e63e1a00dd4f47a3b6f87903b728feed7a23273bd8de58e4fa257d37a74a5881e5d92ffc0c0c0f8cb94000000000022d473030f116ddee9f6b43ac78ba3f88ff846a03bd1bc9f0c1caeea36d17918170b656242da38ab793ac7579de18100c8c2df4de4e381f9a0000000000001000068b19e59fffffffffffffffffffffffffffffffffffffffff845a0cd14cc5ae1bc0bd5d0ce6971ae58c160366f4e7c9af1891ede2bea70127122d1e3e208a00000000000008000000000007fffffffffffffffffffe61d10f967f8794fee23e1a0cdf6b22f99077db6230168c1d98f66ddbd6759b773c86de25e172d4e35b353c0c0c0c0f8719400000000009726632680fb29d3f7a9734e3010e2c0f842a00000000000000000000000000000000000000000000000000000000000000000a0de0d34d4f7bf3414f80d8d32502529d716ff1fa9f423cd1409d7f99d08fd075cd4d381e990000000000000000003dbfb9429ed8fc7c0c0ef940000000000efa780a8e6f50fc5de9c1497bfd175c0c0d5d482013a900000000000000000000001726124f520c0c0f8a5940000000071727de22e5e9d8baf0edac6f37da032f847f845a06df2cc124fb9eddfc698d0a07846758f68e88093a7a51be5f2bf8b7b005a825ee3e219a00000000000000000000000000000000000000000000000000000000000000001f842a00000000000000000000000000000000000000000000000000000000000000002a05e7790995149cd394adfae18934f7312b3f381581362ed0c970a982e2f17869cc0c0c0f89f9400000961ef480eb55e80d19ad83579a64c007002c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f8e1940000206329b97db379d5e1bf586bbdb969c63274c0f8c6a00000000000000000000000000000000000000000000000000000000000000035a01259640b4adb4af8dfa1fb1e61fcbedd02b012e121c8b41224ae42e6df833153a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca083e29ffce6d2fbf1f0c271d06886a38d0c891f440a61908907e91a556f6dd54fa093870757cb31b01a453ca904251af33e85caabe2631d491839de8262007e362da0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f9016c940000317bec33af037b5fab2028f52d14658f6a56f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e234a000000000000000000000000000000000000000000000000000000000000080b2f90108a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa012d911b54d832865fb42c73262858caaf8e30c53f33e1dc0fb0c2f5947b2c96ea0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca05e5777fab7622aff3c042c1ece74307c2e9d699a9da444f416c35f2e1def28a5a06568ffeb235291e33a361e55cdee2c50db5ab23b00145c8fac2f394be4192d64a09b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00a0cd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300c0c0c0f83b94000056f7000000ece9003ca63978907a00ffd100c0e1a08a544f7a90e767d9bea3a08f370e08d816168745881d640911c6f3d377888215c0c0c0f854940000a26b00c1f0df003000390027140000faa719c0c0f839d20390000000000000000032fd22d7fac8120ed20690000000000000000032fd63d809562f0ed21190000000000000000032fd724963187f0ec0c0f89f940000bbddc7ce488642fb579f8b00f3a590007251c0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f862940000f90827f1c53a10cb7a02335b175320002935f847f845a00000000000000000000000000000000000000000000000000000000000001ba3e3e280a0ac0a5a14f347269715b618c8fa88f9f8ad15f1fc1f6116d3e28b20ca41229fd0c0c0c0c0f83b9400071196a9129b7068404523e2047ef79f4ed0dec0e1a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0ee94000714bad5097985304ae187a64c80bd07e8694dc0c0d4d381dd900000000000000000926320eabb0ea745c0c0f8a994000f3df6d732807ef1319fb7b8bb8522d0beac02f88ef845a00000000000000000000000000000000000000000000000000000000000001601e3e280a000000000000000000000000000000000000000000000000000000000688a1197f845a00000000000000000000000000000000000000000000000000000000000003600e3e280a043382fa828ccb8ae9633e36a492715ec664985ebeccd035eadf056d1b39677adc0c0c0c0f9045d9400253582b2a3fe112feec532221d9708c64cefabc0f90441a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdaba00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdaca00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdada00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdaea00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdafa00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb0a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb1a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb2a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb3a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb4a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb5a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb6a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb7a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb8a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdb9a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbaa00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbba00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbca00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbda00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbea00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdbfa00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdc0a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdc1a00613e1309f1f4a675a311b2c378db255be0cf9c2d1154148fd1c6970f848bdc2a02761937fb52f3d1f6912a62e68bf2e7d1aff8a31e98b1b4af384509032186e7ba0c1f2f38dde3351ac0a64934139e816326caa800303a1235dc53707d0de05d8bda0c1f2f38dde3351ac0a64934139e816326caa800303a1235dc53707d0de05d8bea0d62134cece6a5228d4d7908227d8d5290aebe1688166fd47933dd5759abe3ddda0d62134cece6a5228d4d7908227d8d5290aebe1688166fd47933dd5759abe3ddea0d62134cece6a5228d4d7908227d8d5290aebe1688166fd47933dd5759abe3ddfa0d62134cece6a5228d4d7908227d8d5290aebe1688166fd47933dd5759abe3de2a0f714bc55ab500649c6fa224622306976f190fc0fd0f1713757fe954b1300af28a0f7e80f70c93aa356ac8612f343ec2951a94e0deae2dc0637eb52c7e05fd61ffac0c0c0f9012e9400869e8e2e0343edd11314e6ccb0d78d51547ee5f88ef845a02230d0ae1ee7ae79b8f3624e29ce1ebc0f74f53d3535fda30a2c2b5dcac7ee6ae3e21da000000000000000000000000000000000000000000000553be9b0bafd81e5e5caf845a03219a028fd7c2a1b908842a1d51cc4bacae5742282b971d6f1f91ab2d5f302e8e3e21da000000000000000000000000000000000000000000037fead2fcc74dcf04ba13bf884a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a058524eb6fc976a5072c9b12b3161d93b26c1dc1831012e447c4eae9abc63fdfdc0c0c0f901de94009c5b7ff119972e3437b51c4f94addb8dbb2bcdf9011cf845a08be2a7121582abe077530ad3c62f5f9b078bab9fc2ef2a83498c509bdbba8380e3e266a00000000000000000000000000000000000000000000000000000000000000001f845a0aa04e8c3bc33696b6de2dd8ca030700a124a634ce36389b0709be2297f814659e3e266a00000000000000000000000000000000000000000000000000000000000000000f845a0ca3ddf8200123e447b2fa02fd7902b43b3f4f836796f6cab49d15a1c074ee98be3e266a00000000000000000000000000000000000000000000000000000000000000001f845a0edb88eb6887722cedc23aeb78c448fd1ff70bcf56242999080473a6ebd466abee3e266a00000000000000000000000000000000000000000000000000000000000000000f8a5a025f533f5816fea98721b07c867512a09046bce89bbaef0fc894d3d6ef5121f89a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca038ab998390c0f0d40334ccd731dc9574d7eb8711b196e55a07122eb0cdf43e8da0c6c9b674dde25306a588253a1b458bcf70ced5cec4f3a95648b34af02e1be1aaa0ec042f961d17328f4e0f726775c0ee755b3072a4d82a7244ea5a7a0c683795efc0c0c0da9400c600b30fb0400701010f4b080409018b9006e0c0c0c0c0c0f09400ffb0a36fba9ba77ccfa8be56972ddcced05820c0c0d3d20f90000000000000000001e40d4fd3e18287c3c20f59c0f84c94012259c7510e65b3acad37cbb4fd67eaac24937cc0c0e6d25690000000000000000954c3ca972e77f184d25790000000000000000954c2abe581fbb2ccccc5568305d0a2c5578305d0a3c0f094018bb2f454385bc878d802685ae674c90d95444cc0c0d3d276900000000000000000000023bce5fce980c3c27604c0da9401dcb88678aedd0c4cc9552b20f4718550250574c0c0c0c0c0f09402215a21eb83e959522158aedc776a0dc995ee5ac0c0d3d263900000000000000000000873971cadb5adc3c26303c0da94025106374196586e8bc91ee8818dd7b0efd2b78bc0c0c0c0c0f09402e376b6d590680d99dc422be8ce718730e15d50c0c0d3d24a90000000000000000000074516d2822081c3c24a08c0f09403176009748a757985c79f852498c279cf6d24e9c0c0d3d259900000000000000000000750326782c4fcc3c2590bc0f294046db6d00ad3ba82966b6f79099d49c5e3e5fb0bc0c0d4d381aa900000000000000000d7c8ee5411a74e47c4c381aa1dc0f9026e94050362ab1072cb2ce74d74770e22a3203ad04ee5f901cdf8afa00000000000000000000000000000000000000000000000000000000000000035f88ce25aa0000000000000000000000000000000000000000000000000ea65558350590000e25ba0000000000000000000000000000000000000000000000000eb16f83f7f1e0000e25ca0000000000000000000000000000000000000000000000000ebc89afbade30000e25da0000000000000000000000000000000000000000000000000ec7a3db7dca80000f845a0210b973bd7f4c7abc9f7e83edcd2c13ee790c598665119fb3e22b41abb2ada2ee3e25aa00000000000000000000000000000000000000000000000003997c30329df0000f845a0970096429554d9b710fc3248002defd83d960e12966bca99048d04b5ef9a98ece3e25ba00000000000000000000000000000000000000000000000003810f6985c940000f845a0a466451530d5054781a968d8eb1f4bb39149ce649fff0a5c6f4102ba1b3113a9e3e25da00000000000000000000000000000000000000000000000003a9073a438260000f845a0f997360e2ba32811e5839df15d4bef8dd6d5f7966f9430fb6d9c26aa2b5de66fe3e25ca0000000000000000000000000000000000000000000000000404110781e0f0000f884a0000000000000000000000000000000000000000000000000000000000000012da0000000000000000000000000000000000000000000000000000000000000015fa03b1c0a3df7a3e29a3f093a0d57306f7741c6da12b68b39213eb87923f236dc12a0a3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50c0c0c0f294054ae108de8f68dab97b8508bacd6a915381b14cc0c0d3d27b9000000000000000000000000000000000c5c47b82021ac0f90108940591926d5d3b9cc48ae6efb8db68025ddc3adfa5f847f845a00000000000000000000000000000000000000000000000000000000000000005e3e230a00000000000000000000000000000000000000000000000000000000000000851f8a5a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a04a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8a0528a00ed2fb81b8539ca83d896e99a8b2447d5745188ae265814adcc5d59111ca0d2c3c16adb7c0c03ff3dacd2242c2e2532daa71938073ae5631ae9b5ed200fb7c0c0c0ef94061be0bc209a27588b7e85c0032ac8a0cdfd12a5c0c0d5d482011190000000000000000001789c3bf3c0d622c0c0f294065ac3d33fec104fba9f2f4d674afaa7c4ebcf43c0c0d3d23290000000000000000101815b9db36ad3e3c5c432827792c0f29406a4c42134cf1dca553112f71249dac3b9862242c0c0d3d2269000000000000000000e9f9c849ccb0f3dc5c4268203edc0ed9406d9de307b550b54acaf8a654322da29f45cfe05c0c0d3d257900000000000000000000102b674ed31f9c0c0f49406e0e19d02025dd6d9b5d6dde8109d2e7456f8f9c0c0d5d4820111900000000000000000000028b74a532d92c5c482011119c0f094075288e6dbea91c969cb1c93a425573c24c4093ac0c0d3d2559000000000000000000001f4d813521a0ac3c25549c0f83b9407a5739ab2f4590f1909ed5fa110a92ef940fd70c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f09407cb86bbe058cf36534f2f723e4b374b97723622c0c0d3d2189000000000000000000005f1ca9899c28ec3c21802c0f9012e94085780639cc2cacd35e474e71f4d000e2405d8f6f88ef845a07f084301dccacb1b31603f885ad6fa465363b06dca39d826357104bd038d9ca2e3e251a0000000000000000000000000000000000000000000009010b47b07a0c7ad20a6f845a089bcc199ac17a3d0b6a47a9f375743e8c6409684396005aa50bd459b3d4db4afe3e251a00000000000000000000000000000000000000000000cef77fe89dec6d5c58345f884a02bcae90d8019b744a79f5fe0371b29d49746ee29e5dd653f2bdf45154fee95b7a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0641a0b1cce6113928b4c3514efbfa57e40e5ebbcfd4cba8e76085e20913b9429a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f90133940a39d62bbb4976ae2e4184dc365e81fb73356b5cf8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e236a0000100000100010000fcf9e00000000000000000000342d58d71716ed4c78e97f845a00000000000000000000000000000000000000000000000000000000000000001e3e236a0000000000000000000000000000001c98a70671d00653e164f89f7d6bbc6bbe3f845a00000000000000000000000000000000000000000000000000000000000000008e3e236a00100000000000000000000002807c294a8468df98efffffc0bb16d10688a1197f842a00000000000000000000000000000000000000000000000000000000000000004a0d8a47cc0a98b326a7d41ebdba69f2440a3db7a31c7edca3bc5f01523d2fabe24c0c0c0f8e9940a992d191deec32afe36203ad87d7d289a738f81f849f847a064e1acb13eeb851346a80b7c1d1f82fe74310c98872564af7bdbafd45a27e559e5e482011ca00000000000000000000000000000000000000000000000000000000000000001f884a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000004a0e02b1e83e1e04adbc50a55e8ca8dcb102afb9a3d974fcc42100380e5f67d8b30a0ee01226252351611ebf84fc893d9de7477a97e335dd65a37f0ada02ea12ffce3c0c0c0f3940ab3fbc9025ece0ea4e0f9d29fbaa94b70923e37c0c0d3d26c900000000000000006212a96e30568db5ec6c56c83012a27c0f863940b2fa18342d38909e3e44d7103cf3f37b0a0403af848f846a06f3504a2e281620f68052ebb2f627e08c0bcbc440d737a4153561a82f2ad80cbe4e381cca00000000000000000000000000000000000000000000000000011abb81f05d296c0c0c0c0f2940ba0a5f4425129dc261d5323e9c0066fcd2e24d0c0c0d3d22490000000000000000002aa1dcacf3366ddc5c42482074ac0f87e940ba6d224169bbfad4eb2d38c500d2c8e8ede696cc0f863a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000004c0c0c0f90166940c1df9ddd357c326b410f60904242cd8917bb931c0f9014aa00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000ea013f7507e64d7986f1516239122fd35a72de0baad7e5bb26e6e6b23e8984ca8baa03817baa969faf1767c244f2ade78fa0b80530927b15a252978a418205db88ceea040fcb98b1130c08bba5f040aad7cf7f223963a65ccb9e2e5ca7f16237836eb05a06d7dfbb9a4112df2130eae7564aef3bc18a2fc1beb290b0df160fcdbec51645da08a0b52ec4a28516cc30451632938dcb471f4ab15f9b45401ea30fe063ce583f3a09ff29f90a7de0646e6a8702b3cfbd32219dcd7e154abdb806954853bdb997faca0d33a68369d4ef6824db0497f69f2488da5c3eedac6cd7a3d84497f5d6aed69c8a0f942f4688cdba65adc8aa59da583acae93fa87351143ebc775559218bfa5f832c0c0c0f87e940d5f4aadf3fde31bbb55db5f42c080f18ad54df5c0f863a0000000000000000000000000000000000000000000000000000000000000000ba0c6e53a41f830843dc3dcf4e311f022c8cb332183ad4f7d1997a8b38406719084a0c72832eeeb6c954f2d21e989302b29df1947780c65be799f4fd90fe3c616336bc0c0c0f83b940dd0489bf03f0e31489ea33114ab261959c89986c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f2940dff7526ebe29be4553d1f5817d8257da3f2579fc0c0d4d38197900000000000000000000131c2ae2a225ac4c3819702c0ef940e331a293bb22e93e2e5e417d84d755ae185da02c0c0d5d4820142900000000000000006c58ee435813c1d3fc0c0f898940e87bf5286c4091e0eeb7814d802115dfbb4c4cdf848f846a025b5e2a05b4193c39534625acd7c87082dcea62b0cc5a23f5c4d85cbc89f5dffe4e381b0a0000000000000000000000000000000000000000000000000000000688a1c2301e1a00000000000000000000000000000000000000000000000000000000000000002d4d381b0900000000000000001cf7cf20fcb14ead1c0c0f2940e8cf9f54fe94c31331b1a147cdc5dd81cfab549c0c0d4d381e19000000000000000000001518fb762f894c4c381e102c0f5940e9e39190df7a2d38872736910cddfa519e419cac0c0d4d381e590000000000000000665fef590e3cf450bc7c681e583075aa7c0ef940f7936a55a6c29c4c344b50acea925142b092a1cc0c0d5d4820117900000000000000000000221b262dd8000c0c0f901549410f9b71043ff1c882dd7b928e724935b9dcca9ddf8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e25ea0688a119700000000000009577b1f00962acf000000000000132ec8981331331df845a00000000000000000000000000000000000000000000000000000000000000009e3e25ea00000000000000000000000000000001941a2d04687f462dabd5af793445a25c4f845a0000000000000000000000000000000000000000000000000000000000000000ae3e25ea00000000000000000000000000000014262b427a98cbe305c52b58fdff50b9894f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0da941103c0550771ffb586688ffbba90c6b85ebfc224c0c0c0c0c0f88394111111125421ca6dc452d289314280a0f8842a65f847f845a0b60746c954665f59975784c796b478be7bf99adc1977555d15b50bf6290dacd5e3e265a00000000000000000000000000000000000000000000000000000000000000001e1a00000000000000000000000000000000000000000000000000000000000000003c0c0c0da941111111254eeb25477b68fb85ed929f73a960582c0c0c0c0c0f9015a9411b815efb8f581194ae79006d24e0d814b7697f6f8dbf847a00000000000000000000000000000000000000000000000000000000000000000e5e482013ca00001000190019000e1fd0a52000000000000000000040674d11d82761bcc0f3ff847a00000000000000000000000000000000000000000000000000000000000000001e5e482013ca000000000000000000000000000001b75688b1a5df1f39b981ff71e64e1963da9f847a000000000000000000000000000000000000000000000000000000000000000e9e5e482013ca0010000000000000009cd0413e671a73798ef47b227ffe7d68bc8d0b1688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000000e8a02f2606b2c0d121a5cc1b59088ba7234e9d1c805f41724c938a2661d69532e0e9c0c0c0f90124941231deb6f5749ef6ce6943a275a1d3e7486f4eaec0f90108a00bec691802661a606c8bc6fe9577f8e94849b97a5ffd094963f0c77c0585626ca031f5357de7a47a7ed3a6810af415c7ed66aeb4651d07848ead0606cdba9c8a16a057a1b6793381655140bd623273394b8dab82adb402765f3433b81ecf035e4a2da0a65bb2f450488ab0858c00edc14abc5297769bf42adb48cfb77752890e8b697ba0b269973686955134850c5973c540ca3061d6a326964b2224ddbc00023a045092a0b3f3361cb646afaced8225648f30eec402f4d7cd20ae3ceca8ad57b39fc3391ea0d4b09808ca0bb39d4433b29d05786b45ea1ff016726a2b747acb136db277e42ea0e316ae1a88fdf2c09ee13c9e4dfa09930537e6530cd8f8929c42b22bdd863f69c0c0c0f49412825cdba96a46fbc7d3e3fba2c9ff3dd84d09b8c0c0d5d4820136900000000000000000000000c2cb0de1a0c5c482013606c0da94136f1efcc3f8f88516b9e94110d56fdbfb1778d1c0c0c0c0c0f494138d2da478ae9873c8185bafc17d9e5da9a0598dc0c0d5d482013590000000000000000000b02d1ac5e49641c5c482013503c0ee9413e1ec44037bf6764950777b8d2b1b26b99632f6c0c0d4d381d9900000000000000003afacfa39d1c98000c0c0f29413e9363758de918ee531482a7b19e615aeede423c0c0d3d237900000000000000000006ef7edc46bb630c5c437820fddc0f83b9413f4ea83d0bd40e75c8222255bc855a974568dd4c0e1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0f85d94156f73fc73197555e950743cb2b23f411c751002c0f842a0185e45d88af45849d78c989b76920d90583eaa2278e64a5aae6329f2ef0dee51a0834458dc7352fae924833cb7598fe0247c42914818a279b8ce4e2c01039b7a80c0c0c0da9416623c35ef61d92aaaf98f25bd64ea962fbd6a12c0c0c0c0c0f901579416ac64ddfacca437be7fe0da0c45bcd97227588af8d8f846a00000000000000000000000000000000000000000000000000000000000000008e4e381fca0688a11970000000000045c73e2cfc593365d0000000000000005f64ab89cd250f846a00000000000000000000000000000000000000000000000000000000000000009e4e381fca0000000000000000000000000020be0ffbacdfa2c2765fe6b22b9ebd04e77f94cf846a0000000000000000000000000000000000000000000000000000000000000000ae4e381fca00000000000000000000000000000000bc0b06045763b2c6afe45b7e64204b0b8f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0ef9416beb8782c2607fd90ca93f3119cf12c00255c6ec0c0d5d48201479000000000000000444d78be7343619026c0c0ed9418342abc7cea61c04889f145c89b584a2ca93778c0c0d3d20a90000000000000000000118928417e22c6c0c0f85d9418577f0f4a0b2ee6f4048db51c7acd8699f97db8c0f842a0000000000000000000000000000000000000000000000000000000000000003aa0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f901fd94186f04af76ccf1a99f86b49175e89960903de747f8d8f846a06783cf743dec8bee953a966099d2b43454e44d609db101887fb8b2aa3c76c8a9e4e38183a000000000000000000000000000000000000000000000000000006ec7542acfd6f846a08ac150a06d1303d7b276beccd2df131d9360ab0036a5e5094aa0dc068c8b821be4e38183a00000000000000000000000000000000000000000000000000017d8cab133ddeaf846a0d89359e15bcd91e83fcf5d0a4b0acf703026fecff85245f9ce2baca63b0aab7ae4e38183a0000000000000000000000000000000000000000000000000015ff5f4c7e657b6f90108a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da037836a7135fae77e265e35732c70286035736c8b57b12590769780e067ead81ca09eb9dbac5972920082cba1eea1e2792b8033f91c80f7f1b245e77d3ef4fcb68ca0c8092037aa878ba2113bb8a8fd8f2ff609bea97a87d4e80ca61a16f0ca18dd99a0ce0fbc8cb1df34eb23ee46d9cf16e1a93241e42344197171bd07612b0bde7491c0c0c0ee941876d76b77ddf37a78a073aa35131bd5f58f19fdc0c0d4d3819f90000000000000000000016bcc41e90000c0c0f29418e296053cbdf986196903e889b7dca7a73882f6c0c0d3d231900000000000000002157410a215fb36b5c5c43182a2cec0f8ca9418efe565a5373f430e2f809b97de30335b3ad96af88ef845a00000000000000000000000000000000000000000000000000000000000000036e3e208a0000000000000000000000000000000000000000000431c1b69e12ab108b99cabf845a094d7c397f910069f86ea2de900e1f4e266063e4394049dd244977c755e42c4a5e3e208a00000000003462ad45178006aa8f3ad0600000000001503436cfb7c4b872d1eb9e1a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f094195d09fcba4ac3f93898e51f6edaa7fbf1f8c0aec0c0d3d24d90000000000000000000074516d2822081c3c24d05c0f294197bced376e0dc12c00dcc19b43d2afd9b8533c8c0c0d3d2789000000000000000000fad652b70acc705c5c478820994c0f90102941a7e4e63778b4f12a199c062f3efdd288afcbce8c0f8e7a00000000000000000000000000000000000000000000000000000000000000035a003cc2d281da0ffdaa3b9283c2e5b394b074a5ededae910f6bb6e3a4d11a010c5a01259640b4adb4af8dfa1fb1e61fcbedd02b012e121c8b41224ae42e6df833153a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca05fedb3d67ddedda3418dd7feb9217a7583ec41bc24951ec14e72901d3624b9bea093870757cb31b01a453ca904251af33e85caabe2631d491839de8262007e362da0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f854941ab4973a48dc892cd9971ece8e01dcc7688f8f23c0c0ead48201149000000000000002809b0b354bd69bb549d48201159000000000000002809b0a9d8f26c1bca6d0c7820114831bd300c7820115831bd301c0f902e2941abaea1f7c830bd89acc67ec4af516284b1bc33cf8d6f846a08c40cf33b9ceba87302dac0dad21186d7b361192fb3f09d1b96fc4f94580601ee4e381ada0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff845a0dd21eaa2a7b91d700e186a088ec24a6ffae451cbb867f014da63e9784f6a5049e3e210a0000000000000000000000000000000000000000000000000000000000098d8fef845a0e9a042b510c4a11346b2bb8943779a6be59156b3193c52e4e1d3b52c400ec46fe3e210a0000000000000000000000000000000000000000000000000000001d71dcada73f901efa00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000006a010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba0115839bfee55578761c7edec3b2ff273385ed4d5df39b75ab78870ff966c69b8a01f1a80db9a82d9fd4ea3322b7d2dcad52fb678229d487c4bb44e821a2544709fa024bd6c6f7d1eeabad95a250d5ab0bbeaae9c7223e36ba7b50aea221573ce7122a02b37bb377e3251959b71e36eb5119bc1a95c91cb6bc962484ad3386a13b4db4da040ef085811c240d7d326b31f33d2ba08796489e37cb6fc10c7a3ea547d92cba0a045dd9f8e28a0592fd98e277ec8668c9664b6acfea9def7dcae17c6bf9c297112a07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a09bdc0f5dbc7c5ca99391aff6b41dba4ce516f1bcb64a70dc8a8338bf35d08e49a0acfa85f8dca23f171b19bf4838ca8f35894504326d0e8354507c4fd5f1a60701a0cf842f22bd921fa3f6c476d873397f10ff82548909c51ec0b56f889a142139dfa0de2897c64f0af56474cba6e3bb2d58b5c85a42ee6dff042546bc919c03512137a0ef83a7fd694bd220a3a3d2f147c273844b8326010a4cfff2bc72b6665aeb6b13c0c0c0f901de941ac1a8feaaea1900c4166deeed0c11cc10669d36f9011cf845a00000000000000000000000000000000000000000000000000000000000000000e3e207a0000000000b000b000302f5b20000000000003f9d9d5f88995fd963b9687a79f8f845a00000000000000000000000000000000000000000000000000000000000000002e3e207a00000000000000000000000000000000000037373281c28b3736d9f0a73bd212bf845a00000000000000000000000000000000000000000000000000000000000000004e3e207a000000000000000000095ba901291763e00000000000000000000000009760941f845a0000000000000000000000000000000000000000000000000000000000000000ce3e207a00100000000000001e449e9956ad20d18fb4328a5b2000d4256f7a0e8688a1197f8a5a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000010008a07a74ceeb7e4916057732409db262d7d77bc2c68592f515eadf744fc0b19727d3c0c0c0ee941b715ddc19e3738251f91ea21e9c3eb702f5c9b0c0c0d4d3818b9000000000000000000ab70821a716ac37c0c0f2941ba114a6cde4fb1f65039cfeba13eaca7fda67ccc0c0d3d25290000000000000000000557d9614b5c0ccc5c452820180c0f2941ce998946b00b61a00f69a77049aae108c408fcbc0c0d4d381ed90000000000000000000012c36c7310690c4c381ed06c0f83b941e0049783f008a0085193e00003d00cd54003c71c0e1a058d378c017b147e20c5e2165d42ae5632b3439c6aa34a0ecc35c5db91bbf26a4c0c0c0ee941e4258d9a285d3382c21b5555239468defb91d6ec0c0d4d381f7900000000000000000001ff973cafa8000c0c0f83b941e9297c3c1bd669f86522763f16b0e4ebf673b21c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0ef941ede399f9ecbca800a2655f4274f3df535ed7123c0c0d5d4820146900000000000000000107c6170c3920a53c0c0ed941f47f15fdbc9b9ba5b72e90c3ee0324db5cb09fac0c0d3d268900000000000000000000aea082ab80b49c0c0f8ee941f9840a85d5af5bf1d1762f925bdaddc4201f984f890f846a0ace61e29be7067193354e9e9063ec5a13634b45c5589e1cc0dc901f3b7f30287e4e381caa00000000000000000000000000000000000000000000022180c2bacf589db1800f846a0b11b2b2fb103174cd512ae891e4756b7aab3e7f6f806bd0a18eb436b3174f84be4e381caa000000000000000000000000000000000000000000000000000000000001c0000f842a008cba8c407b93d4ad104b6aed9467903dc943b52ce23fabf876bf1ad44e0d3b4a09de5f436cd59c5c3a345cf320707d5a259457b4cc5665c19201d1db59b103a6dc0c0c0f90111941fc122fe8b6fa6b8598799baf687539b5d3b2783f8d5f845a02422a63740c835eaf609f2253a3d68f4f962e5e3700be653757bffb7511df3ace3e26fa0000000000000000000000000000000000000007e37be2022c0914af5e7701080f845a0350c7aac05939e3aa7c17dfd2940b6a28728d4d22476d6d9b7bbcadc72c16d67e3e26fa000000000000000000000000000000000000000000000000000000000000ed30bf845a0b90fd484c7fdab79d88527e78135ea54d0fd198d20d41ea87e625a17346712aae3e26fa0000000000000000000000000000000000000000000000000000003dca1285520e1a00000000000000000000000000000000000000000000000000000000000000005c0c0c0f901579420ce9219b423ab30468ddb5fde57ab23dc2a7c65f8d8f846a00000000000000000000000000000000000000000000000000000000000000008e4e381e9a0688a1197000000000000b5ee1c2237dc7145000000a712c2d2f54d7f7551d065f846a00000000000000000000000000000000000000000000000000000000000000009e4e381e9a000000000000000000000000000000000000000470d848b898bfb80cfb0c92c18f846a0000000000000000000000000000000000000000000000000000000000000000ae4e381e9a00000000000000000000000000016693c17c40b1239af4313853891f010ce9aacf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f86c9421a31ee1afc51d94c2efccaa2092ad1028285549c0c0f83cd381cd9000000000000007b0ca206c1555136178d381ce9000000000000007b0ca1ffa7aabda8ce1d381cf9000000000000007b0ca1805aac9431789d5c681cd83bfdde1c681ce83bfdde2c681cf83bfdde3c0f9064c94222222fd79264bbe280b4986f6fefbc3524d0137c0f90630a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df77a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df78a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df79a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7aa00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7ba00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7ca00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7da00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7ea00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df7fa00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df80a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df81a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df82a00b4728f31968907413766ead4408f0d695318c988a8369b4fc85a7183816df83a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df4a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df5a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df6a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df7a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df8a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214df9a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dfaa049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dfba049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dfca049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dfda049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dfea049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214dffa049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e00a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e01a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e02a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e03a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e04a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e05a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e06a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e07a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e08a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e09a049c934932e9be834913ca97d2d2d63ba4f81ca11e1c1a0fa922b5b1199214e0aa074bbe6d0cc07a4b7d1ec352e4461c00af830e486fe31a0ab5a772b85a189628ea074bbe6d0cc07a4b7d1ec352e4461c00af830e486fe31a0ab5a772b85a189628fa097f94e1b6f1f73e598ab5fd3eb79c7b5cb0599a73b01d3883739b7c31585aee0a097f94e1b6f1f73e598ab5fd3eb79c7b5cb0599a73b01d3883739b7c31585aee3a097f94e1b6f1f73e598ab5fd3eb79c7b5cb0599a73b01d3883739b7c31585aee4a097f94e1b6f1f73e598ab5fd3eb79c7b5cb0599a73b01d3883739b7c31585aee5a0aff9bc33f5c50fb76b881dfb0c57123bf484a2c7240cee706070de06f0a1cfaaa0c1f2f38dde3351ac0a64934139e816326caa800303a1235dc53707d0de05d8bda0c1f2f38dde3351ac0a64934139e816326caa800303a1235dc53707d0de05d8bea0c1f2f38dde3351ac0a64934139e816326caa800303a1235dc53707d0de05d8bfa0f714bc55ab500649c6fa224622306976f190fc0fd0f1713757fe954b1300af28a0fd48a94d0d1a2d046b04cee0722784378c199fc6dea8c8f8bdca5dfcdf9a3cf6c0c0c0f09422243827a8df1aa885dab5ee7687797ea81e41c7c0c0d3d25190000000000000000001dd0e22b67eafa5c3c25151c0f90159942260fac5e5542a773aa44fbcfedf7c193bc2c599f9011cf845a045f0b7fa21a4287c5918668b6ede365097a5a85bd0ca3a042787e67ede5e18b4e3e20ea0000000000000000000000000000000000000000000000000000000000286cdf0f845a069a1997d5f27fd969be349ea22f3e24ef4e94682f22fa7a1b6e23f01fc312c59e3e20ea0000000000000000000000000000000000000000000000000000000000000001af845a0886009abd6dd5151b65fee0191dd9d9bd486b40a87d629d2d1d4fd72013f2859e3e20ea0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdf6ae7f845a0dc276a4f120117ad5ae6415d1c724b4f3a0e81f0ee6466e1392ca121b63123f2e3e20ea000000000000000000000000000000000000000000000000000000000d37ac93ae1a00000000000000000000000000000000000000000000000000000000000000005c0c0c0f88394231b0ee14048e9dccd1d247744d114a4eb5e8e63f847f845a02b1af9e4b5950876f69e3833c3939f3309f28bd37d533255756b953403fa2291e3e239a0353636372e686f6c65722e65746800000000000000000000000000000000001ce1a01b7bdf7aaaf4a7bf4a4756f270ee99d9e9b6223012c1ac9bc423435181210951c0c0c0f49423214fc64f1b609ff79e3ebb666ff892e2c0ca40c0c0d5d482012d90000000000000000000000ae6e57aff18c5c482012d2dc0f79423f4569002a5a07f0ecf688142eeb6bcd883eef8c0c0d5d482013190000000000000000698df6a5cd76d93f8c8c7820131830a7826c0f9030c94240d6faf8c3b1a7394e371792a3bf9d28dd65515f90120f846a0000000000000000000000000000000000000000000000000000000000000000ee4e381fca00000000000000000000000000000000000000000000000000000000000008770f846a05d0b89f1b51e963958b73fa8b0835e08bfdaecca196291273f337d25b48d0ab3e4e381fca0000000000000000000000000000000000000000000000000000000f934085867f846a086dc9f48f922470d891ad8166fd17c619a5348d23af6c21756b135fb49eb5ac6e4e381fca00000000000000000000000000000000000000000000000000002748f74e45858f846a089f5b3833a9bc58098227bec834ea97ac15be9fb10bb4ab19443b191c76f42b5e4e381fca00000000000000000000000000000000000000000000000000005f64ab89cd250f901cea00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000013a00000000000000000000000000000000000000000000000000000000000000014a02c681bc5e3516f0bb262f170d216d88300ee524f00f4e27e476716d2242a9e49a0a16803f2d86ce308ef835139aa4ab43f190fbf8b1d2d71cb9f8475167e35c40ba0b4c2b7416d73f8e278a67a96d8dee301381a96139e7d1e6430e319f9b0eb43d6a0cee0c0d05f224c5801a59822161796562458a631ec31c0bbe4f7b20cad8f8a7da0ee61682ec263cf9ac626d5a9b0cd5e26edbfa98b5b368b540e65bd739c37e3b7a0f617aed219efc795cc0cd455bc8c1b56c59215667268ccc45369199554342a26c0c0c0f494245287bb1afafac630a0e478872ec1fef4048bb8c0c0d5d4820105900000000000000000000af93b5d92c51dc5c482010546c0f4942458e701b1b51911540bf8259f112bcef43b3b0cc0c0d4d381e390000000000000000000f283f06160e9e9c6c581e38201aac0f85d942486ff197be4553775daf666a815deac482bb6b0c0f842a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003c0c0c0ee9424c62a454eed391d99b152299adf160792417489c0c0d4d381fb90000000000000000000057f249641d400c0c0f86394253553366da8546fc250f225fe3d25d0c782303bf848f846a00e8c03cdc070ac416f451e06ce792af4fd5ef7708902542b2828cd0910b109fae4e38191a000000000000000000000000000000000000000000000000000000000688a1197c0c0c0c0f694258494a21d9ea90fcbcb9e22bd57c6899de0d995c0c0d5d48201229000000000000000005eace9819926d664c7c682012282cfb6c0f29425ac9c9a876e9739a035036d87e7e64c7f3f42f5c0c0d3d25a90000000000000000001f5e8c122db6ab4c5c45a82042bc0f19425d4c9c8abf1fa541f194fd93e4e3cac07e509e8c0c0d3d20b900000000000000000048ebffba735c80fc4c30b81fbc0f49425ea2f3efd7fd87b830c993e55e1fb6066c753b1c0c0d5d4820109900000000000000000001203178dc2e612c5c48201091fc0f294260b364fe0d3d37e6fd3cda0fa50926a06c54ceac0c0d3d220900000000000000001175975114efd9eb8c5c42082392ec0f494265867026965298ce9579b1a04899454ebfb12e2c0c0d4d3819690000000000000000001fb9827009542bbc6c581968201bac0ef9426ab2ebe1a3bd8c6b752e9d5765728f8e5706496c0c0d5d482012f90000000000000000003f7ed4932ae6000c0c0f930dd9426d85a13212433fe6a8381969c2b0db390a0b0aef92f54f9066da00000000000000000000000000000000000000000000000000000000000000008f90649e218a0000000000000000000000000000000000000000000000000000000000004d277e23ca0000000000000000000000000000000000000000000000000000000000004d278e23da0000000000000000000000000000000000000000000000000000000000004d279e240a0000000000000000000000000000000000000000000000000000000000004d27ae241a0000000000000000000000000000000000000000000000000000000000004d27be242a0000000000000000000000000000000000000000000000000000000000004d27ce243a0000000000000000000000000000000000000000000000000000000000004d27de244a0000000000000000000000000000000000000000000000000000000000004d27ee245a0000000000000000000000000000000000000000000000000000000000004d27fe246a0000000000000000000000000000000000000000000000000000000000004d280e247a0000000000000000000000000000000000000000000000000000000000004d281e248a0000000000000000000000000000000000000000000000000000000000004d282e249a0000000000000000000000000000000000000000000000000000000000004d283e24aa0000000000000000000000000000000000000000000000000000000000004d284e24ba0000000000000000000000000000000000000000000000000000000000004d285e24ca0000000000000000000000000000000000000000000000000000000000004d286e24da0000000000000000000000000000000000000000000000000000000000004d287e24ea0000000000000000000000000000000000000000000000000000000000004d288e24fa0000000000000000000000000000000000000000000000000000000000004d289e250a0000000000000000000000000000000000000000000000000000000000004d28ae259a0000000000000000000000000000000000000000000000000000000000004d28be27ea0000000000000000000000000000000000000000000000000000000000004d28ce27fa0000000000000000000000000000000000000000000000000000000000004d28de381aea0000000000000000000000000000000000000000000000000000000000004d28ee381bea0000000000000000000000000000000000000000000000000000000000004d28fe381c0a0000000000000000000000000000000000000000000000000000000000004d290e381c2a0000000000000000000000000000000000000000000000000000000000004d291e381c4a0000000000000000000000000000000000000000000000000000000000004d292e381c6a0000000000000000000000000000000000000000000000000000000000004d293e381c8a0000000000000000000000000000000000000000000000000000000000004d294e381d1a0000000000000000000000000000000000000000000000000000000000004d295e381e4a0000000000000000000000000000000000000000000000000000000000004d296e381f8a0000000000000000000000000000000000000000000000000000000000004d297e482010ca0000000000000000000000000000000000000000000000000000000000004d298e482010fa0000000000000000000000000000000000000000000000000000000000004d299e4820116a0000000000000000000000000000000000000000000000000000000000004d29ae482011aa0000000000000000000000000000000000000000000000000000000000004d29be482011da0000000000000000000000000000000000000000000000000000000000004d29ce482011ea0000000000000000000000000000000000000000000000000000000000004d29de482011fa0000000000000000000000000000000000000000000000000000000000004d29ee4820133a0000000000000000000000000000000000000000000000000000000000004d29fe4820134a0000000000000000000000000000000000000000000000000000000000004d2a0e4820139a0000000000000000000000000000000000000000000000000000000000004d2a1e4820143a0000000000000000000000000000000000000000000000000000000000004d2a2e4820144a0000000000000000000000000000000000000000000000000000000000004d2a3f845a001c7b51d269b3bd26cfe3377d5788f5d8a124597ac0f4320e4acdbbb4490d217e3e23ca00000000000000000000000000000000000000000000000000000000000000001f845a0023563ddca61babfa9367b24f7a5af97c80644872dc32f7e9a7dfe2c723ecfd9e3e24ba0000000000000000000000000a869c2b2546945b17fbcac82127e4b50b7b46eecf846a002492cee0c19418cc18fe865a79c051c3b2891c5b5c7bc55a1fe2135837831a1e4e381c4a0000000000000000000000000b53b99ecdb5e3e6c5ba60c04292d225c766435d9f847a003d1ad6171cabfc9761d6784dcafc333b471a77be4c1e5257ac6a2e0bdfb7538e5e482010ca00000000000000000000000000000000000000000000000000000000000000001f847a0065cb5cf44f21c8e6dfae32d712d1429a9be301615e42ac25b4556ae4668585be5e482011ea00000000000000000000000000000000000000000000000000000000000000001f845a006d595c0c546a50bba19672cb41ee05f9fc9538406e77ee48e2779d97dc0b78be3e24fa00000000000000000000000000000000000000000000000000000000000000001f847a008182415916572173dda50f2307a8f00d9b271c6f8782d509d0bcbd0f6059c0de5e482011aa00000000000000000000000000000000000000000000000000000000000000001f847a008492a8bf060586aeb075f5bbf285763147d0a778b9d78d60f391082796bf4d0e5e482010ca00000000000000000000000000000000000000000000000000000000000000001f845a008b305075006aad97d950ef8c77a457b6523d3a177f3edf12a6e64ca403c4f02e3e24da00000000000000000000000000000000000000000000000000000000000000001f845a0099c6ef5aa6a4c1e9a3be59509c91076c05c3333924f9847b66254db7480e887e3e246a00000000000000000000000000000000000000000000000000000000000000001f846a009ac6f6a902ac97aebb793401ff773cc8604451550cb6fc3b9b974455427d333e4e381c8a00000000000000000000000000000000000000000000000000000000000000001f846a00a04f2bc9631644195e2ed103c16f2f02c70055febd326cbafb967bf3a51c293e4e381aea00000000000000000000000000000000000000000000000000000000000000001f845a00dcf590c3286f494129c8fdaafbf8ad4268a7b36bc228b34d654d2374cfc7daee3e24fa0000000000000000000000000d4b454f6f95d9c66b38383810de0fa8cb7c135fff846a00f6a8809a1d761e2412d5e5f45e104df6d5105f1522b690032c512f061f4717fe4e381f8a00000000000000000000000000000000000000000000000000000000000000001f845a00fadac1105f0ce6216f0a1e200989a175b54a9d690fe5ac5707ec95c6b6d1be5e3e246a00000000000000000000000000000000000000000000000000000000000000001f845a0109d419491277d6bbb523197641c1f7a1cb7219d37303c5170f3a7d59751561fe3e24aa00000000000000000000000000000000000000000000000000000000000000001f845a011217b655961bb5f7e38c069773ee88a15dec9883fa435226e9ec9a46e75e7e5e3e244a00000000000000000000000000000000000000000000000000000000000000001f845a012eab2cf36bae9871896d6cfd7fbadba2a3857778288e90b704f5cbfe81acfd3e3e240a0000000000000000000000000ca43f78dd8a9c8e96b53dea4e13bda504a0f8c4ff846a01733b0890ca89ef5f97dc3f2ac040040814182384890717cde4f09d440a3f492e4e381c2a00000000000000000000000000000000000000000000000000000000000000001f845a01853fe16d1f8e5db8a7f7ec8334ac7472bfd692aaae23cecd1bad6c019f64113e3e23ca00000000000000000000000000000000000000000000000000000000000000001f847a018b32fd875fa28caacc7d044ce63a24787a6efd776d016cc7260c355df8a510fe5e482010ca0000000000000000000000000571f7be0d706e84bfc902fb503f668f73d97636bf847a018c663f03ca11fdb12822c154443dee653baa6bd2b24d8e4637c213e7ff4b44fe5e4820133a00000000000000000000000007f06f9a74ed2cf670c8320d5e53c88442dafedbaf845a01acd654019360477df7401d794a8565481097e13d88975cc81f2ee8f44ec54b3e3e27fa00000000000000000000000008381f65f5ec4fdd94a5c033fa4261085fbd005cbf845a0228eabc0daa0eba8ed5ef4a0d407f2989d365cd161c8c84f9026da3d43c2ea6ee3e259a00000000000000000000000000000000000000000000000000000000000000001f845a0236c781ae80cbdbea0071847d4b5202a045e5c168b050c60e60207e6fd303424e3e27fa00000000000000000000000000000000000000000000000000000000000000001f846a02465b20f86e1184d1214573d40864ad0cd24d58534e934ebe368745dc2226019e4e381c8a00000000000000000000000000000000000000000000000000000000000000001f847a02b03b506f06affef971166caa8d710bc0dc885044b4403661e8e7a19af25e8e3e5e4820134a000000000000000000000000070fb47c51e443e3106625fbbe839b6b21aa9f09ff847a02b3c06387edb756b68e999d8e88763ffc3306f5940b36017f3ece5498637abd7e5e482011aa0000000000000000000000000ad35bbbd3e34939017a37dbfc0f3d402d41d014df845a02cd8f695e31bbea5a3db3b364e344e010f9a847e02db82698835caa09149df3be3e243a00000000000000000000000000000000000000000000000000000000000000001f847a02f6ffd495f8f24e366309e900e470251f878294070e83b18869657f55bbb1d8be5e482011da00000000000000000000000000000000000000000000000000000000000000001f846a02f9a1d308f6c10387b18684becf4935e0d9e2857f99c7dddda786781385289c0e4e381bea00000000000000000000000000000000000000000000000000000000000000001f845a030af0e0ae86e84df6970023463b34388b810d34891a9ee2e186e98ec6cce40b5e3e27ea00000000000000000000000000000000000000000000000000000000000000001f845a030cebc16b291bd6ef3a54ca9b0f28a10b3fbf829df58276b8389ecee3f7f2aaee3e203a0000000000000000000000000000000000000000000000000000000000000000af845a030d47318d78f71cb5858894134a221f1bdb98d5f406283eed18b8c8b8d43bd2be3e27ea00000000000000000000000000000000000000000000000000000000000000001f845a032ad1271aa2d1bb282101240690bc521311ad2a499e5c9d03eae089f085639e7e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f845a03416eb792adb4c0ed7811bae6e769a9e55d6427edcf64dc10e2c1c833f634664e3e240a00000000000000000000000000000000000000000000000000000000000000001f845a03603dad2df3696ebb289a869b3ebccad9f603d6464f7098d3f3afde43ddab8bde3e23da00000000000000000000000005f034589713d3a9f4c67a09b478b800db7764cd0f845a03947e3dc7b356dae8a706f4e10cd4aa2cce52bb278eaa71e6412dc719005b01ce3e242a00000000000000000000000000000000000000000000000000000000000000001f845a03a41204c118c8f63063ab63369abe305e8d692537864fd1949e4857ec7b57b03e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f845a03e554a85c54e69b073489460a209289d16f23192068666d576e93e2ff3205ab2e3e247a0000000000000000000000000b8667563548726af6732c799e066c27e3e1efe62f845a03fdc10bf04f71f30599979cce58a6b8b4e7a2b1cf06ddeccaa6ab46cadbee832e3e24ea00000000000000000000000000000000000000000000000000000000000000001f845a042ad5ab90b088f605921428816a1d7a6adc7615d131e976b1e5732cfb8f0bc73e3e23da00000000000000000000000000000000000000000000000000000000000000001f847a043213d897f76d3e4023c39b2dbf73860e759affeb9315e8a508c6fbbdade1a70e5e482011fa00000000000000000000000000000000000000000000000000000000000000001f845a04648affe48e39f928027131793b67940650406cd37857db3f30efbde78bdc11ee3e241a00000000000000000000000000000000000000000000000000000000000000001f845a0465684ac534db0845cf39cd92cbf554d116524506ea702cbc698970497cb6df9e3e218a00000000000000000000000000000000000000000000000000000000000000001f847a04c9974627f731e1817656d8c44efff741fcf6a21c0fac235323cebfb1fba5b72e5e4820116a00000000000000000000000007aece52635da73195fc6163b6d250018e6b99c7cf847a04f73ebcab0dc5c7e1fcfeb5afcf35c9d49047dc89295395e242e783417935719e5e4820116a00000000000000000000000000000000000000000000000000000000000000001f845a056ced8fc63f7854f083af8af3f01193f92e58af4b047ca5782a59184ce5cb49fe3e259a000000000000000000000000003176009748a757985c79f852498c279cf6d24e9f846a0583f6234249df1a1e3e3ca949219cb66260297da47d4478c3342eb0d18fc8539e4e381c6a00000000000000000000000000000000000000000000000000000000000000001f845a05859ece2efb5e2f9d91571b8831ce326f79b66e84c4320963c164f5cb5b8778ce3e240a00000000000000000000000000000000000000000000000000000000000000001f845a05994a8fc5e1794df3ab93408749e5e72b9132ea80f0b999c2e4ecb16d1a2569be3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f846a059c62db97e61053849aa184abf25ebf9238dad3467c719adc4a1480db185b515e4e381aea00000000000000000000000000000000000000000000000000000000000000001f847a05bf144ca85003711f5cae46eb74a909bb316b0168ddc1b9311e5edb272f1266fe5e4820133a00000000000000000000000000000000000000000000000000000000000000001f847a05ce41ff757241aa6556286bd10a54ad0f612bda29014988b943257ec5b764568e5e482010fa00000000000000000000000000000000000000000000000000000000000000001f845a05e0ce3f0008ab6062d6c1d76192ff3d9f91408ee056571bd9d5ee01804d0b607e3e23da00000000000000000000000000000000000000000000000000000000000000001f845a05e814d91083df367d84dbb005ff9709145f5fd16780c061529b9cfbe1866df57e3e27fa00000000000000000000000000000000000000000000000000000000000000001f846a05ed702fc67f7a19252d2440997532741e5eb50fce6079c0dd004d17b0b76e0eee4e381c0a00000000000000000000000000000000000000000000000000000000000000001f846a05ee27e6432e6b3fae1fa182a6d29e8c8f4983b46be5fad50be06a11b8eb32d48e4e381bea00000000000000000000000000000000000000000000000000000000000000001f845a05f15c8aa9acdc7b4b861ebd26720a060692eca6fdc806879ae4995124b543923e3e249a00000000000000000000000000000000000000000000000000000000000000001f845a060307571513c92bf8f33862322318db5722bb4167386ca1ad200593b4d1bb32fe3e243a00000000000000000000000000000000000000000000000000000000000000001f845a06201495553e5dd1a61b45a4deb1414bf6f680fdc050d196e7f5e4b0e032e09d6e3e249a000000000000000000000000084d76430449a30e20805a52086f711912824340df847a0623fc8c6e538b81f77c85b30f805b3bab7dfab18ad92c661dbcf35d4962284d4e5e4820139a00000000000000000000000000000000000000000000000000000000000000001f845a062fdb39ec57501d95fc463138335ec1ec31c4f24e47800e432189683dd916618e3e24ba00000000000000000000000000000000000000000000000000000000000000001f847a063540e8c7256e7b968929b3a85e45c4f583a8e69b6e548bcd56bfb3ffdc04986e5e482011fa0000000000000000000000000998c061820903de9af6d950eddb978af328c8326f847a06549fcb34c2dfddc63340bd5d3e1c0413fa924e937ef17f46208a29021f3705ae5e4820143a00000000000000000000000000000000000000000000000000000000000000001f845a0686e0314c7fb25edcb26e8ae086f3ae65d9fe7155e120d52b676c0c80281e947e3e24da0000000000000000000000000195d09fcba4ac3f93898e51f6edaa7fbf1f8c0aef845a06963759c548d949203e6878b0c064d67daf09c951b851e3b0df59aa4b50a024ee3e250a00000000000000000000000000000000000000000000000000000000000000001f846a0776a11a4f2c706dbdd123a60354e7800e5e0707f0122fccf126f6cc5690efd17e4e381e4a00000000000000000000000000000000000000000000000000000000000000001f845a078f054f015d4d9bd71e4242c3813cd545468d547a8d9f942c694e17eb74b9218e3e245a00000000000000000000000000000000000000000000000000000000000000001f845a07971e4274935c018c9d131bed748cb1e1d2c7bd6b46bd84155b25b97f9eb5434e3e218a000000000000000000000000007cb86bbe058cf36534f2f723e4b374b97723622f845a079f4aad8a401c1f0439d3b8234961522357b04930ed8a4db24dd356f321be1e1e3e24ba00000000000000000000000000000000000000000000000000000000000000001f846a07a10e3940ba73450375e9d4705dbf4773015540adc5273d161a8a46b5e38520de4e381c4a00000000000000000000000000000000000000000000000000000000000000001f847a07a9d957bace703f411144caef8561539618a43a2aaa2ec3dc20fb1c1318d311de5e4820143a0000000000000000000000000aaae7fd759ac75dbfb5a85b8fc9d93fd3646ea5df847a07b174d0b5f902c4b8cd10249732f3f64910f84a16d713df1d14187329b70162ce5e4820133a00000000000000000000000000000000000000000000000000000000000000001f846a07d707313bf3a7050725b8952e34b0df62bf70b787251a0569a54cbddaa717fdbe4e381c0a00000000000000000000000008e109b88c7765c3e7502781a5e3f034863df9e97f845a07de3d71bd86a6c7f4c3394022c28c979b2cef3a4abde9f61e6ce9b9b13f5d8a3e3e248a00000000000000000000000000000000000000000000000000000000000000001f846a0807a6c3729944cec8388134461a20518b784ef18a72e9e1fbaccdf23b86501a4e4e381c2a00000000000000000000000000000000000000000000000000000000000000001f847a080815ab5e3035a0d4d275219b8e354936a2fc409c91646bc315148b7fb236bdce5e4820143a00000000000000000000000000000000000000000000000000000000000000001f846a0821e2af61cb00de6efbeac2b3d3832ffa089871c1e39c3995d214584103cc9e0e4e381c8a0000000000000000000000000fe93e9ce4f730a1c64846bc782e45904ffaa633af845a0848b9cc617e2947515869b422eb8815a5ad891f80df602b678e1a190f017ba1ce3e242a00000000000000000000000000000000000000000000000000000000000000001f846a086d9cee311b771d6016304cc4232a76243a92929f9600bc10ca7b5a542f1ba72e4e381c4a00000000000000000000000000000000000000000000000000000000000000001f847a087b59b8db4f221eae51302f4190f492996ab1a9c97f5afef6ea315316242a798e5e4820134a00000000000000000000000000000000000000000000000000000000000000001f845a089872db687d669b45f62b97cdfd7839b0f3f56b436dfd69849c67d9b97a8e327e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f847a089c4c403ceb7b392dc63cd1d1ec467bef2c4dcda4b24ead911e1010826d5983ae5e482011ea0000000000000000000000000f6473fa77d6c2b7e4c32936e7bed7b213aa4325ef845a08a0fb0da05d5fec6f3d9ab2d4dca243c78fdc2af895c33aced9022bb050d4ecae3e24ca00000000000000000000000000000000000000000000000000000000000000001f845a08d69ee905693a5e0da42c1950b06403f281aed9aca3896bb135f1bb78ab665d4e3e24da00000000000000000000000000000000000000000000000000000000000000001f847a08e0fc90e14f7b35dad61b5e31d473ee0cf266e09b65f07fb7937e97b598175a8e5e4820116a00000000000000000000000000000000000000000000000000000000000000001f845a092e5d2c1dbcaea1dffcaa0e6d2b6686f822141133e389f29bb35d516c2d1352de3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f845a092f62bc4942b27309081e88c6b601e4848be4e4a0035c179aee26757bcb9755ae3e23ca0000000000000000000000000d85ed0e5b0130e1f9b9f7ae59a1c73387bde4651f845a09a83ab91050a11f0837f4a689ce5e8457b376afdc9bc3ecad73e55dd9d122457e3e243a00000000000000000000000003f8a2b6ff85da8d791190e287965d4fa745a8f92f845a09ac0a89ff900160c78f0e89ec63e48a057d17121f290a91d43eff009cda315f3e3e250a0000000000000000000000000706b46919914e1208a10473f411fc65095e582def845a09b92c75de072ef82c7304c90e3ebd534cca653ddc3d0cdf29c9e420a033ebb24e3e242a00000000000000000000000002e0093c8d59062caf3cc790d7b7adcb3cdd5dcf4f845a0a127a674a51243111d1f1a640a51875c3d76fd6df6288acb6b52f96b5e5ff07ae3e24ca0000000000000000000000000caab29ecae649ae94a24bf7eba52a924ec56935ef845a0a216442c485571b32f196b9eb0e91729e1ae5fd5b9e8b9aa4c9fd2ec543c546ae3e248a00000000000000000000000000000000000000000000000000000000000000001f847a0a3ad83b210ad01992affa9b8dc996bb85a7461ef6344798952b0129b06d268a4e5e482011ea00000000000000000000000000000000000000000000000000000000000000001f847a0a592a60cf6c642b47fed0d3ec2be81c95a96604d7b5274b5e6bc1afc6dac8088e5e482011fa00000000000000000000000000000000000000000000000000000000000000001f845a0a652d818e6a45381a5d95bae38f9e915696bab2fe889a902d7ca6d3861c45ff3e3e247a00000000000000000000000000000000000000000000000000000000000000001f847a0a6ad215d5047e987f16a4ee32610633ece5ff2da3202b2970c3807b5d842eed8e5e482011da000000000000000000000000067a45c5a86ebf8c66958b11b019c6ae44c797f41f846a0a81da1c7edd313c5c065edc65ecb1fa27ad3c8c342e38db248934ef0916db3cbe4e381bea00000000000000000000000008d95149160bae314d8a757c827ed62177c335794f845a0a943987ca627c64beb0180a5aaae1b33705f10e8a726da8096d90d9eae9d0992e3e24aa00000000000000000000000000000000000000000000000000000000000000001f847a0a967185e0d80009ab9a9051e247d544c25a9d3b754495e9f057f100fef9a9ea7e5e482010fa00000000000000000000000000000000000000000000000000000000000000001f845a0a99cac543e9f5e2f6c5fa6d461b9747e456a1f41d8712080f96517b4f2411ee4e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f845a0ab7f0d36420b293b754af5907b5846bd0eb903fbf881546d9c563c2ea6a02b7fe3e244a0000000000000000000000000cab50a2b987fe307553c9ef2f4d4ac574a1ba13ef846a0ac106f08d9f250c929f757323e65cbcc22b0910962fc4f0e44888aa237c5d824e4e381c6a0000000000000000000000000a9c4046eea8f0db337b3394ce83e70529f3eb8eff846a0b02785ccf2a3ba3ef4e5b59173076880eac3bcc577f6e759f39842ea5dd98db2e4e381d1a00000000000000000000000000000000000000000000000000000000000000002f846a0b16c5656476e3e3b0b7ca6376277d0d36c369fa145d558ed10f242913cb0144ae4e381d1a00000000000000000000000000000000000000000000000000000000000000001f847a0b2fc907d21eee1ffd655aeeb7ad16890d7e214907ad667539907ee6e2660e394e5e4820134a00000000000000000000000000000000000000000000000000000000000000001f845a0b5a052ea42b271c75f64df912e20708f230c3a620469c99ffc36651491b0bb67e3e245a0000000000000000000000000d099bc9db95489b6a036623fb2c744f40dac983cf846a0b8839200738c8f45ba65b18cebdaf4646aee9bdb79469be1bba05888574120c5e4e381c2a000000000000000000000000047ff49e729a36560c53125223cd4aa453c53e5c4f847a0baed2eed1c1388b1a88f2b0aa3740acf736e140659d8b9e1aa9268a199e53fcfe5e4820144a0000000000000000000000000e680366019c64871ff35a6996296782e5a25920bf845a0bd54eb358f2945f0608c9aa105f8c3d05c3493fc9deee7078f09489d81e72ebce3e248a0000000000000000000000000a193c160aa1a8971eca9490cafd807250791b7c6f845a0bf87d9bd9339e85ff8f4a162b159565521179504c78504cb5e128c531cf42846e3e203a00000000000000000000000000000000000000000000000000000000000000293f845a0c07319c1fa6bc4790a488b7c067c303dcd977c6646a7a11356d82a097cad2524e3e24aa000000000000000000000000002e376b6d590680d99dc422be8ce718730e15d50f846a0c1b3555dd4051cc61362dc403b0ead6469543e90f9f983446156165ca520a705e4e381aea000000000000000000000000077b2c0784794f28582232e6c73932ae6d1cea4baf845a0c1b3df7a47cfcbdd6dcf2743982dabc92dc5c49fd4c9a81972445921887a07b3e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f847a0c339a88e386e318f3b192e3275308b63c64d7594fe66530245f1d5f87e11a1abe5e482011aa00000000000000000000000000000000000000000000000000000000000000001f845a0c466bcac5bc09509cfcdf22d753b89a57425de93e8ca7283bf25fb431ea5d407e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f847a0c4f88893fc67a77222a3104d67c76843c921e1b0f1fc2c11d762f7dcda7f47f9e5e4820139a00000000000000000000000000000000000000000000000000000000000000001f845a0c81b690cc8dfbd6787d50ba9e14da4938d92a164f417883a505f5fcd9b9ff44fe3e27ea000000000000000000000000054975a68c0609280f67050f3b167d554e89c325cf845a0cb51683c780daf9b68e3599d0ed651c9c9877c2de0c61b8576a6dac86e1de937e3e24fa00000000000000000000000000000000000000000000000000000000000000001f845a0cb98f598a50ee3c4fd5568ea53a7cd5c66c25eee4af794b0bd59c9e0b0cb865fe3e218a00000000000000000000000000000000000000000000000000000000000000001f845a0cc566b326325c6a2ea2fbb0d50374ba1efbc95f8b77551d701407c44f4dd80d3e3e24ea00000000000000000000000000000000000000000000000000000000000000001f847a0d3c2ff8ece0ce5a0b4ec00cc9d277389e3a3486e8163210bb12b32c43a95d6e2e5e482011da00000000000000000000000000000000000000000000000000000000000000001f845a0d3d9c09a7c86eb54c847253ffdcc735ff48132eb9d97b6e97af38e425995dbbee3e24ea0000000000000000000000000e68df48c92b21367043797c24f62c392c5d922eef846a0d40d5560afd88aba969e7916abb940241912f34a735061c0d7494ede6be97f36e4e381f8a00000000000000000000000004100f0730b8771856e4200e12434c4a19f1057ddf846a0d4936ed3cebbb3694b184ac208cad12289038e99d69e356156c87fdaac6e7978e4e381d1a000000000000000000000000088d0e9fd5058114be1df181271fc979beb66dcecf845a0d51721ea8ad9101fbc35f3b74dfe91988605f5008323fde5a0545c7671efae39e3e24ca00000000000000000000000000000000000000000000000000000000000000001f845a0d73267d100a4dbfb504103f49869d310fc07b9c95e958afbb93707cd55421b5fe3e259a00000000000000000000000000000000000000000000000000000000000000001f845a0d87721e0b18aac57e5e679f2e8179177adece5a67e5ee09f2b70d5198f54e3bee3e241a00000000000000000000000000000000000000000000000000000000000000001f845a0d8ad4158b0c8fe304213511da307e5f9f0193f64d41ef9616c862a80c62c9ee5e3e241a0000000000000000000000000a166d72fa029fe68b2e9d8364f9ca88a7814dbb3f846a0d9762b3ebb67e80f5885b83c7e5bb849c785e81fe30fc10cd23494aaa8cd03c0e4e381c0a00000000000000000000000000000000000000000000000000000000000000001f845a0d9e0efe215ec3b3f0aad0dd4b80d6d8678d0ae21f2abdbd0c95d5ea602297ba3e3e203a0000000000000000000000000e4166f06730fe19e6487a56ba79b2371b4c52515f845a0da45eef48194f42560b44b2ed3ed260424c479b4d59d2ee3c985bd92251088d0e3e246a0000000000000000000000000f093f201d85872748a3a1f8f09808e713cd9c0f9f847a0db89b42158ed8be4e19e9b513a17603adc01d82285eb6b8a179745163c2e832be5e4820144a00000000000000000000000000000000000000000000000000000000000000001f846a0dc791cefeaad24bed83f45fe76340c75b62f04da19999ec2187bee4620c6b596e4e381e4a00000000000000000000000005ebab6791bb90ac6acc1afa394e440366b933310f845a0dce3a67201b0e7b3fbe984f3f4ad58dc2e775b5746393e423899faacb8f2abefe3e244a00000000000000000000000000000000000000000000000000000000000000001f847a0dd0423bcf9c9674ac81c61493f169b3c401f5e85ec9b65e9ee7552b91866fd91e5e4820144a00000000000000000000000000000000000000000000000000000000000000001f845a0e010dade8cfbe9b415531440b7e6661a7b62c3a89fa5e640e0d1cb0aab9fd8b6e3e245a00000000000000000000000000000000000000000000000000000000000000001f847a0eca5db0165b2f81abff22c0b25b78707b5cf982eae670799ffc41805082e5191e5e482010fa00000000000000000000000004b95963192df7c7e5fe2c017997a7c281a3be2cff847a0f0e201cd2eb08e6a485841676fa1a96223a27de19e0f201dfae92522cce3b79be5e4820139a0000000000000000000000000ae134de48af0f087472b89b042952e09407e40a1f845a0f158d968c784c3142fe9198550c34e11bfa0e278502a62dce509bdd6bb689776e3e249a00000000000000000000000000000000000000000000000000000000000000001f846a0f6bb257887ba95d45ffaec94c1fffc735130b4e252fd9ee3de784529c5a04656e4e381f8a00000000000000000000000000000000000000000000000000000000000000001f846a0f8ee8e02a727f24e2944383e957ac2e7e7cd1dda8d4ec16d54199001923dbe79e4e381c6a00000000000000000000000000000000000000000000000000000000000000001f846a0fbadf8dd63a027e0be7c0dd0a658dace314b3d62785898f970f1de0a14000735e4e381e4a00000000000000000000000000000000000000000000000000000000000000001f845a0ff5a974c8ef6aee2df55c5c4cbed3366de67d1233a3bb9cc3012d828263bfb4ae3e247a00000000000000000000000000000000000000000000000000000000000000001f845a0ffa78031d05b20df28b5ee39c910bd65e77284345dd71443259f9259e3d31755e3e250a00000000000000000000000000000000000000000000000000000000000000001f9016ba00000000000000000000000000000000000000000000000000000000000000007a00262dc66aa8f41171fd409327f2d6b94350b81d726e9ef3a2527feef9e0f7aeca02a4d8d92661a9803e64ff15fba03fd8c139581844906f7dbbac7e01a798e92f0a04b73de2ec11cd2486ec445ba87fa2c8c267bb14782c0c9096fb840a54e37cbe2a058578b504a53f35a041324e4c0d63a996ac8c16109ce6523a8d774c45067cc24a076ca87635bd64b3606d36ab664f6ad0c59124ffdc0f3f79c92d7de94638dcc94a08d942c9de457954de17f931e3ffcc6eff41b684c26a099ff06e23d121228456aa0928408311edd485229c0b057a43d2b87950b1e0fc73fbe481e8e939ee2b2f9afa0c13b37751d34b04c93efdb606b63bdf3bfa7b486cf111771c55b16bbf00ade87a0e7ef37b076cd1bc8f1dda41378362ac154b13d47592bf2241d7955a9356a0bada0fe7a1e6b7aa2e418614e65c8a093c329f5ee9d3169986801a72febb24894dc42c0c0c0f29426fbe4173188c73439d2eccab830fbb3f7547b74c0c0d4d381d09000000000000000000000000000000000c4c381d004c0f09427b4342d9f9504c7667c34401931b9873304f632c0c0d3d2689000000000000000000031b6472cc3b97fc3c26802c0f9015494284e590de09e82b6706e4dff386fc678a01250e7f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20ea0688a1197000000001033ee67bc14b0f9015900000000000166a4cfd0047359b5f845a00000000000000000000000000000000000000000000000000000000000000009e3e20ea000000000000000000000000000c0f9661a1c26b76efc99ea9c031d26592abb1bf845a0000000000000000000000000000000000000000000000000000000000000000ae3e20ea000000000000000000000000000000000c858eec0b006bf2339f46f84dea2ea7df863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f8bd9428c6c06298d514db089934071355e5743bf21d60c0c0f878d381b590000000000000153469f2ae3dd9c461dbd381b690000000000000153469f2126cd23c95f8d381b790000000000000153469f17b691b943649d381b890000000000000153469e82ee1dda240f1d381b990000000000000153469e7da95f1c35215d381ba90000000000000153469e76b4304e3c29deac681b583c66ff2c681b683c66ff3c681b783c66ff4c681b883c66ff5c681b983c66ff6c681ba83c66ff7c0f87e942905d7e4d048d29954f81b02171dd313f457a4a4c0f863a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000006c0c0c0ee94290e92c9177c7151cac5c9b98a04e2dceacf4819c0c0d4d381aa9000000000000000000163b29c0ae98000c0c0da942ad5004c60e16e54d5007c80ce329adde5b51ef5c0c0c0c0c0f2942b34ea1459b796203a2b785a86772da5cf09c0bfc0c0d3d26290000000000000000003b912efa477b128c5c46282070cc0f90356942c158bc456e027b2affccadf1bdbd9f5fc4c5c8cf901cdf8afa00000000000000000000000000000000000000000000000000000000000000005f88ce25aa00000000000000000000000000000000000000000000000000000000000000fc6e25ba00000000000000000000000000000000000000000000000000000000000000fc7e25ca00000000000000000000000000000000000000000000000000000000000000fc8e25da00000000000000000000000000000000000000000000000000000000000000fc9f845a00c8a0a486ab5533ad3fb97b3edc5d3d323813834c7a71049a0465e4b4d5ed62ae3e25aa00000000000000000000000000000000000000000000000000000000000000001f845a061161c9978bb25a1db74988204dfbb00785f5a55cfd5ded98b1c44eb166d77cee3e25ca00000000000000000000000000000000000000000000000000000000000000001f845a08db09278c49865221f6f974add0c494689f5b5627205e38452d3b93a250a7323e3e25ba00000000000000000000000000000000000000000000000000000000000000001f845a0f85c551a484165b827e47bc8ee7990004c36ce3c8c1eab4e839e17f9597bdd4ae3e25da00000000000000000000000000000000000000000000000000000000000000001f9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea0000000000000000000000000000000000000000000000000000000000000000fa00e3573256455e043764e1015e46264b82dbc5a70bbda3551e3d797fad2854f8ba03675391838767b4f684656f1b7730f0b957e91f2e46c383064036757c752d3a1a064ed8176068ca759d2bede4084e0e4d76c7bce2e149d6706bb0c102c6cd3baeec0c0c0f87e942c4c28ddbdac9c5e7055b4c863b72ea0149d8afec0f863a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca088d075c869ce192f20da9bfc0d2db81b73b4aa4af2ce17e52384cb021d06bd06a0b78fd7e1706dee4e251f6f3c52649313d4e032077a25f6c26ef8fbf58f357651c0c0c0ee942cb0f4968e89d66d1aa806155d601b34dcc1e3f4c0c0d4d381cf900000000000000000000f7c3b95468000c0c0f4942d23ef144a3665c4d6b668cb3efd3c9ce3db129bc0c0d4d381ee90000000000000000000002f9c551c5c56c6c581ee8204d1c0f0942e0093c8d59062caf3cc790d7b7adcb3cdd5dcf4c0c0d3d24290000000000000000000074516d2822081c3c24205c0f9025d942e3fb0298a8dde91860ee8d7b1b0a11d7a1101c0f8d5f845a018e1511117a28e3cc8a208df7264328791cc690bdf8a207492fa6b0ccac7e423e3e20fa000000000000000000000000000000000000000000000000005504d85965decfdf845a02cf1005e2b00dd50583ecb1ad409063b364c1fbdb904b4132058210a989ac7dae3e20fa00000000000000000000000000000000000000000000000000000000000000000f845a035b37a6bab11b7263dcb2b876306c908042aefa17c85164ca45c37c48aca2ca7e3e20fa0ffffffffffffffffffffffffffffffffffffffffffffffffffd12eac44aa5f76f9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000011a00000000000000000000000000000000000000000000000000000000000000014a03dbf2ca99ffda4398b43eef828f27c7f9231dc34c5a1d61a224b1e8b47ea05fea06a4f0d0d80ea1dcb80998dc73cf75d4f9f7165199cf70b8d8c7e34ff34c33708a0c626547b4f99cf47c579146a6a1c4ddbd9430da66725a8d1bff899ef495683e6c0c0c0f2942eb26622a353b29553e16273d4ec27c5f2f2a7a4c0c0d4d3819d900000000000000000000131c2ae2a225ac4c3819d02c0f9021b942ec24fc75cf4dcc8475a48b3f6a880dbc6901ecff8d5f845a036ebda387537566728900b76db13103a5417b9b66c546474c5cd1783fdaad81ce3e20ca0ffffffffffffffffffffffffffffffffffffffffffffffffffe006328b0057b8f845a082dad63db9b278d69b9b7ef8bd34ca0ff7fd0b069419bfc8e46ffdd818b27626e3e20ca00000000000000000000000000000000000000000000000000000000000000000f845a0ec4e86c2e8ab3a5a06850e4ace3bd24b781fc1a4c942390cb4c357f1bf17056ae3e20ca0000000000000000000000000000000000000000000000000005a3cd5ba74498df90129a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000012a09926c02dd9b42803c52558baef716ee12c3060b8ab7defa19dccea4896e93582c0c0c0da942f18f339620a63e43f0839eeb18d7de1e1be4dfbc0c0c0c0c0f85d942f39d218133afab8f2b819b1066c7e434ad94e9ec0f842a00d2c1bcee56447b4f46248272f34207a580a5c40f666a31f4e2fbb470ea53ab8a0740f710666bd7a12af42df98311e541e47f7fd33d382d11602457a6d540cbd63c0c0c0f85d942fe934eb46720a56e64e28ab30f1caa926288bb8c0f842a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f294308f544d88aecd34016781b9f3cba30a2e524eefc0c0d4d3818d90000000000000000029a1fefdb9eb993fc4c3818d0ec0f29430eb78f620b23f81865b346e3d4432c8e39a78a9c0c0d4d3819390000000000000000001e5f6ff02b1618bc4c381934dc0f2943131c77f79513efab8d703176f831dc3870d5557c0c0d3d23090000000000000000008c1661a09d46993c5c430820c91c0ef9431628c318662be176a1559ec8b7c4ad7744418cbc0c0d5d4820129900000000000000000004380663abb8000c0c0f901339431a43eb48625239d0bd2d2213a6202b3eecbecf2f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e26fa0000100000100010000ff7d670000000000000000301db28dfa2f448217eb123ef845a00000000000000000000000000000000000000000000000000000000000000001e3e26fa00000000000000000000000000000000012c9b11e774f7dcfaa3f939beeb22e51f845a00000000000000000000000000000000000000000000000000000000000000008e3e26fa0010000000000019d51ee34bfd3a2e9511e7db7627cffffbe41899a28688a1197f842a00000000000000000000000000000000000000000000000000000000000000004a063187d71e139eee983a88d0737447c7451979b3dbb75903c76b5fe430d36588ec0c0c0f09431da337b2a1409e8fde14e1f49d487a8b72f3018c0c0d3d21b9000000000000000001c07f53f61c71bdfc3c21b3dc0da943225737a9bbb6473cb4a45b7244aca2befdb276ac0c0c0c0c0f8ef943328f7f4a1d1c57c35df56bbf0c9dcafca309c49f86bf869a00000000000000000000000000000000000000000000000000000000000000001f846e20ba00000000000000000000000000000000000000000000000001ad362f0665fc7c9e20fa00000000000000000000000000000000000000000000000001ad567b3f6feb8cbf842a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103e6d20b9000000000000000001ad362f0665fc7cbd20f9000000000000000001ad567b3f6feb8cbc0c0f83b94337685fdab40d39bd02028545a4ffa7d287cc3e2c0e1a0d138504697de92b7ed8a8aae629d27d26630b4fde8dd22e85a384a6e6c5b456ec0c0c0f84e9433b99ccbc84e8a87e10ec86386a7eb5ad434fb7ec0e1a00000000000000000000000000000000000000000000000000000000000000000d3d21d900000000000000005f7ba1621c5ef8797c0c0ee94354d3b3a8393c558ddfa03fa8c2a229d9714817fc0c0d4d381ac9000000000000000000002d79883d20000c0c0f394354e9fa5c6ee7e6092158a8c1b203ccac932d66dc0c0d3d2609000000000000000028c285f05753ac0e7c6c5608303d002c0f9010d9435d1b3f3d7966a1dfe207aa4514c12a259a0492bf88ef845a02f0515d3e6804b0792136a69384e701be39b327661d6725ed995364120a01e72e3e208a0000000000000000000309408842186d963c3c8660f1801968dd5e88898000000f845a07adce341587c7ad28f80a9ef54da0d2f0bfd917b11856be2d246a8c66685b1a0e3e208a000000000000000000027072c8bc198476fed123181286a0310d67b8b00000000f863a03ea716f0f3cd66e12a20758b3a58856947ad2da16282984f676db0ef6a0e8a05a0642c1ef9ff934c392343f46c239eb029ededdd3ab04b8cb7aa8cc5db70738602a0c14b81a281327ab8314e03b228c5bfd656d25a645058ed894ce584e2dd01ac56c0c0c0ee9436475477b99a3b9bd11846aa3e3830fde60278e9c0c0d4d381ea9000000000000000007c9eb34f61e9a616c0c0f49436ab40ec7001e10f440ddc143be2e0cefe22ceaac0c0d5d4820113900000000000000000000028d94b22a5d1c5c482011319c0f09437f20c3c40ab16332851958c1d2e311ef2b4e4b2c0c0d3d210900000000000000000000c90b14692ba46c3c21021c0f09438144eac0ed090a9f8f36c110e7d20544cdc6191c0c0d3d2799000000000000000000000000000000000c3c27956c0ee94382ffce2287252f930e1c8dc9328dac5bf282ba1c0c0d4d381fc900000000000000007b0b07bfb47e6c411c0c0f49438d46ed543b8e646cea1e640097a1026a6b21f5fc0c0d5d482010e900000000000000000000932ab15a3a986c5c482010e0bc0f294391e7c679d29bd940d63be94ad22a25d25b5a604c0c0d3d27d90000000000000000000ca8098f1373ca2c5c47d821fc9c0f494394311a6aaa0d8e3411d8b62de4578d41322d1bdc0c0d4d381db90000000000000000005a2674b6dab5f6cc6c581db8224c5c0ed943943f80e410ce8408e4fc59c2e3f8fbcd963a469c0c0d3d25490000000000000000000551b34d6dfb828c0c0f9015a94397ff1542f962076d0bfe58ea045ffa2d347aca0f8dbf847a00000000000000000000000000000000000000000000000000000000000000008e5e4820142a0688a119700000000001659a19cff1f0d8c60000000000000000001699c721b2cf847a00000000000000000000000000000000000000000000000000000000000000009e5e4820142a000000000000000000000013f3b21c5ba2f94bdcf133095164bf606524e24a524f847a0000000000000000000000000000000000000000000000000000000000000000ae5e4820142a00000000000000000000000000000000000005a8ac9b4dcbba04f49707f373f2cf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f902799439953e6429210c39d11688fa40219c811287caadf88ef845a00000000000000000000000000000000000000000000000000000000000000002e3e20ea00000000000000000000000000000000000000000000000089f8c3eaa3770967af845a00f12b799b59d030df91357dbc659788be3afe85ed8ab31fb27b990a45e64d037e3e20ea000000000000000000000000000000000000000000000000166a4cfd0047359b5f901cea00000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da00175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9a00175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbaa00175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01dbba05badfc95b22ecec658d9412b095439c9f361ac60e252f54add70f3bc8a4b4177a05badfc95b22ecec658d9412b095439c9f361ac60e252f54add70f3bc8a4b4178a0c87f3dae70d3ac23e82424b4132d779583ae145fc334a52160c2d1a246c2bc2ba0c87f3dae70d3ac23e82424b4132d779583ae145fc334a52160c2d1a246c2bc2ca0d83ad78b506c893b2f5085be44098e29b515bc23bda8d0bd00bb6197ae79c921a0fcac38389b0ae9c7b76ee4a3db54fd3e6e6e102c6c49fbef5ee1e04c33e64576a0fcac38389b0ae9c7b76ee4a3db54fd3e6e6e102c6c49fbef5ee1e04c33e64577c0c0c0f901329439d5313c3750140e5042887413ba8aa6145a9bd2f892f847a06a8d1776a0e0bdd5bf66be72fdccda03186aedc4dd2f4efc89504913f5a04f83e5e4820141a000000000000000000000000000000000000000000000023710b84eb2f2b9acfcf847a08780aab2c059dd07998a4b329d71b1304a73cc15a6613407133364f4a1a92adae5e4820141a0000000000000000000000000000000000000000000000000ad6b6b832cf3544df884a03169387b6397812ad2dc3dbab8209e79125d6d660efb8f584892a9a17e69b2faa0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca09016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a0cd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300c0c0c0f69439f8670420c75b2631021e823ad6378332396fe3c0c0d5d482011d90000000000000000017d793d3d1b5d8e3c7c682011d820352c0f8e6943a10dc1a145da500d5fba38b9ec49c8ff11a981fc0f8a5a00000000000000000000000000000000000000000000000000000000000000003a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca05ca141d3f9e350a91781a19938bb2685e08c79fd2d64ff66623659dc746ae186a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0bb07cda484c6b77b83179e90ab7f7417d246ee46d6cffb991494116df8bd6769e6d20a900000000000000000ac8df8e54835a11cd20c900000000000000000ac98fc60fdefbab7c0c0f845943aab9b3145df48a22ae337d50d1d66a3b2a3bda0c0c0e6d2039000000000000000000835423ef624b593d26e900000000000000000082f6c3bcb88ed63c5c46e821f9bc0da943ac05161b76a35c1c28dc99aa01bed7b24cea3bfc0c0c0c0c0ee943ad13a5b6557446d9788ac8943775dc9380481fec0c0d4d381969000000000000000000000e7ebd06af000c0c0da943c0f895007ca717aa01c8693e59df1e8c3777febc0c0c0c0c0f87e943c11f6265ddec22f4d049dde480615735f451646c0f863a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a01a1e6821cde7d0159c0d293177871e09677b4e42307c7db3ba94f8648a5a050fc0c0c0f0943d2f4de780472f5ada58707b50da12a1cac14596c0c0d3d26a900000000000000000003d729a2a2f2ad0c3c26a13c0f4943d30a58d75e84bdeb8c54b4839cd6eb048ce5852c0c0d4d381989000000000000000007246909db88fb2cfc6c581988203c0c0ee943d5d35899ebf2967e9c7c58afe4371681863f68dc0c0d4d381e790000000000000000000061aca1973cb58c0c0f85d943e7d1eab13ad0104d2750b8863b489d65364e32dc0f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000005c0c0c0ed943eb45601c4b3f7fef6a4e8c99410c967f814af7bc0c0d3d20d90000000000000000003aa39cb0ef4ccd2c0c0f2943ed1d0cddbd28be822c8d44a58047d87cb2a5b08c0c0d3d26f90000000000000000007741365b304dd9cc5c46f820692c0f83b943f73f03aa83b2a48ed27e964ed0fdb590332095bc0e1a00000000000000000000000000000000000000000000000000000000000000002c0c0c0f0943f8a2b6ff85da8d791190e287965d4fa745a8f92c0c0d3d2439000000000000000000078235a95125c6cc3c24301c0ee943fe5a843da0c582ad36dc066961d1241ca496e98c0c0d4d381f59000000000000000005edffbd71d440c22c0c0f29440196a1dc6e27bf650c090f8ca243332758f028bc0c0d3d25b90000000000000000001bc564cc0a52481c5c45b8203c3c0ed9440995fd172c745559432b1ed5e753dcaaee9a94fc0c0d3d256900000000000000000960830eddc42e58ac0c0f83b9440aa958dd87fc8305b97f2ba922cddca374bcd7fc0e1a00000000000000000000000000000000000000000000000000000000000000065c0c0c0f8c09440c57923924b5c5c5455c48d93317139addac8fbc0f8a5a019706acfa9d2e31e428368727f9392c708911c02f840270637707ddbb43164f4a024228eb7b5b16f25220decd05b5f6b9e16f15e54f3b304109c433b9dece0be4ca02eca26cd308bad6efd2b97274931094db504b833b663fd64ce183284b1f3d0e8a0a1c9e0290ca6c4de8f39b7226c2675ebece747467385dfcae54a36af04e73b42a0fa98da98dd68729325da45b3f03194f6462ca6f95d2ea2f716d76f0a7bdf07abc0c0c0f902d99440d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2ff90238f845a00fb186ed409fb55c160c88b3a2bbb4dfc38770e045ecd36537badf3dd35609bae3e208a000000000000000000000000000000000000000000003122ece76599b4cd8a2aff845a03388518c3d41ceee0101c43655cee68cb09523570c301f91829bf14fb3d19568e3e208a00000000000000000000000000000000000000000000000000000000000000001f845a0716b2abbdf59a791bc7405e16e39229cd2c5d3cb861b829e0ea05cf161cbb9c1e3e208a00000000000000000000000000000000000000000000b2724849870a7759117aef845a08c241777840efd6e6869fe5d7e7705ca1dbe8cfd17ea110a57f6e6ebe85c4a9ee3e208a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff845a08e485ef046749f65faa7aa167bdfe2d6d40cf685615648f709078e0cb9978a8ae3e208a0000000000000000000000000000000000000000000000545ca060a6df79f7e6af845a09d98752c354deebddd53535455198eacf8cfb934237d3523207f70386be5e3dce3e208a0000000000000000000000000000000000000000000000008ab4a102244f6c9f7f845a0bd526339cd386d579e5f59ddf30d855fc94193920d5dc39efb74be6613369732e3e208a00000000000000000000000000000000000000000ffc8947aac8134f29deff424f845a0f97069568437baf44d453bc59f46a2a35b3d69e489355c43c42aa8758f493461e3e208a00000000000000000000000000000000000000000000000000000000000000000f884a05414e47ea6d7a1b524a5e945a70f59c722a19981dfd15bb0b46b4d0fe64a6d3da05665cabdc5830e97eccee5b2c7ca740e4f87cded540788a9587cfb71db89a9ffa09d29ddfc8945dd05453aec23c6d0297290596dacbf751bcb0cc9ba8f8b8c0658a0d746e82bcbc0d36cbe5d00c038700ff4c469dde63202d9fc0d7a116218422b91c0c0c0f2944100f0730b8771856e4200e12434c4a19f1057ddc0c0d4d381f89000000000000000000000d7a5b6b5fe13c4c381f801c0ee944185e3ba2861c31189e9dde816d55acf96a48c44c0c0d4d381e290000000000000000000a2c7dbf09fcd18c0c0f902609442069e779838929495ed0152ffc27145ce5c7f98f8d8f846a07d97b657a253d2ad17db2ccb2bd132924a108741808865a71ac7f53303fad65be4e38184a0000000000000000000000000000000000000000000000dc921fa94b282387beaf846a0ac888ba83bc9dea591f6bdd5d5ca99b3b39e2f7003164f691afd3003f5d63749e4e38184a00000000000000000000000000000000000000000000000000000000000000000f846a0de36076a70d7e04b58a1504ce475124a0ac66ec1cf217b1c7ade70ebd959e1eae4e38184a0fffffffffffffffffffffffffffffffffffffffffffffff947fecbafc5e11bfbf9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000013a00d80ddd128d9961c8eb52e7a2730e4bf9f454e0830fd51e14407f7de9858eb1ba01f1ec3178783207d382bde9b7bedcd0061b68f650f540d6980d95593a44066f6a072e52795d031fea1a082e68f783b729768b75aca0b02da91a411401ba66f75fcc0c0c0f2944255d5c59c9a23ca51fa31f9ad1916d1f1da6301c0c0d4d381819000000000000000000004e09337932a4fc4c3818102c0f83b9442bc86f2f08419280a99d8fbea4672e7c30a86ecc0e1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0f87e944305fb66699c3b2702d4d05cf36551390a4c69c6c0f863a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca06def1416485b70233832df230fd802914ad65c17b325a7ff495c9ffb27cda201a07749865cc250abcd902df3f9b06d804adfdaa67cc5a070ee1f6409f32a8ff15ec0c0c0f594435ac6bc32b01a5b7e749f90a6a580f70f3af04ec0c0d5d48201299000000000000000000a6b7c80e23f9818c6c582012981e2c0f87e944515b64147b8eff871929c90795ae9ecb542f162c0f863a02e87e72e5b8d4993f1c5d68b37d42ab10d71fec7cbdbe6527e44ebe90207e3f7a06378b2e17ea75746272f244cbc4e08201823b700669acd0856252352668b0742a092c714bf53756b5954d914075b684440a5b6b3a6b0cbd9be79b08c77434a2551c0c0c0f87e9445312ea0eff7e09c83cbe249fa1d7598c4c8cd4ec0f863a00000000000000000000000000000000000000000000000000000000000000000a0ea4369cdd784effddb5849de3d072f82a40d15d3d81f0de55b8b181d509bc8a8a0ff68c7e56b106d8ffb900910533f72176d9e07f6ada08105f291dff3669a62b4c0c0c0f90154944532090213cc1b87b7a5293cae58680b867dc69df8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20fa0688a11970000000000001d53f9eb7714a54100000000000005504d85965decfdf845a00000000000000000000000000000000000000000000000000000000000000009e3e20fa000000000000000000000000000000836c904a1fff29bdf48a46814aee83238ccf845a0000000000000000000000000000000000000000000000000000000000000000ae3e20fa00000000000000000000000000000003d34f2c4e62f6a10405d8e2ffed14929ecf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f85d944579a27af00a62c0eb156349f31b345c08386419c0f842a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca09d9574a64928c994d335836e3e6a2a2847be75de0c5847bba8d7a923b835a249c0c0c0ee9445831b407b5680d3439a967d8adf1dc43fc14c3fc0c0d4d3819c90000000000000000002fa1dc1ffbd4dc8c0c0f8ec944585fe77225b41b697c938b018e2ac67ac5a20c0f88ef845a00000000000000000000000000000000000000000000000000000000000000000e3e20ea0000100012c012c00bd0409e100000000000885b3d462b7ee2982da20761ce48bf845a00000000000000000000000000000000000000000000000000000000000000002e3e20ea000000000000000000000000000007efcf9b1c7019513427cad51e110dd9332c1f842a00000000000000000000000000000000000000000000000000000000000000004a062552337360b040ac00905130c40c0e814a32ae1bf4d7c7b02730b479e5eef98c0c0c0ee9445f61d9a081ab8ca2005d6434a54ed4842a80849c0c0d4d381e5900000000000000000005615d84dbaa810c0c0f9027e944628a0a564debfc8798eb55db5c91f2200486c24f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e201a0000100003300330009fef1b50000000000000000080cbaa9798ad72034f3127ef845a00000000000000000000000000000000000000000000000000000000000000002e3e201a0000000000000000000000000000000000944cb33db277d22833f94599c78aa1ef845a00000000000000000000000000000000000000000000000000000000000000011e3e201a0010000000000000000004123d28c5a38c2e3283369fff78f100e69a8688a1197f9018ca00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000010a04a6f485c1c32cc07b41a962ff745e1d2a15f32520f92181d93f06ac057458486a0b35704a07132c2ea4ca6cef1e774d02e5bf51e9136708281402ce2deff80628ba0b35704a07132c2ea4ca6cef1e774d02e5bf51e9136708281402ce2deff80628ca0b35704a07132c2ea4ca6cef1e774d02e5bf51e9136708281402ce2deff80628da0b35704a07132c2ea4ca6cef1e774d02e5bf51e9136708281402ce2deff80628ea0b3fb4ee8142aab034b0eec5d60e08ed1e69d38201f0242c94e8e18c9f0ba0980a0b3fb4ee8142aab034b0eec5d60e08ed1e69d38201f0242c94e8e18c9f0ba0981a0b3fb4ee8142aab034b0eec5d60e08ed1e69d38201f0242c94e8e18c9f0ba0982a0b3fb4ee8142aab034b0eec5d60e08ed1e69d38201f0242c94e8e18c9f0ba0983c0c0c0f49447ff49e729a36560c53125223cd4aa453c53e5c4c0c0d4d381c290000000000000000000698c38b6f2fa45c6c581c28201f1c0f919c5944838b106fce9647bdf1e7877bf73ce8b0bad5f97c0c0f9192cd2019000000000000000010f482c774f0bb8f6d2029000000000000000010f56d9731df15bb0d2039000000000000000010f5ecf2ef7d15bb0d2049000000000000000010f61b4dc2abf9fb0d2059000000000000000010f61d81c90fa4ab0d2069000000000000000010f6307287f5049f4d2079000000000000000010f64882eb323d7f4d2089000000000000000010f65107f57664744d2099000000000000000010f6a6102cea1e3e9d20a9000000000000000010f6dd1063ed8ede9d20b9000000000000000010f70fe71d2f8cfe9d20c9000000000000000010f73c95cae0b47e9d20d9000000000000000010f757bfcdb454fe9d20e9000000000000000010f7722902f103333d20f9000000000000000010f784ca8c16f5333d2109000000000000000010f796f5eba90b333d2119000000000000000010f7a59d06a207733d2129000000000000000010f7a71eea9bd03a3d2139000000000000000010f7a85081b4753a3d2149000000000000000010f7b3a413e07b707d2159000000000000000010f7b4d5aaf934f27d2169000000000000000010f7c25bf010af6e5d2179000000000000000010f7ce3658c62a4e5d2189000000000000000010f7d99592e68e8e5d2199000000000000000010f7e46f0e27e280fd21a9000000000000000010f822116cad7193fd21b9000000000000000010f8223497350d6bfd21c9000000000000000010f82a2102df7f73fd21d9000000000000000010f831af0451264e3d21e9000000000000000010f838dea410e18e3d21f9000000000000000010f83e1c96e2fdce3d2209000000000000000010f845309808dece3d2219000000000000000010f84c1dd76ee08ddd2229000000000000000010f852bd4cbb445fcd2239000000000000000010f8594f96a466592d2249000000000000000010f85fe1c679cff21d2259000000000000000010f8666a14d640f21d2269000000000000000010f86cb869a437f21d2279000000000000000010f8729113bfa9321d2289000000000000000010f878690b0abc721d2299000000000000000010f87e41b5262db21d22a9000000000000000010f8838e530ffff21d22b9000000000000000010f888da9791a3321d22c9000000000000000010f88e27357b75721d22d9000000000000000010f893732094e9b21d22e9000000000000000010f898bfbe7ebbf21d22f9000000000000000010f89e982cff63321d2309000000000000000010f8c5d961121d321d2319000000000000000010f8cb17ad4c68721d2329000000000000000010f8d01f2b567dd21d2339000000000000000010f8d5172e69d6521d2349000000000000000010f8d9e712dd17b21d2359000000000000000010f8deab24a6e8381d2369000000000000000010f8e7710e818d781d2379000000000000000010f8eb4e87585ef81d2389000000000000000010f8eeaef10a1d381d2399000000000000000010f8f1ed81b565b81d23a9000000000000000010f8f4c7be3cb8629d23b9000000000000000010f8f7a1fac40b0d1d23c9000000000000000010f8fa79c94c241d1d23d9000000000000000010f8fd5197d43d2d1d23e9000000000000000010f90060c8716aed1d23f9000000000000000010f9036ff90e98ad1d2409000000000000000010f90647c796b1bd1d2419000000000000000010f9091f961ecacd1d2429000000000000000010f90bf764a6e3dd1d2439000000000000000010f90ecf332efced1d2449000000000000000010f911a701b715fd1d2459000000000000000010f9147ed03f2f0d1d2469000000000000000010f917569ec7481d1d2479000000000000000010f91a2e6d4f612d1d2489000000000000000010f91d063bd77a3d1d2499000000000000000010f91fde0a5f934d1d24a9000000000000000010f922b5d8e7ac5d1d24b9000000000000000010f9258da76fc56d1d24c9000000000000000010f9286575f7de7d1d24d9000000000000000010f92b3d447ff78d1d24e9000000000000000010f92e151308109d1d24f9000000000000000010f930ece19029ad1d2509000000000000000010f933c4b01842bd1d2519000000000000000010f93d458193d37d1d2529000000000000000010f93fc1697a1e3c2d2539000000000000000010f94224e5e6915c2d2549000000000000000010f944881417db5c2d2559000000000000000010f946eb4249255c2d2569000000000000000010f9494e707a6f5c2d2579000000000000000010f94bb19eabb95c2d2589000000000000000010f94e14ccdd035c2d2599000000000000000010f9503ae21512247d25a9000000000000000010f956b96df582b82d25b9000000000000000010f95d175bb15bb9bd25c9000000000000000010f9637537228a7b0d25d9000000000000000010f969d31293b93c5d25e9000000000000000010f9743db606347c5d25f9000000000000000010f976314383d01c5d2609000000000000000010f977fba628c79c5d2619000000000000000010f979c09018341c5d2629000000000000000010f97b846613569f5d2639000000000000000010f97d2d4af6ddaf5d2649000000000000000010f97ec93f22b684dd2659000000000000000010f9804a3d4743d4dd2669000000000000000010f9a2b415d3cfd4dd2679000000000000000010f98202bb4e4ad4dd2689000000000000000010f98352e1830054dd2699000000000000000010f984a256c42e24dd26a9000000000000000010f985e9280c12c99d26b9000000000000000010f9871b15f52d81cd26c9000000000000000010f9884cad0dd281cd26d9000000000000000010f98979230cfca86d26e9000000000000000010f98a98b9f768a86d26f9000000000000000010f98e703d4c98386d2709000000000000000010f98f7e53295a6a6d2719000000000000000010f9908827dbc0a6ed2729000000000000000010f9a48a9abf3826ed2739000000000000000010f9a57c3239ec17ed2749000000000000000010f9a669ce71f04c2d2759000000000000000010f9a756f939f28c2d2769000000000000000010f9a83f9df57ba3ad2779000000000000000010f9a91f02fddd1a2d2789000000000000000010f9a9ef1213232a2d2799000000000000000010f9aabc59d0e190ad27a9000000000000000010f9ab89a18e9ff72d27b9000000000000000010f9ac56e94c5e5dad27c9000000000000000010f9ad24310a1cc42d27d9000000000000000010f9adf178c7d60a2d27e9000000000000000010f9b0c9474fef1a2d27f9000000000000000010f9b3a115d8082a2d381809000000000000000010f9b46c363a88f62d381819000000000000000010f9b9b30778fd4d2d381829000000000000000010f9ba7a17402251ad381839000000000000000010f9bb38deff1f1aad381849000000000000000010f9c9bc56bdf53aad381859000000000000000010f9d80380e26d5aad381869000000000000000010f9d8b1f404c72d2d381879000000000000000010f9d960672720ffad381889000000000000000010f9dbbd605e2ffcad381899000000000000000010f9dc616c5db402ed3818a9000000000000000010f9d4610d920fdc5d3818c9000000000000000010f9d6c43bc359dc5d3818d9000000000000000010f9d92769f4a3dc5d3818e9000000000000000010f9db8a9825eddc5d3818f9000000000000000010f9dcfb745985955d381909000000000000000010f9dcfb918cb4f89d381919000000000000000010f9dcfb9a09ef299d381929000000000000000010f9df20a18437c99d381939000000000000000010f9dfb96d108a499d381949000000000000000010f9e052389cdcc99d381959000000000000000010f9e0eb04292f499d381969000000000000000010f9e183cfb581c99d381979000000000000000010f9e21c9b41d4499d381989000000000000000010f9e2b566ce26c99d381999000000000000000010f9e34e325a79499d3819a9000000000000000010f9e3e6fde6cbc99d3819b9000000000000000010f9e47fc9731e499d3819c9000000000000000010f9e51894ff70c99d3819d9000000000000000010f9e5b1608bc3499d3819e9000000000000000010f9e64a2c1815c99d3819f9000000000000000010f9e6e2f7a468499d381a09000000000000000010f9e77bc330bac99d381a19000000000000000010f9eacbd5b569941d381a29000000000000000010f9eb50ab7139969d381a39000000000000000010f9ee1c6867df992d381a49000000000000000010f9f00a123f18e02d381a59000000000000000010f9f2a90ba827002d381a69000000000000000010f9f53a35c448002d381a79000000000000000010f9f5b47c06f1586d381a89000000000000000010f9f62b8a5cfa02ed381a99000000000000000010f9f6a298b302ad6d381aa9000000000000000010f9f71921130e1ced381ab9000000000000000010f9f8d9e1e6b3cbed381ac9000000000000000010f9f94d5dc89ca06d381ad9000000000000000010f9fb07b068fd672d381ae9000000000000000010f9fb7aaebcf5132d381af9000000000000000010f9fbe318b7f5902d381b09000000000000000010f9fd22abf5be772d381b19000000000000000010f9fe902d7f029bed381b29000000000000000010f9fffd96ac9b97ad381b39000000000000000010fa014fdae8ee07ad381b49000000000000000010fa02b7e2575467ad381b59000000000000000010fa03127bff9fa7ad381b69000000000000000010fa036e772f9c37ad381b79000000000000000010fa03c79cafd707ad381b89000000000000000010fa03e62bcbe787ad381b99000000000000000010fa0417eecc98c7ad381ba9000000000000000010fa0459a635cf47ad381bb9000000000000000010fa04b3d169a8ffad381bc9000000000000000010fa103254e9d4ffad381bd9000000000000000010fa0546f19677ffad381be9000000000000000010fa10a62bb6dc3fad381bf9000000000000000010fa05d8814b49cfad381c09000000000000000010fa1137bb6bae0fad381c19000000000000000010fa066a11001b9fad381c29000000000000000010fa11c94b207fdfad381c39000000000000000010fa06fba0b4ed6fad381c49000000000000000010fa125adad551afad381c59000000000000000010fa078d3069bf3fad381c69000000000000000010fa12ec6a8a237fad381c79000000000000000010fa081ec01e910fad381c89000000000000000010fa137dfa3ef54fad381c99000000000000000010fa08b04fd362dfad381ca9000000000000000010fa0907e18151efad381cb9000000000000000010fa0ab95a73db6fad381cc9000000000000000010fa0b3a71f995e7ed381cd9000000000000000010fa0b85444cd177ed381ce9000000000000000010fa0bc8544ce7c7ed381cf9000000000000000010fa0be6e368f847ed381d09000000000000000010fa0da00613b250ed381d19000000000000000010fa17920e517190ed381d29000000000000000010fa0e1f5349fbe0ed381d39000000000000000010fa0e626c3ae3b0ed381d49000000000000000010fa0e80fb56f430ed381d59000000000000000010fa0edb94ff3f70ed381d69000000000000000010fa0f37902f3c00ed381d79000000000000000010fa0f92255f1e80ed381d89000000000000000010fa0fd53e500650ed381d99000000000000000010fa0ff3cd6c16d0ed381da9000000000000000010fa10125c882750ed381db9000000000000000010fa105020e62830ed381dc9000000000000000010fa108c3db36400ed381dd9000000000000000010fa151fa23abc406d381de9000000000000000010fa1651395361406d381df9000000000000000010fa1782d06c06406d381e09000000000000000010fa18b46784ab406d381e19000000000000000010fa19e5fe9d50406d381e29000000000000000010fa1b1795b5f5406d381e39000000000000000010fa1b4f461779ea6d381e49000000000000000010fa1b8510e727939d381e59000000000000000010fa1bb9b2f910fd9d381e69000000000000000010fa1beb27be991d9d381e79000000000000000010fa1ccab91d92881d381e89000000000000000010fa1cf88fc7ab481d381e99000000000000000010fa1d260509c4c61d381ea9000000000000000010fa1d52d21bb70b9d381eb9000000000000000010fa1df954bfc1519d381ec9000000000000000010fa1e92204c13d19d381ed9000000000000000010fa2138bd7c26119d381ee9000000000000000010fa21574c9836919d381ef9000000000000000010fa2175dbb447119d381f09000000000000000010fa21946ad057919d381f19000000000000000010fa21b2f9ec68119d381f29000000000000000010fa21d1890878919d381f39000000000000000010fa21f0182489119d381f49000000000000000010fa22332c9d08219d381f59000000000000000010fa224fe65621e19d381f69000000000000000010fa226b18e76e019d381f79000000000000000010fa22864b78ba219d381f89000000000000000010fa22a02708cdd87d381f99000000000000000010fa22b9d563b3707d381fa9000000000000000010fa22d1f9a564507d381fb9000000000000000010fa22e781791d3cfd381fc9000000000000000010fa240a8e4261bcfd381fd9000000000000000010fa298fdde756fcfd381fe9000000000000000010fa245138a43accfd381ff9000000000000000010fa29acd39a98ccfd48201009000000000000000010fa2495cd308c4cfd48201019000000000000000010fa29e4838b788cfd48201029000000000000000010fa24d9bcb5181cfd48201039000000000000000010fa2a17561f055cfd48201049000000000000000010fa251cd12d972cfd48201059000000000000000010fa2a5974b9032cfd48201069000000000000000010fa255fd95af62cfd48201079000000000000000010fa2a9c237e332cfd48201089000000000000000010fa25a2dd0fec6cfd48201099000000000000000010fa25ab97317c0cfd482010a9000000000000000010fa265219d58652fd482010b9000000000000000010fa26f20ab4636efd482010c9000000000000000010fa27839a69353efd482010d9000000000000000010fa28131b5c7aaefd482010e9000000000000000010fa2819ceef6bdc7d482010f9000000000000000010fa289b5ba245507d48201109000000000000000010fa289ef86662107d48201119000000000000000010fa28a208c282a87d48201129000000000000000010fa28a517121de87d48201139000000000000000010fa28a82561b9287d48201149000000000000000010fa28c6ccd2e5c1fd48201159000000000000000010fa29206a56caa07d48201169000000000000000010fa2920ff64d3a87d48201179000000000000000010fa29283271e2d1fd48201189000000000000000010fa292880ad0bf1fd48201199000000000000000010fa2928bfe95c70fd482011a9000000000000000010fa2928e52cdeb2fd482011b9000000000000000010fa29290a247374fd482011c9000000000000000010fa29292424c585fd482011d9000000000000000010fa29293d744ff97d482011e9000000000000000010fa292956c3da6cfd482011f9000000000000000010fa2929701364e07d48201209000000000000000010fa29297c7291c07d48201219000000000000000010fa292987cf6bc47d48201229000000000000000010fa2929911190623d48201239000000000000000010fa29299984d67abd48201249000000000000000010fa2929a1cfa11f7d48201259000000000000000010fa2929a9eadf867d48201269000000000000000010fa2929afaade947d48201279000000000000000010fa2929b3e2555a7d48201289000000000000000010fa2929b819cc207d48201299000000000000000010fa2929bc5142e67d482012a9000000000000000010fa2929c088b9ac7d482012b9000000000000000010fa2929c4c030727d482012c9000000000000000010fa2929c89585267d482012d9000000000000000010fa2929cc6ad9da7d482012e9000000000000000010fa2929d0402e8e7d482012f9000000000000000010fa2929d41583427d48201309000000000000000010fa2929d7ead7f67d48201319000000000000000010fa2929dbc02caa7d48201329000000000000000010fa2929deb3cc9b7d48201339000000000000000010fa2929f0f62897fd48201349000000000000000010fa292a033884947d48201359000000000000000010fa292a0c257a0afd48201369000000000000000010fa292a13f8315afd48201379000000000000000010fa292a1a6546f4fd48201389000000000000000010fa292a1e3a9ba8fd48201399000000000000000010fa2c01ecc2b4b8fd482013a9000000000000000010fa2e6343f8d900ed482013b9000000000000000010fa3074539cba5f4d482013c9000000000000000010fa371422514d32bd482013d9000000000000000010fa3714708c7652bd482013e9000000000000000010fa3c9826693ed2bd482013f9000000000000000010fa3c982f7a9b60bd48201409000000000000000010fa47c275b7dc407d48201419000000000000000010fa515a03ce8fd6ed48201429000000000000000010fa515d78d62886ed48201439000000000000000010fa5435475e4196ed48201449000000000000000010fa570d15e65aa6ed48201459000000000000000010fa57da5da4190d6d48201469000000000000000010f153c646821f680f87cc56783282094c6818a83282095c681bd83282096c681bf83282097c681c183282098c681c383282099c681c58328209ac681c78328209bc681c98328209cc681d28328209dc681fe8328209ec78201008328209fc7820102832820a0c7820104832820a1c7820106832820a2c7820108832820a3c7820146832820a4c0ef944945ce2d1b5bd904cac839b7fdabafd19cab982bc0c0d5d482012d9000000000000000004c3e99215cf0d8c9c0c0da94497218097ed364385356d84e7c289b6f10b82919c0c0c0c0c0f5944976a4a02f38326660d17bf34b431dc6e2eb2327c0c0d4d381ef9000000000000005bcb28603d5865487b3c7c681ef834e6757c0ee944988c9c03950f10b14bf7e8bee5d5a4ede8c45b7c0c0d4d381909000000000000000000007f7e759e9ff16c0c0f29449e57033e27ed548fc5eac37a49f342869e45545c0c0d4d3819290000000000000000000293fdbdc68b3b4c4c381926fc0ed944a97cfef367ceaf890470d7a3095e2c4a5f8a09ac0c0d3d2779000000000000000000592febbd2acbd98c0c0f0944b8586be71d5e400cf5fd768fd206bb179815c24c0c0d3d20c900000000000000000044695cb7570ddcbc3c20c1ec0f4944b95963192df7c7e5fe2c017997a7c281a3be2cfc0c0d5d482010f90000000000000000000105cd6900576bcc5c482010f29c0f6944baa5b44700e0d6360933ee6998c2a6f44a6f4afc0c0d5d48201199000000000000000003e4cedfe4d13983fc7c682011982d5aac0f89f944bab2cb2ed6ee2d564ba5398cab4161d95e58f1bc0f884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000004a0d771858cdfbf142190c9fa275a8300d7c88cd19b5988d815e0f7dbe4069c0400c0c0c0f4944c08704798dfea887c5687e309de82bfd87950cac0c0d5d482012c900000000000000000000013e4322af7e0c5c482012c02c0f0944c56862612828498c21ed2d534da9687da57bc68c0c0d3d25890000000000000000000287c1452fd4b81c3c2580cc0f885944c9edd5852cd905f086c759e8383e09bff1e68b3f849f847a0b209b3d48408fa1096c0b2d98ecd4a8e873a5efd6855d2bbc45eb4d65831aa90e5e4820123a000000000000000000000000000000000000000000000000a2f7f50f6d5d40000e1a0396a2330c3e96731d20b554a4cd7844bd51ef95f0419f3cf9913a09b68863984c0c0c0ef944cd00e387622c35bddb9b4c962c136462338bc31c0c0d5d482011090000000000000000001208b8193fcc7d1c0c0f90375944da08a1bff50be96bded5c7019227164b49c2bfcf901aaf845a00000000000000000000000000000000000000000000000000000000000000008e3e272a0dc686571248e88c9209777e8caf9e46b9a3bb776459bfcea25d009143dd22060f845a00000000000000000000000000000000000000000000000000000000000000009e3e272a00000000000000000000000000000000000000000007bbfd294e8569419c7470df845a03767beb2f170e71264fea18f450de2fa4e162ab243858f39c42ac274c4ae344de3e272a020e042aaf468766fa2d19d14c7531d4aeaa633fbe66c219785b9e4d6000fc817f845a055e0f83a73febb53fb1fe91294da54100ec98355bf853cc50ce134b8d3907b02e3e272a000013ad74fcc67116b4fd08fb2a67d21938051b12c4f26795cb9c82afbfef3a2f845a0a3913c801a5a2004ae510df5a3fe05727ea86bd445df7d6c30f1aa88c5958711e3e272a0002765c08515f933c70647665f51ca4bbe23c0e073df8bec544286a5f9782696f845a0ce21d460b235ccf8881889e96cc980faa97543599c2f65bacf9346d98ed42048e3e272a0ffffffffffffffffffffffffffffffffffffffffffff3561ff7b5cc9b0084c17f901ada00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea0000000000000000000000000000000000000000000000000000000000000000fa02b789ff1281ebcfed0ef8889d9eec892a26e2df3ec6e40596c55391dc6da3948a050090274db5ce8b33350f708ef273c856e357a7dad54bbfaf7c356fe86ae01f0a092673577f1dec7fd1a60ea85672b9ab1e3c76200998b04af0bafb78b04db0b2aa0c677d98febed3a2933ad0474d2250dc0fe0d2f66b65a981ddb4515207448812ca0d3816fca9c5c37ad2415843fe0786962f569eb3ca39ddef9d5e65493cde8e90ba0e18a12436aba97254a10178d9b72b85c81071917a5628a2d652c1e2ecda2d396c0c0c0f4944de787046fbc43377208a020aa7cb8cf6b6bb31bc0c0d5d4820123900000000000000000003276a87094633ac5c48201234fc0ee944df817c2be2c1645226e6177f4efef2eff3661dcc0c0d4d381de900000000000000000033f4392008bc000c0c0f901de944e033931ad43597d96d6bcc25c280717730b58b1f9011cf845a0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff17e3e208a000000000001e6873f8c3339e937398400000000003462ad45178006aa8f3ad06f845a0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff18e3e208a00000000000286a37e6c1d74d737d44d50000000003588ff0d7ef131fea0d22bdf845a0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff19e3e208a0000000000000000000000600688a119700000000000000000000000000000000f845a0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff1ee3e208a000000000000b2724849870a7759117ae00000000000000a07037b267b7152517f8a5a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff16a0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff1aa0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff1ca0fd2dab4be6d07bba0109696859cf3ea9f610b92569d2a062e705af4b9c58ff1fc0c0c0f902eb944e502545debffbb0f20b9793d7c7d38b3f0f877df90120f846a032e6ed36d6136973cb3b1827dfe925c5089ca7c5e3df30a8474f27fcff44b52ae4e381dda00000000000000000000000000000000000000000000000000000000000000000f846a094c7885a7d14a580346d330f5dae0d609fbf3c0a3fc9ad60894a5d924377750be4e381dda0000000000000000000000000000000000000000000000000002c23872eddb87cf846a0a81db9717717012aa1d211cbfdca77b24437e69f83de246dac7b0b24390ddd86e4e381dda0ffffffffffffffffffffffffffffffffffffffffffffffffffffae9e63995252f846a0faeddf7f426ac4da780ca6b94481c6c6e54f8a68842d18a05c815c8428586612e4e381dda0ffffffffffffffffffffffffffffffffffffffffffffffffffff86f8cdeba4edf901ada00000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000015a00000000000000000000000000000000000000000000000000000000000000018a018941ce65cc1c52c15a23e526e4c35e14179c4d45da8127b5f3b5e1e6dce12a3a04c940f44f7daa735b43c25d09e30c001f0f658dae546ad67beb0f7bf0f38c1a6a063410be2035d9fe6d79863091d9e7405f927d9343a7c7a8f08cf36b1f25c6b5da0ada9f2f180bbfb702a0feafe0810fa96879835ca0ae4f7e7e81eba129f0efc98a0feaf537cfef56ffbc04ac5e9b458b9367508657de833f755f8407fc9261f1d77c0c0c0f7944e5b2e1dc63f6b91cb6cd759936495434c7e972fc0c0d5d482012790000000000000004897b01c6bb07de581c8c78201278310a586c0f8ca944f4495243837681061c4743b74b3eedf548d56a5f88ef845a03405731bebdbcb79e500db48ac8e016c3ba57fa37dc2520a3416b70b428e8b2be3e217a00000000000000000000000000000000000000000000000000000000000000001f845a0a61e3476bec9160520db11b0671e68af7b3828e5ecb8bff66ccac312da2d933be3e217a00000000000000000000000000000000000000000000000000000000000000001e1a011141f466c69fd409e1990e063b49cd6d61ed2ecff27a2e402e259ca6b9a01a3c0c0c0f4944fa628f0d1759a6f5d38d8ffd6a2d39e5800774dc0c0d4d381fc9000000000000000000086cfe80a6092fac6c581fc820102c0ef945001e8ee78f3c7366c23053e12110869ed70c51dc0c0d5d48201389000000000000000000369bd787caf3f79c0c0f90154945013b8c94ff2e95e8b36e3c064274b20987403f8f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e272a0688a11970000000000016ebf91362eb963460000007b61d9607246939da5e949f845a00000000000000000000000000000000000000000000000000000000000000009e3e272a0000000000000000000000000000000000126a4eb29dd8eb96d7ba0cb092e7422f845a0000000000000000000000000000000000000000000000000000000000000000ae3e272a000000000000000000000000100a397c974dd08d10832790adeeafd24b95b8ea2f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f90333945018be882dcce5e3f2f3b0913ae2096b9b3fb61ff901aaf845a00000000000000000000000000000000000000000000000000000000000000002e3e251a0000000000000000000000000000000000000000000000000000008f50f675d0af845a00000000000000000000000000000000000000000000000000000000000000003e3e251a00000000000000000000000000000000000000000000cef77fe89dec6d5c58345f845a00000000000000000000000000000000000000000000000000000000000000011e3e251a00000000000000000000000000000000000000000000000000000000007d41a93f845a0000000000000000000000000000000000000000000000000000000000000001ae3e251a000000000000000000ddf40182660ce6300000000000000000ddf385c53eb0b60f845a00000000000000000000000000000000000000000000000000000000000000022e3e251a000000000001529a7d58eec9cba5a59ac00000000001514c5d79d3b3e673bc9cbf845a00000000000000000000000000000000000000000000000000000000000000025e3e251a0000000000000000000000000688a1197000000000000000000000000688a1197f9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000019a00000000000000000000000000000000000000000000000000000000000000023a00000000000000000000000000000000000000000000000000000000000000024c0c0c0da945141b82f5ffda4c6fe1e372978f1c5427640a190c0c0c0c0c0f29451e413ac811eed825fffc2dec3f5e576e8dcf82cc0c0d4d381eb9000000000000000020224112c3ce08380c4c381eb2bc0f4945297715b9fd3ca8074f66931c7d2849203a7425ac0c0d5d4820118900000000000000000002355121cd008a8c5c482011817c0f902929452aa899454998be5b000ad077a46bbe360f4e497f901f1f845a00bb8667e1ee6b6e6118785dfbdc2fd4ded28cfca648afcf3bad9572eece212d9e3e208a000000000024422000e104e20d114232e000000000000000000054ae7e52e7001f845a0152a69092738c066daa08b8777778da6b057279be14ca7d1d49b640d77c39157e3e208a00000000002314a000e104e20d114232f6ce94cab85e25a376ce94cab85e25a39f845a0682167f03d2f86443da5d0f51d2ca8859c50078b4cd1c8ebc64109e9b18631e6e3e208a00000000000000000aa10bb486fef681e0000000000000000b890b0d39efa9c1ef845a08871f11c8fe6bd1f8deafc6624e239df84ab1d8dcc20834245629b415f9a1b3fe3e208a000000ff88f9822000e104e20d114232e003fe00000000000002ebdb7d4e57c01f845a0a1c9baadae9605c2d80ba1bb7f78050c6c3bdf08337ca0849c9b62b20d1a5e00e3e208a000000fb92f894a000e104e20d114232fc480000000000039492e827fb940b237f845a0a8e1248eddf82e10c0adc6c737b6d8da17674abf51801ea5a4549f41c2dfdf21e3e208a000000000000000085805efd0200000080ff7177f91a228465c01e86c43e80277f845a0f942f4688cdba65adc8aa59da583acae93fa87351143ebc775559218bfa5f832e3e208a0000000000000000000ceeaa10a654a00000000000000000000f8453270690200f884a00000000000000000000000000000000000000000000000000000000000000001a00ddd0a1faca9fe65b00c7ddd2626a1f25bf1f1e7f2f7a3e465a43e3fc77db927a0ad967e153ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0c54ea4dd6a734fa461b7ee355eb25a8cb9467ce4d0e78fa68c9c085fda8edba2c0c0c0f294532682ff559666d62ff928d7b57c334fa9e84de7c0c0d4d381a29000000000000000000000000000000000c4c381a206c0f494533d2ab03e309ad73be9bcd80b298cb41c6db142c0c0d5d4820125900000000000000000000ca0f18b7254d4c5c482012501c0f85d9454586be62e3c3580375ae3723c145253060ca0c2c0f842a04b52de242af8e0998837b071693bd8f6ce57e6a143f73600341bde1d27dd942ea0c6521c8ea4247e8beb499344e591b9401fb2807ff9997dd598fd9e56c73a264dc0c0c0f09454869c9a52baccc9f987c50ed238441a9841ba6ec0c0d3d25e900000000000000000009f7b91d1f7dd67c3c25e0dc0f09454975a68c0609280f67050f3b167d554e89c325cc0c0d3d27e9000000000000000000007c49ecf2a793ac3c27e0ec0f85d9454a8757c2fef8649830b158a8c19d3a670e80318c0f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f794555ce236c0220695b68341bc48c68d52210cc35bc0c0d5d48201429000000000000000051fc859e118620649c8c78201428307e5b2c0f9010294556b9306565093c855aea9ae92a594704c2cd59ec0f8e7a0000000000000000000000000000000000000000000000000000000000000000da00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000011a032b8c3e0163a3eceb0dd8f4a69ff6a8f786c1f0b2824e29def2a833031a4d374a03e5fec24aa4dc4e5aee2e025e51e1392c72a2500577559fae9665c6d52bd6a31a08819ef417987f8ae7a81f42cdfb18815282fe989326fbff903d13cf0e03ace29a0f68cd560cd5285fa96f176cf0d55ce2f05178e0b25aac465f04891d21bfb0c8bc0c0c0f84c94559432e18b281731c054cd703d4b49872be4ed53c0c0e6d21e90000000000000000377de7d2d5cd147dcd21f90000000000000000377ddbbd5f040b63dccc51e83077f0dc51f83077f0ec0f29455a28a0334b507a3bf827a2050fde1519139ad70c0c0d3d2239000000000000000001fe58d74143a428bc5c423826c95c0f79455fe002aeff02f77364de339a1292923a15844b8c0c0d5d482012090000000000000000e123525a7ad4d597cc8c78201208314f291c0f39456de1961fda5454e6f8e6d0e3124ff648fd69400c0c0d3d253900000000000000005485cbf2a7b99da99c6c55383027832c0f86c9456eddb7aa87536c09ccc2793473599fd21a8b17fc0c0f83cd381d89000000000000004955dd44b95d8ba40a2d381d99000000000000004947891f93e6f3b534ad381da9000000000000004947752bcda416765f2d5c681d8837ca5cfc681d9837ca5d0c681da837ca5d1c0f87e945703b683c7f928b721ca95da988d73a3299d4757c0f863a00000000000000000000000000000000000000000000000000000000000000065a00000000000000000000000000000000000000000000000000000000000000097a077041d625e83eec30f111a00cc41237389024e39804657e1d8c85ac038f24dbdc0c0c0f494571f7be0d706e84bfc902fb503f668f73d97636bc0c0d5d482010c90000000000000000008a35be45f0867dbc5c482010c28c0f842945724ff60178dbda504cda6759ebf54a16146141dc0c0e8d381f1900000000000000008eecce29ebbdc2255d381f290000000000000000aaae3b913a85c2255c0c0f9010d9457ab1e0003f623289cd798b1824be09a793e4becf88ef845a04691f66de7d80ce42a081aa4bd60fb51aabcc0c52150ebb3eaccf4ffeb2db73fe3e251a00000000000000000000000000000000000000000000000000000000000000000f845a07c68612ac0fbdb3238c22e90b72e70b9964f9c124d83372937aaf35501f86269e3e251a000000000000000000000000000000000000000000001e308032fba4d36524372f863a034585ab80ba1df59a989f3ef0a7d93fb0e76402b62389d18ccbca9dfe77bb036a0a3f5d68cb14a82c6ebcf0673f80f5f00cf5479cc58f9d91e709d98b9088ff445a0de7fd8cf191180ddca506141632dde4c47d40344b3edcd10f1d74c93da232c9fc0c0c0f901c89457e114b691db790c35207b2e685d4a43181e6061f901acf845a006694443be61d3bead28ad564139d8813adce3e1cc7bfabe502d0df2c8325df1e3e22fa00000000000000000000000000000000000000000004ee54fd6aaeb13b0f21637f846a042c3c975aef34537203fe419bd3d01ced0b59024258736cdd687a16eaef719e1e4e381cda0000000000000000000000000000000000000000000000bff3280256a6e8f0000f845a097c61cf25a75e9ae063e7f086838f5736319e97f8e04cdbe66d8130ec48cfa79e3e201a0000000000000000000000000000000000000000000037e5cf33515f9e48fe62df845a0a37b0e3adbdb184815c07baddfda6fefa100f864754f985fcbe24f44178bb025e3e201a00000000000000000000000000000000000000000000002ab8fc7b5cc30dbcd74f845a0b138da85d4a6bc6d9df0416684b5310719fcb67b92e491f770223e14366bbe60e3e22fa000000000000000000000000000000000000000000000006db0b7e2e9e7e08000f846a0df70affd7af96966d2670c0f3ed33a9da6b177986dc23d985c9fbdd265f954a7e4e381cda00000000000000000000000000000000000000000000d8464fac9eae21919b800c0c0c0c0f8c094580ee6b001348fa0deb675f4d55259c96c4a3a31c0f8a5a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000008a063187d71e139eee983a88d0737447c7451979b3dbb75903c76b5fe430d36588ec0c0c0f494582a7549a02c8e8ea68aed17d5b638da616ac3fec0c0d4d3819e900000000000000000070e62d7ab243875c6c5819e820330c0f8c0945857feb095302407a718ba6386a53c35da0deddfc0f8a5a05e155a97eb175a88171e46c32490e27b233d3f88cf417d8c63adbae4a9953ab2a0c5d97fac1fb086771aed8e18097f4a592ac1d148017b2d4abf3c362c10779e02a0cc2c919a9936f62868368e517bb4f3602dd0536094fb620e6a8aa11a373ff033a0dc4540db8f0963570faf41f59ae94abe06c7fa67e9c4153c5704dd20bbe1fb51a0e6af6d2ae335a90de767f2f5fd24405f8a29336d7c37a63d0df3b582130a2a08c0c0c0f8ec9458aea10748a00d1781d6651f9d78a414ea32ca46f88ef845a052bdb5b93751a403e782f61a949614bd7cc89ef403e33deded79eebec6239c1de3e273a000000000000000000000000000000000000000000000e621b6e5fd6c94d92ce4f845a071e19aa0420ec31c5cf3b7de7d806bfb695520bbc3beb3416e6d09f1f045289ee3e273a000000000000000000000000000000000000000000000022512f7b2a0d73a0ae0f842a006afade4f2eae53ba4bcd503fa4c80b231de2aba72f5cd0019688695d56bb105a067495bebbcea6b2f8ff22cc5a8ff1151ac065b75f0f9556d64b186e9f5fc47e0c0c0c0f69458c42647eb2cee1686c3ca596e31dd69a8c993d1c0c0d5d482013f900000000000000000aad9e19e01ffc24ec7c682013f825280c0f39458edf78281334335effa23101bbe3371b6a36a51c0c0d3d25f90000000000000000267258d9876e376f7c6c55f830b8469c0f3945b43453fce04b92e190f391a83136bfbecedefd1c0c0d3d2019000000000000000065996dbef1fd3a689c6c50183043c47c0f84a945babe600b9fcd5fb7b66c0611bf4896d967b23a1c0c0e8d27d90000000000000000184578c5890118408d48201129000000000000000018456acd91671158dc8c782011283034d56c0f9010e945c7bcd6e7de5423a257d81b442095a1a6ced35c5f88ff846a0c3ec7e2084dfdd3a494595954ffa85dd2a8b45664068342aacc7574de347f2c5e4e381dba00000000000000000000000000000000000000000000000000000000000000002f845a0d0ef1a95eb5fe93910150115352e8ccf0857ea3cfcfbe8794a49fbdc05d80915e3e274a00000000000000000000000000000000000000000000000000000000000000002f863a00000000000000000000000000000000000000000000000000000000000000065a0000000000000000000000000000000000000000000000000000000000000086ba0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f0945d3964e8bd3e4aaa5c86927e3c944cb1c0c4f474c0c0d3d2709000000000000000000000b529f4c0e8c8c3c27012c0f2945d93e31a2235e2c6af10799fc333e97f5b5fdbd8c0c0d4d381cb900000000000000000000212a1dcf58cccc4c381cb34c0f6945d947f367bbb2251d3d2b7e9320424d5d6ba980bc0c0d5d482014190000000000000000004dac290c5bf9a06c7c6820141820240c0f83b945de1b25d0a94549c9dace58777c385fa67aa846fc0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f3945e2b6c6b2240d582995537d3fafdb556e4a3822fc0c0d3d2029000000000000000006a4680cb7e1cbefdc6c50283065021c0f2945e5778536d3338bf6f001f3e2f44d99d5400fe28c0c0d4d38180900000000000000000000ebf2751374884c4c3818007c0f3945ebab6791bb90ac6acc1afa394e440366b933310c0c0d4d381e49000000000000000000006d6c4711faacdc5c481e48188c0f0945f034589713d3a9f4c67a09b478b800db7764cd0c0c0d3d23d90000000000000000000074516d2822081c3c23d05c0f85d945f4ec3df9cbd43714fe2740f5e3616155c5b8419c0f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000005c0c0c0f90232945f64ab1544d28732f0a24f4713c2c8ec0da089f0f901f5f845a00f12b799b59d030df91357dbc659788be3afe85ed8ab31fb27b990a45e64d037e3e20ea0000000000000000000000000000000000000000000001033ee67bc14b0f90159f845a02578ed7667b2e2fae43f52095eadc659b460ddc1f604cb70acdbc3932261f315e3e20ea0fffffffffffffffffffffffffffffffffffffffffffffed54c49e51be710ff79f845a045f0b7fa21a4287c5918668b6ede365097a5a85bd0ca3a042787e67ede5e18b4e3e20ea0000000000000000000000000000000000000000000001728c6940f601be28828f847a062d1ba451cd9bff25ab16da34e80f27dcea3a8871d60843b624dac8e638653bfe5e482013aa000000000000000000000000000000000000000000000937d4b36a4da7f77582bf845a069a1997d5f27fd969be349ea22f3e24ef4e94682f22fa7a1b6e23f01fc312c59e3e20ea0000000000000000000000000000000000000000000000000000000000000001af847a0a5f0144de9db59486366f7b445c5b2533283c0ee862892e9cdfe04f7c6984aa4e5e482013aa000000000000000000000000000000000000000000000038c16b563b32006e252f845a0d7598ba2b8edb81a78e85bd10b6ed1bc2d0c0ec981dbaa7b7b55f301cd407c8be3e20ea000000000000000000000000000000000000000000000703da631a866375521fbe1a021c83849a98c24884fa1ccc35ff710ab3d9f125ff1af7913b10a7defa78f35b3c0c0c0f83b946088d94c5a40cecd3ae2d4e0710ca687b91c61d0c0e1a00000000000000000000000000000000000000000000000000000000000000065c0c0c0da946131b5fae19ea4f9d964eac0408e4408b66337b5c0c0c0c0c0f844946164fb05d6fcf67f7c5ec0d4e013d5895fd169c9c0c0ead482011890000000000000000000e3fd38263b524cd482013d90000000000000000001c55be812cd03a9c0c0f8a594616de58c011f8736fa20c7ae5352f7f6fb9f0669f847f845a0d6515c1ab23c173d3fa75dfb8397d82d6cac6cf050c7d433cbe2fe8ab1c7b17ee3e205a00000000000000000000000000000000000000000000000000000000000000001f842a00000000000000000000000000000000000000000000000000000000000000000a0b24e76a7f8dd0cbaae591c6f267ed91d1658e795c6799def790dc03129e6817ec0c0c0f4946206702c381281e56b128a9d7719b8844d84385bc0c0d5d482013d900000000000000000002355121cd008a8c5c482013d17c0f86b94623777cc098c6058a46cf7530f45150ff6a8459dc0c0f850d381949000000000000000005c7211ef9a484462d381979000000000000000005c72207ce4994850d3819a9000000000000000005c722f0a2eea4c3ed3819d9000000000000000005c723d97793b502cc0c0f8649462c1e9f6830098dff647ef78e1f39244258f7bf5f849f847a03f5e2ad317328859de1d5234a8bd619fe5fdf66d2173998bc4cb0c47f1e90dbde5e4820101a00000000000000000000000000000000000000000000000000000000000000001c0c0c0c0da94634769eb87542eaf41c0008c05d5d8f5d8bec3a5c0c0c0c0c0f9015494637340ffaeec55e9da0efb799f99fdb879c12a3cf8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20ca0688a11970000000000001eac6d08bfd9dae9000000000000005a3cd5ba74498df845a00000000000000000000000000000000000000000000000000000000000000009e3e20ca0000000000000000000000000000001d687dd6515edd5f74ba9af18aab2385800f845a0000000000000000000000000000000000000000000000000000000000000000ae3e20ca00000000000000000000000000000000007cf576938e3cc04f0810de301c2d698f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f90133946378f1fdaf3048ca71d8f69671c31cb83cabee69f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e21da0000100000100010000fdbe46000000000000000000284795e597f0925bdc82f2f845a00000000000000000000000000000000000000000000000000000000000000002e3e21da000000000000000000000000000000000001dc707a2cf8fbf6286217746a5b0c2f845a00000000000000000000000000000000000000000000000000000000000000008e3e21da00100000000000000000000027f8088d894924f237bffffd0f53a21fc688a1197f842a00000000000000000000000000000000000000000000000000000000000000004a0c0d1c00078410fd0164580b0bad93d8a579580d06cf45fc2696a823498097b8ac0c0c0f901759464351fc9810adad17a690e4e1717df5e7e085160f8d5f845a006a9252c6538079518b2b352a6ce6548cf0b6b07fa7b705bdd31a9f476041f68e3e265a00000000000000000000000000000000000000000000000000429056bd1973ec1f845a0c269910e79664bb02e4ea6b58a6546a8f30ea3f5840ed79d34d06b85ebddd776e3e265a0000000000000000000000000000000000000000000000000001fe4f872e101aef845a0fc5da5621ecdf503c1b60d3853c14e4222d580d5bc84c71986837c27fa27ef1ae3e265a00000000000000000000000000000000000000000000000b026959c730dc7ee1af884a00055db1c41b2272eaef9225cd2750f18a33dcfe06f456a5d8bbff9f2785bd740a02a9f100b9103993ec2993a2dbe2dd6ab0ef65a4bc285359a6d2f66ad33120716a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0ee94649a9d2ad1d874acd8dd4feb069db362989b1971c0c0d4d381f6900000000000000000000768ee60903e60c0c0f69464bd83be58924106695ca923b159cbebb20628bdc0c0d5d48201269000000000000000001b0664689d04d821c7c682012682676bc0f29465233016509316a31725521d270985d017605260c0c0d4d381f7900000000000000000000310156c90f777c4c381f702c0ee9465b07a984be4e826fd23969d0b2e45a0eeb25e73c0c0d4d3818d900000000000000000000110d9316ec000c0c0ed946627f796f092b84b406981c1e4ce81f9a4fb43a5c0c0d3d2129000000000000000000001fbe638bc2319c0c0f89f94663dc15d3c1ac63ff12e45ab68fea3f0a883c251c0f884a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca085666f94a26173b9c3e389a3740c029d5e88a5af5fa6b800fbeb55b3ff0317b9a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0cd42f7cfea4a1fa33b3d0afadef76575855644c7d27312e535ac5e13da0e1a81c0c0c0da9466a9893cc07d91d95644aedd05d03f95e1dba8afc0c0c0c0c0f09466b733cd5dc02830c5d4897c729fef517abb8487c0c0d3d269900000000000000000000bf39661519229c3c26902c0da946747bcaf9bd5a5f0758cbe08903490e45ddfacb5c0c0c0c0c0f83b9467a45c5a86ebf8c66958b11b019c6ae44c797f41c0c0c0c5c482011d01dcdb82011d97ef0100411d38d27f6f2c7f3b70ff29dada64cbd7bfa9b2ed946836451238fb831812c3aa45fb81ea19404dde5dc0c0d3d258900000000000000000fd2f8f23ef256000c0c0f9010d9468749665ff8d2d112fa859aa293f07a622782f38f88ef845a03caf690871077112de96bbade7af43609bcff39f4bcd9207ac2e91caa223daade3e216a0000000000000000000000000000000000000000000000000000000000ae124d5f845a0a929bef58f099c4c46db9c22f6056fac18e57926e858af3c5e84dd1a1636c6a4e3e216a00000000000000000000000000000000000000000000000000000000000000000f863a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca05e65dcfd484b35a6aa2a9a0211b40586ac81e38baf9773281f6ed989e7296755a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0ee9468b0ad78d79c7c92a16b33f4a7de900c94aa6f06c0c0d4d381ef900000000000000000000c7741308e34dec0c0ee9469089031dbae3fe96971a8c2566012d9ae2e7250c0c0d4d381f0900000000000000000000176cea201eceac0c0f901119469209dd095f6d835e51f9db68732893ae636ee41f8d5f845a0002d2dfb03405be9a3ac6d76cb83ef7d39f8feddf1f48a6128a3c0c28ce8482ce3e236a00000000000000000000000000000000000000000000000000000000000000000f845a0227fd1fc1d4e46436eca13e12e074e4be5c45dd7e8ce87a9b180cc2da5f1d343e3e236a00000000000000000000000000000000000000000000000000000000000000000f845a022d2c177893fcc717cc52307253660f1a5b11f7e88c9b91361f8127d949f5e60e3e236a00000000000000000000000000000000000000000028bc3f098bc4516643c8e04e1a00000000000000000000000000000000000000000000000000000000000000007c0c0c0f8c09469460570c93f9de5e2edbc3052bf10125f0ca22dc0f8a5a0322c2f1d9209969f334c8955443b224cadc85f453939eb2b4ffb8af019944ecea044629564ae37fcd36b0f7214cc9716ba49370bb164b94e906f723ae0fe6c69bea04fe94118b1030ac5f570795d403ee5116fd91b8f0b5d11f2487377c2b0ab2559a0de98b90b1713f5063c3418d1b357cf3b187ddec8b5d652063ec9e3f92c541caea0e465ee32fd52e3c4788da8b0bc9bfc53726e040cab58010f4bf2a85773817a55c0c0c0f901549469c4292fb03abc9ea6efe97471bd9c07ffbfda29f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20aa0688a11970000000000007055c8637af4c7990000000118a6ddaa03dcc885696bf845a00000000000000000000000000000000000000000000000000000000000000009e3e20aa0000000000000000000000000000000000000766e5b3a43553ada8b939a744804f845a0000000000000000000000000000000000000000000000000000000000000000ae3e20aa00000000000000000000000000258b9809b50bb6e213e87761ed9d4b2fdb9aadcf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f87e946a000f20005980200259b80c5102003040001068c0f863a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002a0c6521c8ea4247e8beb499344e591b9401fb2807ff9997dd598fd9e56c73a264dc0c0c0f885946a8ee8251a43aa2fdb564d57a6b5df5d4f91b909f849f847a00000000000000000000000000000000000000000000000000000000000000000e5e4820119a00000000000000000000000000000000000000000000000000000000000005d2ce1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0ef946adb3bab5730852eb53987ea89d8e8f16393c200c0c0d5d482012e9000000000000000004563918244f40000c0c0f90154946b175474e89094c44da98b954eedeac495271d0ff8d5f845a00000000000000000000000000000000000000000000000000000000000000001e3e208a000000000000000000000000000000000000000000c141e5b8914b4bcfb661035f845a00e3d19729328f478ffc901c115f05d0195e5b68e282b84da93c6bafd953fdc80e3e208a00000000000000000000000000000000000000000014522188f60a0a649f90000f845a031adef62206227419133dd9a6b4041532c22595206a596cf74f19493bfc8f368e3e208a0000000000000000000000000000000000000000000000043a1f911d441537ec0f863a02c0119782c3461cd19a69469612191780a22daff93839018912934cb127d9581a0848ae903f6887b5d0bca5bf7f90c9bbed50dcdc8354dd3e207a5ea769fe48a2ca0d556473391dfdd4316b55e4e1ced572841875f0c55cffcc5f9f3e87c1d988d3bc0c0c0f3946bf97afe2d2c790999cded2a8523009eb8a0823fc0c0d3d2079000000000000000007a7f4144629ba3e3c6c507830157e0c0f90145946ca298d2983ab03aa1da7679389d955a4efee15cc0f90129a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000000000000000000000000000000000000000006fa00000000000000000000000000000000000000000000000000000000000010008a0842abfc34bb944881d81341dcd1e6de46da5c5989c26bece76592f9f0c2056c8c0c0c0f2946cd66d90fd1d90f7c58c0027cdcf31448a9d7d0bc0c0d4d381df900000000000000000000af51b403513e8c4c381df08c0f4946cde44c54a5dcefb86363920ebc5ce5f4bc4710ac0c0d4d381b090000000000000000001923ab6c88d3d98c6c581b0820d4ec0f862946d19568a959fcb4211852f6472d3df7b67c6cd54f847f845a0c9e7e710c81fe3c0e1d581bd7eac244732ea2d03be3efe2f6e33ecd72fa390b0e3e269a00000000000000000000000000000000000000000000000000000000000000001c0c0c0c0ef946d2fe9c5d142880f3a93fa11f38fe8e0d22728a5c0c0d5d4820145900000000000000000039e4c2c385d2671c0c0f90154946d59b6c2058114f16e2ed839a92d89a91271474af8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e207a0688a1197000000000000eec30b1bcde32c7e000000001423fe1609d86175aee1f845a00000000000000000000000000000000000000000000000000000000000000009e3e207a000000000000000000000000000000000027b3c3f08aef6aeac177eec139d9d54f845a0000000000000000000000000000000000000000000000000000000000000000ae3e207a000000000000000000000000000076ad8dc28b9f7d42deb125d20f8ec39b6f12cf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0ee946d5a597129ad5f55a03d2e33c5e965a9cb9450bcc0c0d4d381df900000000000000000007f62bfa6071578c0c0f901de946de037ef9ad2725eb40118bb1702ebb27e4aeb24f9011cf845a024451c947f191ba0b530f793257e0d96a35841b777a0fe4fcdd85e4f88753b98e3e201a0000000000000000000000000000000000000000000001139d334fc8ac34e8db8f845a082925f70a42a8633fa28e1d369e47cc833e0a19d22b9450795322233e965ef3ee3e201a000000000000000000000000000000000000000000000440cb9373851f7f36bddf845a095eb8b8ef7069f8b5c679fa94eca1f6d19856b88aa03f8eaa9ca2b675d797698e3e201a000000000000000000000000000000000000000000000053e9cd22074e537d5a7f845a0de3396827fa575355c40f2591e90c244df1fd101730e44da9e54c1d040587739e3e201a00000000000000000000000000000000000000000000012245bc54fade1d07e26f8a5a010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba02ab7b5d3a006a03e38412a2b87a8a91e6939d7831cfd5517a58d87e84041330ea07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a075d179b340750ce57d2ecf17f98f27e78c742b8df016157c225ee4bb188bd639a0d92ed7809a90db8233b3d257c046658f4c9e922961998ee652d620cdfdc796c6c0c0c0f8c0946e4141d33021b52c91c28608403db4a0ffb50ec6c0f8a5a043c8f2d2647a5364f7b9d60f7457f48e15dc0fcee12fdafb7946de2931cd3de8a08b66d076df2dd6688722f6cdac8c0fc9b6fc751fcb1e4dd762ec011ebffb8730a099cd62e62486ae867831c2c0011b576c017cb6aabcc65c7f3a0f4ff306f3ab1da09f3ab2fdd07f5dfd9349894140475205652787be63392a23d141589f7f34ebd1a0ac2f39fb2f41b5739dacad3513735e479a2afcaa35de82131596b12966965028c0c0c0f2946f05b59d4e58e821f48e67d5962f82bf4df1c9ffc0c0d4d381e29000000000000000002c60e982581802a8c4c381e201c0ee946f443e96dd5d6a29be066b5887357e2ba8c8c0b1c0c0d4d381af900000000000000000255cf2670333a127c0c0f2946fa67393be5d9a5b6b3570646ef238bb52795350c0c0d3d23a900000000000000002912b68a75e4468c4c5c43a8261f2c0f29470167b76543c4a12b49b2f2b70cbf04d99345786c0c0d3d22f90000000000000000135356b731358e576c5c42f82093ec0f90154947069f8cea562778bde0bac8f512d3f6b73e1e7f4f8d5f845a0262bb27bbdd95c1cdc8e16957e36e38579ea44f7f6413dd7a9c75939def06b2ce3e26ba00000000000000000000000000000000000000005b71182d9048ec8fafb3f138bf845a052c54c7d408b9ca1cead7ed6a491323e5451549d9ef0992d42f595e36ce08f77e3e26ba0000000000000000000000000000000000000014b1008247b2695eec690f74782f845a0d722500f1a1a9b3f1ad91802a20b180ba05ffe232f9a17da4eddda61a4632ac2e3e26ba000000000000000000000000000000000000000001898faa1d0e7c15f6fa16aa7f863a00000000000000000000000000000000000000000000000000000000000000002a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f094706b46919914e1208a10473f411fc65095e582dec0c0d3d250900000000000000000000600047e5f6881c3c25001c0f85d9470cbb871e8f30fc8ce23609e9e0ea87b6b222f58c0f842a00000000000000000000000000000000000000000000000000000000000000066a0302232516c31bd2939bbdce153021eb76453892c0c2585337fa9076ae9576614c0c0c0f49470fb47c51e443e3106625fbbe839b6b21aa9f09fc0c0d5d48201349000000000000000000002624cefa45907c5c48201340ec0ee947137c38e1b31ea9a2ef59cf7179a2adc4a8729acc0c0d4d3819e90000000000000000000034c26f436ec18c0c0ee947273e0c112517de2d174f0786ee090f5c30fab7ac0c0d4d381ee9000000000000000000078c8f1981eceb0c0c0f29472d4a8e80319e7380acf5fe87b3a218e99cdcecac0c0d4d381f69000000000000000000000004aa91ad198c4c381f602c0f39472e5263ff33d2494692d7f94a758aa9f82062f73c0c0d3d26d900000000000000003718d4ffffcbb70dbc6c56d8306683bc0f8ca9472e95b8931767c79ba4eee721354d6e99a61d004f88ef845a0000000000000000000000000000000000000000000000000000000000000003ae3e230a0000000000000000000000000000000000000000000000000000a98d68f4bb169f845a0e87579d97cad2f208f65e0e8103dc581de8b3fa3d6afc16ee30039101baf1cc4e3e230a00000000003d32fca4546a285774eaad000000000000000000000110dfbe3ac29e1a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f29472f36389a2fd387d0f7e72051f2537bb5cf7990fc0c0d3d25c90000000000000000007aed6992e71a8aec5c45c82082ec0f29474b5c6bb8d946dabe97c537fe78c0e3643baa103c0c0d4d381ad900000000000000000001c39c6dd24bcf5c4c381ad09c0f2947506ad2445690f3046a147991f585afa073d696fc0c0d3d2719000000000000000000000000000000000c5c4718202f9c0f901339476665642f513aaf2a00be05711a598f44e3970a7f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e202a000010000010001000004907c00000000002fba628913a211d8466e15c3064908f845a00000000000000000000000000000000000000000000000000000000000000002e3e202a0000000000000000000000000000058e8e22ba81549f1f667d671fbb74b82d439f845a00000000000000000000000000000000000000000000000000000000000000008e3e202a00100000000000000000369b00e7a7dd8b3a219165b00010ade2e83f4688a1197f842a00000000000000000000000000000000000000000000000000000000000000004a0224033bf7d20523e17d8bf9fe0c779d691193b8ae991a81edb5a115c29808a33c0c0c0f9010f9477146784315ba81904d654466968e3a7c196d1f3f890f846a032d3054900052ea18fe8954b67e1b5f4e5cc5405181cb5704ef34a458e834520e4e381b7a000000000000000000000000000000000000000000000035a3cd59f1c88010000f846a0c2a4f35c1894d1d151069ca483cc639a905a92f57c0e20e6a2a8aed52f731c29e4e381b7a00000000000000000000000000000000000000000002e826cda50079371bbbd2cf863a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0ad3074f3b9db4c8616e5e0839d423e4dafccac8645228abfd7638455b345c6eda0e90c2d9fcdf1569e25899c5c30e83f418710c1e8284fa4620f8d7aa38b6696fcc0c0c0f29477b2c0784794f28582232e6c73932ae6d1cea4bac0c0d4d381ae9000000000000000000024d5ca79fed1e1c4c381ae01c0f85e9478d250172dad653ae5b8cd9b825de8e0caac2c94c0c0f839d2139000000000000000000a252c60b0ff4022d2149000000000000000000377440aeebf7b8ad215900000000000000000036ff14e0795e8f6cac4148204ebc4158204ecc0f29479538bf55bb10715a440d7cf1f809e1cb8fcf3d3c0c0d4d381f0900000000000000000011b7f8db81a2fb6c4c381f042c0f494797fcce0bf1dcf528752f2cb338f0b948c74a01ac0c0d5d482011090000000000000000000003e70bc0da7bac5c48201100ec0ee9479a77a1bf7335dd8dd7b3316a8424d9c90af397ac0c0d4d381e1900000000000000000012a6d8e11220000c0c0da947a250d5630b4cf539739df2c5dacb4c659f2488dc0c0c0c0c0f4947aece52635da73195fc6163b6d250018e6b99c7cc0c0d5d48201169000000000000000000001025bdd0d2f81c5c482011603c0f83b947bbc9768c5ac85f382829c734f88eaf1c9029c47c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f85d947bc3485026ac48b6cf9baf0a377477fff5703af8c0f842a00773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0ee947bfcad3faa7409d61844eb1b7686a31a2d60505ac0c0d4d381a29000000000000000009fd42ad53f53a2acc0c0f9015a947c706586679af2ba6d1a9fc2da9c6af59883fdd3f8dbf847a00000000000000000000000000000000000000000000000000000000000000000e5e4820140a00001000708070801c4fda4f80000000000000000001d22a053233567c9311ca1f847a00000000000000000000000000000000000000000000000000000000000000001e5e4820140a0000000000000000000000000000002d3fef5ee28f276eef401500bfff8c22f84f847a000000000000000000000000000000000000000000000000000000000000001cce5e4820140a0010000000000000001c42813b6a328daa03ecbec86fffc61370a9f1c688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000001cba0ff6987e143035363eecd684681725f471ab8abd92df8eadf4a03ce437f80e0ddc0c0c0f90263947c9f94a79b6b859dfb1d3312fc2311b39f89c677f8dbf847a021694ce8c2469f7002f4eff1ec246e47d9a4df409d03165e44a314c5eabfc02ce5e482011ba0ffffffffffffffffffffffffffffffffffffffffffffffffffd613f667a67ffff847a0671df6dace0af28b1fa15ba77ba2ef3473ec978caa4a238a2a68872648da10eee5e482011ba00000000000000000000000000000000000000000000000010d6ad4c743de45f9f847a0d42ff8000262f173426226e51699ffa2287e636536b0427c4689468cfeea41d9e5e482011ba0000000000000000000000000000000000000000000000000001d21db47288000f9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000011a00000000000000000000000000000000000000000000000000000000000000014a0373991609e5416c4abafff665187355663e47f6712c1c24938357f18f6209b1ca05d51962934d53af948187c9d296d8853c841d6714470bf244e738b103999e10ca06edf5c507081ebffe694b4ac63f611a06793a2d56d719e1a7278228eb5da1680c0c0c0f6947caf266ac3ffb916ade7fb94f965cb42452ffafcc0c0d5d482011f9000000000000000001a3ccb332c986f9fc7c682011f82011dc0f87e947d4e742018fb52e48b08be73d041c18b21de6fb5c0f863a0000000000000000000000000000000000000000000000000000000000000000ba0f5f4b7ea0c109bcb513cfbce575588a57ffd28fdc1df7bab08988234e35ca0cfa0fb44bcdd0398172ec04229ecdf2731caab3b9195751a90735b5969e03b3bac03c0c0c0f8a7947e6027a6a84fc1f6db6782c523efe62c923e46fff849f847a04cf67c78a17bfce7c308c76d72e09d2d09c59c0fedf97f0acc5a40aa4be57211e5e4820109a00000000000000000000000000000000000000000000000000000000000000001f842a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f0947eae43db7afcd5244d6a10209b2778f222b2a433c0c0d3d23890000000000000000001c8fa1564821090c3c2384ec0f4947f06f9a74ed2cf670c8320d5e53c88442dafedbac0c0d5d48201339000000000000000000030721205fac0b9c5c482013321c0f90224947fab4e14e828897c649d2fdbd341688bb0446431f90120f846a00000000000000000000000000000000000000000000000000000000000000006e4e381e9a00000000000000000000000000000000000000000000008970d65c4e029103374f846a0874d03188f0f72622f82b8079e79c30859ab7d6f601d32702617cdbc0c4478e7e4e381e9a00000000000000000000000000000000000000000000008970d65c4e029103374f846a0bd62ba0c1f9014b731e455cfa5dacd23ea7ee805f7bba645eb90e8d67e661e19e4e381e9a0000000000000000000000000000000000000000000a712c2d2f54d7f7551d065f846a0c05d80ee60be459896f795be1fce257ed92856e661742b6c27020a18fa26ad50e4e381e9a000000000000000000000000000000000000000000000408be3aad921ba9a39d0f8e7a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000000aa02ce84882189aca4175fbe260fb4d17ebb462b6ba2b6e29053a9cf24026927efca0aa648bd1a57367eed11f26da60c5d2ca752fe8fd2aa17d8f83a410746396b25fa0b070612fe3f0653f089df472dae55cad389b619ef42af006eb465993ccd4934fc0c0c0f8ee947fc66500c84a76ad7e9c93437bfc5ac33e2ddae9f890f846a0b417fe07be4f12fa0e586f2afc4d165061b10c4a65aafa89cc730b5c05b85edbe4e381aba0000000000000000000000000000000000000000000000000e28df12ab4134c00f846a0dcee52077e6881a673692780ddb6d3689e3e54ed3739777e2c95826a7e607d26e4e381aba00000000000000000000000000000000000000000000000000000000000000000f842a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f294806dd8539ca5be5cacf4dd26829d014141cb1eaec0c0d4d381fd9000000000000000000000b87d76b81e9ac4c381fd04c0f69480ab66f245fd26e5142a1d2a8835400f8f1b059ec0c0d5d482011b9000000000000000000092051447b02007c7c682011b820923c0f902fa94810916a2f5820ae696c34c6e4d2bafc5a4b1da77f90238f845a02a93bec74bfccce482ad341da994c475872b2e6a7991a8a50634edaf76ab69e0e3e206a00000000000000000000000000000000000000000000000000000000000000001f845a0309dd35fc0a466a2ea52797db240a71f633a90b1ce237535b384be3e07416ce4e3e206a00000000000000000000000000000000000000000000000000000000000000000f845a03210768e1ead6fed8d80f20848c72937d8fbf9fc714c11fdb43c5fed3231797ce3e206a0000000000000000000000000497218097ed364385356d84e7c289b6f10b82919f845a05e033968725822e8e540b930e28ad5f45b518194ad0261123b0d4864b653caa1e3e206a000000000000000000000000000000000000000000000000000000000000002e4f845a0a25821d68be1eef419de375b4be0b5a6cc0a9534f460028cafafc36373c56910e3e206a00000000000000000000000000000000000000000000000000000000000000003f845a0b1c5e60e28908bad326ed5638233fc388c12f34a0fdc62ea9eddb37d27a303efe3e206a00000000000000000000000000000000000000000000000000000000000000000f845a0bc878a40eb755143dfedff20e99eeaa4590fcdf92e1651db30a9a080a456a33fe3e206a00000000000000000000000000000000000000000000000000000000000000002f845a0c7e799f3314643c70ed3f6c3d7002938a443ef0bff5d02f4dd43b513f42397d4e3e206a00000000000000000000000000000000000000000000000000000000000000238f8a5a0000000000000000000000000000000000000000000000000000000000000012da0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca04cdc11ac0fec738088f1b94fd91a3abb4ef4fc46d462c8a3ab98e2ed69ed67faa057c34e831b6339d477a7900c1361accaf07bea3374b83acb6e0f2ea078295d61a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f9015494811beed0119b4afce20d2583eb608c6f7af1954ff8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e272a0688a11970000000000084d439be32c3d20950000926e2427f2810ecdf5375e68f845a00000000000000000000000000000000000000000000000000000000000000009e3e272a0000000000000000000000000000000000000d529e534667d446709faa4168835f845a0000000000000000000000000000000000000000000000000000000000000000ae3e272a00000000000000000000508531a38b195f5ad2324cca96530ae1836a0fbcca9d2f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f90114948164b40840418c77a68f6f9eedb5202b36d8b288f8d8f846a00000000000000000000000000000000000000000000000000000000000000019e4e38192a000000000000000000000000000000000000000000000000000000000000016c8f846a086f8800df768673e8d57a3a4ab217bc56c42cdf90c1cdb74fa1ba22cb17b28dde4e38192a00000000000000000000000000000000000000000000000000000000000000001f846a096f4e8ccd696954ad45f78c7d51069e76680215041f7fb23f8724e46e07e78c9e4e38192a000000000000000000000000000000000000000000000000000000000688a1197e1a0000000000000000000000000000000000000000000000000000000000000001ac0c0c0f89f948164cc65827dcfe994ab23944cbc90e0aa80bfcbc0f884a0311ca47c9f29aa77854680a801702c92b0648c49b0c9db20a98bc81682126958a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca04ba33687ddacd057cd141427be2712bc56f5891fc9bca4e09ce17d67f81afc0ba0ad55efbb0ebf9f958bfa260d560c49e9831d25890cd7f7560cbad0d761c0610bc0c0c0ee9481705d166f1978e3368ba6906b98a55a7bdef962c0c0d4d381a09000000000000000000005543df729c000c0c0f294818164f12a47ea39924d1c2ddf1b499425d8fd6ec0c0d4d3818c9000000000000000000000121a98c3de98c4c3818c02c0f0948381f65f5ec4fdd94a5c033fa4261085fbd005cbc0c0d3d27f90000000000000000000110b8bde4b7a75c3c27f0ec0f49483d91529dea3fe969c051e2a04ad99861a979a2ec0c0d4d38199900000000000000000001113c9aded78e8c6c58199820142c0f8ee94840deeef2f115cf50da625f7368c24af6fe74410f890f846a00000000000000000000000000000000000000000000000000000000000000000e4e381f9a000010000b400b400400003cc00000000000000010cc17165840dc00bc8f7da9df846a00000000000000000000000000000000000000000000000000000000000000001e4e381f9a0000000000000000000000000000000000470bc385a96c70693907dad2e98d013f842a00000000000000000000000000000000000000000000000000000000000000004a054cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8c0c0c0ee94842138c437649bf07959efb846402a8cd7c9e8a2c0c0d4d381fa900000000000000000000ed373f38d6d18c0c0f86294845adb2c711129d4f3966735ed98a9f09fc4ce57f847f845a05e7790995149cd394adfae18934f7312b3f381581362ed0c970a982e2f17869ce3e219a0000000000000000000000000d86e1929ca7611f284d9922ca44151c62c485701c0c0c0c0ee948492d79e09413a726bcbec69bf84dd32e4816fddc0c0d4d381959000000000000000000029911687df4000c0c0f09484d76430449a30e20805a52086f711912824340dc0c0d3d24990000000000000000000033a39287f55ecc3c2490bc0f29484f4014c74aa98615b608caa44facf6d03369983c0c0d4d381869000000000000000000000000000000000c4c3818624c0da94851b73c4bfd5275d47fff082f9e8b4997dccb253c0c0c0c0c0f8a994858b4d5df158fdf4b6dcd4b080841b27030178d5f88ef845a0b75cd79c04101231881d96727a22eb83fa5afbbe12b4b417017e2e6210285100e3e238a00000000000000000000000000000000000000000000004f36d35e872098209f7f845a0f9955cf8e6ac8d96ef7f9e8f22b5a8c20accfd3ca6306343edf359ecaa81b37ae3e238a000000000000000000000000000000000000000000008471ea7d2fd6d94c79d1ec0c0c0c0f83b9485b2b559bc2d21104c4defdd6efca8a20343361dc0e1a00000000000000000000000000000000000000000000000000000000000000006c0c0c0f694862f39ed6ea00f778b71ff2a890cd4375f62011ac0c0d5d482012e90000000000000000082d0de1f8785fd90c7c682012e823c41c0f8a99486e9bd5e42a9afde8d9c2594e84e49cc7718f381f88ef845a00000000000000000000000000000000000000000000000000000000000000001e3e207a000000000000000000000000000240236e1adc62a35811064fd8e09743ada0765f845a00000000000000000000000000000000000000000000000000000000000000003e3e207a0000000000000000000000001688a1197000000000000000000822abf050bfebec0c0c0c0f9016e94870ac11d48b15db9a138cf899d20f13f79ba00bcf849f847a0aa1064c618e4cfbba3acd5c01d73df21d593b4d11be600e22373110b1f5f7bece5e482010da00000000000000000000000000000000000000000000000000000000102f44180f90108a011e0f8d0aadf9f352219ddf351f4f9bf8a5767c1343a3539753694408353ef98a0354310daf1ecbc3b5ebca8f24c698ffda90e7edd6ad44ee1b60bfbc5b7fc47e2a09c54ea0b0b4353c90fbf64dd1e3ab5ceccb231a1a7ae6f365bb25cc5078ef67aa0bd476473e43e6965e788ce400469cf7e914dbb45a75364d50674af406ce2d9cda0d124ea604de008f89ecd12b262af761b16737e43a1e4a9abefc3d36362ea9c77a0d4383996bd54bf5a1434192efce65841e94d73322a64458bf3a7a9f6ab57c409a0db1449447e38bb5fc343657c1f8b68100f5e721fa06c8d255cbca0cd71399d36a0ebc9181e74a8f3a8001ad38ca6825ce7098d15c51cdd87de66c832eefccc09b4c0c0c0ee948750701562b7f13be081614a2b875a1a0277c175c0c0d4d381d090000000000000000000599b963b13e2e8c0c0f5948758b4449661306403ef25a5bf359a6dc2782dc7c0c0d5d4820140900000000000000000005e984f016f4b40c6c582014081e6c0f903f89487870bca3f3fd6335c3f4ce8392d69350b4fa4e2f90188f869a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf21f846e208a00000000000220ac8e21ab1ed6a5e99500000000003ad76b37dadf7bc76336513e230a00000000000221173d529d37d3b3029980000000003ad76b37dadf7bc76336513f869a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf22f846e208a000000000002b3fb582a82486cd602e640000000003d32fca4546a285774eaad0e230a000000000002b43f199e039d40e0631be0000000003d32fca4546a285774eaad0f845a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf23e3e208a0000000000000000000000300688a119700000000000000000000000000000000f869a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf28f846e208a000000000000000000001cc5a45af643b00000000000000000000002173f3f33ae230a000000000000000000001cb1ff36a283b00000000000000000000002173f3f33af90252a02175d0d8ef3ab7028ae5221371e123aa082ff9b633d969fafaec5a779f30127ba02a98f2679411c97049eec169ee6f16b3ea7321e5ae243b8379a6618d8484e695a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca07635c6f6fb0dc990d132e97ffe82e07606fac72c3d39da71ac41d6a8564adddaa07635c6f6fb0dc990d132e97ffe82e07606fac72c3d39da71ac41d6a8564adddca0b587e101db980eb9a3d4491a64340bd6e10aa0a7bfd3cc48f4b5cadccf068deda0b587e101db980eb9a3d4491a64340bd6e10aa0a7bfd3cc48f4b5cadccf068deea0b587e101db980eb9a3d4491a64340bd6e10aa0a7bfd3cc48f4b5cadccf068df0a0b587e101db980eb9a3d4491a64340bd6e10aa0a7bfd3cc48f4b5cadccf068df1a0b836b893bd676d88c16cc18e2cd838e74dba8b7bb45a7ea5304999612dd7fd93a0bc2f57311c21670184a5dbcdfc5939827a8f57c97f69166be8694e2ce000cebca0ca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244da0ca6decca4edae0c692b2b0c41376a54b812edb060282d36e07a7060ccb58244fa0cd902be0fc56a89d07a716bd9b7c456d37dc7915414959f0a71c648aabdc28e3a0e87579d97cad2f208f65e0e8103dc581de8b3fa3d6afc16ee30039101baf1cc4a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf20a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf24a0ed960c71bd5fa1333658850f076b35ec5565086b606556c3dd36a916b43ddf26c0c0c0f49488d0e9fd5058114be1df181271fc979beb66dcecc0c0d4d381d19000000000000000000004c17e825ff42ac6c581d18206a8c0f901549488e6a0c2ddd26feeb64f039a2c41296fcb3f5640f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e223a000010002d302d301c102f5ad0000000000003f9931acec966d86bca2361025f5f845a00000000000000000000000000000000000000000000000000000000000000002e3e223a00000000000000000000000000000146183e4b360cf103a7448aaf7059453c3e7f845a000000000000000000000000000000000000000000000000000000000000001c9e3e223a0010000000000000001f2add8a5728d45d38ae18fde00182967820493688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000001c8a0020c72644b6d1ac052ff0fc75ce872138c9c3684978ace385f3ce79ec651e607c0c0c0ef948919c0172774d21e2761406a733ab41a031e29e8c0c0d5d48201129000000000000000000001ab07777a3a8ec0c0f83b948958b1c39269167527821f8c276ef7504883f2fac0e1a08464d39f2e013f742832d01b64507041c699fe53e2b530f6afd3f7345d1a56ffc0c0c0f8509489e51fa8ca5d66cd220baed62ed01e8951aa7c40c0c0e8d233900000000000000000be3e5847d80e4ef6d482013e900000000000000000be3d8ce35ff929f8cec5338330bb01c782013e8330bb02c0f6948a14ce0fecbefdcc612f340be3324655718ce1c1c0c0d5d482013c9000000000000000001fe44384aeb1816fc7c682013c826957c0ed948b4ddd7a804978508c20b1238bde91e1f6f4802fc0c0d3d2729000000000000000002eeee5b0950e77eac0c0f0948b9b00961b8c2f885389918cdbcae213760c367ec0c0d3d2739000000000000000000006df99ee8e55cbc3c2731cc0f2948b9d99cc6997973a450aa5269bb2cd3672b283f6c0c0d4d381b49000000000000000000060addf9d3869f4c4c381b40ec0f2948beb76a4ade7bd4471319845bdd1db6f11013784c0c0d4d38190900000000000000000000fdefa2a0ca445c4c3819002c0f2948d00a749c1e2a66f08b35dcbb8bfb0ab79876123c0c0d4d381de900000000000000000000f4eaa061ab018c4c381de02c0f7948d18d00074dd99de3e3f22210b705b9b0a19835ac0c0d5d4820128900000000000000000d86c246129fa731fc8c782012883011477c0f2948d95149160bae314d8a757c827ed62177c335794c0c0d4d381be9000000000000000000002348bb15b3389c4c381be08c0f2948dbee8f3917049bc30fef01924a7ac79d16cf2b9c0c0d3d21790000000000000000078ebf7dde4b64baec5c41782651dc0f0948dc0567448f21f13965a06e48fdca122f0dae90fc0c0d3d236900000000000000000009803f4382f0498c3c23630c0f1948dfd856af8b868bdc73b4eddbf34511310402c03c0c0d3d2669000000000000000000611035e47f58bedc4c36681b3c0f2948e109b88c7765c3e7502781a5e3f034863df9e97c0c0d4d381c09000000000000000000024a2d9d78e8636c4c381c01bc0da948f4e8439b970363648421c692dd897fb9c0bd1d9c0c0c0c0c0f85d948fffffd4afb6115b954bd326cbe7b4ba576818f6c0f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000005c0c0c0f8ca949008d19f58aabd9ed0d60971565aa8510560ab41f88ef845a0d2a44d6468e40395efc88cb72a54683220fb6bd7f3b8b36c4e7b8a4e72dc8fd8e3e207a000000000000000000000000000000000000000000000003635c9adc5dea00000f845a0e175000ea091183a65fa6ddff418f171c94a1dd9bc4eeca4024d215ba1285159e3e208a0000000000000000000000000000000000000000000004008c038fbe087c01c7ee1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0f294903efd0817e41edef20e24876cfd24b463db4d9cc0c0d4d381ea9000000000000000000000000000000000c4c381ea01c0f29490582a67d18b707ec0e184c3b5fa97c9366e2a5ac0c0d4d381dc9000000000000000000000c8ccd22ebe7ac4c381dc02c0f2949119cc7792dd60e0dfbf6406ae3f6dc2adaeddf1c0c0d4d381ab9000000000000000000041d207ad8d2f69c4c381ab04c0f85d94914d5cb27cb30e80bde8215ff577ed63eb986b79c0f842a05bb716c9a951003d5f36b17b3484e884f28eac0491351d3f9d2d7dcff64fe1bda05bb716c9a951003d5f36b17b3484e884f28eac0491351d3f9d2d7dcff64fe1bec0c0c0f0949198cbdfca77d43053d691651c93c9106dad9176c0c0d3d234900000000000000000018d9488af461a24c3c23439c0f09491c87d02e2d38c440f11c4d9898d15ab8b5d97a1c0c0d3d21d90000000000000000002d4f777c6531d3bc3c21d16c0f39492b6b4bccb093d917c7934c621d3c34b7a240ae3c0c0d4d381879000000000000000000000000000000000c5c4818781b0c0f8ab9494314a14df63779c99c0764a30e0cd22fa78fc0ef890f846a04f101767299382d1c021d3cb6e9f8c5a041bdd4e7b82c383efc77b84e54cd198e4e38182a000000000000000000000000000000000000000000000001f1d929a4c6c01c1b2f846a0a873c99d3fefd4d3f899789b76a5d82cc5f4c93b381f4b83732b34cebb721f85e4e38182a0000000000000000000000000000000000000000000000016eddf90569829dc00c0c0c0c0f90a5d949503a25e9be822d6ab0f6cf5401c1cb2c8cef7afc0c0c0c3c26e01f90a3ef90a3b6eb90a37608060405234801561000f575f5ffd5b506004361061004a575f3560e01c80630d9019e11461004e5780638467be0d1461006c5780639642ddaf14610088578063cc1f2afa146100a6575b5f5ffd5b6100566100c4565b60405161006391906104b8565b60405180910390f35b61008660048036038101906100819190610508565b6100e8565b005b6100906102cc565b60405161009d91906104b8565b60405180910390f35b6100ae610448565b6040516100bb91906104b8565b60405180910390f35b7f0000000000000000000000003aab9b3145df48a22ae337d50d1d66a3b2a3bda081565b5f811161012a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101219061058d565b60405180910390fd5b5f7f00000000000000000000000026d85a13212433fe6a8381969c2b0db390a0b0ae73ffffffffffffffffffffffffffffffffffffffff166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610194573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101b891906105bf565b90505f5f90505b828110156102c7575f600182846101d69190610617565b6101e09190610617565b90505f3343846040516020016101f8939291906106af565b604051602081830303815290604052805190602001209050807f00000000000000000000000026d85a13212433fe6a8381969c2b0db390a0b0ae7f0000000000000000000000003aab9b3145df48a22ae337d50d1d66a3b2a3bda0846040516102609061046c565b61026c939291906106fa565b8190604051809103905ff5905080158015610289573d5f5f3e3d5ffd5b5050827fbe084de9f7dee36e14cada510169063ee31eab57fcf89573237491b9730ecd8f60405160405180910390a2505080806001019150506101bf565b505050565b5f5f60017f00000000000000000000000026d85a13212433fe6a8381969c2b0db390a0b0ae73ffffffffffffffffffffffffffffffffffffffff166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610339573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061035d91906105bf565b6103679190610617565b90505f334360405160200161037d929190610783565b6040516020818303038152906040528051906020012090505f817f00000000000000000000000026d85a13212433fe6a8381969c2b0db390a0b0ae7f0000000000000000000000003aab9b3145df48a22ae337d50d1d66a3b2a3bda0856040516103e69061046c565b6103f2939291906106fa565b8190604051809103905ff590508015801561040f573d5f5f3e3d5ffd5b5090508093505f7fbe084de9f7dee36e14cada510169063ee31eab57fcf89573237491b9730ecd8f60405160405180910390a250505090565b7f00000000000000000000000026d85a13212433fe6a8381969c2b0db390a0b0ae81565b610248806107ba83390190565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6104a282610479565b9050919050565b6104b281610498565b82525050565b5f6020820190506104cb5f8301846104a9565b92915050565b5f5ffd5b5f819050919050565b6104e7816104d5565b81146104f1575f5ffd5b50565b5f81359050610502816104de565b92915050565b5f6020828403121561051d5761051c6104d1565b5b5f61052a848285016104f4565b91505092915050565b5f82825260208201905092915050565b7f436f756e74206d7573742062652067726561746572207468616e2030000000005f82015250565b5f610577601c83610533565b915061058282610543565b602082019050919050565b5f6020820190508181035f8301526105a48161056b565b9050919050565b5f815190506105b9816104de565b92915050565b5f602082840312156105d4576105d36104d1565b5b5f6105e1848285016105ab565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610621826104d5565b915061062c836104d5565b9250828201905080821115610644576106436105ea565b5b92915050565b5f8160601b9050919050565b5f6106608261064a565b9050919050565b5f61067182610656565b9050919050565b61068961068482610498565b610667565b82525050565b5f819050919050565b6106a96106a4826104d5565b61068f565b82525050565b5f6106ba8286610678565b6014820191506106ca8285610698565b6020820191506106da8284610698565b602082019150819050949350505050565b6106f4816104d5565b82525050565b5f60608201905061070d5f8301866104a9565b61071a60208301856104a9565b61072760408301846106eb565b949350505050565b5f81905092915050565b7f74657374000000000000000000000000000000000000000000000000000000005f82015250565b5f61076d60048361072f565b915061077882610739565b600482019050919050565b5f61078e8285610678565b60148201915061079e8284610698565b6020820191506107ad82610761565b9150819050939250505056fe608060405234801561000f575f5ffd5b50604051610248380380610248833981810160405281019061003191906101a4565b5f8390508073ffffffffffffffffffffffffffffffffffffffff16631249c58b6040518163ffffffff1660e01b81526004015f604051808303815f87803b15801561007a575f5ffd5b505af115801561008c573d5f5f3e3d5ffd5b505050508073ffffffffffffffffffffffffffffffffffffffff166323b872dd3085856040518463ffffffff1660e01b81526004016100cd93929190610212565b5f604051808303815f87803b1580156100e4575f5ffd5b505af11580156100f6573d5f5f3e3d5ffd5b505050503273ffffffffffffffffffffffffffffffffffffffff16ff5b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61014082610117565b9050919050565b61015081610136565b811461015a575f5ffd5b50565b5f8151905061016b81610147565b92915050565b5f819050919050565b61018381610171565b811461018d575f5ffd5b50565b5f8151905061019e8161017a565b92915050565b5f5f5f606084860312156101bb576101ba610113565b5b5f6101c88682870161015d565b93505060206101d98682870161015d565b92505060406101ea86828701610190565b9150509250925092565b6101fd81610136565b82525050565b61020c81610171565b82525050565b5f6060820190506102255f8301866101f4565b61023260208301856101f4565b61023f6040830184610203565b94935050505056fea264697066735822122076ef473b0cfdf9e6d581bdeb8885c20099c7a8a3bb8b83dcb25312e2027dc07064736f6c634300081e0033f39495480d3f27658e73b2785d30beb0c847d78294c7c0c0d3d20890000000000000000124a4d48fd5caaa00c6c5088301b9adc0f9015a9495ad61b0a150d79219dcf64e1e6cc01f0b64c4cef9011df845a00f35726ae3bff83fcad6995b6195b9a193e30855073b62d25fc1fd35ef7df12ee3e272a000000000000000000000000000000000000000000006a5cfdc0bff9a38060d24f846a0a75c56f09d8a96b55ae79285e0fe580b0ec76014fa22878b1db9aafad18d72fee4e381b3a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff845a0cf7d6310d012a42a76bb7228a6cf07ba28907b0ab736158bda4c3607018527b3e3e272a000000000000000000000000000000000000000000008ff169f56a6aa38f1b570f845a0eeea55bf32804ce36029867ad77ef16d33e74d0d1361b89a2534a5c497f47caae3e272a00000000000000000000000000000000000000000926e2427f2810ecdf5375e68e1a0eeb55c3dd980302982531d70b1334b544705f58d48d8c8f429429486cd476d59c0c0c0f902589495ae252633e9ea03bdfe67874b349b41163464cef88ef845a08e882feac761dc10ca2a0ec95a04947da660d6ff1895536a2c2cdde330fdfe5fe3e207a000000000000000000000000000000000000000000000003635c9adc5dea00000f845a09851164991f620a3b2d19d9cbad2beb69def2e6a66b85b309cfcd70612fd8d27e3e207a0000000000000000000000000000000000000000000001423fe1609d86175aee1f901ada00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000011a00bdfc17ceca3651c0f5ba28bbd281f2ae1b13186b756b39c53be6b3eaabf715fa0213d086b2fb4056a25ca6c8e4d04b7d7a6520ec5b56775df68dbf722c19d40bea0618059526d7d1bb5608c8e3a0740d1f656fa8a764ecca600a8e0e3e0c313ce66a06b0c010fbb9ec388ddd2f1cbf436c72ca3f9707d1acbe4ffdd60d7adac0f26a2a0812dc1b11d9d9ef2e86ff8ef8e28edce2507c887ff5b798551117d154f8e0a54a08c2000c1017e62d1ecc66de504b7ea2ef33ea67838f32342be49c748b99672bba0cce0021b97ad796f1fd12e80abd48c106dc9f8e7cccdc84c8ffa0f36635252eba0e90c6be031f8a055c6f812e34adfc65c81aa64866330e1e1d825ad44c1d295f1c0c0c0f29495cbda20bf1885492252c1820cb17bfc050b739ec0c0d4d3818f9000000000000000000000000000000000c4c3818f02c0f9014f9495dbb3c7546f22bce375900abfdd64a4e5bd73d6f88ef845a00000000000000000000000000000000000000000000000000000000000000000e3e210a000010000330033001b00057e000000000000000112a7597eea0a9d1b7d468351f845a00000000000000000000000000000000000000000000000000000000000000002e3e210a000000000000000000000000000000000020062a65ceed16e9793da03e3f2c7a8f8a5a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000023a00000000000000000000000000000000000000000000000000000000000000024a054cdd369e4e8a8515e52ca72ec816c2101831ad1f18bf44102ed171459c9b4f8c0c0c0ed9495f20c5dfab020a1ac6180b65b7e75e72a1008e7c0c0d3d21990000000000000000000044364c5bb0000c0c0f2949683495e5bbd30d741eddd3f972d7ae5188423b2c0c0d3d265900000000000000000412b3709642c91b1c5c465828faec0f850949696f59e4d72e237be84ffd425dcad154bf96976c0c0e8d381f39000000000000005edeb87ba97a500fa55d381f49000000000000005edeb8748f56925eabacec681f3837a139ac681f4837a139bc0ef9496c195f6643a3d797cb90cb6ba0ae2776d51b5f3c0c0d5d482011b90000000000000000178c3022cf7207236c0c0f85d949759a6ac90977b93b58547b4a71c78317f391a28c0f842a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002c0c0c0f094976650814731b98f7afefdb57b015f31205fd2bac0c0d3d26490000000000000000000014bb4c514b970c3c26402c0f6949766f924c835f49ab122b086bf59ae6416c292c7c0c0d5d482010a9000000000000000000144dfa330804804c7c682010a8201acc0f83b94985462c9aa4d6c3ad59ae6e1e9c0c11347ed1598c0e1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0f8ec9498c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5cf88ef845a00000000000000000000000000000000000000000000000000000000000000036e3e208a0000000000000000000000000000000000000000000000000000c98cc84d5f0ccf845a03b05d2bff73ff12707cb4ac03597c8d6176c69b849b8f2ebafececb921a3c116e3e208a00000000003ad76b37dadf7bc76336513000000000000000000003e1dd2784022f842a0000000000000000000000000000000000000000000000000000000000000003da0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f87e9498c3d3183c4b8a650614ad179a1a98be0a8d6b8ec0f863a038cb777087d30c725da4e6f250353cdec5e3824ff4666b967bf755b67c55b0a4a038cb777087d30c725da4e6f250353cdec5e3824ff4666b967bf755b67c55b0a5a085b379096c46275e23a78d31780a35a3347db19cf17aa35c8f6b6325dc2d31cec0c0c0f902b29498db502215da1ad9f626d4a0090a8a2f4971003cf901f0f8afa0d01a293d013d5426dd5cc2a98a194f6994ad72d277cc3a5942ea20b2a409b465f88ce25aa00000000000000000000000000000000000000000000000004e2e006c867ee400e25ba00000000000000000000000000000000000000000000000009c5c00d90cfdc800e25ca0000000000000000000000000000000000000000000000000ea8a0145937cac00e25da000000000000000000000000000000000000000000000000138b801b219fb9000f845a0d01a293d013d5426dd5cc2a98a194f6994ad72d277cc3a5942ea20b2a409b466e3e25aa000000000000000000000000000000000000000000000000000000000688a1197f8afa0e0fa9bdcea0c363399173621ce766be19c26afa0a2c80805b59df661ef0b696ef88ce25aa00000000000000000000000000000000000000000000000004e2e006c867ee400e25ba00000000000000000000000000000000000000000000000009c5c00d90cfdc800e25ca0000000000000000000000000000000000000000000000000ea8a0145937cac00e25da000000000000000000000000000000000000000000000000138b801b219fb9000f845a0e0fa9bdcea0c363399173621ce766be19c26afa0a2c80805b59df661ef0b696fe3e25aa000000000000000000000000000000000000000000000000000000000688a1197f8a5a03e0b7bd22739b28f4246949d0ae401774af42657d89995345148ada71a16daf0a0d01a293d013d5426dd5cc2a98a194f6994ad72d277cc3a5942ea20b2a409b467a0d01a293d013d5426dd5cc2a98a194f6994ad72d277cc3a5942ea20b2a409b468a0e0fa9bdcea0c363399173621ce766be19c26afa0a2c80805b59df661ef0b6970a0e0fa9bdcea0c363399173621ce766be19c26afa0a2c80805b59df661ef0b6971c0c0c0f85d9498f3c9e6e3face36baad05fe09d375ef1464288bc0f842a00000000000000000000000000000000000000000000000000000000000000000a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f83b94998c061820903de9af6d950eddb978af328c8326c0c0c0c5c482011f01dcdb82011f97ef0100411d38d27f6f2c7f3b70ff29dada64cbd7bfa9b2f29499d5edff0165df808eec380f3d1932325f55fd46c0c0d3d2759000000000000000002e83bd87bfccfa7fc5c47582013ac0f2949a0a11c3a1827baa2f8187658a5ebc6b611c2a53c0c0d4d381e79000000000000000000000000000000000c4c381e701c0ee949bc478678f97c82bc9be301464acf5c9c4ce8f01c0c0d4d38199900000000000000000000144429cd96067c0c0ef949c1a360c220bcd53c2aad612d17210f7aaf5ffc0c0c0d5d48201269000000000000000000001085e7e748740c0c0f863949ca8530ca349c966fe9ef903df17a75b8a778927f848f846a0f0ef4cdcc1cb5911848e4349c1b3c92225ab10baa3b6a42bcaf6d713c4b7ccc8e4e381cba0000000000000000000000000000000000000000000000562e369224bd4000000c0c0c0c0ef949d320f5c353b38064451b026210dcc0af42a00ccc0c0d5d482012b900000000000000000004216fb196a178ec0c0f87e949d39a5de30e57443bff2a8307a4256c8797a3497c0f863a00000000000000000000000000000000000000000000000000000000000000006a0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ec0c0c0f901a5949dfad1b7102d46b1b197b90095b5c4e9f5845bbaf90168f846a02d75c38ca86ceda630251d44cb2f708dea1a3b58cfb2649eb61bf2a3d131ee57e4e381bca0000000000000000000000000000000000000000000000029b004d1a9d3640000f846a03b0d4f93a460bf9a01f865b6a20338ca4f15cd68ff19d4134589bf608c7139bae4e381bca00000000000000000000000000000000000000000000000000000000000000000f846a04ae535cd77c06b26ce05139418f8abc4169f06c338bcd47f7680680a58ec5d9be4e381bca000000000000000000000000000000000000000000000003635c9adc5dea00000f846a08e01c616c341f0cf08a4dcc7360397f6a73ca88251123c5a9dfd08f8bafddfc8e4e381bca0000000000000000000000000000000000000000000000010f7bf61a7a9440000f846a0db305dca8f6e2c25995b0dbb821239c2a8ad96b78ab221b9d84687bcbcc60d3be4e381bca000000000000000000000000000000000000000000000016ebb3c097e6d940000e1a09d199992ad03b84d8843ae23ba7165e8448dcfe6ac421eef22f0eb1e6258752cc0c0c0f1949e19bc2a0170a58634dc4bbecf4b5df64dc1b912c0c0d3d27c9000000000000000000000000000000000c4c37c81a2c0f83b949ec6f08190dea04a54f8afc53db96134e5e3fdfbc0e1a0c6521c8ea4247e8beb499344e591b9401fb2807ff9997dd598fd9e56c73a264dc0c0c0ef949ff0a00cf9d3a7949df87ea58311bb1c5328d9c2c0c0d5d482012a90000000000000000000c56bed013ccc3ac0c0f8ca94a0b73e1ff0b80914ab6fe0444e65848c4c34450bf88ef845a08b25b2d179e99dc765952487fa4ea9aa95337a18ccd74edd852b419f7ea48c81e3e26da000000000000000000000000000000000000000000000000000000018a3ce9e40f845a08f060a7e8fbfb20e8b3f45562d6bedf7d45f0ad0ebd2a48934eaa558cfd6fd54e3e26da000000000000000000000000000000000000000000000000000000cd37a84dcfbe1a00000000000000000000000000000000000000000000000000000000000000004c0c0c0f91d3c94a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48f91a48f847a0000000000000000000000000000000000000000000000000000000000000000be5e482011ca00000000000000000000000000000000000000000000000000091e0c5640cbd67f845a00403a8ca3a9903ca93cf71f6768e67d9c7686c7c285e94db9a93d79fffb1e6cae3e26fa00000000000000000000000000000000000000000000000000000001301e28bc8f86ba007081a045c3dbf2e63b62a407ef205e7586e2629d2e2b95ff093308ca0ff3727f848e381b5a00000000000000000000000000000000000000000000000000003338fe2bbc60ce381baa00000000000000000000000000000000000000000000000000003338f65724d74f845a013b47f23d2a1108525541d5ea06db599b85218b4099197240b2ce242653286b9e3e208a0fffffffffffffffffffffffffffffffffffffffffffffffffffcd032ea9aebc5f845a016f44a28305a3de6619326825df2d5b9f237445dc76089ca6a36cbc218c84016e3e228a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffe8435db6bff845a01b0c0f0159019b48c57aa1c9a5c120b3f8b13f80cdd673344a5443e1525d4f57e3e22ca0fffffffffffffffffffffffffffffffffffffffffffffffffffff6f03d0aeefff846a01b91f984d257abdb09c24c60317087b94480f5b6f25cc1e79ea4d913f4c4d2b9e4e381a1a00000000000000000000000000000000000000000000000000000000000000000f845a01cfe18bf93cdf05690b0062c9de5d226d3a2c24e01f06d145cf8b3e2daf50237e3e229a0ffffffffffffffffffffffffffffffffffffffffffffffffffffcff2f9f5f71ef845a01f21a62c4538bacf2aabeca410f0fe63151869f172e03c0e00357ba26a341effe3e223a000000000000000000000000000000000000000000000000000003e6a2fc77b83f845a025336f8c6add5ee5dffdc2d268b04933e2b26aecf651507285be25d3f2120dc2e3e270a00000000000000000000000000000000000000000000000000000000000000000f847a028bdbd20214d5db5a524544a9daae0b25f2b7fa90ef4687010ee5b1212ba34e9e5e4820121a0000000000000000000000000000000000000000000000000000000025f0ac98df846a02aba44f4280c6c9754570434b3b2d1846bd3de45fdf03af4031deb742b0982e3e4e381b4a00000000000000000000000000000000000000000000000000000006152c7572af846a02b4f5bc1e37313a2e9c7f897fc88d97382bb24e64f7a038ea443cc53fe46b94be4e381a1a000000000000000000000000000000000000000000000000000000091b8f74851f847a02bed307e395012c0177f428c356a6332ecc58ce972eff3d36392e192ec2bfbc6e5e482010ea00000000000000000000000000000000000000000000000000000000701e7a332f845a02c2e75085c617e01c003f77162c742100495af062d8ccbd8ad0f00c43cc7878ce3e229a000000000000000000000000000000000000000000000000000000000000359caf845a032b8c3e0163a3eceb0dd8f4a69ff6a8f786c1f0b2824e29def2a833031a4d374e3e207a0000000000000000000000000000000000000000000000000000000dfd1a01798f869a0368c3f5f03e5634b3e4381c9c3caac98f4d254c7027fd14b53436c90d060fef4f846e208a00000000000000000000000000000000000000000000000000001cc5bbf83ca9de230a00000000000000000000000000000000000000000000000000001cb216d3e8e9df847a0388062d5a9f733d43b8c03df09117064fe91d11c1b44351b834d8b3c3d0c8ac0e5e482013fa0000000000000000000000000000000000000000000000000000000065bec0c00f847a03b9552a383c613359f42e8efcad3d02a7d52b94c1f5a55810d70edf307f4e4a7e5e482010ea000000000000000000000000000000000000000000000000000000000396636c0f845a03d0c86eb34c0568c1be30a27ea167721c77bcfec1f1d2867daa03c9441f0c811e3e274a000000000000000000000000000000000000000000000000000000006a4dce33af846a0405c3165962566349997e7ade88f32fa21fe1e88f7929ccd59085e2bb237e1b3e4e381d7a0000000000000000000000000000000000000000000000000000000001dcd6500f847a043cb05957f9185d0b7cbe5f806b641971b2b7b2225fca1f7edfcc6d8ae5c5b59e5e4820142a0fffffffffffffffffffffffffffffffffffffffffffffffffffc3a2a6d16b2eaf846a0446604e397083529c53ad480b1c55633cac95c735b2eda7f254bd3b3030e83cbe4e381d5a000000000000000000000000000000000000000000000000000000000f42cbaacf847a048cd8e013feae6dd6853385c1d31fe4df90230e5ec726300d9f5e34b7b15e13ce5e482011ca0000000000000000000000000000000000000000000000000000000000010bc05f846a049485e14c2b5c1d2611f08a6dcbd51a2c4953444e6d78fbb7f0ce6ce635ea868e4e38189a00000000000000000000000000000000000000000000000000000000000000000f845a04dbb2215e3ad761c55be3ba0e131fa99e8c058060b10bc090c66aefb0b7791dde3e208a0000000000000000000000000000000000000000000000000000025fe9a9330f4f845a04e4c0dac904fd59242813530a7881d473265c091b3208a3775628deb31615018e3e22ea0fffffffffffffffffffffffffffffffffffffffffffffffffffffff8e1ef549ff846a04f9d5bbda28e579b0d753b7ca4b799524362c18ae4f3d8686597de0649319b74e4e381baa00000000000000000000000000000000000000000000000000000000144c10238f845a0573dc4942e986ed220476310bfc2963e22d785708d7092254e761a8870a434b7e3e210a00000000000000000000000000000000000000000000000000000001c3af93a28f845a0579daf8760b73f773247e46549d90262f85d3f88e0791ea09dc90982358fcc5ee3e227a00000000000000000000000000000000000000000000000000000000000014aa6f847a057d18af793d7300c4ba46d192ec7aa095070dde6c52c687c6d0d92fb8532b305e5e4820120a000000000000000000000000000000000000000000000000000006658bc106dfaf847a05918522d81a707d247543fa58a114c8631bbc4b976e099438ead7e1df9c55182e5e4820142a000000000000000000000000000000000000000000000000000000036cd903e30f845a059e52fc8e98ff9c9372e71ea7a66e3a0aa36468dc746b4160ab339c47e931566e3e208a000000000000000000000000000000000000000000000000000000014d1af62b5f845a062ab965db00f05dbcc7d35fd84c307563fc8904ac2d05ec4912622d5c269c16ce3e22ba00000000000000000000000000000000000000000000000000000000000000000f846a06377367ae5d683c63df1fd56270a0926d0a7467373c17b9a3773ff054f1971c0e4e381b5a00000000000000000000000000000000000000000000000000000000005defda0f845a06849d239d5844775b16c52af3b2b9616758170cbbfbfd2131d698ddea030ad48e3e26fa00000000000000000000000000000000000000000000000000000001292909ceaf845a069ea9dcf41ae69de972396868946ce09924d9df2df7677ae366da949f2678de2e3e270a00000000000000000000000000000000000000000000000000000000442f832dbf845a06a7b5495a5f5e82a088149d7fd806b94bc72cb8b3b6eb11f4bd1c3c086526396e3e207a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffede07d9b05f846a06a9ce6256c4c8fe5291cb1967fd41fd30d5c662227f36fbdd306bff2e8435efbe4e381b4a00000000000000000000000000000000000000000000000000000002c3ce1ec00f845a06bc64cb6c48d697e2319078d295b7393e104818737ad694bea9b928cdcbaabd5e3e261a0000000000000000000000000000000000000000000000000000000000632ea00f846a0703224e8552824c017c9a6a7af77dc93ae940ab9a40480c64f63d12f2214b3bce4e381a4a00000000000000000000000000000000000000000000000000000001a842ac8e7f847a07599c49598f9115df88ff41af1ae9d8c38b52091cf63f46c737a6634939cbd7ce5e482013fa000000000000000000000000000000000000000000000000000000073f0718965f845a07d0ee6848e9c0a6e4d77f6a7041dbfaf7208e7d7650c46bafd6a635d1ae04ea6e3e220a00000000000000000000000000000000000000000000000000000000001c6906bf845a07f155bee5979bf810991e861eafb8119cf00cc265482cfd4b004eb742dc393fee3e261a0000000000000000000000000000000000000000000000000000000000637cc45f9013ca082fc8338773fc4426092c36a88f1631578741710523a800748804783a20f57a2f90118e227a0000000000000000000000000000000000000000000000000000089e54e9ee54de228a0000000000000000000000000000000000000000000000000000089efd9c1e64de229a0000000000000000000000000000000000000000000000000000089fe0893090de22aa0000000000000000000000000000000000000000000000000000089fe0c8dea56e22ba0000000000000000000000000000000000000000000000000000089fe79158b2de22ca000000000000000000000000000000000000000000000000000008ae74dba9b2de22da000000000000000000000000000000000000000000000000000008ae753b07c2de22ea000000000000000000000000000000000000000000000000000008ae7746e4a5df88da088483bf000a1009d5df96db9349a9e2c3b91af50be04719cbc643daad6e36e36f86ae264a00000000000000000000000000000000000000000000000000000005d551fbcfae26aa00000000000000000000000000000000000000000000000000000005f4e806aaae38189a00000000000000000000000000000000000000000000000000000009287863446f845a08bfa486c82ae4ad4f79ac4b5235f23d15408d726e237b266f693aa4dd66bf9dde3e220a0000000000000000000000000000000000000000000000000000078f926369f8ff845a08ec66e8cc73ea976ba36e242b81bb6837eea609cb224831f21e15e16cd61ac84e3e22da0ffffffffffffffffffffffffffffffffffffffffffffffffffffff2ff7240df9f845a0912c5c1e6f999b60f96feb06dcc847403c149facada9563b85e42f1e4c74e44ae3e225a0000000000000000000000000000000000000000000000000000000001e8b6fd2f845a09254cb65314db3d2d7ca17f753f1d9c7f1b6fa05111d18d10ed5b9519d1b247ce3e208a00000000000000000000000000000000000000000000000000006af725ba583dbf845a09480b08a056d73ea26cb86f728aee215dc077e9979e7a8a973e055f2bbe76d66e3e202a0000000000000000000000000000000000000000000000000000000190827c786f845a095a8b87fafd40fb07c9ba218831195b85a3b0341a782d6aaf81c2889fa577cd9e3e227a0ffffffffffffffffffffffffffffffffffffffffffffffffffffe5b83d2cd4fdf845a0985e01148b0463093183b86a09c5b603149b0ae0b41e72d8e62756e4665d1e62e3e210a00000000000000000000000000000000000000000000000000000001741e53810f846a099be877cdaeaa967ecefd0a4dc1a8f04df06b7b6f1fe33a208faf3606a8dcc10e4e381a6a0000000000000000000000000000000000000000000000000000000007df4e907f846a09c7c5d9cd3606cac3a7f2ea7dd3b916bfadaf43a5ab2fbe69d11fc36dc112d08e4e381a6a00000000000000000000000000000000000000000000000000000000029f28680f845a09e1447541c5b5d2520d0edad47ed9d2498027eacee92438c675d79875388b9f1e3e207a0ffffffffffffffffffffffffffffffffffffffffffffffffffffffa7bf093012f845a0a55e3ee1a2cf8165d514ca1a10b7023d3f578fd115a81ad42cedd4ea26a94bfee3e251a000000000000000000000000000000000000000000000000000000010d75f2e6bf845a0a6ad7d9647a4af31649116ec7f8ae68fe377968100f32dc76d4023b11b67d8ebe3e228a000000000000000000000000000000000000000000000000000000000000003e8f845a0acec6a9dc14aa6f92f59567b8f97d09140452ddae6f906dbf533b40ac75d02eee3e264a00000000000000000000000000000000000000000000000000000000000000000f845a0aea84ce797a9726e2eb216af35aff86089a97682167e4727f8eaf73f6c05ada4e3e26aa00000000000000000000000000000000000000000000000000000000000000000f845a0aeafead97bed83438c2afcf7a89b579bc8a372420c97e4e21414f62d9b8efb3de3e225a00000000000000000000000000000000000000000000000000000000000000000f845a0b5340454f0e9164591d809f517e96a2c8c3dc6f928aa8d3e26ed909435286f53e3e274a0ffffffffffffffffffffffffffffffffffffffffffffffffffffe1f46b23e8fcf845a0b8201eb34c3d7359dd6941d52f94f4c2068619b673d1fb8dc9e43aab9295c673e3e22ea00000000000000000000000000000000000000000000000000000000000000000f845a0b9f6591fc52eba3e15c8a35522a85e6d320e9084debe30399ae4770e63f89389e3e224a00000000000000000000000000000000000000000000000000000040933dd10abf847a0bb77b8825753962eb316185bc7456a8e44c6191463d4e3d1fe1a07646764a3f7e5e4820121a0000000000000000000000000000000000000000000000000000000000ec82e00f845a0be18d092cdba38c47e9610abe635ec24394c9e691d55f0861c9255ed64ddd2bfe3e230a00000000000000000000000000000000000000000000000000000013a52453c00f845a0bfb47d0454d8de9b5c2a21355845088b948e43ac0cf66ab853b4d92ab163bc5ae3e22da00000000000000000000000000000000000000000000000000000000000000000f845a0c5dd1caf4fede68327b6fb815283538b7291ab366354d84166fdd110d5b597f3e3e274a000000000000000000000000000000000000000000000000000000000b6f8cb70f869a0cc236083e86ee3df0f3160002f381f1404bd44c4dec1322196f34d52548202f5f846e207a0000000000000000000000000000000000000000000000000000000001d78357de208a0000000000000000000000000000000000000000000000000000000001d78357af847a0cd1718b39694c8b71b5b7030a9f4416a979d5bffcde56263723356d0f75e4776e5e4820142a0000000000000000000000000000000000000000000000000000001699c721b2cf845a0cdb7651d4e5401de141afb114df033f0f4f3fdf0bae5bbf312c8baace8a83c9ce3e22aa0fffffffffffffffffffffffffffffffffffffffffffffffffffffffff2b0e68ef847a0d24a61e94450386b8c40fa9a314eda432022998ab4730a824532e129094486bce5e482011ca0000000000000000000000000000000000000000000000000000168f0346040fff847a0d3c7bccbcb9a38b179e4ff6c239fc7b692bc0144074f5c8371ef03ace723161fe5e4820142a0fffffffffffffffffffffffffffffffffffffffffffffffffffd9bc5b8ee0e2cf845a0d6cb24c70e488b7b7eefc44fce798fabbf91f0ceeb167552e13e71816a239969e3e22ca00000000000000000000000000000000000000000000000000000000000000000f847a0d7c2ca1b2001ebdfb058bef6a6231a3c83cff193a748eecbefc3e36fdd8bc0d6e5e4820125a0000000000000000000000000000000000000000000000000000000d3595eb4d2f845a0d98c4f2afefeb3cf39eaf17e664c256211e9c21f9f4996ad98a8b1f55237590fe3e22ba0fffffffffffffffffffffffffffffffffffffffffffffffffffffffd1c9b4469f845a0dd21eaa2a7b91d700e186a088ec24a6ffae451cbb867f014da63e9784f6a5049e3e210a000000000000000000000000000000000000000000000000000000000005e7519f845a0df6b2ab211358052d6a681774c49d8a6e0bfaf0fdf575141826340ff9dbb5823e3e251a0000000000000000000000000000000000000000000000000000008f50f675d0af845a0e455a23b9bd8cf2e61da796ba2fcd25d4308f14e1467e151b5f748657ffc8c3ce3e22aa00000000000000000000000000000000000000000000000000000000000000000f845a0e5b3177dfa02823e57fc5c907cd9f3aa3ca557747f255abfad1faa15be6adc16e3e202a0000000000000000000000000000000000000000000000000000000c9c55130daf845a0e9a042b510c4a11346b2bb8943779a6be59156b3193c52e4e1d3b52c400ec46fe3e210a00000000000000000000000000000000000000000000000000000019e686509d8f847a0eb7cc7a75a8aa77a2cf7ab48f7be4a9a78f7adb81d3a7e395f3dcef71cda1bc9e5e4820125a00000000000000000000000000000000000000000000000000000000000000000f847a0ee6f9ab52d4876837d498b3cbcfe9ffbe6fb057563bd0fe5d3db71854ac6219ee5e4820120a00000000000000000000000000000000000000000000000000000036485e3e8a9f86ba0f44681571ddec7a61b19173091fe5695116de851b72e26704b9c0765af0929e7f848e381d5a00000000000000000000000000000000000000000000000000000a574331ad957e381d7a00000000000000000000000000000000000000000000000000000a574154d7457f869a0f56408d23e6790fec5453738cf042a4a3ef7ec36e9ceae8978e4ffce8e903bc3f846e223a000000000000000000000000000000000000000000000000000000b10024def30e224a000000000000000000000000000000000000000000000000000000b1258208d5ff846a0f99328222adaeb11584f5a4c4ebb1641966b73a9a615ca1590569555a0eaffffe4e381a4a00000000000000000000000000000000000000000000000000000000001036640f845a0f9d167372ad3ed68278b8207f5ac8f4771f1fd52f662e287847e8f1b743684aee3e207a0000000000000000000000000000000000000000000000000000000000b7955aef902d6a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000006a00dd5d254edfad4a1e855ecc27eb0259904a06513d6c6c00150e3838c28855f61a010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba0115839bfee55578761c7edec3b2ff273385ed4d5df39b75ab78870ff966c69b8a01f1a80db9a82d9fd4ea3322b7d2dcad52fb678229d487c4bb44e821a2544709fa02209089ff8bd3b7bac6ff21ec014fbd229af53b4ecc03dcfbd102128759cb6c8a024bd6c6f7d1eeabad95a250d5ab0bbeaae9c7223e36ba7b50aea221573ce7122a0260b0b4579836006fdc9488505d30b092b206f5c35a9e8a198bfc75f36f18ce0a02ea87998dc80e6571fcfa09535ee2fe763d2a4d8fe160db740860905088012f2a03c2304a4bd8ffa93cccd3b8e4c28dbcd5c94236b1a7b40e3dc80ba6953b4cc7aa046f92c9c87fd0acc2c0b8d6eafa5c91e008e40b8eb918f129e106a10acdd5f00a048cded43b819614416e7748119a4c9311836b0c62ef8044c4aa24e9ac91548ada05b60a69638bcb2dd73cec9b8e9eea3257269241aa3171fc39b72bec82367040ea0659f5b53123b3e7a886575e106645d4d7c5af120440f3f409542b3987fa1ea07a07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a0aacfabd8359c7d556c0806d682fd47987dffc8442f69a0ee2211e786298eda53a0acfa85f8dca23f171b19bf4838ca8f35894504326d0e8354507c4fd5f1a60701a0ad83b682faf556b6f35037f886f1baa3824496554eaf6a410686373783f1ac14a0c1e9f6c54e8b284fca05f4d31686794a1ceeb011fe91b9e4cd2e9d58809afc01a0d4833240551f51c64948d4971df43f5379482d5ab5c61bc3e254bd13d086fde3a0d4c2888f964e4f1e878a271ae0188061e68cb48175ccfe7fe23880b7bc1d9b2cc0c0c0f9015494a1444ac5b8ac4f20f748558fe4e848087f528e00f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20ea0688a11970000000000015aad205dc2d47ed100000000703da631a866375521fbf845a00000000000000000000000000000000000000000000000000000000000000009e3e20ea0000000000000000000000000000000003f7a32ae251375185490ddbb357061f3f845a0000000000000000000000000000000000000000000000000000000000000000ae3e20ea00000000000000000000000000bfde57035f1363b6bfd440f452ac631ad88b8f2f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f094a166d72fa029fe68b2e9d8364f9ca88a7814dbb3c0c0d3d24190000000000000000000027285d998e881c3c24101c0f094a193c160aa1a8971eca9490cafd807250791b7c6c0c0d3d248900000000000000000000819e93162c081c3c24805c0ee94a239366d40b724dd2b7f1e5750e82cf4d56aa7c2c0c0d4d3819090000000000000000000930512cabb3c66c0c0f88f94a26148ae51fa8e787df319c04137602cc018b521c0c0f874d2799000000000000000012de2164dc8a58509d27a9000000000000000012e66bfd802b4a1d7d27b9000000000000000012eda5e32b6155e17d27c9000000000000000012f5c1d5bb06eba57d381869000000000000000013033e1a3a82d8fd7d3818790000000000000000131593f491c095557c0c0ee94a2b7d79ef29b966e4ed54f4137177aaff9d4ce89c0c0d4d381eb9000000000000000003a801bf7422e8000c0c0f87e94a34f5e6d1a5c89ff19a8bbc6c7d9314d84954366c0f863a00000000000000000000000000000000000000000000000000000000000000005a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f394a377aa6822603cc52e4e9ed6eaa045c46ef6b3e2c0c0d3d23590000000000000000031a5045898b304c8c6c535830a52e2c0f8e794a3b5efa6b7004cae689ed1019e106a84df9e5060f847f845a00000000000000000000000000000000000000000000000000000000000000005e3e205a0000000000000000000000000000000000000000000000000000000000000017af884a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a04a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8a088c285dbebbd05a382ad5ab67cf5ffc5a2a46a2cb7c6e3e2ada0bb6a2bb716b5c0c0c0f9019194a3ee21c306a700e682abcdfe9baa6a08f3820419f88ef845a0622ae4d47e6dceb758d5c31172f2ca5950fcd50f7be12b1b577122e5a84f24a6e3e226a0000000000000000000000000000000000000000000008fb013022155f7d69158f845a0720891d7585a0424ee35660cc9ec9a28cb4e4cee1d8e426c15e60acd7f3a19d0e3e226a00000000000000000000000000000000000000000000000af993d819067f5341bf8e7a00000000000000000000000000000000000000000000000000000000000000001a0391b3b40ccfaff5a5492b970966cd01a33076e765e2920215207137fb9a83dcba052f9bb52ea281646e34e82a2e2ec54444b06ddfb8169009272b333f1d9d56fb3a056f79616b2ea27290683fac649a963eee217708f1d3318adbc620456ff8bea0ca0710f540c7b4ebf1305bd038276951ca0b923ab9c5b3c14cc6bdb4633e7a770f3a0adfa909a1451f37a80c1512e4a327b7901ff3332d9cb192c088587196a476d34a0f9579fb00abd61abbaea6ff3d70388309db96eed599a97c0a651686c6d74fe2fc0c0c0f9026394a4c567c662349bec3d0fb94c4e7f85ba95e208e4f9011cf845a00000000000000000000000000000000000000000000000000000000000000004e3e265a00000000000000000000000000000000000000000000000b024da72b4e4433708f845a00000000000000000000000000000000000000000000000000000000000000005e3e265a000000000000000000000000000000000000000000000006f0fcd33a89fc8fd21f845a00000000000000000000000000000000000000000000000000000000000000019e3e265a000000000000000000de99fa882153f0500000000000000000de99e1a251361f2f845a0000000000000000000000000000000000000000000000000000000000000001be3e265a000000000000000000000000000000000000000000000000000000000688a1197f90129a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000006a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000ea0000000000000000000000000000000000000000000000000000000000000001ac0c0c0f494a55e4b9a4f637d7515fe83b4cce59b0af6a66c54c0c0d4d38184900000000000000000003a9e5feeff948ac6c581848201c7c0f9025d94a563ae4f348e93c7f50e8fcbffafa44f5aed0b36f8d5f845a004c5856110b85993c260ca59766f06b99c59f1f8e98415cbf2a9b2d5e6981fc8e3e20aa0000000000000000000000000000000000000000000000e5a60a0c6aac1d3e1f4f845a018e009cda939d1fd36ccab098bf96284f74d74cf8260d6551fb99d3bffca540be3e20aa000000000000000000000000000000000000000000000006d182c993c6f1954e1f845a08554e4187951285ebada776cc1d42cd454aa0e31d552f89f416720c43a8de42be3e20aa00000000000000000000000000000000000000000000118a6ddaa03dcc885696bf9016ba00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000015a0000000000000000000000000000000000000000000000000000000000000001ba0000000000000000000000000000000000000000000000000000000000000001ea00000000000000000000000000000000000000000000000000000000000000020a00000000000000000000000000000000000000000000000000000000000000023a00000000000000000000000000000000000000000000000000000000000000025a00184490f07303c6c826dd60f64aa3223736d17e70f4516d1e19cff0d5dc9e085a04020d20232fc91a17a73dc98e19c39e5f97ab3d3d127dca225db5d4614aaaffaa0796349f928668a48ac84ca093ff677e016847f2a17dc74a939097965d0d0236ba0a466b5f32b5e0f2546af9e810dcd22074b2d1e86c766541c11833875c3ec7676c0c0c0f294a56f846233c2ac0d83fb5b1f9a64aa9cc1885b02c0c0d4d381fb900000000000000000000003c5ac56b808c4c381fb03c0f83b94a58e81fe9b61b5c3fe2afd33cf304c454abfc7cbc0e1a00000000000000000000000000000000000000000000000000000000000000002c0c0c0f394a5a13f62ce1113838e0d9b4559b8caf5f76463c0c0c0d3d21c90000000000000000054135dfa49087dc2c6c51c8305176bc0da94a5f565650890fba1824ee0f21ebbbf660a179934c0c0c0c0c0f9010194a69babef1ca67a37ffaf7a485dfff3382056e78cc0f884a00951aeed2e27b702ce516900c08a7c82271f51c487c2d80500b85e40af550d41a09ea658ca2d24493910b2374d3573a0281cefd09b43a9f7f583f0676365bb92dea0a7194a069909d28dfed8825ef6d2521aaf009fb3233a57fba5e2a5fd5ace3899a0afba4495c9b6be110d982b68170bc068f64a356bcfdf2ac9c368098cdc35064df861d21c90000000000000000a75d2b59646ec457fd22290000000000000000a75d2b5964712e797d22390000000000000000a75d2b596475e28afd22490000000000000000a75d2b596483ac4c7d482013c90000000000000000a75d2b59648e320dfc0c0f9022c94a7ca2c8673bcfa5a26d8ceec2887f2cc2b0db22ac0f90210a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba00000000000000000000000000000000000000000000000000000000000000015a00000000000000000000000000000000000000000000000000000000000000017a07d4f62d7a1d233431639862c23098947a59fa9d610342acf75daa657c10f6cfaa09144aee7f37b7f43b6b90b5f10849f7e24f0d10aec9ad49f57e3aa0eb644251fa0acabee26514bb791c4f3218b82e44269bbee7e9c7cb78209c6483d49ed0c8663a0aeb398aaef01950a3b20c728e1997bb95d411b0de44b9c8c5376cf18fee7cb8ba0b64dca7c2c7c1042e75fbf9b93ca1120ef9e6feac25da501460cd6d486bb8882a0d8f023ef571f4c023b56aad4efa6735cfb9b0d823767a0cec823d80982edadf5a0ed4a303e04cc9a91fc1acab40e65e7a14b8b8cc64c83ade8187695355ea4625ca0eed48e1b7f1147de10c3442cf7f36d7b541207e7f0041b1a7c8c303e3c978fb2a0f8d6e1552be1e8b38e47ebdfc2dc3d3a17782cf5647bb8549806e12f62f320fca0f8d8af571bcf8331a05936e065bfc9134b6461ab3226d0f94b32b989e63dadf0c0c0c0f494a7de6bcf69afd444bf3e229b72377cf0efec3980c0c0d5d482010d900000000000000000000633dd23055946c5c482010d50c0f094a869c2b2546945b17fbcac82127e4b50b7b46eecc0c0d3d24b90000000000000000000074516d2822081c3c24b03c0f494a8f7775002e4f7deed0702373dc7612a66be0313c0c0d5d48201329000000000000000000001656bbdb93fb8c5c482013202c0f394a9ac43f5b5e38155a288d1a01d2cbc4478e14573c0c0d3d2549000000000000006516fc5b41affba2151c6c5548303b8cfc0f294a9c4046eea8f0db337b3394ce83e70529f3eb8efc0c0d4d381c6900000000000000000006fd32b6e475288c4c381c606c0f494aaae7fd759ac75dbfb5a85b8fc9d93fd3646ea5dc0c0d5d4820143900000000000000000018adb7a4ac87d83c5c48201431bc0ee94aab4a34d3e7c3d91c187d78b6545583637987dd6c0c0d4d38193900000000000000000000221b262dd8000c0c0df94aac5d4240af87249b3f71bc8e4a2cae074a3e419c0c0c0c5c41982529ec0f9059f94aaee1a9723aadb7afa2810263653a34ba2c21c7af90140f845a04f025435256321a0ae3334b421d2dfb60520d2bdba7701059ff06fb7f4754e42e3e21ba00000000000000000000000000000000000000000000000000000000000000000f845a06e9e84ae7b8edc848e54262afc59a03baa298e039f2917f989989f0639ac12e4e3e21ca00000000000000000000000000000000000000000971580b81199161e2681fe3af869a0e8370f8e7f793f91c2b6e49010bf52dbaaf58e8f07d988605364509f42c3ba3df846e21ba000000000000000000000000000000000000000632f83b3a8a6dd6b3a8e8c2300e21ca0000000000000000000000000000000000000006324e1c7b0ca365349d94c1828f845a0fb65d29cf96abc7a12646808930270725f963bf8cf4a5af80f8ad438c187a4e8e3e21ba0000000000000000000000000000000000000000003780cc48bc9e13378356ac8f90441a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000005a0000000000000000000000000000000000000000000000000000000000000000ea0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000011a00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000013a00000000000000000000000000000000000000000000000000000000000000015a00000000000000000000000000000000000000000000000000000000000000018a0000000000000000000000000000000000000000000000000000000000000001da0000000000000000000000000000000000000000000000000000000000000001ea0000000000000000000000000000000000000000000000000000000000000001fa003cb0006519beb269e6d0b397340a704011d3923c6c9d3286b06ff8b33437ce1a0087c10145799fe03a38a6e7ef91cdb9ae5473267d413a046dd5e84b80ee9d6b0a00a3aff3e51479d2994c9e730ccd79248689e13938ccabd921c0825a5bc4ea151a013316eea440f62b057a7cb4124abadfd7e5d62103e506e3bb5b58baa24c834bda01aecba4ebe7a4e0673e4891b2b092b2228e4322380b579fb494fad3da8586e22a024ea3a70e6554e54fb42d5581a0588f07c5f950d7669c377ad2fa0c30cc7dec6a041660f896507c0d61826eb173be1231662684344d3f8e2e45edd87409dd68bfda04fdcdd851f57f54cc11880efc989407936a09d270df6b7150f9d3215a1492b79a0637f1900d714d8240f11e76c1668505f8edd1ae1e087ba01a6e53d88cde04513a067a26e0766ead2f527810d7475bd1037ecf96e4c7520f75e1e0c887356a16111a06df8a2103564f8c1191b90129e821e9160df5e528ae9ad52fdc5e077349c11e8a06e93b9e676f44adb30f78eff514d0e45730c6213c6385e999e872568aa3711eba08e0430c7f2f6361648b5315cca00d62170e488667dce19185fbb0dea1cd45d1fa0c44efbb32cad5cd0a322d840f6cf4a56c87a05cfbfa8514d036a1fd89a12816ba0d53ff65c502fb8d4ae0b7bc70542a2dca8a2dc122df4c232a02acdfd6e6a2fcda0e469febafc2e9b166ae3ac7542fb9d9ced5de878defd8033d44188b51a0099c8a0f56408d23e6790fec5453738cf042a4a3ef7ec36e9ceae8978e4ffce8e903bc3a0f800ab3c6019c234c0cd9e10d337b2fa51dcb3800bc9bfa22e41d22bc7ca3ceba0fa6c4e1c9df36b7728428843169d6f98510c692a471f46a598282ba5eb68a4d8a0ff7875bfa388daea06ccd6779b39a3681af6827624c3575d70cd11afb58a757cc0c0c0da94ac4c6e212a361c968f1725b4d055b47e63f80b75c0c0c0c0c0f9031494ace8e719899f6e91831b18ae746c9a965c2119f1f901cdf8afa000000000000000000000000000000000000000000000000000000000000000cbf88ce25aa0000000000000000000000000000000000000000000000e5ba528abe5c8c7f6f1e25ba0000000000000000000000000000000000000000000000e5b56faab79424912f1e25ca0000000000000000000000000000000000000000000000e5b08ccab0cbbca2ef1e25da0000000000000000000000000000000000000000000000e5aba9eaaa0354b4af1f845a03656d717f1360ad295fad31dc461d83ebe41f8bfc2bb20643498245e065cba68e3e25ba00000000000000000000000000000000000000000000001834b8f13c6d4f1cf66f845a0549013f204566681cfdc6c21a5d2423a9f68f4604fd614b428b4b45bc6343fb8e3e25aa0000000000000000000000000000000000000000000000095cb66e737dd426089f845a083b666bce0c808dab81a97f57a1d33c46284daf8560103f2195356dd38ab31c3e3e25ca00000000000000000000000000000000000000000000001093709c1a52ae4e88af845a09a1dc32cb2e3f1c2d69f37f312975cc2caa2ca8736f3e00dbb9b2434da80ee58e3e25da00000000000000000000000000000000000000000000000923cc3a71a91c28fd2f90129a0000000000000000000000000000000000000000000000000000000000000012da000000000000000000000000000000000000000000000000000000000000001c3a00aabb8e6c679c0a9ed5effb02f8649130b66c828c9974e485f91fc45736f6946a033d4bfc1cd4cdb0642f5dce045fdc46c2bb61911f16f94f8fb4d01bfbe49e541a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0485e7d2e1bda975843795347885b3f170cec2b00bb47ff8fc2d80851bcdf24e0a06208b0917dee425fd8d85d69c5ced832c254fd3863f1f5371eb83e18a837fd85a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0bbcf953f93e136b5652c8483e05bf4e38e9b5f3c5675003568d32719f122e105c0c0c0ed94ad2113692f8a7d66c3bd27fb8c0b2f101c2c7c92c0c0d3d211900000000000000000001e7d303304b299c0c0da94ad27827c312cd5e71311d68e180a9872d42de23dc0c0c0c0c0f294ad31c2989b00aeaf49fea8998dbf095ff89d9602c0c0d4d381e8900000000000000000000040bd3ef716c1c4c381e80cc0f494ad35bbbd3e34939017a37dbfc0f3d402d41d014dc0c0d5d482011a900000000000000000003e7fcb7f02dd33c5c482011a15c0f83b94ad3b67bca8935cb510c8d18bd45f0b94f54a968fc0e1a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f494ae134de48af0f087472b89b042952e09407e40a1c0c0d5d482013990000000000000000000076fe16b012731c5c482013911c0f8e394ae2d4617c862309a3d75a0ffb358c7a5009c673fc0c0f898d22790000000000000000616bfa9c6b69f00e7d22890000000000000000616bed23e9fce8164d22990000000000000000616bdfa9cc5c0c3d9d22a90000000000000000616bd3728552a1b92d22b90000000000000000616bc73c0c632124fd22c90000000000000000616bbb04c559b6a08d22d90000000000000000616baecf1a841ffc9d22e90000000000000000616ba297d37ab5782f0c527831a14fdc528831a14fec529831a14ffc52a831a1500c52b831a1501c52c831a1502c52d831a1503c52e831a1504c0ee94ae5dde433888a7456c3e0aecbb2e0a5748cbc1ebc0c0d4d381e890000000000000006d8896912297006236c0c0f294aeee5c50ea49e879359a48576c4e981a59bc5dc3c0c0d4d381a8900000000000000000000033d758c09000c4c381a801c0f84094af1931c20ee0c11bea17a41bfbbad299b2763bc0c0c0e6d2759000000000000000360a10a4ae9ebfc651d2769000000000000000360c48aec04d195069c0c0f294af88ad36427959838ebccf44b1c5d8ce8ee1e976c0c0d4d381b1900000000000000000000a320c3c161794c4c381b102c0ef94af8fcc234fd60a4171efdc9e4440345168a0ebbac0c0d5d48201279000000000000000000220057433343d63c0c0f84e94b028b84783a0381d51dcf0e8ef04b5e502958618c0c0e8d381f190000000000000008cdca9dfb394ec59d0d381f290000000000000008b2092d57a476cec78ccc581f18221ebc581f28221ecc0f294b043ad2dcf8edc981ab69cc527950054ca879cbbc0c0d4d381a09000000000000000000000762611a02af2c4c381a002c0f494b0af27a11d3b0fa69d4933efedf7373354bf709bc0c0d4d381ff900000000000000000000690cd0f700fbec6c581ff821238c0f9033394b0ef04ace97d350e24efa5139d2590d26a61a8dcf901aaf845a00000000000000000000000000000000000000000000000000000000000000002e3e251a000000000000000000000000000000000000000000001e308032fba4d36524372f845a00000000000000000000000000000000000000000000000000000000000000003e3e251a0000000000000000000000000000000000000000000009010b47b07a0c7ad20a6f845a00000000000000000000000000000000000000000000000000000000000000012e3e251a00000000000000000000000000000000000000000000000019a36d6329ae557f3f845a0000000000000000000000000000000000000000000000000000000000000001ae3e251a000000000000000000dffd456b707083300000000000000000e06c1414f6f1d74f845a00000000000000000000000000000000000000000000000000000000000000022e3e251a00000000000027f615b366568062cbd2c00000000000272718f2556e107a43b43f845a00000000000000000000000000000000000000000000000000000000000000025e3e251a0000000000000000000000000688a1197000000000000000000000000688a1197f9016ba00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000011a00000000000000000000000000000000000000000000000000000000000000019a00000000000000000000000000000000000000000000000000000000000000023a00000000000000000000000000000000000000000000000000000000000000024c0c0c0f84c94b1b2d032aa2f52347fbcfd08e5c3cc55216e8404c0c0e6d23e9000000000000000003681998ef330a563d23f900000000000000000367c6ad4e2f34eefccc53e831b23dec53f831b23dfc0ed94b1b72debde32aa19837890b95e3fe8fc108d98d5c0c0d3d23a90000000000000000000039f78e6b38ab5c0c0f9012994b2ecfe4e4d61f8790bbb9de2d1259b9e2410cea5f847f845a00573e6c3f75fccf4cc7f76ecdc09c583262996211572c033f8a883502f37bf01e3e20da00000000000000000000000000000000000000000000000000000000000000001f8c6a000000000000000000000000000000000000000000000000000000000000000fba0000000000000000000000000000000000000000000000000000000000000012fa0000000000000000000000000000000000000000000000000000000000000015fa0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca038b78f65113205a84534609a8f50d048573843947c3707fed6d8f33385501fe6a03dc40693fb3bf64b979c0e0b720cba651945e1a1c67007f82f8a170b92d7faf8c0c0c0f85d94b300000b72deaeb607a12d5f54773d1c19c7028dc0f842a0087371c27b87233e7918c6381aa1f04f93b449f9ba5d0f9d1057bfb75da88479a089d01902a77e9ad7f7103ded15feb0071b100f807050f8feeaeeed16a845d921c0c0c0ef94b3aee9e08c333d59406e56959cd63727cb5aae5fc0c0d5d4820114900000000000000000002bf5ed66db8816c0c0f902c694b40865a6ed718f57468cd3f4f60825a130b89a51f9011cf845a0000000000000000000000000000000000000000000000000000000000000000de3e214a00000000000000000000000000000000000000000000000000000000000001618f845a03bba9108b904cfb969b317a6a0847c2d13a92bb924e87f843935fe7b8f315911e3e214a0000000000000000000000000000000000000000000000000000003904ef5d027f845a04344f879482970d41fbbbc31133bdc9f73172c700eaa6faab1c53fe76bdaac61e3e214a000000000000000000000000000000000000000000000000000038d7ea4c68000f845a06b8c06e5a8d9639de2c13b3e8f375241846e6839c3042ca985e9487ac24aa3c7e3e214a000000000000000000000000000000000000000000000000000b3ef4a469f39c9f9018ca00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ca0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000010a00000000000000000000000000000000000000000000000000000000000000013a00000000000000000000000000000000000000000000000000000000000000014a045fb090341396a0fcacdccaaeb956cbe22170a2c413f5b36791920012135634da0bbdb6c46a65073920ce1493e5770cf927f61df994d2df4d42ef47a5eb0452f16a0fb437cb06df854b0e9a5f9c071f54ee63d1407c751de4e81c7cb3a621a923fa3c0c0c0f094b41f901c022aceb538c7db67da458dc1f03b2110c0c0d3d2619000000000000000000054f3ae5d5344fcc3c2612ec0f294b43984074fff6e0af7fd717bba78fa31bc69b65ec0c0d3d2229000000000000000001fec1cb464e487fac5c422827312c0ee94b45cc5d4abcf4693b648aa171a1913b15c2345abc0c0d4d381e09000000000000000000323c1b0bd98aa17c0c0ef94b4d4f697405b83aece6bb0b265d7755517607935c0c0d5d4820136900000000000000000280e4bcf092bca39c0c0f8ab94b528edbef013aff855ac3c50b381f253af13b997f890f846a011ea15c3ce93730f20bb9cd4a213d501d858af90bf49a7059634239d81e1ac89e4e381b9a00000000000000000000000000000000000000000000008f8954e24b7bed1633ef846a044a6c51a1742636c5f52a567e08612f0b1f580be1e93cc4318bbc864b75b21fee4e381b9a0000000000000000000000000000000000000000000072ef2ee1ce41fe8796b5ec0c0c0c0f294b53b99ecdb5e3e6c5ba60c04292d225c766435d9c0c0d4d381c4900000000000000000000704af355eb4d6c4c381c401c0f84e94b685760ebd368a891f27ae547391f4e2a289895bc0e1a00000000000000000000000000000000000000000000000000000000000000001d3d235900000000000000002f29baeb2409b9678c0c0f294b69f393936891243fe5b459e32f84bf7b79f55d8c0c0d3d22590000000000000000021038d63f325ccc3c5c425820228c0f294b6accfe5a01a868afb5b76f71178978b98729b1ec0c0d4d381a59000000000000000000003b9d743a5e07ec4c381a562c0f294b72fc3efa90079a37b5b958419a1fc66939a65b3c0c0d4d381fa90000000000000000000003bfbc14f78a8c4c381fa07c0f294b76403a587cf79b8817f6034f3bb99d9013e3bb6c0c0d4d381a690000000000000000000dfd4fc930fa9bcc4c381a61ec0f094b8430c2c0098e933c5d83abb1866c941328817acc0c0d3d204900000000000000000000ddfc462db608bc3c2042cc0f094b8667563548726af6732c799e066c27e3e1efe62c0c0d3d247900000000000000000000279e9fa57e646c3c24707c0f094b8d0633c0b02e2fcb369e06715d073c26c3c82d5c0c0d3d239900000000000000000000a1239016013f1c3c23914c0f594b8ff877ed78ba520ece21b1de7843a8a57ca47cbc0c0d4d38188900000000000000002933a7c4323ad9c8ec7c68188831996a3c0ee94b9865f0a2b6b2536fd3c4c494120043d637282b0c0c0d4d3819b900000000000000000001e0bdd1530f9e2c0c0f494b9d0132ad2147d62c86ff401b488ff05d164d54ac0c0d5d482010790000000000000000000dddcead8b4cfdfc5c482010744c0f9015494b9dce40ac352c84ca11f94c3ce8ec37b747fa658f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e26ba0688a1197000000000001a24573153c793e6800001898faa1d0e7c15f6fa16aa7f845a00000000000000000000000000000000000000000000000000000000000000009e3e26ba00000000000000000000000000000000003514f6566cebf1bc72298f40b8c0518f845a0000000000000000000000000000000000000000000000000000000000000000ae3e26ba0000000000000000000000614520683202a931bc8ce8ef13c0317ab73222d847cf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f87e94ba1030459e75f6041f938c5470f4e0f6468d5253c0f863a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a0d00bfb29ca3e97671eaccbe9ff0f65231aa99da580a6636b674ae752bf0c5cd4c0c0c0f904dd94ba1333333333a1ba1108e8412f11850a5c319ba9f90354f845a007c9a400ecc2eebc565cfc2d20feeb7918c3db214452397115bd5b87f3a55881e3e208a00000000000000001de5fca3bb379d24400000000000000009dfe94f47bc6aec8f845a018c88b4e60515ef72c23539b62239eccb14b91dc549674c82804dd71609edde6e3e208a0000000000000000000000000005dbd0f000000000000000000000000007be61cf845a02cafc07baf118f0b41f7995537587843f281cea80518f05b7c5640d3078c9937e3e208a000000000000341134521b34020ebc608000000000000000000000324c96c4adaf845a0618892da266eb69cf8eb5ae04d271c1f10815eaf33e9530c9767a4049e6cd84ae3e208a000000000000354e206d1435a09c4de2c00000000000000000000033cba5f6710f845a06b9cbc6b73e21fd9bc53d596b85366ea95a7d6b79c2a023cd093664a3a02dd11e3e208a00000000000000534189bfdd29442152b0000000000000545ca060a6df79a0dd8f845a0806f95c6c03f580ef5eb6e158a5a8b6115094c7f98fbc837989b35d307298f07e3e208a0000000000000000000000000000000000000000000000545ca060a6df79f7e6af845a09c2bd49637b09bb19d305ef2a3256da5af7f527123146adfe40e24b91fdbd36ce3e208a00000000000123debcc322c7b653d796d00000000001200b5325e34bb48159e24f845a0a197b6ae0044c90610b63b51392eb7144f4390bba5cb14eeeb9a37521cf0dd8ae3e208a000000000000000000000000000000000000000000000000000000014d1af62b5f845a0a274010baffefa384e4ac371badf58bca8d20e332f0f733fe7dd0e9c16ac2377e3e208a0000000000000000000000005310bb3a9000000000000000000000005e8aa9483f845a0bf689e24506b4e2538defc573210ae5359b2d843bbdcf15f1f50bcf519c49792e3e208a0000000000000000000000000005173d8000000000000000000000000003a36f3f845a0cf10c860f03dc44636de67dec1730250e4b90dfddb0ff1f8fe0d2f1e75a5b1e8e3e208a00000000000000000000000000000000000000000001503315b56b84675f08529f845a0fff9795d68180769da5da6823aa61ce98c2583c5905188dffc9c5a9e2a63fc3ae3e208a0000000000000000000000000000000000000000000000000000007060bfdffd7f9016ba00000000000000000000000000000000000000000000000000000000000000007a00dfa2ee427b421c051d292da37e8460226427485cdffe9d500aca38f183ce207a00e476ca10c29101f2290899d7f5241021a1746454d48735c8d52a4add14e3b82a04bf6b1b93044fab8fcdca37f8f6796e8535fdfcff73340b7bc2b86610393fda6a0b31853c56ad924d32a840844f595dda9cff91d27ef81bfdcd87dbc54c7c45311a0b6ff0d2bdefa3725dc0da1016ab284bb380197eaff3529cafadfefa431121c7ea0cdc72f7cbf91ffd23fc50ffc94c9daf41b37446291f3f2c4b6b3ad6273ff1f8ca0d95a51339aca4132b1d22675f2b4141e4f7c71bdd8cc621825924685d1a93ad3a0daf9cc1810cd2c0a7e7d5984f6803ce8214d86835f739246b8634738b7ac93afa0daf9cc1810cd2c0a7e7d5984f6803ce8214d86835f739246b8634738b7ac93b0a0daf9cc1810cd2c0a7e7d5984f6803ce8214d86835f739246b8634738b7ac93b1c0c0c0f294ba9a3c0a22baebe8c4926227bde32d6edc0d5d28c0c0d3d2199000000000000000002d4cfe402028a60bc5c41982a262c0f90e1494bbbbbbbbbb9cc5e90e3b3af64bdaf62c37eeffcbf901f9f847a02983fce370821ae790208bad5f2f1c8885852c62cfafd26825794144088bba43e5e482010da0000000288b82d6ff969e88f572ee2b34000000000002aff414f2d170414ab34ff847a02983fce370821ae790208bad5f2f1c8885852c62cfafd26825794144088bba44e5e482010da00000001c38d64d449536b74c6d48bb79000000000001df2bde0a7274c60fd080f847a02983fce370821ae790208bad5f2f1c8885852c62cfafd26825794144088bba45e5e482010da000000000000000000000000000000000000000000000000000000000688a1197f845a060c12edcccc16be024210a44e5eb7eca9457e6fce839d511c6e9263082b76ad6e3e204a0000000aece6f59361ddbc7aac287348000000000000b74c3b69d108e114ffcc2f845a060c12edcccc16be024210a44e5eb7eca9457e6fce839d511c6e9263082b76ad8e3e204a000000000000000000000000000000000000000000000000000000000688a1197f847a0aab7517210a335e22c46861d875132b3f597f0d2fb4da60abdb3665953df0627e5e482010da0000000000000000221a777e9a4c419930000000000062c2667546fec46501427f845a0f1d1adf8a778765e14f267061a4d966f2bf165b342e2c73111d88e5145414cf3e3e204a000000000000000000000000000000000000000aece6e8574020cfabd21873480f90bfda0014e88cdcec5072e0f7058e16e9eb972ca7bbd5502a9f70d1abb896bcb8c3496a009cbcb9fc4459cb8933dad695db393378b4cdc1dc4e15646d7ca23a049870e2ba009cbcb9fc4459cb8933dad695db393378b4cdc1dc4e15646d7ca23a049870e2ca009cbcb9fc4459cb8933dad695db393378b4cdc1dc4e15646d7ca23a049870e2da00a6e7835878e4f7bc2a0b5ddb8eb0abbf054ce8476e5ee51eb381fd60e372a3ca00a6e7835878e4f7bc2a0b5ddb8eb0abbf054ce8476e5ee51eb381fd60e372a3da00a6e7835878e4f7bc2a0b5ddb8eb0abbf054ce8476e5ee51eb381fd60e372a3ea0100ca93e729bac9ad357392bd2cbeec6ce5c116219abfc02e7ca9ea18fe8930ca0100ca93e729bac9ad357392bd2cbeec6ce5c116219abfc02e7ca9ea18fe8930da0100ca93e729bac9ad357392bd2cbeec6ce5c116219abfc02e7ca9ea18fe8930ea015387e2f86595f853b2991e9e06726ac173e446b72141a15ee2ee8259f08c282a015387e2f86595f853b2991e9e06726ac173e446b72141a15ee2ee8259f08c283a015387e2f86595f853b2991e9e06726ac173e446b72141a15ee2ee8259f08c284a015387e2f86595f853b2991e9e06726ac173e446b72141a15ee2ee8259f08c285a015387e2f86595f853b2991e9e06726ac173e446b72141a15ee2ee8259f08c286a01edbac83ea6d707ad5846936e45b9183fa039f90a84a7c7d51d3fd61dc4cc726a01edbac83ea6d707ad5846936e45b9183fa039f90a84a7c7d51d3fd61dc4cc727a01edbac83ea6d707ad5846936e45b9183fa039f90a84a7c7d51d3fd61dc4cc728a01edbac83ea6d707ad5846936e45b9183fa039f90a84a7c7d51d3fd61dc4cc729a01edbac83ea6d707ad5846936e45b9183fa039f90a84a7c7d51d3fd61dc4cc72aa024cb18d68d0cfe9a2bb9bfc5f08613e92ac9d2b1fc7ba64727074d4fd4eb55d4a024cb18d68d0cfe9a2bb9bfc5f08613e92ac9d2b1fc7ba64727074d4fd4eb55d5a024cb18d68d0cfe9a2bb9bfc5f08613e92ac9d2b1fc7ba64727074d4fd4eb55d6a024cb18d68d0cfe9a2bb9bfc5f08613e92ac9d2b1fc7ba64727074d4fd4eb55d7a024cb18d68d0cfe9a2bb9bfc5f08613e92ac9d2b1fc7ba64727074d4fd4eb55d8a02b09c537d7f61076857476574e8f684218d23dc4f9d6fdf7873f769bd40cc28aa02c865c6b862372f86793aa2cc9ea0467f0a047d1d115107f95883d45bff01603a02c865c6b862372f86793aa2cc9ea0467f0a047d1d115107f95883d45bff01604a02c865c6b862372f86793aa2cc9ea0467f0a047d1d115107f95883d45bff01605a02c865c6b862372f86793aa2cc9ea0467f0a047d1d115107f95883d45bff01606a02c865c6b862372f86793aa2cc9ea0467f0a047d1d115107f95883d45bff01607a05c11e8aca61e714c59b41edd08a1c7ddb200dc95fd1850c87557f11b07a0735ea05c11e8aca61e714c59b41edd08a1c7ddb200dc95fd1850c87557f11b07a0735fa05c11e8aca61e714c59b41edd08a1c7ddb200dc95fd1850c87557f11b07a07360a05ee4ce392a8259f279816dd11ccfee0b6187441c55f612819835e5c7f81db140a060c12edcccc16be024210a44e5eb7eca9457e6fce839d511c6e9263082b76ad7a0730220c4429e85d5222eebaec14f8d0afaa627295fec6c3f4e88e436df75adaba0730220c4429e85d5222eebaec14f8d0afaa627295fec6c3f4e88e436df75adaca0730220c4429e85d5222eebaec14f8d0afaa627295fec6c3f4e88e436df75adada0730220c4429e85d5222eebaec14f8d0afaa627295fec6c3f4e88e436df75adaea0730220c4429e85d5222eebaec14f8d0afaa627295fec6c3f4e88e436df75adafa07d7d1b8041fb3bf7235bd4f4685b96edab920138b7939763f9de9d190bf3b60ca07d7d1b8041fb3bf7235bd4f4685b96edab920138b7939763f9de9d190bf3b60da07d7d1b8041fb3bf7235bd4f4685b96edab920138b7939763f9de9d190bf3b60ea0999d5bf78123a7d7adbc843d9fa78fc049b1c3b4ebbee62d94b3d47e1bfc7b3fa09a2ec4ad825cccad0e3df2604489ca6733b296d5b21ebcf3fe4e7b033e40edd8a09b70cba3c51d5e03d0509012d4ca17c701a6b9b33598946c13313c68a985a852a09b70cba3c51d5e03d0509012d4ca17c701a6b9b33598946c13313c68a985a853a09b70cba3c51d5e03d0509012d4ca17c701a6b9b33598946c13313c68a985a854a09b70cba3c51d5e03d0509012d4ca17c701a6b9b33598946c13313c68a985a855a09b70cba3c51d5e03d0509012d4ca17c701a6b9b33598946c13313c68a985a856a0a1b332bc5e90cdb8d3550421b625d92a21c09812b23a0e29239d57c120f3b5aaa0a3088256c79a919c2204737cfe51f142ba9e88acc68d1dc03efa3dc828bf9df4a0a3088256c79a919c2204737cfe51f142ba9e88acc68d1dc03efa3dc828bf9df5a0a3088256c79a919c2204737cfe51f142ba9e88acc68d1dc03efa3dc828bf9df6a0af5f02fb405ee52198ad944841235017894e4a1f2f9e57ce42e6df356ed67f5da0af5f02fb405ee52198ad944841235017894e4a1f2f9e57ce42e6df356ed67f5ea0af5f02fb405ee52198ad944841235017894e4a1f2f9e57ce42e6df356ed67f5fa0b180ec50a97fa320faf31c657698203cd2b9fa7d7953454f7e2c9eab019c8ffaa0b64a4ce6d3654d1e76d49fc82617aa6c045883d11ce567e50569ac978546e7f8a0b964268a08a58762c4353a7c9bba397c8bed4cc752e32bf941852f34c4f1190fa0b964268a08a58762c4353a7c9bba397c8bed4cc752e32bf941852f34c4f11910a0b964268a08a58762c4353a7c9bba397c8bed4cc752e32bf941852f34c4f11911a0b964268a08a58762c4353a7c9bba397c8bed4cc752e32bf941852f34c4f11912a0b964268a08a58762c4353a7c9bba397c8bed4cc752e32bf941852f34c4f11913a0d1769b32dc40bdfab31a687a4a2ec4d28477dc25514665f82c096f0b5eac0049a0d1769b32dc40bdfab31a687a4a2ec4d28477dc25514665f82c096f0b5eac004aa0d1769b32dc40bdfab31a687a4a2ec4d28477dc25514665f82c096f0b5eac004ba0d1769b32dc40bdfab31a687a4a2ec4d28477dc25514665f82c096f0b5eac004ca0d1769b32dc40bdfab31a687a4a2ec4d28477dc25514665f82c096f0b5eac004da0dc0f67ab076b0af820d51e0734250f2ed6b92fc137325782d20ae31463d01d2da0dc0f67ab076b0af820d51e0734250f2ed6b92fc137325782d20ae31463d01d2ea0dc0f67ab076b0af820d51e0734250f2ed6b92fc137325782d20ae31463d01d2fa0dc0f67ab076b0af820d51e0734250f2ed6b92fc137325782d20ae31463d01d30a0dc0f67ab076b0af820d51e0734250f2ed6b92fc137325782d20ae31463d01d31a0de2b9900ea527d046a7bd50e5531b5c09aee5690bd465f5218b87e1862fa8eaba0de2b9900ea527d046a7bd50e5531b5c09aee5690bd465f5218b87e1862fa8eaca0de2b9900ea527d046a7bd50e5531b5c09aee5690bd465f5218b87e1862fa8eada0de2b9900ea527d046a7bd50e5531b5c09aee5690bd465f5218b87e1862fa8eaea0de2b9900ea527d046a7bd50e5531b5c09aee5690bd465f5218b87e1862fa8eafa0e022014e7cbd5faefd0e7dc8754427aebb27f4d5bb4792a8400638874a210b0ca0e022014e7cbd5faefd0e7dc8754427aebb27f4d5bb4792a8400638874a210b0da0e022014e7cbd5faefd0e7dc8754427aebb27f4d5bb4792a8400638874a210b0ea0e4f45349da4b4952be89a2fca9b67b521edffed4ca3f6fdf3306187962ec513aa0e4f45349da4b4952be89a2fca9b67b521edffed4ca3f6fdf3306187962ec513ba0e4f45349da4b4952be89a2fca9b67b521edffed4ca3f6fdf3306187962ec513ca0f16c651a8235081ec7600d9a8a02b1a0ef2237a422beba88901c7c9624fa414fa0f1974be9d7fb96697dbff41eff9f6cf3631ebe12e6bb8bddb0d0c5b383bd8ef5a0fa8529202f11d7ec3740650be296985b41436be1e7e7959945f145e1f977cf1fa0fa8529202f11d7ec3740650be296985b41436be1e7e7959945f145e1f977cf20a0fa8529202f11d7ec3740650be296985b41436be1e7e7959945f145e1f977cf21a0fa8529202f11d7ec3740650be296985b41436be1e7e7959945f145e1f977cf22a0fa8529202f11d7ec3740650be296985b41436be1e7e7959945f145e1f977cf23c0c0c0f9028594bbcf554cfadfe4584245b28123e069dfff519fa0f9011df845a0000000000000000000000000000000000000000000000000000000000000000de3e20ba00000000000000000000000000000000000000000000000000000000000000078f846a0cdd00605a828d63894f25a63cf98a42dbab3dc3119afdb11f80e971400cd62cce4e381ffa0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff845a0dba6406710d5b070b1e40f4435fdc77fed8c298350a49fd73bfe9f28aa3021bee3e20ba000000000000000000000000000000000000000000000000a3dc408f990f76a41f845a0eb5ad7fd2957a64d35cb4e2a0839327871057e6783eeef0101829de38020a067e3e20ba0000000000000000000000000000000000000000000000000514a26f8136cd8d6f9014aa00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ea0000000000000000000000000000000000000000000000000000000000000000fa00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000013a044d09c05649d869c9b296f59c0dba4e9357274b923c0c22684affaf69581fb92a04aafdfb9e8791872f6c3a39c17837c3c7a9c8fdaf7f0cc7678d2cdb30b109cb5a0b3c5ce9f281976d53af4e2aefbefed86e204bbf9aad878428fb924f0953b0cb4c0c0c0f9015794bbebd57cbf4e511c443736fd2d255f6fc97a833ff8d8f846a00000000000000000000000000000000000000000000000000000000000000008e4e38183a0688a1197000000000001b32b5058671576270000000000000017d8cab133ddeaf846a00000000000000000000000000000000000000000000000000000000000000009e4e38183a0000000000000000000000000000001994bbaf633c11276313fa16ac968a0bc18f846a0000000000000000000000000000000000000000000000000000000000000000ae4e38183a000000000000000000000000000000000000460dbe63bede9972926d803d86450f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f494bc8a0f5e41857292d12ef620e2a5895ce65a5ce0c0c0d4d381ca90000000000000000f120744870bdc1882c6c581ca820c44c0f85d94bcb4e4bcc41ab1494a3eb3456ed4edb8da5d46e4c0f842a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000002c0c0c0f85d94bd3fa81b58ba92a82136038b25adec7066af3155c0f842a00000000000000000000000000000000000000000000000000000000000000003a0c59312466997bb42aaaf719ece141047820e6b34531e1670dc1852a453648f0fc0c0c0f294bd4f6966e3438251c9e6d2b1ee6f5f742debae35c0c0d4d381bb90000000000000000001c46c9e4b096c81c4c381bb02c0f9014e94bd6c7b0d2f68c2b7805d88388319cfb6ecb50ea9f9011ef845a041ce5b871c5fecc02a51d9ef7ddeff5698f770b5eeb37678975c424ebe93cf23e3e21ba0000000000000000000000000000000000000000000a1539d3012afc86e48a3e1f846a0a6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49e4e38180a000000000000000000000000000000000000000000000000000763bdf1356d499f845a0d5021e00f3f9ad6fdf398a54facfed680ef0225cdb575de7f274291eeb48534ee3e21ba0000000000000000000000000000000000000000000a8f098b75da23f2873484bf846a0e41e474b0f9679d35875f98ca4229f64da069cb2154397d286e4e19139b3cbade4e38180a000000000000000000000000000000000000000000000001cc50306a703ccd99dc0d4d381809000000000000000223b8e081320d1f71bc0c0f9015794bdc02589ef451396ba41fae280dbe5ccd19c254ef8d8f846a00000000000000000000000000000000000000000000000000000000000000008e4e381dda0688a1197000000000000599108d865795337000000000000002c23872eddb87cf846a00000000000000000000000000000000000000000000000000000000000000009e4e381dda0000000000000000000000000000000149951b6ab89d8747e4e53f89c796444bcf846a0000000000000000000000000000000000000000000000000000000000000000ae4e381dda0000000000000000000000000000000000009992e08b97ca4e036a5a38e1a5cbcf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0ee94bdd89ea2cdd522f727b46e39aa518f6bc7541743c0c0d4d3818c9000000000000000000120ef3cc8b47917c0c0f294be3e915de846767ac379e0fcaabc06fb4e9569d6c0c0d3d27a9000000000000000000000000000000000c5c47a820152c0f901ba94be9895146f7af43049ca1c1ae358b0541ea49704f8d8f846a04a6546822cb6f658b6190c162580ab1437ed4daf8b47da4496c410e57272f7c9e4e381f9a0000000000000000000000000000000000000000000000003509a98b1613f2630f846a0a1a3d4d3186308ed9ecf84298f55d261c894c3a4efc53c46241acc7cbee10fcce4e381f9a0ffffffffffffffffffffffffffffffffffffffffffffffffff1912d829997ffff846a0bcade124ef8284d5c3d1082dc7d2fdd3505132ff30f7d2c77901cf564496c763e4e381f9a00000000000000000000000000000000000000000000000000093b0c72c18aed4f8c6a00000000000000000000000000000000000000000000000000000000000000001a010d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390ba054e90f8f697f70d3073cc6b21c5d9694b40f346ff3cda150122ba99bdb1585a8a06a65c2750b6ce96ae7d01a811cd7dfc2f2a2491b047f29924849b3b15268b437a07050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3a0a7e7c6de3403f57f907d83f4ff7270f1db6606646146303d47116d6832d36f47c0c0c0ef94bea9f7fd27f4ee20066f18def0bc586ec221055ac0c0d5d482012c9000000000000008d0e164878ddfd0423ec0c0f9012494beef01735c132ada46aa9aa4c54623caa92a64cbc0f90108a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000015a00000000000000000000000000000000000000000000000000000000000000016a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec475a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec476a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec477a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec478c0c0c0ed94bf0a610404baa9d9c9843db582b1157db1355f39c0c0d3d2359000000000000000000005486b2ac7f000c0c0f9165394c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2f90ff2f845a0066ed9c225804c48da3ca94afc0896c50e7e287f4837a09a7e9fc0d04a63854ee3e236a000000000000000000000000000000000000000000000000005e5ef5142e95769f845a006a9252c6538079518b2b352a6ce6548cf0b6b07fa7b705bdd31a9f476041f68e3e265a00000000000000000000000000000000000000000000000000ee63c7b5ff47af3f846a008c449fdfc18cbd1fe3a9b3df38017c8d277fe294b54c03f65f196fd369260f2e4e381dba00000000000000000000000000000000000000000000000027ffb1862f88a9e8ef847a00cb865ff1951c90111975d77bc75fa8312f25b08bb19b908f6b9c43691ac0cafe5e482013ca00000000000000000000000000000000000000000000000b8e813469eed3982e7f847a00e06ccf59ca9d931a29fa5f7c0b8083b0a8f23ad7e3d4c9da08471f9e5c43b77e5e482013aa0000000000000000000000000000000000000000000000000053442deafaf27d1f845a019b801df7d999cea8e6d7d7f7b2b0bb8ff07cb169b5922c61a5601805d4e5aa6e3e224a0000000000000000000000000000000000000000000000027f5d642c6dbcca3dff845a024451c947f191ba0b530f793257e0d96a35841b777a0fe4fcdd85e4f88753b98e3e201a00000000000000000000000000000000000000000000000006c9ff08605a4009df86aa02a306d7595987ab10375e6e054d7489cc80c1cf5351587e21ffbd126573f06c6f847e222a000000000000000000000000000000000000000000000002af8d4893991ef5393e38185a000000000000000000000000000000000000000000000002afc891c4f6cf6d393f846a0308e039d225c7d5843e449dddd18a0217a9e77cecd4b8b21297a5d745994a16ce4e381e9a0000000000000000000000000000000000000000000000000b5ee1c2237dc7145f845a0390f6178407c9b8e95802b8659e6df8e34c1e3d4f8d6a49e6132bbcdd937b63ae3e223a0000000000000000000000000000000000000000000000144c6fd2e5bd747596ef845a0450afbadc95be6567831d88455f6a59d850c71c1de4e873bb084146dfb06dd47e3e238a0000000000000000000000000000000000000000000000006c262eec93ac62bc6f847a04d299e0f7464af29a45a350994d64dff17b529d7865327065e4e3d9e8b934c87e5e482011ba0000000000000000000000000000000000000000000000000be17f165955c42b4f845a04f1c9f629bb71aab7d1714da61a9b22e16f92772ad0928e730427024ac5f2b43e3e20fa00000000000000000000000000000000000000000000000001d53f9eb7714a541f845a051c9007cca334123558f0f85f608fa128a98cefc56001cbaad432c54c96adf16e3e272a00000000000000000000000000000000000000000000000016ebf91362eb96346f845a0527c604469ebbe767341beccb5fc5bf6289ffdd59210ea76988145d7ff20d38de3e20ea00000000000000000000000000000000000000000000000015aad205dc2d47ed1f845a052c54c7d408b9ca1cead7ed6a491323e5451549d9ef0992d42f595e36ce08f77e3e26ba000000000000000000000000000000000000000000000000014078de3ac014d18f846a054e90f8f697f70d3073cc6b21c5d9694b40f346ff3cda150122ba99bdb1585a8e4e381f9a0000000000000000000000000000000000000000000000004dbafa132187f9d83f845a05bf0517b0d7cdcf96fc757e4041c30756ba2908355ade5112aa1f97767b9dfefe3e25ea0000000000000000000000000000000000000000000000000132ec8981331331df847a05f14a05004979efb0b63d87957e34df56d04a57ecd44c40ff3ba3f2477694f65e5e4820137a000000000000000000000000000000000000000000000000050bb306e02acdd6df845a061bd8b7d7ea1350e36cb998c4b61b337d0b7e6c625816251eef440c23f9b21a3e3e266a00000000000000000000000000000000000000000000000001e46cf6ead9cca6af845a063080d95ec085011378c4c6d1dfde229de39c228e5471df3ccf065f05bef0f02e3e20ba000000000000000000000000000000000000000000000000016e44754449a0d2af847a069d4b4ad61a248c9c09011fa9f24ebdc295eaab0719dc261fc601f40cffadeaae5e4820141a000000000000000000000000000000000000000000000000006568b5a698fd596f845a0745225778312a78692ae15528abef363d748e20db0ffc5ea6379b04c157a0880e3e20ea000000000000000000000000000000000000000000000000031d654038d425655f8d4a075245230289a9f0bf73a6c59aef6651b98b3833a62a3c0bd9ab6b0dec8ed4d8ff8b1e21ca000000000000000000000000000000000000000000000016c0c70eefbef7f63f4e222a000000000000000000000000000000000000000000000016be462fa2314b81550e223a000000000000000000000000000000000000000000000016b95bd5ac96aa29cb7e224a000000000000000000000000000000000000000000000016b70d4d62771041535e482013ca000000000000000000000000000000000000000000000016b15ecb80b6712ee20f847a078e6348bdf2ad14523bbcce353173548161983034fb9502a2f16285450871c34e5e4820105a000000000000000000000000000000000000000000000000000071b02771a1477f846a080a75eda56e9aa2d42642b3413c0e9464697932fb78e0a3eab0e3d0a6c110629e4e381fca00000000000000000000000000000000000000000000000045c73e2cfc593365df845a0828d961e33c5d8c110d40025a0c470038f9143adb9df2526f11234503cd86a90e3e201a0000000000000000000000000000000000000000000000011e4a4668a5edaf517f845a082925f70a42a8633fa28e1d369e47cc833e0a19d22b9450795322233e965ef3ee3e201a00000000000000000000000000000000000000000000000024dbcd0cf94b27408f845a083f8975b25917b7df8ba465bc82d6b4472b7883d2ee49412f33c76db8bf49be0e3e207a000000000000000000000000000000000000000000000000126c4a57abed78a17f846a0857f56b3cddd78c4aaaf8728697967a1077c02f54da55abdfa04b585ad1a70cde4e38184a000000000000000000000000000000000000000000000000064263e48896de706f845a087490cfb653047f997b69e61c7a1fc30f26358b189fb850deb46079b7f96ff2fe3e272a00000000000000000000000000000000000000000000000084d439be32c3d2095f869a08a0869b48cb256b682d977ea9bc491a3e1d479c22ec392a89df1b845f379cceef846e21ba000000000000000000000000000000000000000000000009aafb728bb2c481402e21ca000000000000000000000000000000000000000000000009ac05abad672f25ac9f845a095eb8b8ef7069f8b5c679fa94eca1f6d19856b88aa03f8eaa9ca2b675d797698e3e201a000000000000000000000000000000000000000000000008eb8f03f3f40e9337ff845a09ede93be0d8fc6a5eb9cf1c7345a85b7519d8487a727aef0c2f00ab966aa7716e3e20ea000000000000000000000000000000000000000000000013800b8c21e6e378361f845a0a1b89e3efbe19d22f0f2de6e743974967781c6b8e779d8458bcae97471cc4afce3e214a0000000000000000000000000000000000000000000000001569964a2a306a768f845a0a94b60751ceff49e15cca86a84cd4efda434ad5c2edb4d38d4fdc8553582b377e3e20aa00000000000000000000000000000000000000000000000007055c8637af4c799f845a0ac920e969edbd20b319b8f09e6c5edb5fdbef36ec770c6e8e8615f7c8dbff7dfe3e266a0000000000000000000000000000000000000000000000000014eaa66a1d78000f845a0aed6c24093e5abdac44c93ae0026006b58f9c9e3e3d3efe28aa052354c1d2ddee3e20ca00000000000000000000000000000000000000000000000001eac6d08bfd9dae9f846a0af136c4fec1e7946aef1b5e16763785d917b705abf974ed4b3d651f3777689cae4e38183a0000000000000000000000000000000000000000000000001b32b505867157627f847a0b440be4deffff278a0a54bece4d5bba517a3236073012092c2f7526f6434fd2ce5e4820142a000000000000000000000000000000000000000000000001659a19cff1f0d8c60f845a0ca44a2617bb49e924f4d410857de307ff46f4a5f397212319f5f108a9483beb6e3e234a00000000000000000000000000000000000000000000000004b89abd1a44a083ff845a0ce923bfef35df4b395299bc115a8bcd2c216db39539fc2860179bf5a8c79c956e3e21da000000000000000000000000000000000000000000000000153e17d8ac9567121f847a0d1d920a751518467a4afbad02d833a0722142520115df52e62a4524258bf04ece5e482013aa00000000000000000000000000000000000000000000000000af7ce6c0f1a872bf847a0d3e3b20fa7319b86dff7d4caea5efb7cb38df6f847944644871774561cbb2479e5e4820107a0000000000000000000000000000000000000000000000000016345785d8a0000f845a0d722500f1a1a9b3f1ad91802a20b180ba05ffe232f9a17da4eddda61a4632ac2e3e26ba0000000000000000000000000000000000000000000000001a24573153c793e68f845a0de3396827fa575355c40f2591e90c244df1fd101730e44da9e54c1d040587739e3e201a0000000000000000000000000000000000000000000000004979cfaa67a39265af845a0f5995e255aa2c800c3136844499a825cdcc83d4178273f5699d957072e2ea32ae3e266a0000000000000000000000000000000000000000000000000040140b5a3377000f847a0f6caf2559869b1744c19aa1ae15b6c45a8d6eea9961e6958111ec18b9b1184e3e5e482013aa0000000000000000000000000000000000000000000000002127c9a0493b06a0ef847a0f6cc6c4222dae194a09f19d368f4b2b85698ddef67e513b005143d861f8ea8e2e5e4820140a000000000000000000000000000000000000000000000001218b8a8c4ef40c5e3f847a0f6edbc4da13df00f9be8b14d0897246ee8b8426ce2aac633ff8e2ef1aed82681e5e4820141a000000000000000000000000000000000000000000000000915cfbd3569d31c4bf845a0fbe94dd69d14ade7bb30a2170823967d3b17cea6812f10ce99d7af34cc1fce88e3e207a0000000000000000000000000000000000000000000000000eec30b1bcde32c7ef845a0fc5da5621ecdf503c1b60d3853c14e4222d580d5bc84c71986837c27fa27ef1ae3e265a000000000000000000000000000000000000000000000006f127b9a5423018dfbf846a0feb42399ca12f84d56ee1233f1ceaa7ba80395fa620c0eee2be7fffb2dfebcfce4e38184a000000000000000000000000000000000000000000000000000460bc121655abaf846a0ff16d5a122c2eb199e8ef2084c012b4487fb7e03147d1078197667329aec5a2ee4e381dda0000000000000000000000000000000000000000000000000599108d865795337f90441a00055db1c41b2272eaef9225cd2750f18a33dcfe06f456a5d8bbff9f2785bd740a005902947d7d27d664fb8adef648d91225cd9af622622bc6017334d64ee900baca01d3fb04fdc6a2d6c977d0dfdc776498014ce6f919157e7dedc0e808359bf42f1a02a9f100b9103993ec2993a2dbe2dd6ab0ef65a4bc285359a6d2f66ad33120716a02c349f31d69293493b8c2ab64bcccd406f797edc7e1212b1bf52c9b880d9357aa033d1d1698be76ca5d91049be950e70ca725f45b8071d46047f3aed98e2ab9b4fa037d920089e11921f3bad8fcb28b6f27623f73b6106c6bfa773de07b1a86ace12a05858aa37184aaf71b925c7b1195e2651f175f2c7b9f554327fbf067105636b51a072c035c75929d7db79209e766c47068b10cba63d27a4398e5acdcc0d293f3931a075d179b340750ce57d2ecf17f98f27e78c742b8df016157c225ee4bb188bd639a07c0512acb854d8e91fe67b506dce9014e9ff1254c53d57c43d236ed5bcdb9332a07fe8dcb6d34fcedcf0ba0a0b5fa06de59c603cbc70e5e412f56789af5956f102a08b11f9ede7572b59be1d054f022f7461c08a14388ce60921f5b80e9eb9ad0417a08cf181065d916b677e13a8b08261f5dd2c87c6a137d26f35c60d201944bb9816a09a6d31c950cf59a1d1d442a6793f5f0dfb71b8df6e52f28b93a232a67238ec69a09d98752c354deebddd53535455198eacf8cfb934237d3523207f70386be5e3dca0a6b50e66a554852d72183d0018a031e0dcab4677a6096b5aaf6b614db4753667a0a72580ecd31d3907f6d97e38f044047d93e81ccbefd09661d34a6d7cf5ed4491a0aa97f9cb2e742b698cf6a3d3aafc16d74f2ed30deffa5ab5483d1e475d26c806a0add657f797c27ca305e224df1a7036a0467f58ed0a61d1fff55bbf4d6348cf51a0b233d602ee7e7852b80874e525944fcf0080ba7000c145d0ba99edc2e7ba518ca0b2940356fd89a84cf9a86227596a2f59f085c679d5f9450737d0ae4daf0efceaa0bd5496627759cada847047c1c161530363af6f4760cd5a05b62f0f2e7d7d4a11a0c269910e79664bb02e4ea6b58a6546a8f30ea3f5840ed79d34d06b85ebddd776a0c43de090e120c56163c78a79947fb033da57ecb0a8775adba22c13a3cf4abf83a0c565b8467ed0be432a896df92668a8397c5e16b4f50e6c53d4929ca811966139a0cc2e8e5cd534eb5e35dabd4cb4b69550879b28082441ee3da8b83455b07b7046a0df20db09d6de1c44987e388a59580c27855c014bc46effb29442a144f5f6ebdda0e12062fee1f3b1b93862cab62f50f623186a70b5189a76998d699bde08be3bf3a0e4a61b47dd0f7a272937ded65a59d4f6d9a0d7e356f5d0001ac052cffa90c191a0ee61682ec263cf9ac626d5a9b0cd5e26edbfa98b5b368b540e65bd739c37e3b7a0fb19a963956c9cb662dd3ae48988c4b90766df71ea130109840abe0a1b23dba8a0fb437cb06df854b0e9a5f9c071f54ee63d1407c751de4e81c7cb3a621a923fa3f90200d20a90000000000001dcd1e0269436549ee0b0d20b90000000000001dcd1e0d754b471539d2fd20c90000000000001dcd1dc89f86174a19c9bd20e90000000000001dcd1dc81b780d1ab2c28d20f90000000000001dcd1dbb7db1c53950735d21490000000000001dcd1e263352803343db6d21b90000000000001dcd1d60969f4824f8ad1d21d90000000000001dcd1d6ddefed9cc64ad1d23490000000000001dcd1d9a47ade57da4ad1d23690000000000001dcd1d98f5602f1a72e05d23890000000000001dcd1dd9af3b791adee05d25e90000000000001dcd1dd1a660ee491a62fd27290000000000001dcd1dd150b5dea092025d3818390000000000001dcd1cd3dcc103d869793d3818490000000000001dcd1cd0d61c41c31c01cd3818590000000000001dcd1d0c1f4d9f739401cd381db90000000000001dcd1cd28af3c0a3ad98cd381dd90000000000001dcd1ccf8b642a168c162d381e990000000000001dcd1cd1f75aa0ba6e962d381f990000000000001dcd1cc2112c4f12b9714d381fc90000000000001dcd1ccdb7ff0607ec890d482011b90000000000001dcd1ccbdfdd48edf1d43d482013790000000000001dcd1cb13aa7751d31d43d482014090000000000001dcd1ce632d38af93e519d482014190000000000001dcd1c9b65a4ed836f4f0d482014290000000000001dcd1c9282d8bf12178b8c0c0f86394c02ab410f0734efa3f14628780e6e695156024c2f848f846a027e994043f913917671926020e61f80e9d344c590cd84a611a4e66eed35d2480e4e38188a00000000000000000000000000000000000000000000000000000000000000f01c0c0c0c0f394c066ac5d385419b1a8c43a0e146fa439837a8b8cc0c0d3d2139000000000000000005939e6c550f99467c6c51383019552c0f9015494c0a6bb3d31bb63033176edba7c48542d6b4e406df8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e201a0688a1197000000000004979cfaa67a39265a0000000012245bc54fade1d07e26f845a00000000000000000000000000000000000000000000000000000000000000009e3e201a000000000000000000000000000000002acbaf92492e7afa61a92b1aa283df688f845a0000000000000000000000000000000000000000000000000000000000000000ae3e201a00000000000000000000000000049911dfc489568006593cf41781eac13550703f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f494c0e53ab8bc6e1f134cc52b02cbe8655d9f562a65c0c0d4d381dd90000000000000000000d770eb1e50532ac6c581dd8205a8c0f494c0f990b2800f237fff5a8874048f2c218343e828c0c0d5d4820124900000000000000000002324e65202ad59c5c482012401c0f901de94c20059e0317de91738d13af027dfc4a50781b066f9011cf845a01bce331f95603049939b113a9b550350bb5211cd73cffcc46c2f247a6d7093bde3e202a000000000000000000000000000000000000000000000002b6d578046631578e0f845a0519b10c201f018d3126f87a72ad07cc05f09a7bd903f0f757351e83cdd370b1ae3e25fa0000000000000000000000000000000000000000000020e0b9d102c639607b400f845a08796f7cd1ae14ad9d18ad1dd756ca9bd5b1ded08a1a029cbdcbf076630e41a31e3e202a0000000000000000000000000000000000000000000070493fa7d149feb1fc378f845a09e4fbd1ca1ca3f2d1ab98938cb11e2cd2408b2b559fed6cb26f4aba2f9860295e3e25fa0000000000000000000000000000000000000000000000174c816309b2976b000f8a5a01122ad22a463736ebcabe1ffc0287ab7db0d9e1995b696d6d5f26ae378d7a5d9a06d12d10bcbfde5f52ed4616b131cb09c52b5d4f0610906a60f7cc538184e5334a086b8c57e0757f0c3a2432a08485d6720a63076399a78b553d6175cea884e19caa0a93c393b9d73c86c9241ec40447c61f731d053e2c00795422397513efd2e002ca0c58d67b91536e945ae318f2f3959fe83fafbf45c20a6916d9e295f488bc75575c0c0c0f83b94c26d4a1c46d884cff6de9800b6ae7a8cf48b4ff8c0e1a00000000000000000000000000000000000000000000000000000000000000002c0c0c0ee94c2727414e591de8ed740980f78c56e1599f5b048c0c0d4d3818e9000000000000000000002d0f320f4efa2c0c0f9015494c28b4ee55c3f3c3a7310ba073ee860d56a11bb00f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e238a0688a1197000000000006c262eec93ac62bc600000008471ea7d2fd6d94c79d1ef845a00000000000000000000000000000000000000000000000000000000000000009e3e238a0000000000000000000000000000000000000a5c2a37f8f7185174d569f11bda0f845a0000000000000000000000000000000000000000000000000000000000000000ae3e238a000000000000000000000000000040f12393aebe397b025f0f5f551e904b189fcf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f294c2ba4f0f29687557bd107357e5b974a6558cbee9c0c0d4d381a1900000000000000000000b7f8dc4f2254ac4c381a102c0f9017894c2eab7d33d3cb97692ecb231a5d0e4a649cb539df8f9f869a00000000000000000000000000000000000000000000000000000000000000008f846e21ba0688a119700000000009aafb728bb2c48140200632f83b3a8a6dd6b3a8e8c2300e21ca0688a119700000000009ac05abad672f25ac9006324e1c7b0ca365349d94c1828f845a00000000000000000000000000000000000000000000000000000000000000009e3e21ba000000000000000000000000000000000000004f101cf9ff7e914b3c039869b88f845a0000000000000000000000000000000000000000000000000000000000000000ae3e21ba000000000000000000000139d6db306e7bd8781cea1ad5910cb2872cf290edf5cf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f094c33023a7b5e9c1d6cc4189ff60d1b582957f8930c0c0d3d26b9000000000000000000011faf7639f66f2c3c26b1bc0f8db94c38e4e6a15593f908255214653d3d947ca1c2338f848f846a0ec3b28e995691dceec552e4da2a5fcb8a2ff5fe83e14a0710683d8dc6512581ce4e38180a0000000000000000000000000000000000000000000000100000000005dcce400f863a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003d4d381809000000000000000029a86e8450ab27ae8c0c0f9015494c3db44adc1fcdfd5671f555236eae49f4a8eea18f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e201a00001000258025801a9fea7c9000000000000000003200ced8360ee41d9b497f2f845a00000000000000000000000000000000000000000000000000000000000000002e3e201a00000000000000000000000000000000000dce7ae8275d280be648e019bde75b8f845a000000000000000000000000000000000000000000000000000000000000001b1e3e201a0010000006c0000000000000aa725a0331d9b71d135fffcb3e7312830688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a000000000000000000000000000000000000000000000000000000000000001b0a09b637a02e6f8cc8aa1e3935c0b27bde663b11428c7707039634076a3fb8a0c48c0c0c0f8a994c3f822e94c321dd3ee53ca46b78098ea79b7ec8df88ef845a0e06ca6d6a9f55e10a529892a92452af02e401249d4684cef2254701a2bdb46d4e3e221a0000000000000000000000000000000000000000000000d416d3ee82591147c22f845a0e1953d2330c8ae316ede9f4d1cfa46ef799836a53537bb86686fa94340fc7519e3e221a000000000000000000000000000000000000000000cacf02e01e1458d7d3d543fc0c0c0c0da94c465cc50b7d5a29b9308968f870a4b242a8e1873c0c0c0c0c0f87e94c4922d64a24675e16e1586e3e3aa56c06fabe907c0f863a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000007a04fc648e708d8f69cd7ac3668ac52a76237ca386ea913760840ac4e7cea226778c0c0c0ee94c5bcfed20e3c1146939f29b3942a504a5b081daec0c0d4d381839000000000000000018ade4aa832054ddac0c0f9014c94c6093fd9cc143f9f058938868b2df2daf9a91d28c0c0f90130d2679000000000000000001ab5102d524e8273d381bd9000000000000000001ab58d081845ed1bd381bf9000000000000000001ab608074fc0aec3d381c19000000000000000001ab68306873b706bd381c39000000000000000001ab6fe05beb63213d381c59000000000000000001ab77904f630f3bbd381c79000000000000000001ab7f4042dabb563d381c99000000000000000001ab86f036526770bd381d29000000000000000001ab8d453a65f6cb3d381fe9000000000000000001ab8f6628b52ca5bd48201009000000000000000001ab915f782b52d03d48201029000000000000000001ab934c880dcceabd48201049000000000000000001ab9529560b54c53d48201069000000000000000001ab97053a757b6fbd48201089000000000000000001ab98e0c9efdbda3c0c0f294c610bda7d181a1a4159ad10a6775e7564511b9f2c0c0d4d3818e90000000000000000000008a3c2ac42b7cc4c3818e0dc0f294c677ece487d8843d71a8730507681422b3b982afc0c0d4d381cc9000000000000000000003110246679542c4c381cc02c0f9012e94c71ea051a5f82c67adcf634c36ffe6334793d24cf88ef845a052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02e3e208a00000000000000000000000000000000000000000001503436cfb7c4b872d0e3df845a0a254218e41f18636cb903aa2f0c79bc9acecf420fbf0c6cb721e2d1a76c66852e3e208a00000000000000000000000000000000000000000001503315b56b84675f08529f884a00773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca04fad66563f105be0bff96185c9058c4934b504d3ba15ca31e86294f0b01fd201a0cd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300c0c0c0f901c094c7bbec68d12a0d1830360f8ec58fa599ba1b0e9bf9011ff86aa00000000000000000000000000000000000000000000000000000000000000000f847e222a0000100003300330017fd0a4e0000000000000000000406422e542ecb388aff7ee38185a0000100003300330017fd0a4e00000000000000000004063d7a35129e4862ac30f86aa00000000000000000000000000000000000000000000000000000000000000001f847e222a000000000000000000000000000000ad00931b64332253b79cc01a13ec0aba64ae38185a000000000000000000000000000000ad009339d8dd8b461efc0160dff3a644968f845a0000000000000000000000000000000000000000000000000000000000000001fe3e222a0010065520c000028ed9534f75b74428bd3b380be2cfff12fcae39dfc688a1197f884a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000001ea00c27731cac1e121449a18deab4cdfc63afb79524904e9a0a1a4146c3fad6baf4c0c0c0ed94c8262e0d39503e2d9d23ce3c0c9915481205973cc0c0d3d2609000000000000000000002aa1efb94e000c0c0f294c895bdfe2d758a6fd07d7a2ba5dd24a1014e2832c0c0d4d3818590000000000000000000309c9f790fc2f3c4c3818503c0f294c8fc2f2e08844f6e28a7362afd8c847f7925bc64c0c0d3d20e900000000000000000402dcba8d7cc4f6ac5c40e82149bc0da94c92e8bdf79f0507f65a392b0ab4667716bfe0110c0c0c0c0c0f794c94ebb328ac25b95db0e0aa968371885fa516215c0c0d5d4820145900000000000000023b1d2765837579d88c8c7820145831f140ac0f8c094c9ca319f6da263910fd9b037ec3d817a814ef3d8c0f8a5a00000000000000000000000000000000000000000000000000000000000000006a006e19b5b71ac08ff2ded214d51d1a1d50d6141c6df303c8b575e6452c46b6beaa0243a29a879f85d582d7ef566191daaafba060067ab09ea58e58f534fb7fe331da02e7444e1dcf538780ee1ad019a97354d3151a7f5401006c2514689e41277879aa0dda6f4c15f798be1d364c3c8f74af42d33c7b174779f9417d9156b62551558fdc0c0c0f294c9cf82c17133ac0d55b5ec9daca5464260529c56c0c0d4d38183900000000000000000011ef9960ae72a48c4c381830bc0f87e94c9e1a09622afdb659913fefe800feae5dbbfe9d7c0f863a0000000000000000000000000000000000000000000000000000000000000000ba04c7ad7de04cdbbed7ac607cce756ab69f3ce6b2782a5114a9819ccb8d6b7d5dba0f8ad5150ff7987f0464cd848559b538152a0a36ed83b24aa0c11a5e92599ffd7c0c0c0f901fa94c9e25234a08a97b84ba69be5ec3dbae7efceb473f8d5f845a00f802f75b434022f278b7df1a7b27939b475521ad4d6abe1085059b8238dab97e3e25ea000000000000000000000000000000000000000000000000009577b1f00962acff845a09d1bf6e7ab54ead0d5e09cb854c8c99252b8180162427124d61c453c8a0ced02e3e25ea00000000000000000000000000000000000000000000000000000000000000000f845a0c326274d9bb1d2bfba31e78d4ee52df978c3bfa2a4abf03e1d81466889943736e3e25ea00000000000000000000000000000000000000000000000000000000000000000f90108a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000da012661a4849e4a87f27c9e27dc256775758e41d84c741595ef491e8b235b94ff0a049ab0e01a8b680fe30f908310c98990d96cab16d2860fddf6427683cdd7d89b0a0734ebaa36c3d592bdf9b040bb2c4a2898bbc834dcee29f0b053d59f4ab712047a0b77d6cf9e09aa2ef67e67ca879adb2bd657311f78f4a70636f897451e304f8dac0c0c0f9015494ca21d4228cdcc68d4e23807e5e370c07577dd152f8d5f845a042cd46fa0013bf95c0dd67cfc79c9a305e4cb6c006650413fe16b8dfa59c61d8e3e211a00000000000000000000000000000000000000000000000000000000000000000f845a050eb0985276ea25d0507ab28e29eb9bb845e9a09547727e6c7b346719d689645e3e211a00000000000000000000000000000000000000000000000000000000000000001f845a08111a1be5f0890ad24830bea78661e2c2e4a9bdc8521a95078611bd55baf7435e3e211a00000000000000000000000001103c0550771ffb586688ffbba90c6b85ebfc224f863a00a119bdc053a377f14890a6805fb9d8b22717c4e0cfc30127602db182170d862a093712c693d8d2610427a9e76c00539e3b0d15688564068f8bea21f0c26d5e6c8a0d72ac986e14b90cdbbe02e23a8d94b8f10b969959b07328f4e62d27111d958d0c0c0c0f094ca43f78dd8a9c8e96b53dea4e13bda504a0f8c4fc0c0d3d24090000000000000000000087a8eb85b4bacc3c24018c0f094caab29ecae649ae94a24bf7eba52a924ec56935ec0c0d3d24c90000000000000000000074516d2822081c3c24c02c0f094cab50a2b987fe307553c9ef2f4d4ac574a1ba13ec0c0d3d244900000000000000000000c069c8c605081c3c24401c0f394cad97616f91872c02ba3553db315db4015cbe850c0c0d3d274900000000000000000043779ef69b10159c6c57483018115c0f294cb95cb20f4d55a435e6474b9da5ef25f4d870667c0c0d4d381af9000000000000000000000000000000000c4c381af07c0ed94cbd6832ebc203e49e2b771897067fce3c58575acc0c0d3d2719000000000000000656cf1440ad0e816ddc0c0f294cc57297cb89986ae26d27d763fbe575522c67ab7c0c0d3d209900000000000000000151a4ef6c3339382c5c409820631c0ef94cc87b91e911d964805df6915510af1eeccc13552c0c0d5d4820128900000000000000000002386f26fc10000c0c0f694cdd583ce1f021a81388ff04d45836f24a0f26d97c0c0d5d482012190000000000000000000f4cc0186acc9f4c7c6820121820105c0da94cde3eb49d53d932c605efbc1bc425c6affa5eac2c0c0c0c0c0f294cdf060936cb2a58512d4e9860bfc94c1e4dbf485c0c0d4d38195900000000000000000004fe0f25bce3f0ac4c3819503c0f9015a94ce1bec6c2fab43d52c12802e13f481f8326ed2f3f8dbf847a00000000000000000000000000000000000000000000000000000000000000008e5e482011ba0688a1197000000000000be17f165955c42b40000000000010d6ad4c743de45f9f847a00000000000000000000000000000000000000000000000000000000000000009e5e482011ba0000000000000000000000000000000006844a775bb30a1b0b80c1f165f495434f847a0000000000000000000000000000000000000000000000000000000000000000ae5e482011ba0000000000000000000000000000000046be80b8ee5c6df1f314e74fc9bbd6d14f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f9015494cec9f77c90f54ef57acfa773822131dcca60f867f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e20ba0688a119700000000000016e44754449a0d2a00000000000a3dc408f990f76a41f845a00000000000000000000000000000000000000000000000000000000000000009e3e20ba000000000000000000000000000000000002bbcd064feb0aabd473713a534aa90f845a0000000000000000000000000000000000000000000000000000000000000000ae3e20ba000000000000000000000000000000003a4650b8560b118856a478225c057599cf863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f694cf420f085b284fad6b0f5d17d3772c315d11ed80c0c0d5d48201039000000000000000000001643d26c6345dc7c682010382012ac0f8e194cf6958d69d535fd03bd6df3f4fe6cdcd127d97dfc0f8c6a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca058d1ee36a2635b7cdabf6627c9ceeadc55e3dddd558db1b31e1c365e5228cedea06463ab5738278afcafb9b8bcb5b23cc6d342298f0b18756bbdc343ef7dd7f001a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0c51c65a607d170e09469c330237d35e163544dfad7c72dc16691ad981a11ff41a0fc19541bf3335480b9ec331368f41d19b99f9d7928bff4fd4600e6c1a99b2439c0c0c0f294cf7d915b40551130cf585de6df95d4ae62013e4bc0c0d4d381a790000000000000000000e360a566118c61c4c381a719c0f83b94cfbf336fe147d643b9cb705648500e101504b16dc0e1a0b035f62398c2f37b04e1eceb7c8e682f004b880b099118069ef0a8d3cb0fcdaec0c0c0f294d00f88fd0242ea338aa891c34c84296357a53781c0c0d4d381919000000000000000001430194affb14b8ac4c3819107c0f83b94d057b3c39d563bb16b05a3eb8ca8a2fa43c8df49c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f094d099bc9db95489b6a036623fb2c744f40dac983cc0c0d3d245900000000000000000000775ff949416d9c3c24509c0f8ce94d0ec028a3d21533fdd200838f39c85b03679285df892f847a066f6b2e45cb63245a76eec0485a03230238e69c78424d209ada7c9072fa24e30e5e482013ba00000000000000000000000000000000000000000000000000000000000000000f847a0a4e954e7aa230b38643562858f106670f2867a6d26fc141190e0b45577d9f076e5e482013ba000000000000000000000000000000000000000000000d59ece829d886dcf1ea4e1a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbcc0c0c0f094d0f8853333cfaffb59336cf4c521648faeba15fcc0c0d3d23b9000000000000000000000000000000000c3c23b01c0da94d152f549545093347a162dce210e7293f1452150c0c0c0c0c0f294d160842a0538dc5605d87c7b9e42a8904b0ac33cc0c0d4d38189900000000000000000002419503efd0576c4c3818916c0ee94d21fdab5bda02aba6c415a98649e529fa22e6413c0c0d4d381e3900000000000000000012dfb0cb5e88000c0c0ee94d2b344b91708e81c19543fefc5f9facaf60fa530c0c0d4d381d49000000000000000000348377f39614000c0c0f694d2c811d48b4cbccf8afe1428fbade11beffb269ec0c0d5d482013b9000000000000000008258391c21c73c32c7c682013b824df0c0f294d398784fbfd611cb3a43825986ed1be71649e553c0c0d4d381b2900000000000000000008d80beafcd95c4c4c381b209c0f9015a94d3c41c080a73181e108e0526475a690f3616a859f8dbf847a00000000000000000000000000000000000000000000000000000000000000008e5e482013aa0688a11970000000000000af7ce6c0f1a872b00000000038c16b563b32006e252f847a00000000000000000000000000000000000000000000000000000000000000009e5e482013aa00000000000000000000000000000000031b64ac74b2cf0fac4304eb27e70151ef847a0000000000000000000000000000000000000000000000000000000000000000ae5e482013aa00000000000000000000000000bb6067de8647bde5e24eba83c0f7704fbe92ccff863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f9019194d3c6cc89e3e260c0e2d47071b31ba1715b1416a9f9011cf845a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbce3e219a0000000000000000000000000bac849bb641841b44e965fb01a4bf5f074f84b4df845a07bcaa2ced2a71450ed5a9a1b4848e8e5206dbc3f06011e595f7f55428cc6f84fe3e219a0000000000000000000000101845adb2c711129d4f3966735ed98a9f09fc4ce57f845a09247b7d6487ac2b73ae4daed6d3093defc3077de1abc059ec7978bb4065925a2e3e219a00000000000000000000000000000000000000000000000000000000100000001f845a0db85417abd40b6e8de40c4dfebdbcfc91c0257fc733e6f49bf7f0c8af81a065ae3e219a00000000000000000000000000000000000000000000000000000000000000001c0d3d2199000000000000000000000000000000000c3c21901f842f84019b83d363d3d373d3d363d7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc545af43d6000803e6038573d6000fd5b3d6000f3f094d4b454f6f95d9c66b38383810de0fa8cb7c135ffc0c0d3d24f90000000000000000000075a66745fdc59c3c24f0cc0f9012e94d4fa2d31b7968e448877f69a96de69f5de8cd23ef88ef845a052c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace02e3e208a000000000000000000000000000000000000000000000000000003e1dd27826a8f845a0a254218e41f18636cb903aa2f0c79bc9acecf420fbf0c6cb721e2d1a76c66852e3e208a0000000000000000000000000000000000000000000000000000007060bfdffd7f884a00773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca04fad66563f105be0bff96185c9058c4934b504d3ba15ca31e86294f0b01fd201a0cd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300c0c0c0f84894d5055a782813da3862e9c9980bb047511960749fc0c0e8d3818a900000000000000000000050136f3ce511d3818b900000000000000000000001e8f1c10800c6c5818b821905c0f494d519cad6fbdb66ddb282d182d2c816c7bab07601c0c0d5d482013790000000000000000001bb7e1e1b4da3bbc5c48201376ec0ee94d5ab48f40bd9fe23c91c841d1285c249a4bf1bacc0c0d4d381f390000000000000000014d05c275a218000c0c0f294d609485c109e3fb9721b605d99c44e7c0c6b7d98c0c0d4d381e090000000000000000003103352214c1d59c4c381e03ac0f094d64fea829f2b117cc86be5f84697337449eb0a20c0c0d3d2729000000000000000000010b5914f45c26bc3c2720cc0f294d6e86437cdeed131f5ae496a81ebfc8ad32f8404c0c0d4d3819b90000000000000000000000fc33e39ce4cc4c3819b02c0f83b94d703aae79538628d27099b8c4f621be4ccd142d5c0e1a0c1c54f32065b0650d9213ccb9e868d7b2b3e86d37ccbc99e0a93122ed102f1f2c0c0c0ed94d7463d3df4081d751b26329ecad08ab5650821d5c0c0d3d2629000000000000000000046acab46fb3800c0c0f89f94d75a60afcbc113c1c76e42184663da141f839053c0f884a00000000000000000000000000000000000000000000000000000000000000007a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0a66cc928b5edb82af9bd49922954155ab7b0942694bea4ce44661d9a8736c9b7a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f094d773f422d9e505d05de084b36a07075b75209d7cc0c0d3d2169000000000000000000017a8e48b32cfbdc3c21601c0f9017594d774557b647330c91bf44cfeab205095f7e6c367f8d5f845a071dbd51ab73c2c78feef3bec8132148c156007f5ac50b5bd880f026a67d38d74e3e20da00000000000000000000000000000000000000000000000000000000000000001f845a08515324f70593fe473ac361da0f982276a44dcfb1777f82d75ef40d4b2839f61e3e20da00000000000000000000000000000000000000000000000000000000000000000f845a0b029eafeb51e5998dc0654ec19c40cf4043037bb24f6604f0e8e52fb667fa4e2e3e20da00000000200000000688a1197eefc04cdac13c49ced4ec03be7345b9a55555e2ff884a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000016a0087f745496bb462f7aaeebd5723ac6cbeab25cb4762f8980754b216072110ddba07ed53fac0420d4d8bcdeda0f96369166adb26777a0979e70920749d5c105d072c0c0c0f094d85ed0e5b0130e1f9b9f7ae59a1c73387bde4651c0c0d3d23c9000000000000000000004eb87fa5c6881c3c23c01c0f294d88e6bf720452c3fdf92dd89ff55d8136021e71bc0c0d4d3819a900000000000000000000131c2ae2a225ac4c3819a02c0f694d952f539b8612694378e9298a1aba62036c010e3c0c0d5d482011e9000000000000000001a41e270ccea44bec7c682011e820119c0f494d9c2170878db71fcdf4ea2c7cd1d606475cc384ac0c0d5d482010190000000000000000000049d3543afe84fc5c482010119c0f294d9e0de3a144db007d0cea41905018ebe84a19a48c0c0d4d381a9900000000000000000000033d758c09000c4c381a901c0ed94da6e0384c4ba56ceecb64f625acf1fd4214ac645c0c0d3d23b90000000000000000000073ac27a9de59dc0c0f294da941d5041636b0d45a372d95cecb46deed26573c0c0d4d38194900000000000000000000138207ead925ac4c3819402c0f9150694dac17f958d2ee523a2206206994597c13d831ec7f90fc0f845a0010ee45acead1949877eba539d021f02e78c799e290ff49cabf14eccbbc80dc8e3e231a00000000000000000000000000000000000000000000000000000000e6624b6cdf846a0012e2698f7c0296097db4431e04ec2bc8f617063fec7dc24b54bc9a6ef792e69e4e381dca00000000000000000000000000000000000000000000000000000000000000000f846a006662ee7694feab155ac1403967ed86459f3ae1f78b36f3ca5b990c98639e521e4e381a3a00000000000000000000000000000000000000000000000000000000000008bd4f845a0091db9d9a9c6e118727ad07f6cd0cfe3b3277fe2b643c6b0eff07704f324c69ce3e231a000000000000000000000000000000000000000000000000000011d6d8d1dbb37f845a0098542e5dca0f570929f6e8a011795ad3c1017c2e6cc71e0357aefaa2827ed81e3e263a00000000000000000000000000000000000000000000000000000000000000000f845a00bb53dc5636274aaa5c5f6b30de6383ed141834d816f70940a3a95f81a84ffd3e3e233a00000000000000000000000000000000000000000000000000000000000000000f846a016453497ac0bdcc383ab404efc0c22904d0f8eb7246a1f4a0e894eec2884335ae4e381cea0000000000000000000000000000000000000000000000000000000000c769ce0f847a0169228ca33ea854d54aa1e506e59ec687f618a41074f5f5de937a0e9c6343e5ae5e482013ca000000000000000000000000000000000000000000000000000000b8ce04cc409f847a019853c897618c9c0b744a559e6fa594161c914c8420240a87d50a95be8ee6553e5e4820122a0000000000000000000000000000000000000000000000000000000000da29de7f847a01e94d915672230ecb7d63beff03cc51238d99e81931e330cd7083b8d0d745202e5e4820122a00000000000000000000000000000000000000000000000000000012268a6f195f846a0205f3a071cc7f68d7ca7bab24ed6f78a0e2ba9377d5cc04343fa91fe6ea960c8e4e381b2a00000000000000000000000000000000000000000000000000000000922a7e358f847a021386081866b0f509bc0fe2dcbac3f64ced018a940b764f8d81d99ae5580f907e5e482013ea0000000000000000000000000000000000000000000000000000000000006f198f846a0326695ca13563327c27f892d50db8b7940486eaba321f5d2ae15606c636a3d3fe4e381d3a0000000000000000000000000000000000000000000000000000000000e327db0f86ba035d7fb7665514f774d2c2df607e197eb8674b6e63d2638472758647a2e67406af848e222a00000000000000000000000000000000000000000000000000000041492d78b72e482013ca00000000000000000000000000000000000000000000000000000041a5322a0c0f845a03ae4a71841db7a729399381506b05d53bdc2176f18642012771fd15317f1b20de3e21fa00000000000000000000000000000000000000000000000000000000006350ce0f846a03ee1bd6d278517ad2a4f241292ca82943d4d026e8794f828f06af8cb9d3ad594e4e381fda00000000000000000000000000000000000000000000000000000000005d0f3e0f846a04166fd35951e38bb7049abf2664fb4ab3f47f65fca54e107e7976e2cc1a65447e4e381a7a000000000000000000000000000000000000000000000000000000001889e7dc0f846a041e9f97bfce85596afc82230caa082dc49bcd13952da65f4481bc29e8a29549ae4e381d8a0000000000000000000000000000000000000000000000000000000966f2e1c0af846a04966c331fd21bb95e5096f54ff5bc865ffddab6a0f3c4edcb615c80b8d472ba5e4e381d8a000000000000000000000000000000000000000000000000000006696c09be90ef846a04d05df8ee08876e0beecf6bf7f10f3f49de471880214be117126cbfc989ba055e4e381bba00000000000000000000000000000000000000000000000000000000000000000f846a0507800023e98b83ddb7530e2ca770c8b7b257ee4814ae4d656f41e451029274ae4e381b2a00000000000000000000000000000000000000000000000000000002eed13e5c7f846a05e797cce47bcd782ced8fd290c03ca6235fd69dc45367ed35cffacac1d7bbffbe4e381a5a000000000000000000000000000000000000000000000000000000001122e6e00f88da065e825459894c28caa3bf45c046bcb806478983a744f47c04782164e994daae2f86ae21ea00000000000000000000000000000000000000000000000000000b2bb5aa4b5a1e21fa00000000000000000000000000000000000000000000000000000b2bb57a7a241e381bba00000000000000000000000000000000000000000000000000000b2bb8d2b1ab3f846a06f5981580c988edb58080dfdb48d7c0c4d2c84e66babd1859ed594936d9a6cdce4e38181a000000000000000000000000000000000000000000000000000000000074a2660f846a0702d961875f786cb53ba76bd893cd31e95d09cd1caab6f45f8a5b9a0fca3c131e4e381f4a00000000000000000000000000000000000000000000000000000000015ef3c00f846a075163de790a78cc82f42f45d14b90b6ef3ea879a2c78dfec960d143b0272441ce4e38185a000000000000000000000000000000000000000000000000000000000c532860cf846a078b35599871be95768b2fdfaf9293a4491ecdc8ef25b872ee404fa1e441436e0e4e381b6a0000000000000000000000000000000000000000000000000000363c3840c574df846a07dc909bd982997536f175846bfe9d231280ca5132f05011845e67862a8ada194e4e38181a00000000000000000000000000000000000000000000000000000000000000000f846a07e6dc8ad1383fb566081d3e4df148d026e5118386ed3ade7d2eded6ffc3a917de4e381d6a00000000000000000000000000000000000000000000000000000000008e95811f846a08300cc53f61a4c7b23603082c3b6f30c9e609cf582d9371dfc97f1a84416fca9e4e381a5a00000000000000000000000000000000000000000000000000000000db75da653f846a085456519d71699f68c07e97ca0e826687d8ac34844ae6047518b5c19d6acd032e4e381dca00000000000000000000000000000000000000000000000000000004e79fcdc68f846a08c5fad4f7ce194285eb0f9b7c7a7c8742b655fb32af9d297b1b81f559efd89b4e4e38185a0000000000000000000000000000000000000000000000000000000003b95f0b1f847a08d55779954f3aeb0eb7fa7a0f55012a69cfd62879b9e3628d84f73f93afb059ae5e4820135a0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff845a090f02405e941d2e0589e86ae70c028f367128bdb627bfc5380c851a078b4460ce3e232a00000000000000000000000000000000000000000000000000000000005f5e100f846a091f0e4f6e05dbbb514bc4cf940cca183556079b5e5807deaea0f5cfe137e71f6e4e381a3a000000000000000000000000000000000000000000000000000000011985cbed8f847a09417a648468c3cac073b004e2e206a28a66554481070d068ab6c33ccf2e2acc9e5e4820124a000000000000000000000000000000000000000000000000000000008c8a1d187f847a094cd747d0493e4c3e2a9093b7045c88cc41d729edc4afa05e4063b39502084abe5e4820103a00000000000000000000000000000000000000000000000000000000023c34600f86ba0a3b88bef8df2f6096822c988ffd14e98b0513d518130e72efa1debea259cbf43f848e381d3a000000000000000000000000000000000000000000000000000008376e408d302e381d6a000000000000000000000000000000000000000000000000000008376db1f7af1f846a0a4c2b4b10c784ac08d6e785a62c7805ab5b11cf48dffc50561d457a62988e50de4e381a7a00000000000000000000000000000000000000000000000000000000403fe8046f845a0abdf55938d7687a176c3b405c64b0999584cfa1076137127b94f01ac164d48dae3e252a0000000000000000000000000000000000000000000000000000000014a873233f846a0aeaaef6bf10eec9d792ab619d7408738d380fa1a2a3680f4c9d150e6a2274f4ee4e381b1a00000000000000000000000000000000000000000000000000000000000989680f845a0aeed24d3ec3e9b34d78813777ab6cea0c58c58eaa158a04e69dbfa60b4741e25e3e263a00000000000000000000000000000000000000000000000000000000059682f00f845a0b4e1f9d6d9eb60dd6385737433550a0c037867d7a9b23e9958e17f494701c3ece3e252a00000000000000000000000000000000000000000000000000000000000000000f846a0c1b90b187e69c17dfe5dc7c7d6ee1cd03e0a45bd6cb877905c093f403f880bc6e4e381b6a0000000000000000000000000000000000000000000000000000000000b9f76c0f846a0ce14656036e17a87cea5093cc525ed309b0a84b2a8e7933d20e718963514a00be4e381b1a0000000000000000000000000000000000000000000000000000000018c0a0004f86aa0d06ef82a07326a6e964ce1700825547c428fbc96deb2e33e7e20498001080d7df847e222a000000000000000000000000000000000000000000000000000000570ebf25b13e38185a000000000000000000000000000000000000000000000000000000570aff0e2a1f845a0d550f0f4b2167d6425eda4d01862155673532665dc1b18f3bf88e2810adef465e3e232a00000000000000000000000000000000000000000000000000000016ef922b77cf846a0df70affd7af96966d2670c0f3ed33a9da6b177986dc23d985c9fbdd265f954a7e4e381cea000000000000000000000000000000000000000000000000000005e96593dfb12f847a0e9561e474d18eee2e0b1eadbbf02bc673103c4f888c69050023dc41f7eab4e47e5e4820124a00000000000000000000000000000000000000000000000000000000000000000f845a0ec455b5dfb0a23ba20e161abdbe1ac6daca7a5bed55a5172df14e281f6576e02e3e21ea000000000000000000000000000000000000000000000000000000000022f0214f847a0f2b603b8b87884f80a87138a09cd511bfd30ff9c70d4454c852060eab7d35cc1e5e4820103a0000000000000000000000000000000000000000000000000000000002b4dc48cf86ba0f94b05ba7d0a6aa6692eecf3cf30c6ca5d527c3e5fd03d7081ff14e4910ec033f848e233a00000000000000000000000000000000000000000000000000001187e34fc97c7e482013ea00000000000000000000000000000000000000000000000000001187e419896f7f846a0ff2acf1040b8375aae9ffa186abdb003c9aec45e162f5b564ab4bc02ff148678e4e381f4a0000000000000000000000000000000000000000000000000000041d189bdc4ccf90528a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000000aa00a2899f983353d31758d7dc1b242eafdfd6645fea4842fbab8a68ad7e79074ffa0125617aba6520df91ba2f6482cb0f4289d91d40d63d800e4260764bc629f6b28a022fddf1564c6df0782dd8fdb3d9c2acb66635cf7ce8550a7e275f8a61aebbc48a02a63c13458ad66368443e3d2e2647e67d325cffe9a3e00131b204db545d6daf5a02d1b4c814fbdaef8c9507ba0630d2dad0af80fd2f182cf981c7634bb9c96ec75a02fa180f7ea52504da27316a4288fd6a940ccf7214a708ca5c4371e06aa8d560ba032bfdf0ea3a8758e06fb5062d44430296208c490a4ef6f74ef7daab46a9daa33a03ad2db55fe5657fe773e3b7111e43f4b662a181a20e875b3b8be52dd9f0e2333a03e1b68b6d651153b7a461a5b6ae597ec232abbb94ddacdfdc6e2c1516897ca62a0447f801ecd7e6ab47cbe7ad0ce36e9df21f75f7c2044ee5318b3647aceb856eba04f423963bb26e5e82fc5f4d134e79edafa336aab3357b18cf082b215262a8331a0513e3e116209adb6412b240bd8470864f36bb58bbd57ffae28c277b2fb1996a1a0543e9815de75f84feb85b9a4bfc718ebb17c5fc8fbcd4593544ed6b8ad77e291a057a88ac119e9fd51495848929b3bd1b2afa77fa8ccc7197b0769bec85463aaa6a0759ebf061baf585c99e31ae2e878792704a737b70719d1ed8895fe7d10c01bf7a076c3196c40c1047b842470c952d43291e241a446825b4c8ce4a16add3def3f38a07a464961c125fc990109c53fc6324f805cc788adf74934778792f1f7cfd292baa08d2f8ed385a5e54ce1fb7697f6dc700f7b39fc435bde364c6104b657a1b25874a08fe6c2b94cfeb65caef5a01fd4f3f9e4001d9f1261cad7bc858be81b37a2bb70a0911b0cefa008e074d4280934de50b4b1b8f05de617405d8de4c6ed613956722ea09598b4e483d0cabe0a56d7eaa8d8a372e6b1ea15e197d2a68207d90e2240f04ea09666d24b0b766b5ea0b25f70d2919907474d959623cf4270f64590edf7474894a09c896fe541acf9f15ae430cc488a39173f95ac859a96fcfd6aa32818072410f0a0a747ca6bc08aa9bebf2827869a8f6f5a67ddaec42654c8a09a958368cedf919fa0aaca8adeba41402e798d23c6fa1e4c59bebc1b015479e88abc524873577606b7a0beaced3d2b647528b23073319ec0275def0dbad9c424c3f35b17e6024731a60ba0cbb01fc6425c1a34b1f955b23ec29481607a8b65109024ad87949f3acb43077aa0d1eef4f4158ca69b8eeec0f5e41d3a88b8f58a6a1cb0ebd1a64a77d20eb5e508a0d297114012a56198e4078fbbc01248b079e0d51b2f5dc2ca3c038c5d19170832a0d2f26d0f1caf1970b353c424c3383553e2250f3adaac73ea5a85bb766824d6e5a0d8e9d8ea6f134d03da245979710275316370450c0e8a5afcfc4e8e535674cd77a0ea66b23739c88c4f9d97acf50a97d8c337fa4f2ab587d059ee953d0cda66978ba0efef8b0f5a82fde715bb2988d0cb6b4085bf6a1886304f75dd2be5ca27f23529a0f7d9cb707f6bcedae1da42a45421a9c7cc1aa05971d3473ad51b9515039017fea0f991184810abaacd6ee89d436306af648330a6b462c169e9594a6e9a392e9fc4a0fab60c9cffe3fcdc6b3fe890e5e7f90611ce0db7e7302ca8dcce864818bd4d51c0c0c0ef94db63440638307c8d80b638426eed8df6cc27a210c0c0d5d482010a9000000000000000000f03412db11f4000c0c0f9032a94dc035d45d973e3ec169d2276ddab16f1e407384ff902aaf845a00000000000000000000000000000000000000000000000000000000000000001e3e208a000000000000000000000000000000000000000000f08c4108c8269b504ff946ff845a031adef62206227419133dd9a6b4041532c22595206a596cf74f19493bfc8f368e3e208a0000000000000000000000000000000000000000000000009d18dab239dd07072f86ba0504a62fae10df45f50376ec7922e386a7acac2c4b795f91a639350bbc3d00c02f848e204a00000000000000000000000000000000000000000001122d89332dbc79b0f6875e482010da00000000000000000000000000000000000000000001122d97ee29c50d0c7c89ef845a08d756b0c25da8ff78cb57dc7f0a84e0129be053d02c43098e1e25e3e9e2bdf73e3e208a0000000000000000000000000000000000000000000003fed7d01c7310c03be34f845a09201bf90a4e7720a6332e6fc45789e7efe49a64f51b014e374446558ec26526be3e204a00000000000000000000000000000000000000000000000996a23b60473780000f845a095bf9ded295729c1f99631fe3757452f817fbca3d1318ec783e1a9f215498212e3e237a00000000000000000000000000000000000000000000001adae3b3b71d8d00000f847a0b475e89f4461c0c02a45185b854f9a1ed04bace2c9a69f8c64679970b5791ab9e5e482010da00000000000000000000000000000000000000000000000000298577e53bc9fd7f847a0ca89db3ac11bbc9e449a5d63fe9e404d168d96d38850e71570dbf9349d6b43c2e5e482010da00000000000000000000000000000000000000000000000004c4cb1f8fa3e5f28f846a0e542bb1f99cb2a44bc925b4bfc0ae5488e088171da1df43f473ca7e6c0e907ece4e381e6a000000000000000000000000000000000000000000000005fd2fc334d78240000f863a004af2650cdc358b537adffd5f648a345270bd1eff29d11e7735cd7b84ef65741a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0c1c4ba3e29f945ec5afe27ef72b2e9908b20c1f712a4a8aa71a582b97b3a0789c0c0c0f394dc7bf4d19f98e3082251a75938ce4775224b19bbc0c0d4d381b390000000000000000002086101154a2221c5c481b381d6c0f86394dd3b11ef34cd511a2da159034a05fcb94d806686f848f846a0ddeafbfbd84bd37a420faa42e95f479566d43aa1dc6c007f7909133572b74234e4e381eda0ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc0c0c0c0f594dd3d72c53ff982ff59853da71158bf1538b3ceeec0c0d4d381829000000000000000009079aeea0ade3ebdc7c681828308153ac0ee94ddfc5a330f44463d973cb8f914bcfd1804e270e3c0c0d4d381ec90000000000000000000016bcc41e90000c0c0f694de303e9d6b88ebe371cebf9a519fa4dc48089a85c0c0d5d482012f900000000000000000001197feb2285099c7c682012f8203cfc0f88394de632c3a214d5f14c1d8ddf0b92f8bcd188fee45f847f845a00000000000000000000000000000000000000000000000000000000000000000e3e208a0000000000000000000070000f0d114232f9d971e4e759cf3c352759cf4a63e74e1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0ed94de7259893af7cdbc9fd806c6ba61d22d581d5667c0c0d3d25e90000000000000000000337f76fed71e24c0c0ee94de7562cdb6e53fb9dd1e39c8407ecaeab5b51fffc0c0d4d381da900000000000000000058b71811bc86678c0c0f494ded4e991819b9e66ad5302fe3f1932461e69ceddc0c0d4d381a3900000000000000000000bff4b2dcbd3b8c6c581a382015fc0f9028494def1fce2df6270fdf7e1214343bebbab8583d43df9011cf845a00000000000000000000000000000000000000000000000000000000000000002e3e204a000000000000000000000000000000000000000000037b4a1998cea8c50a767d8f845a00000000000000000000000000000000000000000000000000000000000000016e3e204a00000000000000000000000000000000000000000003983c2a9889fdbbaab586cf845a03773aa40f235bd6d49b3c5ff1680c632908957bd1dbb8aa897a3dbba9148e4dfe3e204a00000000000000000000000000000000000000000000000b76b9b90ad95fbe893f845a0a103e3ca16ac9d936492ba26988f3c70ff13ddc992f8ebc1a189fd16ed521a9ae3e204a00000000000000000000000000000000000000000000000949160848abf9e4446f9014aa00000000000000000000000000000000000000000000000000000000000000012a00000000000000000000000000000000000000000000000000000000000000015a00000000000000000000000000000000000000000000000000000000000000017a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec475a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec476a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec477a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec478a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec479a055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec47aa055f448fdea98c4d29eb340757ef0a66cd03dbb9538908a6a81d96026b71ec47bc0c0c0da94df31a70a21a1931e02033dbba7deace6c45cfd0fc0c0c0c0c0f794dfaa75323fb721e5f29d43859390f62cc4b600b8c0c0d5d482012a900000000000000022be878d073838e9bbc8c782012a8304aa4ec0f8a294dfd5293d8e347dfe59e90efd55b2956a1343963dc0c0f864d381d39000000000000005b6cce2695e97eb2700d381d49000000000000005b6cc82ff24f6f0b9a8d381d59000000000000005b6cc8265aad106a8ecd381d69000000000000005b6cc81c9d9c97edd09d381d79000000000000005b6cc81306736370751e3c681d383b7c89dc681d483b7c89ec681d583b7c89fc681d683b7c8a0c681d783b7c8a1c0f9015494e0554a476a092703abdb3ef35c80e0d76d32939ff8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e224a000010000330033000002f5b10000000000003f9c739b2ff7c4bc2d1c6276a133f845a00000000000000000000000000000000000000000000000000000000000000002e3e224a0000000000000000000000000000012e8262664e27aa91412d1c5ff02a109fc66f845a00000000000000000000000000000000000000000000000000000000000000008e3e224a001002a700a000033873a4a305266ac5424eee1c50700152f3d302280688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000003aa0219bb19de15289a5105980da3f53414be34e28ab30c2a2f67932fcfb522122dcc0c0c0f9015a94e092769bc1fa5262d4f48353f90890dcc339bf80f8dbf847a00000000000000000000000000000000000000000000000000000000000000000e5e4820141a0000100004000400023ff7a1800000000000000002e1f5565d7a99913009b4152f847a00000000000000000000000000000000000000000000000000000000000000001e5e4820141a00000000000000000000000000000000475f7a5b3622044481b93387a4745b816f847a0000000000000000000000000000000000000000000000000000000000000002be5e4820141a001000000000000000000090768016a3eb158249b59fffe0c48924698688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000002aa063187d71e139eee983a88d0737447c7451979b3dbb75903c76b5fe430d36588ec0c0c0f85d94e0cf093ce6649ef94fe46726745346afc25214d8c0f842a06a050807f83537a6fb37f3bbb52aec8cf8b181c24d1ee209b83e85d563959f94a0b701f162d2f11e4238517eb7c8562a022035aea7e623e23c45d3c52011bedc21c0c0c0f9028494e0f63a424a4439cbe457d80e4f4b51ad25b2c56cf8dbf847a03bba9108b904cfb969b317a6a0847c2d13a92bb924e87f843935fe7b8f315911e5e4820140a000000000000000000000000000000000000000000000000000000009dcca783cf847a09d8106a7b639091777047eab6393a843ae4f778b78087f1dc0a2a206ede77128e5e4820140a000000000000000000000000000000000000000000000000000003f96d72dcb22f847a0c0bb2d864141e3dcf7dad97a717c50fb6136318d913ffa59cb4f47ed4f0c1898e5e4820140a00000000000000000000000000000000000000000000000000000002432e46e5ff9018ca00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000ea00000000000000000000000000000000000000000000000000000000000000014a011234190717ef1d3c4e2cfd4321470b7159a7441273729cfb45569397b76f4efa05c112e25c8ca97e0ac5683e85b3a24d8fba09abed7a3ac95561d1e3222ed5324a0ac6fb4f0f81e11dc1f89f596e243b0ed419667343ccffe17523030f10980cedda0bbdb6c46a65073920ce1493e5770cf927f61df994d2df4d42ef47a5eb0452f16a0c02c10ae588f2466ea6e6350d07ab12ee7521cf6f0c110cec9b4bd1ec5838085a0dcfe32d795676ae47de7ed447c658398dd1361446ecb596c0337c8fe9e7951ebc0c0c0f87e94e13fafe4fb769e0f4a1cb69d35d21ef99188eff7c0f863a0000000000000000000000000000000000000000000000000000000000000000da00000000000000000000000000000000000000000000000000000000000000012a0e464df08b1a6bc4a7efd0b57900b5ac59bc86655560732a1d4391a671c7e1d1ac0c0c0f9015794e1a4506697e653565b80cbb773760f8067e34ea3f8d8f846a00000000000000000000000000000000000000000000000000000000000000008e4e38184a0688a119700000000000064263e48896de706000000000dc921fa94b282387beaf846a00000000000000000000000000000000000000000000000000000000000000009e4e38184a000000000000000000000000000000000004e1c74d37b72056e36919970027f50f846a0000000000000000000000000000000000000000000000000000000000000000ae4e38184a0000000000000000000000000000083195c77d604047fd4b8583ec985e03be744f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0f494e243b5b9745300354d447e9fc57d9d931b86efcfc0c0d4d381a4900000000000000000036184f09d729764c6c581a4821ed6c0ed94e3478b0bb1a5084567c319096437924948be1964c0c0d3d2159000000000000000268754b4f10bb1d84fc0c0f85d94e3b83f79fbf01b25659f8a814945ab82186a8ad0c0f842a00000000000000000000000000000000000000000000000000000000000000000a0d31f4fef635961ffe318d8a370546958486ab4520b40eac96ae24caff178bdbcc0c0c0f294e4166f06730fe19e6487a56ba79b2371b4c52515c0c0d3d2039000000000000000000022aeb1846a4157c5c4038203c1c0f83b94e49210ad0684dfa3690734e239ac816bf483b021c0e1a00000000000000000000000000000000000000000000000000000000000000000c0c0c0f294e4c72b5f538d8619f2c459bd3c706e791145a93bc0c0d3d25d90000000000000000001ec6e6837ace33bc5c45d8203d9c0f494e5414ccba3b083b56930d7037625fa734568da1ac0c0d4d381e990000000000000000000984db3050d9bb8c6c581e9820333c0da94e592427a0aece92de3edee1f18e0157c05861564c0c0c0c0c0f83b94e59dbf08cccf8d1ab90156b9664d31fd20bb2ac7c0e1a00000000000000000000000000000000000000000000000000000000000000002c0c0c0f294e620a6b35852c88578d9e611a3bd1d14d2694a9ec0c0d4d3819f9000000000000000000029ae2e03b7731ac4c3819f24c0f494e680366019c64871ff35a6996296782e5a25920bc0c0d5d482014490000000000000000000074516d2822081c5c482014405c0f094e68df48c92b21367043797c24f62c392c5d922eec0c0d3d24e900000000000000000041226775a62e267c3c24e2bc0f9015394e7351fd770a37282b91d153ee690b63579d6dd7ff892f847a0efcb751bd2e77b8fd7a2dde57afdcd3ef309ad50df373786465e397276d8874ae5e4820142a00000000000000000000000555ce236c0220695b68341bc48c68d52210cc35b01f847a0efcb751bd2e77b8fd7a2dde57afdcd3ef309ad50df373786465e397276d8874be5e4820142a00000000000000000000000000000000000000000000000000000000000000038f8a5a00000000000000000000000000000000000000000000000000000000000000097a000000000000000000000000000000000000000000000000000000000000000cba0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0bc7d7a646c08b125fcff2052fb7b7c18018756a26f7c9cb7febc577fc6dbbed5c0c0c0f9015494e936f0073549ad8b1fa53583600d629ba9375161f8d5f845a00000000000000000000000000000000000000000000000000000000000000000e3e201a0000100009000900057fef16f00000000000000000805834b02ee9d244821cdabf845a00000000000000000000000000000000000000000000000000000000000000002e3e201a00000000000000000000000000000000008a6f21839d6162ba005716279a2c995f845a0000000000000000000000000000000000000000000000000000000000000005fe3e201a001009f41af00000000000a80beb2d6656d0ecaff73fff10682cad28d688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a0000000000000000000000000000000000000000000000000000000000000005ea09c04773acff4c5c42718bd0120c72761f458e43068a3961eb935577d1ed4effbc0c0c0f9015a94e9c761b372a9e98c966baac334a05fc8c12771b3f8dbf847a00000000000000000000000000000000000000000000000000000000000000000e5e482013aa0000100007800780040fe7b1c000000000000000001c39fcf3a5a31906b087ef7f847a00000000000000000000000000000000000000000000000000000000000000001e5e482013aa00000000000000000000000000000000f26f9484874dbb131523596db116de20ef847a00000000000000000000000000000000000000000000000000000000000000048e5e482013aa0010370fbf0000000000672b98346ea4509ee4b13b2fff51fbbb8986c688a1197f863a00000000000000000000000000000000000000000000000000000000000000004a00000000000000000000000000000000000000000000000000000000000000047a08a9a513b9791a18d5272680de0b8170980d584db74d5cc608d89e97f4ae4c892c0c0c0ef94ea1efc089232816a401a20e85ddab94d3b8264a4c0c0d5d482013190000000000000000000055487da27bf18c0c0f85d94ea674bbc33ae708bc9eb4ba348b04e4eb55b496bc0f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000005c0c0c0f84294eba88149813bec1cccccfdb0dacefaaa5de94cb1c0c0e8d381a890000000000000000212a79f7db44ceb9ad381a9900000000000000002239e639fe4d7ab9ac0c0ed94ebc6a1749f2c536c161b6d5ad88276e7a27556cfc0c0d3d20690000000000000000002d31d23291f7387c0c0f194ec1e2f0fb0a2147e04aad5ad9e8e86fcff13a144c0c0d3d20a900000000000000000005ecc46b9b2e6aec4c30a81fcc0f294ec69aa3aafb9187a466f0ce0f75a8bb7e6786a9fc0c0d4d381f9900000000000000000015f64eba76d0ff5c4c381f914c0f9015494eca3607e271b53ec39eb3a8f70a0f697c0551ee9f8d5f845a00000000000000000000000000000000000000000000000000000000000000008e3e214a0688a1197000000000001569964a2a306a76800000000000000b3ef4a469f39c9f845a00000000000000000000000000000000000000000000000000000000000000009e3e214a00000000000000000000000000000191c676f69026e369f826b1d7ac6b2dd0c04f845a0000000000000000000000000000000000000000000000000000000000000000ae3e214a0000000000000000000000000000000002d8cdd05a0bdaed885fc7648be5c53b8f863a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a0000000000000000000000000000000000000000000000000000000000000000cc0c0c0ed94ed310c9d7d88828f4c66735ec48fb932d7cf2bb9c0c0d3d26c900000000000000000000d29ad88e49000c0c0ef94edbbbd423691e6d3011baebd58e9a9234a2dd469c0c0d5d4820142900000000000000000008c507cc64e178dc0c0f694edd1ae45b7727fe87bb924abbcbf7a662475f17dc0c0d5d482012b900000000000000001379864a46f17beffc7c682012b8266fbc0ed94edf48a5981da93ff92c0cded4d2a892b72ddcc01c0c0d3d2539000000000000000000074c6127c90a004c0c0da94edf63cce4ba70cbe74064b7687882e71ebb0e988c0c0c0c0c0f9014594ee030ec6f4307411607e55acd08e628ae6655b86c0f90129a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba0000000000000000000000000000000000000000000000000000000000000000ca00147d6e0a075db7ac4e79e624ee65862ab0638d1f09c1e8bb6764e99aaeaf702c0c0c0f494ee128271d882ccc1a14ca82dc3bde5ea369165f4c0c0d5d482013890000000000000000000003520af1f8fb0c5c482013810c0f294eefc04cdac13c49ced4ec03be7345b9a55555e2fc0c0d3d20d9000000000000000000650903e44e98f81c5c40d820854c0f494ef74e87cedb44c84095c1d5f5198e657756f51dac0c0d4d381e6900000000000000000005e2f762983f4d3c6c581e6820129c0da94efc2c1444ebcc4db75e7613d20c6a62ff67a167cc0c0c0c0c0f094f093f201d85872748a3a1f8f09808e713cd9c0f9c0c0d3d246900000000000000000000784295118ffd1c3c24610c0ee94f0a86031c0a007c4628f47d0e8af0eeb3c38b68bc0c0d4d381b8900000000000000000000d0e9d375aa1fec0c0ee94f0b0816e0be8c3db2dc0c8cc3fb1de1ff71a7e0dc0c0d4d381db90000000000000000003a51a9940defcddc0c0f903a094f0bae7189bf0ebbaa76b5a6b372c9db837e455c4f90238f845a00000000000000000000000000000000000000000000000000000000000000068e3e221a0000000000000000000000000000000000000000006b5eb656fe3203cf23afdecf845a00000000000000000000000000000000000000000000000000000000000000069e3e221a000000000000000000000000000000000000000000000000000000000015f70a9f845a0000000000000000000000000000000000000000000000000000000000000006ae3e221a00000000000000000000000000000000000000000000000000001e6d437788a01f845a0fb99638a8a0d4f3d05233afb0414573fd446a47342aa07bdec575bfeb3883072e3e221a00000000000000000000000000000000000000000000000000000000000000000f845a0fb99638a8a0d4f3d05233afb0414573fd446a47342aa07bdec575bfeb3883073e3e221a00000000000000000000000000000000000000000000000000000000000000000f845a0fb99638a8a0d4f3d05233afb0414573fd446a47342aa07bdec575bfeb3883074e3e221a00000000000000000000000000000000000000000000000000000000000000000f845a0fb99638a8a0d4f3d05233afb0414573fd446a47342aa07bdec575bfeb3883075e3e221a00000000000000000000000000000000000000000000000000000000000000000f845a0fb99638a8a0d4f3d05233afb0414573fd446a47342aa07bdec575bfeb3883076e3e221a00000000000000000000000000000000000000000000000000000000000000000f9014aa00000000000000000000000000000000000000000000000000000000000000065a0000000000000000000000000000000000000000000000000000000000000006ba0000000000000000000000000000000000000000000000000000000000000006ca0000000000000000000000000000000000000000000000000000000000000006da0000000000000000000000000000000000000000000000000000000000000006ea0000000000000000000000000000000000000000000000000000000000000006fa0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca08093b32f3d03a295948279e7b7486c594a9e251ed35fc1543709a9a77eb3b1aba0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103a0d0ad710dabeb0bf9758fa9998a9d0b9ad80960ee51348684b4be199ceb27f8c1c0c0c0ef94f2f6531f66e22d51b6dc978348a810050c3ff48fc0c0d5d4820130900000000000000000001094238ac06000c0c0ee94f2f77cb086da7af5205bb137e5f0eb273ac658aec0c0d4d3818f900000000000000000009a23d5748fc3f3c0c0f294f3a32ac94a64ff42edcfbb692fd151babe518057c0c0d3d205900000000000000000086c87525ea19cddc5c405820134c0f294f47b274bf5e6ac9190620bca1ef10aa417d00319c0c0d3d21a900000000000000000006c0b80de5381e6c5c41a8205eec0f85d94f5042e6ffac5a625d4e7848e0b01373d8eb9e222c0f842a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002c0c0c0ef94f52605c7b778563a5a9144ef4dc53b57463ca2c7c0c0d5d4820113900000000000000001223ddc0dd06c6c35c0c0f094f573fba8aca8b861731249f6ab8a595f8940c9e7c0c0d3d2219000000000000000000021a5161d2cceb7c3c2212ec0da94f62304dbb23275ae07c6b5af8c7364cf3784ba7dc0c0c0c0c0f83b94f6473fa77d6c2b7e4c32936e7bed7b213aa4325ec0c0c0c5c482011e01dcdb82011e97ef0100411d38d27f6f2c7f3b70ff29dada64cbd7bfa9b2f794f6d4e5a7c5215f91f59a95065190cca24bf64554c0c0d5d48201309000000000000000060f41a9582694f0edc8c7820130830102fdc0f83b94f6e72db5454dd049d0788e411b06cfaf16853042c0e1a00000000000000000000000000000000000000000000000000000000000000003c0c0c0f88f94f70da97812cb96acdf810712aa562db8dfa3dbefc0c0f862d2069000000000000000216eaab20c6006b7dcd2119000000000000000216e9c7cdd70f5d9dcd2129000000000000000216e9a37668591c25bd381909000000000000000216e9dce0aa4e0b631d482010b9000000000000000216ea0a351a351ae98d2c506832eb16fc511832eb170c512832eb171c0f494f724ecbf1c623691b7926452d452a5cfb5c15752c0c0d5d48201179000000000000000000022348da7fa935ec5c482011709c0f394f746d71c795ad20787c061c3d9b20e6b1d6a325ac0c0d4d381ec9000000000000000000074ea2194eba7c3c5c481ec81b9c0f294f7c97cdd83b17fb16cb8fc739f1f738d05bf18f9c0c0d4d381ac900000000000000000022decf9e5bcce1bc4c381ac49c0f294f866bc6f3622b687873a6b38b4d5cc35dd7f14bcc0c0d4d3819c900000000000000000008f91b595069f7ec4c3819c03c0ed94f86c6a05a5184592556074cc549013e56ab931a7c0c0d3d255900000000000000000000c9609d7af9f2cc0c0f9014b94f8ab2dbe6c43bf1a856471182290f91d621ba76df848f846a0bff8e45742cd98c178e13ccb0a80ba8aa27033ea4b8ce6352653517fda8d6850e4e38190a00000000000000000000000000000000000000000000000000000000000000001f8e7a00000000000000000000000000000000000000000000000929eee149b4bd21268a001507f32017765a5cdb7ee1b0de7932806231a22d521c294381df8c987762902a001507f32017765a5cdb7ee1b0de7932806231a22d521c294381df8c987762903a01105e3a5e09fef9802c1673427a4426578a66df072b53d7c2b4374e406740169a0360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbca06b614260192271344ae671278f13c26450d600eb9ccf9b5c9ec5ce3c5b405219a0d40de0af8d9b50cb541ba7e1c8a1245db546361652a5684ca8eab5d31a204826c0c0c0f83b94f93191d350117723dbeda5484a3b0996d285cecfc0e1a00000000000000000000000000000000000000000000000000000000000000001c0c0c0ee94f94efc74958aef588fdb081b0637164b867a8a40c0c0d4d3819890000000000000000000047cc1c731b2d0c0c0f494fab33555c5960a3ef5beda377b3dc43490e5076bc0c0d5d482010b9000000000000000000003bccbbd0dbb7ec5c482010b06c0f9013294faba6f8e4a5e8ab82f62fe7c39859fa577269be3f892f847a037726c8e8c1b8b3301e2cfbadf9e35806a201bd76b13f288729f71ed580af174e5e4820115a000000000000000000000000000000000000000000000001c9491eb27f7f36400f847a0e7bd7062509abed6600ff9a901b83b8255539df85176d54c5fbdf72966ad9bbde5e4820115a0000000000000000000000000000000000000000000035e4172e43c9246cd1f7cf884a00000000000000000000000000000000000000000000000000000000000000003a06fca8a5341b3cd5043dfa93787b46d80bd1c82c5ef2e94684f0ae3344e3751e6a0c88c68aeebda37930d17589b7dc8000f8daf8d376f87915802e39bc158c61671a0d1a527e9ba89a12597c8855c6f733676f5576981597ab846288e60f33e2a33ffc0c0c0f9016694fbd46de044cf34dfd7386c6915cce74ef186c23ec0f9014aa00000000000000000000000000000000000000000000000000000000000000005a00000000000000000000000000000000000000000000000000000000000000006a00000000000000000000000000000000000000000000000000000000000000007a00000000000000000000000000000000000000000000000000000000000000008a00000000000000000000000000000000000000000000000000000000000000009a0000000000000000000000000000000000000000000000000000000000000000aa0000000000000000000000000000000000000000000000000000000000000000ba00175b7a638427703f0dbe7bb9bbf987a2551717b34e79f33b5b1008d1fa01db9a0c65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a8a0c65a7bb8d6351c1cf70c95a316cc6a92839c986682d98bc35f958f4883f9d2a9c0c0c0ed94fbd4cdb413e45a52e2c8312f670e9ce67e794c37c0c0d3d2019000000000000000017a1d08e2c4d215e8c0c0f904d894fcee41852e5ec9b907714200b2a3c5b7ac29e7aef9030df845a00000000000000000000000000000000000000000000000000000000000000075e3e278a000000000015b6da1000000000000033200000000000003320000000000000331f845a04b987a7c6f512cf0d3515ea389cd88d89be38dbdb4b081db64961c39f48f31f3e3e278a00000000000000001000000000000000000000000000003320000000000000000f845a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad4e3e278a00000000000000001000000000160358900000000016034450000000000000330f845a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad5e3e278a000000000015f6f65000000000000033200000000015f70a90000000000000001f845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e308e3e278a0f8734d40457e9794e88505de53bbbd1a77544f273223652cc1a6efc43051068cf845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e309e3e278a0fc32d4960e75784aa0e04bb437ca94ff4e860b663510e796109379cb541a12e1f845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e30ae3e278a00c352f8c1e9322f6749f36f9a66c7c15122aac8426e575184386bbd86837056df845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e30be3e278a00000000000000001000000000160358900000000016035890000000000000331f845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e30ce3e278a000000000015f70a9000000000000000000000000000000000000000000000000f845a093be49759a4d1a649bc96da5cb9a9ca3ff72cc60898e77cdec8d1d770376e30de3e278a0e165b1b5ba88758e15eb8a173c8fae256b4eef9de7d636e6920bef8a8abb18f7f845a0ad098b06e061271a6abfaa98c13f8d6b2a2553008e1da106dcfb71c3d1be1694e3e278a00000000000000000000000000000000000000000000000000000000000000001f901ada00000000000000000000000000000000000000000000000000000000000000033a00000000000000000000000000000000000000000000000000000000000000065a00000000000000000000000000000000000000000000000000000000000000068a0000000000000000000000000000000000000000000000000000000000000006aa00000000000000000000000000000000000000000000000000000000000000073a02b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546da032943b47d27a210e8b7e1c927d5bb7b1aed10a78f1f0b3d678c3029f6e5cb75fa063cf6dea2131c66021c98b179b47c74fe3fe9313f961c55303acb46de2ffcca8a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad1a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad2a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad3a08f87f829e1b795a76478796732356cadb5abe698565b66a058a5bec1e6eabad6a0b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103c0c0c0f394fd25808ffffbef621c4dbf0171fa647c916cb33bc0c0d4d381bc90000000000000000079a4e0046d95314dc5c481bc81a1c0f85d94fd48112e448417ca79305a518c4186df4b0a200ac0f842a00000000000000000000000000000000000000000000000000000000000000003a0d01a293d013d5426dd5cc2a98a194f6994ad72d277cc3a5942ea20b2a409b465c0c0c0f694fd62020cee216dc543e29752058ee9f60f7d9ff9c0c0d5d482011c90000000000000000002e44040571e0388c7c682011c8262eec0f094fdf6211204deab098aba067dab76d06b40b73edcc0c0d3d2779000000000000000000000000000000000c3c27735c0f494fe263102682933297cb65dc813e5193249769251c0c0d4d381f59000000000000000000d26cea816944531c6c581f582a396c0f394fe93e9ce4f730a1c64846bc782e45904ffaa633ac0c0d4d381c8900000000000000000002280bd5dc61537c5c481c88196c0ef94ff0f50fa7016e4390cb6b069347c635cfd035c6bc0c0d5d4820132900000000000000008afb60eb856ce0603c0c0 diff --git a/core/types/bal/bal_encoding.go b/core/types/bal/bal_encoding.go index 1b1406ea32..124b7712c9 100644 --- a/core/types/bal/bal_encoding.go +++ b/core/types/bal/bal_encoding.go @@ -19,8 +19,11 @@ package bal import ( "bytes" "cmp" + "encoding/hex" + "encoding/json" "errors" "fmt" + "github.com/ethereum/go-ethereum/log" "io" "maps" "slices" @@ -33,27 +36,95 @@ import ( "github.com/holiman/uint256" ) -//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type BlockAccessList -decoder +//go:generate go run github.com/ethereum/go-ethereum/rlp/rlpgen -out bal_encoding_rlp_generated.go -type AccountAccess -decoder // These are objects used as input for the access list encoding. They mirror // the spec format. -// BlockAccessList is the encoding format of ConstructionBlockAccessList. -type BlockAccessList struct { - Accesses []AccountAccess `ssz-max:"300000"` +// BlockAccessList is the encoding format of AccessListBuilder. +type BlockAccessList []AccountAccess + +func (e BlockAccessList) EncodeRLP(_w io.Writer) error { + w := rlp.NewEncoderBuffer(_w) + l := w.List() + for _, access := range e { + access.EncodeRLP(w) + } + w.ListEnd(l) + return w.Flush() } +func (e *BlockAccessList) DecodeRLP(dec *rlp.Stream) error { + if _, err := dec.List(); err != nil { + return err + } + *e = (*e)[:0] + for dec.MoreDataInList() { + var access AccountAccess + if err := access.DecodeRLP(dec); err != nil { + return err + } + *e = append(*e, access) + } + dec.ListEnd() + return nil +} + +func (e *BlockAccessList) EncodedSize() int { + b, err := rlp.EncodeToBytes(e) + if err != nil { + // TODO: proper to crit here? + log.Crit("failed to rlp encode access list", "err", err) + } + return len(b) +} + +func (e *BlockAccessList) JSONString() string { + res, _ := json.MarshalIndent(e.StringableRepresentation(), "", " ") + return string(res) +} + +// StringableRepresentation returns an instance of the block access list +// which can be converted to a human-readable JSON representation. +func (e *BlockAccessList) StringableRepresentation() interface{} { + res := []AccountAccess{} + for _, aa := range *e { + res = append(res, aa) + } + return &res +} + +func (e *BlockAccessList) String() string { + var res bytes.Buffer + enc := json.NewEncoder(&res) + enc.SetIndent("", " ") + // TODO: check error + enc.Encode(e) + return res.String() +} + +// TODO: check that no fields are nil in Validate (unless it's valid for them to be nil) // Validate returns an error if the contents of the access list are not ordered // according to the spec or any code changes are contained which exceed protocol // max code size. -func (e *BlockAccessList) Validate() error { - if !slices.IsSortedFunc(e.Accesses, func(a, b AccountAccess) int { +func (e BlockAccessList) Validate(blockTxCount int) error { + if !slices.IsSortedFunc(e, func(a, b AccountAccess) int { return bytes.Compare(a.Address[:], b.Address[:]) }) { return errors.New("block access list accounts not in lexicographic order") } - for _, entry := range e.Accesses { - if err := entry.validate(); err != nil { + // check that the accounts are unique + addrs := make(map[common.Address]struct{}) + for _, acct := range e { + addr := acct.Address + if _, ok := addrs[addr]; ok { + return fmt.Errorf("duplicate account in block access list: %x", addr) + } + addrs[addr] = struct{}{} + } + + for _, entry := range e { + if err := entry.validate(blockTxCount); err != nil { return err } } @@ -70,53 +141,135 @@ func (e *BlockAccessList) Hash() common.Hash { // under reasonable conditions. panic(err) } + /* + bal, err := json.MarshalIndent(e.StringableRepresentation(), "", " ") + if err != nil { + panic(err) + } + */ return crypto.Keccak256Hash(enc.Bytes()) } -// encodeBalance encodes the provided balance into 16-bytes. -func encodeBalance(val *uint256.Int) [16]byte { - valBytes := val.Bytes() - if len(valBytes) > 16 { - panic("can't encode value that is greater than 16 bytes in size") - } - var enc [16]byte - copy(enc[16-len(valBytes):], valBytes[:]) - return enc -} - // encodingBalanceChange is the encoding format of BalanceChange. type encodingBalanceChange struct { - TxIdx uint16 `ssz-size:"2"` - Balance [16]byte `ssz-size:"16"` + TxIdx uint16 `json:"txIndex"` + Balance *uint256.Int `json:"balance"` } // encodingAccountNonce is the encoding format of NonceChange. type encodingAccountNonce struct { - TxIdx uint16 `ssz-size:"2"` - Nonce uint64 `ssz-size:"8"` + TxIdx uint16 `json:"txIndex"` + Nonce uint64 `json:"nonce"` } // encodingStorageWrite is the encoding format of StorageWrites. type encodingStorageWrite struct { - TxIdx uint16 - ValueAfter [32]byte `ssz-size:"32"` + TxIdx uint16 `json:"txIndex"` + ValueAfter *EncodedStorage `json:"valueAfter"` +} + +// EncodedStorage can represent either a storage key or value +type EncodedStorage struct { + inner *uint256.Int +} + +var _ rlp.Encoder = &EncodedStorage{} +var _ rlp.Decoder = &EncodedStorage{} + +func (e *EncodedStorage) ToHash() common.Hash { + if e == nil { + return common.Hash{} + } + return e.inner.Bytes32() +} + +func newEncodedStorageFromHash(hash common.Hash) *EncodedStorage { + return &EncodedStorage{ + new(uint256.Int).SetBytes(hash[:]), + } +} + +func (s *EncodedStorage) UnmarshalJSON(b []byte) error { + var str string + if err := json.Unmarshal(b, &str); err != nil { + return err + } + + str = strings.TrimLeft(str, "0x") + if len(str) == 0 { + return nil + } + + if len(str)%2 == 1 { + str = "0" + str + } + + val, err := hex.DecodeString(str) + if err != nil { + return err + } + + if len(val) > 32 { + return fmt.Errorf("storage key/value cannot be greater than 32 bytes") + } + + // TODO: check is s == nil ?? should be programmer error + + *s = EncodedStorage{ + inner: new(uint256.Int).SetBytes(val), + } + return nil +} + +func (s EncodedStorage) MarshalJSON() ([]byte, error) { + return json.Marshal(s.inner.Hex()) +} + +func (s *EncodedStorage) EncodeRLP(_w io.Writer) error { + return s.inner.EncodeRLP(_w) +} + +func (s *EncodedStorage) DecodeRLP(dec *rlp.Stream) error { + if s == nil { + *s = EncodedStorage{} + } + s.inner = uint256.NewInt(0) + return dec.ReadUint256(s.inner) } // encodingStorageWrite is the encoding format of SlotWrites. type encodingSlotWrites struct { - Slot [32]byte `ssz-size:"32"` - Accesses []encodingStorageWrite `ssz-max:"300000"` + Slot *EncodedStorage `json:"slot"` + Accesses []encodingStorageWrite `json:"accesses"` } // validate returns an instance of the encoding-representation slot writes in // working representation. -func (e *encodingSlotWrites) validate() error { - if slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int { +func (e *encodingSlotWrites) validate(blockTxCount int) error { + if e.Slot == nil { + return errors.New("nil slot key") + } + if !slices.IsSortedFunc(e.Accesses, func(a, b encodingStorageWrite) int { return cmp.Compare[uint16](a.TxIdx, b.TxIdx) }) { - return nil + return errors.New("storage write tx indices not in order") } - return errors.New("storage write tx indices not in order") + for i, access := range e.Accesses { + if access.ValueAfter == nil { + return errors.New("nil storage write post") + } + if i > 0 && e.Accesses[i-1].TxIdx == access.TxIdx { + return errors.New("duplicate storage write index") + } + } + // TODO: add test that covers there are actually storage modifications here + // if there aren't, it should be a bad block + if len(e.Accesses) == 0 { + return fmt.Errorf("empty storage writes") + } else if int(e.Accesses[len(e.Accesses)-1].TxIdx) >= blockTxCount+2 { + return fmt.Errorf("storage access reported index higher than allowed") + } + return nil } // encodingCodeChange contains the runtime bytecode deployed at an address @@ -126,64 +279,120 @@ type encodingCodeChange struct { Code []byte `ssz-max:"300000"` // TODO(rjl493456442) shall we put the limit here? The limit will be increased gradually } -// AccountAccess is the encoding format of ConstructionAccountAccess. +// AccountAccess is the encoding format of ConstructionAccountAccesses. type AccountAccess struct { - Address [20]byte `ssz-size:"20"` // 20-byte Ethereum address - StorageWrites []encodingSlotWrites `ssz-max:"300000"` // Storage changes (slot -> [tx_index -> new_value]) - StorageReads [][32]byte `ssz-max:"300000"` // Read-only storage keys - BalanceChanges []encodingBalanceChange `ssz-max:"300000"` // Balance changes ([tx_index -> post_balance]) - NonceChanges []encodingAccountNonce `ssz-max:"300000"` // Nonce changes ([tx_index -> new_nonce]) - CodeChanges []encodingCodeChange `ssz-max:"300000"` // Code changes ([tx_index -> new_code]) + Address common.Address `json:"address,omitempty"` // 20-byte Ethereum address + StorageChanges []encodingSlotWrites `json:"storageChanges,omitempty"` // EncodedStorage changes (slot -> [tx_index -> new_value]) + StorageReads []*EncodedStorage `json:"storageReads,omitempty"` // Read-only storage keys + BalanceChanges []encodingBalanceChange `json:"balanceChanges,omitempty"` // Balance changes ([tx_index -> post_balance]) + NonceChanges []encodingAccountNonce `json:"nonceChanges,omitempty"` // Nonce changes ([tx_index -> new_nonce]) + CodeChanges []encodingCodeChange `json:"code,omitempty"` // CodeChanges changes ([tx_index -> new_code]) } // validate converts the account accesses out of encoding format. // If any of the keys in the encoding object are not ordered according to the // spec, an error is returned. -func (e *AccountAccess) validate() error { +func (e *AccountAccess) validate(blockTxCount int) error { // Check the storage write slots are sorted in order - if !slices.IsSortedFunc(e.StorageWrites, func(a, b encodingSlotWrites) int { - return bytes.Compare(a.Slot[:], b.Slot[:]) + if !slices.IsSortedFunc(e.StorageChanges, func(a, b encodingSlotWrites) int { + aHash, bHash := a.Slot.ToHash(), b.Slot.ToHash() + return bytes.Compare(aHash[:], bHash[:]) }) { return errors.New("storage writes slots not in lexicographic order") } - for _, write := range e.StorageWrites { - if err := write.validate(); err != nil { + for _, write := range e.StorageChanges { + if err := write.validate(blockTxCount); err != nil { return err } } + readKeys := make(map[common.Hash]struct{}) + writeKeys := make(map[common.Hash]struct{}) + for _, readKey := range e.StorageReads { + if _, ok := readKeys[readKey.ToHash()]; ok { + return errors.New("duplicate read key") + } + readKeys[readKey.ToHash()] = struct{}{} + } + for _, write := range e.StorageChanges { + writeKey := write.Slot + if _, ok := writeKeys[writeKey.ToHash()]; ok { + return errors.New("duplicate write key") + } + writeKeys[writeKey.ToHash()] = struct{}{} + } + + for readKey := range readKeys { + if _, ok := writeKeys[readKey]; ok { + return errors.New("storage key reported in both read/write sets") + } + } // Check the storage read slots are sorted in order - if !slices.IsSortedFunc(e.StorageReads, func(a, b [32]byte) int { - return bytes.Compare(a[:], b[:]) + if !slices.IsSortedFunc(e.StorageReads, func(a, b *EncodedStorage) int { + aHash, bHash := a.ToHash(), b.ToHash() + return bytes.Compare(aHash[:], bHash[:]) }) { return errors.New("storage read slots not in lexicographic order") } // Check the balance changes are sorted in order + // and that none of them report an index above what is allowed if !slices.IsSortedFunc(e.BalanceChanges, func(a, b encodingBalanceChange) int { return cmp.Compare[uint16](a.TxIdx, b.TxIdx) }) { return errors.New("balance changes not in ascending order by tx index") } + if len(e.BalanceChanges) > 0 && int(e.BalanceChanges[len(e.BalanceChanges)-1].TxIdx) > blockTxCount+2 { + return errors.New("highest balance change index beyond what is allowed") + } + // check that the balance values are set and there are no duplicate index entries + for i, balanceChange := range e.BalanceChanges { + if balanceChange.Balance == nil { + return errors.New("nil balance change value") + } + if i > 0 && e.BalanceChanges[i-1].TxIdx == balanceChange.TxIdx { + return errors.New("duplicate index for balance change") + } + } + // Check the nonce changes are sorted in order + // and that none of them report an index above what is allowed if !slices.IsSortedFunc(e.NonceChanges, func(a, b encodingAccountNonce) int { return cmp.Compare[uint16](a.TxIdx, b.TxIdx) }) { return errors.New("nonce changes not in ascending order by tx index") } + if len(e.NonceChanges) > 0 && int(e.NonceChanges[len(e.NonceChanges)-1].TxIdx) >= blockTxCount+2 { + return errors.New("highest nonce change index beyond what is allowed") + } + for i, nonceChange := range e.NonceChanges { + if i > 0 && nonceChange.TxIdx == e.NonceChanges[i-1].TxIdx { + return errors.New("duplicate index reported in nonce changes") + } + } - // Check the code changes are sorted in order + // TODO: contact testing team to add a test case which has the code changes out of order, + // as it wasn't checked here previously if !slices.IsSortedFunc(e.CodeChanges, func(a, b encodingCodeChange) int { return cmp.Compare[uint16](a.TxIndex, b.TxIndex) }) { - return errors.New("code changes not in ascending order by tx index") + return errors.New("code changes not in ascending order") } - for _, change := range e.CodeChanges { - // TODO(rjl493456442): This check should be fork-aware, since the limit may - // differ across forks. - if len(change.Code) > params.MaxCodeSize { - return errors.New("code change contained oversized code") + if len(e.CodeChanges) > 0 && int(e.CodeChanges[len(e.CodeChanges)-1].TxIndex) >= blockTxCount+2 { + return errors.New("highest code change index beyond what is allowed") + } + for i, codeChange := range e.CodeChanges { + if i > 0 && codeChange.TxIndex == e.CodeChanges[i-1].TxIndex { + return errors.New("duplicate index reported in code changes") + } + } + + // validate that code changes could plausibly be correct (none exceed + // max code size of a contract) + for _, codeChange := range e.CodeChanges { + if len(codeChange.Code) > params.MaxCodeSize { + return fmt.Errorf("code change contained oversized code") } } return nil @@ -196,41 +405,40 @@ func (e *AccountAccess) Copy() AccountAccess { StorageReads: slices.Clone(e.StorageReads), BalanceChanges: slices.Clone(e.BalanceChanges), NonceChanges: slices.Clone(e.NonceChanges), - StorageWrites: make([]encodingSlotWrites, 0, len(e.StorageWrites)), - CodeChanges: make([]encodingCodeChange, 0, len(e.CodeChanges)), } - for _, storageWrite := range e.StorageWrites { - res.StorageWrites = append(res.StorageWrites, encodingSlotWrites{ + for _, storageWrite := range e.StorageChanges { + res.StorageChanges = append(res.StorageChanges, encodingSlotWrites{ Slot: storageWrite.Slot, Accesses: slices.Clone(storageWrite.Accesses), }) } for _, codeChange := range e.CodeChanges { - res.CodeChanges = append(res.CodeChanges, encodingCodeChange{ - TxIndex: codeChange.TxIndex, - Code: bytes.Clone(codeChange.Code), - }) + res.CodeChanges = append(res.CodeChanges, + encodingCodeChange{ + codeChange.TxIndex, + bytes.Clone(codeChange.Code), + }) } return res } // EncodeRLP returns the RLP-encoded access list -func (b *ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { - return b.toEncodingObj().EncodeRLP(wr) +func (c ConstructionBlockAccessList) EncodeRLP(wr io.Writer) error { + return c.ToEncodingObj().EncodeRLP(wr) } var _ rlp.Encoder = &ConstructionBlockAccessList{} -// toEncodingObj creates an instance of the ConstructionAccountAccess of the type that is +// toEncodingObj creates an instance of the ConstructionAccountAccesses of the type that is // used as input for the encoding. -func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAccess { +func (a *ConstructionAccountAccesses) toEncodingObj(addr common.Address) AccountAccess { res := AccountAccess{ Address: addr, - StorageWrites: make([]encodingSlotWrites, 0, len(a.StorageWrites)), - StorageReads: make([][32]byte, 0, len(a.StorageReads)), - BalanceChanges: make([]encodingBalanceChange, 0, len(a.BalanceChanges)), - NonceChanges: make([]encodingAccountNonce, 0, len(a.NonceChanges)), - CodeChanges: make([]encodingCodeChange, 0, len(a.CodeChange)), + StorageChanges: make([]encodingSlotWrites, 0), + StorageReads: make([]*EncodedStorage, 0), + BalanceChanges: make([]encodingBalanceChange, 0), + NonceChanges: make([]encodingAccountNonce, 0), + CodeChanges: make([]encodingCodeChange, 0), } // Convert write slots @@ -238,7 +446,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc slices.SortFunc(writeSlots, common.Hash.Cmp) for _, slot := range writeSlots { var obj encodingSlotWrites - obj.Slot = slot + obj.Slot = newEncodedStorageFromHash(slot) slotWrites := a.StorageWrites[slot] obj.Accesses = make([]encodingStorageWrite, 0, len(slotWrites)) @@ -248,17 +456,17 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc for _, index := range indices { obj.Accesses = append(obj.Accesses, encodingStorageWrite{ TxIdx: index, - ValueAfter: slotWrites[index], + ValueAfter: newEncodedStorageFromHash(slotWrites[index]), }) } - res.StorageWrites = append(res.StorageWrites, obj) + res.StorageChanges = append(res.StorageChanges, obj) } // Convert read slots readSlots := slices.Collect(maps.Keys(a.StorageReads)) slices.SortFunc(readSlots, common.Hash.Cmp) for _, slot := range readSlots { - res.StorageReads = append(res.StorageReads, slot) + res.StorageReads = append(res.StorageReads, newEncodedStorageFromHash(slot)) } // Convert balance changes @@ -267,7 +475,7 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc for _, idx := range balanceIndices { res.BalanceChanges = append(res.BalanceChanges, encodingBalanceChange{ TxIdx: idx, - Balance: encodeBalance(a.BalanceChanges[idx]), + Balance: new(uint256.Int).Set(a.BalanceChanges[idx]), }) } @@ -282,77 +490,31 @@ func (a *ConstructionAccountAccess) toEncodingObj(addr common.Address) AccountAc } // Convert code change - codeIndices := slices.Collect(maps.Keys(a.CodeChange)) - slices.SortFunc(codeIndices, cmp.Compare[uint16]) - for _, idx := range codeIndices { + codeChangeIdxs := slices.Collect(maps.Keys(a.CodeChanges)) + slices.SortFunc(codeChangeIdxs, cmp.Compare[uint16]) + for _, idx := range codeChangeIdxs { res.CodeChanges = append(res.CodeChanges, encodingCodeChange{ - TxIndex: idx, - Code: a.CodeChange[idx], + idx, + bytes.Clone(a.CodeChanges[idx]), }) } return res } -// toEncodingObj returns an instance of the access list expressed as the type +// ToEncodingObj returns an instance of the access list expressed as the type // which is used as input for the encoding/decoding. -func (b *ConstructionBlockAccessList) toEncodingObj() *BlockAccessList { +func (c ConstructionBlockAccessList) ToEncodingObj() *BlockAccessList { var addresses []common.Address - for addr := range b.Accounts { + for addr := range c { addresses = append(addresses, addr) } slices.SortFunc(addresses, common.Address.Cmp) var res BlockAccessList for _, addr := range addresses { - res.Accesses = append(res.Accesses, b.Accounts[addr].toEncodingObj(addr)) + res = append(res, c[addr].toEncodingObj(addr)) } return &res } -func (e *BlockAccessList) PrettyPrint() string { - var res bytes.Buffer - printWithIndent := func(indent int, text string) { - fmt.Fprintf(&res, "%s%s\n", strings.Repeat(" ", indent), text) - } - for _, accountDiff := range e.Accesses { - printWithIndent(0, fmt.Sprintf("%x:", accountDiff.Address)) - - printWithIndent(1, "storage writes:") - for _, sWrite := range accountDiff.StorageWrites { - printWithIndent(2, fmt.Sprintf("%x:", sWrite.Slot)) - for _, access := range sWrite.Accesses { - printWithIndent(3, fmt.Sprintf("%d: %x", access.TxIdx, access.ValueAfter)) - } - } - - printWithIndent(1, "storage reads:") - for _, slot := range accountDiff.StorageReads { - printWithIndent(2, fmt.Sprintf("%x", slot)) - } - - printWithIndent(1, "balance changes:") - for _, change := range accountDiff.BalanceChanges { - balance := new(uint256.Int).SetBytes(change.Balance[:]).String() - printWithIndent(2, fmt.Sprintf("%d: %s", change.TxIdx, balance)) - } - - printWithIndent(1, "nonce changes:") - for _, change := range accountDiff.NonceChanges { - printWithIndent(2, fmt.Sprintf("%d: %d", change.TxIdx, change.Nonce)) - } - - printWithIndent(1, "code changes:") - for _, change := range accountDiff.CodeChanges { - printWithIndent(2, fmt.Sprintf("%d: %x", change.TxIndex, change.Code)) - } - } - return res.String() -} - -// Copy returns a deep copy of the access list -func (e *BlockAccessList) Copy() (res BlockAccessList) { - for _, accountAccess := range e.Accesses { - res.Accesses = append(res.Accesses, accountAccess.Copy()) - } - return -} +type ContractCode []byte diff --git a/core/types/bal/bal_encoding_json.go b/core/types/bal/bal_encoding_json.go new file mode 100644 index 0000000000..bf25345b5e --- /dev/null +++ b/core/types/bal/bal_encoding_json.go @@ -0,0 +1,107 @@ +package bal + +import ( + "encoding/json" + "fmt" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rlp" +) + +func (c *ContractCode) MarshalJSON() ([]byte, error) { + hexStr := fmt.Sprintf("%x", *c) + return json.Marshal(hexStr) +} +func (e encodingBalanceChange) MarshalJSON() ([]byte, error) { + type Alias encodingBalanceChange + return json.Marshal(&struct { + TxIdx string `json:"txIndex"` + *Alias + }{ + TxIdx: fmt.Sprintf("0x%x", e.TxIdx), + Alias: (*Alias)(&e), + }) +} + +func (e *encodingBalanceChange) UnmarshalJSON(data []byte) error { + type Alias encodingBalanceChange + aux := &struct { + TxIdx string `json:"txIndex"` + *Alias + }{ + Alias: (*Alias)(e), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" { + if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil { + return err + } + } + return nil +} +func (e encodingAccountNonce) MarshalJSON() ([]byte, error) { + type Alias encodingAccountNonce + return json.Marshal(&struct { + TxIdx string `json:"txIndex"` + Nonce string `json:"nonce"` + *Alias + }{ + TxIdx: fmt.Sprintf("0x%x", e.TxIdx), + Nonce: fmt.Sprintf("0x%x", e.Nonce), + Alias: (*Alias)(&e), + }) +} + +func (e *encodingAccountNonce) UnmarshalJSON(data []byte) error { + type Alias encodingAccountNonce + aux := &struct { + TxIdx string `json:"txIndex"` + Nonce string `json:"nonce"` + *Alias + }{ + Alias: (*Alias)(e), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + if len(aux.TxIdx) >= 2 && aux.TxIdx[:2] == "0x" { + if _, err := fmt.Sscanf(aux.TxIdx, "0x%x", &e.TxIdx); err != nil { + return err + } + } + if len(aux.Nonce) >= 2 && aux.Nonce[:2] == "0x" { + if _, err := fmt.Sscanf(aux.Nonce, "0x%x", &e.Nonce); err != nil { + return err + } + } + return nil +} + +// UnmarshalJSON implements json.Unmarshaler to decode from RLP hex bytes +func (b *BlockAccessList) UnmarshalJSON(input []byte) error { + // Handle both hex string and object formats + var hexBytes hexutil.Bytes + if err := json.Unmarshal(input, &hexBytes); err == nil { + // It's a hex string, decode from RLP + return rlp.DecodeBytes(hexBytes, b) + } + + // Otherwise try to unmarshal as structured JSON + var tmp []AccountAccess + if err := json.Unmarshal(input, &tmp); err != nil { + return err + } + *b = BlockAccessList(tmp) + return nil +} + +// MarshalJSON implements json.Marshaler to encode as RLP hex bytes +func (b BlockAccessList) MarshalJSON() ([]byte, error) { + // Encode to RLP then to hex + rlpBytes, err := rlp.EncodeToBytes(b) + if err != nil { + return nil, err + } + return json.Marshal(hexutil.Bytes(rlpBytes)) +} diff --git a/core/types/bal/bal_encoding_rlp_generated.go b/core/types/bal/bal_encoding_rlp_generated.go index 640035e30e..f61ebe2675 100644 --- a/core/types/bal/bal_encoding_rlp_generated.go +++ b/core/types/bal/bal_encoding_rlp_generated.go @@ -2,275 +2,260 @@ package bal +import "github.com/ethereum/go-ethereum/common" import "github.com/ethereum/go-ethereum/rlp" +import "github.com/holiman/uint256" import "io" -func (obj *BlockAccessList) EncodeRLP(_w io.Writer) error { +func (obj *AccountAccess) EncodeRLP(_w io.Writer) error { w := rlp.NewEncoderBuffer(_w) _tmp0 := w.List() + w.WriteBytes(obj.Address[:]) _tmp1 := w.List() - for _, _tmp2 := range obj.Accesses { + for _, _tmp2 := range obj.StorageChanges { _tmp3 := w.List() - w.WriteBytes(_tmp2.Address[:]) + if err := _tmp2.Slot.EncodeRLP(w); err != nil { + return err + } _tmp4 := w.List() - for _, _tmp5 := range _tmp2.StorageWrites { + for _, _tmp5 := range _tmp2.Accesses { _tmp6 := w.List() - w.WriteBytes(_tmp5.Slot[:]) - _tmp7 := w.List() - for _, _tmp8 := range _tmp5.Accesses { - _tmp9 := w.List() - w.WriteUint64(uint64(_tmp8.TxIdx)) - w.WriteBytes(_tmp8.ValueAfter[:]) - w.ListEnd(_tmp9) + w.WriteUint64(uint64(_tmp5.TxIdx)) + if err := _tmp5.ValueAfter.EncodeRLP(w); err != nil { + return err } - w.ListEnd(_tmp7) w.ListEnd(_tmp6) } w.ListEnd(_tmp4) - _tmp10 := w.List() - for _, _tmp11 := range _tmp2.StorageReads { - w.WriteBytes(_tmp11[:]) - } - w.ListEnd(_tmp10) - _tmp12 := w.List() - for _, _tmp13 := range _tmp2.BalanceChanges { - _tmp14 := w.List() - w.WriteUint64(uint64(_tmp13.TxIdx)) - w.WriteBytes(_tmp13.Balance[:]) - w.ListEnd(_tmp14) - } - w.ListEnd(_tmp12) - _tmp15 := w.List() - for _, _tmp16 := range _tmp2.NonceChanges { - _tmp17 := w.List() - w.WriteUint64(uint64(_tmp16.TxIdx)) - w.WriteUint64(_tmp16.Nonce) - w.ListEnd(_tmp17) - } - w.ListEnd(_tmp15) - _tmp18 := w.List() - for _, _tmp19 := range _tmp2.CodeChanges { - _tmp20 := w.List() - w.WriteUint64(uint64(_tmp19.TxIndex)) - w.WriteBytes(_tmp19.Code) - w.ListEnd(_tmp20) - } - w.ListEnd(_tmp18) w.ListEnd(_tmp3) } w.ListEnd(_tmp1) + _tmp7 := w.List() + for _, _tmp8 := range obj.StorageReads { + if err := _tmp8.EncodeRLP(w); err != nil { + return err + } + } + w.ListEnd(_tmp7) + _tmp9 := w.List() + for _, _tmp10 := range obj.BalanceChanges { + _tmp11 := w.List() + w.WriteUint64(uint64(_tmp10.TxIdx)) + if _tmp10.Balance == nil { + w.Write(rlp.EmptyString) + } else { + w.WriteUint256(_tmp10.Balance) + } + w.ListEnd(_tmp11) + } + w.ListEnd(_tmp9) + _tmp12 := w.List() + for _, _tmp13 := range obj.NonceChanges { + _tmp14 := w.List() + w.WriteUint64(uint64(_tmp13.TxIdx)) + w.WriteUint64(_tmp13.Nonce) + w.ListEnd(_tmp14) + } + w.ListEnd(_tmp12) + _tmp15 := w.List() + for _, _tmp16 := range obj.CodeChanges { + _tmp17 := w.List() + w.WriteUint64(uint64(_tmp16.TxIndex)) + w.WriteBytes(_tmp16.Code) + w.ListEnd(_tmp17) + } + w.ListEnd(_tmp15) w.ListEnd(_tmp0) return w.Flush() } -func (obj *BlockAccessList) DecodeRLP(dec *rlp.Stream) error { - var _tmp0 BlockAccessList +func (obj *AccountAccess) DecodeRLP(dec *rlp.Stream) error { + var _tmp0 AccountAccess { if _, err := dec.List(); err != nil { return err } - // Accesses: - var _tmp1 []AccountAccess + // Address: + var _tmp1 common.Address + if err := dec.ReadBytes(_tmp1[:]); err != nil { + return err + } + _tmp0.Address = _tmp1 + // StorageChanges: + var _tmp2 []encodingSlotWrites if _, err := dec.List(); err != nil { return err } for dec.MoreDataInList() { - var _tmp2 AccountAccess + var _tmp3 encodingSlotWrites { if _, err := dec.List(); err != nil { return err } - // Address: - var _tmp3 [20]byte - if err := dec.ReadBytes(_tmp3[:]); err != nil { + // Slot: + _tmp4 := new(EncodedStorage) + if err := _tmp4.DecodeRLP(dec); err != nil { return err } - _tmp2.Address = _tmp3 - // StorageWrites: - var _tmp4 []encodingSlotWrites + _tmp3.Slot = _tmp4 + // Accesses: + var _tmp5 []encodingStorageWrite if _, err := dec.List(); err != nil { return err } for dec.MoreDataInList() { - var _tmp5 encodingSlotWrites - { - if _, err := dec.List(); err != nil { - return err - } - // Slot: - var _tmp6 [32]byte - if err := dec.ReadBytes(_tmp6[:]); err != nil { - return err - } - _tmp5.Slot = _tmp6 - // Accesses: - var _tmp7 []encodingStorageWrite - if _, err := dec.List(); err != nil { - return err - } - for dec.MoreDataInList() { - var _tmp8 encodingStorageWrite - { - if _, err := dec.List(); err != nil { - return err - } - // TxIdx: - _tmp9, err := dec.Uint16() - if err != nil { - return err - } - _tmp8.TxIdx = _tmp9 - // ValueAfter: - var _tmp10 [32]byte - if err := dec.ReadBytes(_tmp10[:]); err != nil { - return err - } - _tmp8.ValueAfter = _tmp10 - if err := dec.ListEnd(); err != nil { - return err - } - } - _tmp7 = append(_tmp7, _tmp8) - } - if err := dec.ListEnd(); err != nil { - return err - } - _tmp5.Accesses = _tmp7 - if err := dec.ListEnd(); err != nil { - return err - } - } - _tmp4 = append(_tmp4, _tmp5) - } - if err := dec.ListEnd(); err != nil { - return err - } - _tmp2.StorageWrites = _tmp4 - // StorageReads: - var _tmp11 [][32]byte - if _, err := dec.List(); err != nil { - return err - } - for dec.MoreDataInList() { - var _tmp12 [32]byte - if err := dec.ReadBytes(_tmp12[:]); err != nil { - return err - } - _tmp11 = append(_tmp11, _tmp12) - } - if err := dec.ListEnd(); err != nil { - return err - } - _tmp2.StorageReads = _tmp11 - // BalanceChanges: - var _tmp13 []encodingBalanceChange - if _, err := dec.List(); err != nil { - return err - } - for dec.MoreDataInList() { - var _tmp14 encodingBalanceChange + var _tmp6 encodingStorageWrite { if _, err := dec.List(); err != nil { return err } // TxIdx: - _tmp15, err := dec.Uint16() + _tmp7, err := dec.Uint16() if err != nil { return err } - _tmp14.TxIdx = _tmp15 - // Balance: - var _tmp16 [16]byte - if err := dec.ReadBytes(_tmp16[:]); err != nil { + _tmp6.TxIdx = _tmp7 + // ValueAfter: + _tmp8 := new(EncodedStorage) + if err := _tmp8.DecodeRLP(dec); err != nil { return err } - _tmp14.Balance = _tmp16 + _tmp6.ValueAfter = _tmp8 if err := dec.ListEnd(); err != nil { return err } } - _tmp13 = append(_tmp13, _tmp14) + _tmp5 = append(_tmp5, _tmp6) } if err := dec.ListEnd(); err != nil { return err } - _tmp2.BalanceChanges = _tmp13 - // NonceChanges: - var _tmp17 []encodingAccountNonce - if _, err := dec.List(); err != nil { - return err - } - for dec.MoreDataInList() { - var _tmp18 encodingAccountNonce - { - if _, err := dec.List(); err != nil { - return err - } - // TxIdx: - _tmp19, err := dec.Uint16() - if err != nil { - return err - } - _tmp18.TxIdx = _tmp19 - // Nonce: - _tmp20, err := dec.Uint64() - if err != nil { - return err - } - _tmp18.Nonce = _tmp20 - if err := dec.ListEnd(); err != nil { - return err - } - } - _tmp17 = append(_tmp17, _tmp18) - } - if err := dec.ListEnd(); err != nil { - return err - } - _tmp2.NonceChanges = _tmp17 - // CodeChanges: - var _tmp21 []encodingCodeChange - if _, err := dec.List(); err != nil { - return err - } - for dec.MoreDataInList() { - var _tmp22 encodingCodeChange - { - if _, err := dec.List(); err != nil { - return err - } - // TxIndex: - _tmp23, err := dec.Uint16() - if err != nil { - return err - } - _tmp22.TxIndex = _tmp23 - // Code: - _tmp24, err := dec.Bytes() - if err != nil { - return err - } - _tmp22.Code = _tmp24 - if err := dec.ListEnd(); err != nil { - return err - } - } - _tmp21 = append(_tmp21, _tmp22) - } - if err := dec.ListEnd(); err != nil { - return err - } - _tmp2.CodeChanges = _tmp21 + _tmp3.Accesses = _tmp5 if err := dec.ListEnd(); err != nil { return err } } - _tmp1 = append(_tmp1, _tmp2) + _tmp2 = append(_tmp2, _tmp3) } if err := dec.ListEnd(); err != nil { return err } - _tmp0.Accesses = _tmp1 + _tmp0.StorageChanges = _tmp2 + // StorageReads: + var _tmp9 []*EncodedStorage + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + _tmp10 := new(EncodedStorage) + if err := _tmp10.DecodeRLP(dec); err != nil { + return err + } + _tmp9 = append(_tmp9, _tmp10) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.StorageReads = _tmp9 + // BalanceChanges: + var _tmp11 []encodingBalanceChange + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + var _tmp12 encodingBalanceChange + { + if _, err := dec.List(); err != nil { + return err + } + // TxIdx: + _tmp13, err := dec.Uint16() + if err != nil { + return err + } + _tmp12.TxIdx = _tmp13 + // Balance: + var _tmp14 uint256.Int + if err := dec.ReadUint256(&_tmp14); err != nil { + return err + } + _tmp12.Balance = &_tmp14 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp11 = append(_tmp11, _tmp12) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.BalanceChanges = _tmp11 + // NonceChanges: + var _tmp15 []encodingAccountNonce + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + var _tmp16 encodingAccountNonce + { + if _, err := dec.List(); err != nil { + return err + } + // TxIdx: + _tmp17, err := dec.Uint16() + if err != nil { + return err + } + _tmp16.TxIdx = _tmp17 + // Nonce: + _tmp18, err := dec.Uint64() + if err != nil { + return err + } + _tmp16.Nonce = _tmp18 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp15 = append(_tmp15, _tmp16) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.NonceChanges = _tmp15 + // CodeChanges: + var _tmp19 []encodingCodeChange + if _, err := dec.List(); err != nil { + return err + } + for dec.MoreDataInList() { + var _tmp20 encodingCodeChange + { + if _, err := dec.List(); err != nil { + return err + } + // TxIndex: + _tmp21, err := dec.Uint16() + if err != nil { + return err + } + _tmp20.TxIndex = _tmp21 + // Code: + _tmp22, err := dec.Bytes() + if err != nil { + return err + } + _tmp20.Code = _tmp22 + if err := dec.ListEnd(); err != nil { + return err + } + } + _tmp19 = append(_tmp19, _tmp20) + } + if err := dec.ListEnd(); err != nil { + return err + } + _tmp0.CodeChanges = _tmp19 if err := dec.ListEnd(); err != nil { return err } diff --git a/core/types/bal/bal_test.go b/core/types/bal/bal_test.go index 52c0de825e..a0538c2b95 100644 --- a/core/types/bal/bal_test.go +++ b/core/types/bal/bal_test.go @@ -36,9 +36,9 @@ func equalBALs(a *BlockAccessList, b *BlockAccessList) bool { return true } -func makeTestConstructionBAL() *ConstructionBlockAccessList { - return &ConstructionBlockAccessList{ - map[common.Address]*ConstructionAccountAccess{ +func makeTestConstructionBAL() *AccessListBuilder { + return &AccessListBuilder{ + FinalizedAccesses: map[common.Address]*ConstructionAccountAccesses{ common.BytesToAddress([]byte{0xff, 0xff}): { StorageWrites: map[common.Hash]map[uint16]common.Hash{ common.BytesToHash([]byte{0x01}): { @@ -60,9 +60,10 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList { 1: 2, 2: 6, }, - CodeChange: map[uint16][]byte{ - 0: common.Hex2Bytes("deadbeef"), - }, + CodeChanges: map[uint16]CodeChange{0: { + TxIdx: 0, + Code: common.Hex2Bytes("deadbeef"), + }}, }, common.BytesToAddress([]byte{0xff, 0xff, 0xff}): { StorageWrites: map[common.Hash]map[uint16]common.Hash{ @@ -84,9 +85,6 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList { NonceChanges: map[uint16]uint64{ 1: 2, }, - CodeChange: map[uint16][]byte{ - 0: common.Hex2Bytes("deadbeef"), - }, }, }, } @@ -95,7 +93,8 @@ func makeTestConstructionBAL() *ConstructionBlockAccessList { // TestBALEncoding tests that a populated access list can be encoded/decoded correctly. func TestBALEncoding(t *testing.T) { var buf bytes.Buffer - bal := makeTestConstructionBAL() + balBuilder := makeTestConstructionBAL() + bal := balBuilder.FinalizedAccesses err := bal.EncodeRLP(&buf) if err != nil { t.Fatalf("encoding failed: %v\n", err) @@ -104,10 +103,10 @@ func TestBALEncoding(t *testing.T) { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 10000000)); err != nil { t.Fatalf("decoding failed: %v\n", err) } - if dec.Hash() != bal.toEncodingObj().Hash() { + if dec.Hash() != bal.ToEncodingObj().Hash() { t.Fatalf("encoded block hash doesn't match decoded") } - if !equalBALs(bal.toEncodingObj(), &dec) { + if !equalBALs(bal.ToEncodingObj(), &dec) { t.Fatal("decoded BAL doesn't match") } } @@ -115,18 +114,18 @@ func TestBALEncoding(t *testing.T) { func makeTestAccountAccess(sort bool) AccountAccess { var ( storageWrites []encodingSlotWrites - storageReads [][32]byte + storageReads []common.Hash balances []encodingBalanceChange nonces []encodingAccountNonce ) for i := 0; i < 5; i++ { slot := encodingSlotWrites{ - Slot: testrand.Hash(), + Slot: newEncodedStorageFromHash(testrand.Hash()), } for j := 0; j < 3; j++ { slot.Accesses = append(slot.Accesses, encodingStorageWrite{ TxIdx: uint16(2 * j), - ValueAfter: testrand.Hash(), + ValueAfter: newEncodedStorageFromHash(testrand.Hash()), }) } if sort { @@ -138,7 +137,7 @@ func makeTestAccountAccess(sort bool) AccountAccess { } if sort { slices.SortFunc(storageWrites, func(a, b encodingSlotWrites) int { - return bytes.Compare(a.Slot[:], b.Slot[:]) + return bytes.Compare(a.Slot.inner.Bytes(), b.Slot.inner.Bytes()) }) } @@ -146,7 +145,7 @@ func makeTestAccountAccess(sort bool) AccountAccess { storageReads = append(storageReads, testrand.Hash()) } if sort { - slices.SortFunc(storageReads, func(a, b [32]byte) int { + slices.SortFunc(storageReads, func(a, b common.Hash) int { return bytes.Compare(a[:], b[:]) }) } @@ -154,7 +153,7 @@ func makeTestAccountAccess(sort bool) AccountAccess { for i := 0; i < 5; i++ { balances = append(balances, encodingBalanceChange{ TxIdx: uint16(2 * i), - Balance: [16]byte(testrand.Bytes(16)), + Balance: new(uint256.Int).SetBytes(testrand.Bytes(32)), }) } if sort { @@ -175,16 +174,20 @@ func makeTestAccountAccess(sort bool) AccountAccess { }) } + var encodedStorageReads []*EncodedStorage + for _, slot := range storageReads { + encodedStorageReads = append(encodedStorageReads, newEncodedStorageFromHash(slot)) + } return AccountAccess{ Address: [20]byte(testrand.Bytes(20)), - StorageWrites: storageWrites, - StorageReads: storageReads, + StorageChanges: storageWrites, + StorageReads: encodedStorageReads, BalanceChanges: balances, NonceChanges: nonces, - CodeChanges: []encodingCodeChange{ + CodeChanges: []CodeChange{ { - TxIndex: 100, - Code: testrand.Bytes(256), + TxIdx: 100, + Code: testrand.Bytes(256), }, }, } @@ -193,10 +196,10 @@ func makeTestAccountAccess(sort bool) AccountAccess { func makeTestBAL(sort bool) BlockAccessList { list := BlockAccessList{} for i := 0; i < 5; i++ { - list.Accesses = append(list.Accesses, makeTestAccountAccess(sort)) + list = append(list, makeTestAccountAccess(sort)) } if sort { - slices.SortFunc(list.Accesses, func(a, b AccountAccess) int { + slices.SortFunc(list, func(a, b AccountAccess) int { return bytes.Compare(a.Address[:], b.Address[:]) }) } @@ -216,9 +219,9 @@ func TestBlockAccessListCopy(t *testing.T) { } // Make sure the mutations on copy won't affect the origin - for _, aa := range cpyCpy.Accesses { + for _, aa := range cpyCpy { for i := 0; i < len(aa.StorageReads); i++ { - aa.StorageReads[i] = [32]byte(testrand.Bytes(32)) + aa.StorageReads[i] = &EncodedStorage{new(uint256.Int).SetBytes(testrand.Bytes(32))} } } if !reflect.DeepEqual(list, cpy) { @@ -228,8 +231,9 @@ func TestBlockAccessListCopy(t *testing.T) { func TestBlockAccessListValidation(t *testing.T) { // Validate the block access list after RLP decoding + testBALMaxIndex := 8 enc := makeTestBAL(true) - if err := enc.Validate(); err != nil { + if err := enc.Validate(testBALMaxIndex); err != nil { t.Fatalf("Unexpected validation error: %v", err) } var buf bytes.Buffer @@ -241,14 +245,17 @@ func TestBlockAccessListValidation(t *testing.T) { if err := dec.DecodeRLP(rlp.NewStream(bytes.NewReader(buf.Bytes()), 0)); err != nil { t.Fatalf("Unexpected RLP-decode error: %v", err) } - if err := dec.Validate(); err != nil { + if err := dec.Validate(testBALMaxIndex); err != nil { t.Fatalf("Unexpected validation error: %v", err) } // Validate the derived block access list - cBAL := makeTestConstructionBAL() - listB := cBAL.toEncodingObj() - if err := listB.Validate(); err != nil { + cBAL := makeTestConstructionBAL().FinalizedAccesses + listB := cBAL.ToEncodingObj() + if err := listB.Validate(testBALMaxIndex); err != nil { t.Fatalf("Unexpected validation error: %v", err) } } + +// BALReader test ideas +// * BAL which doesn't have any pre-tx system contracts should return an empty state diff at idx 0 diff --git a/core/types/bal_blocks_test.go b/core/types/bal_blocks_test.go new file mode 100644 index 0000000000..9500cf8ca7 --- /dev/null +++ b/core/types/bal_blocks_test.go @@ -0,0 +1,32 @@ +package types + +import ( + "bytes" + "fmt" + "github.com/ethereum/go-ethereum/rlp" + "io" + "os" + "testing" +) + +func TestBALDecoding(t *testing.T) { + var ( + err error + data []byte + ) + data, err = os.ReadFile("blocks_bal_one.rlp") + if err != nil { + t.Fatalf("error opening file: %v", err) + } + reader := bytes.NewReader(data) + stream := rlp.NewStream(reader, 0) + var blocks Block + for i := 0; err == nil; i++ { + fmt.Printf("decode %d\n", i) + err = stream.Decode(&blocks) + if err != nil && err != io.EOF { + t.Fatalf("error decoding blocks: %v", err) + } + fmt.Printf("block number is %d\n", blocks.NumberU64()) + } +} diff --git a/core/types/block.go b/core/types/block.go index d092351b58..1bf003d1ea 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -28,6 +28,8 @@ import ( "sync/atomic" "time" + "github.com/ethereum/go-ethereum/core/types/bal" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rlp" @@ -99,6 +101,9 @@ type Header struct { // RequestsHash was added by EIP-7685 and is ignored in legacy headers. RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` + // BlockAccessListHash was added by EIP-7928 and is ignored in legacy headers. + BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"` + // SlotNumber was added by EIP-7843 and is ignored in legacy headers. SlotNumber *uint64 `json:"slotNumber" rlp:"optional"` } @@ -163,10 +168,8 @@ func (h *Header) SanityCheck() error { // EmptyBody returns true if there is no additional 'body' to complete the header // that is: no transactions, no uncles and no withdrawals. func (h *Header) EmptyBody() bool { - var ( - emptyWithdrawals = h.WithdrawalsHash == nil || *h.WithdrawalsHash == EmptyWithdrawalsHash - ) - return h.TxHash == EmptyTxsHash && h.UncleHash == EmptyUncleHash && emptyWithdrawals + // quick hack to ensure that we download bodies for empty blocks so that we receive the BALs + return false } // EmptyReceipts returns true if there are no receipts for this header/block. @@ -204,6 +207,7 @@ type Block struct { uncles []*Header transactions Transactions withdrawals Withdrawals + accessList *bal.BlockAccessList // caches hash atomic.Pointer[common.Hash] @@ -220,7 +224,8 @@ type extblock struct { Header *Header Txs []*Transaction Uncles []*Header - Withdrawals []*Withdrawal `rlp:"optional"` + Withdrawals []*Withdrawal `rlp:"optional"` + AccessList *bal.BlockAccessList `rlp:"optional"` } // NewBlock creates a new block. The input data is copied, changes to header and to the @@ -284,6 +289,14 @@ func NewBlock(header *Header, body *Body, receipts []*Receipt, hasher ListHasher return b } +func NewBlockWithAccessList(header *Header, body *Body, receipts []*Receipt, accessList *bal.BlockAccessList, hasher ListHasher) *Block { + block := NewBlock(header, body, receipts, hasher) + block.accessList = accessList + balHash := accessList.Hash() + block.header.BlockAccessListHash = &balHash + return block +} + // CopyHeader creates a deep copy of a block header. func CopyHeader(h *Header) *Header { cpy := *h @@ -329,12 +342,14 @@ func CopyHeader(h *Header) *Header { // DecodeRLP decodes a block from RLP. func (b *Block) DecodeRLP(s *rlp.Stream) error { - var eb extblock + var ( + eb extblock + ) _, size, _ := s.Kind() if err := s.Decode(&eb); err != nil { return err } - b.header, b.uncles, b.transactions, b.withdrawals = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals + b.header, b.uncles, b.transactions, b.withdrawals, b.accessList = eb.Header, eb.Uncles, eb.Txs, eb.Withdrawals, eb.AccessList b.size.Store(rlp.ListSize(size)) return nil } @@ -346,6 +361,7 @@ func (b *Block) EncodeRLP(w io.Writer) error { Txs: b.transactions, Uncles: b.uncles, Withdrawals: b.withdrawals, + AccessList: b.accessList, }) } @@ -358,9 +374,10 @@ func (b *Block) Body() *Body { // Accessors for body data. These do not return a copy because the content // of the body slices does not affect the cached hash/size in block. -func (b *Block) Uncles() []*Header { return b.uncles } -func (b *Block) Transactions() Transactions { return b.transactions } -func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } +func (b *Block) Uncles() []*Header { return b.uncles } +func (b *Block) Transactions() Transactions { return b.transactions } +func (b *Block) Withdrawals() Withdrawals { return b.withdrawals } +func (b *Block) AccessList() *bal.BlockAccessList { return b.accessList } func (b *Block) Transaction(hash common.Hash) *Transaction { for _, transaction := range b.transactions { @@ -513,6 +530,24 @@ func (b *Block) WithBody(body Body) *Block { return block } +// WithAccessList returns a copy of the block with the access list embedded. +// It does not set the access list hash in the header of the returned block. +// TODO: ^ when support for --experimental.bal is removed, this function should set the access list hash in the header +func (b *Block) WithAccessList(accessList *bal.BlockAccessList) *Block { + alCopy := accessList.Copy() + block := &Block{ + header: b.header, + transactions: slices.Clone(b.transactions), + uncles: make([]*Header, len(b.uncles)), + withdrawals: slices.Clone(b.withdrawals), + accessList: &alCopy, + } + for i := range b.uncles { + block.uncles[i] = CopyHeader(b.uncles[i]) + } + return block +} + // Hash returns the keccak256 hash of b's header. // The hash is computed on the first call and cached thereafter. func (b *Block) Hash() common.Hash { diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 16fb03f612..2e2f1cdca5 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,29 +16,30 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h Header) MarshalJSON() ([]byte, error) { type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` - RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` - SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` - Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` + BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"` + SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` + Hash common.Hash `json:"hash"` } var enc Header enc.ParentHash = h.ParentHash @@ -62,6 +63,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) enc.ParentBeaconRoot = h.ParentBeaconRoot enc.RequestsHash = h.RequestsHash + enc.BlockAccessListHash = h.BlockAccessListHash enc.SlotNumber = (*hexutil.Uint64)(h.SlotNumber) enc.Hash = h.Hash() return json.Marshal(&enc) @@ -70,28 +72,29 @@ func (h Header) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (h *Header) UnmarshalJSON(input []byte) error { type Header struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom *Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` - BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` - RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` - SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase *common.Address `json:"miner"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + RequestsHash *common.Hash `json:"requestsHash" rlp:"optional"` + BlockAccessListHash *common.Hash `json:"balHash" rlp:"optional"` + SlotNumber *hexutil.Uint64 `json:"slotNumber" rlp:"optional"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -172,6 +175,9 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.RequestsHash != nil { h.RequestsHash = dec.RequestsHash } + if dec.BlockAccessListHash != nil { + h.BlockAccessListHash = dec.BlockAccessListHash + } if dec.SlotNumber != nil { h.SlotNumber = (*uint64)(dec.SlotNumber) } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index cfbd57ab8a..3b7eb2c926 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -43,8 +43,9 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { _tmp4 := obj.ExcessBlobGas != nil _tmp5 := obj.ParentBeaconRoot != nil _tmp6 := obj.RequestsHash != nil - _tmp7 := obj.SlotNumber != nil - if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + _tmp7 := obj.BlockAccessListHash != nil + _tmp8 := obj.SlotNumber != nil + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -54,42 +55,49 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } - if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 { if obj.WithdrawalsHash == nil { w.Write([]byte{0x80}) } else { w.WriteBytes(obj.WithdrawalsHash[:]) } } - if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 { + if _tmp3 || _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 { if obj.BlobGasUsed == nil { w.Write([]byte{0x80}) } else { w.WriteUint64((*obj.BlobGasUsed)) } } - if _tmp4 || _tmp5 || _tmp6 || _tmp7 { + if _tmp4 || _tmp5 || _tmp6 || _tmp7 || _tmp8 { if obj.ExcessBlobGas == nil { w.Write([]byte{0x80}) } else { w.WriteUint64((*obj.ExcessBlobGas)) } } - if _tmp5 || _tmp6 || _tmp7 { + if _tmp5 || _tmp6 || _tmp7 || _tmp8 { if obj.ParentBeaconRoot == nil { w.Write([]byte{0x80}) } else { w.WriteBytes(obj.ParentBeaconRoot[:]) } } - if _tmp6 || _tmp7 { + if _tmp6 || _tmp7 || _tmp8 { if obj.RequestsHash == nil { w.Write([]byte{0x80}) } else { w.WriteBytes(obj.RequestsHash[:]) } } - if _tmp7 { + if _tmp7 || _tmp8 { + if obj.BlockAccessListHash == nil { + w.Write([]byte{0x80}) + } else { + w.WriteBytes(obj.BlockAccessListHash[:]) + } + } + if _tmp8 { if obj.SlotNumber == nil { w.Write([]byte{0x80}) } else { diff --git a/core/vm/evm.go b/core/vm/evm.go index ae28b89c9f..b4a611cf8b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -489,25 +489,32 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b // create creates a new contract using code as deployment code. func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas uint64, err error) { + // Depth check execution. Fail if we're trying to execute above the + // limit. + var nonce uint64 + if evm.depth > int(params.CallCreateDepth) { + err = ErrDepth + } else if !evm.Context.CanTransfer(evm.StateDB, caller, value) { + err = ErrInsufficientBalance + } else { + nonce = evm.StateDB.GetNonce(caller) + if nonce+1 < nonce { + err = ErrNonceUintOverflow + } + } + + if err == nil { + evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) + } if evm.Config.Tracer != nil { evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig()) defer func(startGas uint64) { evm.captureEnd(evm.depth, startGas, leftOverGas, ret, err) }(gas) } - // Depth check execution. Fail if we're trying to execute above the - // limit. - if evm.depth > int(params.CallCreateDepth) { - return nil, common.Address{}, gas, ErrDepth + if err != nil { + return nil, common.Address{}, gas, err } - if !evm.Context.CanTransfer(evm.StateDB, caller, value) { - return nil, common.Address{}, gas, ErrInsufficientBalance - } - nonce := evm.StateDB.GetNonce(caller) - if nonce+1 < nonce { - return nil, common.Address{}, gas, ErrNonceUintOverflow - } - evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator) // Charge the contract creation init gas in verkle mode if evm.chainRules.IsEIP4762 { @@ -533,6 +540,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas uint64, value *ui // - the storage is non-empty contractHash := evm.StateDB.GetCodeHash(address) storageRoot := evm.StateDB.GetStorageRoot(address) + if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (storageRoot != (common.Hash{}) && storageRoot != types.EmptyRootHash) { // non-empty storage diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 23a2cbbf4d..0f4fa124aa 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -374,7 +374,33 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor return gas, nil } -func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + ) + + if transfersValue { + if evm.readOnly { + return 0, ErrWriteProtection + } else if !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } + } + + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, ErrGasUintOverflow + } + + return gas, nil +} + +func gasCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 transfersValue = !stack.Back(2).IsZero() @@ -391,15 +417,22 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } else if !evm.StateDB.Exist(address) { gas += params.CallNewAccountGas } - if transfersValue && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas - } - memoryGas, err := memoryGasCost(mem, memorySize) + + return gas, nil +} +func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + stateless, err := gasCallStateless(evm, contract, stack, mem, memorySize) if err != nil { return 0, err } - var overflow bool - if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + + stateful, err := gasCallStateful(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + + gas, overflow := math.SafeAdd(stateless, stateful) + if overflow { return 0, ErrGasUintOverflow } @@ -410,25 +443,41 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } - return gas, nil } -func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCallCodeStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasCallCodeStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } var ( - gas uint64 - overflow bool + gas uint64 + overflow bool + transfersValue = !stack.Back(2).IsZero() ) - if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { - gas += params.CallValueTransferGas + if transfersValue { + if !evm.chainRules.IsEIP4762 { + gas += params.CallValueTransferGas + } } if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { return 0, ErrGasUintOverflow } + return gas, nil +} + +func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var overflow bool + gas, err := gasCallCodeStateless(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err @@ -440,10 +489,16 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory } func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) + var ( + err error + gas uint64 + ) + + gas, err = gasDelegateCallStateless(evm, contract, stack, mem, memorySize) if err != nil { return 0, err } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err @@ -455,11 +510,36 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me return gas, nil } -func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasDelegateCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasDelegateCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { return 0, err } + return gas, nil +} + +func gasStaticCallStateless(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + return gas, nil +} + +func gasStaticCallStateful(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return 0, nil +} + +func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasStaticCallStateless(evm, contract, stack, mem, memorySize) + if err != nil { + return 0, err + } + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err @@ -477,11 +557,16 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } var gas uint64 + // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { gas = params.SelfdestructGasEIP150 var address = common.Address(stack.Back(0).Bytes20()) + if gas > contract.Gas { + return gas, nil + } + if evm.chainRules.IsEIP158 { // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index bdf8bd8a52..7db6949952 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -516,9 +516,6 @@ func opSload(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { } func opSstore(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { - if evm.readOnly { - return nil, ErrWriteProtection - } loc := scope.Stack.pop() val := scope.Stack.pop() evm.StateDB.SetState(scope.Contract.Address(), loc.Bytes32(), val.Bytes32()) diff --git a/core/vm/interface.go b/core/vm/interface.go index e285b18b0f..98ff3b823d 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" ) @@ -94,5 +95,5 @@ type StateDB interface { AccessEvents() *state.AccessEvents // Finalise must be invoked at the end of a transaction - Finalise(bool) + Finalise(bool) bal.StateMutations } diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 89a2ebf6f4..dc63e633bd 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -28,6 +28,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { switch { case rules.IsVerkle: return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") + case rules.IsAmsterdam: + return newPragueInstructionSet(), nil case rules.IsOsaka: return newOsakaInstructionSet(), nil case rules.IsPrague: diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index ce394d9384..716a82ab97 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -155,50 +155,12 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem return 0, nil } -func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - addr := common.Address(stack.Back(addressPosition).Bytes20()) - // Check slot presence in the access list - warmAccess := evm.StateDB.AddressInAccessList(addr) - // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so - // the cost to charge for cold access, if any, is Cold - Warm - coldCost := params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929 - if !warmAccess { - evm.StateDB.AddAddressToAccessList(addr) - // Charge the remaining difference here already, to correctly calculate available - // gas for call - if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return 0, ErrOutOfGas - } - } - // Now call the old calculator, which takes into account - // - create new account - // - transfer value - // - memory expansion - // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) - if warmAccess || err != nil { - return gas, err - } - // In case of a cold access, we temporarily add the cold charge back, and also - // add it to the returned gas. By adding it to the return, it will be charged - // outside of this function, as part of the dynamic gas, and that will make it - // also become correctly reported to tracers. - contract.Gas += coldCost - - var overflow bool - if gas, overflow = math.SafeAdd(gas, coldCost); overflow { - return 0, ErrGasUintOverflow - } - return gas, nil - } -} - var ( - gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall, 1) - gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall, 1) - gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall, 1) - gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode, 1) + // TODO: we can use the same functions already defined above for the 7702 gas handlers + gasCallEIP2929 = makeCallVariantGasCall(gasCallStateless, gasCallStateful) + gasDelegateCallEIP2929 = makeCallVariantGasCall(gasDelegateCallStateless, gasDelegateCallStateful) + gasStaticCallEIP2929 = makeCallVariantGasCall(gasStaticCallStateless, gasStaticCallStateful) + gasCallCodeEIP2929 = makeCallVariantGasCall(gasCallCodeStateless, gasCallCodeStateful) gasSelfdestructEIP2929 = makeSelfdestructGasFn(true) // gasSelfdestructEIP3529 implements the changes in EIP-3529 (no refunds) gasSelfdestructEIP3529 = makeSelfdestructGasFn(false) @@ -243,6 +205,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return 0, ErrOutOfGas } } + if contract.Gas < gas { + return gas, nil + } + // if empty and transfers value if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas @@ -256,33 +222,25 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { } var ( - innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCall) - gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCall) - gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCall) - gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCode) + gasCallEIP7702 = makeCallVariantGasCall(gasCallStateful, gasCallStateless) + gasDelegateCallEIP7702 = makeCallVariantGasCall(gasDelegateCallStateful, gasDelegateCallStateless) + gasStaticCallEIP7702 = makeCallVariantGasCall(gasStaticCallStateful, gasStaticCallStateless) + gasCallCodeEIP7702 = makeCallVariantGasCall(gasCallCodeStateful, gasCallCodeStateless) ) -func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // Return early if this call attempts to transfer value in a static context. - // Although it's checked in `gasCall`, EIP-7702 loads the target's code before - // to determine if it is resolving a delegation. This could incorrectly record - // the target in the block access list (BAL) if the call later fails. - transfersValue := !stack.Back(2).IsZero() - if evm.readOnly && transfersValue { - return 0, ErrWriteProtection - } - return innerGasCallEIP7702(evm, contract, stack, mem, memorySize) -} - -func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { +func makeCallVariantGasCall(oldCalculatorStateful, oldCalculatorStateless gasFunc) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( - total uint64 // total dynamic gas used - addr = common.Address(stack.Back(1).Bytes20()) + eip150BaseGas uint64 // gas used for memory expansion, transfer costs -> input to the 63/64 bounding + eip7702Gas uint64 + eip2929Gas uint64 + addr = common.Address(stack.Back(1).Bytes20()) + overflow bool + err error ) // Check slot presence in the access list - if !evm.StateDB.AddressInAccessList(addr) { + if evm.chainRules.IsEIP2929 && !evm.StateDB.AddressInAccessList(addr) { evm.StateDB.AddAddressToAccessList(addr) // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so // the cost to charge for cold access, if any, is Cold - Warm @@ -292,44 +250,87 @@ func makeCallVariantGasCallEIP7702(oldCalculator gasFunc) gasFunc { if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } - total += coldCost + eip2929Gas = coldCost } - - // Check if code is a delegation and if so, charge for resolution. - if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { - var cost uint64 - if evm.StateDB.AddressInAccessList(target) { - cost = params.WarmStorageReadCostEIP2929 - } else { - evm.StateDB.AddAddressToAccessList(target) - cost = params.ColdAccountAccessCostEIP2929 - } - if !contract.UseGas(cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { - return 0, ErrOutOfGas - } - total += cost - } - - // Now call the old calculator, which takes into account - // - create new account - // - transfer value - // - memory expansion - // - 63/64ths rule - old, err := oldCalculator(evm, contract, stack, mem, memorySize) + eip150BaseGas, err = oldCalculatorStateless(evm, contract, stack, mem, memorySize) if err != nil { - return old, err + return 0, err } + // ensure the portion of the call cost which doesn't depend on state lookups + // is covered by the provided gas + if contract.Gas < eip150BaseGas { + return 0, ErrOutOfGas + } + + oldStateful, err := oldCalculatorStateful(evm, contract, stack, mem, memorySize) + if err != nil { + return oldStateful, err + } + + // this should cause BAL test failures if uncommented + baseCost, overflow := math.SafeAdd(eip150BaseGas, oldStateful) + if overflow { + return 0, ErrGasUintOverflow + } else if contract.Gas < baseCost { + return 0, ErrOutOfGas + } + + if eip150BaseGas, overflow = math.SafeAdd(eip150BaseGas, oldStateful); overflow { + return 0, ErrOutOfGas + } + + if evm.chainRules.IsPrague { + // Check if code is a delegation and if so, charge for resolution. + if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok { + if evm.StateDB.AddressInAccessList(target) { + eip7702Gas = params.WarmStorageReadCostEIP2929 + } else { + evm.StateDB.AddAddressToAccessList(target) + eip7702Gas = params.ColdAccountAccessCostEIP2929 + } + if !contract.UseGas(eip7702Gas, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + return 0, ErrOutOfGas + } + } + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, eip150BaseGas, stack.Back(0)) + if err != nil { + return 0, err + } + + // TODO: it's not clear what happens if there is enough gas to cover the stateless component + // but not enough to cover the whole call: do all the state reads happen in this case, and + // we fail at the very end? + // Temporarily add the gas charge back to the contract and return value. By // adding it to the return, it will be charged outside of this function, as // part of the dynamic gas. This will ensure it is correctly reported to // tracers. - contract.Gas += total - - var overflow bool - if total, overflow = math.SafeAdd(old, total); overflow { + contract.Gas, overflow = math.SafeAdd(contract.Gas, eip2929Gas) + if overflow { return 0, ErrGasUintOverflow } - return total, nil + contract.Gas, overflow = math.SafeAdd(contract.Gas, eip7702Gas) + if overflow { + return 0, ErrGasUintOverflow + } + + var totalCost uint64 + totalCost, overflow = math.SafeAdd(eip2929Gas, eip7702Gas) + if overflow { + return 0, ErrGasUintOverflow + } + totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp) + if overflow { + return 0, ErrGasUintOverflow + } + totalCost, overflow = math.SafeAdd(totalCost, eip150BaseGas) + if overflow { + return 0, ErrGasUintOverflow + } + + return totalCost, nil } } diff --git a/eth/api_backend.go b/eth/api_backend.go index 3f826b7861..a816512eb2 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -19,6 +19,7 @@ package eth import ( "context" "errors" + "fmt" "math/big" "time" @@ -499,3 +500,22 @@ func (b *EthAPIBackend) RPCTxSyncDefaultTimeout() time.Duration { func (b *EthAPIBackend) RPCTxSyncMaxTimeout() time.Duration { return b.eth.config.TxSyncMaxTimeout } + +// GetBlockAccessList returns a block access list for the given number/hash +// or nil if one does not exist. +func (b *EthAPIBackend) BlockAccessListByNumberOrHash(number rpc.BlockNumberOrHash) (interface{}, error) { + var block *types.Block + if num := number.BlockNumber; num != nil { + block = b.eth.blockchain.GetBlockByNumber(uint64(num.Int64())) + } else if hash := number.BlockHash; hash != nil { + block = b.eth.blockchain.GetBlockByHash(*hash) + } + + if block == nil { + return nil, fmt.Errorf("block not found") + } + if block.AccessList() == nil { + return nil, nil + } + return block.AccessList().StringableRepresentation(), nil +} diff --git a/eth/backend.go b/eth/backend.go index eaa68b501c..156b3ac59b 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -275,6 +275,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { overrides.OverrideVerkle = config.OverrideVerkle } options.Overrides = &overrides + options.BALExecutionMode = config.BALExecutionMode eth.blockchain, err = core.NewBlockChain(chainDb, config.Genesis, eth.engine, options) if err != nil { diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 1e019ffb15..57a40e9ce6 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -201,9 +201,13 @@ func (api *ConsensusAPI) ForkchoiceUpdatedV3(update engine.ForkchoiceStateV1, pa return engine.STATUS_INVALID, attributesErr("missing withdrawals") case params.BeaconRoot == nil: return engine.STATUS_INVALID, attributesErr("missing beacon root") - case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5): + case !api.checkFork(params.Timestamp, forks.Cancun, forks.Prague, forks.Osaka, forks.BPO1, forks.BPO2, forks.BPO3, forks.BPO4, forks.BPO5, forks.Amsterdam): return engine.STATUS_INVALID, unsupportedForkErr("fcuV3 must only be called for cancun/prague/osaka payloads") } + + if api.checkFork(params.Timestamp, forks.Amsterdam) { + return api.forkchoiceUpdated(update, params, engine.PayloadV4, false) + } } // TODO(matt): the spec requires that fcu is applied when called on a valid // hash, even if params are wrong. To do this we need to split up @@ -498,6 +502,7 @@ func (api *ConsensusAPI) GetPayloadV6(payloadID engine.PayloadID) (*engine.Execu // // Note passing nil `forks`, `versions` disables the respective check. func (api *ConsensusAPI) getPayload(payloadID engine.PayloadID, full bool, versions []engine.PayloadVersion, forks []forks.Fork) (*engine.ExecutionPayloadEnvelope, error) { + log.Trace("Engine API request received", "method", "GetPayload", "id", payloadID) if versions != nil && !payloadID.Is(versions...) { return nil, engine.UnsupportedFork @@ -750,6 +755,8 @@ func (api *ConsensusAPI) NewPayloadV5(ctx context.Context, params engine.Executa return invalidStatus, paramsErr("nil beaconRoot post-cancun") case executionRequests == nil: return invalidStatus, paramsErr("nil executionRequests post-prague") + case params.BlockAccessList == nil: + return invalidStatus, paramsErr("nil block access list post-amsterdam") case params.SlotNumber == nil: return invalidStatus, paramsErr("nil slotnumber post-amsterdam") case !api.checkFork(params.Timestamp, forks.Amsterdam): @@ -759,7 +766,7 @@ func (api *ConsensusAPI) NewPayloadV5(ctx context.Context, params engine.Executa if err := validateRequests(requests); err != nil { return engine.PayloadStatusV1{Status: engine.INVALID}, engine.InvalidParams.With(err) } - return api.newPayload(ctx, params, versionedHashes, beaconRoot, requests, false) + return api.newPayload(context.Background(), params, versionedHashes, beaconRoot, requests, false) } func (api *ConsensusAPI) newPayload(ctx context.Context, params engine.ExecutableData, versionedHashes []common.Hash, beaconRoot *common.Hash, requests [][]byte, witness bool) (result engine.PayloadStatusV1, err error) { @@ -1180,6 +1187,10 @@ func getBody(block *types.Block) *engine.ExecutionPayloadBody { result.Withdrawals = []*types.Withdrawal{} } + if block.AccessList() != nil { + result.AccessList = block.AccessList() + } + return &result } diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 8aa6e4ef09..5f144243f4 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -209,6 +209,8 @@ type Config struct { // RangeLimit restricts the maximum range (end - start) for range queries. RangeLimit uint64 `toml:",omitempty"` + + BALExecutionMode int } // CreateConsensusEngine creates a consensus engine for the given chain config. diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go index 6f94a409e5..68f9b7dc6a 100644 --- a/eth/ethconfig/gen_config.go +++ b/eth/ethconfig/gen_config.go @@ -68,6 +68,7 @@ func (c Config) MarshalTOML() (interface{}, error) { TxSyncDefaultTimeout time.Duration `toml:",omitempty"` TxSyncMaxTimeout time.Duration `toml:",omitempty"` RangeLimit uint64 `toml:",omitempty"` + BALExecutionMode int } var enc Config enc.Genesis = c.Genesis @@ -121,6 +122,7 @@ func (c Config) MarshalTOML() (interface{}, error) { enc.TxSyncDefaultTimeout = c.TxSyncDefaultTimeout enc.TxSyncMaxTimeout = c.TxSyncMaxTimeout enc.RangeLimit = c.RangeLimit + enc.BALExecutionMode = c.BALExecutionMode return &enc, nil } @@ -178,6 +180,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { TxSyncDefaultTimeout *time.Duration `toml:",omitempty"` TxSyncMaxTimeout *time.Duration `toml:",omitempty"` RangeLimit *uint64 `toml:",omitempty"` + BALExecutionMode *int } var dec Config if err := unmarshal(&dec); err != nil { @@ -336,5 +339,8 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { if dec.RangeLimit != nil { c.RangeLimit = *dec.RangeLimit } + if dec.BALExecutionMode != nil { + c.BALExecutionMode = *dec.BALExecutionMode + } return nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index eed404622e..7cdbdec323 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1055,7 +1055,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor // Call Prepare to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) - _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm) + _, _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm) if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index ff6797f67b..6c65bff69e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1007,6 +1007,9 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param if block.Withdrawals() != nil { fields["withdrawals"] = block.Withdrawals() } + if block.AccessList() != nil { + fields["accessList"] = block.AccessList() + } return fields } @@ -1378,6 +1381,18 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } } +// GetBlockAccessListByBlockNumber returns a block access list for the given block number +// or nil if one does not exist. +func (api *BlockChainAPI) GetBlockAccessListByBlockNumber(number rpc.BlockNumber) (interface{}, error) { + return api.b.BlockAccessListByNumberOrHash(rpc.BlockNumberOrHash{BlockNumber: &number}) +} + +// GetBlockAccessListByBlockHash returns a block access list for the given block hash +// or nil if one does not exist. +func (api *BlockChainAPI) GetBlockAccessListByBlockHash(hash common.Hash) (interface{}, error) { + return api.b.BlockAccessListByNumberOrHash(rpc.BlockNumberOrHash{BlockHash: &hash}) +} + // TransactionAPI exposes methods for reading and creating transaction data. type TransactionAPI struct { b Backend diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index af3d592b82..222a0da479 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -74,6 +74,7 @@ type Backend interface { GetEVM(ctx context.Context, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) *vm.EVM SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription + BlockAccessListByNumberOrHash(number rpc.BlockNumberOrHash) (interface{}, error) // Transaction pool API SendTx(ctx context.Context, signedTx *types.Transaction) error diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 325ee6d5bb..d5688eeadb 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -354,11 +354,11 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, return nil, nil, nil, err } // EIP-7002 - if err := core.ProcessWithdrawalQueue(&requests, evm); err != nil { + if _, err := core.ProcessWithdrawalQueue(&requests, evm); err != nil { return nil, nil, nil, err } // EIP-7251 - if err := core.ProcessConsolidationQueue(&requests, evm); err != nil { + if _, err := core.ProcessConsolidationQueue(&requests, evm); err != nil { return nil, nil, nil, err } } @@ -372,7 +372,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, Withdrawals: *block.BlockOverrides.Withdrawals, } chainHeadReader := &simChainHeadReader{ctx, sim.b} - b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts) + b, err := sim.b.Engine().FinalizeAndAssemble(chainHeadReader, header, sim.state, blockBody, receipts, nil) if err != nil { return nil, nil, nil, err } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 9ba8776360..7c66337d53 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -474,6 +474,11 @@ web3._extend({ params: 1, inputFormatter: [null], }), + new web3._extend.Method({ + name: 'getEncodedBlockAccessList', + call: 'debug_getEncodedBlockAccessList', + params: 1 + }), ], properties: [] }); @@ -611,7 +616,17 @@ web3._extend({ name: 'config', call: 'eth_config', params: 0, - }) + }), + new web3._extend.Method({ + name: 'getBlockAccessListByBlockNumber', + call: 'eth_getBlockAccessListByBlockNumber', + params: 1, + }), + new web3._extend.Method({ + name: 'getBlockAccessListByBlockHash', + call: 'eth_getBlockAccessListByBlockHash', + params: 1, + }), ], properties: [ new web3._extend.Property({ diff --git a/miner/worker.go b/miner/worker.go index d1a8b25b2a..e573c81ff9 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -70,7 +71,8 @@ type environment struct { sidecars []*types.BlobTxSidecar blobs int - witness *stateless.Witness + witness *stateless.Witness + accessList bal.ConstructionBlockAccessList } // txFits reports whether the transaction fits into the block size limit. @@ -168,7 +170,10 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay } // Collect consensus-layer requests if Prague is enabled. - var requests [][]byte + var ( + requests [][]byte + postMut = make(bal.StateMutations) + ) if miner.chainConfig.IsPrague(work.header.Number, work.header.Time) { requests = [][]byte{} // EIP-6110 deposits @@ -176,23 +181,47 @@ func (miner *Miner) generateWork(genParam *generateParams, witness bool) *newPay return &newPayloadResult{err: err} } // EIP-7002 - if err := core.ProcessWithdrawalQueue(&requests, work.evm); err != nil { + mut, err := core.ProcessWithdrawalQueue(&requests, work.evm) + if err != nil { return &newPayloadResult{err: err} } + + postMut.Merge(mut) // EIP-7251 consolidations - if err := core.ProcessConsolidationQueue(&requests, work.evm); err != nil { + mut, err = core.ProcessConsolidationQueue(&requests, work.evm) + if err != nil { return &newPayloadResult{err: err} } + postMut.Merge(mut) + + work.accessList.AccumulateMutations(postMut, uint16(work.tcount)+1) + work.accessList.AccumulateReads(work.state.Reader().(state.StateReaderTracker).GetStateAccessList()) } if requests != nil { reqHash := types.CalcRequestsHash(requests) work.header.RequestsHash = &reqHash } - block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts) + // set the block access list on the body after the block has finished executing + // but before the header hash is computed (in FinalizeAndAssemble). + // + // I considered trying to instantiate the beacon consensus engine with a tracer. + // however, the BAL tracer instance is used once per block, while the engine object + // lives for the entire time the client is running. + var onBlockFinalization func(mutations bal.StateMutations) *bal.BlockAccessList + if miner.chainConfig.IsAmsterdam(work.header.Number, work.header.Time) { + onBlockFinalization = func(withdrawalMut bal.StateMutations) *bal.BlockAccessList { + work.accessList.AccumulateMutations(withdrawalMut, uint16(work.tcount)+1) + work.accessList.AccumulateReads(work.state.Reader().(state.StateReaderTracker).GetStateAccessList()) + return work.accessList.ToEncodingObj() + } + } + + block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts, onBlockFinalization) if err != nil { return &newPayloadResult{err: err} } + return &newPayloadResult{ block: block, fees: totalFees(block, work.receipts), @@ -287,50 +316,68 @@ func (miner *Miner) prepareWork(genParams *generateParams, witness bool) (*envir log.Error("Failed to create sealing context", "err", err) return nil, err } + mut := make(bal.StateMutations) if header.ParentBeaconRoot != nil { - core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm) + mut.Merge(core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, env.evm)) } if miner.chainConfig.IsPrague(header.Number, header.Time) { - core.ProcessParentBlockHash(header.ParentHash, env.evm) + mut.Merge(core.ProcessParentBlockHash(header.ParentHash, env.evm)) } + env.accessList.AccumulateMutations(mut, 0) return env, nil } // makeEnv creates a new environment for the sealing block. func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, witness bool) (*environment, error) { // Retrieve the parent state to execute on top. - state, err := miner.chain.StateAt(parent.Root) + sdb, err := miner.chain.StateAt(parent.Root) if err != nil { return nil, err } + var accessListBuilder bal.ConstructionBlockAccessList + if miner.chainConfig.IsAmsterdam(header.Number, header.Time) { + accessListBuilder = make(bal.ConstructionBlockAccessList) + sdb = sdb.WithReader(state.NewReaderWithTracker(sdb.Reader())) + } if witness { + if miner.chainConfig.IsAmsterdam(header.Number, header.Time) { + panic("fix this edge case so that the prefetcher invocation below doesn't populate the readset for constructing the BAL") + } bundle, err := stateless.NewWitness(header, miner.chain) if err != nil { return nil, err } - state.StartPrefetcher("miner", bundle, nil) + sdb.StartPrefetcher("miner", bundle, nil) } + // Note the passed coinbase may be different with header.Coinbase. return &environment{ - signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), - state: state, - size: uint64(header.Size()), - coinbase: coinbase, - gasPool: core.NewGasPool(header.GasLimit), - header: header, - witness: state.Witness(), - evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{}), + signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time), + state: sdb, + size: uint64(header.Size()), + coinbase: coinbase, + gasPool: core.NewGasPool(header.GasLimit), + header: header, + witness: sdb.Witness(), + evm: vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), sdb, miner.chainConfig, vm.Config{}), + accessList: accessListBuilder, }, nil } -func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) error { +var ( + errAccessListOversized = errors.New("access list oversized") +) + +func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) (err error) { if tx.Type() == types.BlobTxType { return miner.commitBlobTransaction(env, tx) } + receipt, err := miner.applyTransaction(env, tx) if err != nil { return err } + env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt) env.size += tx.Size() @@ -338,7 +385,7 @@ func (miner *Miner) commitTransaction(env *environment, tx *types.Transaction) e return nil } -func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transaction) error { +func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transaction) (err error) { sc := tx.BlobTxSidecar() if sc == nil { panic("blob transaction without blobs in miner") @@ -372,11 +419,41 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* snap = env.state.Snapshot() gp = env.gasPool.Snapshot() ) - receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx) + var stateCopy *state.StateDB + var prevReader state.Reader + if env.accessList != nil { + prevReader = env.state.Reader() + stateCopy = env.state.WithReader(state.NewReaderWithTracker(env.state.Reader())) + env.evm.StateDB = stateCopy + } + + mutations, receipt, err := core.ApplyTransaction(env.evm, env.gasPool, stateCopy, env.header, tx) if err != nil { - env.state.RevertToSnapshot(snap) + if env.accessList != nil { + // transaction couldn't be applied. reset env state to what it was before + env.state = env.state.WithReader(prevReader) + env.evm.StateDB = env.state + } else { + env.state.RevertToSnapshot(snap) + } env.gasPool.Set(gp) - return nil, err + } + if env.accessList != nil { + al := env.accessList.Copy() + al.AccumulateMutations(mutations, uint16(env.tcount)+1) + al.AccumulateReads(stateCopy.Reader().(state.StateReaderTracker).GetStateAccessList()) + if env.size+tx.Size()+uint64(al.ToEncodingObj().EncodedSize()) >= params.MaxBlockSize-maxBlockSizeBufferZone { + env.gasPool.Set(gp) + + // transaction couldn't be applied. reset env state to what it was before + env.state = env.state.WithReader(prevReader) + env.evm.StateDB = env.state + return nil, errAccessListOversized + } + + env.state = stateCopy.WithReader(prevReader) + env.evm.StateDB = env.state + env.accessList = al } env.header.GasUsed = env.gasPool.Used() return receipt, nil @@ -384,6 +461,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error { isCancun := miner.chainConfig.IsCancun(env.header.Number, env.header.Time) +loop: for { // Check interruption signal and abort building if it's fired. if interrupt != nil { @@ -482,7 +560,12 @@ func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *tran case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account txs.Shift() - + case errors.Is(err, errAccessListOversized): + // Transaction can't be applied because it would cause the block to be oversized due to the + // contribution of the state accesses/modifications it makes. + // terminate the payload construction as it's not guaranteed we will be able to find a transaction + // that can fit in a short amount of time. + break loop default: // Transaction is regarded as invalid, drop all consecutive transactions from // the same sender because of `nonce-too-high` clause. diff --git a/params/config.go b/params/config.go index 197ed56f8a..dec1d70d9b 100644 --- a/params/config.go +++ b/params/config.go @@ -236,6 +236,8 @@ var ( Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, Osaka: DefaultOsakaBlobConfig, + BPO1: DefaultBPO1BlobConfig, + BPO2: DefaultBPO2BlobConfig, }, } @@ -1015,9 +1017,11 @@ func (c *ChainConfig) CheckConfigForkOrder() error { } if cur.timestamp != nil { // If the fork is configured, a blob schedule must be defined for it. - if cur.config == nil { - return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", cur.name) - } + /* + if cur.config == nil { + return fmt.Errorf("invalid chain configuration: missing entry for fork %q in blobSchedule", cur.name) + } + */ } } return nil @@ -1172,6 +1176,9 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { // BlobConfig returns the blob config associated with the provided fork. func (c *ChainConfig) BlobConfig(fork forks.Fork) *BlobConfig { switch fork { + case forks.Amsterdam: + // TODO: (????) + return c.BlobScheduleConfig.BPO2 case forks.BPO5: return c.BlobScheduleConfig.BPO5 case forks.BPO4: @@ -1217,6 +1224,8 @@ func (c *ChainConfig) ActiveSystemContracts(time uint64) map[string]common.Addre // the fork isn't defined or isn't a time-based fork. func (c *ChainConfig) Timestamp(fork forks.Fork) *uint64 { switch { + case fork == forks.Amsterdam: + return c.AmsterdamTime case fork == forks.BPO5: return c.BPO5Time case fork == forks.BPO4: diff --git a/rlp/decode.go b/rlp/decode.go index 19074072fb..d92be332e4 100644 --- a/rlp/decode.go +++ b/rlp/decode.go @@ -25,6 +25,7 @@ import ( "io" "math/big" "reflect" + "runtime/debug" "strings" "sync" @@ -672,6 +673,7 @@ func (s *Stream) ReadBytes(b []byte) error { return nil case String: if uint64(len(b)) != size { + debug.PrintStack() return fmt.Errorf("input value has wrong size %d, want %d", size, len(b)) } if err = s.readFull(b); err != nil { diff --git a/tests/block_test.go b/tests/block_test.go index c718b304b6..ba2e71c499 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -24,6 +24,65 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" ) +func TestBlockchainBAL(t *testing.T) { + bt := new(testMatcher) + + // We are running most of GeneralStatetests to tests witness support, even + // though they are ran as state tests too. Still, the performance tests are + // less about state andmore about EVM number crunching, so skip those. + bt.skipLoad(`^GeneralStateTests/VMTests/vmPerformance`) + + // 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.*`) + + // After the merge we would accept side chains as canonical even if they have lower td + bt.skipLoad(`.*bcMultiChainTest/ChainAtoChainB_difficultyB.json`) + bt.skipLoad(`.*bcMultiChainTest/CallContractFromNotBestBlock.json`) + bt.skipLoad(`.*bcTotalDifficultyTest/uncleBlockAtBlock3afterBlock4.json`) + bt.skipLoad(`.*bcTotalDifficultyTest/lotsOfBranchesOverrideAtTheMiddle.json`) + bt.skipLoad(`.*bcTotalDifficultyTest/sideChainWithMoreTransactions.json`) + bt.skipLoad(`.*bcForkStressTest/ForkStressTest.json`) + bt.skipLoad(`.*bcMultiChainTest/lotsOfLeafs.json`) + bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain.json`) + bt.skipLoad(`.*bcFrontierToHomestead/blockChainFrontierWithLargerTDvsHomesteadBlockchain2.json`) + + // With chain history removal, TDs become unavailable, this transition tests based on TTD are unrunnable + bt.skipLoad(`.*bcArrowGlacierToParis/powToPosBlockRejection.json`) + + // This directory contains no test. + bt.skipLoad(`.*\.meta/.*`) + + bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { + config, ok := Forks[test.json.Network] + if !ok { + t.Fatalf("unsupported fork: %s\n", test.json.Network) + } + gspec := test.genesis(config) + // skip any tests which are not past the cancun fork (selfdestruct removal) + if gspec.Config.CancunTime == nil || *gspec.Config.CancunTime != 0 { + return + } + execBlockTest(t, bt, test, true) + }) + // There is also a LegacyTests folder, containing blockchain tests generated + // prior to Istanbul. However, they are all derived from GeneralStateTests, + // which run natively, so there's no reason to run them here. +} + func TestBlockchain(t *testing.T) { bt := new(testMatcher) @@ -67,17 +126,16 @@ func TestBlockchain(t *testing.T) { bt.skipLoad(`.*\.meta/.*`) bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - execBlockTest(t, bt, test) + execBlockTest(t, bt, test, false) }) // There is also a LegacyTests folder, containing blockchain tests generated // prior to Istanbul. However, they are all derived from GeneralStateTests, // which run natively, so there's no reason to run them here. } -// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests. -func TestExecutionSpecBlocktests(t *testing.T) { - if !common.FileExist(executionSpecBlockchainTestDir) { - t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir) +func testExecutionSpecBlocktests(t *testing.T, testDir string) { + if !common.FileExist(testDir) { + t.Skipf("directory %s does not exist", testDir) } bt := new(testMatcher) @@ -85,12 +143,24 @@ func TestExecutionSpecBlocktests(t *testing.T) { bt.skipLoad(".*prague/eip7251_consolidations/test_system_contract_deployment.json") bt.skipLoad(".*prague/eip7002_el_triggerable_withdrawals/test_system_contract_deployment.json") - bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { - execBlockTest(t, bt, test) + bt.walk(t, testDir, func(t *testing.T, name string, test *BlockTest) { + execBlockTest(t, bt, test, true) }) } -func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { +// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests. +func TestExecutionSpecBlocktests(t *testing.T) { + testExecutionSpecBlocktests(t, executionSpecBlockchainTestDir) +} + +// TestExecutionSpecBlocktestsBAL runs the BAL release test fixtures from execution-spec-tests. +func TestExecutionSpecBlocktestsBAL(t *testing.T) { + testExecutionSpecBlocktests(t, executionSpecBALBlockchainTestDir) +} + +var failures = 0 + +func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest, buildAndVerifyBAL bool) { // Define all the different flag combinations we should run the tests with, // picking only one for short tests. // @@ -106,7 +176,13 @@ func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { } for _, snapshot := range snapshotConf { for _, dbscheme := range dbschemeConf { - if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil { + if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, buildAndVerifyBAL, nil, nil)); err != nil { + failures++ + /* + if failures > 10 { + panic("adsf") + } + */ t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err) return } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index dc680fea14..6d2b3affd9 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -22,11 +22,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - stdmath "math" - "math/big" - "os" - "reflect" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -37,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -44,6 +40,11 @@ import ( "github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/pathdb" + stdmath "math" + "math/big" + "os" + "reflect" + "strings" ) // A BlockTest checks handling of entire blocks. @@ -71,6 +72,7 @@ type btBlock struct { ExpectException string Rlp string UncleHeaders []*btHeader + AccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"` } //go:generate go run github.com/fjl/gencodec -type btHeader -field-override btHeaderMarshaling -out gen_btheader.go @@ -97,6 +99,7 @@ type btHeader struct { BlobGasUsed *uint64 ExcessBlobGas *uint64 ParentBeaconBlockRoot *common.Hash + BlockAccessListHash *common.Hash SlotNumber *uint64 } @@ -113,27 +116,20 @@ type btHeaderMarshaling struct { SlotNumber *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { - config, ok := Forks[t.json.Network] - if !ok { - return UnsupportedForkError{t.json.Network} - } - +func (t *BlockTest) createTestBlockChain(config *params.ChainConfig, snapshotter bool, scheme string, witness, createAndVerifyBAL bool, tracer *tracing.Hooks) (*core.BlockChain, error) { // import pre accounts & construct test genesis block & state root - // Commit genesis state var ( - gspec = t.genesis(config) db = rawdb.NewMemoryDatabase() tconf = &triedb.Config{ Preimages: true, - IsVerkle: gspec.Config.VerkleTime != nil && *gspec.Config.VerkleTime <= gspec.Timestamp, } ) - if scheme == rawdb.PathScheme || tconf.IsVerkle { + if scheme == rawdb.PathScheme { tconf.PathDB = pathdb.Defaults } else { tconf.HashDB = hashdb.Defaults } + gspec := t.genesis(config) // if ttd is not specified, set an arbitrary huge value if gspec.Config.TerminalTotalDifficulty == nil { @@ -142,15 +138,15 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t triedb := triedb.NewDatabase(db, tconf) gblock, err := gspec.Commit(db, triedb, nil) if err != nil { - return err + return nil, err } triedb.Close() // close the db to prevent memory leak if gblock.Hash() != t.json.Genesis.Hash { - return fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) + return nil, fmt.Errorf("genesis block hash doesn't match test: computed=%x, test=%x", gblock.Hash().Bytes()[:6], t.json.Genesis.Hash[:6]) } if gblock.Root() != t.json.Genesis.StateRoot { - return fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6]) + return nil, fmt.Errorf("genesis block state root does not match test: computed=%x, test=%x", gblock.Root().Bytes()[:6], t.json.Genesis.StateRoot[:6]) } // Wrap the original engine within the beacon-engine engine := beacon.New(ethash.NewFaker()) @@ -164,12 +160,27 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t Tracer: tracer, StatelessSelfValidation: witness, }, + NoPrefetch: true, } if snapshotter { options.SnapshotLimit = 1 options.SnapshotWait = true } chain, err := core.NewBlockChain(db, gspec, engine, options) + if err != nil { + return nil, err + } + return chain, nil +} + +func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, createAndVerifyBAL bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { + config, ok := Forks[t.json.Network] + if !ok { + return UnsupportedForkError{t.json.Network} + } + // import pre accounts & construct test genesis block & state root + + chain, err := t.createTestBlockChain(config, snapshotter, scheme, witness, createAndVerifyBAL, tracer) if err != nil { return err } @@ -203,7 +214,50 @@ func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *t } } } - return t.validateImportedHeaders(chain, validBlocks) + err = t.validateImportedHeaders(chain, validBlocks) + if err != nil { + return err + } + + if createAndVerifyBAL { + newChain, _ := t.createTestBlockChain(config, snapshotter, scheme, witness, createAndVerifyBAL, tracer) + defer newChain.Stop() + + var blocksWithBAL types.Blocks + for i := uint64(1); i <= chain.CurrentBlock().Number.Uint64(); i++ { + block := chain.GetBlockByNumber(i) + if chain.Config().IsAmsterdam(block.Number(), block.Time()) && block.AccessList() == nil { + return fmt.Errorf("block %d missing BAL", block.NumberU64()) + } + blocksWithBAL = append(blocksWithBAL, block) + } + + amt, err := newChain.InsertChain(blocksWithBAL) + if err != nil { + return err + } + _ = amt + newDB, err := newChain.State() + if err != nil { + return err + } + if err = t.validatePostState(newDB); err != nil { + return fmt.Errorf("post state validation failed: %v", err) + } + // Cross-check the snapshot-to-hash against the trie hash + if snapshotter { + if newChain.Snapshots() != nil { + if err := chain.Snapshots().Verify(chain.CurrentBlock().Root); err != nil { + return err + } + } + } + err = t.validateImportedHeaders(newChain, validBlocks) + if err != nil { + return err + } + } + return nil } // Network returns the network/fork name for this test. @@ -213,20 +267,21 @@ func (t *BlockTest) Network() string { func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis { return &core.Genesis{ - Config: config, - Nonce: t.json.Genesis.Nonce.Uint64(), - Timestamp: t.json.Genesis.Timestamp, - ParentHash: t.json.Genesis.ParentHash, - ExtraData: t.json.Genesis.ExtraData, - GasLimit: t.json.Genesis.GasLimit, - GasUsed: t.json.Genesis.GasUsed, - Difficulty: t.json.Genesis.Difficulty, - Mixhash: t.json.Genesis.MixHash, - Coinbase: t.json.Genesis.Coinbase, - Alloc: t.json.Pre, - BaseFee: t.json.Genesis.BaseFeePerGas, - BlobGasUsed: t.json.Genesis.BlobGasUsed, - ExcessBlobGas: t.json.Genesis.ExcessBlobGas, + Config: config, + Nonce: t.json.Genesis.Nonce.Uint64(), + Timestamp: t.json.Genesis.Timestamp, + ParentHash: t.json.Genesis.ParentHash, + ExtraData: t.json.Genesis.ExtraData, + GasLimit: t.json.Genesis.GasLimit, + GasUsed: t.json.Genesis.GasUsed, + Difficulty: t.json.Genesis.Difficulty, + Mixhash: t.json.Genesis.MixHash, + Coinbase: t.json.Genesis.Coinbase, + Alloc: t.json.Pre, + BaseFee: t.json.Genesis.BaseFeePerGas, + BlobGasUsed: t.json.Genesis.BlobGasUsed, + ExcessBlobGas: t.json.Genesis.ExcessBlobGas, + BlockAccessListHash: t.json.Genesis.BlockAccessListHash, } } @@ -256,6 +311,16 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) return nil, fmt.Errorf("block RLP decoding failed when expected to succeed: %v", err) } } + + // check that if we encode the same block, it will result in the same RLP + var enc bytes.Buffer + if err := rlp.Encode(&enc, cb); err != nil { + return nil, err + } + expected := common.Hex2Bytes(strings.TrimLeft(b.Rlp, "0x")) + if !bytes.Equal(enc.Bytes(), expected) { + return nil, fmt.Errorf("mismatch. expected\n%s\ngot\n%x\n", expected, enc.Bytes()) + } // RLP decoding worked, try to insert into chain: blocks := types.Blocks{cb} i, err := blockchain.InsertChain(blocks) @@ -268,7 +333,7 @@ func (t *BlockTest) insertBlocks(blockchain *core.BlockChain) ([]btBlock, error) } if b.BlockHeader == nil { if data, err := json.MarshalIndent(cb.Header(), "", " "); err == nil { - fmt.Fprintf(os.Stdout, "block (index %d) insertion should have failed due to: %v:\n%v\n", + fmt.Fprintf(os.Stderr, "block (index %d) insertion should have failed due to: %v:\n%v\n", bi, b.ExpectException, string(data)) } return nil, fmt.Errorf("block (index %d) insertion should have failed due to: %v", diff --git a/tests/gen_btheader.go b/tests/gen_btheader.go index eb6d9a8271..fcab0b0681 100644 --- a/tests/gen_btheader.go +++ b/tests/gen_btheader.go @@ -38,6 +38,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) { BlobGasUsed *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64 ParentBeaconBlockRoot *common.Hash + BlockAccessListHash *common.Hash SlotNumber *math.HexOrDecimal64 } var enc btHeader @@ -62,6 +63,7 @@ func (b btHeader) MarshalJSON() ([]byte, error) { enc.BlobGasUsed = (*math.HexOrDecimal64)(b.BlobGasUsed) enc.ExcessBlobGas = (*math.HexOrDecimal64)(b.ExcessBlobGas) enc.ParentBeaconBlockRoot = b.ParentBeaconBlockRoot + enc.BlockAccessListHash = b.BlockAccessListHash enc.SlotNumber = (*math.HexOrDecimal64)(b.SlotNumber) return json.Marshal(&enc) } @@ -90,6 +92,7 @@ func (b *btHeader) UnmarshalJSON(input []byte) error { BlobGasUsed *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64 ParentBeaconBlockRoot *common.Hash + BlockAccessListHash *common.Hash SlotNumber *math.HexOrDecimal64 } var dec btHeader @@ -159,6 +162,9 @@ func (b *btHeader) UnmarshalJSON(input []byte) error { if dec.ParentBeaconBlockRoot != nil { b.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot } + if dec.BlockAccessListHash != nil { + b.BlockAccessListHash = dec.BlockAccessListHash + } if dec.SlotNumber != nil { b.SlotNumber = (*uint64)(dec.SlotNumber) } diff --git a/tests/init.go b/tests/init.go index d10b47986c..117410b7a8 100644 --- a/tests/init.go +++ b/tests/init.go @@ -493,6 +493,70 @@ var Forks = map[string]*params.ChainConfig{ BPO1: bpo1BlobConfig, }, }, + "Amsterdam": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + AmsterdamTime: u64(0), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, + }, + }, + "BPO2ToAmsterdamAtTime15k": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + BerlinBlock: big.NewInt(0), + LondonBlock: big.NewInt(0), + ArrowGlacierBlock: big.NewInt(0), + MergeNetsplitBlock: big.NewInt(0), + TerminalTotalDifficulty: big.NewInt(0), + ShanghaiTime: u64(0), + CancunTime: u64(0), + PragueTime: u64(0), + OsakaTime: u64(0), + BPO1Time: u64(0), + BPO2Time: u64(0), + AmsterdamTime: u64(15_000), + DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, + BlobScheduleConfig: ¶ms.BlobScheduleConfig{ + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + Osaka: params.DefaultOsakaBlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, + }, + }, "OsakaToBPO1AtTime15k": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), diff --git a/tests/init_test.go b/tests/init_test.go index b933c9808c..2f2e4101f8 100644 --- a/tests/init_test.go +++ b/tests/init_test.go @@ -34,17 +34,18 @@ import ( ) var ( - baseDir = filepath.Join(".", "testdata") - blockTestDir = filepath.Join(baseDir, "BlockchainTests") - stateTestDir = filepath.Join(baseDir, "GeneralStateTests") - legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests") - transactionTestDir = filepath.Join(baseDir, "TransactionTests") - rlpTestDir = filepath.Join(baseDir, "RLPTests") - difficultyTestDir = filepath.Join(baseDir, "BasicTests") - executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests") - executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests") - executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests") - benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") + baseDir = filepath.Join(".", "testdata") + blockTestDir = filepath.Join(baseDir, "BlockchainTests") + stateTestDir = filepath.Join(baseDir, "GeneralStateTests") + legacyStateTestDir = filepath.Join(baseDir, "LegacyTests", "Constantinople", "GeneralStateTests") + transactionTestDir = filepath.Join(baseDir, "TransactionTests") + rlpTestDir = filepath.Join(baseDir, "RLPTests") + difficultyTestDir = filepath.Join(baseDir, "BasicTests") + executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests") + executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests") + executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests") + benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") + executionSpecBALBlockchainTestDir = filepath.Join(".", "spec-tests-bal", "fixtures", "blockchain_tests") ) func readJSON(reader io.Reader, value interface{}) error { diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index a509c471b8..cb31d783cf 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -239,6 +239,14 @@ func (t *BinaryTrie) GetStorage(addr common.Address, key []byte) ([]byte, error) return t.root.Get(GetBinaryTreeKeyStorageSlot(addr, key), t.nodeResolver) } +func (t *BinaryTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error { + panic("not implemented") +} + +func (t *BinaryTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error { + panic("not implemented") +} + // UpdateAccount updates the account information for the given address. func (t *BinaryTrie) UpdateAccount(addr common.Address, acc *types.StateAccount, codeLen int) error { var ( diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 1f150ede8c..356c07dcc2 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -210,6 +210,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { return nil } +// UpdateStorageBatch attempts to update a list storages in the batch manner. +func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error { + var ( + hkeys = make([][]byte, 0, len(keys)) + evals = make([][]byte, 0, len(values)) + ) + for _, key := range keys { + hk := crypto.Keccak256(key) + if t.preimages != nil { + t.secKeyCache[common.Hash(hk)] = key + } + hkeys = append(hkeys, hk) + } + for _, val := range values { + data, err := rlp.EncodeToBytes(val) + if err != nil { + return err + } + evals = append(evals, data) + } + return t.trie.UpdateBatch(hkeys, evals) +} + // UpdateAccount will abstract the write of an account to the secure trie. func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error { hk := crypto.Keccak256(address.Bytes()) @@ -226,6 +249,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun return nil } +// UpdateAccountBatch attempts to update a list accounts in the batch manner. +func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error { + var ( + hkeys = make([][]byte, 0, len(addresses)) + values = make([][]byte, 0, len(accounts)) + ) + for _, addr := range addresses { + hk := crypto.Keccak256(addr.Bytes()) + if t.preimages != nil { + t.secKeyCache[common.Hash(hk)] = addr.Bytes() + } + hkeys = append(hkeys, hk) + } + for _, acc := range accounts { + data, err := rlp.EncodeToBytes(acc) + if err != nil { + return err + } + values = append(values, data) + } + return t.trie.UpdateBatch(hkeys, values) +} + func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { return nil } diff --git a/trie/tracer.go b/trie/tracer.go index 04122d1384..042fa468bf 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -33,12 +33,10 @@ import ( // while the latter is inserted/deleted in order to follow the rule of trie. // This tool can track all of them no matter the node is embedded in its // parent or not, but valueNode is never tracked. -// -// Note opTracer is not thread-safe, callers should be responsible for handling -// the concurrency issues by themselves. type opTracer struct { inserts map[string]struct{} deletes map[string]struct{} + lock sync.RWMutex } // newOpTracer initializes the tracer for capturing trie changes. @@ -53,6 +51,9 @@ func newOpTracer() *opTracer { // in the deletion set (resurrected node), then just wipe it from // the deletion set as it's "untouched". func (t *opTracer) onInsert(path []byte) { + t.lock.Lock() + defer t.lock.Unlock() + if _, present := t.deletes[string(path)]; present { delete(t.deletes, string(path)) return @@ -64,6 +65,9 @@ func (t *opTracer) onInsert(path []byte) { // in the addition set, then just wipe it from the addition set // as it's untouched. func (t *opTracer) onDelete(path []byte) { + t.lock.Lock() + defer t.lock.Unlock() + if _, present := t.inserts[string(path)]; present { delete(t.inserts, string(path)) return @@ -73,12 +77,18 @@ func (t *opTracer) onDelete(path []byte) { // reset clears the content tracked by tracer. func (t *opTracer) reset() { + t.lock.Lock() + defer t.lock.Unlock() + clear(t.inserts) clear(t.deletes) } // copy returns a deep copied tracer instance. func (t *opTracer) copy() *opTracer { + t.lock.RLock() + defer t.lock.RUnlock() + return &opTracer{ inserts: maps.Clone(t.inserts), deletes: maps.Clone(t.deletes), @@ -87,6 +97,9 @@ func (t *opTracer) copy() *opTracer { // deletedList returns a list of node paths which are deleted from the trie. func (t *opTracer) deletedList() [][]byte { + t.lock.RLock() + defer t.lock.RUnlock() + paths := make([][]byte, 0, len(t.deletes)) for path := range t.deletes { paths = append(paths, []byte(path)) diff --git a/trie/transitiontrie/transition.go b/trie/transitiontrie/transition.go index 4c73022082..4969159585 100644 --- a/trie/transitiontrie/transition.go +++ b/trie/transitiontrie/transition.go @@ -49,6 +49,14 @@ func NewTransitionTrie(base *trie.SecureTrie, overlay *bintrie.BinaryTrie, st bo } } +func (t *TransitionTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error { + panic("not implemented") +} + +func (t *TransitionTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error { + panic("not implemented") +} + // Base returns the base trie. func (t *TransitionTrie) Base() *trie.SecureTrie { return t.base diff --git a/trie/trie.go b/trie/trie.go index 1ef2c2f1a6..1f117e1e42 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -480,6 +480,72 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error } } +// UpdateBatch updates a batch of entries concurrently. +func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error { + // Short circuit if the trie is already committed and unusable. + if t.committed { + return ErrCommitted + } + if len(keys) != len(values) { + return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values)) + } + // Insert the entries sequentially if there are not too many + // trie nodes in the trie. + fn, ok := t.root.(*fullNode) + + if !ok || len(keys) < 4 { // TODO(rjl493456442) the parallelism threshold should be twisted + for i, key := range keys { + err := t.Update(key, values[i]) + if err != nil { + return err + } + } + return nil + } + var ( + ikeys = make(map[byte][][]byte) + ivals = make(map[byte][][]byte) + eg errgroup.Group + ) + for i, key := range keys { + hkey := keybytesToHex(key) + ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey) + ivals[hkey[0]] = append(ivals[hkey[0]], values[i]) + } + if len(keys) > 0 { + fn.flags = t.newFlag() + } + for p, k := range ikeys { + pos := p + ks := k + eg.Go(func() error { + vs := ivals[pos] + for i, k := range ks { + if len(vs[i]) != 0 { + _, n, err := t.insert(fn.Children[pos], []byte{pos}, k[1:], valueNode(vs[i])) + if err != nil { + return err + } + fn.Children[pos] = n + } else { + _, n, err := t.delete(fn.Children[pos], []byte{pos}, k[1:]) + if err != nil { + return err + } + fn.Children[pos] = n + } + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return err + } + t.unhashed += len(keys) + t.uncommitted += len(keys) + return nil +} + // MustDelete is a wrapper of Delete and will omit any encountered error but // just print out an error message. func (t *Trie) MustDelete(key []byte) { diff --git a/trie/trie_test.go b/trie/trie_test.go index 3423cde59c..7c0969568f 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -1500,82 +1500,56 @@ func testTrieCopyNewTrie(t *testing.T, entries []kv) { } } -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// cpu: Apple M1 Pro -// BenchmarkTriePrefetch -// BenchmarkTriePrefetch-8 9961 100706 ns/op -func BenchmarkTriePrefetch(b *testing.B) { - db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) - tr := NewEmpty(db) - vals := make(map[string]*kv) - for i := 0; i < 3000; i++ { - value := &kv{ - k: randBytes(32), - v: randBytes(20), - t: false, - } - tr.MustUpdate(value.k, value.v) - vals[string(value.k)] = value - } - root, nodes := tr.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) - b.ResetTimer() +func TestUpdateBatch(t *testing.T) { + testUpdateBatch(t, []kv{ + {k: []byte("do"), v: []byte("verb")}, + {k: []byte("ether"), v: []byte("wookiedoo")}, + {k: []byte("horse"), v: []byte("stallion")}, + {k: []byte("shaman"), v: []byte("horse")}, + {k: []byte("doge"), v: []byte("coin")}, + {k: []byte("dog"), v: []byte("puppy")}, + }) - for i := 0; i < b.N; i++ { - tr, err := New(TrieID(root), db) - if err != nil { - b.Fatalf("Failed to open the trie") - } - var keys [][]byte - for k := range vals { - keys = append(keys, []byte(k)) - if len(keys) > 64 { - break - } - } - tr.Prefetch(keys) + var entries []kv + for i := 0; i < 256; i++ { + entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)}) } + testUpdateBatch(t, entries) } -// goos: darwin -// goarch: arm64 -// pkg: github.com/ethereum/go-ethereum/trie -// cpu: Apple M1 Pro -// BenchmarkTrieSeqPrefetch -// BenchmarkTrieSeqPrefetch-8 12879 96710 ns/op -func BenchmarkTrieSeqPrefetch(b *testing.B) { - db := newTestDatabase(rawdb.NewMemoryDatabase(), rawdb.HashScheme) - tr := NewEmpty(db) - vals := make(map[string]*kv) - for i := 0; i < 3000; i++ { - value := &kv{ - k: randBytes(32), - v: randBytes(20), - t: false, - } - tr.MustUpdate(value.k, value.v) - vals[string(value.k)] = value +func testUpdateBatch(t *testing.T, entries []kv) { + var ( + base = NewEmpty(nil) + keys [][]byte + vals [][]byte + ) + for _, entry := range entries { + base.Update(entry.k, entry.v) + keys = append(keys, entry.k) + vals = append(vals, entry.v) + } + for i := 0; i < 10; i++ { + k, v := testrand.Bytes(32), testrand.Bytes(32) + base.Update(k, v) + keys = append(keys, k) + vals = append(vals, v) } - root, nodes := tr.Commit(false) - db.Update(root, types.EmptyRootHash, trienode.NewWithNodeSet(nodes)) - b.ResetTimer() - for i := 0; i < b.N; i++ { - tr, err := New(TrieID(root), db) - if err != nil { - b.Fatalf("Failed to open the trie") - } - var keys [][]byte - for k := range vals { - keys = append(keys, []byte(k)) - if len(keys) > 64 { - break - } - } - for _, k := range keys { - tr.Get(k) + cmp := NewEmpty(nil) + if err := cmp.UpdateBatch(keys, vals); err != nil { + t.Fatalf("Failed to update batch, %v", err) + } + + // Traverse the original tree, the changes made on the copy one shouldn't + // affect the old one + for _, key := range keys { + v1, _ := base.Get(key) + v2, _ := cmp.Get(key) + if !bytes.Equal(v1, v2) { + t.Errorf("Unexpected data, key: %v, want: %v, got: %v", key, v1, v2) } } + if base.Hash() != cmp.Hash() { + t.Errorf("Hash mismatch: want %x, got %x", base.Hash(), cmp.Hash()) + } } diff --git a/triedb/database.go b/triedb/database.go index e7e47bb91a..db652b2f2b 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -18,7 +18,6 @@ package triedb import ( "errors" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb"